Interrupts and Shared State

Critical sections, Mutex<RefCell<T>>, and why static mut is banned.

Part 3 of 3Updated

An interrupt handler is a second thread of execution. Sharing data with main needs the same discipline as any concurrency problem β€” and static mut is not it (it’s a hard error in Rust 2024).

The classic pattern:

static LED: Mutex<RefCell<Option<LedPin>>> = Mutex::new(RefCell::new(None));

#[interrupt]
fn TIM2() {
    critical_section::with(|cs| {
        if let Some(led) = LED.borrow(cs).borrow_mut().as_mut() {
            led.toggle();
        }
    });
}

critical_section::with disables interrupts for the duration, and the cs token is proof β€” you cannot borrow the Mutex without one. The safety argument is encoded in the types.

Keep handlers short. Set a flag, push to a queue, and do the real work in main. A long interrupt handler starves everything else on the chip.

If this state-sharing dance gets tedious, RTIC does it for you at compile time with no runtime cost.