Hello readers, I hope your all are doing great. We know that a Raspberry Pi Pico module comes with multiple inbuilt features for example onboard memory, processing units, GPIOs or General Purpose Input Outputs (used to control and receive inputs from various electronic peripherals) etc.
In our previous tutorials, we discussed how to access GPIO pins of the Raspberry Pi Pico module for both input as well as output operations.
In this tutorial, we are going to discuss another important feature of the Raspberry Pi Pico module (RP2040) which is Dual Core Processor. The Pico board features with 133MHz ARM Cortex-M0+, Dual Core Processor. This dual-core feature makes the Pico module capable of multiple thread execution or multithreading.
Now before writing the MicroPython program let’s first understand the concept of the dual-core processor in the Raspberry Pi Pico module.
Fig. 1 raspberry Pi Pico dual-core programming
Raspberry Pi Pico Dual Core
A core is the basic unit of any processor which is responsible for executing program instructions. A multi core processor comes with the features of executing multiple tasks at a time. Multithreading is the ability of a processing unit to provide multiple threads of execution simultaneously (operating system supported). In multithreading, threads share their resources with each other. So this dual-core processor feature results in increased processing speed.
Raspberry Pi Pico (RP2040) module is having two processing cores, Core0 and Core1. In the default mode of Raspberry Pi Pico program execution, Core_0 executes all the tasks and Core1 remains idle or on standby mode. Using both the cores of RP2040 provides us with two threads of execution and hence a more powerful project with better processing speed.
Both core0 and core1 execute their assigned tasks independent of each other while sharing all the resources like memory space and program code with each other. Sharing the memory location between two cores can create race conditions and hence can cause trouble when mutual memory accessing is not assured. On the other hand, sharing program code with each other (core0 and core1) may sound troublesome but practically it is not. The reason is fetching code is a read instruction that does not create a race condition.
Core0 and Core1 communication
Fig. 2 Core_0 and Core_1 communication
As we mentioned above, sharing memory space with two cores simultaneously can cause race conditions. So, to make the cores to communicate with each other the Raspberry Pi Pico module is featured with two individual ‘First In First Out’ (FIFO) structures. Each core can access only one FIFO structure so both core have their own FIFO structure to write codes which helps in avoiding race condition or writing to the same memory location simultaneously.
You can follow the given link for detailed study on Raspberry Pi Pico: https://www.theengineeringprojects.com/2022/04/getting-started-with-raspberry-pi-pico.html
Software and Hardware components required
- Raspberry Pi Pico development board
- Thonny IDE
- Data cable
- Connecting wires
- Breadboard
- LEDs
Programming
The development environment we are using is Thonny IDE, to program the Raspberry Pi Pico board for accessing the dual core feature with MicroPython programming language.
So, before writing the MicroPython program user need to install the respective development environment.
We already published a tutorial on how to install and access Thonny IDE for Raspberry Pi Pico programming using MicroPython programming language. You can find the details at the given link address: https://www.theengineeringprojects.com/2022/04/installing-thonny-ide-for-raspberry-pi-pico-programming.html
Now let’ write a MicroPython program with Thonny IDE to access raspberry Pi Pico’s both cores:
In this example code we using just a simple “print()” commands to print the messages from each core for testing purpose.
- The first task while writing a code is importing necessary libraries. Here we are importing three libraries, “machine”, “utime” and “_thread”.
- utime library provides the access to internal clock of raspberry Pi Pico module which is further used to add delay when required.
- _thread library is responsible for implementing the threading function provided by “Raspberry Pi Pico” community.
- As we are using a dual core processor and accessing both the cores simultaneously means we are using two threads (one thread from each core). This _thread module allows a user to work with multiple threads and also allow them to share global data space.
- For more details on _thread module follow the given link: https://docs.python.org/3.5/library/_thread.html#module-_thread
Fig. 3 Importing necessary libraries
- Semaphores or Simple locks are provided with “_thread” module which is responsible for synchronization between multiple threads. The allocate_lock() function provided with _thread module is responsible for returning a new lock object.
- Here we are declaring a “spLock”
Fig. 4 declaring thread lock object
- Next, we are creating a function where we are assigning the task to core_1.
- The “spLock.acquire()” function “acquires the thread lock without any optional argument”. The reason for using the this “acquire()” function is to make sure that only one thread of execution is acquiring the lock at a time until the lock is released for other thread. Here we using the “print()” command as a task assigned or to be executed by core_1 along with a delay of 0.5sec or 500us.
- Next, is the release() command, which is used to release the lock acquired earlier. So that other threads can execute their respective tasks.
- The task assigned to core_1 will be executed continuously because it is written in “while loop”.
Fig. 5 Task for Core_1
- The function core1_task() defined earlier which is containing the task assigned to core_1 will be passed as an argument inside start_new_thread() function. As per the official documentation from python.org, this function is responsible for “starting a new thread and returning its identifier”. The thread will execute the assigned function (as an argument). The tread will automatically exit when the function returns. We can even assign more than one argument in this “-thread.start_new_thread()” function.
Fig. 6 Core_1 thread
- As we mentioned earlier, Raspberry Pi Pico uses core_0 as default a core for processing purpose. So here we do no need to use the _thread.start_new_thread() function to start a new thread for Core_0. Core_0 will automatically start a thread for code execution. But we still need to call the acquire(a lock) and then release functions, just to ensure only thread is acquiring the lock at a time. Here again we are using the print() command to print a message from core_0 like we did in case of Core_1.
Fig. 7 Task for default core (core_0)
Code
The MicroPython code with thonny IDE for Raspberry Pi Pico is written below:
import machine
import utime # access internal clock of raspberry Pi Pico
import _thread # to access threading function
spLock = _thread.allocate_lock() # creating semaphore
def core1_task():
while True:
spLock.acquire() # acquiring semaphore lock
print( "message from core_1")
utime.sleep(0.5) # 0.5 sec or 500us delay
spLock.release()
_thread.start_new_thread(core1_task, ())
while True:
spLock.acquire()
print( "message from Core_0 ")
utime.sleep( 0.5)
spLock.release()
- Copy the above code and paste in your Thonny IDE window.
- Save the code either on Raspberry Pi Pico or your computer and once it is successfully saved click on the ‘Run’ icon to run the code.
- Now go to Shell section to check the result. If the Shell is not enabled then go to View >> Shell and check ‘Shell’.
- Now in the shell section you should see the message printed from both the cores.
Fig. 8 Enabling Shell
Result
The result obtained from the above code is attached below. Where we can see the messages received or executed by both the cores as per the instructions provided in the micropython code.
Fig. 9 output printed on shell
Let’s take another example where we will interface some peripheral LEDs and will toggle those LEDs using different threads of execution or cores.
- Most of the instructions are similar to the previous example with some additional ones. Here we are importing one more library ‘Pin’ (along with the previous ones) to access the GPIO pins of raspberry Pi Pico module.
Fig.10 importing necessary libraries
- We are declaring two led objects for each LED. Using Pin() function the GPIO pins 14, 15 are assigned as output pins for LED interfacing.
- We already published a tutorial on LED interfacing with Raspberry Pi Pico, you can check the tutorial at: https://www.theengineeringprojects.com/2022/04/led-blinking-with-raspberry-pi-pico-and-micropython.html
Fig. 11 declaring led objects
- Next task is assigning the task for each core. Core_1 is assigned to make the led_1 (GPIO_14) to toggle along with printing the given message.
Fig. 12 Toggling LED with core_1
- Core_0, which is the default one is assigned to make the led_0 (i.e., GPIO_15) toggle with a delay of 0.5sec. Rest of the programming instructions are similar to the previous example code so we are not explaining them again.
Fig. 13 toggling LED with core_0
Code
from machine import Pin
import utime # access internal clock of raspberry Pi Pico
import _thread # to access threading function
# declaring led object
led_0 = Pin( 14, Pin.OUT ) # led object for core_0
led_1 = Pin( 15, Pin.OUT ) # led object for core_1
spLock = _thread.allocate_lock() # creating semaphore lock
def core1_task():
while True:
spLock.acquire() # acquiring semaphore lock
print( " message from core_1" )
led_1.value( 1)
utime.sleep( 0.5) # 0.5 sec or 500us delay
led_1.value(0)
spLock.release()
_thread.start_new_thread( core1_task, () )
while True:
spLock.acquire()
print( "message from Core_0" )
led_0.value(1)
utime.sleep( 0.5)
led_0.value(0)
spLock.release()
Result
In the results attached below we can that two LEDs are attached with raspberry Pi Pico boar. Green (GPIO14) and Red (GPIO15) LEDs represent the output of Core_1 and Core_0 respectively.
Fig. 14 Core_1 output (GPIO 14)
Fig. 15 Core_0 Output (GPIO15)
Conclusion
In this tutorial, we learned how to access both the cores of the raspberry pi Pico module and to execute task or control peripherals with individual cores. We also learned the concept of threading and multithreading.
This concludes the tutorial. I hope you found this of some help and also hope to see you soon with a new tutorial on raspberry Pi Pico programming.