Running Dual-Core Applications on the RP2040
The RP2040 microcontroller integrates a dual‑core Arm Cortex‑M0+ processor alongside 264 KB of on-chip SRAM and a 2 MB external flash. While many projects rely on a single core, leveraging both processors can improve parallelism and responsiveness.
Official SDK and Examples
The pico‑examples repository provides several dual‑core demonstrations inside the multicore folder:
hello_multicoremulticore_fifo_irqsmulticore_runnermulticore_runner_queue
Core communication primitives are declared in pico‑sdk/src/rp2_common/pico_multicore/multicore.c. The main API consists of:
void multicore_launch_core1(void (*entry)(void));
// FIFO operations
uint32_t multicore_fifo_pop_blocking(void);
void multicore_fifo_push_blocking(uint32_t data);
// Interrupt management
inline void multicore_fifo_clear_irq(void);
void irq_set_exclusive_handler(uint num, irq_handler_t handler);
void irq_set_enabled(uint num, bool enabled);
inline bool multicore_fifo_rvalid(void);
FreeRTOS Symmetric Multiprocessing
FreeRTOS now includes SMP support through a dedicated branch. The kernel repository can be found at https://github.com/FreeRTOS/FreeRTOS-Kernel (smp branch), with introductory material on the official website at https://www.freertos.org/symmetric-multiprocessing-introduction.html. A page specifically covering the Pico board is available at https://www.freertos.org/smp-demos-for-the-raspberry-pi-pico-board.html.
Demo projects are hosted in the FreeRTOS-SMP-Demos repository, which includes the SMP‑capable kernel as a submodule. Clone recursively to get started:
git clone --recursive https://github.com/FreeRTOS/FreeRTOS-SMP-Demos
The RP2040 project lives under FreeRTOS/Demo/CORTEX_M0+_RP2040. It ships with two example configurations:
- Standard
- OnEitherCore
The build uses CMake and the Pico SDK integration file pico_sdk_import.cmake. Compile both examples by runing:
mkdir build && cd build
cmake ..
make
Each example folder will contain the resulting binaries after a successful build.
Understanding the Standard Demo
The Standard example produces two executables: main_blinky and main_full. The vLaunch() function selects between them based on the mainCREATE_SIMPLE_BLINKY_DEMO_ONLY preprocessor constant.
void vLaunch( void )
{
#if( mainCREATE_SIMPLE_BLINKY_DEMO_ONLY == 1 )
main_blinky();
#else
main_full();
#endif
}
The main_blinky variant spawns a pair of tasks—prvQueueReceiveTask and prvQueueSendTask. By default the tasks run on whichever core the scheduler assigns, wich may change across reboots. To observe core placement, insert a debug print:
printf("task %s executing on core %d\r\n", __FUNCTION__, portGET_CORE_ID());
Possible output:
task prvQueueSendTask executing on core 1
task prvQueueReceiveTask executing on core 0
Alternatively, a different run could schedule both tasks on core 0 or core 1.
To pin tasks to specific cores, use FreeRTOS's SMP‑specific affinity API. The following code forces the sender to core 0 and the receiver to core 1:
void main_blinky( void )
{
printf("Booting main_blinky.\n");
QueueHandle_t sampleQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof( uint32_t ) );
if( sampleQueue != NULL )
{
TaskHandle_t consumerHandle = NULL;
xTaskCreate( prvQueueReceiveTask, "Consumer",
configMINIMAL_STACK_SIZE, NULL,
mainQUEUE_RECEIVE_TASK_PRIORITY, &consumerHandle );
UBaseType_t consumerAffinity = (1 << 1);
vTaskCoreAffinitySet( consumerHandle, consumerAffinity );
TaskHandle_t producerHandle = NULL;
xTaskCreate( prvQueueSendTask, "Producer",
configMINIMAL_STACK_SIZE, NULL,
mainQUEUE_SEND_TASK_PRIORITY, &producerHandle );
UBaseType_t producerAffinity = (1 << 0);
vTaskCoreAffinitySet( producerHandle, producerAffinity );
vTaskStartScheduler();
}
for( ;; );
}
With affinity set, the output confirms the fixed assignment:
task Producer executing on core 0
task Consumer executing on core 1