Search code examples
multithreadingrustembeddedrp2040

How do I use both cores on an RP2040 in Rust?


I have read that the RP2040 has two cores. How can I use the second core in a Rust program?

I do not need to go all the way to generic multithreading, I just want to have two threads, each of which owns one of the cores, and they can communicate with each other.

The Rust book's section about Fearless Concurrency (suggested by Jeremy) is not much help.

thread::spawn(|| {
    let mut x = 0;
    x = x + 1;
});

fails to compile

error[E0433]: failed to resolve: use of undeclared crate or module `thread`
   --> src/main.rs:108:5
    |
108 |     thread::spawn(|| {
    |     ^^^^^^ use of undeclared crate or module `thread`

which is hardly surprising given that thread is part of std and the RP2040 is a #![no_std] environment.

In the C API there is a function multicore_launch_core1. Is there an equivalent Rust API?


Solution

  • As you have already discovered, the multi threading facilities of the Rust std library rely on the facilities of an OS kernel which are not available when working in a bare metal embedded environment.

    The actual process of getting the second core to execute code is a little complex and low level. It is described in the RP2040 datasheet in the section titled "2.8.2. Launching Code On Processor Core 1".

    In summary - after the second core boots up, it goes into a sleep state waiting for instructions to be sent to it over the SIO FIFO, which is a communications channel between the two cores. The instructions sent through provides an interrupt vector table, a stack pointer and an entry point for the core to begin executing.

    Luckily, the rp2040_hal crate provides a higher level abstraction for this . The example below is from the multicore module of this crate:

    use rp2040_hal::{pac, gpio::Pins, sio::Sio, multicore::{Multicore, Stack}};
    
    static mut CORE1_STACK: Stack<4096> = Stack::new();
    
    fn core1_task() -> ! {
        loop {}
    }
    
    fn main() -> ! {
        let mut pac = pac::Peripherals::take().unwrap();
        let mut sio = Sio::new(pac.SIO);
        // Other init code above this line
        let mut mc = Multicore::new(&mut pac.PSM, &mut pac.PPB, &mut sio.fifo);
        let cores = mc.cores();
        let core1 = &mut cores[1];
        let _test = core1.spawn(unsafe { &mut CORE1_STACK.mem }, core1_task);
        // The rest of your application below this line
    }
    

    In the above example, the code within the core1_task function will be executed on the second core, while the first core continues to execute the main function. There are more complete examples in the crate's examples directory.

    Disclaimer: I have not used this crate or microcontroller myself - all info was found from online documentation.