diff --git a/boards/pyportal/.cargo/config.toml b/boards/pyportal/.cargo/config.toml index 4cf66548fe50..89f72492afdc 100644 --- a/boards/pyportal/.cargo/config.toml +++ b/boards/pyportal/.cargo/config.toml @@ -1,7 +1,7 @@ # vim:ft=toml: [target.thumbv7em-none-eabihf] -runner = "hf2 elf" -# runner = 'probe-rs run --chip ATSAMD51J20A' +# runner = "hf2 elf" +runner = 'probe-rs run --chip ATSAMD51J20A --speed=2000' [build] target = "thumbv7em-none-eabihf" diff --git a/boards/pyportal/Cargo.toml b/boards/pyportal/Cargo.toml index f32f7611f0fc..1eb50932744a 100644 --- a/boards/pyportal/Cargo.toml +++ b/boards/pyportal/Cargo.toml @@ -19,7 +19,8 @@ version = "0.7.5" optional = true [dependencies.atsamd-hal] -version = "0.21.0" +# TODO(stillinbeta): set to release version +path = "../../hal" default-features = false [dependencies.display-interface-parallel-gpio] @@ -34,27 +35,58 @@ optional=true version = "0.3.2" optional = true +[dependencies.embassy-nina] +version = "0.1" +optional = true +features = [] +git = "https://github.com/stillinbeta/embassy-nina.git" +rev = "ce3ecde85e0fdd0cfe81f40478f46322de099abf" + [dev-dependencies] panic-halt = "0.2" panic-semihosting = "0.5" smart-leds = "~0.3" usbd-serial = "0.2.2" embedded-graphics = "0.8.1" +embassy-time = "0.5" +embassy-sync = "0.7.2" +critical-section = "1.2.0" +defmt-rtt = "1.2" +defmt = "1.1" +panic-probe = "1.0" +embedded-nal-async = "0.9.0" + +[dev-dependencies.embassy-executor] +version = "0.10" +features = ["platform-cortex-m", "executor-thread"] [dev-dependencies.cortex-m] features = ["critical-section-single-core"] version = "0.7.5" +[dev-dependencies.cortex-m-semihosting] +version = "0.5" + [dev-dependencies.ws2812-timer-delay] version = "~0.3" +[dev-dependencies.reqwless] +version = "0.14.0" +default-features = false +features = ["defmt"] + [features] # ask the HAL to enable atsamd51j support -default = ["rt", "atsamd-hal/samd51j"] +dma = ["atsamd-hal/dma"] +default = ["rt", "atsamd-hal/samd51j", "atsamd-hal/undoc-features"] rt = ["cortex-m-rt", "atsamd-hal/samd51j-rt"] usb = ["atsamd-hal/usb", "usb-device"] display = ["display-interface-parallel-gpio", "ili9341"] use_semihosting = [] +rtic = ["atsamd-hal/rtic"] +embassy-time = ["atsamd-hal/embassy-time"] +wifi = ["atsamd-hal/async", "embassy-nina"] +defmt = ["atsamd-hal/defmt", "embassy-nina/defmt"] # add defmt codegen link # for cargo flash [package.metadata] @@ -73,3 +105,11 @@ name = "neopixel_rainbow" [[example]] name = "usb_echo" required-features = ["usb"] + +[[example]] +name = "embassy_timer" +required-features = ["embassy-time"] + +[[example]] +name = "embassy_wifi" +required-features = ["wifi", "embassy-time", "defmt"] diff --git a/boards/pyportal/build.rs b/boards/pyportal/build.rs index 4bed4688f2c0..33ce4fbe5dd3 100644 --- a/boards/pyportal/build.rs +++ b/boards/pyportal/build.rs @@ -13,4 +13,8 @@ fn main() { println!("cargo:rerun-if-changed=memory.x"); } println!("cargo:rerun-if-changed=build.rs"); + + if env::var_os("CARGO_FEATURE_DEFMT").is_some() { + println!("cargo::rustc-link-arg=-Tdefmt.x") + } } diff --git a/boards/pyportal/examples/blinky_interrupt.rs b/boards/pyportal/examples/blinky_interrupt.rs new file mode 100644 index 000000000000..a1c55f8f8d09 --- /dev/null +++ b/boards/pyportal/examples/blinky_interrupt.rs @@ -0,0 +1,163 @@ +//! Turn on and off with an LED +#![no_std] +#![no_main] + +use core::{cell::RefCell, mem}; + +use atsamd_hal::ehal::digital::{OutputPin, StatefulOutputPin}; +use bsp::{ + pac::{interrupt, CorePeripherals, Interrupt, Peripherals}, + pin_alias, RedLed, +}; +use cortex_m::{asm, peripheral::NVIC}; +use cortex_m_rt::entry; +use critical_section::Mutex; +use pyportal as bsp; +use rtt_target::rprintln; + +use panic_semihosting as _; + +macro_rules! sync_wait { + ($mode0:expr, $register:ident) => { + while $mode0.syncbusy().read().$register().bit_is_set() {} + }; +} + +static BACKLIGHT_PIN: Mutex> = Mutex::new(RefCell::new(unsafe { mem::zeroed() })); + +#[entry] +fn main() -> ! { + rtt_target::rtt_init_print!(); + + let peripherals = Peripherals::take().unwrap(); + let _core = CorePeripherals::take().unwrap(); + + let pins = bsp::Pins::new(peripherals.port); + let red_led: bsp::RedLed = pin_alias!(pins.red_led).into(); + + critical_section::with(|cs| { + let _ = BACKLIGHT_PIN.replace(cs, red_led); + BACKLIGHT_PIN.borrow_ref_mut(cs).set_low().unwrap(); + }); + + rprintln!("starting up"); + + // enable global interrupts + unsafe { + NVIC::mask(Interrupt::RTC); + // core.NVIC.set_priority(Interrupt::RTC, 8); + cortex_m::interrupt::enable(); + }; + + // use the 32k clock + peripherals + .osc32kctrl + .rtcctrl() + .write(|w| w.rtcsel().ulp32k()); + + let mode0 = peripherals.rtc.mode0(); + + // Run RTC when main chip is paused + mode0.dbgctrl().write(|w| w.dbgrun().set_bit()); + + // disable the clock + mode0.ctrla().write(|w| w.enable().clear_bit()); + // write sync for RTC enable + sync_wait!(mode0, enable); + + // trigger a reset + mode0.ctrla().modify(|_, w| w.swrst().set_bit()); + // write sync reset + sync_wait!(mode0, swrst); + + // set the mode + mode0.ctrla().modify(|_, w| w.mode().count32()); + + mode0.ctrla().modify(|_, w| { + // Use 32 bit counter + w.prescaler().div1(); + // The COUNT register requires synchronization when reading. + // Disabling the synchronization will prevent reading valid values from the + // COUNT register. + w.countsync().set_bit(); + w.matchclr().clear_bit(); + w + }); + // write sync for countsync + let init = mode0.count().read().count().bits(); + sync_wait!(mode0, countsync); + // When CTRLA.COUNTSYNC is enabled, the first COUNT value is not correctly + // synchronized and thus it is a wrong value. + + // clear flag + mode0.intflag().write(|w| w.cmp0().set_bit()); + + // wait for count to be ready + sync_wait!(mode0, count); + // read the current count + let count: u32 = mode0.count().read().count().bits(); + // add 5 seconds + let next = count + (5 * 32_768); + rprintln!("count is {}, waking up at {}", count, next); + + mode0.comp(0).write(|w| unsafe { w.comp().bits(next) }); + // wait write + + sync_wait!(mode0, comp0); + + mode0.intflag().write(|w| w.cmp0().set_bit()); + // enable interupt + mode0.intenset().write(|w| w.cmp0().set_bit()); + + // critical_section::with(|cs| { + // BACKLIGHT_PIN.borrow_ref_mut(cs).set_low(); + // }); + + unsafe { + NVIC::unmask(Interrupt::RTC); + } + + // Enable the RTC + mode0.ctrla().modify(|_, w| w.enable().set_bit()); + + // Block to wait for countsync to be correct + while mode0.count().read().count().bits() == init {} + // write sync for RTC enable + sync_wait!(mode0, enable); + + loop { + asm::wfi(); + } +} + +#[interrupt] +fn RTC() { + // unsafe { NVIC::mask(Interrupt::RTC) }; + + critical_section::with(|_cs| { + let peripherals = unsafe { Peripherals::steal() }; + + let mode0 = peripherals.rtc.mode0(); + + // is this actually an RTC compare0 interrupt + if mode0.intflag().read().cmp0().bit_is_set() { + // clear the interrupt bit + + critical_section::with(|cs| { + BACKLIGHT_PIN.borrow_ref_mut(cs).toggle().unwrap(); + }); + + sync_wait!(mode0, count); + let count: u32 = mode0.count().read().count().bits(); + // add 5 seconds + let next = count + (2 * 32_768); + rprintln!("count is {}, waking up at {}", count, next); + + mode0.comp(0).write(|w| unsafe { w.comp().bits(next) }); + // wait write + sync_wait!(mode0, comp0); + mode0.intflag().write(|w| w.cmp0().set_bit()); + } + // unsafe { NVIC::unmask(Interrupt::RTC) }; + }) +} diff --git a/boards/pyportal/examples/embassy_timer.rs b/boards/pyportal/examples/embassy_timer.rs new file mode 100644 index 000000000000..80c3cb9d80ca --- /dev/null +++ b/boards/pyportal/examples/embassy_timer.rs @@ -0,0 +1,52 @@ +#![no_std] +#![no_main] + +#[cfg(feature = "defmt")] +use {defmt_rtt as _, panic_probe as _}; +#[cfg(feature = "use_semihosting")] +use panic_semihosting as _; + +use bsp::{hal, pac, pin_alias}; +use hal::{ + clock::v2::{clock_system_at_reset, osculp32k::OscUlp32k, rtcosc::RtcOsc}, + ehal::digital::StatefulOutputPin, +}; +use pyportal as bsp; + +use embassy_time::{Timer, Duration}; + + + +hal::embassy_time!(Driver); + +#[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 red_led: bsp::RedLed = pin_alias!(pins.red_led).into(); + + // Select the 32khz source + let (_, clocks, tokens) = clock_system_at_reset( + peripherals.oscctrl, + peripherals.osc32kctrl, + peripherals.gclk, + peripherals.mclk, + &mut peripherals.nvmctrl, + ); + + let (osculp32k, _) = OscUlp32k::enable(tokens.osculp32k.osculp32k, clocks.osculp32k_base); + + let (rtc, _) = RtcOsc::enable(tokens.rtcosc, osculp32k); + + // SAFETY: not in a critical section + + unsafe { + Driver::init(rtc); + } + + loop { + red_led.toggle().unwrap(); + Timer::after(Duration::from_millis(200)).await; + } +} diff --git a/boards/pyportal/examples/embassy_wifi.rs b/boards/pyportal/examples/embassy_wifi.rs new file mode 100644 index 000000000000..ab46abd25d66 --- /dev/null +++ b/boards/pyportal/examples/embassy_wifi.rs @@ -0,0 +1,146 @@ +#![no_std] +#![no_main] + +use bsp::{hal, pac}; +use embassy_time::Duration; +use hal::{ + clock::v2::{clock_system_at_reset, osculp32k::OscUlp32k, pclk::Pclk, rtcosc::RtcOsc}, + eic, + sercom::{spi, Sercom2}, + time::Hertz, +}; +use pyportal as bsp; +use reqwless::request::RequestBuilder; + +hal::embassy_time!(Driver); + +hal::bind_multiple_interrupts!(struct SercomIrq { + SERCOM2: [SERCOM2_0, SERCOM2_1, SERCOM2_2, SERCOM2_OTHER] => spi::InterruptHandler; +}); + +hal::bind_interrupts!(struct EicIrq { + EIC_EXTINT_0 => eic::InterruptHandler; +}); + +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_s: embassy_executor::Spawner) { + defmt::println!("Hello world!"); + + let mut peripherals = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + let pins = bsp::Pins::new(peripherals.port); + + // Select the 32khz source + let (mut buses, clocks, tokens) = clock_system_at_reset( + peripherals.oscctrl, + peripherals.osc32kctrl, + peripherals.gclk, + peripherals.mclk, + &mut peripherals.nvmctrl, + ); + + let (osculp32k, _) = OscUlp32k::enable(tokens.osculp32k.osculp32k, clocks.osculp32k_base); + let (rtc, _) = RtcOsc::enable(tokens.rtcosc, osculp32k); + + // SAFETY: not in a critical section + + unsafe { + Driver::init(rtc); + } + + let _spi_clk = buses.apb.enable(tokens.apbs.sercom2); + let (sercom2_pclk, gclk) = Pclk::enable(tokens.pclks.sercom2, clocks.gclk0); + + let (_, _, _, mut mclk) = unsafe { clocks.pac.steal() }; + + let baud = Hertz::Hz(115_200); + + let (miso, mosi, sclk): (pyportal::Miso, pyportal::Mosi, pyportal::Sck) = + (pins.miso.into(), pins.mosi.into(), pins.sck.into()); + let pads = spi::Pads::default().data_in(miso).data_out(mosi).sclk(sclk); + let spi = spi::Config::new(&mclk, peripherals.sercom2, pads, sercom2_pclk.freq()) + .baud(baud.into()) + .spi_mode(spi::MODE_0) + .enable(); + + let (eicclk, _gclk) = Pclk::enable(tokens.pclks.eic, gclk); + + let eic = hal::eic::Eic::new(&mut mclk, &(eicclk.into()), peripherals.eic).split(); + + defmt::println!("trying to init wifi"); + + // warm_up(&mut red_led).await; + + let mut nina = pyportal::wifi( + spi, + SercomIrq, + eic.0, + pins.esp_cs, + pins.esp_busy, + EicIrq, + pins.esp_reset, + pins.esp_gpio0, + ) + .await + .expect("couldn't init wifi"); + + defmt::println!("wifi awake"); + + let mut version = [0_u8; 32]; + + defmt::expect!(nina.get_fw_version(&mut version).await); + defmt::println!( + "Firmware version is {=str}", + str::from_utf8(&version).expect("not a valid string") + ); + + defmt::println!("sending credentials"); + defmt::unwrap!( + nina.connect_wpa( + env!("WIFI_NETWORK").as_bytes(), + env!("WIFI_PASSWORD").as_bytes() + ) + .await + ); + defmt::println!("waiting for connection"); + defmt::unwrap!(nina.wait_for_connected(Duration::from_secs(10)).await); + defmt::println!("connected!"); + + let mutex = embassy_sync::mutex::Mutex::< + embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex, + _, + >::new(nina); + let stack = embassy_nina::nal::NinaStack::new(&mutex); + + let client = reqwless::client::HttpClient::new(&stack, &stack); + + let mut buf = [0_u8; 1024]; + let result = defmt::unwrap!(get_ip(client, &mut buf).await); + + // // loop { + let string = str::from_utf8(&result).unwrap(); + defmt::println!("IP: {}", string); + // // } +} + +async fn get_ip<'a, T, D>( + mut client: reqwless::client::HttpClient<'a, T, D>, + buf: &'a mut [u8], +) -> Result<&'a mut [u8], reqwless::Error> +where + T: embedded_nal_async::TcpConnect + 'a, + D: embedded_nal_async::Dns + 'a, +{ + client + .request(reqwless::request::Method::GET, "http://jsonip.com") + .await? + .content_type(reqwless::headers::ContentType::ApplicationJson) + .host("jsonip.com") + .send(buf) + .await? + .body() + .read_to_end() + .await +} diff --git a/boards/pyportal/memory.x b/boards/pyportal/memory.x index 0d08d118a5ad..9b9df2d6e198 100644 --- a/boards/pyportal/memory.x +++ b/boards/pyportal/memory.x @@ -1,7 +1,7 @@ MEMORY { /* Leave 16k for the default bootloader on the PyPortal */ - FLASH (rx) : ORIGIN = 0x00000000 + 16K, LENGTH = 512K - 16K + FLASH (rx) : ORIGIN = 0x00000000 + 16K, LENGTH = 1024K - 16K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K } _stack_start = ORIGIN(RAM) + LENGTH(RAM); diff --git a/boards/pyportal/src/lib.rs b/boards/pyportal/src/lib.rs index d868d0cf0035..77a88560e9b6 100644 --- a/boards/pyportal/src/lib.rs +++ b/boards/pyportal/src/lib.rs @@ -8,10 +8,10 @@ pub use hal::pac; pub use cortex_m_rt::entry; use hal::clock::GenericClockController; -use hal::sercom::uart; -use hal::sercom::uart::BaudMode; -use hal::sercom::uart::Oversampling; -use hal::sercom::{i2c, spi, IoSet1, IoSet6}; +use hal::sercom::{ + i2c, spi, + uart::{self, BaudMode, Oversampling}, +}; use hal::time::Hertz; pub mod pins; @@ -25,13 +25,19 @@ pub use display::*; #[cfg(feature = "usb")] use hal::usb::{usb_device::bus::UsbBusAllocator, UsbBus}; +#[cfg(feature = "wifi")] +use hal::{ + eic, + sercom::{spi::Duplex, Sercom2}, +}; + hal::bsp_peripherals!( Sercom2 { SpiSercom } Sercom4 { EspUartSercom } Sercom5 { I2cSercom } ); -pub type SpiPads = spi::Pads; +pub type SpiPads = spi::Pads; pub type Spi = spi::Spi, spi::Duplex>; @@ -61,10 +67,49 @@ pub fn spi_master( .enable() } +#[cfg(feature = "wifi")] +/// Initialize embassy-nina to talk to the ESP32 coprocessor +/// see examples/embassy_wifi.rs +pub async fn wifi( + spi: Spi, + spi_interrupt: SI, + ch: hal::eic::Channel, + cs: impl Into, + busy: impl Into, + busy_interrupt: BI, + reset: impl Into, + gpio: impl Into, +) -> Result< + embassy_nina::Nina< + spi::SpiFuture, Duplex>, + EspCs, + eic::ExtInt, + EspReset, + EspGpio, + >, + embassy_nina::Error, +> +where + SI: hal::async_hal::interrupts::Binding< + hal::async_hal::interrupts::SERCOM2, + spi::InterruptHandler, + >, + BI: hal::async_hal::interrupts::Binding< + hal::async_hal::interrupts::EIC_EXTINT_0, + eic::InterruptHandler, + >, +{ + let spi = spi.into_future(spi_interrupt); + let busy2 = ch.with_pin(busy.into()).into_future(busy_interrupt); + let mut nina = embassy_nina::Nina::new(spi, cs.into(), busy2, reset.into(), gpio.into()); + nina.init().await?; + Ok(nina) +} + /// I2C pads for the labelled I2C peripheral /// -/// You can use these pads with other, user-defined [`i2c::Config`]urations. -pub type I2cPads = i2c::Pads; +/// You can use these pads with other, user-defined Durations. +pub type I2cPads = i2c::Pads; /// I2C master for the labelled I2C peripheral /// @@ -94,7 +139,7 @@ pub fn i2c_master( } /// UART Pads for the ESP32 Wi-Fi co-processor -pub type EspUartPads = uart::Pads; +pub type EspUartPads = uart::Pads; /// UART device for the ESP32 Wi-Fi co-processor pub type EspUart = uart::Uart, uart::Duplex>; diff --git a/boards/pyportal/src/pins.rs b/boards/pyportal/src/pins.rs index 4d97459c2110..6126be91f90a 100644 --- a/boards/pyportal/src/pins.rs +++ b/boards/pyportal/src/pins.rs @@ -221,13 +221,15 @@ hal::bsp_pins!( name: esp_cs, aliases: { Reset: EspCsReset, + PushPullOutput: EspCs, } } PB15 { /// Pin ESP GPIO0 name: esp_gpio0, aliases: { - Reset: EspGpio0Reset + Reset: EspGpio0Reset, + PushPullOutput: EspGpio, } } PB16 { @@ -235,6 +237,7 @@ hal::bsp_pins!( name: esp_busy, aliases: { Reset: EspBusyReset, + FloatingInterrupt: EspBusy, } } PB17 { @@ -242,6 +245,7 @@ hal::bsp_pins!( name: esp_reset, aliases: { Reset: EspResetReset, + PushPullOutput: EspReset, } } PA15 { diff --git a/crates.json b/crates.json index c58ab16cbdd8..d5e47ab0c75a 100644 --- a/crates.json +++ b/crates.json @@ -1,6 +1,5 @@ { "boards": { - "atsame54_xpro": { "tier": 1, "build": "cargo build --examples --all-features", @@ -267,75 +266,75 @@ }, "hal_build_variants": { "samd11c": { - "features": [ "samd11c", "dma", "rtic", "defmt", "async", "undoc-features" ], + "features": [ "samd11c", "dma", "rtic", "embassy-time", "defmt", "async", "undoc-features" ], "target": "thumbv6m-none-eabi" }, "samd11d": { - "features": [ "samd11d", "dma", "rtic", "defmt", "async", "undoc-features" ], + "features": [ "samd11d", "dma", "rtic", "embassy-time", "defmt", "async", "undoc-features" ], "target": "thumbv6m-none-eabi" }, "samd21e": { - "features": [ "samd21e", "usb", "dma", "rtic", "defmt", "async", "undoc-features" ], + "features": [ "samd21e", "usb", "dma", "rtic", "embassy-time", "defmt", "async", "undoc-features" ], "target": "thumbv6m-none-eabi" }, "samd21el": { - "features": [ "samd21el", "dma", "rtic", "defmt", "async", "undoc-features" ], + "features": [ "samd21el", "dma", "rtic", "embassy-time", "defmt", "async", "undoc-features" ], "target": "thumbv6m-none-eabi" }, "samd21g": { - "features": [ "samd21g", "usb", "dma", "rtic", "defmt", "async", "undoc-features" ], + "features": [ "samd21g", "usb", "dma", "rtic", "embassy-time", "defmt", "async", "undoc-features" ], "target": "thumbv6m-none-eabi" }, "samd21gl": { - "features": [ "samd21gl", "dma", "rtic", "defmt", "async", "undoc-features" ], + "features": [ "samd21gl", "dma", "rtic", "embassy-time", "defmt", "async", "undoc-features" ], "target": "thumbv6m-none-eabi" }, "samd21j": { - "features": [ "samd21j", "usb", "dma", "rtic", "defmt", "async", "undoc-features" ], + "features": [ "samd21j", "usb", "dma", "rtic", "embassy-time", "defmt", "async", "undoc-features" ], "target": "thumbv6m-none-eabi" }, "samd51g": { - "features": [ "samd51g", "usb", "dma", "sdmmc", "rtic", "defmt", "async", "undoc-features" ], + "features": [ "samd51g", "usb", "dma", "sdmmc", "rtic", "embassy-time", "defmt", "async", "undoc-features" ], "target": "thumbv7em-none-eabihf" }, "samd51j": { - "features": [ "samd51j", "usb", "dma", "sdmmc", "rtic", "defmt", "async", "undoc-features" ], + "features": [ "samd51j", "usb", "dma", "sdmmc", "rtic", "embassy-time", "defmt", "async", "undoc-features" ], "target": "thumbv7em-none-eabihf" }, "samd51n": { - "features": [ "samd51n", "usb", "dma", "sdmmc", "rtic", "defmt", "async", "undoc-features" ], + "features": [ "samd51n", "usb", "dma", "sdmmc", "rtic", "embassy-time", "defmt", "async", "undoc-features" ], "target": "thumbv7em-none-eabihf" }, "samd51p": { - "features": [ "samd51p", "usb", "dma", "sdmmc", "rtic", "defmt", "async", "undoc-features" ], + "features": [ "samd51p", "usb", "dma", "sdmmc", "rtic", "embassy-time", "defmt", "async", "undoc-features" ], "target": "thumbv7em-none-eabihf" }, "same51g": { - "features": [ "same51g", "usb", "dma", "sdmmc", "rtic", "can", "defmt", "async", "undoc-features" ], + "features": [ "same51g", "usb", "dma", "sdmmc", "rtic", "embassy-time", "can", "defmt", "async", "undoc-features" ], "target": "thumbv7em-none-eabihf" }, "same51j": { - "features": [ "same51j", "usb", "dma", "sdmmc", "rtic", "can", "defmt", "async", "undoc-features" ], + "features": [ "same51j", "usb", "dma", "sdmmc", "rtic", "embassy-time", "can", "defmt", "async", "undoc-features" ], "target": "thumbv7em-none-eabihf" }, "same51n": { - "features": [ "same51n", "usb", "dma", "sdmmc", "rtic", "can", "defmt", "async" , "undoc-features"], + "features": [ "same51n", "usb", "dma", "sdmmc", "rtic", "embassy-time", "can", "defmt", "async", "undoc-features" ], "target": "thumbv7em-none-eabihf" }, "same53j": { - "features": [ "same53j", "usb", "dma", "sdmmc", "rtic", "defmt", "async", "undoc-features" ], + "features": [ "same53j", "usb", "dma", "sdmmc", "rtic", "embassy-time", "defmt", "async", "undoc-features" ], "target": "thumbv7em-none-eabihf" }, "same53n": { - "features": [ "same53n", "usb", "dma", "sdmmc", "rtic", "defmt", "async", "undoc-features" ], + "features": [ "same53n", "usb", "dma", "sdmmc", "rtic", "embassy-time", "defmt", "async", "undoc-features" ], "target": "thumbv7em-none-eabihf" }, "same54n": { - "features": [ "same54n", "usb", "dma", "sdmmc", "rtic", "can", "defmt", "async", "undoc-features" ], + "features": [ "same54n", "usb", "dma", "sdmmc", "rtic", "embassy-time", "can", "defmt", "async", "undoc-features" ], "target": "thumbv7em-none-eabihf" }, "same54p": { - "features": [ "same54p", "usb", "dma", "sdmmc", "rtic", "can", "defmt", "async", "undoc-features" ], + "features": [ "same54p", "usb", "dma", "sdmmc", "rtic", "embassy-time", "can", "defmt", "async", "undoc-features" ], "target": "thumbv7em-none-eabihf" } } diff --git a/hal/Cargo.toml b/hal/Cargo.toml index 6856d7ae1cf1..4d3c2ee6f72f 100644 --- a/hal/Cargo.toml +++ b/hal/Cargo.toml @@ -40,7 +40,7 @@ critical-section = "1.2.0" embedded-hal-02 = {package = "embedded-hal", version = "0.2", features = ["unproven"]} embedded-hal-1 = {package = "embedded-hal", version = "1.0.0"} embedded-hal-nb = "1.0.0" -embedded-io = "0.6" +embedded-io = "0.7" fugit = "0.3" heapless = "0.8" modular-bitfield = "0.11" @@ -60,9 +60,9 @@ void = {version = "1.0", default-features = false} #=============================================================================== defmt = {version = "1.0.1", optional = true} -embassy-sync = {version = "0.6.0", optional = true} +embassy-sync = {version = "0.7", optional = true} embedded-hal-async = {version = "1.0.0", optional = true} -embedded-io-async = {version = "0.6.1", optional = true} +embedded-io-async = {version = "0.7", optional = true} embedded-sdmmc = {version = "0.8.1", optional = true} futures = {version = "0.3.31", default-features = false, features = ["async-await"], optional = true} jlink_rtt = {version = "0.2", optional = true} @@ -71,6 +71,9 @@ rtic-monotonic = {version = "1.0", optional = true} rtic-time = {version = "2.0", optional = true} usb-device = {version = "0.3.2", optional = true} +embassy-time-driver = { version = "0.2", optional = true, features = ["tick-hz-32_768"] } +embassy-time-queue-utils = { version = "0.3", optional = true } + #=============================================================================== # PACs #=============================================================================== @@ -194,6 +197,7 @@ defmt = ["dep:defmt"] dma = [] max-channels = ["dma"] rtic = ["rtic-monotonic", "rtic-time", "portable-atomic"] +embassy-time = ["embassy-sync", "embassy-time-driver", "embassy-time-queue-utils"] sdmmc = ["embedded-sdmmc"] use_rtt = ["jlink_rtt"] undoc-features = [] diff --git a/hal/src/lib.rs b/hal/src/lib.rs index e20f6b5025d1..244e7be14e91 100644 --- a/hal/src/lib.rs +++ b/hal/src/lib.rs @@ -17,6 +17,11 @@ pub use embedded_io_async; #[cfg(feature = "rtic")] pub use rtic_time; +// Exposed only so the macro can initialize it +#[cfg(feature = "embassy-time")] +#[doc(hidden)] +pub use embassy_time_driver; + pub mod typelevel; mod util; diff --git a/hal/src/rtc/embassy.rs b/hal/src/rtc/embassy.rs new file mode 100644 index 000000000000..a0ba709ecb49 --- /dev/null +++ b/hal/src/rtc/embassy.rs @@ -0,0 +1,185 @@ +//! This is a driver for embassy-time +//! It must be instantiated with embassy_time!() +//! which will configure the chip's RTC + +use core::cell::RefCell; +use core::task::Waker; + +use crate::pac::{Interrupt, NVIC, Rtc}; +use crate::rtc::modes::{ + RtcMode, + mode0::{Compare0, RtcMode0}, +}; +use atsamd_hal_macros::hal_cfg; +use critical_section::CriticalSection; +use embassy_sync::blocking_mutex::{Mutex, raw::CriticalSectionRawMutex}; +use embassy_time_driver::Driver; +use embassy_time_queue_utils::Queue; + +/// Used internally by the embassy time driver. +/// You shouldn't need this +pub struct EmbassyBackend { + queue: Mutex>, +} + +impl EmbassyBackend { + // Stolen from RtcModeMonotonic + const SYNC_SLACK_TICKS: u32 = 12; + + #[allow(clippy::new_without_default)] + pub const fn new() -> Self { + Self { + queue: Mutex::new(RefCell::new(Queue::new())), + } + } + + + fn set_alarm(&self, _cs: &CriticalSection, at: u64, rtc: &Rtc) -> bool { + // Embassy uses u64::MAX as a "no upcoming interrupt" sentinel + let at = match u32::try_from(at) { + _ if at == u64::MAX => return true, + Ok(at) => at, + Err(_) => return false, + }; + + if at.saturating_sub(RtcMode0::count(rtc)) < Self::SYNC_SLACK_TICKS { + // This is in the past + return false; + } + + RtcMode0::set_compare(rtc, 0, at); + // double check that the timestamp is still in the future + + if at.saturating_sub(RtcMode0::count(rtc)) < Self::SYNC_SLACK_TICKS { + // This is in the past + return false; + } + true + } + + pub fn handle_interrupt(&self, rtc: &Rtc, cs: CriticalSection) { + if RtcMode0::check_interrupt_flag::(rtc) { + // Due to synchronization delay, the RTC may be slightly behind + // Assume the current time is at least the time of the comparator + let now = RtcMode0::get_compare(rtc, 0) as u64; + loop { + let now = now.max(RtcMode0::count(&rtc) as u64); + let next = self.queue.borrow(cs).borrow_mut().next_expiration(now); + if self.set_alarm(&cs, next, rtc) { + break; + } + } + RtcMode0::clear_interrupt_flag::(rtc); + } + } + + /// # Safety + /// + /// This enables interrupts, which can break out of critical sections + pub unsafe fn init() { + let rtc = unsafe { Rtc::steal() }; + + RtcMode0::disable(&rtc); + RtcMode0::reset(&rtc); + RtcMode0::set_mode(&rtc); + + RtcMode0::start_and_initialize(&rtc); + RtcMode0::clear_interrupt_flag::(&rtc); + RtcMode0::enable_interrupt::(&rtc); + + RtcMode0::enable(&rtc); + unsafe { + cortex_m::interrupt::enable(); + let mut nvic: cortex_m::peripheral::NVIC = core::mem::transmute(()); + nvic.set_priority(Interrupt::RTC, 1); + NVIC::unmask(Interrupt::RTC); + } + } +} + +impl Driver for EmbassyBackend { + fn now(&self) -> u64 { + critical_section::with(|_cs| { + let rtc = unsafe { Rtc::steal() }; + RtcMode0::count(&rtc) as u64 + }) + } + + fn schedule_wake(&self, at: u64, waker: &Waker) { + critical_section::with(|cs| { + let rtc = unsafe { Rtc::steal() }; + let mut queue = self.queue.borrow(cs).borrow_mut(); + if queue.schedule_wake(at, waker) { + // loop until we've set an alarm, in case "next" was in the past + loop { + let next = queue.next_expiration(self.now()); + + // We can only handle one alarm at a time right now + if self.set_alarm(&cs, next, &rtc) { + break + } + } + } + }); + } +} + +pub trait EmbassyRtcSource {} + +#[hal_cfg(any("clock-d11", "clock-d21"))] +mod rtc_src { + impl super::EmbassyRtcSource for crate::clock::RtcClock {} +} + +#[hal_cfg("clock-d5x")] +mod rtc_src { + use crate::clock::v2::{osculp32k::OscUlp32kId, rtcosc::RtcOsc, xosc32k::Xosc32kId}; + impl super::EmbassyRtcSource for RtcOsc {} + impl super::EmbassyRtcSource for RtcOsc {} +} + +#[allow(unused_imports)] +use rtc_src::*; + +/// Create an embassy-time compliant driver +/// This driver should be called outside any function +/// The driver must be started by calling init() on the created struct +/// with an RTC token to indicate proper configuration +/// ```invalid +/// rtc::embassy::embassy_time!(Driver); +/// +/// #[embassy_executor::main] +/// async fn main(_s: embassy_executor::Spawner) { +/// let rtc_token = set_up_clock!(); +/// /// Safety: called outside a critical section +/// unsafe { +/// Driver::init(rtc_token); +/// } +/// } +/// ``` +#[macro_export] +macro_rules! embassy_time { + ($name: ident) => { + + use $crate::pac::interrupt; + use $crate::{embassy_time_driver, rtc::embassy::{EmbassyBackend, EmbassyRtcSource}}; + + embassy_time_driver::time_driver_impl!(static DRIVER: EmbassyBackend = EmbassyBackend::new()); + + #[$crate::pac::interrupt] + fn RTC() { + critical_section::with(|cs| { + let rtc = unsafe { $crate::pac::Rtc::steal() }; + DRIVER.handle_interrupt(&rtc, cs) + }); + } + + pub struct $name; + + impl $name { + unsafe fn init(_rtc: SRC) where SRC: EmbassyRtcSource { + EmbassyBackend::init(); + } + } + } +} diff --git a/hal/src/rtc/mod.rs b/hal/src/rtc/mod.rs index 72535e5d82cc..15e9a65d0185 100644 --- a/hal/src/rtc/mod.rs +++ b/hal/src/rtc/mod.rs @@ -24,6 +24,9 @@ mod modes; #[cfg(feature = "rtic")] pub mod rtic; +#[cfg(feature = "embassy-time")] +pub mod embassy; + // SAMx5x imports #[hal_cfg("rtc-d5x")] use crate::pac::{Mclk as Pm, rtc::mode0::ctrla::Prescalerselect}; diff --git a/hal/src/rtc/modes.rs b/hal/src/rtc/modes.rs index f30b1ce0acb6..c24cceddeef4 100644 --- a/hal/src/rtc/modes.rs +++ b/hal/src/rtc/modes.rs @@ -114,7 +114,7 @@ pub trait RtcMode { /// /// Should be called only after setting the RTC mode using /// [`set_mode`](RtcMode::set_mode). - #[cfg(feature = "rtic")] + #[cfg(any(feature = "rtic", feature = "embassy-time"))] fn get_compare(rtc: &Rtc, number: usize) -> Self::Count; /// Returns the current synced COUNT value. @@ -402,7 +402,7 @@ pub mod mode0 { } #[inline] - #[cfg(feature = "rtic")] + #[cfg(any(feature = "rtic", feature = "embassy-time"))] fn get_compare(rtc: &Rtc, number: usize) -> Self::Count { // SYNC: Write (we just read though) rtc.mode0().comp(number).read().bits() @@ -439,9 +439,7 @@ pub mod mode1 { use super::*; create_rtc_interrupt!(mode1, Compare0, cmp0); - #[cfg(feature = "rtic")] create_rtc_interrupt!(mode1, Compare1, cmp1); - #[cfg(feature = "rtic")] create_rtc_interrupt!(mode1, Overflow, ovf); /// The RTC operating in MODE1 (16-bit COUNT) @@ -475,7 +473,7 @@ pub mod mode1 { } #[inline] - #[cfg(feature = "rtic")] + #[cfg(any(feature = "rtic", feature = "embassy-time"))] fn get_compare(rtc: &Rtc, number: usize) -> Self::Count { // SYNC: Write (we just read though) rtc.mode1().comp(number).read().bits() @@ -607,7 +605,7 @@ pub mod mode2 { #[inline] #[hal_macro_helper] - #[cfg(feature = "rtic")] + #[cfg(any(feature = "rtic", feature = "embassy-time"))] fn get_compare(rtc: &Rtc, _number: usize) -> Self::Count { // SYNC: Write (we just read though) #[hal_cfg(any("rtc-d11", "rtc-d21"))] diff --git a/hal/src/sercom/spi.rs b/hal/src/sercom/spi.rs index 9ec262b2d646..da7e7eaab8b1 100644 --- a/hal/src/sercom/spi.rs +++ b/hal/src/sercom/spi.rs @@ -602,6 +602,14 @@ pub enum Error { Dma(crate::dmac::Error), } +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "SPI Error: {:?}", self) + } +} + +impl core::error::Error for Error {} + //============================================================================= // Operating mode //============================================================================= diff --git a/hal/src/sercom/uart/async_api.rs b/hal/src/sercom/uart/async_api.rs index a7d963234e25..d003beb777a8 100644 --- a/hal/src/sercom/uart/async_api.rs +++ b/hal/src/sercom/uart/async_api.rs @@ -340,6 +340,13 @@ where self.write(buffer).await; Ok(buffer.len()) } + + + #[inline] + async fn flush(&mut self) -> Result<(), Self::Error> { + // self.write already calls self.wait_flags(Flags::TXC).await; + Ok(()) + } } impl AsRef> for UartFuture @@ -499,11 +506,19 @@ mod dma { S: Sercom + 'static, T: AnyChannel, { + #[inline] async fn write(&mut self, words: &[u8]) -> Result { self.write(words).await?; Ok(words.len()) } + + #[inline] + async fn flush(&mut self) -> Result<(), Self::Error> { + // self.write already calls self.wait_flags(Flags::TXC).await; + Ok(()) + } + } } diff --git a/hal/src/sercom/uart/flags.rs b/hal/src/sercom/uart/flags.rs index b72c0fa7057c..b1374928d629 100644 --- a/hal/src/sercom/uart/flags.rs +++ b/hal/src/sercom/uart/flags.rs @@ -133,6 +133,14 @@ pub enum Error { Dma(crate::dmac::Error), } +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "UART Error: {:?}", self) + } +} + +impl core::error::Error for Error {} + impl From for Status { #[inline] fn from(err: Error) -> Self {