embassy-rs / embassy

Modern embedded framework, using Rust and async.
https://embassy.dev
Apache License 2.0
4.86k stars 660 forks source link

Are interrupt executors currently broken on STM32H7? #2603

Open logerup opened 4 months ago

logerup commented 4 months ago

I started a new project from the current Embassy version (pulled yesterday) and have since been having quite a bit of trouble with the interrupt executors.

When porting some code, I noticed that my interrupt executors were no longer working correctly. I made a minimalistic example for my use case and tested it for a bit. It seems that the example works fine on my old embassy version, but adjusted for the syntax change (probably because of the embedded-hal v1.0 release?) doesn't work for the current version anymore.

The code compiles and flashes fine, but has no or sometimes undeterministic output.

Unfortunately, I'm not able to specify what the exact version my old copy is, but I was previously working with the following dependencies:

[dependencies]
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h743bi", "time-driver-any", "exti", "memory-x", "unstable-pac", "chrono"] }
embassy-sync = { version = "0.5.0", path = "../../embassy-sync", features = ["defmt"] }
embassy-executor = { version = "0.4.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] }
embassy-time = { version = "0.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
embassy-net = { version = "0.2.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] }
embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }

I am now working with:

[dependencies]
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h743bi", "time-driver-any", "exti", "memory-x", "unstable-pac", "chrono"] }
embassy-sync = { version = "0.5.0", path = "../../embassy-sync", features = ["defmt"] }
embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["nightly", "task-arena-size-32768", "arch-cortex-m", "executor-interrupt", "executor-thread", "defmt", "integrated-timers"] }
embassy-time = { version = "0.3.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] }
embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }

Here's the test example I am using:

#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]

use cortex_m_rt::entry;
use defmt::*;
use embassy_executor::{Executor, InterruptExecutor};
use embassy_stm32::exti::ExtiInput;
use embassy_stm32::gpio::{AnyPin, Level, Input, Output, Pull, Speed};
use embassy_stm32::interrupt;
use embassy_stm32::interrupt::{InterruptExt, Priority};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::Mutex;
use embassy_time::Timer;
use static_cell::StaticCell;
use {defmt_rtt as _, panic_probe as _};

// Here mutexes are used for performance difference measurement
// Mutex does not serve any purpose in this example
// A ThreadModeRawMutex is used as this is an interrupt executor
type LedType = Mutex<CriticalSectionRawMutex, Option<Output<'static>>>;

// Initialize static variables
static LED_RED: LedType = Mutex::new(None);
static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new();
static EXECUTOR_LOW: StaticCell<Executor> = StaticCell::new();

// Enable interrupt executor
#[interrupt]
unsafe fn UART4() {
    EXECUTOR_HIGH.on_interrupt()
}

#[entry]
fn main() -> ! {
    // Acquire peripherals
    let p = embassy_stm32::init(Default::default());

    let led_green = Output::new(AnyPin::from(p.PB0), Level::Low, Speed::Low);
    let led_red = Output::new(AnyPin::from(p.PB14), Level::Low, Speed::Low);

    // Map exti on button
    let button = ExtiInput::new(p.PC13, p.EXTI1, Pull::Down);

    // The try_lock method is used because the lock method is an async method and main entry isn't async 
    {
        *(LED_RED.try_lock().unwrap()) = Some(led_external);
    }

    // Spawn interrupt executor for HIGH priority task
    interrupt::UART4.set_priority(Priority::P6);
    let spawner = EXECUTOR_HIGH.start(interrupt::UART4);
    unwrap!(spawner.spawn(interruption(&LED_RED, button)));

    // Spawn normal executor
    let executor = EXECUTOR_LOW.init(Executor::new());
    executor.run(|spawner| {
        unwrap!(spawner.spawn(blinky(led_green)));
    });
}

#[embassy_executor::task]
async fn blinky(mut led_green: Output<'static>) {
    loop {
        led_green.toggle();
        Timer::after_millis(200).await;
    }
}

// Deferred interrupt task with mutex
#[embassy_executor::task]
async fn interruption(led_red: &'static LedType, mut button: ExtiInput<'static>) {
    loop {
        button.wait_for_rising_edge().await;

        // future returns MutexGuard 
        let mut led_red_unlocked = led_red.lock().await;

        if let Some(pin_ref) = led_red_unlocked.as_mut() {
            pin_ref.toggle();
        }
    }
}

Is anyone able to tell me what I am doing wrong, if I somehow messed up some embassy dependency (though I didn't adjust much), or if this is indeed broken currently?

ckellar33 commented 3 months ago

I am also trying to simply enable an interrupt routine to fire on an STM32F4xx. Am I doing something wrong or is it broken?

#[embassy_executor::main]
async fn main(spawner: Spawner) {
    let mut config = Config::default();
    {
        use embassy_stm32::rcc::*;
        config.rcc.hse = Some(Hse {
            freq: Hertz(8_000_000),
            mode: HseMode::Oscillator,
        });
        config.rcc.pll_src = PllSource::HSE;
        config.rcc.pll = Some(Pll {
            prediv: PllPreDiv::DIV4,
            mul: PllMul::MUL168,
            divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 180 / 2 = 180Mhz.
            divq: Some(PllQDiv::DIV4),
            divr: None,
        });
        config.rcc.ahb_pre = AHBPrescaler::DIV1;
        config.rcc.apb1_pre = APBPrescaler::DIV4;
        config.rcc.apb2_pre = APBPrescaler::DIV2;
        config.rcc.sys = Sysclk::PLL1_P;
    }
    let p = embassy_stm32::init(config);

    let mut timer = p.TIM6;
    timer.set_frequency(Hertz(1));
    timer.set_autoreload_preload(true);
    timer.enable_update_interrupt(true);
    timer.reset();
    timer.start();
}

#[interrupt]
fn TIM6_DAC() {
    println!("timer fired");
}