diff --git a/boards/feather_m0/.cargo/config b/boards/feather_m0/.cargo/config index 01e807e22ffd..f3ee604d06ba 100644 --- a/boards/feather_m0/.cargo/config +++ b/boards/feather_m0/.cargo/config @@ -7,6 +7,7 @@ rustflags = [ # See https://github.com/rust-embedded/cortex-m-quickstart/pull/95 "-C", "link-arg=--nmagic", "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x" ] [target.thumbv6m-none-eabi] diff --git a/boards/feather_m0/Cargo.toml b/boards/feather_m0/Cargo.toml index 20226f2366a8..93e2a4a6f717 100644 --- a/boards/feather_m0/Cargo.toml +++ b/boards/feather_m0/Cargo.toml @@ -37,7 +37,9 @@ version = "0.3" optional = true [dev-dependencies] -cortex-m-rtic = "1.0" +rtic = { version = "2.0.1", features = ["thumbv6-backend"] } +rtic-monotonics = { version = "1.3.0", features = ["cortex-m-systick", "systick-10khz"] } +fugit = "0.3.6" cortex-m = "0.7" usbd-serial = "0.1" cortex-m-semihosting = "0.3" @@ -48,6 +50,9 @@ nom = { version = "5", default-features = false } heapless = "0.7" panic-halt = "0.2" panic-semihosting = "0.5" +defmt = "0.3" +defmt-rtt = "0.3" +panic-probe = "0.3" [features] # ask the HAL to enable atsamd21g support @@ -65,8 +70,8 @@ max-channels = ["dma", "atsamd-hal/max-channels"] # Enable pins for the adalogger SD card reader adalogger = [] sdmmc = ["embedded-sdmmc", "atsamd-hal/sdmmc"] -rtic = ["atsamd-hal/rtic"] use_semihosting = [] +nightly = [] [profile.dev] incremental = false @@ -135,7 +140,7 @@ required-features = ["adalogger", "usb", "sdmmc", "unproven"] [[example]] name = "blinky_rtic" -required-features = ["rtic", "unproven"] +required-features = ["unproven", "nightly"] [[example]] name = "uart" @@ -144,3 +149,27 @@ required-features = ["dma"] [[example]] name = "i2c" required-features = ["dma"] + +[[example]] +name = "async_dmac" +required-features = ["dma", "atsamd-hal/async", "nightly"] + +[[example]] +name = "async_timer" +required-features = ["atsamd-hal/async", "nightly"] + +[[example]] +name = "async_eic" +required-features = ["atsamd-hal/async", "nightly"] + +[[example]] +name = "async_i2c" +required-features = ["dma", "atsamd-hal/async", "nightly"] + +[[example]] +name = "async_spi" +required-features = ["dma", "atsamd-hal/async", "nightly"] + +[[example]] +name = "async_uart" +required-features = ["dma", "atsamd-hal/async", "nightly"] \ No newline at end of file diff --git a/boards/feather_m0/examples/async_dmac.rs b/boards/feather_m0/examples/async_dmac.rs new file mode 100644 index 000000000000..7b555620bdb0 --- /dev/null +++ b/boards/feather_m0/examples/async_dmac.rs @@ -0,0 +1,94 @@ +//! This example shows a safe API to +//! execute a memory-to-memory DMA transfer + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt_rtt as _; +use panic_probe as _; + +atsamd_hal::bind_interrupts!(struct Irqs { + DMAC => atsamd_hal::dmac::InterruptHandler; +}); + +#[rtic::app(device = bsp::pac, dispatchers = [I2S])] +mod app { + use bsp::hal; + use feather_m0 as bsp; + use hal::{ + clock::GenericClockController, + dmac::{ + Ch0, Channel, DmaController, PriorityLevel, ReadyFuture, TriggerAction, TriggerSource, + }, + }; + + #[shared] + struct Shared {} + + #[local] + struct Local { + channel: Channel, + } + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + let mut peripherals = cx.device; + let _core = cx.core; + + let _clocks = GenericClockController::with_external_32kosc( + peripherals.GCLK, + &mut peripherals.PM, + &mut peripherals.SYSCTRL, + &mut peripherals.NVMCTRL, + ); + + // Initialize DMA Controller + let dmac = DmaController::init(peripherals.DMAC, &mut peripherals.PM); + + // Turn dmac into an async controller + let mut dmac = dmac.into_future(crate::Irqs); + // Get individual handles to DMA channels + let channels = dmac.split(); + + // Initialize DMA Channel 0 + let channel = channels.0.init(PriorityLevel::LVL0); + + async_task::spawn().ok(); + (Shared {}, Local { channel }) + } + + #[task(local = [channel])] + async fn async_task(cx: async_task::Context) { + let channel = cx.local.channel; + + let mut source = [0xff; 500]; + let mut dest = [0x0; 500]; + + defmt::info!( + "Launching a DMA transfer.\n\tSource: {}\n\tDestination: {}", + &source, + &dest + ); + + channel + .transfer_future( + &mut source, + &mut dest, + TriggerSource::DISABLE, + TriggerAction::BLOCK, + ) + .await + .unwrap(); + + defmt::info!( + "Finished DMA transfer.\n\tSource: {}\n\tDestination: {}", + &source, + &dest + ); + + loop { + cortex_m::asm::wfi(); + } + } +} diff --git a/boards/feather_m0/examples/async_eic.rs b/boards/feather_m0/examples/async_eic.rs new file mode 100644 index 000000000000..3b9c628d2605 --- /dev/null +++ b/boards/feather_m0/examples/async_eic.rs @@ -0,0 +1,85 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt_rtt as _; +use panic_probe as _; + +use bsp::{hal, pin_alias}; +use feather_m0 as bsp; +use hal::{ + clock::{enable_internal_32kosc, ClockGenId, ClockSource, GenericClockController}, + ehal::digital::v2::ToggleableOutputPin, + eic::{ + pin::{ExtInt2, Sense}, + EIC, + }, + gpio::{pin::PA18, Pin, PullUpInterrupt}, + rtc::{Count32Mode, Rtc}, +}; + +atsamd_hal::bind_interrupts!(struct Irqs { + EIC => atsamd_hal::eic::InterruptHandler; +}); + +#[rtic::app(device = bsp::pac, dispatchers = [I2S])] +mod app { + use super::*; + + #[shared] + struct Shared {} + + #[local] + struct Local { + extint: ExtInt2>, + red_led: bsp::RedLed, + } + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + let mut peripherals = cx.device; + let _core = cx.core; + + let mut clocks = GenericClockController::with_external_32kosc( + peripherals.GCLK, + &mut peripherals.PM, + &mut peripherals.SYSCTRL, + &mut peripherals.NVMCTRL, + ); + let pins = bsp::Pins::new(peripherals.PORT); + let red_led: bsp::RedLed = pin_alias!(pins.red_led).into(); + + let internal_clock = clocks + .configure_gclk_divider_and_source(ClockGenId::GCLK2, 1, ClockSource::OSC32K, false) + .unwrap(); + clocks.configure_standby(ClockGenId::GCLK2, true); + + enable_internal_32kosc(&mut peripherals.SYSCTRL); + + // Configure a clock for the EIC peripheral + let gclk2 = clocks.gclk2(); + let eic_clock = clocks.eic(&gclk2).unwrap(); + + let mut eic = EIC::init(&mut peripherals.PM, eic_clock, peripherals.EIC).into_future(Irqs); + let button: Pin<_, PullUpInterrupt> = pins.d10.into(); + let mut extint = ExtInt2::new(button, &mut eic); + extint.enable_interrupt_wake(); + + async_task::spawn().ok(); + + (Shared {}, Local { extint, red_led }) + } + + #[task(local = [extint, red_led])] + async fn async_task(cx: async_task::Context) { + let extint = cx.local.extint; + let red_led = cx.local.red_led; + + loop { + // Here we show straight falling edge detection without + extint.wait(Sense::FALL).await; + defmt::info!("Falling edge detected"); + red_led.toggle().unwrap(); + } + } +} diff --git a/boards/feather_m0/examples/async_i2c.rs b/boards/feather_m0/examples/async_i2c.rs new file mode 100644 index 000000000000..d7d26124dfb9 --- /dev/null +++ b/boards/feather_m0/examples/async_i2c.rs @@ -0,0 +1,102 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt_rtt as _; +use panic_probe as _; + +use bsp::hal; +use feather_m0 as bsp; +use fugit::MillisDuration; +use hal::{ + clock::GenericClockController, + dmac::{Ch0, DmaController, PriorityLevel}, + prelude::*, + sercom::{ + i2c::{self, Config, I2cFutureDma}, + Sercom3, + }, +}; +use rtic_monotonics::systick::Systick; + +atsamd_hal::bind_interrupts!(struct Irqs { + SERCOM3 => atsamd_hal::sercom::i2c::InterruptHandler; + DMAC => atsamd_hal::dmac::InterruptHandler; +}); + +#[rtic::app(device = bsp::pac, dispatchers = [I2S])] +mod app { + use super::*; + + #[shared] + struct Shared {} + + #[local] + struct Local { + i2c: I2cFutureDma, Ch0>, + } + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + let mut peripherals = cx.device; + let _core = cx.core; + + let mut clocks = GenericClockController::with_external_32kosc( + peripherals.GCLK, + &mut peripherals.PM, + &mut peripherals.SYSCTRL, + &mut peripherals.NVMCTRL, + ); + + let pins = bsp::Pins::new(peripherals.PORT); + + // Take SDA and SCL + let (sda, scl) = (pins.sda, pins.scl); + + // Initialize DMA Controller + let dmac = DmaController::init(peripherals.DMAC, &mut peripherals.PM); + + // Turn dmac into an async controller + let mut dmac = dmac.into_future(Irqs); + // Get individual handles to DMA channels + let channels = dmac.split(); + + // Initialize DMA Channel 0 + let channel0 = channels.0.init(PriorityLevel::LVL0); + + let gclk0 = clocks.gclk0(); + let sercom3_clock = &clocks.sercom3_core(&gclk0).unwrap(); + let pads = i2c::Pads::new(sda, scl); + let i2c = i2c::Config::new( + &peripherals.PM, + peripherals.SERCOM3, + pads, + sercom3_clock.freq(), + ) + .baud(100.kHz()) + .enable() + .into_future(Irqs) + .with_dma_channel(channel0); + + async_task::spawn().ok(); + + (Shared {}, Local { i2c }) + } + + #[task(local = [i2c])] + async fn async_task(cx: async_task::Context) { + let i2c = cx.local.i2c; + + loop { + defmt::info!("Sending 0x00 to I2C device..."); + // This test is based on the BMP388 barometer. Feel free to use any I2C + // peripheral you have on hand. + i2c.write(0x76, &[0x00]).await.unwrap(); + + let mut buffer = [0xff; 4]; + i2c.read(0x76, &mut buffer).await.unwrap(); + defmt::info!("Read buffer: {:#x}", buffer); + Systick::delay(MillisDuration::::from_ticks(500).convert()).await; + } + } +} diff --git a/boards/feather_m0/examples/async_spi.rs b/boards/feather_m0/examples/async_spi.rs new file mode 100644 index 000000000000..c3b6b8a78793 --- /dev/null +++ b/boards/feather_m0/examples/async_spi.rs @@ -0,0 +1,103 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt_rtt as _; +use panic_probe as _; + +use bsp::hal; +use feather_m0 as bsp; +use fugit::MillisDuration; +use hal::{ + clock::GenericClockController, + dmac::{Ch0, Ch1, DmaController, PriorityLevel}, + prelude::*, + sercom::{ + spi::{Config, SpiFutureDuplexDma}, + Sercom4, + }, +}; +use rtic_monotonics::systick::Systick; + +atsamd_hal::bind_interrupts!(struct Irqs { + SERCOM4 => atsamd_hal::sercom::spi::InterruptHandler; + DMAC => atsamd_hal::dmac::InterruptHandler; +}); + +#[rtic::app(device = bsp::pac, dispatchers = [I2S])] +mod app { + use super::*; + + #[shared] + struct Shared {} + + #[local] + struct Local { + spi: SpiFutureDuplexDma, Ch0, Ch1>, + } + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + let mut peripherals = cx.device; + let _core = cx.core; + + let mut clocks = GenericClockController::with_external_32kosc( + peripherals.GCLK, + &mut peripherals.PM, + &mut peripherals.SYSCTRL, + &mut peripherals.NVMCTRL, + ); + + let pins = bsp::Pins::new(peripherals.PORT); + + // Take SPI pins + let (miso, mosi, sclk) = (pins.miso, pins.mosi, pins.sclk); + + // Initialize DMA Controller + let dmac = DmaController::init(peripherals.DMAC, &mut peripherals.PM); + + // Turn dmac into an async controller + let mut dmac = dmac.into_future(Irqs); + // Get individual handles to DMA channels + let channels = dmac.split(); + + // Initialize DMA Channels 0 and 1 + let channel0 = channels.0.init(PriorityLevel::LVL0); + let channel1 = channels.1.init(PriorityLevel::LVL0); + + let spi = bsp::spi_master( + &mut clocks, + 100.kHz(), + peripherals.SERCOM4, + &mut peripherals.PM, + sclk, + mosi, + miso, + ) + .into_future(Irqs) + .with_dma_channels(channel0, channel1); + + async_task::spawn().ok(); + + (Shared {}, Local { spi }) + } + + #[task(local = [spi])] + async fn async_task(cx: async_task::Context) { + let spi = cx.local.spi; + + loop { + defmt::info!("Sending 0x00 to SPI device..."); + // This test is based on the BMP388 barometer. Feel free to use any I2C + // peripheral you have on hand. + spi.write(&[0x00]).await.unwrap(); + + defmt::info!("Sent 0x00."); + + let mut buffer = [0xff; 4]; + spi.read(&mut buffer).await.unwrap(); + defmt::info!("Read buffer: {:#x}", buffer); + Systick::delay(MillisDuration::::from_ticks(500).convert()).await; + } + } +} diff --git a/boards/feather_m0/examples/async_timer.rs b/boards/feather_m0/examples/async_timer.rs new file mode 100644 index 000000000000..8e4ed5651a8a --- /dev/null +++ b/boards/feather_m0/examples/async_timer.rs @@ -0,0 +1,81 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +#[cfg(not(feature = "use_semihosting"))] +use panic_halt as _; +#[cfg(feature = "use_semihosting")] +use panic_semihosting as _; + +use bsp::{hal, pin_alias}; +use feather_m0 as bsp; +use fugit::MillisDurationU32; +use hal::{ + async_hal::timer::TimerFuture, + clock::{enable_internal_32kosc, ClockGenId, ClockSource, GenericClockController}, + ehal::digital::v2::ToggleableOutputPin, + pac::TC4, + thumbv6m::timer::TimerCounter, +}; + +atsamd_hal::bind_interrupts!(struct Irqs { + TC4 => atsamd_hal::async_hal::timer::InterruptHandler; +}); + +#[rtic::app(device = bsp::pac, dispatchers = [I2S])] +mod app { + use super::*; + + #[shared] + struct Shared {} + + #[local] + struct Local { + timer: TimerFuture, + red_led: bsp::RedLed, + } + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + let mut peripherals = cx.device; + let _core = cx.core; + + let mut clocks = GenericClockController::with_external_32kosc( + peripherals.GCLK, + &mut peripherals.PM, + &mut peripherals.SYSCTRL, + &mut peripherals.NVMCTRL, + ); + let pins = bsp::Pins::new(peripherals.PORT); + let red_led: bsp::RedLed = pin_alias!(pins.red_led).into(); + + enable_internal_32kosc(&mut peripherals.SYSCTRL); + let timer_clock = clocks + .configure_gclk_divider_and_source(ClockGenId::GCLK2, 1, ClockSource::OSC32K, false) + .unwrap(); + clocks.configure_standby(ClockGenId::GCLK2, true); + + // configure a clock for the TC4 and TC5 peripherals + let tc45 = &clocks.tc4_tc5(&timer_clock).unwrap(); + + // instantiate a timer object for the TC4 peripheral + let timer = TimerCounter::tc4_(tc45, peripherals.TC4, &mut peripherals.PM); + let timer = timer.into_future(Irqs); + async_task::spawn().ok(); + + (Shared {}, Local { timer, red_led }) + } + + #[task(local = [timer, red_led])] + async fn async_task(cx: async_task::Context) { + let timer = cx.local.timer; + let red_led = cx.local.red_led; + + loop { + timer + .delay(MillisDurationU32::from_ticks(500).convert()) + .await; + red_led.toggle().unwrap(); + } + } +} diff --git a/boards/feather_m0/examples/async_uart.rs b/boards/feather_m0/examples/async_uart.rs new file mode 100644 index 000000000000..ebc530b5ed67 --- /dev/null +++ b/boards/feather_m0/examples/async_uart.rs @@ -0,0 +1,115 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt_rtt as _; +use panic_probe as _; + +use bsp::{hal, periph_alias, pin_alias}; +use feather_m0 as bsp; +use fugit::MillisDuration; +use hal::{ + clock::GenericClockController, + dmac::{Ch0, Ch1, DmaController, PriorityLevel}, + prelude::*, + sercom::{ + uart::{Config, UartFutureRxDuplexDma, UartFutureTxDuplexDma}, + Sercom0, + }, +}; +use rtic_monotonics::systick::Systick; + +atsamd_hal::bind_interrupts!(struct Irqs { + SERCOM0 => atsamd_hal::sercom::uart::InterruptHandler; + DMAC => atsamd_hal::dmac::InterruptHandler; +}); + +#[rtic::app(device = bsp::pac, dispatchers = [I2S, AC])] +mod app { + use super::*; + + #[shared] + struct Shared {} + + #[local] + struct Local { + uart_rx: UartFutureRxDuplexDma, Ch0>, + uart_tx: UartFutureTxDuplexDma, Ch1>, + } + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + let mut peripherals = cx.device; + let _core = cx.core; + + let mut clocks = GenericClockController::with_external_32kosc( + peripherals.GCLK, + &mut peripherals.PM, + &mut peripherals.SYSCTRL, + &mut peripherals.NVMCTRL, + ); + let pins = bsp::Pins::new(peripherals.PORT); + + // Take rx and tx pins + let (uart_rx, uart_tx) = (pin_alias!(pins.uart_rx), pin_alias!(pins.uart_tx)); + let uart_sercom = periph_alias!(peripherals.uart_sercom); + + // Initialize DMA Controller + let dmac = DmaController::init(peripherals.DMAC, &mut peripherals.PM); + // Turn dmac into an async controller + let mut dmac = dmac.into_future(Irqs); + // Get individual handles to DMA channels + let channels = dmac.split(); + + // Initialize DMA Channels 0 and 1 + let channel0 = channels.0.init(PriorityLevel::LVL0); + let channel1 = channels.1.init(PriorityLevel::LVL0); + + let (uart_rx, uart_tx) = bsp::uart( + &mut clocks, + 9600.Hz(), + uart_sercom, + &mut peripherals.PM, + uart_rx, + uart_tx, + ) + .into_future(Irqs) + .with_rx_dma_channel(channel0) + .with_tx_dma_channel(channel1) + .split(); + + send_bytes::spawn().ok(); + receive_bytes::spawn().ok(); + + (Shared {}, Local { uart_rx, uart_tx }) + } + + #[task(local = [uart_tx], priority = 1)] + async fn send_bytes(cx: send_bytes::Context) { + let uart = cx.local.uart_tx; + + loop { + uart.write(&[0x00; 10]).await; + defmt::info!("Sent 10 bytes"); + Systick::delay(MillisDuration::::from_ticks(500).convert()).await; + } + } + + #[task(local = [uart_rx], priority = 2)] + async fn receive_bytes(cx: receive_bytes::Context) { + let uart = cx.local.uart_rx; + uart.as_mut().flush_rx_buffer(); + + loop { + let mut buf = [0x00; 10]; + match uart.read(&mut buf).await { + Ok(()) => defmt::info!("read {}", &buf), + Err(_) => { + defmt::error!("UART Error."); + // Flusing the RX buffer may drop a few bytes. + uart.as_mut().flush_rx_buffer(); + } + } + } + } +} diff --git a/boards/feather_m0/examples/blinky_rtic.rs b/boards/feather_m0/examples/blinky_rtic.rs index 01a0cd4648ec..c4e80d8f6dbc 100644 --- a/boards/feather_m0/examples/blinky_rtic.rs +++ b/boards/feather_m0/examples/blinky_rtic.rs @@ -1,9 +1,7 @@ -//! Uses RTIC with the RTC as time source to blink an LED. -//! -//! The idle task is sleeping the CPU, so in practice this gives similar power -//! figure as the "sleeping_timer_rtc" example. +//! Uses RTIC with systick to blink a led in an asynchronous fashion. #![no_std] #![no_main] +#![feature(type_alias_impl_trait)] use feather_m0 as bsp; @@ -11,35 +9,31 @@ use feather_m0 as bsp; use panic_halt as _; #[cfg(feature = "use_semihosting")] use panic_semihosting as _; -use rtic; + +use bsp::{hal, pin_alias}; +use fugit::MillisDurationU32; +use hal::clock::GenericClockController; +use hal::pac::Peripherals; +use hal::prelude::*; +use rtic_monotonics::systick::Systick; #[rtic::app(device = bsp::pac, peripherals = true, dispatchers = [EVSYS])] mod app { use super::*; - use bsp::{hal, pin_alias}; - use hal::clock::{ClockGenId, ClockSource, GenericClockController}; - use hal::pac::Peripherals; - use hal::prelude::*; - use hal::rtc::{Count32Mode, Duration, Rtc}; #[local] - struct Local {} - - #[shared] - struct Shared { - // The LED could be a local resource, since it is only used in one task - // But we want to showcase shared resources and locking + struct Local { red_led: bsp::RedLed, } - #[monotonic(binds = RTC, default = true)] - type RtcMonotonic = Rtc; + #[shared] + struct Shared {} #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(cx: init::Context) -> (Shared, Local) { let mut peripherals: Peripherals = cx.device; let pins = bsp::Pins::new(peripherals.PORT); - let mut core: rtic::export::Peripherals = cx.core; + let _core: rtic::export::Peripherals = cx.core; let mut clocks = GenericClockController::with_external_32kosc( peripherals.GCLK, &mut peripherals.PM, @@ -47,27 +41,20 @@ mod app { &mut peripherals.NVMCTRL, ); let _gclk = clocks.gclk0(); - let rtc_clock_src = clocks - .configure_gclk_divider_and_source(ClockGenId::GCLK2, 1, ClockSource::XOSC32K, false) - .unwrap(); - clocks.configure_standby(ClockGenId::GCLK2, true); - let rtc_clock = clocks.rtc(&rtc_clock_src).unwrap(); - let rtc = Rtc::count32_mode(peripherals.RTC, rtc_clock.freq(), &mut peripherals.PM); - let red_led: bsp::RedLed = pin_alias!(pins.red_led).into(); - // We can use the RTC in standby for maximum power savings - core.SCB.set_sleepdeep(); + let red_led: bsp::RedLed = pin_alias!(pins.red_led).into(); // Start the blink task blink::spawn().unwrap(); - (Shared { red_led }, Local {}, init::Monotonics(rtc)) + (Shared {}, Local { red_led }) } - #[task(shared = [red_led])] - fn blink(mut cx: blink::Context) { - // If the LED were a local resource, the lock would not be necessary - cx.shared.red_led.lock(|led| led.toggle().unwrap()); - blink::spawn_after(Duration::secs(1)).ok(); + #[task(local = [red_led])] + async fn blink(cx: blink::Context) { + loop { + let _ = cx.local.red_led.toggle(); + Systick::delay(MillisDurationU32::from_ticks(1000).convert()).await; + } } } diff --git a/boards/feather_m4/Cargo.toml b/boards/feather_m4/Cargo.toml index bc12e96bb24b..6f8ac72a02bd 100644 --- a/boards/feather_m4/Cargo.toml +++ b/boards/feather_m4/Cargo.toml @@ -40,6 +40,7 @@ panic-semihosting = "0.5" smart-leds = "0.3" ws2812-timer-delay = "0.3" heapless = "0.7" +embassy = { version = "0.1.0", git = "https://github.com/embassy-rs/embassy.git" } [features] # ask the HAL to enable atsamd51j support @@ -50,7 +51,6 @@ usb = ["atsamd-hal/usb", "usb-device"] dma = ["atsamd-hal/dma", "unproven"] max-channels = ["dma", "atsamd-hal/dma"] - [profile.dev] incremental = false codegen-units = 1 @@ -92,4 +92,4 @@ required-features = ["unproven", "usb"] [[example]] name = "i2c" -required-features = ["atsamd-hal/dma"] +required-features = ["atsamd-hal/dma"] \ No newline at end of file diff --git a/boards/metro_m4/.cargo/config b/boards/metro_m4/.cargo/config index 553292e3d068..324261873e35 100644 --- a/boards/metro_m4/.cargo/config +++ b/boards/metro_m4/.cargo/config @@ -9,6 +9,6 @@ rustflags = [ # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x # See https://github.com/rust-embedded/cortex-m-quickstart/pull/95 "-C", "link-arg=--nmagic", - + "-C", "link-arg=-Tdefmt.x", # uncomment if using defmt "-C", "link-arg=-Tlink.x", ] diff --git a/boards/metro_m4/Cargo.toml b/boards/metro_m4/Cargo.toml index c339e1a2978d..b6d2b90f726f 100644 --- a/boards/metro_m4/Cargo.toml +++ b/boards/metro_m4/Cargo.toml @@ -41,16 +41,22 @@ panic-rtt-target = { version = "0.1.1", features = ["cortex-m"] } cortex-m-semihosting = "0.3" rtt-target = { version = "0.3.0", features = ["cortex-m"] } smart-leds = "0.3" +defmt = "0.3" +defmt-rtt = "0.4" +rtic = { version = "2.0.1", features = ["thumbv7-backend"] } [dev-dependencies.ws2812-timer-delay] version = "0.3" + [features] # ask the HAL to enable atsamd51j support default = ["rt", "atsamd-hal/samd51j", "atsamd-hal/samd51"] +dma = ["atsamd-hal/dma"] rt = ["cortex-m-rt", "atsamd-hal/samd51j-rt"] unproven = ["atsamd-hal/unproven"] usb = ["atsamd-hal/usb", "usb-device"] +nightly = [] [profile.dev] incremental = false @@ -101,3 +107,7 @@ required-features = ["usb", "unproven"] [[example]] name = "i2c" required-features = ["atsamd-hal/dma"] + +[[example]] +name = "async_dmac" +required-features = ["dma", "atsamd-hal/async", "nightly"] diff --git a/boards/metro_m4/examples/async_dmac.rs b/boards/metro_m4/examples/async_dmac.rs new file mode 100644 index 000000000000..20a997318298 --- /dev/null +++ b/boards/metro_m4/examples/async_dmac.rs @@ -0,0 +1,95 @@ +//! This example shows a safe API to +//! execute a memory-to-memory DMA transfer + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt_rtt as _; +use panic_probe as _; + +atsamd_hal::bind_interrupts!(struct Irqs { + DMAC: [DMAC_0, DMAC_1, DMAC_2, DMAC_OTHER] => atsamd_hal::dmac::InterruptHandler; +}); + +#[rtic::app(device = bsp::pac, dispatchers = [I2S])] +mod app { + use bsp::hal; + use metro_m4 as bsp; + use hal::{ + clock::GenericClockController, + dmac::{ + Ch0, Channel, DmaController, PriorityLevel, ReadyFuture, TriggerAction, TriggerSource, + }, + }; + + #[shared] + struct Shared {} + + #[local] + struct Local { + channel: Channel, + } + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + let mut peripherals = cx.device; + let _core = cx.core; + + let _clocks = GenericClockController::with_external_32kosc( + peripherals.GCLK, + &mut peripherals.MCLK, + &mut peripherals.OSC32KCTRL, + &mut peripherals.OSCCTRL, + &mut peripherals.NVMCTRL, + ); + + // Initialize DMA Controller + let dmac = DmaController::init(peripherals.DMAC, &mut peripherals.PM); + + // Turn dmac into an async controller + let mut dmac = dmac.into_future(crate::Irqs); + // Get individual handles to DMA channels + let channels = dmac.split(); + + // Initialize DMA Channel 0 + let channel = channels.0.init(PriorityLevel::LVL0); + + async_task::spawn().ok(); + (Shared {}, Local { channel }) + } + + #[task(local = [channel])] + async fn async_task(cx: async_task::Context) { + let channel = cx.local.channel; + + let mut source = [0xff; 1000]; + let mut dest = [0x0; 1000]; + + defmt::info!( + "Launching a DMA transfer.\n\tSource: {}\n\tDestination: {}", + &source, + &dest + ); + + channel + .transfer_future( + &mut source, + &mut dest, + TriggerSource::DISABLE, + TriggerAction::BLOCK, + ) + .await + .unwrap(); + + defmt::info!( + "Finished DMA transfer.\n\tSource: {}\n\tDestination: {}", + &source, + &dest + ); + + loop { + cortex_m::asm::wfi(); + } + } +} diff --git a/hal/Cargo.toml b/hal/Cargo.toml index f251dce9eb6b..9295c8199586 100644 --- a/hal/Cargo.toml +++ b/hal/Cargo.toml @@ -19,7 +19,7 @@ license = "MIT OR Apache-2.0" name = "atsamd-hal" readme = "README.md" repository = "https://github.com/atsamd-rs/atsamd" -rust-version = "1.65" +rust-version = "1.75" version = "0.16.0" [package.metadata.docs.rs] @@ -34,7 +34,8 @@ aes = "0.7.5" bitfield = "0.13" bitflags = "1.2.1" cipher = "0.3" -cortex-m = "0.7" +critical-section = "1.1.2" +cortex-m = { version = "0.7", features = ["critical-section-single-core"] } embedded-hal = "0.2" fugit = "0.3" modular-bitfield = "0.11" @@ -42,6 +43,7 @@ nb = "1.0" num-traits = {version = "0.2.14", default-features = false} opaque-debug = "0.3.0" paste = "1.0.11" +portable-atomic = {version = "1.5.1", optional = true, default-features = false, features = ["critical-section"]} rand_core = "0.6" seq-macro = "0.3" typenum = "1.12.0" @@ -52,7 +54,11 @@ void = {version = "1.0", default-features = false} # Optional depdendencies #=============================================================================== +embassy-sync = {version = "0.4", optional = true} +embedded-hal-alpha = {package = "embedded-hal", version = "1.0.0-rc.1"} +embedded-hal-async = {version = "1.0.0-rc.2", optional = true, features = ["defmt-03"]} embedded-sdmmc = {version = "0.3", optional = true} +futures = {version = "0.3", default-features = false, features = ["async-await"], optional = true} jlink_rtt = {version = "0.2", optional = true} mcan-core = {version = "0.2", optional = true} rtic-monotonic = {version = "1.0", optional = true} @@ -186,6 +192,13 @@ sdmmc = ["embedded-sdmmc"] unproven = ["embedded-hal/unproven"] usb = ["usb-device"] use_rtt = ["jlink_rtt"] +async = [ + "unproven", + "embassy-sync", + "embedded-hal-async", + "futures", + "portable-atomic" +] #=============================================================================== # Implementation-details diff --git a/hal/src/async_hal/interrupts.rs b/hal/src/async_hal/interrupts.rs new file mode 100644 index 000000000000..78dcf3dfea74 --- /dev/null +++ b/hal/src/async_hal/interrupts.rs @@ -0,0 +1,394 @@ +use core::mem; +use core::sync::atomic::{compiler_fence, Ordering}; +use cortex_m::interrupt::InterruptNumber; +use cortex_m::peripheral::NVIC; +use critical_section::CriticalSection; +use paste::paste; +use seq_macro::seq; + +macro_rules! declare_interrupts { + ($($(#[$cfg:meta])* $irqs:ident),* $(,)?) => { + $( + $(#[$cfg])* + #[allow(non_camel_case_types)] + #[doc=stringify!($irqs)] + #[doc=" typelevel interrupt."] + pub enum $irqs {} + $(#[$cfg])* + impl $crate::typelevel::Sealed for $irqs{} + $(#[$cfg])* + impl $crate::async_hal::interrupts::Interrupt for $irqs { + const IRQ: crate::pac::Interrupt = crate::pac::Interrupt::$irqs; + } + )* + } +} + +// Useful when we need to bind multiple interrupt sources to the same handler. +// Calling the `InterruptSource` methods on the created struct will act on all +// interrupt sources at once. +#[allow(unused_macros)] +macro_rules! declare_multiple_interrupts { + ($(#[$cfg:meta])* $name:ident: [ $($irq:ident),+ $(,)? ]) => { + paste! { + $(#[$cfg])* + pub enum $name {} + + $(#[$cfg])* + impl $crate::typelevel::Sealed for $name {} + + $(#[$cfg])* + impl $crate::async_hal::interrupts::InterruptSource for $name { + unsafe fn enable() { + $($crate::pac::Interrupt::$irq.enable();)+ + } + + fn disable() { + $($crate::pac::Interrupt::$irq.disable();)+ + } + + fn unpend() { + $($crate::pac::Interrupt::$irq.unpend();)+ + } + + fn set_priority(prio: $crate::async_hal::interrupts::Priority){ + $($crate::pac::Interrupt::$irq.set_priority(prio);)+ + } + } + } + }; +} + +// ---------- DMAC Interrupts ---------- // +#[cfg(all(feature = "dma", feature = "thumbv7"))] +declare_multiple_interrupts!(DMAC: [DMAC_0, DMAC_1, DMAC_2, DMAC_OTHER]); + +#[cfg(all(feature = "dma", feature = "thumbv7"))] +declare_interrupts!(DMAC_OTHER); + +#[cfg(all(feature = "dma", feature = "thumbv6"))] +declare_interrupts!(DMAC); + +// ---------- SERCOM Interrupts ---------- // +seq!(N in 0..=7 { + paste! { + #[cfg(all(feature = "has-" sercom~N, feature = "thumbv6"))] + declare_interrupts!(SERCOM~N); + #[cfg(all(feature = "has-" sercom~N, feature = "thumbv7"))] + declare_multiple_interrupts!([]: [ [], [], [], [] ]); + } +}); + +// ---------- TC Interrupts ---------- // +seq!(N in 0..=5{ + paste! { + declare_interrupts! { + #[cfg(feature = "has-" tc~N)] + TC~N + } + } +}); + +// ---------- EIC Interrupt ---------- // +#[cfg(feature = "thumbv6")] +declare_interrupts!(EIC); + +#[cfg(feature = "thumbv7")] +seq!(N in 0..= 15 { + paste! { + declare_interrupts! { + EIC_EXTINT_~N + } + + } +}); + +/// Interrupt source. This trait may implemented directly when multiple +/// interrupt sources are needed to operate a single peripheral (eg, SERCOM and +/// DMAC for thumbv7 devices). If using one interrupt source per peripheral, +/// implement [`Interrupt`] instead. When implemented on a type that handles +/// multiple interrupt sources, the methods will act on all interrupt sources at +/// once. +pub trait InterruptSource: crate::typelevel::Sealed { + /// Enable the interrupt. + /// + /// # Safety + /// + /// Do not enable any interrupt inside a critical section. + unsafe fn enable(); + + /// Disable the interrupt. + fn disable(); + + /// Unset interrupt pending. + fn unpend(); + + /// Set the interrupt priority. + fn set_priority(prio: Priority); +} + +impl InterruptSource for T { + unsafe fn enable() { + Self::enable(); + } + + fn disable() { + Self::disable(); + } + + fn unpend() { + Self::unpend(); + } + + fn set_priority(prio: Priority) { + Self::set_priority(prio); + } +} + +/// Type-level interrupt. +/// +/// This trait is implemented for all typelevel single interrupt types in this +/// module. +pub trait Interrupt: crate::typelevel::Sealed { + /// Interrupt enum variant. + /// + /// This allows going from typelevel interrupts (one type per interrupt) to + /// non-typelevel interrupts (a single `Interrupt` enum type, with one + /// variant per interrupt). + const IRQ: crate::pac::Interrupt; + + /// Enable the interrupt. + /// + /// # Safety + /// + /// Do not enable any interrupt inside a critical section. + #[inline] + unsafe fn enable() { + Self::IRQ.enable() + } + + /// Disable the interrupt. + #[inline] + fn disable() { + Self::IRQ.disable() + } + + /// Check if interrupt is enabled. + #[inline] + fn is_enabled() -> bool { + Self::IRQ.is_enabled() + } + + /// Check if interrupt is pending. + #[inline] + fn is_pending() -> bool { + Self::IRQ.is_pending() + } + + /// Set interrupt pending. + #[inline] + fn pend() { + Self::IRQ.pend() + } + + /// Unset interrupt pending. + #[inline] + fn unpend() { + Self::IRQ.unpend() + } + + /// Get the priority of the interrupt. + #[inline] + fn get_priority() -> Priority { + Self::IRQ.get_priority() + } + + /// Set the interrupt priority. + #[inline] + fn set_priority(prio: Priority) { + Self::IRQ.set_priority(prio) + } + + /// Set the interrupt priority with an already-acquired critical section + #[inline] + fn set_priority_with_cs(cs: critical_section::CriticalSection, prio: Priority) { + Self::IRQ.set_priority_with_cs(cs, prio) + } +} + +/// Interrupt handler trait. +/// +/// Drivers that need to handle interrupts implement this trait. +/// The user must ensure `on_interrupt()` is called every time the interrupt +/// fires. Drivers must use use [`Binding`] to assert at compile time that the +/// user has done so. +pub trait Handler { + /// Interrupt handler function. + /// + /// Must be called every time the `I` interrupt fires, synchronously from + /// the interrupt handler context. + /// + /// # Safety + /// + /// This function must ONLY be called from the interrupt handler for `I`. + unsafe fn on_interrupt(); +} + +/// Compile-time assertion that an interrupt has been bound to a handler. +/// +/// For the vast majority of cases, you should use the `bind_interrupts!` +/// macro instead of writing `unsafe impl`s of this trait. +/// +/// # Safety +/// +/// By implementing this trait, you are asserting that you have arranged for +/// `H::on_interrupt()` to be called every time the `I` interrupt fires. +/// +/// This allows drivers to check bindings at compile-time. +pub unsafe trait Binding> {} + +/// Represents an interrupt type that can be configured by the HAL to handle +/// interrupts. +pub unsafe trait InterruptExt: InterruptNumber + Copy { + /// Enable the interrupt. + /// + /// # Safety + /// + /// Do not enable any interrupt inside a critical section. + #[inline] + unsafe fn enable(self) { + compiler_fence(Ordering::SeqCst); + NVIC::unmask(self) + } + + /// Disable the interrupt. + #[inline] + fn disable(self) { + NVIC::mask(self); + compiler_fence(Ordering::SeqCst); + } + + /// Check if interrupt is being handled. + #[inline] + #[cfg(not(feature = "thumbv6"))] + fn is_active(self) -> bool { + NVIC::is_active(self) + } + + /// Check if interrupt is enabled. + #[inline] + fn is_enabled(self) -> bool { + NVIC::is_enabled(self) + } + + /// Check if interrupt is pending. + #[inline] + fn is_pending(self) -> bool { + NVIC::is_pending(self) + } + + /// Set interrupt pending. + #[inline] + fn pend(self) { + NVIC::pend(self) + } + + /// Unset interrupt pending. + #[inline] + fn unpend(self) { + NVIC::unpend(self) + } + + /// Get the priority of the interrupt. + #[inline] + fn get_priority(self) -> Priority { + Priority::from(NVIC::get_priority(self)) + } + + /// Set the interrupt priority. + #[inline] + fn set_priority(self, prio: Priority) { + unsafe { + let mut nvic = steal_nvic(); + + // On thumbv6, set_priority must do a RMW to change 8bit in a 32bit reg. + #[cfg(feature = "thumbv6")] + critical_section::with(|_| nvic.set_priority(self, prio.into())); + // On thumbv7+, set_priority does an atomic 8bit write, so no CS needed. + #[cfg(not(feature = "thumbv6"))] + nvic.set_priority(self, prio.into()); + } + } + + /// Set the interrupt priority with an already-acquired critical section + /// + /// Equivalent to `set_priority`, except you pass a `CriticalSection` to + /// prove you've already acquired a critical section. This prevents + /// acquiring another one, which saves code size. + #[inline] + fn set_priority_with_cs(self, _cs: CriticalSection, prio: Priority) { + unsafe { + let mut nvic = steal_nvic(); + nvic.set_priority(self, prio.into()); + } + } +} + +unsafe impl InterruptExt for T {} + +impl From for Priority { + fn from(priority: u8) -> Self { + unsafe { mem::transmute(priority & PRIO_MASK) } + } +} + +impl From for u8 { + fn from(p: Priority) -> Self { + p as u8 + } +} + +#[cfg(feature = "thumbv6")] +const PRIO_MASK: u8 = 0x60; +#[cfg(feature = "thumbv7")] +const PRIO_MASK: u8 = 0xe0; + +/// The interrupt priority level. +/// +/// P0 represents the most urgent prioriry, whereas P7 represents the least +/// urgent. +#[cfg(feature = "thumbv6")] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P0 = 0x0, + P1 = 0x20, + P2 = 0x40, + P3 = 0x60, +} + +/// The interrupt priority level. +/// +/// P0 represents the most urgent prioriry, whereas P7 represents the least +/// urgent. +#[cfg(feature = "thumbv7")] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P0 = 0x0, + P1 = 0x20, + P2 = 0x40, + P3 = 0x60, + P4 = 0x80, + P5 = 0xa0, + P6 = 0xc0, + P7 = 0xe0, +} + +unsafe fn steal_nvic() -> NVIC { + cortex_m::peripheral::Peripherals::steal().NVIC +} diff --git a/hal/src/async_hal/mod.rs b/hal/src/async_hal/mod.rs new file mode 100644 index 000000000000..333f6478170b --- /dev/null +++ b/hal/src/async_hal/mod.rs @@ -0,0 +1,56 @@ +//! Async APIs + +pub mod interrupts; +pub mod timer; + +/// Macro to bind interrupts to handlers. +/// +/// This defines the right interrupt handlers, creates a unit struct (like +/// `struct Irqs;`) and implements the right [`Binding`]s for it. You can pass +/// this struct to drivers to prove at compile-time that the right interrupts +/// have been bound. +/// +/// # Example +/// ``` +/// use atsamd_hal::bind_interrupts; +/// +/// bind_interrupts!(struct Irqs { +/// SERCOM0 => sercom::InterruptHandler; +/// }); +/// ``` +#[macro_export] +macro_rules! bind_interrupts { + ($vis:vis struct $name:ident { $($irq:ident => $($handler:ty),*;)* }) => { + #[derive(Copy, Clone)] + $vis struct $name; + + $( + #[allow(non_snake_case)] + #[no_mangle] + unsafe extern "C" fn $irq() { + $( + <$handler as $crate::async_hal::interrupts::Handler<$crate::async_hal::interrupts::$irq>>::on_interrupt(); + )* + } + + $( + unsafe impl $crate::async_hal::interrupts::Binding<$crate::async_hal::interrupts::$irq, $handler> for $name {} + )* + )* + }; + + ($vis:vis struct $name:ident { $int_source:ident: [ $($irq:ident),+ ] => $handler:ty; }) => { + #[derive(Copy, Clone)] + $vis struct $name; + + $( + #[allow(non_snake_case)] + #[no_mangle] + unsafe extern "C" fn $irq() { + <$handler as $crate::async_hal::interrupts::Handler<$crate::async_hal::interrupts::$int_source>>::on_interrupt(); + } + )+ + + unsafe impl $crate::async_hal::interrupts::Binding<$crate::async_hal::interrupts::$int_source, $handler> for $name {} + }; +} diff --git a/hal/src/async_hal/timer.rs b/hal/src/async_hal/timer.rs new file mode 100644 index 000000000000..de4f09db1876 --- /dev/null +++ b/hal/src/async_hal/timer.rs @@ -0,0 +1,234 @@ +use crate::{ + async_hal::interrupts::{Binding, Handler, Interrupt}, + ehal::timer::CountDown, + pac, + timer_traits::InterruptDrivenTimer, + typelevel::Sealed, +}; +use core::{ + future::poll_fn, + marker::PhantomData, + sync::atomic::Ordering, + task::{Poll, Waker}, +}; +use embassy_sync::waitqueue::AtomicWaker; +use fugit::NanosDurationU32; +use portable_atomic::AtomicBool; + +#[cfg(feature = "thumbv6")] +use crate::thumbv6m::timer; + +#[cfg(feature = "thumbv7")] +use crate::thumbv7em::timer; + +#[cfg(feature = "has-tc1")] +use crate::pac::TC1; + +#[cfg(feature = "has-tc2")] +use crate::pac::TC2; + +#[cfg(feature = "has-tc3")] +use crate::pac::TC3; + +#[cfg(feature = "has-tc4")] +use crate::pac::TC4; + +#[cfg(feature = "has-tc5")] +use crate::pac::TC5; + +use timer::{Count16, TimerCounter}; + +#[cfg(feature = "periph-d11c")] +type RegBlock = pac::tc1::RegisterBlock; + +#[cfg(feature = "periph-d21")] +type RegBlock = pac::tc3::RegisterBlock; + +#[cfg(feature = "periph-d51")] +type RegBlock = pac::tc0::RegisterBlock; + +/// Trait enabling the use of a Timer/Counter in async mode. Specifically, this +/// trait enables us to register a `TC*` interrupt as a waker for timer futures. +/// +/// **⚠️ Warning** This trait should not be implemented outside of this crate! +pub trait AsyncCount16: Count16 + Sealed { + /// Index of this TC in the `STATE` tracker + const STATE_ID: usize; + + fn reg_block(peripherals: &pac::Peripherals) -> &RegBlock; + + type Interrupt: Interrupt; +} + +/// Interrupt handler for async SPI operarions +pub struct InterruptHandler { + _private: (), + _tc: PhantomData, +} + +impl Handler for InterruptHandler { + /// Callback function when the corresponding TC interrupt is fired + /// + /// # Safety + /// + /// This method may [`steal`](crate::pac::Peripherals::steal) the `TC` + /// peripheral instance to check the interrupt flags. The only + /// modifications it is allowed to apply to the peripheral is to clear + /// the interrupt flag (to prevent re-firing). This method should ONLY be + /// able to be called while a [`TimerFuture`] holds an unique reference + /// to the underlying `TC` peripheral. + unsafe fn on_interrupt() { + let periph = unsafe { crate::pac::Peripherals::steal() }; + let tc = A::reg_block(&periph); + let intflag = &tc.count16().intflag; + + if intflag.read().ovf().bit_is_set() { + // Clear the flag + intflag.modify(|_, w| w.ovf().set_bit()); + STATE[A::STATE_ID].wake(); + } + } +} + +macro_rules! impl_async_count16 { + ($(($TC: ident, $id: expr)),+) => { + $( + impl AsyncCount16 for $TC { + const STATE_ID: usize = $id; + + type Interrupt = crate::async_hal::interrupts::$TC; + + + fn reg_block(peripherals: &pac::Peripherals) -> &crate::async_hal::timer::RegBlock { + &*peripherals.$TC + } + } + + impl Sealed for $TC {} + )+ + }; + } + +#[cfg(feature = "samd11")] +impl_async_count16!((TC1, 0)); +#[cfg(feature = "samd11")] +const NUM_TIMERS: usize = 1; + +#[cfg(feature = "samd21")] +impl_async_count16!((TC3, 0), (TC4, 1), (TC5, 2)); +#[cfg(feature = "samd21")] +const NUM_TIMERS: usize = 3; + +#[cfg(feature = "samd51g")] +impl_async_count16!((TC2, 0), (TC3, 1)); +#[cfg(feature = "samd51g")] +const NUM_TIMERS: usize = 2; + +#[cfg(feature = "periph-d51j")] +impl_async_count16!((TC2, 0), (TC3, 1), (TC4, 2), (TC5, 3)); +#[cfg(feature = "periph-d51j")] +const NUM_TIMERS: usize = 4; + +impl TimerCounter +where + T: AsyncCount16, +{ + /// Transform a [`TimerCounter`] into an [`TimerFuture`] + #[inline] + pub fn into_future(mut self, _irq: I) -> TimerFuture + where + I: Binding>, + { + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + self.enable_interrupt(); + + TimerFuture { timer: self } + } +} + +/// Wrapper around a [`TimerCounter`] with an `async` interface +pub struct TimerFuture +where + T: AsyncCount16, +{ + timer: TimerCounter, +} + +impl TimerFuture +where + T: AsyncCount16, +{ + /// Delay asynchronously + #[inline] + pub async fn delay(&mut self, count: NanosDurationU32) { + self.timer.start(count); + self.timer.enable_interrupt(); + + poll_fn(|cx| { + STATE[T::STATE_ID].register(cx.waker()); + if STATE[T::STATE_ID].ready() { + return Poll::Ready(()); + } + + Poll::Pending + }) + .await; + } +} + +impl Drop for TimerFuture +where + T: AsyncCount16, +{ + #[inline] + fn drop(&mut self) { + T::Interrupt::disable(); + } +} + +impl embedded_hal_async::delay::DelayNs for TimerFuture +where + T: AsyncCount16, +{ + async fn delay_ns(&mut self, ns: u32) { + self.delay(NanosDurationU32::from_ticks(ns).convert()).await; + } +} + +// TODO instead of tracking the state manually, we could use ONESHOT +// mode and check the STATUS.STOP bit +struct State { + waker: AtomicWaker, + ready: AtomicBool, +} + +impl State { + #[inline] + const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + ready: AtomicBool::new(false), + } + } + + #[inline] + fn register(&self, waker: &Waker) { + self.waker.register(waker) + } + + #[inline] + fn wake(&self) { + self.ready.store(true, Ordering::SeqCst); + self.waker.wake() + } + + #[inline] + fn ready(&self) -> bool { + self.ready.swap(false, Ordering::SeqCst) + } +} + +#[allow(clippy::declare_interior_mutable_const)] +const STATE_NEW: State = State::new(); +static STATE: [State; NUM_TIMERS] = [STATE_NEW; NUM_TIMERS]; diff --git a/hal/src/dmac/async_api.rs b/hal/src/dmac/async_api.rs new file mode 100644 index 000000000000..7a81a9a87a46 --- /dev/null +++ b/hal/src/dmac/async_api.rs @@ -0,0 +1,93 @@ +//! APIs for async DMAC operations. + +use crate::{ + async_hal::interrupts::{Handler, DMAC}, + dmac::{waker::WAKERS, TriggerSource}, + util::BitIter, +}; + +// Interrupt handler for the DMAC peripheral. +pub struct InterruptHandler { + _private: (), +} + +#[cfg(feature = "thumbv6")] +impl Handler for InterruptHandler { + unsafe fn on_interrupt() { + // SAFETY: Here we can't go through the `with_chid` method to safely access + // the different channel interrupt flags. Instead, we read the ID in a short + // critical section, and make sure to RESET the CHID field to whatever + // it was before this function ran. + let dmac = unsafe { crate::pac::Peripherals::steal().DMAC }; + + critical_section::with(|_| { + let old_id = dmac.chid.read().id().bits(); + let pending_interrupts = BitIter(dmac.intstatus.read().bits()); + + // Iterate over channels and check their interrupt status + for pend_channel in pending_interrupts { + unsafe { dmac.chid.modify(|_, w| w.id().bits(pend_channel as u8)) }; + + let wake = if dmac.chintflag.read().tcmpl().bit_is_set() { + // Transfer complete. Don't clear the flag, but + // disable the interrupt. Flag will be cleared when polled + dmac.chintenclr.modify(|_, w| w.tcmpl().set_bit()); + true + } else if dmac.chintflag.read().terr().bit_is_set() { + // Transfer error + dmac.chintenclr.modify(|_, w| w.terr().set_bit()); + true + } else { + false + }; + + if wake { + dmac.chctrla.modify(|_, w| w.enable().clear_bit()); + dmac.chctrlb + .modify(|_, w| w.trigsrc().variant(TriggerSource::DISABLE)); + WAKERS[pend_channel as usize].wake(); + } + } + + // Reset the CHID.ID register + unsafe { + dmac.chid.write(|w| w.id().bits(old_id)); + } + }); + } +} + +#[cfg(feature = "thumbv7")] +impl Handler for InterruptHandler { + unsafe fn on_interrupt() { + let dmac = unsafe { crate::pac::Peripherals::steal().DMAC }; + + let pending_channels = BitIter(dmac.intstatus.read().bits()); + for channel in pending_channels.map(|c| c as usize) { + let wake = if dmac.channel[channel].chintflag.read().tcmpl().bit_is_set() { + // Transfer complete. Don't clear the flag, but + // disable the interrupt. Flag will be cleared when polled + dmac.channel[channel] + .chintenclr + .modify(|_, w| w.tcmpl().set_bit()); + true + } else if dmac.channel[channel].chintflag.read().terr().bit_is_set() { + // Transfer error + dmac.channel[channel] + .chintenclr + .modify(|_, w| w.terr().set_bit()); + true + } else { + false + }; + + if wake { + dmac.channel[channel].chctrla.modify(|_, w| { + w.enable().clear_bit(); + w.trigsrc().variant(TriggerSource::DISABLE) + }); + WAKERS[channel].wake(); + } + } + } +} diff --git a/hal/src/dmac/channel/mod.rs b/hal/src/dmac/channel/mod.rs index cd555e5741dd..be1193b1ff19 100644 --- a/hal/src/dmac/channel/mod.rs +++ b/hal/src/dmac/channel/mod.rs @@ -39,7 +39,6 @@ use core::marker::PhantomData; use modular_bitfield::prelude::*; mod reg; - use reg::RegisterBlock; #[cfg(feature = "thumbv7")] @@ -48,20 +47,61 @@ use super::dma_controller::{BurstLength, FifoThreshold}; //============================================================================== // Channel Status //============================================================================== -pub trait Status: Sealed {} +pub trait Status: Sealed { + type Uninitialized: Status; + type Ready: Status; +} /// Uninitialized channel pub enum Uninitialized {} impl Sealed for Uninitialized {} -impl Status for Uninitialized {} +impl Status for Uninitialized { + type Uninitialized = Uninitialized; + type Ready = Ready; +} + /// Initialized and ready to transfer channel pub enum Ready {} impl Sealed for Ready {} -impl Status for Ready {} +impl Status for Ready { + type Uninitialized = Uninitialized; + type Ready = Ready; +} + /// Busy channel pub enum Busy {} impl Sealed for Busy {} -impl Status for Busy {} +impl Status for Busy { + type Uninitialized = Uninitialized; + type Ready = Ready; +} + +/// Uninitialized [`Channel`] configured for `async` operation +#[cfg(feature = "async")] +pub enum UninitializedFuture {} +#[cfg(feature = "async")] +impl Sealed for UninitializedFuture {} +#[cfg(feature = "async")] +impl Status for UninitializedFuture { + type Uninitialized = UninitializedFuture; + type Ready = ReadyFuture; +} + +/// Initialized and ready to transfer in `async` operation +#[cfg(feature = "async")] +pub enum ReadyFuture {} +#[cfg(feature = "async")] +impl Sealed for ReadyFuture {} +#[cfg(feature = "async")] +impl Status for ReadyFuture { + type Uninitialized = UninitializedFuture; + type Ready = ReadyFuture; +} + +pub trait ReadyChannel: Status {} +impl ReadyChannel for Ready {} +#[cfg(feature = "async")] +impl ReadyChannel for ReadyFuture {} //============================================================================== // AnyChannel @@ -117,8 +157,10 @@ where //============================================================================== // Channel //============================================================================== + /// DMA channel, capable of executing -/// [`Transfer`](super::transfer::Transfer)s. +/// [`Transfer`](crate::dmac::transfer::Transfer)s. Ongoing DMA transfers are +/// automatically stopped when a [`Channel`] is dropped. pub struct Channel { regs: RegisterBlock, _status: PhantomData, @@ -132,6 +174,15 @@ pub(super) fn new_chan(_id: PhantomData) -> Channel(_id: PhantomData) -> Channel { + Channel { + regs: RegisterBlock::new(_id), + _status: PhantomData, + } +} + /// These methods may be used on any DMA channel in any configuration impl Channel { /// Configure the DMA channel so that it is ready to be used by a @@ -141,7 +192,7 @@ impl Channel { /// /// A `Channel` with a `Ready` status #[inline] - pub fn init(mut self, lvl: PriorityLevel) -> Channel { + pub fn init(mut self, lvl: PriorityLevel) -> Channel { // Software reset the channel for good measure self._reset_private(); @@ -152,10 +203,7 @@ impl Channel { #[cfg(feature = "thumbv7")] self.regs.chprilvl.modify(|_, w| w.prilvl().bits(lvl as u8)); - Channel { - regs: self.regs, - _status: PhantomData, - } + self.change_status() } /// Selectively enable interrupts @@ -190,6 +238,14 @@ impl Channel { InterruptFlags::from_bytes([cleared]) } + #[inline] + pub(super) fn change_status(self) -> Channel { + Channel { + regs: self.regs, + _status: PhantomData, + } + } + #[inline] fn _reset_private(&mut self) { // Reset the channel to its startup state and wait for reset to complete @@ -201,20 +257,45 @@ impl Channel { fn _trigger_private(&mut self) { self.regs.swtrigctrl.set_bit(); } + + #[inline] + fn _enable_private(&mut self) { + self.regs.chctrla.modify(|_, w| w.enable().set_bit()); + } + + /// Stop transfer on channel whether or not the transfer has completed + #[inline] + pub(crate) fn stop(&mut self) { + self.regs.chctrla.modify(|_, w| w.enable().clear_bit()); + } + + /// Returns whether or not the transfer is complete. + #[inline] + pub(crate) fn xfer_complete(&mut self) -> bool { + !self.regs.chctrla.read().enable().bit_is_set() + } + + /// Returns the transfer's success status. + #[allow(dead_code)] + #[inline] + pub(crate) fn xfer_success(&mut self) -> super::Result<()> { + let success = self.regs.chintflag.read().terr().bit_is_clear(); + success.then_some(()).ok_or(super::Error::TransferError) + } } -/// These methods may only be used on a `Ready` DMA channel -impl Channel { +impl Channel +where + Id: ChId, + R: ReadyChannel, +{ /// Issue a software reset to the channel. This will return the channel to /// its startup state #[inline] - pub fn reset(mut self) -> Channel { + pub fn reset(mut self) -> Channel { self._reset_private(); - Channel { - regs: self.regs, - _status: PhantomData, - } + self.change_status() } /// Set the FIFO threshold length. The channel will wait until it has @@ -238,17 +319,31 @@ impl Channel { .modify(|_, w| w.burstlen().bits(burst_length as u8)); } - /// Start transfer on channel using the specified trigger source. + /// Start the transfer. /// - /// # Return + /// # Safety /// - /// A `Channel` with a `Busy` status. + /// This function is unsafe because it starts the transfer without changing + /// the channel status to [`Busy`]. A [`Ready`] channel which is actively + /// transferring should NEVER be leaked. #[inline] - pub(crate) fn start( - mut self, + pub(super) unsafe fn _start_private( + &mut self, trig_src: TriggerSource, trig_act: TriggerAction, - ) -> Channel { + ) { + // Configure the trigger source and trigger action + self.configure_trigger(trig_src, trig_act); + self._enable_private(); + + // If trigger source is DISABLE, manually trigger transfer + if trig_src == TriggerSource::DISABLE { + self._trigger_private(); + } + } + + #[inline] + pub(super) fn configure_trigger(&mut self, trig_src: TriggerSource, trig_act: TriggerAction) { // Configure the trigger source and trigger action #[cfg(feature = "thumbv6")] self.regs.chctrlb.modify(|_, w| { @@ -261,19 +356,25 @@ impl Channel { w.trigsrc().variant(trig_src); w.trigact().variant(trig_act) }); + } +} - // Start channel - self.regs.chctrla.modify(|_, w| w.enable().set_bit()); - - // If trigger source is DISABLE, manually trigger transfer - if trig_src == TriggerSource::DISABLE { - self._trigger_private(); - } - - Channel { - regs: self.regs, - _status: PhantomData, +impl Channel { + /// Start transfer on channel using the specified trigger source. + /// + /// # Return + /// + /// A `Channel` with a `Busy` status. + #[inline] + pub(crate) fn start( + mut self, + trig_src: TriggerSource, + trig_act: TriggerAction, + ) -> Channel { + unsafe { + self._start_private(trig_src, trig_act); } + self.change_status() } } @@ -285,13 +386,8 @@ impl Channel { self._trigger_private(); } - /// Returns whether or not the transfer is complete. - #[inline] - pub(crate) fn xfer_complete(&mut self) -> bool { - !self.regs.chctrla.read().enable().bit_is_set() - } - - /// Stop transfer on channel whether or not the transfer has completed + /// Stop transfer on channel whether or not the transfer has completed, and + /// return the resources it holds. /// /// # Return /// @@ -299,49 +395,129 @@ impl Channel { /// [`Transfer`](super::transfer::Transfer) #[inline] pub(crate) fn free(mut self) -> Channel { - self.regs.chctrla.modify(|_, w| w.enable().clear_bit()); + self.stop(); while !self.xfer_complete() {} - Channel { - regs: self.regs, - _status: PhantomData, - } - } - - #[inline] - pub(super) fn callback(&mut self) -> CallbackStatus { - // Transfer complete - if self.regs.chintflag.read().tcmpl().bit_is_set() { - self.regs.chintflag.modify(|_, w| w.tcmpl().set_bit()); - return CallbackStatus::TransferComplete; - } - // Transfer error - else if self.regs.chintflag.read().terr().bit_is_set() { - self.regs.chintflag.modify(|_, w| w.terr().set_bit()); - return CallbackStatus::TransferError; - } - // Channel suspended - else if self.regs.chintflag.read().susp().bit_is_set() { - self.regs.chintflag.modify(|_, w| w.susp().set_bit()); - return CallbackStatus::TransferSuspended; - } - // Default to error if for some reason there was in interrupt - // flag raised - CallbackStatus::TransferError + self.change_status() } /// Restart transfer using previously-configured trigger source and action #[inline] pub(crate) fn restart(&mut self) { - self.regs.chctrla.modify(|_, w| w.enable().set_bit()); + self._enable_private(); } } impl From> for Channel { - fn from(item: Channel) -> Self { - Channel { - regs: item.regs, - _status: PhantomData, - } + fn from(mut item: Channel) -> Self { + item._reset_private(); + item.change_status() + } +} + +#[cfg(feature = "async")] +impl Channel { + /// Begin DMA transfer using `async` operation. + /// + /// If [TriggerSource::DISABLE] is used, a software + /// trigger will be issued to the DMA channel to launch the transfer. It + /// is therefore not necessary, in most cases, to manually issue a + /// software trigger to the channel. + /// + /// # Safety + /// + /// In `async` mode, a transfer does NOT require `'static` source and + /// destination buffers. This, in theory, makes + /// [`transfer_future`](Channel::transfer_future) an `unsafe` function, + /// although it is marked as safe (for ergonomics). + /// + /// This means that, as an user, you **must** ensure that the [`Future`] + /// returned by this function may never be forgotten through [`forget`]. + /// [`Channel`]s implement [`Drop`] and will automatically stop any ongoing + /// transfers to guarantee that the memory occupied by the now-dropped + /// buffers may not be corrupted by running transfers. This also means that + /// should you [`forget`] this [`Future`] after its first [`poll`] + /// call, the transfer will keep running, ruining the now-reclaimed + /// memory, as well as the rest of your day. + /// + /// * `await`ing is fine: the [`Future`] will run to completion. + /// * Dropping an incomplete transfer is also fine. Dropping can happen, + /// for example, if the transfer doesn't complete before a timeout + /// expires. + /// + /// [`forget`]: core::mem::forget + /// [`Future`]: core::future::Future + /// [`poll`]: core::future::Future::poll + #[cfg(feature = "async")] + #[inline] + pub async fn transfer_future( + &mut self, + mut source: S, + mut dest: D, + trig_src: TriggerSource, + trig_act: TriggerAction, + ) -> Result<(), super::Error> + where + S: super::Buffer, + D: super::Buffer, + { + use crate::dmac::waker::WAKERS; + use core::sync::atomic; + use core::task::Poll; + + super::Transfer::>::check_buffer_pair( + &source, &dest, + )?; + unsafe { + super::Transfer::>::fill_descriptor( + &mut source, + &mut dest, + false, + ) + }; + + self.disable_interrupts( + InterruptFlags::new() + .with_susp(true) + .with_tcmpl(true) + .with_terr(true), + ); + + self.configure_trigger(trig_src, trig_act); + let mut triggered = false; + + core::future::poll_fn(|cx| { + atomic::fence(atomic::Ordering::Release); + self._enable_private(); + + if !triggered && trig_src == TriggerSource::DISABLE { + triggered = true; + self._trigger_private(); + } + + let flags_to_check = InterruptFlags::new().with_tcmpl(true).with_terr(true); + + if self.check_and_clear_interrupts(flags_to_check).tcmpl() { + return Poll::Ready(()); + } + + WAKERS[Id::USIZE].register(cx.waker()); + self.enable_interrupts(flags_to_check); + + if self.check_and_clear_interrupts(flags_to_check).tcmpl() { + self.disable_interrupts(flags_to_check); + + return Poll::Ready(()); + } + + Poll::Pending + }) + .await; + + // Defensively disable channel + self.stop(); + // TODO currently this will always return Ok(()) since we unconditionally clear + // ERROR + self.xfer_success() } } diff --git a/hal/src/dmac/channel/reg.rs b/hal/src/dmac/channel/reg.rs index 5b8b0502e298..3db9d7f097d7 100644 --- a/hal/src/dmac/channel/reg.rs +++ b/hal/src/dmac/channel/reg.rs @@ -277,6 +277,9 @@ reg_proxy!(swtrigctrl, bit, rw); /// Acts as a proxy to the PAC DMAC object. Only registers and bits /// within registers that should be readable/writable by specific /// [`Channel`]s are exposed. +/// +/// This struct implements [`Drop`]. Dropping this struct will stop +/// any ongoing transfers for the channel which it represents. #[allow(dead_code)] pub(super) struct RegisterBlock { pub chctrla: ChctrlaProxy, @@ -311,3 +314,10 @@ impl RegisterBlock { } } } + +impl Drop for RegisterBlock { + fn drop(&mut self) { + // Stop any potentially ongoing transfers + self.chctrla.modify(|_, w| w.enable().clear_bit()); + } +} diff --git a/hal/src/dmac/dma_controller.rs b/hal/src/dmac/dma_controller.rs index 6b573f894be4..1691cb5d98a6 100644 --- a/hal/src/dmac/dma_controller.rs +++ b/hal/src/dmac/dma_controller.rs @@ -16,9 +16,11 @@ //! //! # Releasing the DMAC //! -//! Using the [`DmaController::free`] method will +//! Using the [`free`](DmaController::free) method will //! deinitialize the DMAC and return the underlying PAC object. +use core::marker::PhantomData; + use modular_bitfield::prelude::*; use seq_macro::seq; @@ -38,10 +40,13 @@ pub use crate::pac::dmac::channel::{ }; use super::{ - channel::{new_chan, Channel, Uninitialized}, + channel::{Channel, Uninitialized}, DESCRIPTOR_SECTION, WRITEBACK, }; -use crate::pac::{DMAC, PM}; +use crate::{ + pac::{DMAC, PM}, + typelevel::NoneT, +}; /// Trait representing a DMA channel ID pub trait ChId { @@ -74,9 +79,27 @@ macro_rules! define_channels_struct { with_num_channels!(define_channels_struct); +#[cfg(feature = "async")] +macro_rules! define_channels_struct_future { + ($num_channels:literal) => { + seq!(N in 0..$num_channels { + /// Struct generating individual handles to each DMA channel for `async` operation + pub struct FutureChannels( + #( + pub Channel, + )* + ); + }); + }; +} + +#[cfg(feature = "async")] +with_num_channels!(define_channels_struct_future); + /// Initialized DMA Controller -pub struct DmaController { +pub struct DmaController { dmac: DMAC, + _irqs: PhantomData, } /// Mask representing which priority levels should be enabled/disabled @@ -127,48 +150,7 @@ pub struct RoundRobinMask { level3: bool, } -impl DmaController { - /// Initialize the DMAC and return a DmaController object useable by - /// [`Transfer`](super::transfer::Transfer)'s. By default, all - /// priority levels are enabled unless subsequently disabled using the - /// `level_x_enabled` methods. - #[inline] - pub fn init(mut dmac: DMAC, _pm: &mut PM) -> Self { - // ----- Initialize clocking ----- // - #[cfg(feature = "thumbv6")] - { - // Enable clocking - _pm.ahbmask.modify(|_, w| w.dmac_().set_bit()); - _pm.apbbmask.modify(|_, w| w.dmac_().set_bit()); - } - - Self::swreset(&mut dmac); - - // SAFETY this is safe because we write a whole u32 to 32-bit registers, - // and the descriptor array addesses will never change since they are static. - // We just need to ensure the writeback and descriptor_section addresses - // are valid. - unsafe { - dmac.baseaddr - .write(|w| w.baseaddr().bits(DESCRIPTOR_SECTION.as_ptr() as u32)); - dmac.wrbaddr - .write(|w| w.wrbaddr().bits(WRITEBACK.as_ptr() as u32)); - } - - // ----- Select priority levels ----- // - dmac.ctrl.modify(|_, w| { - w.lvlen3().set_bit(); - w.lvlen2().set_bit(); - w.lvlen1().set_bit(); - w.lvlen0().set_bit() - }); - - // Enable DMA controller - dmac.ctrl.modify(|_, w| w.dmaenable().set_bit()); - - Self { dmac } - } - +impl DmaController { /// Enable multiple priority levels simultaneously #[inline] pub fn enable_levels(&mut self, mask: PriorityLevelMask) { @@ -219,6 +201,87 @@ impl DmaController { } } + /// Use the [`DmaController`] in async mode. You are required to provide the + /// struct created by the + /// [`bind_interrupts`](crate::bind_interrupts) macro to prove + /// that the interrupt sources have been correctly configured. This function + /// will automatically enable the relevant NVIC interrupt sources. However, + /// you are required to configure the desired interrupt priorities prior to + /// calling this method. Consult [`crate::async_hal::interrupts`] + /// module-level documentation for more information. + /// [`bind_interrupts`](crate::bind_interrupts). + #[cfg(feature = "async")] + #[inline] + pub fn into_future(self, _interrupts: I) -> DmaController + where + I: crate::async_hal::interrupts::Binding< + crate::async_hal::interrupts::DMAC, + super::async_api::InterruptHandler, + >, + { + use crate::async_hal::interrupts::{InterruptSource, DMAC}; + + DMAC::unpend(); + unsafe { DMAC::enable() }; + + DmaController { + dmac: self.dmac, + _irqs: PhantomData, + } + } + + /// Issue a software reset to the DMAC and wait for reset to complete + #[inline] + fn swreset(dmac: &mut DMAC) { + dmac.ctrl.modify(|_, w| w.swrst().set_bit()); + while dmac.ctrl.read().swrst().bit_is_set() {} + } +} + +impl DmaController { + /// Initialize the DMAC and return a DmaController object useable by + /// [`Transfer`](super::transfer::Transfer)'s. By default, all + /// priority levels are enabled unless subsequently disabled using the + /// `level_x_enabled` methods. + #[inline] + pub fn init(mut dmac: DMAC, _pm: &mut PM) -> Self { + // ----- Initialize clocking ----- // + #[cfg(feature = "thumbv6")] + { + // Enable clocking + _pm.ahbmask.modify(|_, w| w.dmac_().set_bit()); + _pm.apbbmask.modify(|_, w| w.dmac_().set_bit()); + } + + Self::swreset(&mut dmac); + + // SAFETY this is safe because we write a whole u32 to 32-bit registers, + // and the descriptor array addesses will never change since they are static. + // We just need to ensure the writeback and descriptor_section addresses + // are valid. + unsafe { + dmac.baseaddr + .write(|w| w.baseaddr().bits(DESCRIPTOR_SECTION.as_ptr() as u32)); + dmac.wrbaddr + .write(|w| w.wrbaddr().bits(WRITEBACK.as_ptr() as u32)); + } + + // ----- Select priority levels ----- // + dmac.ctrl.modify(|_, w| { + w.lvlen3().set_bit(); + w.lvlen2().set_bit(); + w.lvlen1().set_bit(); + w.lvlen0().set_bit() + }); + + // Enable DMA controller + dmac.ctrl.modify(|_, w| w.dmaenable().set_bit()); + Self { + dmac, + _irqs: PhantomData, + } + } + /// Release the DMAC and return the register block. /// /// **Note**: The [`Channels`] struct is consumed by this method. This means @@ -241,12 +304,37 @@ impl DmaController { // Release the DMAC self.dmac } +} - /// Issue a software reset to the DMAC and wait for reset to complete +#[cfg(feature = "async")] +impl DmaController +where + I: crate::async_hal::interrupts::Binding< + crate::async_hal::interrupts::DMAC, + super::async_api::InterruptHandler, + >, +{ + /// Release the DMAC and return the register block. + /// + /// **Note**: The [`Channels`] struct is consumed by this method. This means + /// that any [`Channel`] obtained by [`split`](DmaController::split) must be + /// moved back into the [`Channels`] struct before being able to pass it + /// into [`free`](DmaController::free). #[inline] - fn swreset(dmac: &mut DMAC) { - dmac.ctrl.modify(|_, w| w.swrst().set_bit()); - while dmac.ctrl.read().swrst().bit_is_set() {} + pub fn free(mut self, _channels: FutureChannels, _pm: &mut PM) -> DMAC { + self.dmac.ctrl.modify(|_, w| w.dmaenable().clear_bit()); + + Self::swreset(&mut self.dmac); + + #[cfg(any(feature = "samd11", feature = "samd21"))] + { + // Disable the DMAC clocking + _pm.apbbmask.modify(|_, w| w.dmac_().clear_bit()); + _pm.ahbmask.modify(|_, w| w.dmac_().clear_bit()); + } + + // Release the DMAC + self.dmac } } @@ -258,7 +346,7 @@ macro_rules! define_split { pub fn split(&mut self) -> Channels { Channels( #( - new_chan(core::marker::PhantomData), + crate::dmac::channel::new_chan(core::marker::PhantomData), )* ) } @@ -269,3 +357,31 @@ macro_rules! define_split { impl DmaController { with_num_channels!(define_split); } + +#[cfg(feature = "async")] +macro_rules! define_split_future { + ($num_channels:literal) => { + seq!(N in 0..$num_channels { + /// Split the DMAC into individual channels + #[inline] + pub fn split(&mut self) -> FutureChannels { + FutureChannels( + #( + crate::dmac::channel::new_chan_future(core::marker::PhantomData), + )* + ) + } + }); + }; +} + +#[cfg(feature = "async")] +impl DmaController +where + I: crate::async_hal::interrupts::Binding< + crate::async_hal::interrupts::DMAC, + super::async_api::InterruptHandler, + >, +{ + with_num_channels!(define_split_future); +} diff --git a/hal/src/dmac/mod.rs b/hal/src/dmac/mod.rs index 43d2377f2046..b0925f7e9ca1 100644 --- a/hal/src/dmac/mod.rs +++ b/hal/src/dmac/mod.rs @@ -101,7 +101,7 @@ //! stack-allocated buffers for reuse while the DMAC is still writing to/reading //! from them! Needless to say that is very unsafe. //! Refer [here](https://docs.rust-embedded.org/embedonomicon/dma.html#memforget) -//! or [here](https://blog.japaric.io/safe-dma/) for more information. You may choose to forego +//! or [here](https://blog.japaric.io/safe-dma/#leakpocalypse) for more information. You may choose to forego //! the `'static` lifetimes by using the unsafe API and the //! [`Transfer::new_unchecked`](transfer::Transfer::new_unchecked) method. //! @@ -117,7 +117,7 @@ //! `Drop` implementation is offered for `Transfer`s. //! //! Additionally, you can (unsafely) implement your own buffer types through the -//! unsafe [`Buffer`](transfer::Buffer) trait. +//! unsafe [`Buffer`] trait. //! //! # Example //! ``` @@ -272,6 +272,9 @@ pub enum Error { /// Operation is not valid in the current state of the object. InvalidState, + + /// Chip reported an error during transfer + TransferError, } /// Result for DMAC operations @@ -395,3 +398,18 @@ static mut DESCRIPTOR_SECTION: [DmacDescriptor; NUM_CHANNELS] = [DEFAULT_DESCRIP pub mod channel; pub mod dma_controller; pub mod transfer; + +#[cfg(feature = "async")] +pub mod async_api; +#[cfg(feature = "async")] +pub use async_api::*; + +#[cfg(feature = "async")] +mod waker { + use embassy_sync::waitqueue::AtomicWaker; + + #[allow(clippy::declare_interior_mutable_const)] + const NEW_WAKER: AtomicWaker = AtomicWaker::new(); + pub(super) static WAKERS: [AtomicWaker; with_num_channels!(get)] = + [NEW_WAKER; with_num_channels!(get)]; +} diff --git a/hal/src/dmac/transfer.rs b/hal/src/dmac/transfer.rs index ffd409c360fd..e40b4b53f967 100644 --- a/hal/src/dmac/transfer.rs +++ b/hal/src/dmac/transfer.rs @@ -84,9 +84,9 @@ //! transaction, that will now run uninterrupted until it is stopped. use super::{ - channel::{AnyChannel, Busy, CallbackStatus, Channel, ChannelId, InterruptFlags, Ready}, + channel::{AnyChannel, Busy, Channel, ChannelId, InterruptFlags, Ready}, dma_controller::{ChId, TriggerAction, TriggerSource}, - BlockTransferControl, DmacDescriptor, Error, Result, DESCRIPTOR_SECTION, + BlockTransferControl, DmacDescriptor, Error, ReadyChannel, Result, DESCRIPTOR_SECTION, }; use crate::typelevel::{Is, Sealed}; use core::{ptr::null_mut, sync::atomic}; @@ -109,7 +109,7 @@ pub enum BeatSize { } /// Convert 8, 16 and 32 bit types -/// into [`BeatSize`](BeatSize) +/// into [`BeatSize`] /// /// # Safety /// @@ -303,22 +303,22 @@ where // TODO change source and dest types to Pin? (see https://docs.rust-embedded.org/embedonomicon/dma.html#immovable-buffers) /// DMA transfer, owning the resources until the transfer is done and /// [`Transfer::wait`] is called. -pub struct Transfer +pub struct Transfer where Buf: AnyBufferPair, Chan: AnyChannel, { chan: Chan, buffers: Buf, - waker: Option, complete: bool, } -impl Transfer> +impl Transfer> where S: Buffer + 'static, D: Buffer + 'static, - C: AnyChannel, + C: AnyChannel, + R: ReadyChannel, { /// Safely construct a new `Transfer`. To guarantee memory safety, both /// buffers are required to be `'static`. @@ -329,9 +329,10 @@ where /// (as opposed to slices), then it is recommended to use the /// [`Transfer::new_from_arrays`] method instead. /// - /// # Panics + /// # Errors /// - /// Panics if both buffers have a length > 1 and are not of equal length. + /// Returns [`Error::LengthMismatch`] if both + /// buffers have a length > 1 and are not of equal length. #[allow(clippy::new_ret_no_self)] #[inline] pub fn new( @@ -348,14 +349,14 @@ where } } -impl Transfer, W> +impl Transfer> where S: Buffer, D: Buffer, C: AnyChannel, { #[inline] - fn check_buffer_pair(source: &S, destination: &D) -> Result<()> { + pub(super) fn check_buffer_pair(source: &S, destination: &D) -> Result<()> { let src_len = source.buffer_len(); let dst_len = destination.buffer_len(); @@ -367,7 +368,7 @@ where } #[inline] - unsafe fn fill_descriptor(source: &mut S, destination: &mut D, circular: bool) { + pub(super) unsafe fn fill_descriptor(source: &mut S, destination: &mut D, circular: bool) { let id = ::Id::USIZE; // Enable support for circular transfers. If circular_xfer is true, @@ -426,11 +427,12 @@ where } } -impl Transfer> +impl Transfer> where S: Buffer, D: Buffer, - C: AnyChannel, + C: AnyChannel, + R: ReadyChannel, { /// Construct a new `Transfer` without checking for memory safety. /// @@ -463,7 +465,7 @@ where Transfer { buffers, chan, - waker: None, + complete: false, } } @@ -475,38 +477,17 @@ where D: Buffer, C: AnyChannel, { - /// Append a waker to the transfer. This will be called when the DMAC - /// interrupt is called. - #[inline] - pub fn with_waker( - self, - waker: W, - ) -> Transfer, W> { - Transfer { - buffers: self.buffers, - chan: self.chan, - complete: self.complete, - waker: Some(waker), - } - } -} - -impl Transfer, W> -where - S: Buffer, - D: Buffer, - C: AnyChannel, -{ - /// Begin DMA transfer. If [TriggerSource::DISABLE](TriggerSource::DISABLE) - /// is used, a software trigger will be issued to the DMA channel to - /// launch the transfer. Is is therefore not necessary, in most cases, - /// to manually issue a software trigger to the channel. + /// Begin DMA transfer in blocking mode. If + /// [TriggerSource::DISABLE] is used, a software + /// trigger will be issued to the DMA channel to launch the transfer. Is + /// is therefore not necessary, in most cases, to manually issue a + /// software trigger to the channel. #[inline] pub fn begin( mut self, trig_src: TriggerSource, trig_act: TriggerAction, - ) -> Transfer, Busy>, BufferPair, W> { + ) -> Transfer, Busy>, BufferPair> { // Reset the complete flag before triggering the transfer. // This way an interrupt handler could set complete to true // before this function returns. @@ -521,16 +502,29 @@ where Transfer { buffers: self.buffers, chan, - waker: self.waker, complete: self.complete, } } + + /// Free the [`Transfer`] and return the resources it holds. + /// + /// Similar to [`stop`](Transfer::stop), but it acts on a [`Transfer`] + /// holding a [`Ready`] channel, so there is no need to explicitly stop the + /// transfer. + pub fn free(self) -> (Channel, Ready>, S, D) { + ( + self.chan.into(), + self.buffers.source, + self.buffers.destination, + ) + } } -impl Transfer> +impl Transfer> where B: 'static + Beat, - C: AnyChannel, + C: AnyChannel, + R: ReadyChannel, { /// Create a new `Transfer` from static array references of the same type /// and length. When two array references are available (instead of slice @@ -549,7 +543,7 @@ where } } -impl Transfer, W> +impl Transfer> where S: Buffer, D: Buffer, @@ -642,14 +636,12 @@ where }; let old_buffers = core::mem::replace(&mut self.buffers, new_buffers); - self.chan.as_mut().restart(); - Ok((old_buffers.source, old_buffers.destination)) } /// Modify a completed transfer with a new `destination`, then restart. - + /// /// Returns a Result containing the destination from the /// completed transfer. Returns `Err(_)` if the buffer lengths are /// mismatched or if the previous transfer has not yet completed. @@ -667,14 +659,12 @@ where } let old_destination = core::mem::replace(&mut self.buffers.destination, destination); - self.chan.as_mut().restart(); - Ok(old_destination) } /// Modify a completed transfer with a new `source`, then restart. - + /// /// Returns a Result containing the source from the /// completed transfer. Returns `Err(_)` if the buffer lengths are /// mismatched or if the previous transfer has not yet completed. @@ -692,9 +682,7 @@ where } let old_source = core::mem::replace(&mut self.buffers.source, source); - self.chan.as_mut().restart(); - Ok(old_source) } @@ -712,26 +700,3 @@ where (chan, self.buffers.source, self.buffers.destination) } } - -impl Transfer, W> -where - S: Buffer, - D: Buffer, - C: AnyChannel, - W: FnOnce(CallbackStatus) + 'static, -{ - /// This function should be put inside the DMAC interrupt handler. - /// It will take care of calling the [`Transfer`]'s waker (if it exists). - #[inline] - pub fn callback(&mut self) { - let status = self.chan.as_mut().callback(); - - if let CallbackStatus::TransferComplete = status { - self.complete = true; - } - - if let Some(w) = self.waker.take() { - w(status) - } - } -} diff --git a/hal/src/lib.rs b/hal/src/lib.rs index a266e3f82f85..479fa5059c66 100644 --- a/hal/src/lib.rs +++ b/hal/src/lib.rs @@ -4,6 +4,7 @@ pub use embedded_hal as ehal; pub use fugit; pub use paste; pub mod typelevel; +mod util; #[cfg(not(any(feature = "library", feature = "device")))] compile_error!( @@ -82,6 +83,9 @@ pub mod time; pub mod timer_params; pub mod timer_traits; +#[cfg(feature = "async")] +pub mod async_hal; + #[cfg(feature = "dma")] pub mod dmac; diff --git a/hal/src/sercom/async_dma.rs b/hal/src/sercom/async_dma.rs new file mode 100644 index 000000000000..3915a2cd45fb --- /dev/null +++ b/hal/src/sercom/async_dma.rs @@ -0,0 +1,151 @@ +//! Use the DMA Controller to perform async transfers using the SERCOM +//! peripheral +//! +//! See the [`mod@uart`], [`mod@i2c`] and [`mod@spi`] modules for the +//! corresponding DMA transfer implementations. + +use crate::dmac::{AnyChannel, Beat, Buffer, Error, ReadyFuture, TriggerAction}; +use core::ops::Range; + +use super::Sercom; + +/// Wrapper type over an `&[T]` that can be used as a source buffer for DMA +/// transfers. This is an implementation detail to make async SERCOM-DMA +/// transfers work. Should not be used outside of this crate. +/// +/// # Safety +/// +/// [`ImmutableSlice`]s should only ever be used as **source** buffers for DMA +/// transfers, and never as destination buffers. +#[doc(hidden)] +pub(crate) struct ImmutableSlice(Range<*mut T>); + +impl ImmutableSlice { + #[inline] + pub(in super::super) fn from_slice(slice: &[T]) -> Self { + let ptrs = slice.as_ptr_range(); + + let ptrs = Range { + start: ptrs.start.cast_mut(), + end: ptrs.end.cast_mut(), + }; + + ImmutableSlice(ptrs) + } +} + +unsafe impl Buffer for ImmutableSlice { + type Beat = T; + #[inline] + fn dma_ptr(&mut self) -> *mut Self::Beat { + if self.incrementing() { + self.0.end + } else { + self.0.start + } + } + + #[inline] + fn incrementing(&self) -> bool { + self.buffer_len() > 1 + } + + #[inline] + fn buffer_len(&self) -> usize { + self.0.end as usize - self.0.start as usize + } +} + +/// Wrapper type over Sercom instances to get around lifetime issues when using +/// one as a DMA source/destination buffer. This is an implementation detail to +/// make async SERCOM-DMA transfers work. +#[doc(hidden)] +#[derive(Clone)] +pub(crate) struct SercomPtr(pub(in super::super) *mut T); + +unsafe impl Buffer for SercomPtr { + type Beat = T; + + #[inline] + fn dma_ptr(&mut self) -> *mut Self::Beat { + self.0 + } + + #[inline] + fn incrementing(&self) -> bool { + false + } + + #[inline] + fn buffer_len(&self) -> usize { + 1 + } +} + +/// Perform a SERCOM DMA read with a provided `&mut [T]` +pub(super) async fn read_dma( + channel: &mut impl AnyChannel, + sercom_ptr: SercomPtr, + words: &mut [T], +) -> Result<(), Error> { + read_dma_buffer::<_, _, S>(channel, sercom_ptr, words).await +} + +/// Perform a SERCOM DMA read with a provided [`Buffer`] +pub(super) async fn read_dma_buffer( + channel: &mut impl AnyChannel, + sercom_ptr: SercomPtr, + buf: B, +) -> Result<(), Error> +where + T: Beat, + B: Buffer, + S: Sercom, +{ + #[cfg(feature = "thumbv7")] + let trigger_action = TriggerAction::BURST; + + #[cfg(feature = "thumbv6")] + let trigger_action = TriggerAction::BEAT; + + channel + .as_mut() + .transfer_future(sercom_ptr, buf, S::DMA_RX_TRIGGER, trigger_action) + .await +} + +/// Perform a SERCOM DMA write with a provided `&[T]` +pub(super) async fn write_dma( + channel: &mut impl AnyChannel, + sercom_ptr: SercomPtr, + words: &[T], +) -> Result<(), Error> { + // SAFETY: Using ImmutableSlice is safe because we hold on + // to words as long as the transfer hasn't completed. + let words = ImmutableSlice::from_slice(words); + + write_dma_buffer::<_, _, S>(channel, sercom_ptr, words).await +} + +/// Perform a SERCOM DMA write with a provided [`Buffer`] +pub(super) async fn write_dma_buffer( + channel: &mut impl AnyChannel, + sercom_ptr: SercomPtr, + buf: B, +) -> Result<(), Error> +where + T: Beat, + B: Buffer, + S: Sercom, +{ + #[cfg(feature = "thumbv7")] + let trigger_action = TriggerAction::BURST; + + #[cfg(feature = "thumbv6")] + let trigger_action = TriggerAction::BEAT; + + channel + .as_mut() + .transfer_future(buf, sercom_ptr, S::DMA_TX_TRIGGER, trigger_action) + .await +} diff --git a/hal/src/sercom/dma.rs b/hal/src/sercom/dma.rs index 070ef41be137..809a17014757 100644 --- a/hal/src/sercom/dma.rs +++ b/hal/src/sercom/dma.rs @@ -6,7 +6,7 @@ use crate::{ dmac::{ self, - channel::{AnyChannel, Busy, CallbackStatus, Channel, InterruptFlags, Ready}, + channel::{AnyChannel, Busy, Channel, InterruptFlags, Ready}, transfer::BufferPair, Beat, Buffer, Transfer, TriggerAction, }, @@ -77,18 +77,16 @@ impl I2c { /// /// It is recommended that you check for errors after the transfer is /// complete by calling [`read_status`](I2c::read_status). - pub fn receive_with_dma( + pub fn receive_with_dma( self, address: u8, _ready_token: I2cBusReady, buf: B, mut channel: Ch, - waker: W, - ) -> Transfer, BufferPair, W> + ) -> Transfer, BufferPair> where Ch: AnyChannel, B: Buffer + 'static, - W: FnOnce(CallbackStatus) + 'static, { let len = buf.buffer_len(); assert!(len > 0 && len <= 255); @@ -106,9 +104,7 @@ impl I2c { // SAFETY: This is safe because the of the `'static` bound check // for `B`, and the fact that the buffer length of an `I2c` is always 1. let xfer = unsafe { dmac::Transfer::new_unchecked(channel, self, buf, false) }; - let mut xfer = xfer - .with_waker(waker) - .begin(C::Sercom::DMA_RX_TRIGGER, trigger_action); + let mut xfer = xfer.begin(C::Sercom::DMA_RX_TRIGGER, trigger_action); // SAFETY: we borrow the source from under a `Busy` transfer. While the type // system believes the transfer is running, we haven't enabled it in the @@ -125,18 +121,16 @@ impl I2c { /// It is recommended that you check for errors after the transfer is /// complete by calling [`read_status`](I2c::read_status). #[inline] - pub fn send_with_dma( + pub fn send_with_dma( self, address: u8, _ready_token: I2cBusReady, buf: B, mut channel: Ch, - waker: W, - ) -> Transfer, BufferPair, W> + ) -> Transfer, BufferPair> where Ch: AnyChannel, B: Buffer + 'static, - W: FnOnce(CallbackStatus) + 'static, { let len = buf.buffer_len(); assert!(len > 0 && len <= 255); @@ -154,9 +148,7 @@ impl I2c { // SAFETY: This is safe because the of the `'static` bound check // for `B`, and the fact that the buffer length of an `I2c` is always 1. let xfer = unsafe { dmac::Transfer::new_unchecked(channel, buf, self, false) }; - let mut xfer = xfer - .with_waker(waker) - .begin(C::Sercom::DMA_TX_TRIGGER, trigger_action); + let mut xfer = xfer.begin(C::Sercom::DMA_TX_TRIGGER, trigger_action); // SAFETY: we borrow the source from under a `Busy` transfer. While the type // system believes the transfer is running, we haven't enabled it in the @@ -206,16 +198,14 @@ where /// Transform an [`Uart`] into a DMA [`Transfer`]) and /// start receiving into the provided buffer. #[inline] - pub fn receive_with_dma( + pub fn receive_with_dma( self, buf: B, mut channel: Ch, - waker: W, - ) -> Transfer, BufferPair, W> + ) -> Transfer, BufferPair> where Ch: AnyChannel, B: Buffer + 'static, - W: FnOnce(CallbackStatus) + 'static, { channel .as_mut() @@ -230,8 +220,7 @@ where // SAFETY: This is safe because the of the `'static` bound check // for `B`, and the fact that the buffer length of an `Uart` is always 1. let xfer = unsafe { dmac::Transfer::new_unchecked(channel, self, buf, false) }; - xfer.with_waker(waker) - .begin(C::Sercom::DMA_RX_TRIGGER, trigger_action) + xfer.begin(C::Sercom::DMA_RX_TRIGGER, trigger_action) } } @@ -244,16 +233,14 @@ where /// Transform an [`Uart`] into a DMA [`Transfer`]) and /// start sending the provided buffer. #[inline] - pub fn send_with_dma( + pub fn send_with_dma( self, buf: B, mut channel: Ch, - waker: W, - ) -> Transfer, BufferPair, W> + ) -> Transfer, BufferPair> where Ch: AnyChannel, B: Buffer + 'static, - W: FnOnce(CallbackStatus) + 'static, { channel .as_mut() @@ -268,8 +255,7 @@ where // SAFETY: This is safe because the of the `'static` bound check // for `B`, and the fact that the buffer length of an `Uart` is always 1. let xfer = unsafe { dmac::Transfer::new_unchecked(channel, buf, self, false) }; - xfer.with_waker(waker) - .begin(C::Sercom::DMA_TX_TRIGGER, trigger_action) + xfer.begin(C::Sercom::DMA_TX_TRIGGER, trigger_action) } } @@ -312,16 +298,14 @@ where /// Transform an [`Spi`] into a DMA [`Transfer`]) and /// start a send transaction. #[inline] - pub fn send_with_dma( + pub fn send_with_dma( self, buf: B, mut channel: Ch, - waker: W, - ) -> Transfer, BufferPair, W> + ) -> Transfer, BufferPair> where Ch: AnyChannel, B: Buffer + 'static, - W: FnOnce(CallbackStatus) + 'static, { channel .as_mut() @@ -336,8 +320,7 @@ where // SAFETY: This is safe because the of the `'static` bound check // for `B`, and the fact that the buffer length of an `Spi` is always 1. let xfer = unsafe { Transfer::new_unchecked(channel, buf, self, false) }; - xfer.with_waker(waker) - .begin(C::Sercom::DMA_TX_TRIGGER, trigger_action) + xfer.begin(C::Sercom::DMA_TX_TRIGGER, trigger_action) } } @@ -350,16 +333,14 @@ where /// Transform an [`Spi`] into a DMA [`Transfer`]) and /// start a receive transaction. #[inline] - pub fn receive_with_dma( + pub fn receive_with_dma( self, buf: B, mut channel: Ch, - waker: W, - ) -> Transfer, BufferPair, W> + ) -> Transfer, BufferPair> where Ch: AnyChannel, B: Buffer + 'static, - W: FnOnce(CallbackStatus) + 'static, { channel .as_mut() @@ -374,7 +355,6 @@ where // SAFETY: This is safe because the of the `'static` bound check // for `B`, and the fact that the buffer length of an `Spi` is always 1. let xfer = unsafe { Transfer::new_unchecked(channel, self, buf, false) }; - xfer.with_waker(waker) - .begin(C::Sercom::DMA_RX_TRIGGER, trigger_action) + xfer.begin(C::Sercom::DMA_RX_TRIGGER, trigger_action) } } diff --git a/hal/src/sercom/i2c.rs b/hal/src/sercom/i2c.rs index 64e9225132a6..b9d1f46d2f1b 100644 --- a/hal/src/sercom/i2c.rs +++ b/hal/src/sercom/i2c.rs @@ -276,6 +276,12 @@ pub use config::*; mod impl_ehal; +#[cfg(feature = "async")] +mod async_api; + +#[cfg(feature = "async")] +pub use async_api::*; + /// Word size for an I2C message pub type Word = u8; @@ -295,7 +301,7 @@ pub enum InactiveTimeout { /// Abstraction over a I2C peripheral, allowing to perform I2C transactions. pub struct I2c { - config: C, + pub(super) config: C, } impl I2c { diff --git a/hal/src/sercom/i2c/async_api.rs b/hal/src/sercom/i2c/async_api.rs new file mode 100644 index 000000000000..4c0548e7f448 --- /dev/null +++ b/hal/src/sercom/i2c/async_api.rs @@ -0,0 +1,405 @@ +use crate::{ + async_hal::interrupts::{Binding, Handler, InterruptSource}, + sercom::{ + i2c::{self, AnyConfig, Flags, I2c}, + Sercom, + }, + typelevel::NoneT, +}; +use core::{marker::PhantomData, task::Poll}; +use cortex_m::interrupt::InterruptNumber; + +/// Interrupt handler for async I2C operarions +pub struct InterruptHandler { + _private: (), + _sercom: PhantomData, +} + +impl Handler for InterruptHandler { + // TODO the ISR gets called twice on every MB request??? + #[inline] + unsafe fn on_interrupt() { + let mut peripherals = unsafe { crate::pac::Peripherals::steal() }; + let i2cm = S::reg_block(&mut peripherals).i2cm(); + let flags_to_check = Flags::all(); + let flags_pending = Flags::from_bits_truncate(i2cm.intflag.read().bits()); + + // Disable interrupts, but don't clear the flags. The future will take care of + // clearing flags and re-enabling interrupts when woken. + if flags_to_check.contains(flags_pending) { + i2cm.intenclr + .write(|w| unsafe { w.bits(flags_pending.bits()) }); + S::rx_waker().wake(); + } + } +} + +impl I2c +where + C: AnyConfig, + S: Sercom, +{ + /// Turn an [`I2c`] into an [`I2cFuture`] + #[inline] + pub fn into_future(self, _interrupts: I) -> I2cFuture + where + I: Binding>, + { + S::Interrupt::unpend(); + unsafe { S::Interrupt::enable() }; + + I2cFuture { + i2c: self, + _dma_channel: NoneT, + } + } +} + +/// `async` version of [`I2c`]. +/// +/// Create this struct by calling [`I2c::into_future`](I2c::into_future). +pub struct I2cFuture +where + C: AnyConfig, +{ + pub(in super::super) i2c: I2c, + _dma_channel: D, +} + +#[cfg(feature = "dma")] +/// Convenience type for a [`I2cFuture`] in DMA +/// mode. The type parameter `I` represents the DMA channel ID (`ChX`). +pub type I2cFutureDma = I2cFuture>; + +impl I2cFuture +where + C: AnyConfig, + S: Sercom, +{ + async fn wait_flags(&mut self, flags_to_wait: Flags) { + core::future::poll_fn(|cx| { + // Scope maybe_pending so we don't forget to re-poll the register later down. + { + let maybe_pending = self.i2c.config.as_ref().registers.read_flags(); + if flags_to_wait.intersects(maybe_pending) { + return Poll::Ready(()); + } + } + + self.i2c.disable_interrupts(Flags::all()); + // By convention, I2C uses the sercom's RX waker. + S::rx_waker().register(cx.waker()); + self.i2c.enable_interrupts(flags_to_wait); + let maybe_pending = self.i2c.config.as_ref().registers.read_flags(); + + if !flags_to_wait.intersects(maybe_pending) { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + } +} + +impl I2cFuture +where + C: AnyConfig, + S: Sercom, +{ + /// Use a DMA channel for reads/writes + #[cfg(feature = "dma")] + pub fn with_dma_channel>( + self, + dma_channel: D, + ) -> I2cFuture { + I2cFuture { + i2c: self.i2c, + _dma_channel: dma_channel, + } + } + + /// Return the underlying [`I2c`]. + pub fn free(self) -> I2c { + self.i2c + } + + /// Asynchronously write from a buffer. + #[inline] + pub async fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), i2c::Error> { + self.i2c.config.as_mut().registers.start_write(addr)?; + + for b in bytes { + self.wait_flags(Flags::MB | Flags::ERROR).await; + self.i2c.read_status().check_bus_error()?; + + self.i2c.config.as_mut().registers.write_one(*b); + } + + self.i2c.cmd_stop(); + + Ok(()) + } + + /// Asynchronously read into a buffer. + #[inline] + pub async fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), i2c::Error> { + self.i2c.config.as_mut().registers.start_read(addr)?; + + // Some manual iterator gumph because we need to ack bytes after the first. + let mut iter = buffer.iter_mut(); + *iter.next().expect("buffer len is at least 1") = self.read_one().await; + + loop { + match iter.next() { + None => break, + Some(dest) => { + // Ack the last byte so we can receive another one + self.i2c.config.as_mut().registers.cmd_read(); + *dest = self.read_one().await; + } + } + } + + // Arrange to send NACK on next command to + // stop slave from transmitting more data + self.i2c + .config + .as_mut() + .registers + .i2c_master() + .ctrlb + .modify(|_, w| w.ackact().set_bit()); + + Ok(()) + } + + /// Asynchronously write from a buffer, then read into a buffer. This is an + /// extremely common pattern: writing a register address, then + /// read its value from the slave. + #[inline] + pub async fn write_read( + &mut self, + addr: u8, + write_buf: &[u8], + read_buf: &mut [u8], + ) -> Result<(), i2c::Error> { + self.write(addr, write_buf).await?; + self.read(addr, read_buf).await?; + Ok(()) + } + + async fn read_one(&mut self) -> u8 { + self.wait_flags(Flags::SB | Flags::ERROR).await; + self.i2c.config.as_mut().registers.read_one() + } +} + +// impl Drop for I2cFuture +// where +// C: AnyConfig, +// N: InterruptNumber, +// { +// #[inline] +// fn drop(&mut self) { +// cortex_m::peripheral::NVIC::mask(self.irq_number); +// } +// } + +impl AsRef> for I2cFuture +where + C: AnyConfig, + N: InterruptNumber, +{ + #[inline] + fn as_ref(&self) -> &I2c { + &self.i2c + } +} + +impl AsMut> for I2cFuture +where + C: AnyConfig, + N: InterruptNumber, +{ + #[inline] + fn as_mut(&mut self) -> &mut I2c { + &mut self.i2c + } +} + +mod impl_ehal { + use super::*; + use embedded_hal_async::i2c::{ErrorType, I2c as I2cTrait, Operation}; + + impl ErrorType for I2cFuture + where + C: AnyConfig, + { + type Error = i2c::Error; + } + + impl I2cTrait for I2cFuture + where + C: AnyConfig, + { + #[inline] + async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { + self.read(address, buffer).await?; + Ok(()) + } + + #[inline] + async fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(address, bytes).await?; + Ok(()) + } + + #[inline] + async fn write_read( + &mut self, + address: u8, + wr_buffer: &[u8], + rd_buffer: &mut [u8], + ) -> Result<(), Self::Error> { + self.write_read(address, wr_buffer, rd_buffer).await?; + Ok(()) + } + + #[inline] + async fn transaction<'mut_self, 'a, 'b>( + &'mut_self mut self, + address: u8, + operations: &'a mut [embedded_hal_async::i2c::Operation<'b>], + ) -> Result<(), Self::Error> { + for op in operations { + match op { + Operation::Read(buf) => self.read(address, buf).await?, + Operation::Write(buf) => self.write(address, buf).await?, + } + } + + Ok(()) + } + } + + #[cfg(feature = "dma")] + impl I2cTrait for I2cFuture + where + C: AnyConfig, + D: crate::dmac::AnyChannel, + { + #[inline] + async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { + self.read(address, buffer).await?; + Ok(()) + } + + #[inline] + async fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(address, bytes).await?; + Ok(()) + } + + #[inline] + async fn write_read( + &mut self, + address: u8, + wr_buffer: &[u8], + rd_buffer: &mut [u8], + ) -> Result<(), Self::Error> { + self.write_read(address, wr_buffer, rd_buffer).await?; + Ok(()) + } + + #[inline] + async fn transaction<'mut_self, 'a, 'b>( + &'mut_self mut self, + address: u8, + operations: &'a mut [embedded_hal_async::i2c::Operation<'b>], + ) -> Result<(), Self::Error> { + for op in operations { + match op { + Operation::Read(buf) => self.read(address, buf).await?, + Operation::Write(buf) => self.write(address, buf).await?, + } + } + + Ok(()) + } + } +} + +#[cfg(feature = "dma")] +mod dma { + use super::*; + use crate::dmac::{AnyChannel, ReadyFuture}; + use crate::sercom::async_dma::{read_dma, write_dma, SercomPtr}; + + impl I2cFuture + where + C: AnyConfig, + S: Sercom, + D: AnyChannel, + { + fn sercom_ptr(&self) -> SercomPtr { + SercomPtr(self.i2c.data_ptr()) + } + + /// Asynchronously write from a buffer using DMA. + #[inline] + pub async fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), i2c::Error> { + self.i2c.init_dma_transfer()?; + + // SAFETY: Using SercomPtr and ImmutableSlice is safe because we hold on + // to &mut self and bytes as long as the transfer hasn't completed. + let i2c_ptr = self.sercom_ptr(); + + let len = bytes.len(); + assert!(len > 0 && len <= 255); + self.i2c.start_dma_write(address, len as u8); + + write_dma::<_, S>(&mut self._dma_channel, i2c_ptr, bytes) + .await + .map_err(i2c::Error::Dma)?; + + Ok(()) + } + + /// Asynchronously read into a buffer using DMA. + #[inline] + pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), i2c::Error> { + self.i2c.init_dma_transfer()?; + + // SAFETY: Using SercomPtr is safe because we hold on + // to &mut self as long as the transfer hasn't completed. + let i2c_ptr = self.sercom_ptr(); + + let len = buffer.len(); + assert!(len > 0 && len <= 255); + self.i2c.start_dma_read(address, len as u8); + + read_dma::<_, S>(&mut self._dma_channel, i2c_ptr, buffer) + .await + .map_err(i2c::Error::Dma)?; + + Ok(()) + } + + /// Asynchronously write from a buffer, then read into a buffer, all + /// using DMA. This is an extremely common pattern: writing a + /// register address, then read its value from the slave. + #[inline] + pub async fn write_read( + &mut self, + addr: u8, + write_buf: &[u8], + read_buf: &mut [u8], + ) -> Result<(), i2c::Error> { + self.write(addr, write_buf).await?; + // TODO may need some sort of delay here?? + self.read(addr, read_buf).await?; + Ok(()) + } + } +} diff --git a/hal/src/sercom/i2c/config.rs b/hal/src/sercom/i2c/config.rs index d04adba2bd86..869e2497a533 100644 --- a/hal/src/sercom/i2c/config.rs +++ b/hal/src/sercom/i2c/config.rs @@ -30,7 +30,7 @@ pub struct Config

where P: PadSet, { - pub(super) registers: Registers, + pub(in super::super) registers: Registers, pads: P, freq: Hertz, } diff --git a/hal/src/sercom/i2c/flags.rs b/hal/src/sercom/i2c/flags.rs index 4ee40561afbf..9731424e6755 100644 --- a/hal/src/sercom/i2c/flags.rs +++ b/hal/src/sercom/i2c/flags.rs @@ -81,4 +81,22 @@ pub enum Error { LengthError, Nack, Timeout, + #[cfg(feature = "dma")] + Dma(crate::dmac::Error), +} + +#[cfg(feature = "async")] +impl embedded_hal_async::i2c::Error for Error { + // _ pattern reachable when "dma" feature enabled. + #[allow(unreachable_patterns)] + fn kind(&self) -> embedded_hal_async::i2c::ErrorKind { + use embedded_hal_async::i2c::{ErrorKind, NoAcknowledgeSource}; + match self { + Error::ArbitrationLost => ErrorKind::ArbitrationLoss, + Error::BusError => ErrorKind::Bus, + Error::LengthError | Error::Timeout => ErrorKind::Overrun, + Error::Nack => ErrorKind::NoAcknowledge(NoAcknowledgeSource::Unknown), + _ => ErrorKind::Other, + } + } } diff --git a/hal/src/sercom/i2c/reg.rs b/hal/src/sercom/i2c/reg.rs index d8f59cc64b1d..93ecd5bf8ceb 100644 --- a/hal/src/sercom/i2c/reg.rs +++ b/hal/src/sercom/i2c/reg.rs @@ -10,7 +10,7 @@ use crate::time::Hertz; const MASTER_ACT_READ: u8 = 2; const MASTER_ACT_STOP: u8 = 3; -pub(super) struct Registers { +pub(in super::super) struct Registers { pub sercom: S, } @@ -27,7 +27,7 @@ impl Registers { /// Helper function to access the underlying `I2CM` from the given `SERCOM` #[inline] - fn i2c_master(&self) -> &pac::sercom0::I2CM { + pub(crate) fn i2c_master(&self) -> &pac::sercom0::I2CM { self.sercom.i2cm() } @@ -209,9 +209,10 @@ impl Registers { } } - /// Start a blocking write transaction + /// Start a write transaction. May be used by [`start_write_blocking`], or + /// an async method. #[inline] - pub(super) fn start_write_blocking(&mut self, addr: u8) -> Result<(), Error> { + pub(super) fn start_write(&mut self, addr: u8) -> Result<(), Error> { if self.get_smart_mode() { self.disable(); self.set_smart_mode(false); @@ -228,14 +229,22 @@ impl Registers { .write(|w| w.addr().bits(encode_write_address(addr))); } + Ok(()) + } + + /// Start a blocking write transaction + #[inline] + pub(super) fn start_write_blocking(&mut self, addr: u8) -> Result<(), Error> { + self.start_write(addr)?; + // wait for transmission to complete while !self.i2c_master().intflag.read().mb().bit_is_set() {} self.read_status().check_bus_error() } - /// Start a blocking read transaction - #[inline] - pub(super) fn start_read_blocking(&mut self, addr: u8) -> Result<(), Error> { + /// Start a write transaction. May be used by [`start_write_blocking`], or + /// an async method. + pub(super) fn start_read(&mut self, addr: u8) -> Result<(), Error> { if self.get_smart_mode() { self.disable(); self.set_smart_mode(false); @@ -256,6 +265,14 @@ impl Registers { .write(|w| w.addr().bits(encode_read_address(addr))); } + Ok(()) + } + + /// Start a blocking read transaction + #[inline] + pub(super) fn start_read_blocking(&mut self, addr: u8) -> Result<(), Error> { + self.start_read(addr)?; + // wait for transmission to complete loop { let intflag = self.i2c_master().intflag.read(); @@ -280,7 +297,7 @@ impl Registers { /// and STOP are automatically generated. #[cfg(feature = "dma")] #[inline] - pub(super) fn start_dma_write(&mut self, address: u8, xfer_len: u8) { + pub(in super::super) fn start_dma_write(&mut self, address: u8, xfer_len: u8) { if !self.get_smart_mode() { self.disable(); self.set_smart_mode(true); @@ -305,7 +322,7 @@ impl Registers { /// STOP are automatically generated. #[cfg(feature = "dma")] #[inline] - pub(super) fn start_dma_read(&mut self, address: u8, xfer_len: u8) { + pub(in super::super) fn start_dma_read(&mut self, address: u8, xfer_len: u8) { if !self.get_smart_mode() { self.disable(); self.set_smart_mode(true); @@ -347,12 +364,17 @@ impl Registers { self.sync_sysop(); } + #[inline] + pub(super) fn write_one(&mut self, byte: u8) { + unsafe { + self.i2c_master().data.write(|w| w.bits(byte)); + } + } + #[inline] pub(super) fn send_bytes(&mut self, bytes: &[u8]) -> Result<(), Error> { for b in bytes { - unsafe { - self.i2c_master().data.write(|w| w.bits(*b)); - } + self.write_one(*b); loop { let intflag = self.i2c_master().intflag.read(); @@ -367,15 +389,20 @@ impl Registers { #[inline] pub(super) fn read_one(&mut self) -> u8 { - while !self.i2c_master().intflag.read().sb().bit_is_set() {} self.i2c_master().data.read().bits() } + #[inline] + pub(super) fn read_one_blocking(&mut self) -> u8 { + while !self.i2c_master().intflag.read().sb().bit_is_set() {} + self.read_one() + } + #[inline] pub(super) fn fill_buffer(&mut self, buffer: &mut [u8]) -> Result<(), Error> { // Some manual iterator gumph because we need to ack bytes after the first. let mut iter = buffer.iter_mut(); - *iter.next().expect("buffer len is at least 1") = self.read_one(); + *iter.next().expect("buffer len is at least 1") = self.read_one_blocking(); loop { match iter.next() { @@ -383,7 +410,7 @@ impl Registers { Some(dest) => { // Ack the last byte so that we can receive another one self.cmd_read(); - *dest = self.read_one(); + *dest = self.read_one_blocking(); } } } diff --git a/hal/src/sercom/mod.rs b/hal/src/sercom/mod.rs index a1d2d0a04ae9..a9411271cf9a 100644 --- a/hal/src/sercom/mod.rs +++ b/hal/src/sercom/mod.rs @@ -36,8 +36,7 @@ use core::ops::Deref; use paste::paste; use seq_macro::seq; -use crate::pac; -use pac::sercom0; +use crate::pac::{self, sercom0, Peripherals}; #[cfg(feature = "thumbv7")] use pac::MCLK as APB_CLK_CTRL; @@ -60,6 +59,9 @@ pub mod uart; #[cfg(feature = "dma")] pub mod dma; +#[cfg(all(feature = "dma", feature = "async"))] +mod async_dma; + //============================================================================== // Sercom //============================================================================== @@ -68,14 +70,38 @@ pub mod dma; pub trait Sercom: Sealed + Deref { /// SERCOM number const NUM: usize; + /// RX Trigger source for DMA transactions #[cfg(feature = "dma")] const DMA_RX_TRIGGER: TriggerSource; + /// TX trigger source for DMA transactions #[cfg(feature = "dma")] const DMA_TX_TRIGGER: TriggerSource; + + #[cfg(feature = "async")] + type Interrupt: crate::async_hal::interrupts::InterruptSource; + /// Enable the corresponding APB clock fn enable_apb_clock(&mut self, ctrl: &APB_CLK_CTRL); + + /// Get a reference to the sercom from a + /// [`Peripherals`](crate::pac::Peripherals) block + fn reg_block(peripherals: &mut Peripherals) -> &crate::pac::sercom0::RegisterBlock; + + /// Get a reference to this [`Sercom`]'s associated RX Waker + #[cfg(feature = "async")] + #[inline] + fn rx_waker() -> &'static embassy_sync::waitqueue::AtomicWaker { + &async_api::RX_WAKERS[Self::NUM] + } + + /// Get a reference to this [`Sercom`]'s associated TX Waker + #[cfg(feature = "async")] + #[inline] + fn tx_waker() -> &'static embassy_sync::waitqueue::AtomicWaker { + &async_api::TX_WAKERS[Self::NUM] + } } macro_rules! sercom { @@ -92,14 +118,29 @@ macro_rules! sercom { #[cfg(feature = "has-" sercom~N)] impl Sercom for Sercom~N { const NUM: usize = N; + #[cfg(feature = "dma")] const DMA_RX_TRIGGER: TriggerSource = TriggerSource::[]; + #[cfg(feature = "dma")] const DMA_TX_TRIGGER: TriggerSource = TriggerSource::[]; + + #[cfg(all(feature = "async", feature = "thumbv6"))] + type Interrupt = crate::async_hal::interrupts::SERCOM~N; + + #[cfg(all(feature = "async", feature = "thumbv7"))] + type Interrupt = crate::async_hal::interrupts::[]; + #[inline] fn enable_apb_clock(&mut self, ctrl: &APB_CLK_CTRL) { ctrl.$apbmask.modify(|_, w| w.[]().set_bit()); } + + #[inline] + fn reg_block(peripherals: &mut Peripherals) -> &crate::pac::sercom0::RegisterBlock { + &*peripherals.SERCOM~N + } + } } }); @@ -115,3 +156,32 @@ sercom!(apbamask: (0, 1)); sercom!(apbbmask: (2, 3)); #[cfg(feature = "thumbv7")] sercom!(apbdmask: (4, 7)); + +#[allow(dead_code)] +#[cfg(feature = "samd11")] +const NUM_SERCOM: usize = 2; + +#[allow(dead_code)] +#[cfg(feature = "samd21e")] +const NUM_SERCOM: usize = 4; + +#[allow(dead_code)] +#[cfg(any(feature = "periph-d21g", feature = "samd51g", feature = "samd51j"))] +const NUM_SERCOM: usize = 6; + +#[allow(dead_code)] +#[cfg(feature = "periph-d51n")] +const NUM_SERCOM: usize = 8; + +#[cfg(feature = "async")] +pub(super) mod async_api { + use embassy_sync::waitqueue::AtomicWaker; + + #[allow(clippy::declare_interior_mutable_const)] + const NEW_WAKER: AtomicWaker = AtomicWaker::new(); + /// Waker for a RX event. By convention, if a SERCOM has only one type of + /// event (ie, I2C), this the waker to be used. + pub(super) static RX_WAKERS: [AtomicWaker; super::NUM_SERCOM] = [NEW_WAKER; super::NUM_SERCOM]; + /// Waker for a TX event. + pub(super) static TX_WAKERS: [AtomicWaker; super::NUM_SERCOM] = [NEW_WAKER; super::NUM_SERCOM]; +} diff --git a/hal/src/sercom/spi.rs b/hal/src/sercom/spi.rs index 589795c6d27a..27cbd20ebe5e 100644 --- a/hal/src/sercom/spi.rs +++ b/hal/src/sercom/spi.rs @@ -371,6 +371,11 @@ pub mod impl_ehal; #[path = "spi/impl_ehal_thumbv7em.rs"] pub mod impl_ehal; +#[cfg(feature = "async")] +mod async_api; +#[cfg(feature = "async")] +pub use async_api::*; + //============================================================================= // BitOrder //============================================================================= @@ -388,6 +393,15 @@ pub enum BitOrder { // Flags //============================================================================= +const DRE: u8 = 0x01; +const TXC: u8 = 0x02; +const RXC: u8 = 0x04; +const SSL: u8 = 0x08; +const ERROR: u8 = 0x80; + +pub const RX_FLAG_MASK: u8 = RXC; +pub const TX_FLAG_MASK: u8 = DRE | TXC; + bitflags! { /// Interrupt bit flags for SPI transactions /// @@ -395,14 +409,20 @@ bitflags! { /// `ERROR`. The binary format of the underlying bits exactly matches the /// `INTFLAG` register. pub struct Flags: u8 { - const DRE = 0x01; - const TXC = 0x02; - const RXC = 0x04; - const SSL = 0x08; - const ERROR = 0x80; + const DRE = DRE; + const TXC = TXC; + const RXC = RXC; + const SSL = SSL; + const ERROR = ERROR; } } +#[allow(dead_code)] +impl Flags { + pub(super) const RX: Self = unsafe { Self::from_bits_unchecked(RX_FLAG_MASK) }; + pub(super) const TX: Self = unsafe { Self::from_bits_unchecked(TX_FLAG_MASK) }; +} + //============================================================================= // Status //============================================================================= @@ -447,6 +467,23 @@ impl TryFrom for () { pub enum Error { Overflow, LengthError, + #[cfg(feature = "dma")] + Dma(crate::dmac::Error), +} + +#[cfg(feature = "async")] +impl embedded_hal_async::spi::Error for Error { + // _ pattern reachable when "dma" feature enabled. + #[allow(unreachable_patterns)] + fn kind(&self) -> embedded_hal_async::spi::ErrorKind { + use embedded_hal_async::spi::ErrorKind; + + match self { + Error::Overflow => ErrorKind::Overrun, + Error::LengthError => ErrorKind::Other, + _ => ErrorKind::Other, + } + } } //============================================================================= diff --git a/hal/src/sercom/spi/async_api.rs b/hal/src/sercom/spi/async_api.rs new file mode 100644 index 000000000000..0d4d45e16abe --- /dev/null +++ b/hal/src/sercom/spi/async_api.rs @@ -0,0 +1,594 @@ +use crate::{ + async_hal::interrupts::{Binding, Handler, InterruptSource}, + sercom::{ + spi::{Capability, DataWidth, Duplex, Error, Flags, Rx, Spi, Tx, ValidConfig}, + Sercom, + }, + typelevel::NoneT, +}; +use core::{marker::PhantomData, task::Poll}; +use cortex_m::interrupt::InterruptNumber; +use num_traits::{AsPrimitive, PrimInt}; + +/// Interrupt handler for async SPI operarions +pub struct InterruptHandler { + _private: (), + _sercom: PhantomData, +} + +impl Handler for InterruptHandler { + #[inline] + unsafe fn on_interrupt() { + unsafe { + let mut peripherals = crate::pac::Peripherals::steal(); + + #[cfg(feature = "thumbv6")] + let spi = S::reg_block(&mut peripherals).spi(); + #[cfg(feature = "thumbv7")] + let spi = S::reg_block(&mut peripherals).spim(); + + let flags_pending = Flags::from_bits_truncate(spi.intflag.read().bits()); + let enabled_flags = Flags::from_bits_truncate(spi.intenset.read().bits()); + + // Disable interrupts, but don't clear the flags. The future will take care of + // clearing flags and re-enabling interrupts when woken. + if (Flags::RX & enabled_flags).contains(flags_pending) { + spi.intenclr.write(|w| w.bits(flags_pending.bits())); + S::rx_waker().wake(); + } + + if (Flags::TX & enabled_flags).contains(flags_pending) { + spi.intenclr.write(|w| w.bits(flags_pending.bits())); + S::tx_waker().wake(); + } + } + } +} + +impl Spi +where + C: ValidConfig, + A: Capability, + S: Sercom, +{ + /// Turn an [`Spi`] into a [`SpiFuture`]. In cases where the underlying + /// [`Spi`] is [`Duplex`], reading words need to be accompanied with sending + /// a no-op word. By default it is set to 0x00, but you can configure it + /// by using the [`nop_word`](SpiFuture::nop_word) method. + #[inline] + pub fn into_future(self, _interrupts: I) -> SpiFuture + where + C::Word: Copy, + u8: AsPrimitive, + I: Binding>, + { + S::Interrupt::unpend(); + unsafe { S::Interrupt::enable() }; + + SpiFuture { + spi: self, + nop_word: 0x00_u8.as_(), + _rx_channel: NoneT, + _tx_channel: NoneT, + } + } +} + +/// `async` version of [`Spi`]. +/// +/// Create this struct by calling [`I2c::into_future`](I2c::into_future). +pub struct SpiFuture +where + C: ValidConfig, + A: Capability, +{ + spi: Spi, + nop_word: C::Word, + _rx_channel: R, + _tx_channel: T, +} + +#[cfg(feature = "defmt")] +impl defmt::Format for SpiFuture +where + C: ValidConfig, + A: Capability, +{ + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "SpiFuture defmt shim\n"); + } +} + +/// Convenience type for a [`SpiFuture`] with RX and TX capabilities +pub type SpiFutureDuplex = SpiFuture; + +/// Convenience type for a [`SpiFuture`] with RX capabilities +pub type SpiFutureRx = SpiFuture; + +/// Convenience type for a [`SpiFuture`] with TX capabilities +pub type SpiFutureTx = SpiFuture; + +#[cfg(feature = "dma")] +/// Convenience type for a [`SpiFuture`] with RX and TX capabilities in DMA +/// mode. The type parameter `R` represents the RX DMA channel ID (`ChX`), and +/// `T` represents the TX DMA channel ID. +pub type SpiFutureDuplexDma = SpiFuture< + C, + Duplex, + crate::dmac::Channel, + crate::dmac::Channel, +>; + +#[cfg(feature = "dma")] +/// Convenience type for a [`SpiFuture`] with RX capabilities in DMA mode. The +/// type parameter `R` represents the RX DMA channel ID (`ChX`). +pub type SpiFutureRxDma = + SpiFuture, NoneT>; + +#[cfg(feature = "dma")] +/// Convenience type for a [`SpiFuture`] with TX capabilities in DMA mode. The +/// type parameter `T` represents the TX DMA channel ID (`ChX`). +pub type SpiFutureTxDma = + SpiFuture>; + +impl SpiFuture +where + C: ValidConfig, + A: Capability, + S: Sercom, +{ + /// Return the underlying [`Spi`]. + pub fn free(self) -> Spi { + self.spi + } + + /// Configure the no-op word to send when doing read-only transactions. + pub fn nop_word(&mut self, word: C::Word) { + self.nop_word = word; + } + + #[inline] + async fn wait_flags(&mut self, flags_to_wait: Flags) { + core::future::poll_fn(|cx| { + // Scope maybe_pending so we don't forget to re-poll the register later down. + { + let maybe_pending = self.spi.config.as_ref().regs.read_flags(); + if flags_to_wait.intersects(maybe_pending) { + return Poll::Ready(()); + } + } + + self.spi.disable_interrupts(Flags::all()); + + if flags_to_wait.intersects(Flags::RX) { + S::rx_waker().register(cx.waker()); + } + if flags_to_wait.intersects(Flags::TX) { + S::tx_waker().register(cx.waker()); + } + + self.spi.enable_interrupts(flags_to_wait); + let maybe_pending = self.spi.config.as_ref().regs.read_flags(); + + if !flags_to_wait.intersects(maybe_pending) { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + } +} + +impl SpiFuture +where + C: ValidConfig, + C::Word: PrimInt + AsPrimitive, + DataWidth: AsPrimitive, + S: Sercom, +{ + /// Read words into a buffer asynchronously, word by word. Since we are + /// using a [`Duplex`] [`SpiFuture`], we need to send a word simultaneously + /// while receiving one. This `no-op` word is configurable via the + /// [`nop_word`](Self::nop_word) method. + #[inline] + pub async fn read(&mut self, buffer: &mut [C::Word]) -> Result<(), Error> { + for byte in buffer.iter_mut() { + *byte = self.transfer_word_in_place(self.nop_word).await?; + } + + Ok(()) + } + + /// Write words from a buffer asynchronously, word by word + #[inline] + pub async fn write(&mut self, words: &[C::Word]) -> Result<(), Error> { + // When in Duplex mode, read as many words as we write to avoid buffer overflows + for word in words { + let _ = self.transfer_word_in_place(*word).await?; + } + + Ok(()) + } +} + +impl SpiFuture +where + C: ValidConfig, + C::Word: PrimInt + AsPrimitive, + DataWidth: AsPrimitive, + S: Sercom, +{ + /// Read and write a single word to the bus simultaneously. + pub async fn transfer_word_in_place(&mut self, to_send: C::Word) -> Result { + self.wait_flags(Flags::DRE).await; + self.spi.read_flags_errors()?; + unsafe { + self.spi.write_data(to_send.as_()); + } + + self.wait_flags(Flags::TXC).await; + + self.wait_flags(Flags::RXC).await; + let word = unsafe { self.spi.read_data().as_() }; + + Ok(word) + } + + /// Perform a transfer, word by word. No-op words will be written if `read` + /// is longer than `write`. Extra words are ignored if `write` is longer + /// than `read`. + async fn transfer_word_by_word( + &mut self, + read: &mut [C::Word], + write: &[C::Word], + ) -> Result<(), Error> { + let nop_word = self.nop_word; + for (r, w) in read + .iter_mut() + .zip(write.iter().chain(core::iter::repeat(&nop_word))) + { + *r = self.transfer_word_in_place(*w).await?; + } + + Ok(()) + } +} + +impl AsRef> for SpiFuture +where + C: ValidConfig, + A: Capability, +{ + #[inline] + fn as_ref(&self) -> &Spi { + &self.spi + } +} + +impl AsMut> for SpiFuture +where + C: ValidConfig, + A: Capability, + N: InterruptNumber, +{ + #[inline] + fn as_mut(&mut self) -> &mut Spi { + &mut self.spi + } +} + +mod impl_ehal { + use super::*; + use crate::sercom::spi::Error; + use embedded_hal_async::spi::{ErrorType, SpiBus}; + + impl ErrorType for SpiFuture + where + C: ValidConfig, + C::Word: PrimInt + AsPrimitive, + DataWidth: AsPrimitive, + A: Capability, + S: Sercom, + { + type Error = Error; + } + + impl SpiBus for SpiFuture + where + C: ValidConfig, + C::Word: PrimInt + AsPrimitive, + DataWidth: AsPrimitive, + S: Sercom, + { + async fn flush(&mut self) -> Result<(), Self::Error> { + // Wait for all transactions to complete, ignoring buffer overflow errors. + self.wait_flags(Flags::TXC | Flags::RXC).await; + Ok(()) + } + + async fn write(&mut self, words: &[C::Word]) -> Result<(), Self::Error> { + self.write(words).await + } + + async fn read(&mut self, words: &mut [C::Word]) -> Result<(), Self::Error> { + self.read(words).await + } + + async fn transfer( + &mut self, + read: &mut [C::Word], + write: &[C::Word], + ) -> Result<(), Self::Error> { + self.transfer_word_by_word(read, write).await + } + + async fn transfer_in_place(&mut self, words: &mut [C::Word]) -> Result<(), Self::Error> { + // Can only ever do word-by-word to avoid DMA race conditions + for word in words { + let read = self.transfer_word_in_place(*word).await?; + *word = read; + } + + Ok(()) + } + } + + #[cfg(feature = "dma")] + impl SpiBus for SpiFuture + where + C: ValidConfig, + C::Word: PrimInt + AsPrimitive + crate::dmac::Beat, + C::Size: crate::sercom::spi::Size, + DataWidth: AsPrimitive, + S: Sercom + 'static, + R: crate::dmac::AnyChannel, + T: crate::dmac::AnyChannel, + { + async fn flush(&mut self) -> Result<(), Self::Error> { + // Wait for all transactions to complete, ignoring buffer overflow errors. + self.wait_flags(Flags::TXC | Flags::RXC).await; + Ok(()) + } + + async fn write(&mut self, words: &[C::Word]) -> Result<(), Self::Error> { + self.write(words).await + } + + async fn read(&mut self, words: &mut [C::Word]) -> Result<(), Self::Error> { + self.read(words).await + } + + async fn transfer(&mut self, read: &mut [W], write: &[W]) -> Result<(), Self::Error> { + self.transfer_dma(Some(read), Some(write)).await + } + + async fn transfer_in_place(&mut self, words: &mut [W]) -> Result<(), Self::Error> { + // Can only ever do word-by-word to avoid DMA race conditions + for word in words { + let read = self.transfer_word_in_place(*word).await?; + *word = read; + } + + Ok(()) + } + } +} + +#[cfg(feature = "dma")] +mod dma { + use super::*; + use crate::{ + dmac::{AnyChannel, Beat, Buffer, ReadyFuture}, + sercom::{ + async_dma::{read_dma, read_dma_buffer, write_dma, write_dma_buffer, SercomPtr}, + spi::Size, + }, + }; + + struct DummyBuffer { + word: T, + length: usize, + } + + /// Sink/source buffer to use for unidirectional SPI-DMA transfers. + /// + /// When reading/writing from a [`Duplex`] [`SpiFuture`] with DMA enabled, + /// we must always read and write the same number of words, regardless of + /// whether we care about the result (ie, for [`write`], we discard the read + /// words, whereas for [`read`], we must send a no-op word). + /// + /// This [`Buffer`] implementation provides a source/sink for a single word, + /// but with a variable length. + impl DummyBuffer { + fn new(word: T, length: usize) -> Self { + Self { word, length } + } + } + + unsafe impl Buffer for DummyBuffer { + type Beat = T; + + fn incrementing(&self) -> bool { + false + } + + fn buffer_len(&self) -> usize { + self.length + } + + fn dma_ptr(&mut self) -> *mut Self::Beat { + &mut self.word as *mut _ + } + } + + impl SpiFuture + where + C: ValidConfig, + C::Word: PrimInt + AsPrimitive, + DataWidth: AsPrimitive, + S: Sercom, + { + /// Add a DMA channel for receiving transactions + #[inline] + pub fn with_rx_dma_channel>( + self, + rx_channel: Chan, + ) -> SpiFuture { + SpiFuture { + spi: self.spi, + nop_word: self.nop_word, + _tx_channel: self._tx_channel, + _rx_channel: rx_channel, + } + } + } + + impl SpiFuture + where + C: ValidConfig, + C::Word: PrimInt + AsPrimitive, + DataWidth: AsPrimitive, + S: Sercom, + { + /// Add a DMA channel for receiving transactions + #[inline] + pub fn with_tx_dma_channel>( + self, + tx_channel: Chan, + ) -> SpiFuture { + SpiFuture { + spi: self.spi, + nop_word: self.nop_word, + _rx_channel: self._rx_channel, + _tx_channel: tx_channel, + } + } + } + + impl SpiFuture + where + C: ValidConfig, + C::Word: PrimInt + AsPrimitive, + DataWidth: AsPrimitive, + S: Sercom, + { + /// Add a DMA channel for receiving transactions + #[inline] + pub fn with_dma_channels( + self, + rx_channel: ChanRx, + tx_channel: ChanTx, + ) -> SpiFuture + where + ChanRx: AnyChannel, + ChanTx: AnyChannel, + { + SpiFuture { + spi: self.spi, + nop_word: self.nop_word, + _rx_channel: rx_channel, + _tx_channel: tx_channel, + } + } + } + + impl SpiFuture + where + C: ValidConfig, + C::Word: PrimInt + AsPrimitive + Beat, + C::Size: Size, + DataWidth: AsPrimitive, + R: AnyChannel, + T: AnyChannel, + S: Sercom + 'static, + { + fn sercom_ptr(&self) -> SercomPtr { + SercomPtr(self.spi.data_ptr()) + } + + /// Simultaneously transfer words in and out of the SPI bus. + /// + /// If `read` and `write` are the same length, we can send everything at + /// once, and thus DMA transfers can be utilized. If they are of + /// different lengths, we need to send word by word, so that we + /// can pad `write` if it is longer than `read`. + /// + /// One or both of `read` and `write` can be specified. In any case, + /// words will simultaneously be sent and received, to avoid buffer + /// overflow errors. If `write` is omitted, `self.nop_word` will be + /// sent. If `read` is omitted, the words sent by the device will still + /// be read, but immediately discarded. + #[inline] + pub(super) async fn transfer_dma( + &mut self, + read: Option<&mut [C::Word]>, + write: Option<&[C::Word]>, + ) -> Result<(), Error> { + assert!(read.is_some() || write.is_some()); + + let spi_ptr = self.sercom_ptr(); + + match (read, write) { + (Some(r), Some(w)) => { + if r.len() == w.len() { + let tx_fut = write_dma::<_, S>(&mut self._rx_channel, spi_ptr.clone(), w); + let rx_fut = read_dma::<_, S>(&mut self._tx_channel, spi_ptr, r); + + let (read_res, write_res) = futures::join!(rx_fut, tx_fut); + write_res.and(read_res).map_err(Error::Dma)?; + } else { + // Short circuit if we got a length mismatch, as we have to send word by + // word + self.transfer_word_by_word(r, w).await?; + return Ok(()); + } + } + + (Some(r), None) => { + let source = DummyBuffer::new(self.nop_word, r.len()); + let rx_fut = read_dma::<_, S>(&mut self._rx_channel, spi_ptr.clone(), r); + let tx_fut = + write_dma_buffer::<_, _, S>(&mut self._tx_channel, spi_ptr, source); + + let (read_res, write_res) = futures::join!(rx_fut, tx_fut); + write_res.and(read_res).map_err(Error::Dma)?; + } + + (None, Some(w)) => { + // Use a random value as the sink buffer since we're just going to discard the + // read words + let sink = DummyBuffer::new(0xFF.as_(), w.len()); + let rx_fut = + read_dma_buffer::<_, _, S>(&mut self._rx_channel, spi_ptr.clone(), sink); + let tx_fut = write_dma::<_, S>(&mut self._tx_channel, spi_ptr, w); + + let (read_res, write_res) = futures::join!(rx_fut, tx_fut); + write_res.and(read_res).map_err(Error::Dma)?; + } + + _ => panic!("Must provide at lease one buffer"), + } + + self.spi.read_flags_errors()?; + + // Wait for transmission to complete. If we don't do that, we might return too + // early and disable the CS line, resulting in a corrupted transfer. + self.wait_flags(Flags::TXC).await; + + Ok(()) + } + + /// Read words into a buffer asynchronously, using DMA. + #[inline] + pub async fn read(&mut self, words: &mut [C::Word]) -> Result<(), Error> { + self.transfer_dma(Some(words), None).await + } + + /// Write words from a buffer asynchronously, using DMA. + #[inline] + pub async fn write(&mut self, words: &[C::Word]) -> Result<(), Error> { + self.transfer_dma(None, Some(words)).await + } + } +} diff --git a/hal/src/sercom/spi_future.rs b/hal/src/sercom/spi_future.rs index 7e9a13986efa..5b1070375d77 100644 --- a/hal/src/sercom/spi_future.rs +++ b/hal/src/sercom/spi_future.rs @@ -489,7 +489,7 @@ where if self.rcvd < self.sent { let buf = unsafe { buf.get_unchecked_mut(self.rcvd..) }; let mut data = buf.iter_mut(); - let word = unsafe { self.spi.as_mut().read_data() as u32 }; + let word = unsafe { self.spi.as_mut().read_data() }; let bytes = word.to_le_bytes(); let mut iter = bytes.iter(); for _ in 0..self.spi.step() { diff --git a/hal/src/sercom/uart.rs b/hal/src/sercom/uart.rs index 778131902917..ba15429f27e1 100644 --- a/hal/src/sercom/uart.rs +++ b/hal/src/sercom/uart.rs @@ -411,6 +411,11 @@ pub use config::*; pub mod impl_ehal; +#[cfg(feature = "async")] +mod async_api; +#[cfg(feature = "async")] +pub use async_api::*; + use crate::{sercom::*, typelevel::Sealed}; use core::{convert::TryInto, marker::PhantomData}; use num_traits::AsPrimitive; @@ -507,6 +512,10 @@ pub trait Receive: Capability {} /// capability, but not both pub trait Simplex: Capability {} +/// Type-level enum representing a UART that is *not* half of a split +/// [`Duplex`] +pub trait SingleOwner: Capability {} + /// Marker type representing a UART that has both transmit and receive /// capability pub enum Duplex {} @@ -523,6 +532,7 @@ impl Capability for Duplex { } impl Receive for Duplex {} impl Transmit for Duplex {} +impl SingleOwner for Duplex {} /// Marker type representing a UART that can only receive pub enum Rx {} @@ -539,6 +549,7 @@ impl Capability for Rx { } impl Receive for Rx {} impl Simplex for Rx {} +impl SingleOwner for Rx {} /// Marker type representing a UART that can only transmit pub enum Tx {} @@ -555,6 +566,7 @@ impl Capability for Tx { } impl Transmit for Tx {} impl Simplex for Tx {} +impl SingleOwner for Tx {} /// Marker type representing the Rx half of a [`Duplex`] UART pub enum RxDuplex {} diff --git a/hal/src/sercom/uart/async_api.rs b/hal/src/sercom/uart/async_api.rs new file mode 100644 index 000000000000..438bbf6cdb8d --- /dev/null +++ b/hal/src/sercom/uart/async_api.rs @@ -0,0 +1,432 @@ +use crate::{ + async_hal::interrupts::{Binding, Handler, InterruptSource}, + sercom::{ + uart::{ + Capability, DataReg, Duplex, Error, Flags, Receive, Rx, RxDuplex, SingleOwner, + Transmit, Tx, TxDuplex, Uart, ValidConfig, + }, + Sercom, + }, + typelevel::NoneT, +}; +use core::{marker::PhantomData, task::Poll}; +use cortex_m::interrupt::InterruptNumber; +use num_traits::AsPrimitive; + +/// Interrupt handler for async UART operarions +pub struct InterruptHandler { + _private: (), + _sercom: PhantomData, +} + +impl Handler for InterruptHandler { + #[inline] + unsafe fn on_interrupt() { + unsafe { + let mut peripherals = crate::pac::Peripherals::steal(); + + #[cfg(feature = "thumbv6")] + let uart = S::reg_block(&mut peripherals).usart(); + #[cfg(feature = "thumbv7")] + let uart = S::reg_block(&mut peripherals).usart_int(); + + let flags_pending = Flags::from_bits_unchecked(uart.intflag.read().bits()); + let enabled_flags = Flags::from_bits_unchecked(uart.intenset.read().bits()); + uart.intenclr.write(|w| w.bits(flags_pending.bits())); + + // Disable interrupts, but don't clear the flags. The future will take care of + // clearing flags and re-enabling interrupts when woken. + if (Flags::RX & enabled_flags).intersects(flags_pending) { + S::rx_waker().wake(); + } + + if (Flags::TX & enabled_flags).intersects(flags_pending) { + S::tx_waker().wake(); + } + } + } +} + +impl Uart +where + C: ValidConfig, + D: SingleOwner, + S: Sercom, +{ + /// Turn a [`Uart`] into a [`UartFuture`]. This method is only available for + /// [`Uart`]s which have a [`Tx`](crate::sercom::uart::Tx), + /// [`Rx`](crate::sercom::uart::Rx) or [`Duplex`] [`Capability`]. + #[inline] + pub fn into_future(self, _interrupts: I) -> UartFuture + where + I: Binding>, + { + S::Interrupt::unpend(); + unsafe { S::Interrupt::enable() }; + + UartFuture { + uart: self, + rx_channel: NoneT, + tx_channel: NoneT, + } + } +} + +impl UartFuture +where + C: ValidConfig, + D: SingleOwner, + N: InterruptNumber, +{ + /// Return the underlying [`Uart`]. + pub fn free(self) -> Uart { + self.uart + } +} + +/// `async` version of [`Uart`]. +/// +/// Create this struct by calling [`I2c::into_future`](I2c::into_future). +pub struct UartFuture +where + C: ValidConfig, + D: Capability, +{ + uart: Uart, + rx_channel: R, + tx_channel: T, +} + +/// Convenience type for a [`UartFuture`] with RX and TX capabilities +pub type UartFutureDuplex = UartFuture; + +/// Convenience type for a RX-only [`UartFuture`]. +pub type UartFutureHalfRx = UartFuture; + +/// Convenience type for the RX half of a [`Duplex`] [`UartFuture`]. +pub type UartFutureRx = UartFuture; + +/// Convenience type for a TX-only [`UartFuture`]. +pub type UartFutureTx = UartFuture; + +/// Convenience type for the TX half of a [`Duplex`] [`UartFuture`]. +pub type UartFutureTxDuplex = UartFuture; + +#[cfg(feature = "dma")] +/// Convenience type for a [`UartFuture`] with RX and TX capabilities in DMA +/// mode. The type parameter `R` represents the RX DMA channel ID (`ChX`), and +/// `T` represents the TX DMA channel ID. +pub type UartFutureDuplexDma = UartFuture< + C, + Duplex, + crate::dmac::Channel, + crate::dmac::Channel, +>; + +#[cfg(feature = "dma")] +/// Convenience type for a RX-only [`UartFuture`] in DMA mode. +/// The type parameter `R` represents the RX DMA channel ID (`ChX`). +pub type UartFutureRxDma = + UartFuture, NoneT>; + +#[cfg(feature = "dma")] +/// Convenience type for the RX half of a [`Duplex`] [`UartFuture`] in DMA mode. +/// The type parameter `R` represents the RX DMA channel ID (`ChX`). +pub type UartFutureRxDuplexDma = + UartFuture, NoneT>; + +#[cfg(feature = "dma")] +/// Convenience type for a TX-only [`UartFuture`] in DMA mode. +/// The type parameter `T` represents the TX DMA channel ID (`ChX`). +pub type UartFutureTxDma = + UartFuture>; + +#[cfg(feature = "dma")] +/// Convenience type for the TX half of a [`Duplex`] [`UartFuture`] in DMA mode. +/// The type parameter `T` represents the TX DMA channel ID (`ChX`). +pub type UartFutureTxDuplexDma = + UartFuture>; + +impl UartFuture +where + C: ValidConfig, +{ + /// Split the [`UartFuture`] into [`RxDuplex`]and [`TxDuplex`] halves. + #[inline] + #[allow(clippy::type_complexity)] + pub fn split( + self, + ) -> ( + UartFuture, + UartFuture, + ) { + let config = unsafe { core::ptr::read(&self.uart.config) }; + ( + UartFuture { + uart: Uart { + config: self.uart.config, + capability: PhantomData, + }, + rx_channel: self.rx_channel, + tx_channel: NoneT, + }, + UartFuture { + uart: Uart { + config, + capability: PhantomData, + }, + tx_channel: self.tx_channel, + rx_channel: NoneT, + }, + ) + } + + /// Join [`RxDuplex`] and [`TxDuplex`] halves back into a full + /// `UartFuture` + #[inline] + pub fn join( + rx: UartFuture, + tx: UartFuture, + ) -> Self { + Self { + uart: Uart { + config: rx.uart.config, + capability: PhantomData, + }, + rx_channel: rx.rx_channel, + tx_channel: tx.tx_channel, + } + } +} + +impl UartFuture +where + C: ValidConfig, + D: Capability, + S: Sercom, +{ + #[inline] + async fn wait_flags(&mut self, flags_to_wait: Flags) { + let flags_to_wait = flags_to_wait & unsafe { Flags::from_bits_unchecked(D::FLAG_MASK) }; + + core::future::poll_fn(|cx| { + // Scope maybe_pending so we don't forget to re-poll the register later down. + { + let maybe_pending = self.uart.as_ref().registers.read_flags(); + if flags_to_wait.intersects(maybe_pending) { + return Poll::Ready(()); + } + } + + if flags_to_wait.intersects(Flags::RX) { + self.uart.disable_interrupts(Flags::RX); + S::rx_waker().register(cx.waker()); + } + if flags_to_wait.intersects(Flags::TX) { + self.uart.disable_interrupts(Flags::RX); + S::tx_waker().register(cx.waker()); + } + self.uart.enable_interrupts(flags_to_wait); + let maybe_pending = self.uart.as_ref().registers.read_flags(); + + if !flags_to_wait.intersects(maybe_pending) { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + } +} + +impl UartFuture +where + C: ValidConfig, + D: Receive, + S: Sercom, + DataReg: AsPrimitive, +{ + /// Add a DMA channel for receiving words + #[cfg(feature = "dma")] + #[inline] + pub fn with_rx_dma_channel>( + self, + rx_channel: Chan, + ) -> UartFuture { + UartFuture { + uart: self.uart, + tx_channel: self.tx_channel, + rx_channel, + } + } + + /// Read a single [`Word`](crate::sercom::uart::Word) from the UART. + #[inline] + pub async fn read_word(&mut self) -> Result { + self.wait_flags(Flags::RXC).await; + self.uart.read_status().try_into()?; + Ok(unsafe { self.uart.read_data().as_() }) + } +} + +impl UartFuture +where + C: ValidConfig, + D: Receive, + S: Sercom, + DataReg: AsPrimitive, +{ + /// Read the specified number of [`Word`](crate::sercom::uart::Word)s into a + /// buffer, word by word. In case of an error, returns `Err(Error, usize)` + /// where the `usize` represents the number of valid words read before + /// the error occured. + #[inline] + pub async fn read(&mut self, buffer: &mut [C::Word]) -> Result<(), (Error, usize)> { + for (i, word) in buffer.iter_mut().enumerate() { + match self.read_word().await { + Ok(w) => { + *word = w; + } + Err(e) => { + return Err((e, i)); + } + } + } + Ok(()) + } +} + +impl UartFuture +where + C: ValidConfig, + D: Transmit, + S: Sercom, +{ + /// Add a DMA channel for sending words + #[cfg(feature = "dma")] + #[inline] + pub fn with_tx_dma_channel>( + self, + tx_channel: Chan, + ) -> UartFuture { + UartFuture { + uart: self.uart, + rx_channel: self.rx_channel, + tx_channel, + } + } + + /// Write a single [`Word`](crate::sercom::uart::Word) to the UART. + #[inline] + pub async fn write_word(&mut self, word: C::Word) { + self.wait_flags(Flags::DRE).await; + unsafe { self.uart.write_data(word.as_()) }; + } +} + +impl UartFuture +where + C: ValidConfig, + D: Transmit, + S: Sercom, +{ + /// Write the specified number of [`Word`](crate::sercom::uart::Word)s from + /// a buffer to the UART, word by word. + #[inline] + pub async fn write(&mut self, buffer: &[C::Word]) { + for word in buffer { + self.write_word(*word).await; + } + } +} + +impl AsRef> for UartFuture +where + C: ValidConfig, + D: Capability, +{ + fn as_ref(&self) -> &Uart { + &self.uart + } +} + +impl AsMut> for UartFuture +where + C: ValidConfig, + D: Capability, +{ + fn as_mut(&mut self) -> &mut Uart { + &mut self.uart + } +} + +#[cfg(feature = "dma")] +mod dma { + use super::*; + use crate::{ + dmac::{AnyChannel, Beat, ReadyFuture}, + sercom::{ + async_dma::{read_dma, write_dma, SercomPtr}, + uart, + }, + }; + + impl UartFuture + where + C: ValidConfig, + C::Word: Beat, + A: Capability, + S: Sercom + 'static, + { + fn sercom_ptr(&self) -> SercomPtr { + SercomPtr(self.uart.data_ptr()) + } + } + + impl UartFuture + where + C: ValidConfig, + C::Word: Beat, + D: Receive, + S: Sercom + 'static, + DataReg: AsPrimitive, + R: AnyChannel, + { + /// Read the specified number of [`Word`](crate::sercom::uart::Word)s + /// into a buffer using DMA. + #[inline] + pub async fn read(&mut self, words: &mut [C::Word]) -> Result<(), Error> { + // SAFETY: Using SercomPtr is safe because we hold on + // to &mut self as long as the transfer hasn't completed. + let uart_ptr = self.sercom_ptr(); + + read_dma::<_, S>(&mut self.rx_channel, uart_ptr, words) + .await + .map_err(uart::Error::Dma)?; + + Ok(()) + } + } + + impl UartFuture + where + C: ValidConfig, + C::Word: Beat, + D: Transmit, + S: Sercom + 'static, + T: AnyChannel, + { + /// Write words from a buffer asynchronously, using DMA. + #[inline] + pub async fn write(&mut self, words: &[C::Word]) { + // SAFETY: Using SercomPtr is safe because we hold on + // to &mut self as long as the transfer hasn't completed. + let uart_ptr = self.sercom_ptr(); + + write_dma::<_, S>(&mut self.tx_channel, uart_ptr, words) + .await + .expect("DMA error"); + self.wait_flags(Flags::TXC).await; + } + } +} diff --git a/hal/src/sercom/uart/config.rs b/hal/src/sercom/uart/config.rs index 97d925354c5d..af5816eade6d 100644 --- a/hal/src/sercom/uart/config.rs +++ b/hal/src/sercom/uart/config.rs @@ -88,7 +88,7 @@ impl Config

{ // Enable internal clock mode registers.configure_mode(); - registers.configure_pads(P::RXPO as u8, P::TXPO as u8); + registers.configure_pads(P::RXPO, P::TXPO); registers.set_char_size(EightBit::SIZE); Self { diff --git a/hal/src/sercom/uart/flags.rs b/hal/src/sercom/uart/flags.rs index dc6de64c590d..71cb13eed6d6 100644 --- a/hal/src/sercom/uart/flags.rs +++ b/hal/src/sercom/uart/flags.rs @@ -38,6 +38,14 @@ bitflags! { } } +impl Flags { + /// [`Flags`] which can be used for receiving + pub const RX: Self = unsafe { Self::from_bits_unchecked(RX_FLAG_MASK) }; + + /// [`Flags`] which can be used for transmitting + pub const TX: Self = unsafe { Self::from_bits_unchecked(TX_FLAG_MASK) }; +} + //============================================================================= // Status flags //============================================================================= @@ -89,6 +97,9 @@ pub enum Error { InconsistentSyncField, /// Detected a collision CollisionDetected, + /// DMA error + #[cfg(feature = "dma")] + Dma(crate::dmac::Error), } impl TryFrom for () { @@ -123,6 +134,9 @@ impl From for Status { Overflow => Status::BUFOVF, InconsistentSyncField => Status::ISF, CollisionDetected => Status::COLL, + // Don't try to convert a DMA error into a [`Status`] + #[cfg(feature = "dma")] + _ => unreachable!(), } } } diff --git a/hal/src/thumbv6m/eic/mod.rs b/hal/src/thumbv6m/eic/mod.rs index a0d415d0b829..e9d22dbebb6b 100644 --- a/hal/src/thumbv6m/eic/mod.rs +++ b/hal/src/thumbv6m/eic/mod.rs @@ -1,5 +1,4 @@ -use crate::clock::EicClock; -use crate::pac; +use crate::{clock::EicClock, pac}; pub mod pin; @@ -19,3 +18,50 @@ impl EIC { EIC { eic } } } + +#[cfg(feature = "async")] +mod async_api { + use super::pin::NUM_CHANNELS; + use super::*; + use crate::async_hal::interrupts::{Binding, Handler, Interrupt, EIC as EicInterrupt}; + use crate::util::BitIter; + use embassy_sync::waitqueue::AtomicWaker; + + pub struct InterruptHandler { + _private: (), + } + + impl Handler for InterruptHandler { + unsafe fn on_interrupt() { + let eic = pac::Peripherals::steal().EIC; + + let pending_interrupts = BitIter(eic.intflag.read().bits()); + for channel in pending_interrupts { + let mask = 1 << channel; + // Disable the interrupt but don't clear; will be cleared + // when future is next polled. + eic.intenclr.write(|w| w.bits(mask)); + WAKERS[channel as usize].wake(); + } + } + } + + impl EIC { + pub fn into_future(self, _irq: I) -> EIC + where + I: Binding, + { + EicInterrupt::unpend(); + unsafe { EicInterrupt::enable() }; + + EIC { eic: self.eic } + } + } + + #[allow(clippy::declare_interior_mutable_const)] + const NEW_WAKER: AtomicWaker = AtomicWaker::new(); + pub(super) static WAKERS: [AtomicWaker; NUM_CHANNELS] = [NEW_WAKER; NUM_CHANNELS]; +} + +#[cfg(feature = "async")] +pub use async_api::*; diff --git a/hal/src/thumbv6m/eic/pin.rs b/hal/src/thumbv6m/eic/pin.rs index e069bee1b62c..32cb6ab3595e 100644 --- a/hal/src/thumbv6m/eic/pin.rs +++ b/hal/src/thumbv6m/eic/pin.rs @@ -1,11 +1,11 @@ +use super::EIC; #[cfg(feature = "unproven")] use crate::ehal::digital::v2::InputPin; -use crate::gpio::{ - self, pin::*, AnyPin, FloatingInterrupt, PinMode, PullDownInterrupt, PullUpInterrupt, +use crate::{ + gpio::{self, pin::*, AnyPin, FloatingInterrupt, PinMode, PullDownInterrupt, PullUpInterrupt}, + pac, }; -use crate::pac; - -use super::EIC; +use core::mem::ManuallyDrop; /// The EicPin trait makes it more ergonomic to convert a gpio pin into an EIC /// pin. You should not implement this trait for yourself; only the @@ -18,13 +18,13 @@ pub trait EicPin { type PullDown; /// Configure a pin as a floating external interrupt - fn into_floating_ei(self) -> Self::Floating; + fn into_floating_ei(self, eic: &mut EIC) -> Self::Floating; /// Configure a pin as pulled-up external interrupt - fn into_pull_up_ei(self) -> Self::PullUp; + fn into_pull_up_ei(self, eic: &mut EIC) -> Self::PullUp; /// Configure a pin as pulled-down external interrupt - fn into_pull_down_ei(self) -> Self::PullDown; + fn into_pull_down_ei(self, eic: &mut EIC) -> Self::PullDown; } pub type Sense = pac::eic::config::SENSE0SELECT_A; @@ -56,6 +56,7 @@ crate::paste::item! { where GPIO: AnyPin, { + eic: ManuallyDrop, _pin: Pin, } @@ -66,33 +67,38 @@ crate::paste::item! { /// Construct pad from the appropriate pin in any mode. /// You may find it more convenient to use the `into_pad` trait /// and avoid referencing the pad type. - pub fn new(pin: GPIO) -> Self { + pub fn new(pin: GPIO, eic: &mut super::EIC) -> [<$PadType $num>] { + let eic = unsafe { + ManuallyDrop::new(core::ptr::read(eic as *const _)) + }; + [<$PadType $num>]{ - _pin:pin.into() + _pin:pin.into(), + eic, } } /// Configure the eic with options for this external interrupt - pub fn enable_event(&mut self, eic: &mut EIC) { - eic.eic.evctrl.modify(|_, w| { + pub fn enable_event(&mut self) { + self.eic.eic.evctrl.modify(|_, w| { w.[]().set_bit() }); } - pub fn enable_interrupt(&mut self, eic: &mut EIC) { - eic.eic.intenset.modify(|_, w| { + pub fn enable_interrupt(&mut self) { + self.eic.eic.intenset.modify(|_, w| { w.[]().set_bit() }); } - pub fn enable_interrupt_wake(&mut self, eic: &mut EIC) { - eic.eic.wakeup.modify(|_, w| { + pub fn enable_interrupt_wake(&mut self) { + self.eic.eic.wakeup.modify(|_, w| { w.[]().set_bit() }) } - pub fn disable_interrupt(&mut self, eic: &mut EIC) { - eic.eic.intenclr.modify(|_, w| { + pub fn disable_interrupt(&mut self) { + self.eic.eic.intenclr.modify(|_, w| { w.[]().set_bit() }); } @@ -107,10 +113,10 @@ crate::paste::item! { }); } - pub fn sense(&mut self, _eic: &mut EIC, sense: Sense) { + pub fn sense(&mut self, sense: Sense) { // Which of the two config blocks this eic config is in let offset = ($num >> 3) & 0b0001; - let config = unsafe { &(*pac::EIC::ptr()).config[offset] }; + let config = &self.eic.eic.config[offset]; config.modify(|_, w| unsafe { // Which of the eight eic configs in this config block @@ -128,10 +134,10 @@ crate::paste::item! { }); } - pub fn filter(&mut self, _eic: &mut EIC, filter: bool) { + pub fn filter(&mut self, filter: bool) { // Which of the two config blocks this eic config is in let offset = ($num >> 3) & 0b0001; - let config = unsafe { &(*pac::EIC::ptr()).config[offset] }; + let config = &self.eic.eic.config[offset]; config.modify(|_, w| { // Which of the eight eic configs in this config block @@ -150,9 +156,85 @@ crate::paste::item! { } } - impl ExternalInterrupt for [<$PadType $num>] { - fn id(&self) -> ExternalInterruptID { - $num + #[cfg(feature = "async")] + impl [<$PadType $num>] + where + GPIO: AnyPin, + Self: InputPin, + { + pub async fn wait(&mut self, sense: Sense) + { + use core::{task::Poll, future::poll_fn}; + self.disable_interrupt(); + + match sense { + Sense::HIGH => if self.is_high().unwrap() { return; }, + Sense::LOW => if self.is_low().unwrap() { return; }, + _ => (), + } + + self.sense(sense); + poll_fn(|cx| { + if self.is_interrupt() { + self.clear_interrupt(); + self.disable_interrupt(); + self.sense(Sense::NONE); + return Poll::Ready(()); + } + + super::super::async_api::WAKERS[$num].register(cx.waker()); + self.enable_interrupt(); + + if self.is_interrupt(){ + self.clear_interrupt(); + self.disable_interrupt(); + self.sense(Sense::NONE); + return Poll::Ready(()); + } + + Poll::Pending + }).await; + } + } + + #[cfg(feature = "async")] + impl embedded_hal_alpha::digital::ErrorType for [<$PadType $num>] + where + GPIO: AnyPin, + Self: InputPin, + { + type Error = core::convert::Infallible; + } + + #[cfg(feature = "async")] + impl embedded_hal_async::digital::Wait for [<$PadType $num>] + where + GPIO: AnyPin, + Self: InputPin, + { + async fn wait_for_high(&mut self) -> Result<(), Self::Error>{ + self.wait(Sense::HIGH).await; + Ok(()) + } + + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait(Sense::LOW).await; + Ok(()) + } + + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait(Sense::RISE).await; + Ok(()) + } + + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error>{ + self.wait(Sense::FALL).await; + Ok(()) + } + + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait(Sense::BOTH).await; + Ok(()) } } @@ -180,16 +262,16 @@ crate::paste::item! { type PullUp = [<$PadType $num>]>; type PullDown = [<$PadType $num>]>; - fn into_floating_ei(self) -> Self::Floating { - [<$PadType $num>]::new(self.into_floating_interrupt()) + fn into_floating_ei(self, eic: &mut super::EIC) -> Self::Floating { + [<$PadType $num>]::new(self.into_floating_interrupt(), eic) } - fn into_pull_up_ei(self) -> Self::PullUp { - [<$PadType $num>]::new(self.into_pull_up_interrupt()) + fn into_pull_up_ei(self, eic: &mut super::EIC) -> Self::PullUp { + [<$PadType $num>]::new(self.into_pull_up_interrupt(), eic) } - fn into_pull_down_ei(self) -> Self::PullDown { - [<$PadType $num>]::new(self.into_pull_down_interrupt()) + fn into_pull_down_ei(self, eic: &mut super::EIC) -> Self::PullDown { + [<$PadType $num>]::new(self.into_pull_down_interrupt(), eic) } } @@ -211,6 +293,9 @@ crate::paste::item! { // SAMD11 +#[cfg(feature = "samd11")] +pub const NUM_CHANNELS: usize = 8; + #[cfg(feature = "samd11")] mod impls { use super::*; @@ -248,6 +333,9 @@ mod impls { // SAMD21 +#[cfg(feature = "samd21")] +pub const NUM_CHANNELS: usize = 16; + #[cfg(feature = "samd21")] mod impls { use super::*; diff --git a/hal/src/thumbv6m/usb/bus.rs b/hal/src/thumbv6m/usb/bus.rs index f4c31d774375..7b3c6441fabe 100644 --- a/hal/src/thumbv6m/usb/bus.rs +++ b/hal/src/thumbv6m/usb/bus.rs @@ -16,8 +16,8 @@ use crate::usb::devicedesc::DeviceDescBank; use core::cell::{Ref, RefCell, RefMut}; use core::marker::PhantomData; use core::mem; -use cortex_m::interrupt::{free as disable_interrupts, Mutex}; use cortex_m::singleton; +use critical_section::{with as disable_interrupts, Mutex}; use usb_device::bus::PollResult; use usb_device::endpoint::{EndpointAddress, EndpointType}; use usb_device::{Result as UsbResult, UsbDirection, UsbError}; diff --git a/hal/src/thumbv7em/eic/mod.rs b/hal/src/thumbv7em/eic/mod.rs index ab9505de87bf..345f96af5da4 100644 --- a/hal/src/thumbv7em/eic/mod.rs +++ b/hal/src/thumbv7em/eic/mod.rs @@ -1,5 +1,4 @@ -use crate::clock::EicClock; -use crate::pac; +use crate::{clock::EicClock, pac}; pub mod pin; @@ -57,7 +56,32 @@ pub fn init_with_ulp32k(mclk: &mut pac::MCLK, _clock: EicClock, eic: pac::EIC) - /// A configured External Interrupt Controller. pub struct EIC { - _eic: pac::EIC, + eic: pac::EIC, +} + +impl EIC { + /// Run the provided closure with the EIC peripheral disabled. The + /// enable-protected registers, such as CONFIGx, should be accessed through + /// this method. + /// + /// # Caution + /// + /// You should not re-enable the provided EIC PAC object inside the provided + /// closure. + fn with_disable(&mut self, fun: impl Fn(&mut pac::EIC)) { + self.eic.ctrla.modify(|_, w| w.enable().clear_bit()); + self.enable_sync(); + fun(&mut self.eic); + self.eic.ctrla.modify(|_, w| w.enable().set_bit()); + self.enable_sync(); + } + + /// Busy-wait until SYNCBUSY.ENABLE clears + fn enable_sync(&mut self) { + while self.eic.syncbusy.read().enable().bit_is_set() { + core::hint::spin_loop(); + } + } } impl From for EIC { @@ -67,6 +91,45 @@ impl From for EIC { cortex_m::asm::nop(); } - Self { _eic: eic.eic } + Self { eic: eic.eic } + } +} + +#[cfg(feature = "async")] +mod async_api { + use super::pin::NUM_CHANNELS; + use super::*; + use crate::async_hal::interrupts::Handler; + use crate::util::BitIter; + use embassy_sync::waitqueue::AtomicWaker; + + pub struct InterruptHandler { + _private: (), } + + seq_macro::seq!(N in 0..=15 { + paste::paste! { + impl Handler]> for InterruptHandler { + unsafe fn on_interrupt() { + let eic = unsafe { pac::Peripherals::steal().EIC }; + + let pending_interrupts = BitIter(eic.intflag.read().bits()); + for channel in pending_interrupts { + let mask = 1 << channel; + // Disable the interrupt but don't clear; will be cleared + // when future is next polled. + unsafe { eic.intenclr.write(|w| w.bits(mask)) }; + WAKERS[channel as usize].wake(); + } + } + } + } + }); + + #[allow(clippy::declare_interior_mutable_const)] + const NEW_WAKER: AtomicWaker = AtomicWaker::new(); + pub(super) static WAKERS: [AtomicWaker; NUM_CHANNELS] = [NEW_WAKER; NUM_CHANNELS]; } + +#[cfg(feature = "async")] +pub use async_api::*; diff --git a/hal/src/thumbv7em/eic/pin.rs b/hal/src/thumbv7em/eic/pin.rs index a2a5c2df5949..9b52252eee05 100644 --- a/hal/src/thumbv7em/eic/pin.rs +++ b/hal/src/thumbv7em/eic/pin.rs @@ -1,9 +1,11 @@ +use super::EIC; #[cfg(feature = "unproven")] use crate::ehal::digital::v2::InputPin; -use crate::gpio::{ - self, pin::*, AnyPin, FloatingInterrupt, PinMode, PullDownInterrupt, PullUpInterrupt, +use crate::{ + gpio::{self, pin::*, AnyPin, FloatingInterrupt, PinMode, PullDownInterrupt, PullUpInterrupt}, + pac, }; -use crate::pac; +use core::mem::ManuallyDrop; /// The EicPin trait makes it more ergonomic to convert a gpio pin into an EIC /// pin. You should not implement this trait for yourself; only the @@ -16,13 +18,13 @@ pub trait EicPin { type PullDown; /// Configure a pin as a floating external interrupt - fn into_floating_ei(self) -> Self::Floating; + fn into_floating_ei(self, eic: &mut EIC) -> Self::Floating; /// Configure a pin as pulled-up external interrupt - fn into_pull_up_ei(self) -> Self::PullUp; + fn into_pull_up_ei(self, eic: &mut EIC) -> Self::PullUp; /// Configure a pin as pulled-down external interrupt - fn into_pull_down_ei(self) -> Self::PullDown; + fn into_pull_down_ei(self, eic: &mut EIC) -> Self::PullDown; } pub type Sense = pac::eic::config::SENSE0SELECT_A; @@ -55,102 +57,172 @@ crate::paste::item! { where GPIO: AnyPin, { + eic: ManuallyDrop, _pin: Pin, } // impl !Send for [<$PadType $num>] {}; // impl !Sync for [<$PadType $num>] {}} - impl [<$PadType $num>] { + impl [<$PadType $num>]{ /// Construct pad from the appropriate pin in any mode. /// You may find it more convenient to use the `into_pad` trait /// and avoid referencing the pad type. - pub fn new(pin: GPIO) -> Self { + pub fn new(pin: GPIO, eic: &mut EIC) -> Self { + let eic = unsafe { + ManuallyDrop::new(core::ptr::read(eic as *const _)) + }; + [<$PadType $num>]{ - _pin: pin.into() + _pin: pin.into(), + eic, } } + } - pub fn enable_event(&mut self, eic: &mut super::ConfigurableEIC) { - eic.eic.evctrl.modify(|_, w| unsafe { + impl [<$PadType $num>] { + + pub fn enable_event(&mut self) { + self.eic.eic.evctrl.modify(|_, w| unsafe { w.bits(1 << $num) }); } - pub fn enable_interrupt(&mut self, eic: &mut super::ConfigurableEIC) { - eic.eic.intenset.write(|w| unsafe { + pub fn enable_interrupt(&mut self) { + self.eic.eic.intenset.write(|w| unsafe { w.bits(1 << $num) }) } - pub fn disable_interrupt(&mut self, eic: &mut super::ConfigurableEIC) { - eic.eic.intenclr.write(|w| unsafe { + pub fn disable_interrupt(&mut self) { + self.eic.eic.intenclr.write(|w| unsafe { w.bits(1 << $num) }) } pub fn is_interrupt(&mut self) -> bool { - let intflag = unsafe { &(*pac::EIC::ptr()) }.intflag.read().bits(); + let intflag = self.eic.eic.intflag.read().bits(); intflag & (1 << $num) != 0 } pub fn state(&mut self) -> bool { - let state = unsafe { &(*pac::EIC::ptr()) }.pinstate.read().bits(); + let state = self.eic.eic.pinstate.read().bits(); state & (1 << $num) != 0 } pub fn clear_interrupt(&mut self) { unsafe { - {&(*pac::EIC::ptr())}.intflag.write(|w| { w.bits(1 << $num) }); + self.eic.eic.intflag.write(|w| { w.bits(1 << $num) }); } } - pub fn sense(&mut self, _eic: &mut super::ConfigurableEIC, sense: Sense) { - // Which of the two config blocks this eic config is in - let offset = ($num >> 3) & 0b0001; - let config = unsafe { &(*pac::EIC::ptr()).config[offset] }; - - config.modify(|_, w| unsafe { - // Which of the eight eic configs in this config block - match $num & 0b111 { - 0b000 => w.sense0().bits(sense as u8), - 0b001 => w.sense1().bits(sense as u8), - 0b010 => w.sense2().bits(sense as u8), - 0b011 => w.sense3().bits(sense as u8), - 0b100 => w.sense4().bits(sense as u8), - 0b101 => w.sense5().bits(sense as u8), - 0b110 => w.sense6().bits(sense as u8), - 0b111 => w.sense7().bits(sense as u8), - _ => unreachable!(), - } - }); + pub fn sense(&mut self, sense: Sense) { + self.eic.with_disable(|e| { + // Which of the two config blocks this eic config is in + let offset = ($num >> 3) & 0b0001; + let config = &e.config[offset]; + + config.modify(|_, w| unsafe { + // Which of the eight eic configs in this config block + match $num & 0b111 { + 0b000 => w.sense0().bits(sense as u8), + 0b001 => w.sense1().bits(sense as u8), + 0b010 => w.sense2().bits(sense as u8), + 0b011 => w.sense3().bits(sense as u8), + 0b100 => w.sense4().bits(sense as u8), + 0b101 => w.sense5().bits(sense as u8), + 0b110 => w.sense6().bits(sense as u8), + 0b111 => w.sense7().bits(sense as u8), + _ => unreachable!(), + } + }); + }); + + } - pub fn filter(&mut self, _eic: &mut super::ConfigurableEIC, filter: bool) { - // Which of the two config blocks this eic config is in - let offset = ($num >> 3) & 0b0001; - let config = unsafe { &(*pac::EIC::ptr()).config[offset] }; - - config.modify(|_, w| { - // Which of the eight eic configs in this config block - match $num & 0b111 { - 0b000 => w.filten0().bit(filter), - 0b001 => w.filten1().bit(filter), - 0b010 => w.filten2().bit(filter), - 0b011 => w.filten3().bit(filter), - 0b100 => w.filten4().bit(filter), - 0b101 => w.filten5().bit(filter), - 0b110 => w.filten6().bit(filter), - 0b111 => w.filten7().bit(filter), - _ => unreachable!(), - } + pub fn filter(&mut self, filter: bool) { + self.eic.with_disable(|e| { + // Which of the two config blocks this eic config is in + let offset = ($num >> 3) & 0b0001; + let config = &e.config[offset]; + + config.modify(|_, w| { + // Which of the eight eic configs in this config block + match $num & 0b111 { + 0b000 => w.filten0().bit(filter), + 0b001 => w.filten1().bit(filter), + 0b010 => w.filten2().bit(filter), + 0b011 => w.filten3().bit(filter), + 0b100 => w.filten4().bit(filter), + 0b101 => w.filten5().bit(filter), + 0b110 => w.filten6().bit(filter), + 0b111 => w.filten7().bit(filter), + _ => unreachable!(), + } + }); }); } + + /// Turn an EIC pin into a pin usable as a [`Future`](core::future::Future). + /// The correct interrupt source is needed. + #[cfg(feature = "async")] + pub fn into_future(self, _irq: I) -> [<$PadType $num>] + where + I: crate::async_hal::interrupts::Binding], super::async_api::InterruptHandler> + { + use crate::async_hal::interrupts; + use interrupts::Interrupt; + + interrupts::[]::unpend(); + unsafe { interrupts::[]::enable() }; + + [<$PadType $num>] { + _pin: self._pin, + eic: self.eic, + } + } } - impl ExternalInterrupt for [<$PadType $num>] { - fn id(&self) -> ExternalInterruptID { - $num + #[cfg(feature = "async")] + impl [<$PadType $num>] + where + GPIO: AnyPin, + Self: InputPin, + { + pub async fn wait(&mut self, sense: Sense) + { + use core::{task::Poll, future::poll_fn}; + self.disable_interrupt(); + + + // match sense { + // Sense::LOW => { defmt::debug!("LOW"); }, + // Sense::HIGH => { defmt::debug!("HIGH"); }, + // _ => (), + // } + + self.sense(sense); + poll_fn(|cx| { + if self.is_interrupt() { + self.clear_interrupt(); + self.disable_interrupt(); + self.sense(Sense::NONE); + return Poll::Ready(()); + } + + super::async_api::WAKERS[$num].register(cx.waker()); + self.enable_interrupt(); + + if self.is_interrupt(){ + self.clear_interrupt(); + self.disable_interrupt(); + self.sense(Sense::NONE); + return Poll::Ready(()); + } + + Poll::Pending + }).await; } } @@ -171,6 +243,51 @@ crate::paste::item! { } } + #[cfg(feature = "async")] + impl embedded_hal_alpha::digital::ErrorType for [<$PadType $num>] + where + GPIO: AnyPin, + Self: InputPin, + { + type Error = core::convert::Infallible; + } + + #[cfg(feature = "async")] + impl embedded_hal_async::digital::Wait for [<$PadType $num>] + where + GPIO: AnyPin, + Self: InputPin, + { + async fn wait_for_high(& mut self) -> Result<(), Self::Error> { + self.wait(Sense::HIGH).await; + Ok(()) + } + + + async fn wait_for_low(& mut self) -> Result<(), Self::Error> { + self.wait(Sense::LOW).await; + Ok(()) + } + + + async fn wait_for_rising_edge(& mut self) -> Result<(), Self::Error>{ + self.wait(Sense::RISE).await; + Ok(()) + } + + + async fn wait_for_falling_edge(& mut self) -> Result<(), Self::Error>{ + self.wait(Sense::FALL).await; + Ok(()) + } + + + async fn wait_for_any_edge(& mut self) -> Result<(), Self::Error> { + self.wait(Sense::BOTH).await; + Ok(()) + } + } + $( $(#[$attr])* impl EicPin for Pin { @@ -178,16 +295,16 @@ crate::paste::item! { type PullUp = [<$PadType $num>]>; type PullDown = [<$PadType $num>]>; - fn into_floating_ei(self) -> Self::Floating { - [<$PadType $num>]::new(self.into_floating_interrupt()) + fn into_floating_ei(self, eic: &mut EIC) -> Self::Floating { + [<$PadType $num>]::new(self.into_floating_interrupt(), eic) } - fn into_pull_up_ei(self) -> Self::PullUp { - [<$PadType $num>]::new(self.into_pull_up_interrupt()) + fn into_pull_up_ei(self, eic: &mut EIC) -> Self::PullUp { + [<$PadType $num>]::new(self.into_pull_up_interrupt(), eic) } - fn into_pull_down_ei(self) -> Self::PullDown { - [<$PadType $num>]::new(self.into_pull_down_interrupt()) + fn into_pull_down_ei(self, eic: &mut EIC) -> Self::PullDown { + [<$PadType $num>]::new(self.into_pull_down_interrupt(), eic) } } @@ -204,6 +321,8 @@ crate::paste::item! { }; } +pub const NUM_CHANNELS: usize = 16; + ei!(ExtInt[0] { PA00, PA16, diff --git a/hal/src/thumbv7em/usb/bus.rs b/hal/src/thumbv7em/usb/bus.rs index e8e544001bdd..fbb080cdd5b3 100644 --- a/hal/src/thumbv7em/usb/bus.rs +++ b/hal/src/thumbv7em/usb/bus.rs @@ -16,8 +16,8 @@ use crate::usb::devicedesc::DeviceDescBank; use core::cell::{Ref, RefCell, RefMut}; use core::marker::PhantomData; use core::mem; -use cortex_m::interrupt::{free as disable_interrupts, Mutex}; use cortex_m::singleton; +use critical_section::{with as disable_interrupts, Mutex}; use usb_device::bus::PollResult; use usb_device::endpoint::{EndpointAddress, EndpointType}; use usb_device::{Result as UsbResult, UsbDirection, UsbError}; diff --git a/hal/src/util.rs b/hal/src/util.rs new file mode 100644 index 000000000000..f99ced0a0fc1 --- /dev/null +++ b/hal/src/util.rs @@ -0,0 +1,17 @@ +/// Iterate over all bits that are `1`, returning the bit's position. +/// Shamelessly stolen from [embassy](https://github.com/embassy-rs/embassy/blob/3d1501c02038e5fe6f6d3b72bd18bd7a52595a77/embassy-stm32/src/exti.rs#L67) +pub struct BitIter(pub u32); + +impl Iterator for BitIter { + type Item = u32; + + fn next(&mut self) -> Option { + match self.0.trailing_zeros() { + 32 => None, + b => { + self.0 &= !(1 << b); + Some(b) + } + } + } +}