diff --git a/.github/workflows/build-bsp.yml b/.github/workflows/build-bsp.yml index 705bbeb3cc9..e1a4ec59887 100644 --- a/.github/workflows/build-bsp.yml +++ b/.github/workflows/build-bsp.yml @@ -30,6 +30,7 @@ jobs: - name: Install Rust run: | + rustup update rustup set profile minimal rustup override set ${{ matrix.toolchain }} target=$(cat ./crates.json | jq -Mr --arg board '${{ matrix.bsp.name }}' -c '.boards | .[$board] | .target') diff --git a/.github/workflows/build-hal.yml b/.github/workflows/build-hal.yml index be1e1291f7b..872f134dda1 100644 --- a/.github/workflows/build-hal.yml +++ b/.github/workflows/build-hal.yml @@ -24,6 +24,7 @@ jobs: - name: Install Rust run: | + rustup update rustup set profile minimal rustup override set ${{ matrix.toolchain }} target=$(cat ./crates.json | jq -Mr --arg pac "${{matrix.pac}}" -c '.hal_build_variants["${{matrix.pac}}"].target') diff --git a/README.md b/README.md index ab7e1ea289a..042d617f507 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,25 @@ The Peripheral Access Crates (PACs) are automatically generated from Microchip S [wio_terminal]: https://github.com/atsamd-rs/atsamd/tree/master/boards/wio_terminal [xiao_m0]: https://github.com/atsamd-rs/atsamd/tree/master/boards/xiao_m0 + +### `async` APIs + +[`atsamd_hal`](https://crates.io/crate/atsamd-hal) provides APIs for using `async`/`await` constructs with some of its peripherals. To enable `async` support, use the `async` Cargo feature. +Detailed documentation is provided in the `atsamd_hal::async_hal` module. The [metro_m4](https://github.com/atsamd-rs/atsamd/tree/master/boards/metro_m4/examples) and +[feather_m0](https://github.com/atsamd-rs/atsamd/tree/master/boards/feather_m0/examples) feature complete examples showing how to use async APIs. + +Please note that you must bring your own executor runtime such as [`embassy-executor`](https://crates.io/crates/embassy-executor) or [`rtic`](https://crates.io/crates/rtic) to be able to +use the async APIs. + +#### Supported peripherals + +* SPI +* I2C +* USART +* DMAC +* EIC (GPIO interrupts) +* Timers + ### Examples The BSPs include examples to quickly get up and running with the board. Building the examples diff --git a/boards/atsame54_xpro/Cargo.toml b/boards/atsame54_xpro/Cargo.toml index e1ccb591ec9..876a385e9f7 100644 --- a/boards/atsame54_xpro/Cargo.toml +++ b/boards/atsame54_xpro/Cargo.toml @@ -40,6 +40,9 @@ rtt-target = { version = "0.3", features = ["cortex-m"] } [features] default = ["rt", "atsamd-hal/same54p"] dma = ["atsamd-hal/dma"] +max-channels = ["dma", "atsamd-hal/max-channels"] +# Enable async support from atsamd-hal +async = ["atsamd-hal/async"] rt = ["cortex-m-rt", "atsamd-hal/same54p-rt"] usb = ["atsamd-hal/usb", "usb-device"] can = ["atsamd-hal/can"] diff --git a/boards/atsame54_xpro/examples/mcan.rs b/boards/atsame54_xpro/examples/mcan.rs index f8dd2f3f87b..b580d4176c7 100644 --- a/boards/atsame54_xpro/examples/mcan.rs +++ b/boards/atsame54_xpro/examples/mcan.rs @@ -140,12 +140,12 @@ mod app { let (pclk_eic, gclk0) = clock::pclk::Pclk::enable(tokens.pclks.eic, clocks.gclk0); - let mut eic = hal::eic::init_with_ulp32k(&mut mclk, pclk_eic.into(), ctx.device.eic); - let mut button = bsp::pin_alias!(pins.button).into_pull_up_ei(); - eic.button_debounce_pins(&[button.id()]); - button.sense(&mut eic, Sense::Fall); - button.enable_interrupt(&mut eic); - eic.finalize(); + let mut eic = + hal::eic::init_with_ulp32k(&mut mclk, pclk_eic.into(), ctx.device.eic).finalize(); + let mut button = bsp::pin_alias!(pins.button).into_pull_up_ei(&mut eic); + button.sense(Sense::Fall); + button.debounce(); + button.enable_interrupt(); let can1_rx = bsp::pin_alias!(pins.ata6561_rx).into_mode(); let can1_tx = bsp::pin_alias!(pins.ata6561_tx).into_mode(); diff --git a/boards/feather_m0/.cargo/config.toml b/boards/feather_m0/.cargo/config.toml index 01e807e22ff..e209d0667d7 100644 --- a/boards/feather_m0/.cargo/config.toml +++ b/boards/feather_m0/.cargo/config.toml @@ -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" # uncomment if using defmt ] [target.thumbv6m-none-eabi] diff --git a/boards/feather_m0/Cargo.toml b/boards/feather_m0/Cargo.toml index f917759b29a..d3ee727064b 100644 --- a/boards/feather_m0/Cargo.toml +++ b/boards/feather_m0/Cargo.toml @@ -37,7 +37,10 @@ version = "0.3" optional = true [dev-dependencies] -cortex-m-rtic = "1.0" +embassy-executor = { version = "0.6.2", features = ["arch-cortex-m", "executor-thread", "task-arena-size-64"] } +rtic = { version = "2.1.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.2" cortex-m-semihosting = "0.3" @@ -45,9 +48,12 @@ ssd1306 = "0.7" embedded-graphics = "0.7.1" drogue-nom-utils = "0.1" nom = { version = "5", default-features = false } -heapless = "0.7" +heapless = "0.8" panic-halt = "0.2" -panic-semihosting = "0.5" +panic-semihosting = "0.6" +defmt = "0.3" +defmt-rtt = "0.4" +panic-probe = "0.3" [features] # ask the HAL to enable atsamd21g support @@ -61,6 +67,8 @@ rfm = [] express = [] dma = ["atsamd-hal/dma"] max-channels = ["dma", "atsamd-hal/max-channels"] +# Enable async support from atsamd-hal +async = ["atsamd-hal/async"] # Enable pins for the adalogger SD card reader adalogger = [] # Enable pins for Feather with WINC1500 wifi @@ -72,6 +80,10 @@ use_semihosting = [] [[example]] name = "blinky_basic" +[[example]] +name = "blinky_rtic" +required-features = ["rtic"] + [[example]] name = "timers" @@ -122,8 +134,7 @@ name = "adalogger" required-features = ["adalogger", "usb", "sdmmc"] [[example]] -name = "blinky_rtic" -required-features = ["rtic"] +name = "blinky_monotonic" [[example]] name = "uart" @@ -143,3 +154,27 @@ required-features = ["dma"] [[example]] name = "spi" required-features = ["dma"] + +[[example]] +name = "async_dmac" +required-features = ["dma", "async"] + +[[example]] +name = "async_timer" +required-features = ["async"] + +[[example]] +name = "async_eic" +required-features = ["async"] + +[[example]] +name = "async_i2c" +required-features = ["dma", "async"] + +[[example]] +name = "async_spi" +required-features = ["dma", "async"] + +[[example]] +name = "async_uart" +required-features = ["dma", "async"] diff --git a/boards/feather_m0/examples/async_dmac.rs b/boards/feather_m0/examples/async_dmac.rs new file mode 100644 index 00000000000..9cb8efa8fb9 --- /dev/null +++ b/boards/feather_m0/examples/async_dmac.rs @@ -0,0 +1,73 @@ +//! This example shows a safe API to +//! execute a memory-to-memory DMA transfer + +#![no_std] +#![no_main] + +use defmt_rtt as _; +use panic_probe as _; + +atsamd_hal::bind_interrupts!(struct Irqs { + DMAC => atsamd_hal::dmac::InterruptHandler; +}); + +use bsp::hal; +use bsp::pac; +use feather_m0 as bsp; +use hal::{ + clock::GenericClockController, + dmac::{DmaController, PriorityLevel, TriggerAction, TriggerSource}, +}; + +#[embassy_executor::main] +async fn main(_s: embassy_executor::Spawner) { + let mut peripherals = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + + 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 mut channel = channels.0.init(PriorityLevel::Lvl0); + + let mut source = [0xff; 100]; + let mut dest = [0x0; 100]; + + defmt::info!( + "Launching a DMA transfer.\n\tSource: {:#x}\n\tDestination: {:#x}", + &source, + &dest + ); + + channel + .transfer_future( + &mut source, + &mut dest, + TriggerSource::Disable, + TriggerAction::Block, + ) + .await + .unwrap(); + + defmt::info!( + "Finished DMA transfer.\n\tSource: {:#x}\n\tDestination: {:#x}", + &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 00000000000..31e11203475 --- /dev/null +++ b/boards/feather_m0/examples/async_eic.rs @@ -0,0 +1,60 @@ +#![no_std] +#![no_main] + +use defmt_rtt as _; +use panic_probe as _; + +use bsp::pac; +use bsp::{hal, pin_alias}; +use feather_m0 as bsp; +use hal::{ + clock::{enable_internal_32kosc, ClockGenId, ClockSource, GenericClockController}, + ehal::digital::StatefulOutputPin, + eic::{ + pin::{ExtInt2, Sense}, + EIC, + }, + gpio::{Pin, PullUpInterrupt}, +}; + +atsamd_hal::bind_interrupts!(struct Irqs { + EIC => atsamd_hal::eic::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_s: embassy_executor::Spawner) { + let mut peripherals = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + + 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 mut 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.get_gclk(ClockGenId::Gclk2).unwrap(); + 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(); + + 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 00000000000..7e523808425 --- /dev/null +++ b/boards/feather_m0/examples/async_i2c.rs @@ -0,0 +1,74 @@ +#![no_std] +#![no_main] + +use defmt_rtt as _; +use panic_probe as _; + +use bsp::hal; +use bsp::pac; +use feather_m0 as bsp; +use fugit::MillisDuration; +use hal::ehal_async::i2c::I2c; +use hal::{ + clock::GenericClockController, + dmac::{DmaController, PriorityLevel}, + prelude::*, + sercom::{i2c, Sercom3}, +}; +use rtic_monotonics::systick::Systick; + +atsamd_hal::bind_interrupts!(struct Irqs { + SERCOM3 => atsamd_hal::sercom::i2c::InterruptHandler; + DMAC => atsamd_hal::dmac::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_s: embassy_executor::Spawner) { + let mut peripherals = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + + 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); + let i2c_sercom = bsp::periph_alias!(peripherals.i2c_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 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 mut i2c = i2c::Config::new(&peripherals.pm, i2c_sercom, pads, sercom3_clock.freq()) + .baud(100.kHz()) + .enable() + .into_future(Irqs) + .with_dma_channel(channel0); + + 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 00000000000..9109fb24d98 --- /dev/null +++ b/boards/feather_m0/examples/async_spi.rs @@ -0,0 +1,80 @@ +#![no_std] +#![no_main] + +use defmt_rtt as _; +use panic_probe as _; + +use bsp::hal; +use bsp::pac; +use feather_m0 as bsp; +use fugit::MillisDuration; +use hal::ehal_async::spi::SpiBus; +use hal::{ + clock::GenericClockController, + dmac::{DmaController, PriorityLevel}, + prelude::*, + sercom::Sercom4, +}; +use rtic_monotonics::systick::Systick; + +atsamd_hal::bind_interrupts!(struct Irqs { + SERCOM4 => atsamd_hal::sercom::spi::InterruptHandler; + DMAC => atsamd_hal::dmac::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_s: embassy_executor::Spawner) { + let mut peripherals = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + + 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); + let spi_sercom = bsp::periph_alias!(peripherals.spi_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 mut spi = bsp::spi_master( + &mut clocks, + 100.kHz(), + spi_sercom, + &mut peripherals.pm, + sclk, + mosi, + miso, + ) + .into_future(Irqs) + .with_dma_channels(channel0, channel1); + + 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 00000000000..065ea9bfa84 --- /dev/null +++ b/boards/feather_m0/examples/async_timer.rs @@ -0,0 +1,56 @@ +#![no_std] +#![no_main] + +#[cfg(not(feature = "use_semihosting"))] +use panic_halt as _; +#[cfg(feature = "use_semihosting")] +use panic_semihosting as _; + +use bsp::{hal, pac, pin_alias}; +use feather_m0 as bsp; +use fugit::MillisDurationU32; +use hal::{ + clock::{enable_internal_32kosc, ClockGenId, ClockSource, GenericClockController}, + ehal::digital::StatefulOutputPin, + pac::Tc4, + timer::TimerCounter, +}; + +atsamd_hal::bind_interrupts!(struct Irqs { + TC4 => atsamd_hal::timer::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_s: embassy_executor::Spawner) { + let mut peripherals = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + + 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 mut 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 mut timer = timer.into_future(Irqs); + + 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 00000000000..87a9869f7fc --- /dev/null +++ b/boards/feather_m0/examples/async_uart.rs @@ -0,0 +1,101 @@ +#![no_std] +#![no_main] + +use defmt_rtt as _; +use panic_probe as _; + +use bsp::{hal, pac, 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; +}); + +#[embassy_executor::main] +async fn main(spawner: embassy_executor::Spawner) { + let mut peripherals = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + + 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(); + + // For embassy-executor, spawning multiple tasks on the same executor requires + // either: + // * Tuning the task arena size either via a Cargo feature or the + // `EMBASSY_EXECUTOR_TASK_ARENA_SIZE` environment variable + // * Using the `nightly` Cargo feature along with + // #![feature(type_alias_impl_trait)] + spawner.spawn(send_bytes(uart_tx)).unwrap(); + spawner.spawn(receive_bytes(uart_rx)).unwrap(); +} + +#[embassy_executor::task] +async fn send_bytes(mut uart_tx: UartFutureTxDuplexDma, Ch1>) { + loop { + uart_tx.write(b"Hello, world!").await.unwrap(); + defmt::info!("Sent 10 bytes"); + Systick::delay(MillisDuration::::from_ticks(500).convert()).await; + } +} + +#[embassy_executor::task] +async fn receive_bytes(mut uart_rx: UartFutureRxDuplexDma, Ch0>) { + uart_rx.as_mut().flush_rx_buffer(); + + loop { + let mut buf = [0x00; 10]; + match uart_rx.read(&mut buf).await { + Ok(()) => defmt::info!("read {:#x}", &buf), + Err(_) => { + defmt::error!("UART Error."); + // Flusing the RX buffer may drop a few bytes. + uart_rx.as_mut().flush_rx_buffer(); + } + } + } +} diff --git a/boards/feather_m0/examples/blinky_monotonic.rs b/boards/feather_m0/examples/blinky_monotonic.rs new file mode 100644 index 00000000000..07b6c2d0f8f --- /dev/null +++ b/boards/feather_m0/examples/blinky_monotonic.rs @@ -0,0 +1,37 @@ +//! Uses RTIC with systick to blink a led in an asynchronous fashion. +#![no_std] +#![no_main] + +use feather_m0 as bsp; + +#[cfg(not(feature = "use_semihosting"))] +use panic_halt as _; +#[cfg(feature = "use_semihosting")] +use panic_semihosting as _; + +use bsp::{hal, pac, pin_alias}; +use fugit::MillisDurationU32; +use hal::clock::GenericClockController; +use hal::prelude::*; +use rtic_monotonics::systick::Systick; + +#[embassy_executor::main] +async fn main(_s: embassy_executor::Spawner) { + let mut peripherals = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + let pins = bsp::Pins::new(peripherals.port); + let mut clocks = GenericClockController::with_external_32kosc( + peripherals.gclk, + &mut peripherals.pm, + &mut peripherals.sysctrl, + &mut peripherals.nvmctrl, + ); + let _gclk = clocks.gclk0(); + + let mut red_led: bsp::RedLed = pin_alias!(pins.red_led).into(); + + loop { + let _ = red_led.toggle(); + Systick::delay(MillisDurationU32::from_ticks(1000).convert()).await; + } +} diff --git a/boards/feather_m0/examples/blinky_rtic.rs b/boards/feather_m0/examples/blinky_rtic.rs index fd8fbe86e36..159f10aa126 100644 --- a/boards/feather_m0/examples/blinky_rtic.rs +++ b/boards/feather_m0/examples/blinky_rtic.rs @@ -1,7 +1,4 @@ -//! 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 a software task to blink an LED. #![no_std] #![no_main] @@ -16,10 +13,11 @@ use panic_semihosting as _; mod app { use super::*; use bsp::{hal, pin_alias}; - use hal::clock::{ClockGenId, ClockSource, GenericClockController}; + use fugit::MillisDurationU32; + use hal::clock::GenericClockController; use hal::pac::Peripherals; use hal::prelude::*; - use hal::rtc::{Count32Mode, Duration, Rtc}; + use rtic_monotonics::systick::Systick; #[local] struct Local {} @@ -31,11 +29,8 @@ mod app { red_led: bsp::RedLed, } - #[monotonic(binds = RTC, default = true)] - type RtcMonotonic = Rtc; - #[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; @@ -46,12 +41,6 @@ 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 @@ -60,13 +49,17 @@ mod app { // Start the blink task blink::spawn().unwrap(); - (Shared { red_led }, Local {}, init::Monotonics(rtc)) + (Shared { red_led }, Local {}) } #[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(); + async fn blink(mut cx: blink::Context) { + loop { + // If the LED were a local resource, the lock would not be necessary + cx.shared.red_led.lock(|led| led.toggle().unwrap()); + + // cx.local.rtc.delay(Duration::secs(1)).await; + Systick::delay(MillisDurationU32::from_ticks(1000).convert()).await; + } } } diff --git a/boards/feather_m0/examples/dmac.rs b/boards/feather_m0/examples/dmac.rs index 4297470ded4..eff3d6ce51e 100644 --- a/boards/feather_m0/examples/dmac.rs +++ b/boards/feather_m0/examples/dmac.rs @@ -51,7 +51,6 @@ fn main() -> ! { // Setup a DMA transfer (memory-to-memory -> incrementing source, incrementing // destination) with a 8-bit beat size let xfer = Transfer::new_from_arrays(chan0, buf_src, buf_dest, false) - .with_waker(|_status| asm::nop()) .begin(TriggerSource::Disable, TriggerAction::Block); // Wait for transfer to complete and grab resulting buffers let (chan0, buf_src, buf_dest) = xfer.wait(); diff --git a/boards/feather_m0/examples/eic.rs b/boards/feather_m0/examples/eic.rs index 5d3b06afc13..bfedc2a9d7e 100644 --- a/boards/feather_m0/examples/eic.rs +++ b/boards/feather_m0/examples/eic.rs @@ -47,9 +47,9 @@ fn main() -> ! { let eic_clock = clocks.eic(&gclk0).unwrap(); let mut eic = EIC::init(&mut peripherals.pm, eic_clock, peripherals.eic); let button: Pin<_, PullUpInterrupt> = pins.d10.into(); - let mut extint = ExtInt2::new(button); - extint.sense(&mut eic, Sense::Fall); - extint.enable_interrupt(&mut eic); + let mut extint = ExtInt2::new(button, &mut eic); + extint.sense(Sense::Fall); + extint.enable_interrupt(); // Enable EIC interrupt in the NVIC unsafe { diff --git a/boards/feather_m0/examples/spi.rs b/boards/feather_m0/examples/spi.rs index 753f6e08b46..43f8f9e395d 100644 --- a/boards/feather_m0/examples/spi.rs +++ b/boards/feather_m0/examples/spi.rs @@ -78,7 +78,6 @@ fn main() -> ! { spi.transfer(&mut dest, &source).unwrap(); // Simultaneously read and write from the same buffer - // Cannot use DMA for this method, so it reverts to word by word transfers. spi.transfer_in_place(&mut source).unwrap(); } } diff --git a/boards/feather_m0/examples/uart.rs b/boards/feather_m0/examples/uart.rs index 3bf5cb917f6..a229940c650 100644 --- a/boards/feather_m0/examples/uart.rs +++ b/boards/feather_m0/examples/uart.rs @@ -54,12 +54,7 @@ fn main() -> ! { // Make buffers to store data to send/receive let mut rx_buffer = [0x00; 50]; - let mut tx_buffer = [0x00; 50]; - - // For fun, store numbers from 0 to 49 in buffer - for (i, c) in tx_buffer.iter_mut().enumerate() { - *c = i as u8; - } + let tx_buffer = b"Hello, world!"; loop { // Send data. We block on each byte, but we could also perform some tasks while diff --git a/boards/feather_m0/examples/uart_dma_nonblocking.rs b/boards/feather_m0/examples/uart_dma_nonblocking.rs index f42edf72fe3..dae57b611d7 100644 --- a/boards/feather_m0/examples/uart_dma_nonblocking.rs +++ b/boards/feather_m0/examples/uart_dma_nonblocking.rs @@ -81,11 +81,11 @@ fn main() -> ! { // Setup a DMA transfer to send our data asynchronously. // We'll set the waker to be a no-op - let tx_dma = tx.send_with_dma(tx_buffer, chan0, |_| {}); + let tx_dma = tx.send_with_dma(tx_buffer, chan0); // Setup a DMA transfer to receive our data asynchronously. // Again, we'll set the waker to be a no-op - let rx_dma = rx.receive_with_dma(rx_buffer, chan1, |_| {}); + let rx_dma = rx.receive_with_dma(rx_buffer, chan1); // Wait for transmit DMA transfer to complete let (_chan0, _tx_buffer, _tx) = tx_dma.wait(); diff --git a/boards/feather_m4/Cargo.toml b/boards/feather_m4/Cargo.toml index 2fa543ef19f..362191eda43 100644 --- a/boards/feather_m4/Cargo.toml +++ b/boards/feather_m4/Cargo.toml @@ -48,6 +48,8 @@ rt = ["cortex-m-rt", "atsamd-hal/samd51j-rt"] usb = ["atsamd-hal/usb", "usb-device"] dma = ["atsamd-hal/dma"] max-channels = ["dma", "atsamd-hal/dma"] +# Enable async support from atsamd-hal +async = ["atsamd-hal/async"] use_semihosting = [] [[example]] diff --git a/boards/feather_m4/examples/uart.rs b/boards/feather_m4/examples/uart.rs index 3068709aa60..a21c0666187 100644 --- a/boards/feather_m4/examples/uart.rs +++ b/boards/feather_m4/examples/uart.rs @@ -53,12 +53,7 @@ fn main() -> ! { // Make buffers to store data to send/receive let mut rx_buffer = [0x00; 50]; - let mut tx_buffer = [0x00; 50]; - - // For fun, store numbers from 0 to 49 in buffer - for (i, c) in tx_buffer.iter_mut().enumerate() { - *c = i as u8; - } + let tx_buffer = b"Hello, world!"; loop { // Send data. We block on each byte, but we could also perform some tasks while diff --git a/boards/feather_m4/examples/uart_dma_nonblocking.rs b/boards/feather_m4/examples/uart_dma_nonblocking.rs index 317293d9184..03cee79d6db 100644 --- a/boards/feather_m4/examples/uart_dma_nonblocking.rs +++ b/boards/feather_m4/examples/uart_dma_nonblocking.rs @@ -82,11 +82,11 @@ fn main() -> ! { // Setup a DMA transfer to send our data asynchronously. // We'll set the waker to be a no-op - let tx_dma = tx.send_with_dma(tx_buffer, chan0, |_| {}); + let tx_dma = tx.send_with_dma(tx_buffer, chan0); // Setup a DMA transfer to receive our data asynchronously. // Again, we'll set the waker to be a no-op - let rx_dma = rx.receive_with_dma(rx_buffer, chan1, |_| {}); + let rx_dma = rx.receive_with_dma(rx_buffer, chan1); // Wait for transmit DMA transfer to complete let (_chan0, _tx_buffer, _tx) = tx_dma.wait(); diff --git a/boards/metro_m0/Cargo.toml b/boards/metro_m0/Cargo.toml index 6cc975adc2a..ec7593412fa 100644 --- a/boards/metro_m0/Cargo.toml +++ b/boards/metro_m0/Cargo.toml @@ -43,6 +43,8 @@ cortex-m-rtic = "1.0" default = ["rt", "atsamd-hal/samd21g"] dma = ["atsamd-hal/dma"] max-channels = ["dma", "atsamd-hal/max-channels"] +# Enable async support from atsamd-hal +async = ["atsamd-hal/async"] rt = ["cortex-m-rt", "atsamd-hal/samd21g-rt"] rtic = ["atsamd-hal/rtic"] use_rtt = ["atsamd-hal/use_rtt"] diff --git a/boards/metro_m4/.cargo/config.toml b/boards/metro_m4/.cargo/config.toml index 553292e3d06..7efea61cb91 100644 --- a/boards/metro_m4/.cargo/config.toml +++ b/boards/metro_m4/.cargo/config.toml @@ -1,6 +1,6 @@ # vim:ft=toml: [target.thumbv7em-none-eabihf] -runner = 'probe-run --chip ATSAMD51J19A' +runner = 'probe-rs run --chip ATSAMD51J19A --speed 20000' [build] target = "thumbv7em-none-eabihf" @@ -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 c0d98a88a29..6a2fb662562 100644 --- a/boards/metro_m4/Cargo.toml +++ b/boards/metro_m4/Cargo.toml @@ -36,20 +36,27 @@ cortex-m = "0.7" embedded-hal = "1.0" embedded-hal-nb = "1.0" usbd-serial = "0.2" +embassy-executor = { version = "0.6.2", features = ["arch-cortex-m", "executor-thread", "task-arena-size-8192"] } panic-probe = "0.3" panic-halt = "0.2" panic-semihosting = "0.5" panic-rtt-target = { version = "0.1.1", features = ["cortex-m"] } cortex-m-semihosting = "0.3" +rtic-monotonics = { version = "2.0.1", features = ["cortex-m-systick"] } rtt-target = { version = "0.3.0", features = ["cortex-m"] } smart-leds = "0.3" +defmt = "0.3" +defmt-rtt = "0.4" [dev-dependencies.ws2812-timer-delay] version = "0.3" + [features] # ask the HAL to enable atsamd51j support default = ["rt", "atsamd-hal/samd51j"] +# Enable async support from atsamd-hal +async = ["atsamd-hal/async"] dma = ["atsamd-hal/dma"] max-channels = ["dma", "atsamd-hal/max-channels"] rt = ["cortex-m-rt", "atsamd-hal/samd51j-rt"] @@ -94,3 +101,27 @@ required-features = ["dma"] [[example]] name = "spi" required-features = ["dma"] + +[[example]] +name = "async_dmac" +required-features = ["dma", "async"] + +[[example]] +name = "async_timer" +required-features = ["async"] + +[[example]] +name = "async_eic" +required-features = ["async"] + +[[example]] +name = "async_i2c" +required-features = ["dma", "async"] + +[[example]] +name = "async_spi" +required-features = ["dma", "async"] + +[[example]] +name = "async_uart" +required-features = ["dma", "async"] diff --git a/boards/metro_m4/examples/async_dmac.rs b/boards/metro_m4/examples/async_dmac.rs new file mode 100644 index 00000000000..81a1a120bd3 --- /dev/null +++ b/boards/metro_m4/examples/async_dmac.rs @@ -0,0 +1,74 @@ +//! This example shows a safe API to +//! execute a memory-to-memory DMA transfer + +#![no_std] +#![no_main] + +use defmt_rtt as _; +use panic_probe as _; + +use bsp::hal; +use bsp::pac; +use hal::{ + clock::GenericClockController, + dmac::{DmaController, PriorityLevel, TriggerAction, TriggerSource}, +}; +use metro_m4 as bsp; + +atsamd_hal::bind_multiple_interrupts!(struct Irqs { + DMAC: [DMAC_0, DMAC_1, DMAC_2, DMAC_OTHER] => atsamd_hal::dmac::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_s: embassy_executor::Spawner) { + let mut peripherals = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + + 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 mut channel = channels.0.init(PriorityLevel::Lvl0); + + let mut source = [0xff; 100]; + let mut dest = [0x0; 100]; + + defmt::info!( + "Launching a DMA transfer.\n\tSource: {:#x}\n\tDestination: {:#x}", + &source, + &dest + ); + + channel + .transfer_future( + &mut source, + &mut dest, + TriggerSource::Disable, + TriggerAction::Block, + ) + .await + .unwrap(); + + defmt::info!( + "Finished DMA transfer.\n\tSource: {:#x}\n\tDestination: {:#x}", + &source, + &dest + ); + + loop { + cortex_m::asm::wfi(); + } +} diff --git a/boards/metro_m4/examples/async_eic.rs b/boards/metro_m4/examples/async_eic.rs new file mode 100644 index 00000000000..042314ae33d --- /dev/null +++ b/boards/metro_m4/examples/async_eic.rs @@ -0,0 +1,62 @@ +#![no_std] +#![no_main] + +use defmt_rtt as _; +use panic_probe as _; + +use bsp::pac; +use bsp::{hal, pin_alias}; +use hal::{ + clock::{ClockGenId, ClockSource, GenericClockController}, + ehal::digital::StatefulOutputPin, + eic::{ + self, + pin::{ExtInt7, Sense}, + }, + gpio::{Pin, PullUpInterrupt}, +}; +use metro_m4 as bsp; + +atsamd_hal::bind_interrupts!(struct Irqs { + EIC_EXTINT_7 => atsamd_hal::eic::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_s: embassy_executor::Spawner) { + let mut peripherals = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + + let mut clocks = GenericClockController::with_external_32kosc( + peripherals.gclk, + &mut peripherals.mclk, + &mut peripherals.osc32kctrl, + &mut peripherals.oscctrl, + &mut peripherals.nvmctrl, + ); + let pins = bsp::Pins::new(peripherals.port); + let mut red_led: bsp::RedLed = pin_alias!(pins.red_led).into(); + + let _internal_clock = clocks + .configure_gclk_divider_and_source(ClockGenId::Gclk2, 1, ClockSource::Osculp32k, false) + .unwrap(); + clocks.configure_standby(ClockGenId::Gclk2, true); + + // Configure a clock for the EIC peripheral + let gclk2 = clocks.get_gclk(ClockGenId::Gclk2).unwrap(); + let eic_clock = clocks.eic(&gclk2).unwrap(); + + let mut eic = + eic::init_with_ulp32k(&mut peripherals.mclk, eic_clock, peripherals.eic).finalize(); + // let mut eic = EIC::init(&mut peripherals.MCLK, eic_clock, + // peripherals.EIC).into_future(Irqs); + let button: Pin<_, PullUpInterrupt> = pins.d0.into(); + let mut extint = ExtInt7::new(button, &mut eic).into_future(Irqs); + extint.enable_interrupt(); + + 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/metro_m4/examples/async_i2c.rs b/boards/metro_m4/examples/async_i2c.rs new file mode 100644 index 00000000000..2fec89e5fc9 --- /dev/null +++ b/boards/metro_m4/examples/async_i2c.rs @@ -0,0 +1,84 @@ +#![no_std] +#![no_main] + +use defmt_rtt as _; +use panic_probe as _; + +use bsp::hal; +use bsp::pac; +use hal::ehal_async::i2c::I2c; +use hal::fugit::Hertz; +use hal::fugit::MillisDuration; +use hal::{ + clock::GenericClockController, + dmac::{DmaController, PriorityLevel}, + prelude::*, + sercom::{i2c, Sercom5}, +}; +use metro_m4 as bsp; +use rtic_monotonics::Monotonic; + +rtic_monotonics::systick_monotonic!(Mono, 10000); + +atsamd_hal::bind_multiple_interrupts!(struct DmacIrqs { + DMAC: [DMAC_0, DMAC_1, DMAC_2, DMAC_OTHER] => atsamd_hal::dmac::InterruptHandler; +}); + +atsamd_hal::bind_multiple_interrupts!(struct I2cIrqs { + SERCOM5: [SERCOM5_0, SERCOM5_1, SERCOM5_2, SERCOM5_3, SERCOM5_OTHER] => atsamd_hal::sercom::i2c::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_s: embassy_executor::Spawner) { + let mut peripherals = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + + let mut clocks = GenericClockController::with_external_32kosc( + peripherals.gclk, + &mut peripherals.mclk, + &mut peripherals.osc32kctrl, + &mut peripherals.oscctrl, + &mut peripherals.nvmctrl, + ); + + let freq: Hertz = clocks.gclk0().into(); + Mono::start(_core.SYST, freq.to_Hz()); + + let pins = bsp::Pins::new(peripherals.port); + + // Take SDA and SCL + let (sda, scl) = (pins.sda, pins.scl); + let i2c_sercom = bsp::periph_alias!(peripherals.i2c_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(DmacIrqs); + // 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 sercom5_clock = &clocks.sercom5_core(&gclk0).unwrap(); + let pads = i2c::Pads::new(sda, scl); + let mut i2c = i2c::Config::new(&peripherals.mclk, i2c_sercom, pads, sercom5_clock.freq()) + .baud(100.kHz()) + .enable() + .into_future(I2cIrqs) + .with_dma_channel(channel0); + + 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); + Mono::delay(MillisDuration::::from_ticks(500).convert()).await; + } +} diff --git a/boards/metro_m4/examples/async_spi.rs b/boards/metro_m4/examples/async_spi.rs new file mode 100644 index 00000000000..8c384bf4c12 --- /dev/null +++ b/boards/metro_m4/examples/async_spi.rs @@ -0,0 +1,88 @@ +#![no_std] +#![no_main] + +use defmt_rtt as _; +use panic_probe as _; + +use bsp::hal; +use bsp::pac; +use hal::ehal_async::spi::SpiBus; +use hal::fugit::Hertz; +use hal::fugit::MillisDuration; +use hal::{ + clock::GenericClockController, + dmac::{DmaController, PriorityLevel}, + prelude::*, + sercom::Sercom2, +}; +use metro_m4 as bsp; +use rtic_monotonics::Monotonic; + +rtic_monotonics::systick_monotonic!(Mono, 10000); + +atsamd_hal::bind_multiple_interrupts!(struct DmacIrqs { + DMAC: [DMAC_0, DMAC_1, DMAC_2, DMAC_OTHER] => atsamd_hal::dmac::InterruptHandler; +}); + +atsamd_hal::bind_multiple_interrupts!(struct SpiIrqs { + SERCOM2: [SERCOM2_0, SERCOM2_1, SERCOM2_2, SERCOM2_3, SERCOM2_OTHER] => atsamd_hal::sercom::spi::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_s: embassy_executor::Spawner) { + let mut peripherals = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + + let mut clocks = GenericClockController::with_external_32kosc( + peripherals.gclk, + &mut peripherals.mclk, + &mut peripherals.osc32kctrl, + &mut peripherals.oscctrl, + &mut peripherals.nvmctrl, + ); + + let freq: Hertz = clocks.gclk0().into(); + Mono::start(_core.SYST, freq.to_Hz()); + + let pins = bsp::Pins::new(peripherals.port); + + // Take SPI pins + let (miso, mosi, sclk) = (pins.miso, pins.mosi, pins.sclk); + let spi_sercom = bsp::periph_alias!(peripherals.spi_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(DmacIrqs); + // 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 mut spi = bsp::spi_master( + &mut clocks, + 100.kHz(), + spi_sercom, + &mut peripherals.mclk, + sclk, + mosi, + miso, + ) + .into_future(SpiIrqs) + .with_dma_channels(channel0, channel1); + + loop { + defmt::info!("Sending 0x00 to SPI device..."); + 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); + Mono::delay(MillisDuration::::from_ticks(1000).convert()).await; + } +} diff --git a/boards/metro_m4/examples/async_timer.rs b/boards/metro_m4/examples/async_timer.rs new file mode 100644 index 00000000000..3e29366e5af --- /dev/null +++ b/boards/metro_m4/examples/async_timer.rs @@ -0,0 +1,49 @@ +#![no_std] +#![no_main] + +#[cfg(not(feature = "use_semihosting"))] +use panic_halt as _; +#[cfg(feature = "use_semihosting")] +use panic_semihosting as _; + +use bsp::{hal, pac, pin_alias}; +use hal::fugit::MillisDurationU32; +use hal::{ + clock::GenericClockController, ehal::digital::StatefulOutputPin, pac::Tc4, timer::TimerCounter, +}; +use metro_m4 as bsp; + +atsamd_hal::bind_interrupts!(struct Irqs { + TC4 => atsamd_hal::timer::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_s: embassy_executor::Spawner) { + let mut peripherals = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + + let mut clocks = GenericClockController::with_external_32kosc( + peripherals.gclk, + &mut peripherals.mclk, + &mut peripherals.osc32kctrl, + &mut peripherals.oscctrl, + &mut peripherals.nvmctrl, + ); + let pins = bsp::Pins::new(peripherals.port); + let mut red_led: bsp::RedLed = pin_alias!(pins.red_led).into(); + + // configure a clock for the TC4 and TC5 peripherals + let timer_clock = clocks.gclk0(); + 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.mclk); + let mut timer = timer.into_future(Irqs); + + loop { + timer + .delay(MillisDurationU32::from_ticks(500).convert()) + .await; + red_led.toggle().unwrap(); + } +} diff --git a/boards/metro_m4/examples/async_uart.rs b/boards/metro_m4/examples/async_uart.rs new file mode 100644 index 00000000000..1cb703874d1 --- /dev/null +++ b/boards/metro_m4/examples/async_uart.rs @@ -0,0 +1,111 @@ +#![no_std] +#![no_main] + +use defmt_rtt as _; +use panic_probe as _; + +use bsp::{hal, pac, periph_alias, pin_alias}; +use hal::fugit::Hertz; +use hal::fugit::MillisDuration; +use hal::{ + clock::GenericClockController, + dmac::{Ch0, Ch1, DmaController, PriorityLevel}, + prelude::*, + sercom::{ + uart::{Config, UartFutureRxDuplexDma, UartFutureTxDuplexDma}, + Sercom3, + }, +}; +use metro_m4 as bsp; +use rtic_monotonics::Monotonic; + +rtic_monotonics::systick_monotonic!(Mono, 10000); + +atsamd_hal::bind_multiple_interrupts!(struct DmacIrqs { + DMAC: [DMAC_0, DMAC_1, DMAC_2, DMAC_OTHER] => atsamd_hal::dmac::InterruptHandler; +}); + +atsamd_hal::bind_multiple_interrupts!(struct UartIrqs { + SERCOM3: [SERCOM3_0, SERCOM3_1, SERCOM3_2, SERCOM3_3, SERCOM3_OTHER] => atsamd_hal::sercom::uart::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: embassy_executor::Spawner) { + let mut peripherals = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + + let mut clocks = GenericClockController::with_external_32kosc( + peripherals.gclk, + &mut peripherals.mclk, + &mut peripherals.osc32kctrl, + &mut peripherals.oscctrl, + &mut peripherals.nvmctrl, + ); + let freq: Hertz = clocks.gclk0().into(); + Mono::start(_core.SYST, freq.to_Hz()); + + let pins = bsp::Pins::new(peripherals.port); + + // Take rx and tx pins + let (uart_rx, uart_tx) = (pin_alias!(pins.d0), pin_alias!(pins.d1)); + 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(DmacIrqs); + // 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.mclk, + uart_rx, + uart_tx, + ) + .into_future(UartIrqs) + .with_rx_dma_channel(channel0) + .with_tx_dma_channel(channel1) + .split(); + + // For embassy-executor, spawning multiple tasks on the same executor requires + // either: + // * Tuning the task arena size either via a Cargo feature or the + // `EMBASSY_EXECUTOR_TASK_ARENA_SIZE` environment variable + // * Using the `nightly` Cargo feature along with + // #![feature(type_alias_impl_trait)] + spawner.spawn(send_bytes(uart_tx)).unwrap(); + spawner.spawn(receive_bytes(uart_rx)).unwrap(); +} + +#[embassy_executor::task] +async fn send_bytes(mut uart_tx: UartFutureTxDuplexDma, Ch1>) { + loop { + uart_tx.write(b"Hello, world!").await.unwrap(); + defmt::info!("Sent 10 bytes"); + Mono::delay(MillisDuration::::from_ticks(500).convert()).await; + } +} + +#[embassy_executor::task] +async fn receive_bytes(mut uart_rx: UartFutureRxDuplexDma, Ch0>) { + uart_rx.as_mut().flush_rx_buffer(); + + loop { + let mut buf = [0x00; 10]; + match uart_rx.read(&mut buf).await { + Ok(()) => defmt::info!("read {:#x}", &buf), + Err(_) => { + defmt::error!("UART Error."); + // Flusing the RX buffer may drop a few bytes. + uart_rx.as_mut().flush_rx_buffer(); + } + } + } +} diff --git a/boards/metro_m4/examples/spi.rs b/boards/metro_m4/examples/spi.rs index e95b6ded8cf..1830b67a4d7 100644 --- a/boards/metro_m4/examples/spi.rs +++ b/boards/metro_m4/examples/spi.rs @@ -77,7 +77,6 @@ fn main() -> ! { spi.transfer(&mut dest, &source).unwrap(); // Simultaneously read and write from the same buffer - // Cannot use DMA for this method, so it reverts to word by word transfers. spi.transfer_in_place(&mut source).unwrap(); } } diff --git a/boards/pygamer/Cargo.toml b/boards/pygamer/Cargo.toml index 6a64736e744..d3b3be29b8a 100644 --- a/boards/pygamer/Cargo.toml +++ b/boards/pygamer/Cargo.toml @@ -52,6 +52,8 @@ max-channels = ["dma", "atsamd-hal/max-channels"] panic_led = [] rt = ["cortex-m-rt", "atsamd-hal/samd51j-rt"] usb = ["atsamd-hal/usb", "usb-device"] +# Enable async support from atsamd-hal +async = ["atsamd-hal/async"] # for cargo flash [package.metadata] diff --git a/boards/samd11_bare/Cargo.toml b/boards/samd11_bare/Cargo.toml index 307d0672275..349794f7e39 100644 --- a/boards/samd11_bare/Cargo.toml +++ b/boards/samd11_bare/Cargo.toml @@ -38,6 +38,8 @@ rtt-target = { version = "0.3.0", features = ["cortex-m"] } default = ["rt", "atsamd-hal/samd11c"] dma = ["atsamd-hal/dma"] max-channels = ["dma", "atsamd-hal/max-channels"] +# Enable async support from atsamd-hal +async = ["atsamd-hal/async"] rt = ["cortex-m-rt", "atsamd-hal/samd11c-rt"] use_semihosting = [] diff --git a/crates.json b/crates.json index 82f9234a40e..ac8230519cb 100644 --- a/crates.json +++ b/crates.json @@ -8,7 +8,7 @@ }, "feather_m0": { "tier": 1, - "build": "cargo build --examples", + "build": "cargo build --examples --features dma,async", "target": "thumbv6m-none-eabi" }, "feather_m4": { @@ -178,7 +178,8 @@ "features": [ "samd11c", "dma", - "defmt" + "defmt", + "async" ], "target": "thumbv6m-none-eabi" }, @@ -186,7 +187,8 @@ "features": [ "samd11d", "dma", - "defmt" + "defmt", + "async" ], "target": "thumbv6m-none-eabi" }, @@ -195,7 +197,8 @@ "samd21g", "usb", "dma", - "defmt" + "defmt", + "async" ], "target": "thumbv6m-none-eabi" }, @@ -204,7 +207,8 @@ "samd21j", "usb", "dma", - "defmt" + "defmt", + "async" ], "target": "thumbv6m-none-eabi" }, @@ -214,7 +218,8 @@ "usb", "sdmmc", "dma", - "defmt" + "defmt", + "async" ], "target": "thumbv7em-none-eabihf" }, @@ -224,7 +229,8 @@ "usb", "sdmmc", "dma", - "defmt" + "defmt", + "async" ], "target": "thumbv7em-none-eabihf" }, @@ -234,7 +240,8 @@ "usb", "sdmmc", "dma", - "defmt" + "defmt", + "async" ], "target": "thumbv7em-none-eabihf" }, @@ -244,82 +251,83 @@ "usb", "sdmmc", "dma", - "defmt" + "defmt", + "async" ], "target": "thumbv7em-none-eabihf" } }, "hal_build_variants": { "samd11c": { - "features": [ "samd11c", "dma", "rtic", "defmt" ], + "features": [ "samd11c", "dma", "rtic", "defmt", "async" ], "target": "thumbv6m-none-eabi" }, "samd11d": { - "features": [ "samd11d", "dma", "rtic", "defmt" ], + "features": [ "samd11d", "dma", "rtic", "defmt", "async" ], "target": "thumbv6m-none-eabi" }, "samd21e": { - "features": [ "samd21e", "usb", "dma", "rtic", "defmt" ], + "features": [ "samd21e", "usb", "dma", "rtic", "defmt", "async" ], "target": "thumbv6m-none-eabi" }, "samd21el": { - "features": [ "samd21el", "dma", "rtic", "defmt" ], + "features": [ "samd21el", "dma", "rtic", "defmt", "async" ], "target": "thumbv6m-none-eabi" }, "samd21g": { - "features": [ "samd21g", "usb", "dma", "rtic", "defmt" ], + "features": [ "samd21g", "usb", "dma", "rtic", "defmt", "async" ], "target": "thumbv6m-none-eabi" }, "samd21gl": { - "features": [ "samd21gl", "dma", "rtic", "defmt" ], + "features": [ "samd21gl", "dma", "rtic", "defmt", "async" ], "target": "thumbv6m-none-eabi" }, "samd21j": { - "features": [ "samd21j", "usb", "dma", "rtic", "defmt" ], + "features": [ "samd21j", "usb", "dma", "rtic", "defmt", "async" ], "target": "thumbv6m-none-eabi" }, "samd51g": { - "features": [ "samd51g", "usb", "dma", "sdmmc", "rtic", "defmt" ], + "features": [ "samd51g", "usb", "dma", "sdmmc", "rtic", "defmt", "async" ], "target": "thumbv7em-none-eabihf" }, "samd51j": { - "features": [ "samd51j", "usb", "dma", "sdmmc", "rtic", "defmt" ], + "features": [ "samd51j", "usb", "dma", "sdmmc", "rtic", "defmt", "async" ], "target": "thumbv7em-none-eabihf" }, "samd51n": { - "features": [ "samd51n", "usb", "dma", "sdmmc", "rtic", "defmt" ], + "features": [ "samd51n", "usb", "dma", "sdmmc", "rtic", "defmt", "async" ], "target": "thumbv7em-none-eabihf" }, "samd51p": { - "features": [ "samd51p", "usb", "dma", "sdmmc", "rtic", "defmt" ], + "features": [ "samd51p", "usb", "dma", "sdmmc", "rtic", "defmt", "async" ], "target": "thumbv7em-none-eabihf" }, "same51g": { - "features": [ "same51g", "usb", "dma", "sdmmc", "rtic", "can", "defmt" ], + "features": [ "same51g", "usb", "dma", "sdmmc", "rtic", "can", "defmt", "async" ], "target": "thumbv7em-none-eabihf" }, "same51j": { - "features": [ "same51j", "usb", "dma", "sdmmc", "rtic", "can", "defmt" ], + "features": [ "same51j", "usb", "dma", "sdmmc", "rtic", "can", "defmt", "async" ], "target": "thumbv7em-none-eabihf" }, "same51n": { - "features": [ "same51n", "usb", "dma", "sdmmc", "rtic", "can", "defmt" ], + "features": [ "same51n", "usb", "dma", "sdmmc", "rtic", "can", "defmt", "async" ], "target": "thumbv7em-none-eabihf" }, "same53j": { - "features": [ "same53j", "usb", "dma", "sdmmc", "rtic", "defmt" ], + "features": [ "same53j", "usb", "dma", "sdmmc", "rtic", "defmt", "async" ], "target": "thumbv7em-none-eabihf" }, "same53n": { - "features": [ "same53n", "usb", "dma", "sdmmc", "rtic", "defmt" ], + "features": [ "same53n", "usb", "dma", "sdmmc", "rtic", "defmt", "async" ], "target": "thumbv7em-none-eabihf" }, "same54n": { - "features": [ "same54n", "usb", "dma", "sdmmc", "rtic", "can", "defmt" ], + "features": [ "same54n", "usb", "dma", "sdmmc", "rtic", "can", "defmt", "async" ], "target": "thumbv7em-none-eabihf" }, "same54p": { - "features": [ "same54p", "usb", "dma", "sdmmc", "rtic", "can", "defmt" ], + "features": [ "same54p", "usb", "dma", "sdmmc", "rtic", "can", "defmt", "async" ], "target": "thumbv7em-none-eabihf" } } diff --git a/hal/Cargo.toml b/hal/Cargo.toml index a250746ecd5..f2f1ba29549 100644 --- a/hal/Cargo.toml +++ b/hal/Cargo.toml @@ -23,7 +23,7 @@ rust-version = "1.77.2" version = "0.19.0" [package.metadata.docs.rs] -features = ["samd21g", "samd21g-rt", "usb", "dma"] +features = ["samd21g", "samd21g-rt", "usb", "dma", "async"] #=============================================================================== # Required depdendencies @@ -35,6 +35,7 @@ atsamd-hal-macros = { version = "0.2.1", path = "../atsamd-hal-macros" } bitfield = "0.13" bitflags = "2.6.0" cipher = "0.3" +critical-section = "1.2.0" cortex-m = "0.7" embedded-hal-02 = {package = "embedded-hal", version = "0.2", features = ["unproven"]} embedded-hal-1 = {package = "embedded-hal", version = "1.0.0"} @@ -43,10 +44,11 @@ embedded-io = "0.6" fugit = "0.3" heapless = "0.8" modular-bitfield = "0.11" -nb = "1.0" -num-traits = {version = "0.2.14", default-features = false} +nb = "1.1" +num-traits = {version = "0.2.19", default-features = false} opaque-debug = "0.3.0" -paste = "1.0.11" +paste = "1.0.15" +portable-atomic = {version = "1.9.0", optional = true, default-features = false, features = ["critical-section"]} rand_core = "0.6" seq-macro = "0.3" typenum = "1.12.0" @@ -57,12 +59,16 @@ void = {version = "1.0", default-features = false} # Optional depdendencies #=============================================================================== +defmt = { version = "0.3.8", optional = true} +embassy-sync = {version = "0.6.0", optional = true} +embedded-hal-async = {version = "1.0.0", optional = true} +embedded-io-async = {version = "0.6.1", optional = true} embedded-sdmmc = {version = "0.3", optional = true} +futures = {version = "0.3.31", 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} -usb-device = {version = "0.3.1", optional = true} -defmt = {version = "0.3.4", optional = true} +usb-device = {version = "0.3.2", optional = true} #=============================================================================== # PACs @@ -184,6 +190,13 @@ rtic = ["rtic-monotonic"] sdmmc = ["embedded-sdmmc"] usb = ["usb-device"] use_rtt = ["jlink_rtt"] +async = [ + "embassy-sync", + "embedded-hal-async", + "embedded-io-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 00000000000..a1d76625ed4 --- /dev/null +++ b/hal/src/async_hal/interrupts.rs @@ -0,0 +1,524 @@ +//! # Async interrupts +//! +//! This module provides APIs for working with interrupts, tailored towards +//! async peripherals. +//! +//! Asynchronous programming relies on tasks that can be paused and resumed +//! without blocking the entire program. When an async task is waiting for a +//! particular event, such as data from a peripheral, it enters a suspended +//! state. It is crucial that the task is properly woken up when the expected +//! event occurs to resume its execution. +//! +//! By having peripherals take interrupts, they can signal the occurrence of +//! relevant events, effectively waking up the associated async tasks. This +//! ensures that the async runtime can schedule and resume tasks in a timely +//! manner, providing the responsiveness required in embedded systems. +//! +//! ## Typelevel and enum-level interrupts +//! +//! There are two main ways of representing interrupts in the HAL: either by +//! using [`pac::Interrupt`], where each interrupt is represented as an enum +//! variant, or by using the typelevel interrupts defined in this module. Each +//! interrupt source *that is usable with async peripherals* is declared as a +//! struct with the same name of the corresponsing [`pac::Interrupt`] variant. +//! Therefore, two distinct traits are needed to perform basic tasks on +//! interrupt types: +//! +//! * Use [`Interrupt`] when dealing with the typelevel interrupt types defined +//! in this module; +//! * Use [`InterruptExt`] when dealing with enum-level interrupt types defined +//! in [`pac`]. +//! +//! [`pac::Interrupt`]: crate::pac::Interrupt +//! [`Interrupt`]: crate::async_hal::interrupts::Interrupt +//! [`InterruptExt`]: crate::async_hal::interrupts::InterruptExt +//! [`pac`]: crate::pac + +use crate::typelevel::Sealed; +use atsamd_hal_macros::{hal_cfg, hal_macro_helper}; +use core::{ + mem, + sync::atomic::{compiler_fence, Ordering}, +}; +use cortex_m::{interrupt::InterruptNumber, peripheral::NVIC}; +use critical_section::CriticalSection; + +/// Marker trait indicating that an interrupt source has one binding and +/// one handler. +/// +/// May not be implemented outside of this HAL. +pub trait SingleInterruptSource: Sealed {} + +/// Marker trait indicating that an interrupt source has multiple bindings and +/// handlers. +/// +/// May not be implemented outside of this HAL. +pub trait MultipleInterruptSources: Sealed {} + +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; + } + + $(#[$cfg])* + impl $crate::async_hal::interrupts::SingleInterruptSource for $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. +// Lint allowed because the macro is unused for thumbv6 devices. +#[allow(unused_macros)] +macro_rules! declare_multiple_interrupts { + ($(#[$cfg:meta])* $name:ident: [ $($irq:ident),+ $(,)? ]) => { + ::paste::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);)+ + } + } + + $(#[$cfg])* + impl $crate::async_hal::interrupts::MultipleInterruptSources for $name {} + } + }; +} + +// ---------- DMAC Interrupts ---------- // +#[cfg(feature = "dma")] +#[hal_cfg("dmac-d5x")] +declare_multiple_interrupts!(DMAC: [DMAC_0, DMAC_1, DMAC_2, DMAC_OTHER]); + +#[cfg(feature = "dma")] +#[hal_cfg(any("dmac-d11", "dmac-d21"))] +declare_interrupts!(DMAC); + +// ---------- SERCOM Interrupts ---------- // +#[hal_cfg(any("sercom0-d11", "sercom0-d21"))] +declare_interrupts!(SERCOM0); + +#[hal_cfg(any("sercom1-d11", "sercom1-d21"))] +declare_interrupts!(SERCOM1); + +#[hal_cfg(any("sercom2-d11", "sercom2-d21"))] +declare_interrupts!(SERCOM2); + +#[hal_cfg("sercom3-d21")] +declare_interrupts!(SERCOM3); + +#[hal_cfg("sercom4-d21")] +declare_interrupts!(SERCOM4); + +#[hal_cfg("sercom5-d21")] +declare_interrupts!(SERCOM5); + +#[hal_cfg("sercom0-d5x")] +declare_multiple_interrupts!(SERCOM0: [SERCOM0_0, SERCOM0_1, SERCOM0_2, SERCOM0_OTHER ]); + +#[hal_cfg("sercom1-d5x")] +declare_multiple_interrupts!(SERCOM1: [SERCOM1_0, SERCOM1_1, SERCOM1_2, SERCOM1_OTHER ]); + +#[hal_cfg("sercom2-d5x")] +declare_multiple_interrupts!(SERCOM2: [SERCOM0_2, SERCOM2_1, SERCOM2_2, SERCOM2_OTHER ]); + +#[hal_cfg("sercom3-d5x")] +declare_multiple_interrupts!(SERCOM3: [SERCOM3_0, SERCOM3_1, SERCOM3_2, SERCOM3_OTHER ]); + +#[hal_cfg("sercom4-d5x")] +declare_multiple_interrupts!(SERCOM4: [SERCOM4_0, SERCOM4_1, SERCOM4_2, SERCOM4_OTHER ]); + +#[hal_cfg("sercom5-d5x")] +declare_multiple_interrupts!(SERCOM5: [SERCOM5_0, SERCOM5_1, SERCOM5_2, SERCOM5_OTHER ]); + +#[hal_cfg("sercom6-d5x")] +declare_multiple_interrupts!(SERCOM6: [SERCOM6_0, SERCOM6_1, SERCOM6_2, SERCOM6_OTHER ]); + +#[hal_cfg("sercom7-d5x")] +declare_multiple_interrupts!(SERCOM7: [SERCOM7_0, SERCOM7_1, SERCOM7_2, SERCOM7_OTHER ]); + +// ---------- TC Interrupts ---------- // + +#[hal_cfg("tc0")] +declare_interrupts!(TC0); + +#[hal_cfg("tc1")] +declare_interrupts!(TC1); + +#[hal_cfg("tc2")] +declare_interrupts!(TC2); + +#[hal_cfg("tc3")] +declare_interrupts!(TC3); + +#[hal_cfg("tc4")] +declare_interrupts!(TC4); + +#[hal_cfg("tc5")] +declare_interrupts!(TC5); + +// ---------- EIC Interrupt ---------- // +#[hal_cfg(any("eic-d11", "eic-d21"))] +declare_interrupts!(EIC); + +#[hal_cfg("eic-d5x")] +seq_macro::seq!(N in 0..= 15 { + paste::paste! { + declare_interrupts! { + EIC_EXTINT_~N + } + + } +}); + +/// An interrupt source that may have one or many interrupt bindings. +/// +/// 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. +/// +/// May not be implemented outside of this HAL. +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 defined +/// in this module. May not be implemented outside of this HAL. +pub trait Interrupt: crate::typelevel::Sealed { + /// Interrupt enum variant. + /// + /// This allows going from typelevel interrupts (one type per interrupt, + /// defined in [`this module`](self)) to non-typelevel interrupts (a + /// single [`Interrupt`](crate::pac::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. + /// + /// Equivalent to [`set_priority`](Self::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(cs: critical_section::CriticalSection, prio: Priority) { + Self::IRQ.set_priority_with_cs(cs, prio) + } +} + +/// Interrupt handler. +/// +/// 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: Sealed { + /// 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> {} + +/// An interrupt type that can be configured by the HAL to handle +/// interrupts. +/// +/// The PAC defined enum-level interrupts implement this trait. +pub 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 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::hw2logical(NVIC::get_priority(self)) + } + + /// Set the interrupt priority. + #[inline] + #[hal_macro_helper] + 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. + #[hal_cfg(any("nvic-d11", "nvic-d21"))] + critical_section::with(|_| nvic.set_priority(self, prio.logical2hw())); + // On thumbv7+, set_priority does an atomic 8bit write, so no CS needed. + #[hal_cfg("nvic-d5x")] + nvic.set_priority(self, prio.logical2hw()); + } + } + + /// Set the interrupt priority with an already-acquired critical section. + /// + /// Equivalent to [`set_priority`](Self::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.logical2hw()); + } + } +} + +impl InterruptExt for T {} + +#[hal_cfg(any("nvic-d11", "nvic-d21"))] +const NVIC_PRIO_BITS: u8 = 2; +#[hal_cfg("nvic-d5x")] +const NVIC_PRIO_BITS: u8 = 3; + +/// Logical interrupt priority level. +/// +/// P4 is the most urgent, and P1 is the least urgent priority. +#[hal_cfg(any("nvic-d11", "nvic-d21"))] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P1 = 1, + P2 = 2, + P3 = 3, + P4 = 4, +} + +/// Logical interrupt priority level. +/// +/// P8 is the most urgent, and P1 is the least urgent priority. +#[hal_cfg("nvic-d5x")] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P1 = 1, + P2 = 2, + P3 = 3, + P4 = 4, + P5 = 5, + P6 = 6, + P7 = 7, + P8 = 8, +} + +impl Priority { + /// Convert a logical priority (where higher priority number = higher + /// priority level) to a hardware priority level (where lower priority + /// number = higher priority level). + /// + /// Taken from [`cortex-m-interrupt`] + /// + /// See LICENSE-MIT for the license. + /// + /// [`cortex-m-interrupt`]: https://github.com/datdenkikniet/cortex-m-interrupt + #[inline] + #[must_use] + pub const fn logical2hw(self) -> u8 { + ((1 << NVIC_PRIO_BITS) - self as u8) << (8 - NVIC_PRIO_BITS) + } + + /// Convert a hardware priority level (where lower priority number = higher + /// priority level) to a logical priority (where a higher priority number = + /// higher priority level). + /// + /// # Panics + /// + /// This method may only be used with allowed hardware priority levels. Ie, + /// * 0x00, + /// * 0x20, + /// * 0x40, + /// * and so on. + /// + /// Any other value will cause a panic. To save yourself some + /// trouble, use this method only with hardware priority values gotten + /// directly from the NVIC. + #[inline] + #[must_use] + pub const fn hw2logical(prio: u8) -> Self { + assert!(prio % 0x20 == 0); + unsafe { mem::transmute((1 << NVIC_PRIO_BITS) - (prio >> (8 - NVIC_PRIO_BITS))) } + } +} + +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 00000000000..7bda201ec14 --- /dev/null +++ b/hal/src/async_hal/mod.rs @@ -0,0 +1,238 @@ +//! # Asynchronous HAL APIs +//! +//! This HAL provides a comprehensive and efficient way to interact with +//! the underlying peripherals in an asynchronous fashion, enabling concurrent +//! and non-blocking programming through the use of `async`/`await` APIs. +//! +//! This module provides the basis for interacting with peripherals through +//! `async` APIs. Notably, in order to function correctly and wake an `await`ing +//! [`Future`](core::future::Future), peripherals must be able to signal when +//! their respective interrupts fire. Traditionally, the user manually writes +//! their own interrupt handlers. When using `async` APIs, the peripherals +//! effectively take control of their own interrupt handlers in order to wake +//! tasks at the appropriate time. +//! +//! ## Using the `async` APIs +//! +//! To use the asynchronous APIs provided by the HAL, enable the `async` Cargo +//! feature. Note that this uses a certain amount of static RAM in order to +//! initialize wakers for each peripheral. +//! +//! ## Supported peripherals +//! +//! * [`UART`](crate::sercom::uart) +//! * [`SPI`](crate::sercom::spi) +//! * [`I2C`](crate::sercom::i2c) +//! * [`DMAC`](crate::dmac) +//! * [`EIC`](crate::eic) (external GPIO interrupts) +//! * [`Timers`](crate::timer) +//! +//! **Note**: The asynchronous APIs for the individual peripherals are provided +//! in their respective modules. This module only deals with the generalities of +//! using `async` constructs throughout the HAL. +//! +//! ## Declaring interrupt bindings +//! +//! In order for the peripherals to wake their respective tasks, the interrupt +//! sources must be bound to their handler at compile time. A struct that +//! implements [`Binding`](self::interrupts::Binding) must be passed to an async +//! peripheral in order to prove to the compiler that the correct interrupt +//! handlers have been bound. +//! +//! This module provides convenient macros that generate the interrupt bindings. +//! Use [`bind_interrupts`](crate::bind_interrupts) to bind single interrupt +//! sources to handlers. See also [Declaring multiple interrupt source +//! bindings](#declaring-multiple-interrupt-source-bindings). +//! +//! ## Declaring multiple interrupt source bindings +//! +//! For some `thumbv7em` peripherals, there are multiple interrupt sources used +//! by a single peripheral. In these cases, we must provide a binding to an +//! interrupt handler for each of these interrupt sources in order for the +//! peripheral driver to function properly. The HAL defines only one interrupt +//! "source" per peripheral. Your job is to tell it where to find all the +//! relevant interrupts it must use to operate the peripheral properly. Use +//! [`bind_multiple_interrupts`](crate::bind_multiple_interrupts) to bind +//! multiple interrupts to a single handler. +//! +//! Currently, the supported peripherals which have multiple interrupts per +//! peripheral (**thumbv7em targets only**): +//! +//! * `SERCOMx: [SERCOMx_0, SERCOMx_1, SERCOMx_2, SERCOMx_OTHER]` +//! * `DMAC: [DMAC_0, DMAC_1, DMAC_2, DMAC_OTHER]` +//! +//! ## Complete example using the `feather_m0` BSP and the [Embassy executor](https://crates.io/crates/embassy-executor) +//! ```no_run +//! #![no_std] +//! #![no_main] +//! +//! use defmt_rtt as _; +//! use panic_probe as _; +//! +//! use bsp::hal; +//! use bsp::pac; +//! use feather_m0 as bsp; +//! use fugit::MillisDuration; +//! use hal::{ +//! clock::GenericClockController, +//! dmac::{DmaController, PriorityLevel}, +//! prelude::*, +//! sercom::Sercom4, +//! }; +//! use rtic_monotonics::systick::Systick; +//! +//! atsamd_hal::bind_interrupts!(struct Irqs { +//! SERCOM4 => atsamd_hal::sercom::spi::InterruptHandler; +//! DMAC => atsamd_hal::dmac::InterruptHandler; +//! }); +//! +//! #[embassy_executor::main] +//! async fn main(_s: embassy_executor::Spawner) { +//! let mut peripherals = pac::Peripherals::take().unwrap(); +//! let _core = pac::CorePeripherals::take().unwrap(); +//! +//! 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 mut spi = bsp::spi_master( +//! &mut clocks, +//! 100.kHz(), +//! peripherals.sercom4, +//! &mut peripherals.pm, +//! sclk, +//! mosi, +//! miso, +//! ) +//! .into_future(Irqs) +//! .with_dma_channels(channel0, channel1); +//! +//! loop { +//! defmt::info!("Sending 0x00 to SPI device..."); +//! 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; +//! } +//! } +//! ``` + +pub mod interrupts; + +/// Bind interrupt sources to a single handler each. +/// +/// This defines the right interrupt handlers, creates a unit struct (like +/// `struct Irqs;`) and implements the right +/// [`Binding`](crate::async_hal::interrupts::Binding)s for it. You can pass +/// this struct to drivers to prove at compile-time that the right interrupts +/// have been bound. +/// +/// Refer to the module-level documentation for details on when to use +/// [`bind_interrupts`](crate::bind_interrupts) vs +/// [`bind_multiple_interrupts`](crate::bind_multiple_interrupts). +/// +/// ## Example +/// ``` +/// use atsamd_hal::{dmac, sercom, bind_interrupts}; +/// +/// bind_interrupts!(struct Irqs { +/// SERCOM0 => sercom::i2c::InterruptHandler; +/// DMAC => dmac::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 + where $crate::async_hal::interrupts::$irq: $crate::async_hal::interrupts::SingleInterruptSource + {} + )* + )* + }; +} + +/// Bind multiple interrupt sources to the same interrupt handler. +/// +/// This defines the right interrupt handlers, creates a unit struct (like +/// `struct Irqs;`) and implements the right +/// [`Binding`](crate::async_hal::interrupts::Binding)s for it. You can pass +/// this struct to drivers to prove at compile-time that the right interrupts +/// have been bound. +/// +/// Due to limitations in the macro's implementation, only one binding per macro +/// call is supported. Call [`bind_multiple_interrupts`] as many times as you +/// need multiple-interrupt bindings. +/// +/// Refer to the module-level documentation for details on when to use +/// [`bind_interrupts`] vs +/// [`bind_multiple_interrupts`]. +/// +/// ## Example +/// ``` +/// atsamd_hal::bind_multiple_interrupts!(struct DmacIrqs { +/// DMAC: [DMAC_0, DMAC_1, DMAC_2, DMAC_OTHER] => atsamd_hal::dmac::InterruptHandler; +/// }); +/// +/// atsamd_hal::bind_multiple_interrupts!(struct SpiIrqs { +/// SERCOM0: [SERCOM0_0, SERCOM0_1, SERCOM0_2, SERCOM0_OTHER] => atsamd_hal::sercom::spi::InterruptHandler; +/// }); +/// ``` +/// +/// [`bind_interrupts`]: crate::bind_interrupts +/// [`bind_multiple_interrupts`]: crate::bind_multiple_interrupts +#[macro_export] +macro_rules! bind_multiple_interrupts { + ($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 + where $crate::async_hal::interrupts::$int_source: $crate::async_hal::interrupts::MultipleInterruptSources + {} + }; +} diff --git a/hal/src/dmac/async_api.rs b/hal/src/dmac/async_api.rs new file mode 100644 index 00000000000..41d99364480 --- /dev/null +++ b/hal/src/dmac/async_api.rs @@ -0,0 +1,103 @@ +//! APIs for async DMAC operations. + +use atsamd_hal_macros::hal_cfg; + +use crate::{ + async_hal::interrupts::{Handler, DMAC}, + dmac::{waker::WAKERS, TriggerSource}, + util::BitIter, +}; + +/// Interrupt handler for the DMAC peripheral. +pub struct InterruptHandler { + _private: (), +} + +impl crate::typelevel::Sealed for InterruptHandler {} + +#[hal_cfg(any("dmac-d11", "dmac-d21"))] +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)); + } + }); + } +} + +#[hal_cfg("dmac-d5x")] +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 d501de79c0a..2623e6119be 100644 --- a/hal/src/dmac/channel/mod.rs +++ b/hal/src/dmac/channel/mod.rs @@ -4,9 +4,9 @@ //! //! Individual channels should be initialized through the //! [`Channel::init`] method. This will return a `Channel` ready for -//! use by a [`Transfer`](super::transfer::Transfer). Initializing a channel -//! requires setting a priority level, as well as enabling or disabling -//! interrupt requests (only for the specific channel being initialized). +//! use by a [`Transfer`]. Initializing a channel requires setting a priority +//! level, as well as enabling or disabling interrupt requests (only for the +//! specific channel being initialized). //! //! # Burst Length and FIFO Threshold (SAMD51/SAME5x only) //! @@ -56,20 +56,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 @@ -125,8 +166,10 @@ where //============================================================================== // Channel //============================================================================== + /// DMA channel, capable of executing -/// [`Transfer`](super::transfer::Transfer)s. +/// [`Transfer`]s. Ongoing DMA transfers are automatically stopped when a +/// [`Channel`] is dropped. pub struct Channel { regs: RegisterBlock, _status: PhantomData, @@ -140,6 +183,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 @@ -150,7 +202,7 @@ impl Channel { /// A `Channel` with a `Ready` status #[inline] #[hal_macro_helper] - 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(); @@ -160,6 +212,7 @@ impl Channel { #[hal_cfg("dmac-d5x")] self.regs.chprilvl.modify(|_, w| w.prilvl().variant(lvl)); + self.change_status() } @@ -324,12 +377,15 @@ impl Channel { } } -/// 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(); self.change_status() } @@ -456,7 +512,7 @@ impl Channel { Ok(()) } - /// Begin a [`Transfer`], without changing the channel's type to [`Busy`]. + /// Begin a transfer, without changing the channel's type to [`Busy`]. /// /// Also provides support for linked transfers via an optional `&mut /// DmacDescriptor`. @@ -510,7 +566,8 @@ impl Channel { self._trigger_private(); } - /// 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 /// @@ -522,28 +579,6 @@ impl Channel { self.change_status() } - #[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 - } - /// Restart transfer using previously-configured trigger source and action #[inline] pub(crate) fn restart(&mut self) { @@ -558,15 +593,194 @@ impl From> for Channel { } } -/// Status of a transfer callback -#[derive(Clone, Copy)] -pub enum CallbackStatus { - /// Transfer Complete - TransferComplete, - /// Transfer Error - TransferError, - /// Transfer Suspended - TransferSuspended, +#[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 better ergonomics. + /// + /// This means that, as an user, you **must** ensure that the [`Future`] + /// returned by this function may never be forgotten through [`forget`] or + /// by wrapping it with a [`ManuallyDrop`]. + /// + /// The returned future implements [`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 + /// [`ManuallyDrop`]: core::mem::ManuallyDrop + /// [`Future`]: core::future::Future + /// [`poll`]: core::future::Future::poll + #[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, + { + unsafe { + self.transfer_future_linked(&mut source, &mut dest, trig_src, trig_act, None) + .await + } + } + + /// Begin an async transfer, without changing the channel's type to + /// [`Busy`]. + /// + /// Also provides support for linked transfers via an optional `&mut + /// DmacDescriptor`. + /// + /// # Safety + /// + /// * This method does not check that the two provided buffers have + /// compatible lengths. You must guarantee that: + /// - Either `source` or `dest` has a buffer length of 1, or + /// - Both buffers have the same length. + /// * You must ensure that the transfer is completed or stopped before + /// returning the [`Channel`]. Doing otherwise breaks type safety, because + /// a [`ReadyFuture`] channel would still be in the middle of a transfer. + /// * If the provided `linked_descriptor` is `Some` it must not be dropped + /// until the transfer is completed or stopped. + /// * Additionnally, this function doesn't take `'static` buffers. Again, + /// you must guarantee that the returned transfer has completed or has + /// been stopped before giving up control of the underlying [`Channel`]. + pub(crate) async unsafe fn transfer_future_linked( + &mut self, + source: &mut S, + dest: &mut D, + trig_src: TriggerSource, + trig_act: TriggerAction, + linked_descriptor: Option<&mut DmacDescriptor>, + ) -> Result<(), super::Error> + where + S: super::Buffer, + D: super::Buffer, + { + super::Transfer::>::check_buffer_pair( + source, dest, + )?; + unsafe { + self.fill_descriptor(source, dest, false); + if let Some(next) = linked_descriptor { + self.link_next(next as *mut _); + } + } + + self.disable_interrupts( + InterruptFlags::new() + .with_susp(true) + .with_tcmpl(true) + .with_terr(true), + ); + + self.configure_trigger(trig_src, trig_act); + + transfer_future::TransferFuture::new(self, trig_src).await; + + // No need to defensively disable channel here; it's automatically stopped when + // TransferFuture is dropped. Even though `stop()` is implicitly called + // through TransferFuture::drop, it *absolutely* must be called before + // this function is returned, because it emits the compiler fence which ensures + // memory operations aren't reordered beyond the DMA transfer's bounds. + + // TODO currently this will always return Ok(()) since we unconditionally clear + // ERROR + self.xfer_success() + } +} + +#[cfg(feature = "async")] +mod transfer_future { + use super::*; + + /// [`Future`](core::future::Future) which starts, then waits on a DMA + /// transfer. + /// + /// This implementation is a standalone struct instead of using + /// [`poll_fn`](core::future::poll_fn), because we want to implement + /// [`Drop`] for the future returned by the + /// [`transfer_future`](super::Channel::transfer_future) method. This way we + /// can stop transfers when they are dropped, thus avoiding undefined + /// behaviour. + pub(super) struct TransferFuture<'a, Id: ChId> { + triggered: bool, + trig_src: TriggerSource, + chan: &'a mut Channel, + } + + impl<'a, Id: ChId> TransferFuture<'a, Id> { + pub(super) fn new(chan: &'a mut Channel, trig_src: TriggerSource) -> Self { + Self { + triggered: false, + trig_src, + chan, + } + } + } + + impl Drop for TransferFuture<'_, Id> { + fn drop(&mut self) { + self.chan.stop(); + } + } + + impl core::future::Future for TransferFuture<'_, Id> { + type Output = (); + + fn poll( + mut self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + use crate::dmac::waker::WAKERS; + use core::task::Poll; + + self.chan._enable_private(); + + if !self.triggered && self.trig_src == TriggerSource::Disable { + self.triggered = true; + self.chan._trigger_private(); + } + + let flags_to_check = InterruptFlags::new().with_tcmpl(true).with_terr(true); + + if self.chan.check_and_clear_interrupts(flags_to_check).tcmpl() { + return Poll::Ready(()); + } + + WAKERS[Id::USIZE].register(cx.waker()); + self.chan.enable_interrupts(flags_to_check); + + if self.chan.check_and_clear_interrupts(flags_to_check).tcmpl() { + self.chan.disable_interrupts(flags_to_check); + + return Poll::Ready(()); + } + + Poll::Pending + } + } } /// Interrupt sources available to a DMA channel diff --git a/hal/src/dmac/dma_controller.rs b/hal/src/dmac/dma_controller.rs index a182357c4b1..0db41764379 100644 --- a/hal/src/dmac/dma_controller.rs +++ b/hal/src/dmac/dma_controller.rs @@ -16,11 +16,12 @@ //! //! # 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. #![allow(unused_braces)] use atsamd_hal_macros::{hal_cfg, hal_macro_helper}; +use core::marker::PhantomData; use modular_bitfield::prelude::*; use seq_macro::seq; @@ -40,10 +41,13 @@ pub use crate::pac::dmac::channel::{ }; use super::{ - channel::{new_chan, Channel, Uninitialized}, + channel::{Channel, Uninitialized}, sram, }; -use crate::pac::{Dmac, Pm}; +use crate::{ + pac::{Dmac, Pm}, + typelevel::NoneT, +}; /// Trait representing a DMA channel ID pub trait ChId { @@ -51,42 +55,6 @@ pub trait ChId { const USIZE: usize; } -macro_rules! define_channels_struct { - ($num_channels:literal) => { - seq!(N in 0..$num_channels { - #( - /// Type alias for a channel number - pub struct Ch~N; - - impl ChId for Ch~N { - const U8: u8 = N; - const USIZE: usize = N; - } - )* - - /// Struct generating individual handles to each DMA channel - pub struct Channels( - #( - pub Channel, - )* - ); - }); - }; -} - -with_num_channels!(define_channels_struct); - -/// Initialized DMA Controller -pub struct DmaController { - dmac: Dmac, -} - -impl Default for PriorityLevelMask { - fn default() -> Self { - Self::new() - } -} - /// Mask representing which priority levels should be enabled/disabled #[bitfield] #[repr(u16)] @@ -105,7 +73,7 @@ pub struct PriorityLevelMask { _reserved: B4, } -impl Default for RoundRobinMask { +impl Default for PriorityLevelMask { fn default() -> Self { Self::new() } @@ -133,6 +101,60 @@ pub struct RoundRobinMask { pub level3: bool, } +impl Default for RoundRobinMask { + fn default() -> Self { + Self::new() + } +} + +macro_rules! define_channels_struct { + ($num_channels:literal) => { + seq!(N in 0..$num_channels { + #( + /// Type alias for a channel number + pub struct Ch~N; + + impl ChId for Ch~N { + const U8: u8 = N; + const USIZE: usize = N; + } + )* + + /// Struct generating individual handles to each DMA channel + pub struct Channels( + #( + pub Channel, + )* + ); + }); + }; +} + +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 { + dmac: Dmac, + _irqs: PhantomData, +} + impl DmaController { /// Initialize the DMAC and return a DmaController object useable by /// [`Transfer`](super::transfer::Transfer)'s. By default, all @@ -175,9 +197,38 @@ impl DmaController { // Enable DMA controller dmac.ctrl().modify(|_, w| w.dmaenable().set_bit()); - Self { dmac } + Self { + dmac, + _irqs: PhantomData, + } } + /// 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] + #[hal_macro_helper] + pub fn free(mut self, _channels: Channels, _pm: &mut Pm) -> Dmac { + self.dmac.ctrl().modify(|_, w| w.dmaenable().clear_bit()); + + Self::swreset(&mut self.dmac); + + #[hal_cfg(any("dmac-d11", "dmac-d21"))] + { + // 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 + } +} + +impl DmaController { /// Enable multiple priority levels simultaneously #[inline] pub fn enable_levels(&mut self, mask: PriorityLevelMask) { @@ -228,6 +279,51 @@ 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() {} + } +} + +#[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 @@ -236,7 +332,7 @@ impl DmaController { /// into [`free`](DmaController::free). #[inline] #[hal_macro_helper] - pub fn free(mut self, _channels: Channels, _pm: &mut Pm) -> Dmac { + 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); @@ -251,13 +347,6 @@ impl DmaController { // Release the DMAC self.dmac } - - /// 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() {} - } } macro_rules! define_split { @@ -268,7 +357,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), )* ) } @@ -279,3 +368,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 6d0a70b156e..e5c4649d675 100644 --- a/hal/src/dmac/mod.rs +++ b/hal/src/dmac/mod.rs @@ -115,7 +115,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 //! ``` @@ -513,3 +513,18 @@ pub(crate) mod sram { 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 f8e4fdf1dbd..503d10f8ffd 100644 --- a/hal/src/dmac/transfer.rs +++ b/hal/src/dmac/transfer.rs @@ -83,9 +83,9 @@ //! stopped. use super::{ - channel::{AnyChannel, Busy, CallbackStatus, Channel, ChannelId, InterruptFlags, Ready}, + channel::{AnyChannel, Busy, Channel, ChannelId, InterruptFlags, Ready}, dma_controller::{TriggerAction, TriggerSource}, - Error, Result, + Error, ReadyChannel, Result, }; use crate::typelevel::{Is, Sealed}; use modular_bitfield::prelude::*; @@ -107,7 +107,7 @@ pub enum BeatSize { } /// Convert 8, 16 and 32 bit types -/// into [`BeatSize`](BeatSize) +/// into [`BeatSize`] /// /// # Safety /// @@ -301,22 +301,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`. @@ -327,9 +327,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( @@ -346,7 +347,7 @@ where } } -impl Transfer, W> +impl Transfer> where S: Buffer, D: Buffer, @@ -365,11 +366,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. /// @@ -403,7 +405,6 @@ where Transfer { buffers, chan, - waker: None, complete: false, } } @@ -415,38 +416,16 @@ 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. @@ -457,16 +436,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 @@ -485,7 +477,7 @@ where } } -impl Transfer, W> +impl Transfer> where S: Buffer, D: Buffer, @@ -580,9 +572,7 @@ where }; let old_buffers = core::mem::replace(&mut self.buffers, new_buffers); - self.chan.as_mut().restart(); - Ok((old_buffers.source, old_buffers.destination)) } @@ -607,9 +597,7 @@ where } let old_destination = core::mem::replace(&mut self.buffers.destination, destination); - self.chan.as_mut().restart(); - Ok(old_destination) } @@ -634,9 +622,7 @@ where } let old_source = core::mem::replace(&mut self.buffers.source, source); - self.chan.as_mut().restart(); - Ok(old_source) } @@ -650,26 +636,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 b96934e528a..91ad7ca0af7 100644 --- a/hal/src/lib.rs +++ b/hal/src/lib.rs @@ -7,7 +7,15 @@ pub use embedded_io; pub use fugit; pub use nb; pub use paste; + +#[cfg(feature = "async")] +pub use embedded_hal_async as ehal_async; + +#[cfg(feature = "async")] +pub use embedded_io_async; + pub mod typelevel; +mod util; macro_rules! define_pac { ( $( ($pac:ident, $feat:literal)),+ ) => { @@ -60,6 +68,9 @@ macro_rules! dbgprint { ($($arg:tt)*) => {{}}; } +#[cfg(feature = "async")] +pub mod async_hal; + #[cfg(feature = "device")] pub mod delay; #[cfg(feature = "device")] diff --git a/hal/src/peripherals/eic/d11/mod.rs b/hal/src/peripherals/eic/d11/mod.rs index aa4c744bb4e..ffc69bf9281 100644 --- a/hal/src/peripherals/eic/d11/mod.rs +++ b/hal/src/peripherals/eic/d11/mod.rs @@ -18,3 +18,52 @@ 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 crate::typelevel::Sealed for InterruptHandler {} + + 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/peripherals/eic/d11/pin.rs b/hal/src/peripherals/eic/d11/pin.rs index a32bf0a357a..966f688c7a5 100644 --- a/hal/src/peripherals/eic/d11/pin.rs +++ b/hal/src/peripherals/eic/d11/pin.rs @@ -157,9 +157,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_1::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(()) } } @@ -217,6 +293,9 @@ crate::paste::item! { // SAMD11 +#[hal_cfg("eic-d11")] +pub const NUM_CHANNELS: usize = 8; + #[hal_cfg("eic-d11")] mod impls { use super::*; @@ -263,6 +342,9 @@ mod impls { // SAMD21 +#[hal_cfg("eic-d21")] +pub const NUM_CHANNELS: usize = 16; + #[hal_cfg("eic-d21")] mod impls { use super::*; diff --git a/hal/src/peripherals/eic/d5x/mod.rs b/hal/src/peripherals/eic/d5x/mod.rs index 8ee0bc11fd7..efe11e4ecae 100644 --- a/hal/src/peripherals/eic/d5x/mod.rs +++ b/hal/src/peripherals/eic/d5x/mod.rs @@ -96,3 +96,45 @@ impl From for 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: (), + } + + impl crate::typelevel::Sealed for InterruptHandler {} + + 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/peripherals/eic/d5x/pin.rs b/hal/src/peripherals/eic/d5x/pin.rs index 305064de372..220aa8195af 100644 --- a/hal/src/peripherals/eic/d5x/pin.rs +++ b/hal/src/peripherals/eic/d5x/pin.rs @@ -178,6 +178,60 @@ crate::paste::item! { e.debouncen().modify(|_, w| unsafe { w.bits($num) }); }); } + + /// 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, + } + } + } + + #[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(); + + 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; + } } impl InputPin for [<$PadType $num>] @@ -196,6 +250,51 @@ crate::paste::item! { } } + #[cfg(feature = "async")] + impl embedded_hal_1::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 { @@ -229,6 +328,8 @@ crate::paste::item! { }; } +pub const NUM_CHANNELS: usize = 16; + ei!(ExtInt[0] { #[hal_cfg("pa00")] PA00, diff --git a/hal/src/peripherals/timer/async_api.rs b/hal/src/peripherals/timer/async_api.rs new file mode 100644 index 00000000000..c94bee9ac8a --- /dev/null +++ b/hal/src/peripherals/timer/async_api.rs @@ -0,0 +1,259 @@ +//! Async APIs for timers. +//! +//! Use [`TimerCounter::into_future`] to convert a regular [`TimerCounter`] into +//! an asynchronous [`TimerFuture`]. + +use crate::{ + async_hal::interrupts::{Binding, Handler, Interrupt}, + pac, + timer_traits::InterruptDrivenTimer, + typelevel::Sealed, +}; +use atsamd_hal_macros::hal_cfg; +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; + +use crate::peripherals::timer; + +#[hal_cfg("tc1")] +#[allow(unused_imports)] +use crate::pac::Tc1; + +#[hal_cfg("tc2")] +#[allow(unused_imports)] +use crate::pac::Tc2; + +#[hal_cfg("tc3")] +#[allow(unused_imports)] +use crate::pac::Tc3; + +#[hal_cfg("tc4")] +#[allow(unused_imports)] +use crate::pac::Tc4; + +#[hal_cfg("tc5")] +#[allow(unused_imports)] +use crate::pac::Tc5; + +use timer::{Count16, TimerCounter}; + +#[hal_cfg("tc1-d11")] +type RegBlock = pac::tc1::RegisterBlock; + +#[hal_cfg("tc3-d21")] +type RegBlock = pac::tc3::RegisterBlock; + +#[hal_cfg("tc1-d5x")] +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; + + /// Get a reference to the timer's register block + fn reg_block(peripherals: &pac::Peripherals) -> &RegBlock; + + /// Interrupt type for this timer + type Interrupt: Interrupt; +} + +/// Interrupt handler for async timer operarions +pub struct InterruptHandler { + _private: (), + _tc: PhantomData, +} + +impl crate::typelevel::Sealed for InterruptHandler {} + +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) => { + paste::paste! { + impl AsyncCount16 for $TC { + const STATE_ID: usize = $id; + + type Interrupt = crate::async_hal::interrupts::[< $TC:upper >]; + + fn reg_block(peripherals: &pac::Peripherals) -> &RegBlock { + &*peripherals.[< $TC:lower >] + } + } + + impl Sealed for $TC {} + } + }; +} + +#[hal_cfg("tc1-d11")] +impl_async_count16!(Tc1, 0); + +#[hal_cfg("tc3-d21")] +impl_async_count16!(Tc3, 0); + +#[hal_cfg("tc4-d21")] +impl_async_count16!(Tc4, 1); + +#[hal_cfg("tc5-d21")] +impl_async_count16!(Tc5, 2); + +#[hal_cfg("tc2-d5x")] +impl_async_count16!(Tc2, 0); + +#[hal_cfg("tc3-d5x")] +impl_async_count16!(Tc3, 1); + +#[hal_cfg("tc4-d5x")] +impl_async_count16!(Tc4, 2); + +#[hal_cfg("tc5-d5x")] +impl_async_count16!(Tc5, 3); + +// Reserve space for the max number of timer peripherals based on chip type, +// even though some wakers may not be used on some chips if they actually don't +// exist on variant's hardware +#[hal_cfg("tc1-d11")] +const NUM_TIMERS: usize = 1; + +#[hal_cfg("tc3-d21")] +const NUM_TIMERS: usize = 3; + +#[hal_cfg("tc3-d5x")] +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/peripherals/timer/d11.rs b/hal/src/peripherals/timer/d11.rs index 1d2fcac4d7c..00b6ff98584 100644 --- a/hal/src/peripherals/timer/d11.rs +++ b/hal/src/peripherals/timer/d11.rs @@ -16,6 +16,12 @@ use crate::clock; use crate::time::{Hertz, Nanoseconds}; use crate::timer_traits::InterruptDrivenTimer; +#[cfg(feature = "async")] +mod async_api; + +#[cfg(feature = "async")] +pub use async_api::*; + // Note: // TC3 + TC4 can be paired to make a 32-bit counter // TC5 + TC6 can be paired to make a 32-bit counter diff --git a/hal/src/peripherals/timer/d5x.rs b/hal/src/peripherals/timer/d5x.rs index 2814cf368b6..2cd9a2ea43d 100644 --- a/hal/src/peripherals/timer/d5x.rs +++ b/hal/src/peripherals/timer/d5x.rs @@ -15,6 +15,12 @@ use crate::timer_traits::InterruptDrivenTimer; use crate::clock; use crate::time::{Hertz, Nanoseconds}; +#[cfg(feature = "async")] +mod async_api; + +#[cfg(feature = "async")] +pub use async_api::*; + // Note: // TC3 + TC4 can be paired to make a 32-bit counter // TC5 + TC6 can be paired to make a 32-bit counter diff --git a/hal/src/peripherals/usb/d11/bus.rs b/hal/src/peripherals/usb/d11/bus.rs index 5a41f9a138a..45315e94bbd 100644 --- a/hal/src/peripherals/usb/d11/bus.rs +++ b/hal/src/peripherals/usb/d11/bus.rs @@ -16,8 +16,8 @@ use atsamd_hal_macros::{hal_cfg, hal_macro_helper}; 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/peripherals/usb/d5x/bus.rs b/hal/src/peripherals/usb/d5x/bus.rs index 44e29a6cad9..1441ff780dc 100644 --- a/hal/src/peripherals/usb/d5x/bus.rs +++ b/hal/src/peripherals/usb/d5x/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/sercom/dma.rs b/hal/src/sercom/dma.rs index 7d03160bf71..1072508f90d 100644 --- a/hal/src/sercom/dma.rs +++ b/hal/src/sercom/dma.rs @@ -10,7 +10,7 @@ use atsamd_hal_macros::hal_macro_helper; use crate::{ dmac::{ self, - channel::{AnyChannel, Busy, CallbackStatus, Channel, InterruptFlags, Ready}, + channel::{AnyChannel, Busy, Channel, InterruptFlags, Ready}, sram::DmacDescriptor, transfer::BufferPair, Beat, Buffer, Transfer, TriggerAction, @@ -81,6 +81,42 @@ unsafe impl Buffer for SharedSliceBuffer<'_, T> { } } +/// Sink/source buffer to use for unidirectional SPI-DMA transfers. +/// +/// When reading/writing from a [`Duplex`] [`Spi`] 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. +pub(super) struct SinkSourceBuffer<'a, T: Beat> { + word: &'a mut T, + length: usize, +} + +impl<'a, T: Beat> SinkSourceBuffer<'a, T> { + pub(super) fn new(word: &'a mut T, length: usize) -> Self { + Self { word, length } + } +} +unsafe impl Buffer for SinkSourceBuffer<'_, T> { + type Beat = T; + #[inline] + fn incrementing(&self) -> bool { + false + } + + #[inline] + fn buffer_len(&self) -> usize { + self.length + } + + #[inline] + fn dma_ptr(&mut self) -> *mut Self::Beat { + self.word as *mut _ + } +} /// 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 SERCOM-DMA transfers work. @@ -111,7 +147,7 @@ unsafe impl Buffer for SercomPtr { // I2C DMA transfers //============================================================================= -/// Token type representing an [`I2c`](super::i2c::I2c) for which the bus is +/// Token type representing an [`I2c`] for which the bus is /// ready to start a transaction. /// /// For use with [`send_with_dma`](super::i2c::I2c::send_with_dma) and @@ -153,7 +189,7 @@ impl I2c { /// # fn init_transfer>(i2c: I2c, chan0: C, buf_src: &'static mut [u8]){ /// // Assume `i2c` is a fully configured `I2c`, and `chan0` a fully configured `dmac::Channel`. /// let token = i2c.init_dma_transfer()?; - /// i2c.send_with_dma(ADDRESS, token, buf_src, chan0, |_| {}); + /// i2c.send_with_dma(ADDRESS, token, buf_src, chan0); /// # } /// ``` /// @@ -182,18 +218,16 @@ impl I2c { )] #[allow(deprecated)] #[hal_macro_helper] - 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); @@ -211,9 +245,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 @@ -236,18 +268,16 @@ impl I2c { note = "Use `I2c::with_dma_chahnnel` instead. You will have access to DMA-enabled `embedded-hal` implementations." )] #[allow(deprecated)] - 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); @@ -265,9 +295,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 @@ -324,16 +352,14 @@ where /// use[`Uart::with_rx_channel`](Self::with_tx_channel) instead. #[inline] #[hal_macro_helper] - 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() @@ -348,8 +374,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) } } @@ -369,16 +394,14 @@ where /// use[`Uart::with_tx_channel`](Self::with_tx_channel) instead. #[inline] #[hal_macro_helper] - 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() @@ -393,8 +416,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) } } @@ -442,16 +464,14 @@ where since = "0.19.0", note = "Use `Spi::with_dma_channels` instead. You will have access to DMA-enabled `embedded-hal` implementations." )] - 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() @@ -466,8 +486,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) } } @@ -485,16 +504,14 @@ where since = "0.19.0", note = "Use `Spi::with_dma_channels` instead. You will have access to DMA-enabled `embedded-hal` implementations." )] - 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() @@ -509,8 +526,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, self, buf, false) }; - xfer.with_waker(waker) - .begin(C::Sercom::DMA_RX_TRIGGER, trigger_action) + xfer.begin(C::Sercom::DMA_RX_TRIGGER, trigger_action) } } @@ -621,3 +637,127 @@ pub(super) unsafe fn write_dma_linked( next, ); } + +/// 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. +#[cfg(feature = "async")] +pub(crate) mod async_dma { + use dmac::{Error, ReadyFuture}; + + use super::*; + + /// Perform a SERCOM DMA read with a provided `&mut [T]` + #[inline] + pub(in super::super) async fn read_dma( + channel: &mut impl AnyChannel, + sercom_ptr: SercomPtr, + buf: &mut B, + ) -> Result<(), Error> + where + B: Buffer, + T: Beat, + S: Sercom, + { + unsafe { read_dma_linked::<_, _, S>(channel, sercom_ptr, buf, None).await } + } + + /// Perform a SERCOM DMA read with a provided [`Buffer`], and add an + /// optional link to a next [`DmacDescriptor`] to support linked + /// transfers. + /// + /// # Safety + /// + /// You **must** guarantee that the DMA transfer is either stopped or + /// completed before giving back control of `channel` AND `buf`. + #[inline] + #[hal_macro_helper] + pub(in super::super) async unsafe fn read_dma_linked( + channel: &mut impl AnyChannel, + mut sercom_ptr: SercomPtr, + buf: &mut B, + next: Option<&mut DmacDescriptor>, + ) -> Result<(), Error> + where + T: Beat, + B: Buffer, + S: Sercom, + { + #[hal_cfg("dmac-d5x")] + let trigger_action = TriggerAction::Burst; + + #[hal_cfg(any("dmac-d11", "dmac-d21"))] + let trigger_action = TriggerAction::Beat; + + // Safety: It is safe to bypass the buffer length check because `SercomPtr` + // always has a buffer length of 1. + channel + .as_mut() + .transfer_future_linked( + &mut sercom_ptr, + buf, + S::DMA_RX_TRIGGER, + trigger_action, + next, + ) + .await + } + + /// Perform a SERCOM DMA write with a provided `&[T]` + #[inline] + pub(in super::super) async fn write_dma( + channel: &mut impl AnyChannel, + sercom_ptr: SercomPtr, + buf: &mut B, + ) -> Result<(), Error> + where + B: Buffer, + T: Beat, + S: Sercom, + { + unsafe { write_dma_linked::<_, _, S>(channel, sercom_ptr, buf, None).await } + } + + /// Perform a SERCOM DMA write with a provided [`Buffer`], and add an + /// optional link to a next [`DmacDescriptor`] to support linked + /// transfers. + /// + /// # Safety + /// + /// You **must** guarantee that the DMA transfer is either stopped or + /// completed before giving back control of `channel` AND `buf`. + #[inline] + #[hal_macro_helper] + pub(in super::super) async unsafe fn write_dma_linked( + channel: &mut impl AnyChannel, + mut sercom_ptr: SercomPtr, + buf: &mut B, + next: Option<&mut DmacDescriptor>, + ) -> Result<(), Error> + where + B: Buffer, + T: Beat, + S: Sercom, + { + #[hal_cfg("dmac-d5x")] + let trigger_action = TriggerAction::Burst; + + #[hal_cfg(any("dmac-d11", "dmac-d21"))] + let trigger_action = TriggerAction::Beat; + + // Safety: It is safe to bypass the buffer length check because `SercomPtr` + // always has a buffer length of 1. + channel + .as_mut() + .transfer_future_linked( + buf, + &mut sercom_ptr, + S::DMA_TX_TRIGGER, + trigger_action, + next, + ) + .await + } +} diff --git a/hal/src/sercom/i2c.rs b/hal/src/sercom/i2c.rs index c33878abe05..f29c459730a 100644 --- a/hal/src/sercom/i2c.rs +++ b/hal/src/sercom/i2c.rs @@ -181,9 +181,9 @@ //! * High-speed mode is not supported. //! * 4-wire mode is not supported. //! * 32-bit extension mode is not supported (SAMx5x). If you need to transfer -//! slices, consider using the DMA methods instead . The dma Cargo feature must be enabled. +//! only">dma. //! //! # Using I2C with DMA dma //! @@ -199,10 +199,10 @@ //! use atsamd_hal::dmac::channel::{AnyChannel, Ready}; //! use atsand_hal::sercom::i2c::{I2c, AnyConfig, Error}; //! use atsamd_hal::embedded_hal::i2c::I2c; -//! fn i2c_send_with_dma>(i2c: I2c, channel: C, bytes: &[u8]) -> Result<(), Error>{ +//! fn i2c_write_with_dma>(i2c: I2c, channel: C, bytes: &[u8]) -> Result<(), Error>{ //! // Attach a DMA channel //! let i2c = i2c.with_dma_channel(channel); -//! i2c.send(0x54, bytes)?; +//! i2c.write(0x54, bytes)?; //! } //! ``` //! @@ -215,6 +215,13 @@ //! across all adjacent operations must not exceed 256. If you need continuous //! transfers of 256 bytes or more, use the non-DMA [`I2c`] implementations. //! +//! * When using [`I2c::transaction`] or [`I2c::write_read`], the +//! [`embedded_hal::i2c::I2c`] specification mandates that a REPEATED START +//! (instead of a STOP+START) is sent between transactions of a different type +//! (read/write). Unfortunately, in DMA mode, the hardware is only capable of +//! sending STOP+START. If you absolutely need repeated starts, the only +//! workaround is to use the I2C without DMA. +//! //! * Using [`I2c::transaction`] consumes significantly more memory than the //! other methods provided by [`embedded_hal::i2c::I2c`] (at least 256 bytes //! extra). @@ -224,6 +231,121 @@ //! need more than 17 adjacent operations of the same type, the transfer will //! reverted to using the byte-by-byte (non-DMA) implementation. //! +//! All these limitations also apply to I2C transfers in async mode when using +//! DMA. They do not apply to I2C transfers in async mode when not using DMA. +//! +//! # `async` operation async +//! +//! An [`I2c`] can be used for +//! `async` operations. Configuring an [`I2c`] in async mode is relatively +//! simple: +//! +//! * Bind the corresponding `SERCOM` interrupt source to the SPI +//! [`InterruptHandler`] (refer to the module-level [`async_hal`] +//! documentation for more information). +//! * Turn a previously configured [`I2c`] into an [`I2cFuture`] by calling +//! [`I2c::into_future`] +//! * Optionally, add a DMA channel by using [`I2cFuture::with_dma_channel`]. +//! The API is exactly the same whether a DMA channel is used or not. +//! * Use the provided async methods for reading or writing to the I2C +//! peripheral. [`I2cFuture`] implements [`embedded_hal_async::i2c::I2c`]. +//! +//! `I2cFuture` implements `AsRef` and `AsMut` so +//! that it can be reconfigured using the regular [`I2c`] methods. +//! +//! ## Considerations when using `async` [`I2c`] with DMA async dma +//! +//! * An [`I2c`] struct must be turned into an [`I2cFuture`] by calling +//! [`I2c::into_future`] before calling `with_dma_channel`. The DMA channel +//! itself must also be configured in async mode by using +//! [`DmaController::into_future`](crate::dmac::DmaController::into_future). +//! If a DMA channel is added to the [`I2c`] struct before it is turned into +//! an [`I2cFuture`], it will not be able to use DMA in async mode. +//! +//! ``` +//! // This will work +//! let i2c = i2c.into_future().with_dma_channel(channel); +//! +//! // This won't +//! let i2c = i2c.with_dma_channel(channel).into_future(); +//! ``` +//! +//! ### Safety considerations +//! +//! In `async` mode, an I2C+DMA transfer does not require `'static` source and +//! destination buffers. This, in theory, makes its use `unsafe`. However it is +//! marked as safe for better ergonomics, and to enable the implementation of +//! the [`embedded_hal_async::i2c::I2c`] trait. +//! +//! This means that, as an user, you **must** ensure that the [`Future`]s +//! returned by the [`embedded_hal_async::i2c::I2c`] methods may never be +//! forgotten through [`forget`] or by wrapping them with a [`ManuallyDrop`]. +//! +//! The returned futures implement [`Drop`] and will automatically stop any +//! ongoing transfers; this guarantees that the memory occupied by the +//! now-dropped buffers may not be corrupted by running transfers. +//! +//! This means that using functions like [`futures::select_biased`] to implement +//! timeouts is safe; transfers will be safely cancelled if the timeout expires. +//! +//! 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. +//! * Dropping an incomplete transfer *without running its destructor* is +//! **unsound** and will trigger undefined behavior. +//! +//! ```ignore +//! async fn always_ready() {} +//! +//! let mut buffer = [0x00; 10]; +//! +//! // This is completely safe +//! i2c.read(&mut buffer).await?; +//! +//! // This is also safe: we launch a transfer, which is then immediately cancelled +//! futures::select_biased! { +//! _ = i2c.read(&mut buffer)?, +//! _ = always_ready(), +//! } +//! +//! // This, while contrived, is also safe. +//! { +//! use core::future::Future; +//! +//! let future = i2c.read(&mut buffer); +//! futures::pin_mut!(future); +//! // Assume ctx is a `core::task::Context` given out by the executor. +//! // The future is polled, therefore starting the transfer +//! future.as_mut().poll(ctx); +//! +//! // Future is dropped here - transfer is cancelled. +//! } +//! +//! // DANGER: This is an example of undefined behavior +//! { +//! use core::future::Future; +//! use core::ops::DerefMut; +//! +//! let future = core::mem::ManuallyDrop::new(i2c.read(&mut buffer)); +//! futures::pin_mut!(future); +//! // To actually make this example compile, we would need to wrap the returned +//! // future from `i2c.read()` in a newtype that implements Future, because we +//! // can't actually call as_mut() without being able to name the type we want +//! // to deref to. +//! let future_ref: &mut SomeNewTypeFuture = &mut future.as_mut(); +//! future.as_mut().poll(ctx); +//! +//! // Future is NOT dropped here - transfer is not cancelled, resulting un UB. +//! } +//! ``` +//! +//! As you can see, unsoundness is relatively hard to come by - however, caution +//! should still be exercised. +//! //! [`enable`]: Config::enable //! [`disable`]: I2c::disable //! [`reconfigure`]: I2c::reconfigure @@ -239,6 +361,15 @@ //! [`PinMode`]: crate::gpio::pin::PinMode //! [`embedded_hal::i2c::I2c`]: crate::ehal::i2c::I2c //! [`I2c::transaction`]: crate::ehal::i2c::I2c::transaction +//! [`I2c::write_read`]: crate::ehal::i2c::I2c::write_read +//! [`i2c::Write`]: embedded_hal::blocking::i2c::Write +//! [`i2c::Read`]: embedded_hal::blocking::i2c::Read +//! [`i2c::WriteRead`]: embedded_hal::blocking::i2c::WriteRead +//! [`async_hal`]: crate::async_hal +//! [`forget`]: core::mem::forget +//! [`ManuallyDrop`]: core::mem::ManuallyDrop +//! [`Future`]: core::future::Future +//! [`poll`]: core::future::Future::poll use atsamd_hal_macros::hal_module; @@ -261,6 +392,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; @@ -436,8 +573,14 @@ impl I2c { } #[cfg(feature = "dma")] -impl> I2c { - /// Reclaim the DMA channels +impl I2c +where + C: AnyConfig, + D: crate::dmac::AnyChannel, + S: crate::dmac::ReadyChannel, +{ + /// Reclaim the DMA channel. Any subsequent I2C operations will no longer + /// use DMA. pub fn take_dma_channel(self) -> (I2c, D) { ( I2c { diff --git a/hal/src/sercom/i2c/async_api.rs b/hal/src/sercom/i2c/async_api.rs new file mode 100644 index 00000000000..56b3c80e809 --- /dev/null +++ b/hal/src/sercom/i2c/async_api.rs @@ -0,0 +1,546 @@ +use crate::{ + async_hal::interrupts::{Binding, Handler, InterruptSource}, + sercom::{ + i2c::{self, impl_ehal::chunk_operations, AnyConfig, Flags, I2c}, + Sercom, + }, + typelevel::NoneT, +}; +use core::{marker::PhantomData, task::Poll}; + +use embedded_hal_async::i2c::{ErrorType, I2c as I2cTrait, Operation}; + +/// Interrupt handler for async I2C operarions +pub struct InterruptHandler { + _private: (), + _sercom: PhantomData, +} + +impl crate::typelevel::Sealed for InterruptHandler {} + +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.intersects(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 } + } +} + +/// `async` version of [`I2c`]. +/// +/// Create this struct by calling [`I2c::into_future`](I2c::into_future). +pub struct I2cFuture +where + C: AnyConfig, +{ + i2c: I2c, +} + +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, +{ + /// Return the underlying [`I2c`]. + pub fn free(self) -> I2c { + self.i2c + } + + /// Use a DMA channel for reads/writes + #[cfg(feature = "dma")] + pub fn with_dma_channel>( + self, + dma_channel: D, + ) -> I2cFuture { + I2cFuture { + i2c: I2c { + config: self.i2c.config, + _dma_channel: dma_channel, + }, + } + } + + async fn write_one(&mut self, byte: u8) -> Result<(), i2c::Error> { + self.wait_flags(Flags::MB | Flags::ERROR).await; + self.i2c.read_status().check_bus_error()?; + self.i2c.config.as_mut().registers.write_one(byte); + Ok(()) + } + + async fn read_one(&mut self) -> Result { + self.wait_flags(Flags::SB | Flags::ERROR).await; + self.i2c.read_status().check_bus_error()?; + Ok(self.i2c.config.as_mut().registers.read_one()) + } +} + +impl AsRef> for I2cFuture { + #[inline] + fn as_ref(&self) -> &I2c { + &self.i2c + } +} + +impl AsMut> for I2cFuture { + #[inline] + fn as_mut(&mut self) -> &mut I2c { + &mut self.i2c + } +} + +impl ErrorType for I2cFuture +where + C: AnyConfig, +{ + type Error = i2c::Error; +} + +impl I2cFuture { + #[inline] + async fn do_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?; + + for byte in iter { + // Ack the last byte so we can receive another one + self.i2c.config.as_mut().registers.cmd_read(); + *byte = self.read_one().await?; + } + + Ok(()) + } + + #[inline] + async fn continue_read(&mut self, buffer: &mut [u8]) -> Result<(), i2c::Error> { + for byte in buffer.iter_mut() { + self.i2c.config.as_mut().registers.cmd_read(); + *byte = self.read_one().await?; + } + Ok(()) + } + + #[inline] + async fn do_write(&mut self, address: u8, buffer: &[u8]) -> Result<(), i2c::Error> { + self.i2c.config.as_mut().registers.start_write(address)?; + + for byte in buffer { + self.write_one(*byte).await?; + } + Ok(()) + } + + #[inline] + async fn continue_write(&mut self, buffer: &[u8]) -> Result<(), i2c::Error> { + for byte in buffer { + self.write_one(*byte).await?; + } + Ok(()) + } +} + +impl I2cTrait for I2cFuture { + #[inline] + async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { + self.do_read(address, buffer).await?; + self.i2c.config.as_mut().registers.cmd_stop(); + Ok(()) + } + + #[inline] + async fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { + self.do_write(address, bytes).await?; + self.i2c.cmd_stop(); + Ok(()) + } + + #[inline] + async fn write_read( + &mut self, + address: u8, + write_buf: &[u8], + read_buf: &mut [u8], + ) -> Result<(), Self::Error> { + self.do_write(address, write_buf).await?; + self.i2c.config.as_mut().registers.cmd_repeated_start(); + self.do_read(address, read_buf).await?; + Ok(()) + } + + #[inline] + async fn transaction( + &mut self, + address: u8, + operations: &mut [Operation<'_>], + ) -> Result<(), Self::Error> { + let mut op_groups = chunk_operations(operations).peekable(); + + while let Some(group) = op_groups.next() { + let mut group = group.iter_mut(); + // Unwrapping is OK here because chunk_operations will never give us a 0-length + // chunk. + let op = group.next().unwrap(); + + // First operation in the group - send a START with the address, and the first + // operation. + match op { + Operation::Read(buf) => self.do_read(address, buf).await?, + Operation::Write(buf) => self.do_write(address, buf).await?, + } + + // For all subsequent operations, just send/read more bytes without any more + // ceremony. + for op in group { + match op { + Operation::Read(buf) => self.continue_read(buf).await?, + Operation::Write(buf) => self.continue_write(buf).await?, + } + } + + let regs = &mut self.i2c.config.as_mut().registers; + if op_groups.peek().is_some() { + // If we still have more groups to go, send a repeated start + regs.cmd_repeated_start(); + } else { + // Otherwise, send a stop + regs.cmd_stop(); + } + } + + Ok(()) + } +} + +#[cfg(feature = "dma")] +mod dma { + use embedded_hal_1::i2c::Operation; + use embedded_hal_async::i2c::I2c as I2cTrait; + + use super::*; + use crate::dmac::sram::DmacDescriptor; + use crate::dmac::{AnyChannel, Channel, ReadyFuture}; + use crate::sercom::dma::async_dma::{read_dma_linked, write_dma_linked}; + use crate::sercom::dma::SharedSliceBuffer; + + #[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, + D: AnyChannel, + { + /// Reclaim the DMA channel. Any subsequent I2C operations will no + /// longer use DMA. + pub fn take_dma_channel(self) -> (I2cFuture, D) { + let (i2c, channel) = self.i2c.take_dma_channel(); + (I2cFuture { i2c }, channel) + } + } + + impl I2cFuture + where + C: AnyConfig, + S: Sercom, + D: AnyChannel, + { + /// Make an async I2C write transaction, with the option to add in + /// linked transfers after this first transaction. + /// + /// # Safety + /// + /// If `next` is [`Some`], the pointer in its `descaddr` field, along + /// with the descriptor it points to, etc, must point to a valid + /// [`DmacDescriptor`] memory location, or be null. They must not be + /// circular (ie, points to itself). Any linked transfer must + /// strictly be a write transaction (source pointer is a byte buffer, + /// destination pointer is the SERCOM DATA register). + #[inline] + pub(super) async unsafe fn write_linked( + &mut self, + address: u8, + bytes: &[u8], + next: Option<&mut DmacDescriptor>, + ) -> Result<(), i2c::Error> { + self.i2c.prepare_write_linked(address, bytes, &next)?; + + let sercom_ptr = self.i2c.sercom_ptr(); + let mut bytes = SharedSliceBuffer::from_slice(bytes); + + write_dma_linked::<_, _, S>(&mut self.i2c._dma_channel, sercom_ptr, &mut bytes, next) + .await?; + + // Unfortunately, gotta take a polling approach here as there is no interrupt + // source that can notify us of an IDLE bus state. Fortunately, it's usually not + // long. 8-10 times around the loop will do the trick. + while !self.i2c.read_status().is_idle() { + core::hint::spin_loop(); + } + self.i2c.read_status().check_bus_error()?; + Ok(()) + } + + /// Asynchronously read into a buffer using DMA. + #[inline] + pub(super) async unsafe fn read_linked( + &mut self, + address: u8, + mut buffer: &mut [u8], + next: Option<&mut DmacDescriptor>, + ) -> Result<(), i2c::Error> { + self.i2c.prepare_read_linked(address, buffer, &next)?; + let i2c_ptr = self.i2c.sercom_ptr(); + + read_dma_linked::<_, _, S>(&mut self.i2c._dma_channel, i2c_ptr, &mut buffer, next) + .await?; + + // Unfortunately, gotta take a polling approach here as there is no interrupt + // source that can notify us of an IDLE bus state. Fortunately, it's usually not + // long. 8-10 times around the loop will do the trick. + while !self.i2c.read_status().is_idle() { + core::hint::spin_loop(); + } + self.i2c.read_status().check_bus_error()?; + + Ok(()) + } + + /// Asynchronously write from a buffer, then read into a buffer, all + /// using DMA. + /// + /// This is an extremely common pattern: for example, 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> { + unsafe { + self.write_linked(addr, write_buf, None).await?; + self.read_linked(addr, read_buf, None).await?; + } + Ok(()) + } + } + + impl I2cTrait for I2cFuture + where + C: AnyConfig, + D: AnyChannel, + { + #[inline] + async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { + unsafe { self.read_linked(address, buffer, None).await } + } + + #[inline] + async fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { + unsafe { self.write_linked(address, bytes, None).await } + } + + #[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, + address: u8, + operations: &mut [Operation<'_>], + ) -> Result<(), Self::Error> { + use crate::dmac::{channel, sram::DmacDescriptor}; + use crate::sercom::dma::SharedSliceBuffer; + use Operation::{Read, Write}; + + const NUM_LINKED_TRANSFERS: usize = 16; + + if operations.is_empty() { + return Ok(()); + } + + let mut sercom_ptr = self.i2c.sercom_ptr(); + + // Reserve some space for linked DMA transfer descriptors. + // Uses 256 bytes of memory. + // + // In practice this means that we can only support 17 continuously + // linked operations of the same type (R/W) before having to issue + // an I2C STOP. DMA-enabled I2C transfers automatically issue stop + // commands, and there is no way to turn off that behaviour. + // + // In the event that we have more than 17 contiguous operations of + // the same type, we must revert to the byte-by-byte I2C implementations. + let mut descriptors = heapless::Vec::::new(); + + // Arrange operations in groups of contiguous types (R/W) + let op_groups = operations.chunk_by_mut(|this, next| { + matches!((this, next), (Write(_), Write(_)) | (Read(_), Read(_))) + }); + + for group in op_groups { + descriptors.clear(); + + // Default to byte-by-byte impl if we have more than 17 continuous operations, + // as we would overflow our DMA linked transfer reeserved space otherwise. + if group.len() > NUM_LINKED_TRANSFERS { + self.i2c.transaction_byte_by_byte(address, group)?; + } else { + // --- Setup all linked descriptors --- + + // Skip the first operation; we will deal with it when creating the I2C transfer + // (read_dma_linked/write_dma_linked). Every other operation is a linked + // transfer, and we must treat them accordingly. + for op in group.iter_mut().skip(1) { + match op { + Read(ref mut buffer) => { + if buffer.is_empty() { + continue; + } + // Add a new linked descriptor to the stack + descriptors + .push(DmacDescriptor::default()) + .unwrap_or_else(|_| panic!("BUG: DMAC descriptors overflow")); + let last_descriptor = descriptors.last_mut().unwrap(); + let next_ptr = + (last_descriptor as *mut DmacDescriptor).wrapping_add(1); + + unsafe { + channel::write_descriptor( + last_descriptor, + &mut sercom_ptr, + buffer, + // Always link the next descriptor. We then set the last + // transfer's link pointer to null lower down in the code. + next_ptr, + ); + } + } + + Write(bytes) => { + if bytes.is_empty() { + continue; + } + // Add a new linked descriptor to the stack + descriptors + .push(DmacDescriptor::default()) + .unwrap_or_else(|_| panic!("BUG: DMAC descriptors overflow")); + let last_descriptor = descriptors.last_mut().unwrap(); + let next_ptr = + (last_descriptor as *mut DmacDescriptor).wrapping_add(1); + + let mut bytes = SharedSliceBuffer::from_slice(bytes); + unsafe { + channel::write_descriptor( + last_descriptor, + &mut bytes, + &mut sercom_ptr, + // Always link the next descriptor. We then set the last + // transfer's link pointer to null lower down in the code. + next_ptr, + ); + } + } + } + } + + // Set the last descriptor to a null pointer to stop the transfer, and avoid + // buffer overflow UB. + if let Some(d) = descriptors.last_mut() { + d.set_next_descriptor(core::ptr::null_mut()); + } + + // Now setup and perform the actual transfer + match group.first_mut().unwrap() { + Read(ref mut buffer) => unsafe { + self.read_linked(address, buffer, descriptors.first_mut()) + .await?; + }, + Write(bytes) => unsafe { + self.write_linked(address, bytes, descriptors.first_mut()) + .await?; + }, + } + } + } + + Ok(()) + } + } +} + +#[cfg(feature = "dma")] +pub use dma::*; diff --git a/hal/src/sercom/i2c/config.rs b/hal/src/sercom/i2c/config.rs index 39f684564b3..5c7362d1440 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/impl_ehal.rs b/hal/src/sercom/i2c/impl_ehal.rs index 6c7838d7804..8d8a0da9819 100644 --- a/hal/src/sercom/i2c/impl_ehal.rs +++ b/hal/src/sercom/i2c/impl_ehal.rs @@ -1,7 +1,7 @@ //! [`embedded-hal`] trait implementations for [`I2c`]s use super::{config::AnyConfig, flags::Error, I2c}; -use crate::ehal::i2c::{self, ErrorKind, ErrorType, NoAcknowledgeSource}; +use crate::ehal::i2c::{self, ErrorKind, ErrorType, NoAcknowledgeSource, Operation}; impl i2c::Error for Error { #[allow(unreachable_patterns)] @@ -23,45 +23,45 @@ impl ErrorType for I2c { } impl I2c { - fn transaction_byte_by_byte( + pub(super) fn transaction_byte_by_byte( &mut self, address: u8, - operations: &mut [i2c::Operation<'_>], + operations: &mut [Operation<'_>], ) -> Result<(), Error> { - /// Helper type for keeping track of the type of operation that was - /// executed last - #[derive(Clone, Copy)] - enum Operation { - Read, - Write, - } + let mut op_groups = chunk_operations(operations).peekable(); + + while let Some(group) = op_groups.next() { + let mut group = group.iter_mut(); + // Unwrapping is OK here because chunk_operations will never give us a 0-length + // chunk. + let op = group.next().unwrap(); - // Keep track of the last executed operation type. The method - // specification demands, that no repeated start condition is sent - // between adjacent operations of the same type. - let mut last_op = None; - for op in operations { + // First operation in the group - send a START with the address, and the first + // operation. match op { - i2c::Operation::Read(buf) => { - if let Some(Operation::Read) = last_op { - self.continue_read(buf)?; - } else { - self.do_read(address, buf)?; - last_op = Some(Operation::Read); - } - } - i2c::Operation::Write(bytes) => { - if let Some(Operation::Write) = last_op { - self.continue_write(bytes)?; - } else { - self.do_write(address, bytes)?; - last_op = Some(Operation::Write); - } + Operation::Read(buf) => self.do_read(address, buf)?, + Operation::Write(buf) => self.do_write(address, buf)?, + } + + // For all subsequent operations, just send/read more bytes without any more + // ceremony. + for op in group { + match op { + Operation::Read(buf) => self.continue_read(buf)?, + Operation::Write(buf) => self.continue_write(buf)?, } } + + let regs = &mut self.config.as_mut().registers; + if op_groups.peek().is_some() { + // If we still have more groups to go, send a repeated start + regs.cmd_repeated_start(); + } else { + // Otherwise, send a stop + regs.cmd_stop(); + } } - self.cmd_stop(); Ok(()) } } @@ -70,7 +70,7 @@ impl i2c::I2c for I2c { fn transaction( &mut self, address: u8, - operations: &mut [i2c::Operation<'_>], + operations: &mut [Operation<'_>], ) -> Result<(), Self::Error> { self.transaction_byte_by_byte(address, operations)?; Ok(()) @@ -103,17 +103,19 @@ impl i2c::I2c for I2c { #[cfg(feature = "dma")] mod dma { use super::*; + use crate::dmac::ReadyChannel; use crate::dmac::{channel, sram::DmacDescriptor, AnyChannel, Ready}; use crate::sercom::dma::{read_dma_linked, write_dma_linked, SercomPtr, SharedSliceBuffer}; use crate::sercom::{self, Sercom}; - impl I2c + impl I2c where C: AnyConfig, S: Sercom, - D: AnyChannel, + D: AnyChannel, + R: ReadyChannel, { - fn sercom_ptr(&self) -> SercomPtr { + pub(in super::super) fn sercom_ptr(&self) -> SercomPtr { SercomPtr(self.data_ptr()) } @@ -144,7 +146,7 @@ mod dma { cnt } - /// Make an I2C read transaction, with the option to add in linked + /// Prepare an I2C read transaction, with the option to add in linked /// transfers after this first transaction. /// /// # Safety @@ -156,22 +158,21 @@ mod dma { /// strictly be a read transaction (destination pointer is a byte /// buffer, source pointer is the SERCOM DATA register). #[inline] - unsafe fn read_linked( + pub(in super::super) unsafe fn prepare_read_linked( &mut self, address: u8, - mut dest: &mut [u8], - next: Option<&mut DmacDescriptor>, + dest: &mut [u8], + next: &Option<&mut DmacDescriptor>, ) -> Result<(), Error> { - self.check_bus_status()?; - let sercom_ptr = self.sercom_ptr(); - if dest.is_empty() { return Ok(()); } + self.check_bus_status()?; + // Calculate the total number of bytes for this transaction across all linked // transfers, including the first transfer. - let transfer_len = dest.len() + Self::linked_transfer_length(&next); + let transfer_len = dest.len() + Self::linked_transfer_length(next); assert!( transfer_len <= 255, @@ -179,6 +180,72 @@ mod dma { ); self.start_dma_read(address, transfer_len as u8); + Ok(()) + } + + /// Prepare an I2C write transaction, with the option to add in linked + /// transfers after this first transaction. + /// + /// # Safety + /// + /// If `next` is [`Some`], the pointer in its `descaddr` field, along + /// with the descriptor it points to, etc, must point to a valid + /// [`DmacDescriptor`] memory location, or be null. They must not be + /// circular (ie, points to itself). Any linked transfer must + /// strictly be a write transaction (source pointer is a byte buffer, + /// destination pointer is the SERCOM DATA register). + pub(in super::super) unsafe fn prepare_write_linked( + &mut self, + address: u8, + source: &[u8], + next: &Option<&mut DmacDescriptor>, + ) -> Result<(), Error> { + self.check_bus_status()?; + + if source.is_empty() { + return Ok(()); + } + + // Calculate the total number of bytes for this transaction across all linked + // transfers, including the first transfer. + let transfer_len = source.len() + Self::linked_transfer_length(next); + + assert!( + transfer_len <= 255, + "I2C write transfers of more than 255 bytes are unsupported." + ); + + self.start_dma_write(address, transfer_len as u8); + Ok(()) + } + } + + impl I2c + where + C: AnyConfig, + S: Sercom, + D: AnyChannel, + { + /// Make an I2C read transaction, with the option to add in linked + /// transfers after this first transaction. + /// + /// # Safety + /// + /// If `next` is [`Some`], the pointer in its `descaddr` field, along + /// with the descriptor it points to, etc, must point to a valid + /// [`DmacDescriptor`] memory location, or be null. They must not be + /// circular (ie, points to itself). Any linked transfer must + /// strictly be a read transaction (destination pointer is a byte + /// buffer, source pointer is the SERCOM DATA register). + #[inline] + unsafe fn read_linked( + &mut self, + address: u8, + mut dest: &mut [u8], + next: Option<&mut DmacDescriptor>, + ) -> Result<(), Error> { + self.prepare_read_linked(address, dest, &next)?; + let sercom_ptr = self.sercom_ptr(); let channel = self._dma_channel.as_mut(); // SAFETY: We must make sure that any DMA transfer is complete or stopped before @@ -215,23 +282,9 @@ mod dma { source: &[u8], next: Option<&mut DmacDescriptor>, ) -> Result<(), Error> { - self.check_bus_status()?; - let sercom_ptr = self.sercom_ptr(); - - if source.is_empty() { - return Ok(()); - } + self.prepare_write_linked(address, source, &next)?; - // Calculate the total number of bytes for this transaction across all linked - // transfers, including the first transfer. - let transfer_len = source.len() + Self::linked_transfer_length(&next); - - assert!( - transfer_len <= 255, - "I2C write transfers of more than 255 bytes are unsupported." - ); - - self.start_dma_write(address, transfer_len as u8); + let sercom_ptr = self.sercom_ptr(); let mut bytes = SharedSliceBuffer::from_slice(source); let channel = self._dma_channel.as_mut(); @@ -290,10 +343,7 @@ mod dma { // the same type, we must revert to the byte-by-byte I2C implementations. let mut descriptors = heapless::Vec::::new(); - // Arrange operations in groups of contiguous types (R/W) - let op_groups = operations.chunk_by_mut(|this, next| { - matches!((this, next), (Write(_), Write(_)) | (Read(_), Read(_))) - }); + let op_groups = chunk_operations(operations); for group in op_groups { descriptors.clear(); @@ -435,3 +485,14 @@ impl crate::ehal_02::blocking::i2c::WriteRead for I2c { Ok(()) } } + +/// Arrange all operations in contiguous chunks of the same R/W type +pub(super) fn chunk_operations<'a, 'op>( + operations: &'a mut [Operation<'op>], +) -> impl Iterator]> { + use i2c::Operation::{Read, Write}; + + operations.chunk_by_mut(|this, next| { + matches!((this, next), (Write(_), Write(_)) | (Read(_), Read(_))) + }) +} diff --git a/hal/src/sercom/i2c/reg.rs b/hal/src/sercom/i2c/reg.rs index 90e53929ad3..41dc725499b 100644 --- a/hal/src/sercom/i2c/reg.rs +++ b/hal/src/sercom/i2c/reg.rs @@ -10,6 +10,7 @@ use atsamd_hal_macros::hal_cfg; const MASTER_ACT_READ: u8 = 2; const MASTER_ACT_STOP: u8 = 3; +const MASTER_ACT_REPEATED_START: u8 = 1; #[hal_cfg(any("sercom0-d11", "sercom0-d21"))] type DataReg = u8; @@ -17,7 +18,7 @@ type DataReg = u8; #[hal_cfg("sercom0-d5x")] type DataReg = u32; -pub(super) struct Registers { +pub(in super::super) struct Registers { pub sercom: S, } @@ -34,7 +35,7 @@ impl Registers { /// Helper function to access the underlying `I2cm` from the given `SERCOM` #[inline] - fn i2c_master(&self) -> &pac::sercom0::I2cm { + pub(in super::super) fn i2c_master(&self) -> &pac::sercom0::I2cm { self.sercom.i2cm() } @@ -218,9 +219,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); @@ -237,14 +239,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); @@ -265,6 +275,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(); @@ -289,7 +307,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); @@ -314,7 +332,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); @@ -330,18 +348,32 @@ impl Registers { self.sync_sysop(); } + /// Send a STOP condition. If the I2C is performing a read, will also send a + /// NACK to the slave. #[inline] - pub(super) fn issue_command(&mut self, cmd: u8) { - self.i2c_master() - .ctrlb() - .modify(|_, w| unsafe { w.cmd().bits(cmd) }); - + pub(super) fn cmd_stop(&mut self) { + unsafe { + self.i2c_master().ctrlb().modify(|_, w| { + // set bit means send NACK + w.ackact().set_bit(); + w.cmd().bits(MASTER_ACT_STOP) + }); + } self.sync_sysop(); } + /// Send a REPEATED START condition. If the I2C is performing a read, will + /// also send a NACK to the slave. #[inline] - pub(super) fn cmd_stop(&mut self) { - self.issue_command(MASTER_ACT_STOP) + pub(super) fn cmd_repeated_start(&mut self) { + unsafe { + self.i2c_master().ctrlb().modify(|_, w| { + // set bit means send NACK + w.ackact().set_bit(); + w.cmd().bits(MASTER_ACT_REPEATED_START) + }); + } + self.sync_sysop(); } #[inline] @@ -356,12 +388,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 as DataReg)); + } + } + #[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 as DataReg)); - } + self.write_one(*b); loop { let intflag = self.i2c_master().intflag().read(); @@ -377,18 +414,24 @@ impl Registers { #[inline] #[allow(clippy::unnecessary_cast)] pub(super) fn read_one(&mut self) -> u8 { - while !self.i2c_master().intflag().read().sb().bit_is_set() {} - // SAMx5x: u32 -> u8 conversion is fine as long as we don't set CTRLC.DATA32B to // 1. self.i2c_master().data().read().bits() as u8 } + #[inline] + pub(super) fn read_one_blocking(&mut self) -> u8 { + while !self.i2c_master().intflag().read().sb().bit_is_set() { + core::hint::spin_loop(); + } + 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() { @@ -396,7 +439,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 188bb7da32d..87aed9d46fc 100644 --- a/hal/src/sercom/mod.rs +++ b/hal/src/sercom/mod.rs @@ -9,20 +9,19 @@ //! experimentation with certain boards which have verifiably demonstrated that //! those features work as intended. //! -//! * [`UndocIoSet1`](pad::UndocIoSet1): Implement an undocumented `IoSet` for -//! PA16, PA17, PB22 & PB23 configured for [`Sercom1`]. The pygamer & -//! feather_m4 use this combination, but it is not listed as valid in the -//! datasheet. +//! * [`UndocIoSet1`]: Implement an undocumented `IoSet` for PA16, PA17, PB22 & +//! PB23 configured for [`Sercom1`]. The pygamer & feather_m4 use this +//! combination, but it is not listed as valid in the datasheet. //! -//! * [`UndocIoSet2`](pad::UndocIoSet2): Implement an undocumented `IoSet` for -//! PA00, PA01, PB22 & PB23 configured for [`Sercom1`]. The itsybitsy_m4 uses -//! this combination, but it is not listed as valid in the datasheet. +//! * [`UndocIoSet2`]: Implement an undocumented `IoSet` for PA00, PA01, PB22 & +//! PB23 configured for [`Sercom1`]. The itsybitsy_m4 uses this combination, +//! but it is not listed as valid in the datasheet. //! //! * [`PB02`] is I2C-capable according to metro_m4. As such, [`PB02`] //! implements [`IsI2cPad`]. //! //! * [`PB03`] is I2C-capable according to metro_m4. As such, [`PB03`] -//! implements [`IsI2cPad`](pad::IsI2cPad). +//! implements [`IsI2cPad`]. //! //! [`PB02`]: crate::gpio::pin::PB02 //! [`PB03`]: crate::gpio::pin::PB03 @@ -34,6 +33,7 @@ use core::ops::Deref; use crate::pac; use pac::sercom0; +use pac::Peripherals; #[hal_cfg("sercom0-d5x")] use pac::Mclk as ApbClkCtrl; @@ -50,6 +50,11 @@ pub use pad::*; pub mod i2c; pub mod spi; + +#[deprecated( + since = "0.19.0", + note = "spi_future is deprecated and will be removed in a later version of atsamd_hal. Consider using the `async` APIs available in the `spi` module as a replacement." +)] pub mod spi_future; pub mod uart; @@ -64,14 +69,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: &ApbClkCtrl); + + /// Get a reference to the sercom from a + /// [`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 { + &crate::sercom::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 { + &crate::sercom::async_api::TX_WAKERS[Self::NUM] + } } macro_rules! sercom { @@ -83,15 +112,33 @@ macro_rules! sercom { impl Sealed for [< Sercom $N >] {} impl Sercom for [< Sercom $N >] { const NUM: usize = $N; + #[cfg(feature = "dma")] const DMA_RX_TRIGGER: TriggerSource = TriggerSource::[< Sercom $N Rx >]; + #[cfg(feature = "dma")] const DMA_TX_TRIGGER: TriggerSource = TriggerSource::[< Sercom $N Tx >]; + + #[cfg(feature = "async")] + #[hal_cfg(any("sercom0-d11", "sercom0-d21"))] + type Interrupt = $crate::async_hal::interrupts::[< SERCOM $N >]; + + #[cfg(feature = "async")] + #[hal_cfg("sercom0-d5x")] + type Interrupt = $crate::async_hal::interrupts::[< SERCOM $N >]; + #[inline] fn enable_apb_clock(&mut self, ctrl: &ApbClkCtrl) { ctrl.$apbmask().modify(|_, w| w.[< sercom $N _>]().set_bit()); } + + #[inline] + fn reg_block(peripherals: &mut Peripherals) -> &crate::pac::sercom0::RegisterBlock { + &*peripherals.[< sercom $N >] + } } + + } }; } @@ -139,3 +186,31 @@ sercom!(apbdmask, 6); #[hal_cfg("sercom7-d5x")] sercom!(apbdmask, 7); + +// Reserve space for the max number of SERCOM peripherals based on chip type, +// even though some wakers may not be used on some chips if they actually don't +// exist on variant's hardware +#[hal_cfg("sercom0-d11")] +#[cfg(feature = "async")] +const NUM_SERCOM: usize = 3; + +#[hal_cfg("sercom0-d21")] +#[cfg(feature = "async")] +const NUM_SERCOM: usize = 6; + +#[hal_cfg("sercom0-d5x")] +#[cfg(feature = "async")] +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 fc6370e8f66..1069d0d02c8 100644 --- a/hal/src/sercom/spi.rs +++ b/hal/src/sercom/spi.rs @@ -243,8 +243,7 @@ //! Only [`Spi`] structs can actually perform transactions. To do so, use the //! various embedded HAL traits, like //! [`spi::SpiBus`](crate::ehal::spi::SpiBus), -//! [`embedded_io::Read`](crate::embedded_io::Read), -//! [`embedded_io::Write`](crate::embedded_io::Write), +//! [`embedded_io::Read`], [`embedded_io::Write`], //! [`embedded_hal_nb::serial::Read`](crate::ehal_nb::serial::Read), or //! [`embedded_hal_nb::serial::Write`](crate::ehal_nb::serial::Write). //! See the [`impl_ehal`] module documentation for more details about the @@ -302,6 +301,119 @@ //! spi.write(&mut buffer)?; //! ``` //! +//! # `async` operation async +//! +//! An [`Spi`] can be used for +//! `async` operations. Configuring a [`Spi`] in async mode is relatively +//! simple: +//! +//! * Bind the corresponding `SERCOM` interrupt source to the SPI +//! [`InterruptHandler`] (refer to the module-level [`async_hal`] +//! documentation for more information). +//! * Turn a previously configured [`Spi`] into a [`SpiFuture`] by calling +//! [`Spi::into_future`] +//! * Optionally, add DMA channels to RX, TX or both using +//! [`SpiFuture::with_rx_dma_channel`] and [`SpiFuture::with_tx_dma_channel`]. +//! The API is exactly the same whether DMA channels are used or not. +//! * Use the provided async methods for reading or writing to the SPI +//! peripheral. [`SpiFuture`] implements [`embedded_hal_async::spi::SpiBus`]. +//! +//! `SpiFuture` implements `AsRef` and `AsMut` so +//! that it can be reconfigured using the regular [`Spi`] methods. +//! +//! ## Considerations when using `async` [`Spi`] with DMA async dma +//! +//! * An [`Spi`] struct must be turned into an [`SpiFuture`] by calling +//! [`Spi::into_future`] before calling `with_dma_channel`. The DMA channel +//! itself must also be configured in async mode by using +//! [`DmaController::into_future`](crate::dmac::DmaController::into_future). +//! If a DMA channel is added to the [`Spi`] struct before it is turned into +//! an [`SpiFuture`], it will not be able to use DMA in async mode. +//! +//! ``` +//! // This will work +//! let spi = spi.into_future().with_dma_channels(rx_channel, tx_channel); +//! +//! // This won't +//! let spi = spi.with_dma_channels(rx_channel, tx_channel).into_future(); +//! ``` +//! +//! ### Safety considerations +//! +//! In `async` mode, an SPI+DMA transfer does not require `'static` source and +//! destination buffers. This, in theory, makes its use `unsafe`. However it is +//! marked as safe for better ergonomics, and to enable the implementation of +//! the [`embedded_hal_async::spi::SpiBus`] trait. +//! +//! This means that, as an user, you **must** ensure that the [`Future`]s +//! returned by the [`embedded_hal_async::spi::SpiBus`] methods may never be +//! forgotten through [`forget`] or by wrapping them with a [`ManuallyDrop`]. +//! +//! The returned futures implement [`Drop`] and will automatically stop any +//! ongoing transfers; this guarantees that the memory occupied by the +//! now-dropped buffers may not be corrupted by running transfers. +//! +//! This means that using functions like [`futures::select_biased`] to implement +//! timeouts is safe; transfers will be safely cancelled if the timeout expires. +//! +//! 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. +//! * Dropping an incomplete transfer *without running its destructor* is +//! **unsound** and will trigger undefined behavior. +//! +//! ```ignore +//! async fn always_ready() {} +//! +//! let mut buffer = [0x00; 10]; +//! +//! // This is completely safe +//! spi.read(&mut buffer).await?; +//! +//! // This is also safe: we launch a transfer, which is then immediately cancelled +//! futures::select_biased! { +//! _ = spi.read(&mut buffer)?, +//! _ = always_ready(), +//! } +//! +//! // This, while contrived, is also safe. +//! { +//! use core::future::Future; +//! +//! let future = spi.read(&mut buffer); +//! futures::pin_mut!(future); +//! // Assume ctx is a `core::task::Context` given out by the executor. +//! // The future is polled, therefore starting the transfer +//! future.as_mut().poll(ctx); +//! +//! // Future is dropped here - transfer is cancelled. +//! } +//! +//! // DANGER: This is an example of undefined behavior +//! { +//! use core::future::Future; +//! use core::ops::DerefMut; +//! +//! let future = core::mem::ManuallyDrop::new(spi.read(&mut buffer)); +//! futures::pin_mut!(future); +//! // To actually make this example compile, we would need to wrap the returned +//! // future from `i2c.read()` in a newtype that implements Future, because we +//! // can't actually call as_mut() without being able to name the type we want +//! // to deref to. +//! let future_ref: &mut SomeNewTypeFuture = &mut future.as_mut(); +//! future.as_mut().poll(ctx); +//! +//! // Future is NOT dropped here - transfer is not cancelled, resulting un UB. +//! } +//! ``` +//! +//! As you can see, unsoundness is relatively hard to come by - however, caution +//! should still be exercised. +//! //! [`enable`]: Config::enable //! [`gpio`]: crate::gpio //! [`Pin`]: crate::gpio::pin::Pin @@ -309,6 +421,11 @@ //! [`PinMode`]: crate::gpio::pin::PinMode //! [`embedded_hal::spi::SpiBus`]: crate::ehal::spi::SpiBus //! [`embedded_hal::spi::SpiDevice`]: crate::ehal::spi::SpiDevice +//! [`async_hal`]: crate::async_hal +//! [`forget`]: core::mem::forget +//! [`ManuallyDrop`]: core::mem::ManuallyDrop +//! [`Future`]: core::future::Future +//! [`poll`]: core::future::Future::poll use core::marker::PhantomData; @@ -360,6 +477,11 @@ pub mod lengths { pub mod impl_ehal; +#[cfg(feature = "async")] +mod async_api; +#[cfg(feature = "async")] +pub use async_api::*; + //============================================================================= // BitOrder //============================================================================= @@ -377,6 +499,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 | ERROR; +pub const TX_FLAG_MASK: u8 = DRE | TXC; + bitflags! { /// Interrupt bit flags for SPI transactions /// @@ -385,14 +516,20 @@ bitflags! { /// `INTFLAG` register. #[derive(Clone, Copy)] 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 = Self::from_bits_retain(RX_FLAG_MASK); + pub(super) const TX: Self = Self::from_bits_retain(TX_FLAG_MASK); +} + //============================================================================= // Status //============================================================================= @@ -1013,8 +1150,8 @@ where Spi { config: self, capability: P::Capability::default(), - rx_channel: NoneT, - tx_channel: NoneT, + _rx_channel: NoneT, + _tx_channel: NoneT, } } } @@ -1182,8 +1319,8 @@ where { config: C, capability: A, - rx_channel: RxDma, - tx_channel: TxDma, + _rx_channel: RxDma, + _tx_channel: TxDma, } /// Get a shared reference to the underlying [`Config`] struct @@ -1231,8 +1368,8 @@ where Spi { config: self.config.into().length(), capability: self.capability, - rx_channel: self.rx_channel, - tx_channel: self.tx_channel, + _rx_channel: self._rx_channel, + _tx_channel: self._tx_channel, } } @@ -1339,8 +1476,13 @@ where while !self.read_flags().intersects(flags | Flags::ERROR) { core::hint::spin_loop(); } + let flags = self.read_flags(); + self.check_and_clear_error(flags) + } - if self.read_flags().contains(Flags::ERROR) { + #[inline] + fn check_and_clear_error(&mut self, flags: Flags) -> Result<(), Error> { + if flags.contains(Flags::ERROR) { let errors = self.read_status(); // Clear all status flags at once; BUFOVF has priority, and will mask LENERR if // both show up at the same time. @@ -1349,7 +1491,6 @@ where return errors.check_bus_error(); } - self.read_flags_errors()?; Ok(()) } } @@ -1374,31 +1515,33 @@ where Spi { capability: self.capability, config: self.config, - rx_channel: rx, - tx_channel: tx, + _rx_channel: rx, + _tx_channel: tx, } } } #[cfg(feature = "dma")] -impl Spi +impl Spi where C: ValidConfig, D: Capability, - RxDma: crate::dmac::AnyChannel, - TxDma: crate::dmac::AnyChannel, + RxDma: crate::dmac::AnyChannel, + TxDma: crate::dmac::AnyChannel, + S: crate::dmac::ReadyChannel, { - /// Reclaim the DMA channels. + /// Reclaim the DMA channels. Any subsequent SPI transaction will not use + /// DMA. pub fn take_dma_channels(self) -> (Spi, RxDma, TxDma) { ( Spi { capability: self.capability, config: self.config, - rx_channel: NoneT, - tx_channel: NoneT, + _rx_channel: NoneT, + _tx_channel: NoneT, }, - self.rx_channel, - self.tx_channel, + self._rx_channel, + self._tx_channel, ) } } @@ -1421,8 +1564,8 @@ where Spi { capability: self.capability, config: self.config, - rx_channel: rx, - tx_channel: tx, + _rx_channel: rx, + _tx_channel: tx, } } } @@ -1444,30 +1587,32 @@ where Spi { capability: self.capability, config: self.config, - rx_channel: rx, - tx_channel: NoneT, + _rx_channel: rx, + _tx_channel: NoneT, } } } #[cfg(feature = "dma")] -impl Spi +impl Spi where C: ValidConfig, D: Receive, - R: crate::dmac::AnyChannel, + R: crate::dmac::AnyChannel, + S: crate::dmac::ReadyChannel, { - /// Reclaim the Rx DMA channel + /// Reclaim the Rx DMA channel. Any subsequent SPI transaction will not use + /// DMA. #[cfg(feature = "dma")] pub fn take_rx_channel(self) -> (Spi, R) { ( Spi { capability: self.capability, config: self.config, - tx_channel: self.tx_channel, - rx_channel: NoneT, + _tx_channel: self._tx_channel, + _rx_channel: NoneT, }, - self.rx_channel, + self._rx_channel, ) } } @@ -1489,29 +1634,31 @@ where Spi { capability: self.capability, config: self.config, - rx_channel: NoneT, - tx_channel: tx, + _rx_channel: NoneT, + _tx_channel: tx, } } } #[cfg(feature = "dma")] -impl Spi +impl Spi where C: ValidConfig, D: Capability, - T: crate::dmac::AnyChannel, + T: crate::dmac::AnyChannel, + S: crate::dmac::ReadyChannel, { - /// Reclaim the DMA channel. + /// Reclaim the DMA channel. Any subsequent SPI transaction will not use + /// DMA. pub fn take_tx_channel(self) -> (Spi, T) { ( Spi { capability: self.capability, config: self.config, - rx_channel: self.rx_channel, - tx_channel: NoneT, + _rx_channel: self._rx_channel, + _tx_channel: NoneT, }, - self.tx_channel, + self._tx_channel, ) } } diff --git a/hal/src/sercom/spi/async_api/dma.rs b/hal/src/sercom/spi/async_api/dma.rs new file mode 100644 index 00000000000..070db74d237 --- /dev/null +++ b/hal/src/sercom/spi/async_api/dma.rs @@ -0,0 +1,470 @@ +use embedded_hal_async::spi::SpiBus; +use num_traits::{AsPrimitive, PrimInt}; + +use super::SpiFuture; +use crate::{ + dmac::{ + channel::{self, Channel}, + sram::DmacDescriptor, + AnyChannel, Beat, Buffer, ReadyFuture, + }, + sercom::{ + dma::{ + async_dma::{self, read_dma, read_dma_linked, write_dma, write_dma_linked}, + SharedSliceBuffer, SinkSourceBuffer, + }, + spi::{ + Capability, Config, DataWidth, Duplex, Error, MasterMode, OpMode, Receive, Rx, Size, + Slave, Spi, Transmit, Tx, ValidConfig, ValidPads, Word, + }, + Sercom, + }, + typelevel::NoneT, +}; + +/// 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, Channel>; + +/// 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>; + +/// 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, + D: Capability, + RxDma: AnyChannel, + TxDma: AnyChannel, +{ + /// Reclaim the DMA channels. Any subsequent SPI transaction will not + /// use DMA. + pub fn take_dma_channels(self) -> (SpiFuture, RxDma, TxDma) { + let (spi, rx, tx) = self.spi.take_dma_channels(); + (SpiFuture { spi }, rx, tx) + } +} + +impl SpiFuture +where + C: ValidConfig, + D: Receive, + R: AnyChannel, +{ + /// Reclaim the Rx DMA channel. Any subsequent SPI transaction will not + /// use DMA. + pub fn take_rx_channel(self) -> (SpiFuture, R) { + let (spi, channel) = self.spi.take_rx_channel(); + (SpiFuture { spi }, channel) + } +} + +impl SpiFuture +where + C: ValidConfig, + D: Capability, + T: AnyChannel, +{ + /// Reclaim the DMA channel. Any subsequent SPI transaction will not use + /// DMA. + pub fn take_tx_channel(self) -> (SpiFuture, T) { + let (spi, channel) = self.spi.take_tx_channel(); + (SpiFuture { spi }, 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_rx_dma_channel>( + self, + rx_channel: Chan, + ) -> SpiFuture { + SpiFuture { + spi: Spi { + config: self.spi.config, + capability: self.spi.capability, + _rx_channel: rx_channel, + _tx_channel: self.spi._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_tx_dma_channel>( + self, + tx_channel: Chan, + ) -> SpiFuture { + SpiFuture { + spi: Spi { + config: self.spi.config, + capability: self.spi.capability, + _rx_channel: self.spi._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: Spi { + config: self.spi.config, + capability: self.spi.capability, + _rx_channel: rx_channel, + _tx_channel: tx_channel, + }, + } + } +} + +// Write implementation is the same for Master and Slave SPIs. +impl SpiFuture, D, R, T> +where + P: ValidPads, + M: OpMode, + Z: Size + 'static, + Config: ValidConfig, + D: Transmit, + S: Sercom, + Z::Word: PrimInt + AsPrimitive + Beat, + DataWidth: AsPrimitive, + T: AnyChannel, +{ + /// Write words from a buffer asynchronously, using DMA. + #[inline] + pub async fn write_dma(&mut self, words: &[Z::Word]) -> Result { + if words.is_empty() { + return Ok(0); + } + + // Ignore RX buffer overflows by disabling the receiver + self.spi.config.as_mut().regs.rx_disable(); + + let sercom_ptr = self.spi.sercom_ptr(); + let tx = self.spi._tx_channel.as_mut(); + let mut buf = SharedSliceBuffer::from_slice(words); + + let tx_result = async_dma::write_dma::<_, _, S>(tx, sercom_ptr, &mut buf).await; + + // Reenable receiver only if necessary + if D::RX_ENABLE { + self.spi.config.as_mut().regs.rx_enable(); + } + + self.flush_tx().await; + tx_result?; + Ok(words.len()) + } +} + +impl SpiFuture, D, R, T> +where + Config: ValidConfig, + S: Sercom, + P: ValidPads, + M: MasterMode, + C: Size + 'static, + C::Word: PrimInt + AsPrimitive + Beat, + D: Capability, + DataWidth: AsPrimitive, + R: AnyChannel, + T: AnyChannel, +{ + #[inline] + async fn transfer_blocking, Dest: Buffer>( + &mut self, + dest: &mut Dest, + source: &mut Source, + ) -> Result<(), Error> { + let sercom_ptr = self.spi.sercom_ptr(); + let rx = self.spi._rx_channel.as_mut(); + let tx = self.spi._tx_channel.as_mut(); + + let (rx_result, tx_result) = futures::join!( + read_dma::<_, _, S>(rx, sercom_ptr.clone(), dest), + write_dma::<_, _, S>(tx, sercom_ptr, source) + ); + + // Check for overflows or DMA errors + self.flush_tx_rx().await?; + rx_result.and(tx_result)?; + Ok(()) + } + + /// Read words into a buffer asynchronously, using DMA. + #[inline] + pub async fn read_dma_master(&mut self, mut words: &mut [C::Word]) -> Result<(), Error> { + if words.is_empty() { + return Ok(()); + } + + let mut source_word = self.spi.config.nop_word.as_(); + let mut source = SinkSourceBuffer::new(&mut source_word, words.len()); + + self.transfer_blocking(&mut words, &mut source).await + } +} + +/// [`SpiBus`] implementation for [`Spi`], using DMA transfers. +impl SpiBus> for SpiFuture, Duplex, R, T> +where + S: Sercom, + Config: ValidConfig, + P: ValidPads, + M: MasterMode, + C: Size + 'static, + C::Word: PrimInt + AsPrimitive + Beat, + DataWidth: AsPrimitive, + R: AnyChannel, + T: AnyChannel, +{ + #[inline] + async fn read(&mut self, words: &mut [C::Word]) -> Result<(), Self::Error> { + self.read_dma_master(words).await + } + + #[inline] + async fn write(&mut self, words: &[C::Word]) -> Result<(), Self::Error> { + self.write_dma(words).await?; + Ok(()) + } + + #[inline] + async fn transfer( + &mut self, + mut read: &mut [C::Word], + write: &[C::Word], + ) -> Result<(), Self::Error> { + use core::cmp::Ordering; + + // No work to do here + if write.is_empty() && read.is_empty() { + return Ok(()); + } + + // Handle 0-length special cases + if write.is_empty() { + return self.read_dma_master(read).await; + } else if read.is_empty() { + self.write_dma(write).await?; + return Ok(()); + } + + // Reserve space for a DMAC SRAM descriptor if we need to make a linked + // transfer. Must not be dropped until all transfers have completed + // or have been stopped. + let mut linked_descriptor = DmacDescriptor::default(); + + // If read < write, the incoming words will be written to this memory location; + // it will be discarded after. If read > write, all writes after the + // buffer has been exhausted will write the nop word to "stimulate" the slave + // into sending data. Must not be dropped until all transfers have + // completed or have been stopped. + let mut source_sink_word = self.spi.config.as_mut().nop_word.as_(); + let mut sercom_ptr = self.spi.sercom_ptr(); + + let (read_link, write_link) = match read.len().cmp(&write.len()) { + Ordering::Equal => { + let mut write = SharedSliceBuffer::from_slice(write); + return self.transfer_blocking(&mut read, &mut write).await; + } + + // `read` is shorter; link transfer to sink incoming words after the buffer has been + // filled. + Ordering::Less => { + let mut sink = + SinkSourceBuffer::new(&mut source_sink_word, write.len() - read.len()); + unsafe { + channel::write_descriptor( + &mut linked_descriptor, + &mut sercom_ptr, + &mut sink, + // Add a null descriptor pointer to end the transfer. + core::ptr::null_mut(), + ); + } + + (Some(&mut linked_descriptor), None) + } + + // `write` is shorter; link transfer to send NOP word after the buffer has been + // exhausted. + Ordering::Greater => { + let mut source = + SinkSourceBuffer::new(&mut source_sink_word, read.len() - write.len()); + unsafe { + channel::write_descriptor( + &mut linked_descriptor, + &mut source, + &mut sercom_ptr, + // Add a null descriptor pointer to end the transfer. + core::ptr::null_mut(), + ); + } + + (None, Some(&mut linked_descriptor)) + } + }; + + let rx = self.spi._rx_channel.as_mut(); + let tx = self.spi._tx_channel.as_mut(); + + let mut write = SharedSliceBuffer::from_slice(write); + + // SAFETY: We make sure that any DMA transfer is complete or stopped before + // returning. The order of operations is important; the RX transfer + // must be ready to receive before the TX transfer is initiated. + let (rx_result, tx_result) = unsafe { + futures::join!( + read_dma_linked::<_, _, S>(rx, sercom_ptr.clone(), &mut read, read_link), + write_dma_linked::<_, _, S>(tx, sercom_ptr, &mut write, write_link) + ) + }; + + // Check for overflows or DMA errors + self.flush_tx_rx().await?; + rx_result.and(tx_result)?; + Ok(()) + } + + #[inline] + async fn transfer_in_place(&mut self, words: &mut [C::Word]) -> Result<(), Self::Error> { + // Safefy: Aliasing the buffer is only safe because the DMA read will always be + // lagging one word behind the write, so they don't overlap on the same memory. + // It's preferable to use two `SharedSliceBuffer`s here; using the `words` slice + // directly as a buffer could potentially cause UB issues if not careful when + // aliasing, as it could be easy to create two `&mut` references pointing to the + // same buffer. `read_buf` and `write_buf` may only be read/written to by the + // DMAC, otherwise an `UnsafeCell` would be necessary. + unsafe { + let mut read_buf = SharedSliceBuffer::from_slice_unchecked(words); + let mut write_buf = SharedSliceBuffer::from_slice(words); + self.transfer_blocking(&mut read_buf, &mut write_buf).await + } + } + + #[inline] + async fn flush(&mut self) -> Result<(), Self::Error> { + // Wait for all transactions to complete, ignoring buffer overflow errors. + self.flush_tx().await; + Ok(()) + } +} + +/// [`embedded_io::Write`] implementation for [`Transmit`] [`SpiFuture`]s in +/// either [`Slave`] or [`MasterMode`], using DMA transfers. +impl embedded_io_async::Write for SpiFuture, D, R, T> +where + P: ValidPads, + M: OpMode, + Z: Size + 'static, + Config: ValidConfig, + D: Transmit, + S: Sercom, + T: AnyChannel, +{ + async fn write(&mut self, buf: &[u8]) -> Result { + SpiFuture::write_dma(self, buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.flush_tx().await; + Ok(()) + } +} + +/// [`embedded_io::Read`] implementation for [`Receive`] [`SpiFuture`]s in +/// [`MasterMode`], using DMA transfers. +impl embedded_io_async::Read for SpiFuture, D, R, T> +where + P: ValidPads, + M: MasterMode, + Z: Size + 'static, + Config: ValidConfig, + D: Receive, + S: Sercom, + R: AnyChannel, + T: AnyChannel, +{ + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.read_dma_master(buf).await?; + Ok(buf.len()) + } +} + +/// [`embedded_io::Read`] implementation for [`Receive`] [`SpiFuture`]s in +/// [`Slave`] mode, using DMA transfers. +impl embedded_io_async::Read for SpiFuture, D, R, T> +where + P: ValidPads, + Z: Size + 'static, + Config: ValidConfig, + D: Receive, + S: Sercom, + R: AnyChannel, +{ + async fn read(&mut self, mut buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + // In Slave mode, RX words can come in even if we haven't sent anything. This + // means some words can arrive asynchronously while we weren't looking (similar + // to UART RX). We need to check if we haven't missed any. + self.flush_rx().await?; + let sercom_ptr = self.spi.sercom_ptr(); + let rx = self.spi._rx_channel.as_mut(); + + // SAFETY: We make sure that any DMA transfer is complete or stopped before + // returning. + let result = read_dma::<_, _, S>(rx, sercom_ptr.clone(), &mut buf).await; + + // Check for overflows or DMA errors + self.flush_rx().await?; + result?; + Ok(buf.len()) + } +} diff --git a/hal/src/sercom/spi/async_api/mod.rs b/hal/src/sercom/spi/async_api/mod.rs new file mode 100644 index 00000000000..701ff4bcb43 --- /dev/null +++ b/hal/src/sercom/spi/async_api/mod.rs @@ -0,0 +1,437 @@ +use crate::{ + async_hal::interrupts::{Binding, Handler, InterruptSource}, + sercom::{ + spi::{ + Capability, Config, DataWidth, Duplex, Error, Flags, MasterMode, OpMode, Rx, Size, Spi, + Tx, ValidConfig, ValidPads, + }, + Sercom, + }, + typelevel::NoneT, +}; +use atsamd_hal_macros::hal_macro_helper; +use core::{marker::PhantomData, task::Poll}; +use embedded_hal_async::spi::{ErrorType, SpiBus}; +use num_traits::{AsPrimitive, PrimInt}; + +#[cfg(feature = "dma")] +mod dma; +#[cfg(feature = "dma")] +pub use dma::*; + +use super::{Receive, Slave, Transmit}; + +/// Interrupt handler for async SPI operarions +pub struct InterruptHandler { + _private: (), + _sercom: PhantomData, +} + +impl crate::typelevel::Sealed for InterruptHandler {} + +impl Handler for InterruptHandler { + #[inline] + #[hal_macro_helper] + unsafe fn on_interrupt() { + unsafe { + let mut peripherals = crate::pac::Peripherals::steal(); + + #[hal_cfg(any("sercom0-d11", "sercom0-d21"))] + let spi = S::reg_block(&mut peripherals).spi(); + #[hal_cfg("sercom0-d5x")] + 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).intersects(flags_pending) { + spi.intenclr().write(|w| w.bits(flags_pending.bits())); + S::rx_waker().wake(); + } + + if (Flags::TX & enabled_flags).intersects(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`](crate::sercom::spi::Config::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 } + } +} + +/// `async` version of [`Spi`]. +/// +/// Create this struct by calling [`Spi::into_future`](Spi::into_future). +pub struct SpiFuture +where + C: ValidConfig, + A: Capability, +{ + spi: Spi, +} + +#[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; + +impl SpiFuture +where + C: ValidConfig, + A: Capability, + S: Sercom, +{ + #[inline] + async fn wait_flags(&mut self, flags_to_wait: Flags) -> Result<(), Error> { + let flags_to_wait = flags_to_wait | Flags::ERROR; + + 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.check_and_clear_error(maybe_pending)); + } + } + + 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(self.spi.check_and_clear_error(maybe_pending)) + } + }) + .await?; + + Ok(()) + } +} + +impl SpiFuture +where + C: ValidConfig, + D: Capability, +{ + /// Return the underlying [`Spi`]. + pub fn free(self) -> Spi { + self.spi + } +} + +impl SpiFuture, D, R, NoneT> +where + Config: ValidConfig, + P: ValidPads, + M: OpMode, + C: Size + 'static, + C::Word: PrimInt + AsPrimitive, + D: Transmit, + DataWidth: AsPrimitive, + S: Sercom, +{ + /// Write words from a buffer asynchronously, word by word + #[inline] + pub async fn write(&mut self, words: &[C::Word]) -> Result<(), Error> { + if words.is_empty() { + return Ok(()); + } + // 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, D, NoneT, T> +where + Config: ValidConfig, + P: ValidPads, + M: MasterMode, + C: Size + 'static, + C::Word: PrimInt + AsPrimitive, + D: Receive, + 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> { + if buffer.is_empty() { + return Ok(()); + } + + let nop_word = self.spi.config.as_ref().nop_word.as_(); + for byte in buffer.iter_mut() { + *byte = self.transfer_word_in_place(nop_word).await?; + } + + Ok(()) + } +} + +impl SpiFuture, D, R, T> +where + Config: ValidConfig, + P: ValidPads, + M: OpMode, + C: Size + 'static, + C::Word: PrimInt + AsPrimitive, + DataWidth: AsPrimitive, + D: Capability, + 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?; + unsafe { + self.spi.write_data(to_send.as_()); + } + + self.flush_rx().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.spi.config.as_ref().nop_word.as_(); + 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(()) + } + + /// Wait on a TXC while ignoring buffer overflow errors. + #[inline] + async fn flush_tx(&mut self) { + // Ignore buffer overflow errors + let _ = self.wait_flags(Flags::TXC).await; + } + + /// Wait on RXC flag + #[inline] + async fn flush_rx(&mut self) -> Result<(), Error> { + self.wait_flags(Flags::RXC).await + } + + /// Wait on TXC and RXC flags + #[inline] + #[cfg(feature = "dma")] + async fn flush_tx_rx(&mut self) -> Result<(), Error> { + self.wait_flags(Flags::TXC | Flags::RXC).await + } +} + +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, +{ + #[inline] + fn as_mut(&mut self) -> &mut Spi { + &mut self.spi + } +} + +impl ErrorType for SpiFuture +where + C: ValidConfig, + A: Capability, +{ + type Error = Error; +} + +impl embedded_io::ErrorType for SpiFuture +where + C: ValidConfig, + A: Capability, +{ + type Error = Error; +} + +impl SpiBus for SpiFuture, Duplex> +where + Config: ValidConfig, + P: ValidPads, + M: MasterMode, + C: Size + 'static, + C::Word: PrimInt + AsPrimitive, + DataWidth: AsPrimitive, + S: Sercom, +{ + async fn read(&mut self, words: &mut [C::Word]) -> Result<(), Self::Error> { + self.read(words).await + } + + async fn write(&mut self, words: &[C::Word]) -> Result<(), Self::Error> { + self.write(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(()) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + // Wait for all transactions to complete, ignoring buffer overflow errors. + self.flush_tx().await; + Ok(()) + } +} + +/// [`embedded_io::Write`] implementation for [`Transmit`] [`SpiFuture`]s in +/// either [`Slave`] or [`MasterMode`]. +impl embedded_io_async::Write for SpiFuture, D, R, NoneT> +where + Config: ValidConfig, + P: ValidPads, + M: OpMode, + Z: Size + 'static, + D: Transmit, + S: Sercom, +{ + async fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf).await?; + Ok(buf.len()) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.flush_tx().await; + Ok(()) + } +} + +/// [`embedded_io::Read`] implementation for [`Receive`] [`SpiFuture`]s in +/// [`MasterMode`]. +impl embedded_io_async::Read for SpiFuture, D, NoneT, T> +where + P: ValidPads, + M: MasterMode, + Z: Size + 'static, + Config: ValidConfig, + D: Receive, + S: Sercom, +{ + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.read(buf).await?; + Ok(buf.len()) + } +} + +/// [`embedded_io::Read`] implementation for [`Receive`] [`SpiFuture`]s in +/// [`Slave`] mode. +impl embedded_io_async::Read for SpiFuture, D, NoneT, T> +where + P: ValidPads, + Z: Size + 'static, + Config: ValidConfig, + D: Receive, + S: Sercom, +{ + async fn read(&mut self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + for word in buf.iter_mut() { + self.flush_rx().await?; + *word = unsafe { self.spi.read_data().as_() }; + } + + Ok(buf.len()) + } +} diff --git a/hal/src/sercom/spi/impl_ehal/dma.rs b/hal/src/sercom/spi/impl_ehal/dma.rs index a94fb275086..8c2e0a3ae80 100644 --- a/hal/src/sercom/spi/impl_ehal/dma.rs +++ b/hal/src/sercom/spi/impl_ehal/dma.rs @@ -6,51 +6,14 @@ use crate::dmac::{channel, sram::DmacDescriptor, AnyChannel, Beat, Buffer, Ready use crate::ehal::spi::SpiBus; use crate::sercom::dma::{ read_dma, read_dma_linked, write_dma, write_dma_linked, SercomPtr, SharedSliceBuffer, + SinkSourceBuffer, }; -use atsamd_hal_macros::hal_macro_helper; use super::{ Capability, Config, DataWidth, Duplex, Error, MasterMode, OpMode, Receive, Sercom, Size, Slave, Spi, Transmit, ValidConfig, ValidPads, Word, }; -struct SinkSourceBuffer<'a, T: Beat> { - word: &'a mut T, - length: usize, -} - -/// Sink/source buffer to use for unidirectional SPI-DMA transfers. -/// -/// When reading/writing from a [`Duplex`] [`Spi`] 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<'a, T: Beat> SinkSourceBuffer<'a, T> { - fn new(word: &'a mut T, length: usize) -> Self { - Self { word, length } - } -} -unsafe impl Buffer for SinkSourceBuffer<'_, T> { - type Beat = T; - #[inline] - fn incrementing(&self) -> bool { - false - } - - #[inline] - fn buffer_len(&self) -> usize { - self.length - } - - #[inline] - fn dma_ptr(&mut self) -> *mut Self::Beat { - self.word as *mut _ - } -} - impl Spi, D, R, T> where P: ValidPads, @@ -61,7 +24,7 @@ where Z::Word: Beat, { #[inline] - pub(super) fn sercom_ptr(&self) -> SercomPtr { + pub(in super::super) fn sercom_ptr(&self) -> SercomPtr { SercomPtr(self.config.regs.spi().data().as_ptr() as *mut _) } } @@ -88,7 +51,7 @@ where self.config.as_mut().regs.rx_disable(); let sercom_ptr = self.sercom_ptr(); - let tx = self.tx_channel.as_mut(); + let tx = self._tx_channel.as_mut(); let mut words = crate::sercom::dma::SharedSliceBuffer::from_slice(buf); // SAFETY: We make sure that any DMA transfer is complete or stopped before @@ -110,7 +73,7 @@ where self.config.as_mut().regs.rx_enable(); } - self.tx_channel.as_mut().xfer_success()?; + self._tx_channel.as_mut().xfer_success()?; self.flush_tx(); Ok(buf.len()) } @@ -136,8 +99,8 @@ where source: &mut Source, ) -> Result<(), Error> { let sercom_ptr = self.sercom_ptr(); - let rx = self.rx_channel.as_mut(); - let tx = self.tx_channel.as_mut(); + let rx = self._rx_channel.as_mut(); + let tx = self._tx_channel.as_mut(); // SAFETY: We make sure that any DMA transfer is complete or stopped before // returning. The order of operations is important; the RX transfer @@ -157,10 +120,10 @@ where // Check for overflows or DMA errors self.flush_tx_rx()?; - self.rx_channel + self._rx_channel .as_mut() .xfer_success() - .and(self.tx_channel.as_mut().xfer_success())?; + .and(self._tx_channel.as_mut().xfer_success())?; Ok(()) } @@ -190,7 +153,6 @@ where R: AnyChannel, T: AnyChannel, { - #[hal_macro_helper] #[inline] fn read(&mut self, words: &mut [C::Word]) -> Result<(), Self::Error> { self.read_dma_master(words) @@ -275,8 +237,8 @@ where } }; - let rx = self.rx_channel.as_mut(); - let tx = self.tx_channel.as_mut(); + let rx = self._rx_channel.as_mut(); + let tx = self._tx_channel.as_mut(); let mut write = SharedSliceBuffer::from_slice(write); @@ -298,10 +260,10 @@ where // Check for overflows or DMA errors self.flush_tx_rx()?; - self.rx_channel + self._rx_channel .as_mut() .xfer_success() - .and(self.tx_channel.as_mut().xfer_success())?; + .and(self._tx_channel.as_mut().xfer_success())?; Ok(()) } @@ -390,7 +352,7 @@ where // to UART RX). We need to check if we haven't missed any. self.flush_rx()?; let sercom_ptr = self.sercom_ptr(); - let rx = self.rx_channel.as_mut(); + let rx = self._rx_channel.as_mut(); // SAFETY: We make sure that any DMA transfer is complete or stopped before // returning. @@ -407,7 +369,7 @@ where // Check for overflows or DMA errors self.flush_rx()?; - self.rx_channel.as_mut().xfer_success()?; + self._rx_channel.as_mut().xfer_success()?; Ok(buf.len()) } } diff --git a/hal/src/sercom/spi_future.rs b/hal/src/sercom/spi_future.rs index 582dee59068..af76a949b61 100644 --- a/hal/src/sercom/spi_future.rs +++ b/hal/src/sercom/spi_future.rs @@ -1,4 +1,3 @@ -#![allow(rustdoc::broken_intra_doc_links)] //! A [`Future`]-like interface for SPI transactions //! //! An [`SpiFuture`] takes ownership of an [`Spi`] `struct` and a `[u8]`-like @@ -532,7 +531,7 @@ where /// Consume the [`SpiFuture`] and free its components /// /// If the transaction is complete, this function will consume the - /// [`SpiFuture`] and return the [`Spi`](super::spi::Spi) object, the + /// [`SpiFuture`] and return the [`Spi`] object, the /// buffer, and the SS pin, if present. /// /// If the transaction is not complete, it will return `Err(self)`. diff --git a/hal/src/sercom/uart.rs b/hal/src/sercom/uart.rs index 8e3d5516664..102f16a1d71 100644 --- a/hal/src/sercom/uart.rs +++ b/hal/src/sercom/uart.rs @@ -381,6 +381,124 @@ //! let (chan1, rx, rx_buffer) = rx_dma.wait(); //! ``` //! +//! # `async` operation async +//! +//! A [`Uart`] can be used for `async` operations. Configuring a [`Uart`] in +//! async mode is relatively simple: +//! +//! * Bind the corresponding `SERCOM` interrupt source to the UART +//! [`InterruptHandler`] (refer to the module-level [`async_hal`] +//! documentation for more information). +//! * Turn a previously configured [`Uart`] into a [`UartFuture`] by calling +//! [`Uart::into_future`] +//! * Optionally, add DMA channels to RX, TX or both using +//! [`UartFuture::with_rx_dma_channel`] and +//! [`UartFuture::with_tx_dma_channel`]. The API is exactly the same whether +//! DMA channels are used or not. +//! * Use the provided async methods for reading or writing to the UART +//! peripheral. +//! +//! `UartFuture` implements `AsRef` and `AsMut` so +//! that it can be reconfigured using the regular [`Uart`] methods. It also +//! exposes a [`split`](UartFuture::split) method to split it into its RX and TX +//! parts. +//! +//! ## Considerations when using `async` [`Uart`] with DMA async dma +//! +//! * An [`Uart`] struct must be turned into an [`UartFuture`] by calling +//! [`Uart::into_future`] before calling `with_dma_channel`. The DMA channel +//! itself must also be configured in async mode by using +//! [`DmaController::into_future`](crate::dmac::DmaController::into_future). +//! If a DMA channel is added to the [`Uart`] struct before it is turned into +//! an [`UartFuture`], it will not be able to use DMA in async mode. +//! +//! ``` +//! // This will work +//! let uart = uart.into_future().with_dma_channels(rx_channel, tx_channel); +//! +//! // This won't +//! let uart = uart.with_dma_channels(rx_channel, tx_channel).into_future(); +//! ``` +//! +//! ### Safety considerations +//! +//! In `async` mode, an SPI+DMA transfer does not require `'static` source and +//! destination buffers. This, in theory, makes its use `unsafe`. However it is +//! marked as safe for better ergonomics. +//! +//! This means that, as an user, you **must** ensure that the [`Future`]s +//! returned by the [`read`](UartFuture::read) and [`write`](UartFuture::write) +//! methods may never be forgotten through [`forget`] or by wrapping them with a +//! [`ManuallyDrop`]. +//! +//! The returned futures implement [`Drop`] and will automatically stop any +//! ongoing transfers; this guarantees that the memory occupied by the +//! now-dropped buffers may not be corrupted by running transfers. +//! +//! This means that using functions like [`futures::select_biased`] to implement +//! timeouts is safe; transfers will be safely cancelled if the timeout expires. +//! +//! 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. +//! * Dropping an incomplete transfer *without running its destructor* is +//! **unsound** and will trigger undefined behavior. +//! +//! ```ignore +//! async fn always_ready() {} +//! +//! let mut buffer = [0x00; 10]; +//! +//! // This is completely safe +//! uart.read(&mut buffer).await?; +//! +//! // This is also safe: we launch a transfer, which is then immediately cancelled +//! futures::select_biased! { +//! _ = uart.read(&mut buffer)?, +//! _ = always_ready(), +//! } +//! +//! // This, while contrived, is also safe. +//! { +//! use core::future::Future; +//! +//! let future = uart.read(&mut buffer); +//! futures::pin_mut!(future); +//! // Assume ctx is a `core::task::Context` given out by the executor. +//! // The future is polled, therefore starting the transfer +//! future.as_mut().poll(ctx); +//! +//! // Future is dropped here - transfer is cancelled. +//! } +//! +//! // DANGER: This is an example of undefined behavior +//! { +//! use core::future::Future; +//! use core::ops::DerefMut; +//! +//! let future = core::mem::ManuallyDrop::new(uart.read(&mut buffer)); +//! futures::pin_mut!(future); +//! // To actually make this example compile, we would need to wrap the returned +//! // future from `i2c.read()` in a newtype that implements Future, because we +//! // can't actually call as_mut() without being able to name the type we want +//! // to deref to. +//! let future_ref: &mut SomeNewTypeFuture = &mut future.as_mut(); +//! future.as_mut().poll(ctx); +//! +//! // Future is NOT dropped here - transfer is not cancelled, resulting un UB. +//! } +//! ``` +//! +//! As you can see, unsoundness is relatively hard to come by - however, caution +//! should still be exercised. +//! //! [`enable`]: Config::enable //! [`disable`]: Uart::disable //! [`reconfigure`]: Uart::reconfigure @@ -398,6 +516,11 @@ //! [`send_with_dma`]: Self::send_with_dma //! [`dmac::Transfer`]: crate::dmac::Transfer //! [`Channel`]: crate::dmac::Channel +//! [`async_hal`]: crate::async_hal +//! [`forget`]: core::mem::forget +//! [`ManuallyDrop`]: core::mem::ManuallyDrop +//! [`Future`]: core::future::Future +//! [`poll`]: core::future::Future::poll use atsamd_hal_macros::{hal_cfg, hal_module}; @@ -423,6 +546,11 @@ pub use config::*; pub mod impl_ehal; +#[cfg(feature = "async")] +mod async_api; +#[cfg(feature = "async")] +pub use async_api::*; + use crate::{ sercom::pad::SomePad, typelevel::{NoneT, Sealed}, @@ -522,6 +650,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 {} @@ -538,6 +670,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 {} @@ -554,6 +687,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 {} @@ -570,6 +704,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 {} @@ -874,13 +1009,15 @@ where } #[cfg(feature = "dma")] -impl Uart +impl Uart where C: ValidConfig, D: Capability, - R: crate::dmac::AnyChannel, + R: crate::dmac::AnyChannel, + S: crate::dmac::ReadyChannel, { - /// Reclaim the RX DMA channel + /// Reclaim the RX DMA channel. Subsequent RX operations will no longer use + /// DMA. pub fn take_rx_channel(self) -> (Uart, R) { ( Uart { @@ -895,13 +1032,15 @@ where } #[cfg(feature = "dma")] -impl Uart +impl Uart where C: ValidConfig, D: Capability, - T: crate::dmac::AnyChannel, + T: crate::dmac::AnyChannel, + S: crate::dmac::ReadyChannel, { - /// Reclaim the TX DMA channel + /// Reclaim the TX DMA channel. Subsequent TX operations will no longer use + /// DMA. pub fn take_tx_channel(self) -> (Uart, T) { ( Uart { @@ -1046,13 +1185,14 @@ where #[inline] pub fn flush_rx_buffer(&mut self) { // TODO Is this a hardware bug??? - /* - usart.ctrlb.modify(|_, w| w.rxen().clear_bit()); - while usart.syncbusy.read().ctrlb().bit() || usart.ctrlb.read().rxen().bit_is_set() {} - usart.ctrlb.modify(|_, w| w.rxen().set_bit()); - while usart.syncbusy.read().ctrlb().bit() || usart.ctrlb.read().rxen().bit_is_clear() {} - */ + // usart.ctrlb.modify(|_, w| w.rxen().clear_bit()); + // while usart.syncbusy.read().ctrlb().bit() || + // usart.ctrlb.read().rxen().bit_is_set() {} + + // usart.ctrlb.modify(|_, w| w.rxen().set_bit()); + // while usart.syncbusy.read().ctrlb().bit() || + // usart.ctrlb.read().rxen().bit_is_clear() {} for _ in 0..=2 { let _data = unsafe { self.config.as_mut().registers.read_data() }; diff --git a/hal/src/sercom/uart/async_api.rs b/hal/src/sercom/uart/async_api.rs new file mode 100644 index 00000000000..667959be61c --- /dev/null +++ b/hal/src/sercom/uart/async_api.rs @@ -0,0 +1,511 @@ +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 atsamd_hal_macros::hal_macro_helper; +use core::{marker::PhantomData, task::Poll}; +use num_traits::AsPrimitive; + +/// Interrupt handler for async UART operarions +pub struct InterruptHandler { + _private: (), + _sercom: PhantomData, +} + +impl crate::typelevel::Sealed for InterruptHandler {} + +impl Handler for InterruptHandler { + #[inline] + #[hal_macro_helper] + unsafe fn on_interrupt() { + unsafe { + let mut peripherals = crate::pac::Peripherals::steal(); + + #[hal_cfg(any("sercom0-d11", "sercom0-d21"))] + let uart = S::reg_block(&mut peripherals).usart(); + #[hal_cfg("sercom0-d5x")] + let uart = S::reg_block(&mut peripherals).usart_int(); + + let flags_pending = Flags::from_bits_retain(uart.intflag().read().bits()); + let enabled_flags = Flags::from_bits_retain(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`], + /// [`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 } + } +} + +/// `async` version of a [`Uart`]. +/// +/// Create this struct by calling [`Uart::into_future`](Uart::into_future). +pub struct UartFuture +where + C: ValidConfig, + D: Capability, +{ + uart: Uart, +} + +/// Convenience type for a [`UartFuture`] with RX and TX capabilities +pub type UartFutureDuplex = UartFuture; + +/// Convenience type for a RX-only [`UartFuture`]. +pub type UartFutureRx = UartFuture; + +/// Convenience type for the RX half of a [`Duplex`] [`UartFuture`]. +pub type UartFutureRxDuplex = 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; + +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.uart.rx_channel, + tx_channel: NoneT, + }, + }, + UartFuture { + uart: Uart { + config, + capability: PhantomData, + rx_channel: NoneT, + tx_channel: self.uart.tx_channel, + }, + }, + ) + } + + /// 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.uart.rx_channel, + tx_channel: tx.uart.tx_channel, + }, + } + } +} + +impl UartFuture +where + C: ValidConfig, + D: SingleOwner, +{ + /// Return the underlying [`Uart`]. + pub fn free(self) -> Uart { + self.uart + } +} + +impl embedded_io::ErrorType for UartFuture +where + C: ValidConfig, + D: Capability, +{ + type Error = Error; +} + +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 & Flags::from_bits_retain(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.config.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.config.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, +{ + /// Use a DMA channel for receiving words on the RX line + #[cfg(feature = "dma")] + #[inline] + pub fn with_rx_dma_channel>( + self, + rx_channel: Chan, + ) -> UartFuture { + UartFuture { + uart: Uart { + config: self.uart.config, + capability: PhantomData, + rx_channel, + tx_channel: self.uart.tx_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().check_bus_error()?; + 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 embedded_io_async::Read for UartFuture +where + C: ValidConfig, + D: Receive, + S: Sercom, +{ + #[inline] + async fn read(&mut self, buffer: &mut [u8]) -> Result { + self.read(buffer).await.map_err(|(e, _)| e)?; + Ok(buffer.len()) + } +} + +impl UartFuture +where + C: ValidConfig, + D: Transmit, + S: Sercom, +{ + /// Use a DMA channel for sending words on the TX line + #[cfg(feature = "dma")] + #[inline] + pub fn with_tx_dma_channel>( + self, + tx_channel: Chan, + ) -> UartFuture { + UartFuture { + uart: Uart { + config: self.uart.config, + capability: PhantomData, + rx_channel: self.uart.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 embedded_io_async::Write for UartFuture +where + C: ValidConfig, + D: Transmit, + S: Sercom, +{ + #[inline] + async fn write(&mut self, buffer: &[u8]) -> Result { + self.write(buffer).await; + Ok(buffer.len()) + } +} + +impl AsRef> for UartFuture +where + C: ValidConfig, + D: Capability, + S: Sercom, +{ + fn as_ref(&self) -> &Uart { + &self.uart + } +} + +impl AsMut> for UartFuture +where + C: ValidConfig, + D: Capability, + S: Sercom, +{ + fn as_mut(&mut self) -> &mut Uart { + &mut self.uart + } +} + +#[cfg(feature = "dma")] +mod dma { + use super::*; + use crate::{ + dmac::{AnyChannel, Beat, Channel, ReadyFuture}, + sercom::dma::{ + async_dma::{read_dma, write_dma}, + SharedSliceBuffer, + }, + }; + + /// 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, Channel>; + + /// 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>; + + /// 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>; + + /// 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>; + + /// 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, + D: Capability, + R: AnyChannel, + { + /// Reclaim the RX DMA channel. Subsequent RX operations will no longer + /// use DMA. + pub fn take_rx_channel(self) -> (UartFuture, R) { + let (uart, channel) = self.uart.take_rx_channel(); + (UartFuture { uart }, channel) + } + } + + impl UartFuture + where + C: ValidConfig, + D: Capability, + T: AnyChannel, + { + /// Reclaim the TX DMA channel. Subsequent TX operations will no longer + /// use DMA. + pub fn take_tx_channel(self) -> (UartFuture, T) { + let (uart, channel) = self.uart.take_tx_channel(); + (UartFuture { uart }, channel) + } + } + + 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, mut 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.uart.sercom_ptr(); + + read_dma::<_, _, S>(&mut self.uart.rx_channel, uart_ptr, &mut words).await?; + Ok(()) + } + } + + impl embedded_io_async::Read for UartFuture + where + C: ValidConfig, + D: Receive, + S: Sercom + 'static, + DataReg: AsPrimitive, + R: AnyChannel, + { + #[inline] + async fn read(&mut self, words: &mut [u8]) -> Result { + self.read(words).await?; + Ok(words.len()) + } + } + + 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]) -> 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.uart.sercom_ptr(); + + let mut words = SharedSliceBuffer::from_slice(words); + write_dma::<_, _, S>(&mut self.uart.tx_channel, uart_ptr, &mut words).await?; + self.wait_flags(Flags::TXC).await; + Ok(()) + } + } + + impl embedded_io_async::Write for UartFuture + where + C: ValidConfig, + D: Transmit, + S: Sercom + 'static, + T: AnyChannel, + { + #[inline] + async fn write(&mut self, words: &[u8]) -> Result { + self.write(words).await?; + Ok(words.len()) + } + } +} + +#[cfg(feature = "dma")] +pub use dma::*; diff --git a/hal/src/sercom/uart/flags.rs b/hal/src/sercom/uart/flags.rs index ebefcbe9708..b72c0fa7057 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 = Self::from_bits_retain(RX_FLAG_MASK); + + /// [`Flags`] which can be used for transmitting + pub const TX: Self = Self::from_bits_retain(TX_FLAG_MASK); +} + //============================================================================= // Status flags //============================================================================= diff --git a/hal/src/sercom/uart/impl_ehal.rs b/hal/src/sercom/uart/impl_ehal.rs index ff9da615558..1d3f0daef7c 100644 --- a/hal/src/sercom/uart/impl_ehal.rs +++ b/hal/src/sercom/uart/impl_ehal.rs @@ -202,7 +202,7 @@ mod dma { D: Capability, W: Beat, { - fn sercom_ptr(&self) -> SercomPtr { + pub(in super::super) fn sercom_ptr(&self) -> SercomPtr { SercomPtr(self.data_ptr()) } } diff --git a/hal/src/util.rs b/hal/src/util.rs new file mode 100644 index 00000000000..f99ced0a0fc --- /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) + } + } + } +}