Search code examples
rustembeddedmutexcortex-m

Is there a lightweight alternative for a Mutex in embedded Rust when a value will be written once at startup and then only read?


According to the Rust Embedded Book about concurrency, one of the better ways to share some data between contexts is using mutexes with refcells. I understand how they work and why this is necessary. But there is a scenario where the overhead cost seems to much.

The mutex of the cortex_m crate works in this way:

cortex_m::interrupt::free(|cs| {
    let my_value = my_mutex.borrow(cs).borrow();
    // Do something with the value
});

The mutex requires the cs (CriticalSection) token before it gives access. In a critical section, no interrupts can happen so we know we're the only ones that can change and read the value. This works well.

The scenario I'm in now, however, has the variable be written to once for initialization (at runtime) and then always be treated as a read-only value. In my case it's the clock speed of the MCU. This cannot be a compile-time constant. An example why is waking up from deep sleep: depending on the state of the hardware, it may be chosen to use a slower clock speed to conserve some energy. So at startup (or rather a wakeup where all the RAM is gone) a different clock speed may be selected every time.

If I simply want to read the value, it seems wasteful to go through the whole critical section setup. If this variable may be changed again, then, yes, it's necessary. But that's not the case here. It will only get read.

Is there a better way of reading a shared variable with less overhead and without using unsafe Rust?


Solution

  • If a &'static will suffice, I would recommend checking out the static_cell crate (Repo, Lib.rs, Docs.rs).

    From the README:

    use static_cell::StaticCell;
    
    // Statically allocate memory for a `u32`.
    static SOME_INT: StaticCell<u32> = StaticCell::new();
    
    // Initialize it at runtime. This returns a `&'static mut`.
    let x: &'static mut u32 = SOME_INT.init(42);
    assert_eq!(*x, 42);
    
    // Trying to call `.init()` again would panic, because the StaticCell is already initialized.
    // SOME_INT.init(42);
    

    I discovered this crate while looking at an implementation of a CYW43439 wifi chip driver. There's a pretty nifty macro you may find useful:

    macro_rules! singleton {
        ($val:expr) => {{
            type T = impl Sized;
            static STATIC_CELL: StaticCell<T> = StaticCell::new();
            STATIC_CELL.init_with(move || $val)
        }};
    }
    
    // ...
    
        // Init network stack
        let stack = &*singleton!(Stack::new(
            net_device,
            config,
            singleton!(StackResources::<1, 2, 8>::new()),
            seed
        ));