One of the interesting things about the Raspberry Pi Pico is that it has two cores, which will allow us to perform multithreaded executions. If you don’t know what multithreaded execution is, I’ll tell you what your computer does and what allows you to have several programs open at the same time. Obviously, the Raspberry Pi Pico does not have an operating system that manages the CPU cycles and therefore will only allow one thread per core, which in this case is two.
To begin with, I will tell you not to be scared, because the operation is quite simple and it will serve you for many things. To take a rough example, you can have a core reading a sensor continuously, and in parallel the other acting depending on the data received by that sensor. I know this example is simple and you would waste the power of the Raspberry Pi Pico, but it is an easy example 😛
Execute function in another core
To perform multithreading, the first thing you have to do is create the function that will be executed in the other core. In the following example you can easily see what it would be like.
import machine import utime import _thread # We configure the pin of the internal led as an output and # we assign to internal_led internal_led = machine.Pin(25, machine.Pin.OUT) # Function that will block the thread with a while loop # which will simply display a message every second def second_thread(): while True: print("Hello, I'm here in the second thread writting every second") utime.sleep(1) # Function that initializes execution in the second core # The second argument is a list or dictionary with the arguments # that will be passed to the function. _thread.start_new_thread(second_thread, ()) # Second loop that will block the main thread, and what it will do # that the internal led blinks every half second while True: internal_led.toggle() utime.sleep(0.25)
As you can see, we have created a function called second_thread, which is a while loop that blocks execution forever. This function in an Arduino would cause it to stop execution and keep displaying the message every second forever. However, since we have two cores in the Raspberry Pi Pico, we can derive this function to the second core with the _thread.start_new_thread function, and continue with the execution of the program. The final part will make the internal led blink twice per second at the same time as the other thread displays a message every second.
You have to bear in mind that as I mentioned above, the microcontroller has only two cores, so you cannot execute the _thread.start_new_thread function again until the first one finishes. In the previous example the execution is infinite, so if we try to launch the function a second time, we will receive an error:
To prevent this from happening to us during the execution because the executions overlap (because one of them has taken too long, for example), we will use the semaphores, which we will talk about in the next section.
Semaphores to control multithreaded execution
As we saw above, multithreaded execution can cause your microcontroller to crash if you are not careful not to run more than one thread at the same time, which is obviously undesirable. To avoid this, the traffic lights come to the rescue. The semaphores are objects, which allow us to stop the execution of the code until the semaphore is released. This will help us to have control over multithreading.
To give an easy example, we will take the one above and add some traffic lights:
import machine import utime import _thread # We configure the pin of the internal led as an output and # we assign to internal_led internal_led = machine.Pin(25, machine.Pin.OUT) # We create a semaphore (A.K.A lock) baton = _thread.allocate_lock() # Function that will block the thread with a while loop # which will simply display a message every second def second_thread(): while True: # We acquire the traffic light lock baton.acquire() print("Hello, I'm here in the second thread writting every second") utime.sleep(1) # We release the traffic light lock baton.release() # Function that initializes execution in the second core # The second argument is a list or dictionary with the arguments # that will be passed to the function. _thread.start_new_thread(second_thread, ()) # Second loop that will block the main thread, and what it will do # that the internal led blinks every half second while True: # We acquire the semaphore lock baton.acquire() internal_led.toggle() utime.sleep(0.25) # We release the semaphore lock baton.release()
As you can see in the code, we have created a semaphore and we have assigned it to the variable baton. Inside the second_thread function we acquire the lock and we do not release it until said function ends. In the main thread we also try to acquire the lock and this makes it wait until it is free. Once it is free, it continues with the execution of the code.
This will cause the led not to blink in parallel, ending the grace of multithreading, but hey, it’s an example :P. It actually opens up several possibilities for you, because with baton.locked() you can check if the semaphore is locked without waiting. Therefore you can only launch processes in the second core when it is free, without blocking the main one.
For more information about how _thread works, I recommend you visit the official page:
Although the truth is not that it has much more lol.
As always, I hope you liked it and that it helps you, and do not hesitate to comment on any questions or comments you may have. All the best!