Using FreeRTOS With The Raspberry Pi Pico
Using FreeRTOS With The Raspberry Pi Pico
BLOG
First o , what is FreeRTOS? As the name suggests, FreeRTOS is a real-time operating system (RTOS) that is
free to use. It is entirely open source with a permissive MIT license, and is intended for embedded applications
that run on microcontrollers such as the one on the Raspberry Pi Pico, and small microprocessors. FreeRTOS
has been available for over 18 years with a strong community of partners and embedded developers utilizing it
for their products and services.
The Raspberry Pi Pico is a small development board built around the RP2040 microcontroller, which was
designed by Raspberry Pi. The RP2040 is a dual-core Arm Cortex-M0+ processor running up to 133 MHz with
264kB on-chip SRAM. The Pico board has 2MB ash with 26 multifunctional GPIO pins. These hardware specs
give us a range of capabilities for creating embedded applications with FreeRTOS, including the dual-core
processor. Raspberry Pi also provides a C/C++ SDK for the Pico, which we will be using later on.
So, why do we need an RTOS such as FreeRTOS for a microcontroller-based board like the Raspberry Pi Pico?
The short answer is, technically, you do not need an RTOS in all cases. Some embedded applications can be
really simple such as reading values from a sensor and sending those values to an output. An RTOS is not
required in this case and you can nd examples illustrating that. However, as microcontrollers have become
more capable devices and embedded applications have become increasingly more complex to take advantage
of the hardware, the bene ts of using an RTOS have grown.
Multitasking is one of the important reasons for using an RTOS, and is a key feature of FreeRTOS. What is
multitasking? It is a way to run multiple tasks in your embedded application so that those tasks can execute
independently of one another while still sharing the same processor. Task execution is managed by what is
known as a scheduler. In the case of a single-core microcontroller, it is the job of the scheduler to give tasks
execution time based on speci ed priorities and other factors. This can give the impression that multiple tasks
are executing simultaneously even though the scheduler is actually managing execution by switching between
tasks in quick succession at the millisecond level.
With the sample code shown later in this blog, we demonstrate how to create and run tasks with FreeRTOS on
the Raspberry Pi Pico. First, however, we will cover how to setup a development environment.
There are di erent ways to setup a development environment for FreeRTOS and the Raspberry Pi Pico. This
particular setup is agnostic to a speci c editor or IDE, and it aims to be simple and exible. The essential tools
needed are: Git, CMake, Make, and the GNU Arm Embedded Toolchain. Below are installation instructions for
Windows, Linux, and MacOS.
Windows
Install Git (includes Bash terminal): https://git-scm.com/downloads
Install CMake: https://cmake.org/download/
Install the Arm GNU Toolchain: https://developer.arm.com/Tools%20and%20Software/GNU%20Toolchain
Install Make using Chocolatey
Chocolatey should already be installed from the Git install, otherwise install it: https://chocolatey.org/
Run this from Git Bash: $ choco install make
Linux
All the tools we need, including Git and Make, should be available after installing these packages:
MacOS
For MacOS, ensure that Homebrew is installed and then follow these steps in the Terminal:
Once you have completed the OS-speci c setup, launch your terminal (Git Bash on Windows) and complete the
steps below to setup the Blink project from the command line:
$ mkdir freertos-pico
$ cd freertos-pico
$ export PICO_SDK_PATH=$PWD/pico-sdk
$ export FREERTOS_KERNEL_PATH=$PWD/FreeRTOS-Kernel
$ mkdir blink
$ cd blink
Now we are ready for the code. Below is a simple FreeRTOS application written in C that you can run on the
Raspberry Pi Pico. It creates a task to blink the LED on the board. Create a le in the ‘blink’ directory and name
it ‘main.c’ using any code editor you wish. Notepad++, vi, Sublime Text, Atom, and VSCode are some examples.
#include "pico/stdlib.h"
#include "FreeRTOS.h"
#include "task.h"
void vBlinkTask() {
for (;;) {
gpio_put(PICO_DEFAULT_LED_PIN, 1);
vTaskDelay(250);
gpio_put(PICO_DEFAULT_LED_PIN, 0);
vTaskDelay(250);
void main() {
gpio_init(PICO_DEFAULT_LED_PIN);
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
vTaskStartScheduler();
Once we have the C le in place, we need to build the project. We will use CMake to build our project, which
requires a ‘CMakeLists.txt’ le. Copy this pre-made ‘CMakeLists.txt’ le into the blink directory.
In addition to the ‘main.c’ and ‘CMakeLists.txt’ les, we also need a ‘FreeRTOSCon g.h’ le to con gure
FreeRTOS for our project. Copy this pre-made ‘FreeRTOSCon g.h’ le into the ‘blink’ directory. Next, build the
project by following the steps below from the ‘blink’ directory:
$ mkdir build
$ cd build
$ cmake ..
$ make
Once the project successfully builds, there should now be a ‘blink.uf2’ in the ‘build’ directory. This le is the
binary we will ash to the Pico. In order to ash this le, rst hold down the BOOTSEL button on the Pico board
while plugging it in to the USB interface. This will mount the Pico as a drive. Then copy the ‘blink.uf2’ le to the
drive location and the Pico will automatically reboot and run the application. For example, if your drive location
is D:, here is how to copy from the command line:
$ cp blink.uf2 /d/
Now that you have ashed the application, the LED on the Raspberry Pi Pico should be blinking.
Congratulations, you are now executing a task with FreeRTOS!
For the next blog in this series, we will describe how and when the scheduler decides which task to run, then
extend the Blink demo to demonstrate FreeRTOS features like Queues and Stream Bu ers while exploring
event-driven designs.
SUBSCRIBE
Daniel Gross is a Senior Developer Advocate at Amazon Web Services (AWS). Focused on FreeRTOS and
embedded devices interacting with cloud services, Daniel is passionate about delivering a great developer
experience for IoT builders.
Categories
OPEN SOURCE - LINUX, FREERTOS & RELATED
Hi Daniel, thank you very much for the tutorial it is very interesting. I am facing a problem, I am using a raspberry Pi B in order to
compile the code but I am getting the next error when executing the ''make'' command:
cc: error: nosys.specs no such le or directory
Displayed next to your comments. Not displayed publicly. If you have a website, link to it here.
BLOG
In this blog, we will cover how and when the FreeRTOS Scheduler
decides which task to run. We will also build on our code example from
the rst blog to demonstrate Queues and Message Bu ers, which are
features of FreeRTOS. Finally, we will brie y touch on event-driven design. So, get your compilers ready, here
we go.
In the rst blog, we created a task with xTaskCreate() and started the Scheduler with vTaskStartScheduler().
Then, the task function we de ned called vBlinkTask() was executed. This is a simple example of the Scheduler
managing a single task. But, what happens when we create multiple tasks to run in our application? After all,
that is the main point of multitasking. In the next section, we will dive deeper into the fundamentals of
scheduling and tasks.
The Scheduler in FreeRTOS implements a scheduling algorithm to determine what task should run at what
time. For scheduling, time is determined by what is known as a “tick.” The time between ticks depends on the
hardware clock speed and con guration, but is generally between 1-10 milliseconds based on the timing
needs of the application. The time between ticks is known as a “time slice.” In its default con guration,
FreeRTOS uses a scheduling policy where it will switch between tasks that are the same priority in round-robin
order on each tick, and task execution happens within a time slice. The priority of a task is set at creation time,
which we saw in the code example from the rst blog as an argument passed to xTaskCreate() with a value of 1.
However, this can be changed at runtime with vTaskPrioritySet(). Also, the scheduling policy is “preemptive,”
meaning that the Scheduler will always run the highest priority task that is available to run. So, if a higher
priority task becomes available to run while a lower priority task is already running, the Scheduler will start the
higher priority task, causing the lower priority task to be immediately preempted. The following diagram
illustrates the full task state machine, including the task states and transitions. When a task is executing, it is in
the “running” state. When a task is not executing, it is in the “suspended,” “blocked,” or “ready” state.
Given the mechanics above around scheduling, there are a couple of points worth noting. First, because the
Scheduler uses a preemptive scheduling policy, a high priority task that is never “blocked” or “suspended” can
“starve” any lower priority tasks from ever executing. Task starvation can be avoided with techniques like
event-driven design, which we will discuss later. Second, the scheduling policy described above holds true for
a single core system. However, multiple cores add another dimension to how the Scheduler may behave,
because with multiple cores tasks are able to run not only serially but also concurrently. This is especially
relevant for the Raspberry Pi Pico as it has a dual core M0+ processor, and FreeRTOS can take advantage of
this multicore capability with symmetric multiprocessing (SMP). SMP will be covered in more detail later in this
blog series, but keep this in mind when considering Scheduler behavior.
Now that we understand how tasks are managed by the Scheduler, let’s look at how tasks can communicate
with each other. This concept is known as inter-task communication. To accomplish this, we can use other
built-in features of FreeRTOS such as Queues, Stream Bu ers, and Message Bu ers. Below is a code example
using a Queue. In this example, one task determines how many LED blinks to perform while the other task
executes the number of blinks based on the value from the queue. Rename the existing “main.c” le from the
example in the rst blog and create a new “main.c” le, adding the block of code below. Then, follow the
instructions from the rst blog to build and ash the application to the Raspberry Pi Pico.
#include "pico/stdlib.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
QueueHandle_t blinkQueue;
void vBlinkReceiverTask() {
for (;;) {
int blinks = 0;
gpio_put(PICO_DEFAULT_LED_PIN, 1);
vTaskDelay(200);
gpio_put(PICO_DEFAULT_LED_PIN, 0);
vTaskDelay(200);
void vBlinkSenderTask() {
int loops = 4;
for (;;) {
vTaskDelay(500 + (i * 500));
void main() {
gpio_init(PICO_DEFAULT_LED_PIN);
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
vTaskStartScheduler();
}
Running the example above, you should see a blink sequence on the LED of 1, 2, 3, and 4 short blinks which
then repeats. The Queue, “blinkQueue,” is dynamically allocated to hold integer values. Queues can be
dynamically allocated from the heap, or statically allocated at compile time. Queues in FreeRTOS use the copy
method, so values sent to the queue are copied byte for byte. That enables queues to pass data across
memory boundaries, and pointers to data to be queued when the data is large. Beyond our simple example,
there is a rich API for Queue Management that you can explore and experiment with further.
As mentioned earlier, other options available with FreeRTOS for communicating between tasks include using a
Stream Bu er or a Message Bu er. A Message Bu er is a type of Stream Bu er and below is a simple example
of Message Bu er usage. For this example, the “CMakeLists.txt” le in our project folder must be modi ed to
enable output which can be read from the USB serial port. Add the following lines to the end of your
“CMakeLists.txt” le:
pico_enable_stdio_usb(blink 1)
pico_enable_stdio_uart(blink 0)
Once the “CMakeLists.txt” le has been modi ed, rename the existing “main.c” le again and create a new
“main.c” le with the following contents:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "pico/stdlib.h"
#include "FreeRTOS.h"
#include "task.h"
#include "message_buffer.h"
size_t lengthReceived;
for (;;) {
if (lengthReceived > 0) {
for (;;) {
vTaskDelay(1000);
void main() {
stdio_init_all();
busy_wait_ms(1000);
vTaskStartScheduler();
Follow the build and ash procedure used from the rst blog and the example earlier to run the code. Then,
launch an application that can read output from the Raspberry Pi Pico over the USB serial port. There are a
number of ways to do this depending on your environment. For Windows, tools like PuTTY or Tera Term are
good examples. On MacOS, “screen” will do nicely. On Linux, you can use “minicom” or even the pySerial
“miniterm” tool. In all cases you will need to nd the port that USB serial is connecting to on your computer and
con gure your tool with that port to display the output.
You should now see the following output if everything is working correctly: “length: 18, message: FreeRTOS + Pi
Pico”. You might notice the allocated message size was created 4 bytes less than the allocated bu er size in
the code. This is because the Message Bu er uses 4 bytes to store the message length. Remember to account
for this when using Message Bu er. Like Queues, Stream Bu ers and Message Bu ers have extensive APIs
that you can explore and experiment with further.
Congratulations, you have now used two di erent methods for inter-task communication with FreeRTOS on the
Raspberry Pi Pico. You may have noticed something similar about both methods demonstrated. In each code
example, the receiver task sits in an in nite loop while checking to see if data was received. If you recall the
point made earlier in this blog about the preemptive scheduling policy and task starvation, you might recognize
that this existing design could lead to a problematic situation if we introduce other tasks with di erent priorities
or change the priority of our sender or receiver task. Furthermore, resources like processor cycles could be
better optimized. Taking these points into consideration, we should consider moving toward an event-driven
design for our application so that tasks only run when key events occur. The next blog in this series will cover
precisely that and more.
SUBSCRIBE
Daniel Gross is a Senior Developer Advocate at Amazon Web Services (AWS). Focused on FreeRTOS and
embedded devices interacting with cloud services, Daniel is passionate about delivering a great developer
experience for IoT builders.
Categories
OPEN SOURCE - LINUX, FREERTOS & RELATED
This was very informative, thank you for contributing to the discussion on embedded applications for Raspberry Pi. Using this we can
build many projects.
Displayed next to your comments. Not displayed publicly. If you have a website, link to it here.
BLOG
First, what do we mean by “event” when we say “event-driven design” for embedded applications? Generally, an
event represents an action that takes place at a particular point in time that can then be captured and acted
upon programmatically. An event could be something like input from a button press or data arriving from a
connected peripheral. Events happen asynchronously to the running application, and are usually triggered by
what are known as interrupts on embedded systems. An interrupt is a signal produced by an embedded
system that indicates a change in state. The speci c source of an interrupt can be identi ed by an Interrupt
Request or IRQ. The RP2040 microcontroller on the Raspberry Pi Pico has 26 IRQs built-in with a range of
sources. See the RP2040 Datasheet (Section 2.3.2) for more details on the speci c IRQs supported.
Knowing that events are based on interrupts in event-driven design, how do we actually utilize this in an
embedded application? Interrupts can happen at any time, so they will not always align perfectly with the timing
of a running application. To properly capture an interrupt, we usually have to implement an interrupt handler.
This is also known as an Interrupt Service Routine or ISR. From a coding perspective, this can be represented
with a callback function. A callback function is called from outside the application by the system. The
application will de ne a callback function and pass the name of that function to an API so that the system can
call that function when an interrupt occurs. With the Raspberry Pi Pico C/C++ SDK, two such examples are:
gpio_set_irq_callback (gpio_irq_callback_t callback) and irq_set_exclusive_handler (uint num, irq_handler_t
handler). We will use yet another API call from this SDK in our example code later.
Now that we understand how event-driven design works, why is this approach preferable? For one, an event-
driven design does not waste execution cycles by continuously looping. With FreeRTOS, tasks can be stopped
from executing to wait for an event, then re-started when an event occurs. This is more e cient for an
embedded system in terms of processor usage because tasks that are not executing do not consume
processor cycles. Another reason is that we can reduce the amount of code we write and maintain to
constantly poll for state changes that may have occurred. Next, we can eliminate certain types of sleeps,
delays, and other less elegant timing techniques used as an attempt to align to when we think events might
occur in the application. This allows the system to respond to events more quickly since tasks can
automatically restart when an event occurs. Finally, with event-driven design, we can simplify our task priorities
and timings with FreeRTOS to avoid potential task starvation or race conditions.
Before we demonstrate a simple event-driven design with FreeRTOS and the Raspberry Pi Pico, we must rst
take a short detour to cover an important topic related to multitasking. When we have multiple tasks executing
in quick succession, what happens when we want to share a particular resource that only one task can access
at a time? In order to avoid possible collisions between tasks, another built-in feature of FreeRTOS available to
us is the Semaphore API. Semaphores allow a developer to manage serial access to a shared resource within
an application. There are 3 main types of Semaphores in FreeRTOS – binary, counting, and mutex. We will use a
mutex in the example below, which is shorthand for “mutual exclusion.” Once a mutex is de ned, a task can
“take” it to indicate that a shared resource is being used, then “give” it back to indicate the resource is no longer
being used. If a mutex is already taken by one task, another task must wait until the mutex is given back before
it can be taken again. We use a mutex in the example below to enable multiple tasks to print to STDOUT, which
is a shared resource that only one task can access at a time.
As mentioned previously, there are di erent types of events that can be captured in an embedded application.
An embedded system may have sensors or actuators attached to GPIO pins, there may be communication or
networking attached to a UART, or there could be IRQs triggered from a programmable I/O (PIO) state machine.
Each of these will have di erent implementations, but can all bene t from event-driven design. One other type
of interrupt that can be captured is a hardware timer. There are 4 hardware timer slots on the Raspberry Pi Pico
according to the datasheet referenced above, and we will use hardware timers to demonstrate event-driven
design in the upcoming example.
In the example below, direct-to-task noti cation as a lightweight mailbox is used to communicate between an
ISR and a task. The application creates and runs 3 tasks, all de ned by the same vNotifyTask() function. Each
task begins by getting its task number and handle, then creating a hardware timer with
add_repeating_timer_ms(). A callback function de ned in the application, vTimerCallback(), is passed to the
timer, which will be called when the interrupt occurs. Also, the handle of the task is passed to the timer as data
so that the callback can identify the task to notify. Each task then enters its ‘for’ loop and calls
xTaskNotifyWait(), which causes each task to wait until a noti cation is received. The task is no longer
executing at this point, so no processor cycles are consumed. The timer interrupt will trigger execution of the
callback function, which then gets the current time in milliseconds since boot and noti es the task by calling
xTaskNotifyFromISR(). At that point, the vNotifyTask() calls vPrintTime() to format the output and print the value
using the vSafePrint() function, which utilizes a mutex. Then, the task waits for the next noti cation from the
ISR.
#include <stdio.h>
#include "pico/stdlib.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
SemaphoreHandle_t mutex;
void vSafePrint(char *out) {
xSemaphoreTake(mutex, portMAX_DELAY);
puts(out);
xSemaphoreGive(mutex);
char out[32];
vSafePrint(out);
return true;
uint32_t time;
for (;;) {
vPrintTime(task, time);
}
void main() {
stdio_init_all();
mutex = xSemaphoreCreateMutex();
vTaskStartScheduler();
To run this application, you can reuse the same project from the second blog in this series. Simply rename the
existing ‘main.c’ le and create a new ‘main.c’ le inserting the code above. Then, follow the build and ash
instructions from the rst blog in this series. The output will be written to the USB serial as shown in the
previous blog. You should see the task number and timestamp from each task printed out, with the hardware
timer repeating every second. This is a sample of the expected output:
Notice how there are no continuously executing loops or arti cial delays set in our application code. Instead,
we use event-driven design to capture interrupts from the hardware to continue task execution, saving
processor cycles. This example is less than 50 lines of code, but these basic concepts can be applied to much
larger applications.
The next blog will continue our exploration of FreeRTOS with the Raspberry Pi Pico by looking into the
multicore capabilities of the hardware. We will compare Asymmetric Multiprocessing (AMP) and Symmetric
Multiprocessing (SMP), which are both options available with FreeRTOS.
SUBSCRIBE
Daniel Gross is a Senior Developer Advocate at Amazon Web Services (AWS). Focused on FreeRTOS and
embedded devices interacting with cloud services, Daniel is passionate about delivering a great developer
experience for IoT builders.
Categories
OPEN SOURCE - LINUX, FREERTOS & RELATED
Comments Login
Displayed next to your comments. Not displayed publicly. If you have a website, link to it here.
BLOG
In this blog, we will cover how to develop code with FreeRTOS that
utilizes the dual-core processor onboard the Raspberry Pi Pico. We
will also explain the di erences between Asymmetric Multiprocessing
(AMP) and Symmetric Multiprocessing (SMP). Furthermore, we will walk through the various con guration
options available with SMP, and address how the use of SMP can lead to non-deterministic behavior on
microcontrollers.
As mentioned from the rst blog in this series, the Raspberry Pi Pico is a development board built around the
RP2040 microcontroller. The RP2040 has a dual-core Arm Cortex-M0+ processor, with both cores available for
application development. Multiple cores can provide added processing capability for an embedded
application. It is worth mentioning that FreeRTOS has been supporting multicore processing with AMP for
many years. In 2021, FreeRTOS introduced support for multicore processing using SMP. The RP2040 is one of
the hardware platforms now supported with the FreeRTOS SMP kernel. You may have noticed from the rst
blog how we used the ‘-b smp’ ag when cloning the FreeRTOS kernel repository for building the example
applications. This means we have been using the SMP branch of the FreeRTOS kernel all along.
Before we proceed, let us rst cover what AMP and SMP are, and how they are di erent from each other in the
context of FreeRTOS. As the name implies, Asymmetric Multiprocessing (AMP) treats each processor core
independently in an embedded system. For AMP, each core in the system runs its own instance of FreeRTOS.
These instances are entirely separate with just a single processor core available to each application. You can
establish inter-core communication with AMP by enabling shared memory in the system, then use a stream
bu er or message bu er to send and receive data between instances. This article from Richard Barry, Senior
Principal Engineer at AWS and founder of the FreeRTOS project, describes how to implement inter-core
communication with AMP.
Symmetric Multiprocessing (SMP), on the other hand, allows a multi-core embedded system to run a single
instance of FreeRTOS with access to multiple processor cores within the same application. The term
‘symmetric’ refers to the fact that all cores in the system must have an identical hardware architecture. The
RP2040 has two M0+ cores, which satis es the requirement for SMP with this microcontroller. To understand
the scheduler behavior for multitasking in FreeRTOS, refer to our second blog in this series. With SMP, the
scheduler in FreeRTOS can run tasks across multiple cores, which introduces important considerations in
terms of scheduler behavior and how you design your applications.
Using SMP on the RP2040 with FreeRTOS, there are notable con guration options available that a ect how the
scheduler behaves and the functions available to the application. In the next section, we will dive into these
speci c options and what they mean. The ‘FreeRTOSCon g.h’ le we used in the rst blog to build our
application contains the con guration de nitions for FreeRTOS, and in that le are the speci c options for SMP
you can alter.
First, the ‘con gNUM_CORES’ de nition sets the number of cores available for FreeRTOS. To use both cores on
the RP2040, set the value to ‘2’.
The next important de nition is ‘con gRUN_MULTIPLE_PRIORITIES’. Normally, FreeRTOS allows tasks to be
given di erent priorities, which a ects the order of execution and preemption by the scheduler. However,
running tasks with multiple cores available can lead to simultaneous task execution, even if one of the tasks is a
lower priority task. This could lead to unexpected and unwanted behavior within applications that assume only
single core execution, so the developer should consider this setting carefully. By setting
‘con gRUN_MULTIPLE_PRIORITIES’ to ‘0’, tasks will run simultaneously only if they have the same priority.
Setting this value to ‘1’ means that tasks with di erent priorities could run simultaneously.
Another key de nition for SMP is ‘con gUSE_CORE_AFFINITY’. With multiple cores available, the application
developer may want to bind or pin certain tasks to run on speci c cores. Pinning a task to one or more speci c
cores is known as core a nity. By setting this option to ‘1’, a developer can use the vTaskCoreA nitySet() and
vTaskCoreA nityGet() functions in the application. These functions allow the developer to set and get the core
a nity mask for each task, which represents the cores a task can run on. These functions may also be helpful
for transitioning an application from AMP or single-core to SMP. We will demonstrate the use of these functions
in the code sample provided later in this blog.
Finally, the ‘con gUSE_PREEMPTION’ de nition allows the use of the vTaskPreemptionDisable() and the
vTaskPreemptionEnable() functions. These functions allow the application developer to disable and enable
preemption of a task over a speci c section of code. This prevents the scheduler from preempting the task
while the section of code is executing, allowing for even greater control of the behavior within a multicore
application using SMP.
Below, we have a code example that demonstrates the use of SMP. You can follow the same instructions from
the previous blogs in this series to build and run the example on your own Raspberry Pi Pico. In the code, we
create four tasks, named A, B, C, and D. Task A is pinned to core 0 on the RP2040 using the
vTaskCoreA nitySet() function, while task B is pinned to core 1. Tasks C and D are not pinned to any particular
core, meaning they can run on either core. On execution, each task gets the core a nity mask for itself by
using vTaskCoreA nityGet(). Then, each task prints its name, the core that it is currently running on, the
current tick count, and the core a nity mask to the serial console. The get_core_num() function is provided by
the Raspberry Pi Pico C/C++ SDK. You might notice we borrowed the vSafePrint() function that uses the
Semaphore API from the previous blog.
#include <stdio.h>
#include "pico/stdlib.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
SemaphoreHandle_t mutex;
xSemaphoreTake(mutex, portMAX_DELAY);
puts(out);
xSemaphoreGive(mutex);
char out[32];
for (;;) {
vSafePrint(out);
vTaskDelay(taskDelay);
void main() {
stdio_init_all();
mutex = xSemaphoreCreateMutex();
TaskHandle_t handleA;
TaskHandle_t handleB;
vTaskStartScheduler();
When you run this application on the Raspberry Pi Pico, you should see output in the serial console similar to
this:
D 1 9101 -1
C 0 9101 -1
B 1 9102 2
A 0 9102 1
A 0 16000 1
C 1 16001 -1
D 0 16001 -1
B 1 16002 2
D 1 60701 -1
A 0 60701 1
C 1 60702 -1
B 1 60702 2
Above, we have extracted three sections of the output to illustrate potential behavior of tasks when using SMP.
Each section shows that all four tasks are executing in close proximity to one another, just within a tick or two.
However, you will notice that the order of execution for each task changes between the sections. You should
also notice that tasks A and B run only on the cores they were con gured to run on (0 and 1 respectively), but
tasks C and D run on either core because of the a nity mask. Also, recognize that C may execute before D, D
may execute before C, or they may execute simultaneously. This kind of behavior can be considered non-
deterministic behavior, because we cannot predict the exact order the tasks will execute or where they will
execute due to the a nity mask and preemption across cores. Therefore, it is important for a developer
employing SMP to properly con gure and use a nity masks, task priorities, and preemption to suit the needs
of the application. This may mean consciously limiting some of the available capabilities to ensure the desired
behavior.
Historically, embedded developers working on single-core microcontrollers did not have to consider certain
kinds of non-deterministic behavior introduced by using multiple cores. As we covered in this blog, there are
several di erent ways to use SMP, depending on the con guration and usage. SMP with FreeRTOS on the
Raspberry Pi Pico provides new capabilities, and developers should design their applications thoughtfully
when leveraging these capabilities. Additional resources for learning more about SMP can be found here and
here.
We hope you have found this blog series instructive and informative. Using FreeRTOS with the Raspberry Pi
Pico for embedded development is straightforward and o ers many capabilities for embedded applications as
we have shown. Use the example code provided as a starting point to experiment. Remember to visit
freertos.org for more information as you explore and build.
SUBSCRIBE
Daniel Gross is a Senior Developer Advocate at Amazon Web Services (AWS). Focused on FreeRTOS and
embedded devices interacting with cloud services, Daniel is passionate about delivering a great developer
experience for IoT builders.
Categories
OPEN SOURCE - LINUX, FREERTOS & RELATED
Comments Login
Displayed next to your comments. Not displayed publicly. If you have a website, link to it here.