diff --git a/.github/ci/doc.sh b/.github/ci/doc.sh index 06c6fa00..57184dc1 100755 --- a/.github/ci/doc.sh +++ b/.github/ci/doc.sh @@ -15,7 +15,6 @@ export BUILDER_COMPRESS=true # which makes rustup very sad rustc --version > /dev/null -docserver-builder -i ./embassy-stm32 -o webroot/crates/embassy-stm32/git.zup docserver-builder -i ./embassy-boot/boot -o webroot/crates/embassy-boot/git.zup docserver-builder -i ./embassy-boot/nrf -o webroot/crates/embassy-boot-nrf/git.zup docserver-builder -i ./embassy-boot/rp -o webroot/crates/embassy-boot-rp/git.zup @@ -36,11 +35,20 @@ docserver-builder -i ./embassy-usb-driver -o webroot/crates/embassy-usb-driver/g docserver-builder -i ./embassy-usb-logger -o webroot/crates/embassy-usb-logger/git.zup docserver-builder -i ./cyw43 -o webroot/crates/cyw43/git.zup docserver-builder -i ./cyw43-pio -o webroot/crates/cyw43-pio/git.zup -docserver-builder -i ./embassy-net-w5500 -o webroot/crates/embassy-net-w5500/git.zup +docserver-builder -i ./embassy-net-wiznet -o webroot/crates/embassy-net-wiznet/git.zup +docserver-builder -i ./embassy-net-enc28j60 -o webroot/crates/embassy-net-enc28j60/git.zup docserver-builder -i ./embassy-net-esp-hosted -o webroot/crates/embassy-net-esp-hosted/git.zup docserver-builder -i ./embassy-stm32-wpan -o webroot/crates/embassy-stm32-wpan/git.zup --output-static webroot/static export KUBECONFIG=/ci/secrets/kubeconfig.yml POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name}) kubectl cp webroot/crates $POD:/data -kubectl cp webroot/static $POD:/data \ No newline at end of file +kubectl cp webroot/static $POD:/data + +# build and upload stm32 last +# so that it doesn't prevent other crates from getting docs updates when it breaks. +rm -rf webroot +docserver-builder -i ./embassy-stm32 -o webroot/crates/embassy-stm32/git.zup + +POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name}) +kubectl cp webroot/crates $POD:/data diff --git a/.vscode/settings.json b/.vscode/settings.json index 29e8812e..d48f7ba1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,16 +6,21 @@ "rust-analyzer.check.allTargets": false, "rust-analyzer.check.noDefaultFeatures": true, "rust-analyzer.cargo.noDefaultFeatures": true, - "rust-analyzer.cargo.target": "thumbv7m-none-eabi", + "rust-analyzer.showUnlinkedFileNotification": false, + // uncomment the target of your chip. + //"rust-analyzer.cargo.target": "thumbv6m-none-eabi", + //"rust-analyzer.cargo.target": "thumbv7m-none-eabi", + "rust-analyzer.cargo.target": "thumbv7em-none-eabi", //"rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", "rust-analyzer.cargo.features": [ - ///"nightly", + // Uncomment if the example has a "nightly" feature. + "nightly", ], "rust-analyzer.linkedProjects": [ - // Declare for the target you wish to develop - // "embassy-executor/Cargo.toml", - // "embassy-sync/Cargo.toml", - "examples/stm32wl/Cargo.toml", + // Uncomment ONE line for the chip you want to work on. + // This makes rust-analyzer work on the example crate and all its dependencies. + "examples/nrf52840/Cargo.toml", + // "examples/nrf52840-rtic/Cargo.toml", // "examples/nrf5340/Cargo.toml", // "examples/nrf-rtos-trace/Cargo.toml", // "examples/rp/Cargo.toml", diff --git a/ci.sh b/ci.sh index 146a1c50..e83b3b84 100755 --- a/ci.sh +++ b/ci.sh @@ -3,7 +3,7 @@ set -euo pipefail export RUSTFLAGS=-Dwarnings -export DEFMT_LOG=trace,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info +export DEFMT_LOG=trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info TARGET=$(rustc -vV | sed -n 's|host: ||p') @@ -81,6 +81,7 @@ cargo batch \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32l041f6,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32l151cb-a,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f398ve,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f378cc,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32g0c1ve,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f217zg,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,stm32l552ze,defmt,exti,time-driver-any,unstable-traits \ diff --git a/cyw43/Cargo.toml b/cyw43/Cargo.toml index 50fb7c5d..855d54b1 100644 --- a/cyw43/Cargo.toml +++ b/cyw43/Cargo.toml @@ -24,7 +24,7 @@ cortex-m = "0.7.6" cortex-m-rt = "0.7.0" futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } -embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.11" } +embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-rc.1" } num_enum = { version = "0.5.7", default-features = false } [package.metadata.embassy_docs] diff --git a/cyw43/src/bus.rs b/cyw43/src/bus.rs index e26f1112..0b5632cf 100644 --- a/cyw43/src/bus.rs +++ b/cyw43/src/bus.rs @@ -102,7 +102,7 @@ where cmd_buf[0] = cmd; cmd_buf[1..][..buf.len()].copy_from_slice(buf); - self.status = self.spi.cmd_write(&cmd_buf).await; + self.status = self.spi.cmd_write(&cmd_buf[..buf.len() + 1]).await; } #[allow(unused)] diff --git a/embassy-boot/nrf/src/lib.rs b/embassy-boot/nrf/src/lib.rs index df94819f..b9d86eb1 100644 --- a/embassy-boot/nrf/src/lib.rs +++ b/embassy-boot/nrf/src/lib.rs @@ -14,28 +14,17 @@ use embassy_nrf::wdt; use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; /// A bootloader for nRF devices. -pub struct BootLoader { - boot: embassy_boot::BootLoader, - aligned_buf: AlignedBuffer, -} +pub struct BootLoader; -impl - BootLoader -{ - /// Create a new bootloader instance using the supplied partitions for active, dfu and state. - pub fn new(config: BootLoaderConfig) -> Self { - Self { - boot: embassy_boot::BootLoader::new(config), - aligned_buf: AlignedBuffer([0; BUFFER_SIZE]), - } - } - - /// Inspect the bootloader state and perform actions required before booting, such as swapping - /// firmware. - pub fn prepare(&mut self) { - self.boot - .prepare_boot(&mut self.aligned_buf.0) - .expect("Boot prepare error"); +impl BootLoader { + /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware. + pub fn prepare( + config: BootLoaderConfig, + ) -> Self { + let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); + let mut boot = embassy_boot::BootLoader::new(config); + boot.prepare_boot(&mut aligned_buf.0).expect("Boot prepare error"); + Self } /// Boots the application without softdevice mechanisms. @@ -45,8 +34,6 @@ impl /// This modifies the stack pointer and reset vector and will run code placed in the active partition. #[cfg(not(feature = "softdevice"))] pub unsafe fn load(self, start: u32) -> ! { - core::mem::drop(self.boot); - let mut p = cortex_m::Peripherals::steal(); p.SCB.invalidate_icache(); p.SCB.vtor.write(start); @@ -59,7 +46,7 @@ impl /// /// This modifies the stack pointer and reset vector and will run code placed in the active partition. #[cfg(feature = "softdevice")] - pub unsafe fn load(&mut self, _app: u32) -> ! { + pub unsafe fn load(self, _app: u32) -> ! { use nrf_softdevice_mbr as mbr; const NRF_SUCCESS: u32 = 0; diff --git a/embassy-boot/rp/src/lib.rs b/embassy-boot/rp/src/lib.rs index f5aefa41..989e7521 100644 --- a/embassy-boot/rp/src/lib.rs +++ b/embassy-boot/rp/src/lib.rs @@ -15,28 +15,17 @@ use embassy_time::Duration; use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; /// A bootloader for RP2040 devices. -pub struct BootLoader { - boot: embassy_boot::BootLoader, - aligned_buf: AlignedBuffer, -} +pub struct BootLoader; -impl - BootLoader -{ - /// Create a new bootloader instance using the supplied partitions for active, dfu and state. - pub fn new(config: BootLoaderConfig) -> Self { - Self { - boot: embassy_boot::BootLoader::new(config), - aligned_buf: AlignedBuffer([0; BUFFER_SIZE]), - } - } - - /// Inspect the bootloader state and perform actions required before booting, such as swapping - /// firmware. - pub fn prepare(&mut self) { - self.boot - .prepare_boot(self.aligned_buf.as_mut()) - .expect("Boot prepare error"); +impl BootLoader { + /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware + pub fn prepare( + config: BootLoaderConfig, + ) -> Self { + let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); + let mut boot = embassy_boot::BootLoader::new(config); + boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); + Self } /// Boots the application. @@ -45,8 +34,6 @@ impl /// /// This modifies the stack pointer and reset vector and will run code placed in the active partition. pub unsafe fn load(self, start: u32) -> ! { - core::mem::drop(self.boot); - trace!("Loading app at 0x{:x}", start); #[allow(unused_mut)] let mut p = cortex_m::Peripherals::steal(); @@ -67,7 +54,7 @@ pub struct WatchdogFlash<'d, const SIZE: usize> { impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> { /// Start a new watchdog with a given flash and watchdog peripheral and a timeout pub fn start(flash: FLASH, watchdog: WATCHDOG, timeout: Duration) -> Self { - let flash = Flash::<_, Blocking, SIZE>::new(flash); + let flash = Flash::<_, Blocking, SIZE>::new_blocking(flash); let mut watchdog = Watchdog::new(watchdog); watchdog.start(timeout); Self { flash, watchdog } @@ -84,11 +71,11 @@ impl<'d, const SIZE: usize> NorFlash for WatchdogFlash<'d, SIZE> { fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { self.watchdog.feed(); - self.flash.erase(from, to) + self.flash.blocking_erase(from, to) } fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { self.watchdog.feed(); - self.flash.write(offset, data) + self.flash.blocking_write(offset, data) } } @@ -96,7 +83,7 @@ impl<'d, const SIZE: usize> ReadNorFlash for WatchdogFlash<'d, SIZE> { const READ_SIZE: usize = as ReadNorFlash>::READ_SIZE; fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> { self.watchdog.feed(); - self.flash.read(offset, data) + self.flash.blocking_read(offset, data) } fn capacity(&self) -> usize { self.flash.capacity() diff --git a/embassy-boot/stm32/src/lib.rs b/embassy-boot/stm32/src/lib.rs index 25f02942..c6350c49 100644 --- a/embassy-boot/stm32/src/lib.rs +++ b/embassy-boot/stm32/src/lib.rs @@ -11,28 +11,17 @@ pub use embassy_boot::{FirmwareState, FirmwareUpdater}; use embedded_storage::nor_flash::NorFlash; /// A bootloader for STM32 devices. -pub struct BootLoader { - boot: embassy_boot::BootLoader, - aligned_buf: AlignedBuffer, -} +pub struct BootLoader; -impl - BootLoader -{ - /// Create a new bootloader instance using the supplied partitions for active, dfu and state. - pub fn new(config: BootLoaderConfig) -> Self { - Self { - boot: embassy_boot::BootLoader::new(config), - aligned_buf: AlignedBuffer([0; BUFFER_SIZE]), - } - } - - /// Inspect the bootloader state and perform actions required before booting, such as swapping - /// firmware. - pub fn prepare(&mut self) { - self.boot - .prepare_boot(self.aligned_buf.as_mut()) - .expect("Boot prepare error"); +impl BootLoader { + /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware + pub fn prepare( + config: BootLoaderConfig, + ) -> Self { + let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); + let mut boot = embassy_boot::BootLoader::new(config); + boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); + Self } /// Boots the application. @@ -41,8 +30,6 @@ impl /// /// This modifies the stack pointer and reset vector and will run code placed in the active partition. pub unsafe fn load(self, start: u32) -> ! { - core::mem::drop(self.boot); - trace!("Loading app at 0x{:x}", start); #[allow(unused_mut)] let mut p = cortex_m::Peripherals::steal(); diff --git a/embassy-embedded-hal/Cargo.toml b/embassy-embedded-hal/Cargo.toml index bba3d48b..fd921d27 100644 --- a/embassy-embedded-hal/Cargo.toml +++ b/embassy-embedded-hal/Cargo.toml @@ -25,8 +25,8 @@ embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [ "unproven", ] } -embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" } -embedded-hal-async = { version = "=0.2.0-alpha.2", optional = true } +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1" } +embedded-hal-async = { version = "=1.0.0-rc.1", optional = true } embedded-storage = "0.3.0" embedded-storage-async = { version = "0.4.0", optional = true } nb = "1.0.0" diff --git a/embassy-embedded-hal/src/adapter/blocking_async.rs b/embassy-embedded-hal/src/adapter/blocking_async.rs index 98ae2b02..ae0d0a7f 100644 --- a/embassy-embedded-hal/src/adapter/blocking_async.rs +++ b/embassy-embedded-hal/src/adapter/blocking_async.rs @@ -1,4 +1,4 @@ -use embedded_hal_02::{blocking, serial}; +use embedded_hal_02::blocking; /// Wrapper that implements async traits using blocking implementations. /// @@ -103,15 +103,6 @@ where } } -// Uart implementatinos -impl embedded_hal_1::serial::ErrorType for BlockingAsync -where - T: serial::Read, - E: embedded_hal_1::serial::Error + 'static, -{ - type Error = E; -} - /// NOR flash wrapper use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; diff --git a/embassy-executor/CHANGELOG.md b/embassy-executor/CHANGELOG.md index 4fd3dccf..43d94e54 100644 --- a/embassy-executor/CHANGELOG.md +++ b/embassy-executor/CHANGELOG.md @@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +- Replaced Pender. Implementations now must define an extern function called `__pender`. +- Made `raw::AvailableTask` public +- Made `SpawnToken::new_failed` public + +## 0.2.1 - 2023-08-10 + +- Avoid calling `pend()` when waking expired timers +- Properly reset finished task state with `integrated-timers` enabled +- Introduce `InterruptExecutor::spawner()` +- Fix incorrect critical section in Xtensa executor + ## 0.2.0 - 2023-04-27 - Replace unnecessary atomics in runqueue diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index 590718e3..d190c95a 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-executor" -version = "0.2.0" +version = "0.2.1" edition = "2021" license = "MIT OR Apache-2.0" description = "async/await executor designed for embedded usage" @@ -14,7 +14,7 @@ categories = [ [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-executor-v$VERSION/embassy-executor/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-executor/src/" -features = ["nightly", "defmt", "pender-callback"] +features = ["nightly", "defmt"] flavors = [ { name = "std", target = "x86_64-unknown-linux-gnu", features = ["arch-std", "executor-thread"] }, { name = "wasm", target = "wasm32-unknown-unknown", features = ["arch-wasm", "executor-thread"] }, @@ -25,7 +25,7 @@ flavors = [ [package.metadata.docs.rs] default-target = "thumbv7em-none-eabi" targets = ["thumbv7em-none-eabi"] -features = ["nightly", "defmt", "pender-callback", "arch-cortex-m", "executor-thread", "executor-interrupt"] +features = ["nightly", "defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"] [features] @@ -37,9 +37,6 @@ arch-xtensa = ["_arch"] arch-riscv32 = ["_arch"] arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"] -# Enable creating a `Pender` from an arbitrary function pointer callback. -pender-callback = [] - # Enable the thread-mode executor (using WFE/SEV in Cortex-M, WFI in other embedded archs) executor-thread = [] # Enable the interrupt-mode executor (available in Cortex-M only) diff --git a/embassy-executor/src/arch/cortex_m.rs b/embassy-executor/src/arch/cortex_m.rs index 94c8134d..0806a22a 100644 --- a/embassy-executor/src/arch/cortex_m.rs +++ b/embassy-executor/src/arch/cortex_m.rs @@ -1,3 +1,49 @@ +const THREAD_PENDER: usize = usize::MAX; + +#[export_name = "__pender"] +#[cfg(any(feature = "executor-thread", feature = "executor-interrupt"))] +fn __pender(context: *mut ()) { + unsafe { + // Safety: `context` is either `usize::MAX` created by `Executor::run`, or a valid interrupt + // request number given to `InterruptExecutor::start`. + + let context = context as usize; + + #[cfg(feature = "executor-thread")] + // Try to make Rust optimize the branching away if we only use thread mode. + if !cfg!(feature = "executor-interrupt") || context == THREAD_PENDER { + core::arch::asm!("sev"); + return; + } + + #[cfg(feature = "executor-interrupt")] + { + use cortex_m::interrupt::InterruptNumber; + use cortex_m::peripheral::NVIC; + + #[derive(Clone, Copy)] + struct Irq(u16); + unsafe impl InterruptNumber for Irq { + fn number(self) -> u16 { + self.0 + } + } + + let irq = Irq(context as u16); + + // STIR is faster, but is only available in v7 and higher. + #[cfg(not(armv6m))] + { + let mut nvic: NVIC = core::mem::transmute(()); + nvic.request(irq); + } + + #[cfg(armv6m)] + NVIC::pend(irq); + } + } +} + #[cfg(feature = "executor-thread")] pub use thread::*; #[cfg(feature = "executor-thread")] @@ -8,18 +54,9 @@ mod thread { #[cfg(feature = "nightly")] pub use embassy_macros::main_cortex_m as main; - use crate::raw::{Pender, PenderInner}; + use crate::arch::THREAD_PENDER; use crate::{raw, Spawner}; - #[derive(Copy, Clone)] - pub(crate) struct ThreadPender; - - impl ThreadPender { - pub(crate) fn pend(self) { - unsafe { core::arch::asm!("sev") } - } - } - /// Thread mode executor, using WFE/SEV. /// /// This is the simplest and most common kind of executor. It runs on @@ -39,7 +76,7 @@ mod thread { /// Create a new Executor. pub fn new() -> Self { Self { - inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender))), + inner: raw::Executor::new(THREAD_PENDER as *mut ()), not_send: PhantomData, } } @@ -86,30 +123,7 @@ mod interrupt { use cortex_m::interrupt::InterruptNumber; use cortex_m::peripheral::NVIC; - use crate::raw::{self, Pender, PenderInner}; - - #[derive(Clone, Copy)] - pub(crate) struct InterruptPender(u16); - - impl InterruptPender { - pub(crate) fn pend(self) { - // STIR is faster, but is only available in v7 and higher. - #[cfg(not(armv6m))] - { - let mut nvic: cortex_m::peripheral::NVIC = unsafe { core::mem::transmute(()) }; - nvic.request(self); - } - - #[cfg(armv6m)] - cortex_m::peripheral::NVIC::pend(self); - } - } - - unsafe impl cortex_m::interrupt::InterruptNumber for InterruptPender { - fn number(self) -> u16 { - self.0 - } - } + use crate::raw; /// Interrupt mode executor. /// @@ -194,9 +208,7 @@ mod interrupt { unsafe { (&mut *self.executor.get()) .as_mut_ptr() - .write(raw::Executor::new(Pender(PenderInner::Interrupt(InterruptPender( - irq.number(), - ))))) + .write(raw::Executor::new(irq.number() as *mut ())) } let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; diff --git a/embassy-executor/src/arch/riscv32.rs b/embassy-executor/src/arch/riscv32.rs index ff7ec157..40c6877e 100644 --- a/embassy-executor/src/arch/riscv32.rs +++ b/embassy-executor/src/arch/riscv32.rs @@ -11,22 +11,16 @@ mod thread { #[cfg(feature = "nightly")] pub use embassy_macros::main_riscv as main; - use crate::raw::{Pender, PenderInner}; use crate::{raw, Spawner}; - #[derive(Copy, Clone)] - pub(crate) struct ThreadPender; - - impl ThreadPender { - #[allow(unused)] - pub(crate) fn pend(self) { - SIGNAL_WORK_THREAD_MODE.store(true, core::sync::atomic::Ordering::SeqCst); - } - } - /// global atomic used to keep track of whether there is work to do since sev() is not available on RISCV static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); + #[export_name = "__pender"] + fn __pender(_context: *mut ()) { + SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); + } + /// RISCV32 Executor pub struct Executor { inner: raw::Executor, @@ -37,7 +31,7 @@ mod thread { /// Create a new Executor. pub fn new() -> Self { Self { - inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender))), + inner: raw::Executor::new(core::ptr::null_mut()), not_send: PhantomData, } } diff --git a/embassy-executor/src/arch/std.rs b/embassy-executor/src/arch/std.rs index 4e4a178f..5b2f7e2e 100644 --- a/embassy-executor/src/arch/std.rs +++ b/embassy-executor/src/arch/std.rs @@ -11,17 +11,12 @@ mod thread { #[cfg(feature = "nightly")] pub use embassy_macros::main_std as main; - use crate::raw::{Pender, PenderInner}; use crate::{raw, Spawner}; - #[derive(Copy, Clone)] - pub(crate) struct ThreadPender(&'static Signaler); - - impl ThreadPender { - #[allow(unused)] - pub(crate) fn pend(self) { - self.0.signal() - } + #[export_name = "__pender"] + fn __pender(context: *mut ()) { + let signaler: &'static Signaler = unsafe { std::mem::transmute(context) }; + signaler.signal() } /// Single-threaded std-based executor. @@ -34,9 +29,9 @@ mod thread { impl Executor { /// Create a new Executor. pub fn new() -> Self { - let signaler = &*Box::leak(Box::new(Signaler::new())); + let signaler = Box::leak(Box::new(Signaler::new())); Self { - inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender(signaler)))), + inner: raw::Executor::new(signaler as *mut Signaler as *mut ()), not_send: PhantomData, signaler, } diff --git a/embassy-executor/src/arch/wasm.rs b/embassy-executor/src/arch/wasm.rs index 08ab16b9..934fd69e 100644 --- a/embassy-executor/src/arch/wasm.rs +++ b/embassy-executor/src/arch/wasm.rs @@ -14,14 +14,12 @@ mod thread { use wasm_bindgen::prelude::*; use crate::raw::util::UninitCell; - use crate::raw::{Pender, PenderInner}; use crate::{raw, Spawner}; - /// WASM executor, wasm_bindgen to schedule tasks on the JS event loop. - pub struct Executor { - inner: raw::Executor, - ctx: &'static WasmContext, - not_send: PhantomData<*mut ()>, + #[export_name = "__pender"] + fn __pender(context: *mut ()) { + let signaler: &'static WasmContext = unsafe { std::mem::transmute(context) }; + let _ = signaler.promise.then(unsafe { signaler.closure.as_mut() }); } pub(crate) struct WasmContext { @@ -29,16 +27,6 @@ mod thread { closure: UninitCell>, } - #[derive(Copy, Clone)] - pub(crate) struct ThreadPender(&'static WasmContext); - - impl ThreadPender { - #[allow(unused)] - pub(crate) fn pend(self) { - let _ = self.0.promise.then(unsafe { self.0.closure.as_mut() }); - } - } - impl WasmContext { pub fn new() -> Self { Self { @@ -48,14 +36,21 @@ mod thread { } } + /// WASM executor, wasm_bindgen to schedule tasks on the JS event loop. + pub struct Executor { + inner: raw::Executor, + ctx: &'static WasmContext, + not_send: PhantomData<*mut ()>, + } + impl Executor { /// Create a new Executor. pub fn new() -> Self { - let ctx = &*Box::leak(Box::new(WasmContext::new())); + let ctx = Box::leak(Box::new(WasmContext::new())); Self { - inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender(ctx)))), - not_send: PhantomData, + inner: raw::Executor::new(ctx as *mut WasmContext as *mut ()), ctx, + not_send: PhantomData, } } diff --git a/embassy-executor/src/arch/xtensa.rs b/embassy-executor/src/arch/xtensa.rs index 017b2c52..601d8500 100644 --- a/embassy-executor/src/arch/xtensa.rs +++ b/embassy-executor/src/arch/xtensa.rs @@ -8,22 +8,16 @@ mod thread { use core::marker::PhantomData; use core::sync::atomic::{AtomicBool, Ordering}; - use crate::raw::{Pender, PenderInner}; use crate::{raw, Spawner}; - #[derive(Copy, Clone)] - pub(crate) struct ThreadPender; - - impl ThreadPender { - #[allow(unused)] - pub(crate) fn pend(self) { - SIGNAL_WORK_THREAD_MODE.store(true, core::sync::atomic::Ordering::SeqCst); - } - } - /// global atomic used to keep track of whether there is work to do since sev() is not available on Xtensa static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); + #[export_name = "__pender"] + fn __pender(_context: *mut ()) { + SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); + } + /// Xtensa Executor pub struct Executor { inner: raw::Executor, @@ -34,7 +28,7 @@ mod thread { /// Create a new Executor. pub fn new() -> Self { Self { - inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender))), + inner: raw::Executor::new(core::ptr::null_mut()), not_send: PhantomData, } } @@ -77,8 +71,8 @@ mod thread { SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst); core::arch::asm!( - "wsr.ps {0}", - "rsync", in(reg) token) + "wsr.ps {0}", + "rsync", in(reg) token) } else { // waiti sets the PS.INTLEVEL when slipping into sleep // because critical sections in Xtensa are implemented via increasing diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index f3760f58..c1d82e18 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -147,10 +147,7 @@ impl TaskStorage { pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken { let task = AvailableTask::claim(self); match task { - Some(task) => { - let task = task.initialize(future); - unsafe { SpawnToken::::new(task) } - } + Some(task) => task.initialize(future), None => SpawnToken::new_failed(), } } @@ -186,12 +183,16 @@ impl TaskStorage { } } -struct AvailableTask { +/// An uninitialized [`TaskStorage`]. +pub struct AvailableTask { task: &'static TaskStorage, } impl AvailableTask { - fn claim(task: &'static TaskStorage) -> Option { + /// Try to claim a [`TaskStorage`]. + /// + /// This function returns `None` if a task has already been spawned and has not finished running. + pub fn claim(task: &'static TaskStorage) -> Option { task.raw .state .compare_exchange(0, STATE_SPAWNED | STATE_RUN_QUEUED, Ordering::AcqRel, Ordering::Acquire) @@ -199,61 +200,30 @@ impl AvailableTask { .map(|_| Self { task }) } - fn initialize(self, future: impl FnOnce() -> F) -> TaskRef { + fn initialize_impl(self, future: impl FnOnce() -> F) -> SpawnToken { unsafe { self.task.raw.poll_fn.set(Some(TaskStorage::::poll)); self.task.future.write(future()); - } - TaskRef::new(self.task) - } -} -/// Raw storage that can hold up to N tasks of the same type. -/// -/// This is essentially a `[TaskStorage; N]`. -pub struct TaskPool { - pool: [TaskStorage; N], -} + let task = TaskRef::new(self.task); -impl TaskPool { - /// Create a new TaskPool, with all tasks in non-spawned state. - pub const fn new() -> Self { - Self { - pool: [TaskStorage::NEW; N], + SpawnToken::new(task) } } - /// Try to spawn a task in the pool. - /// - /// See [`TaskStorage::spawn()`] for details. - /// - /// This will loop over the pool and spawn the task in the first storage that - /// is currently free. If none is free, a "poisoned" SpawnToken is returned, - /// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error. - pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken { - let task = self.pool.iter().find_map(AvailableTask::claim); - match task { - Some(task) => { - let task = task.initialize(future); - unsafe { SpawnToken::::new(task) } - } - None => SpawnToken::new_failed(), - } + /// Initialize the [`TaskStorage`] to run the given future. + pub fn initialize(self, future: impl FnOnce() -> F) -> SpawnToken { + self.initialize_impl::(future) } - /// Like spawn(), but allows the task to be send-spawned if the args are Send even if - /// the future is !Send. + /// Initialize the [`TaskStorage`] to run the given future. /// - /// Not covered by semver guarantees. DO NOT call this directly. Intended to be used - /// by the Embassy macros ONLY. + /// # Safety /// - /// SAFETY: `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn` + /// `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn` /// is an `async fn`, NOT a hand-written `Future`. #[doc(hidden)] - pub unsafe fn _spawn_async_fn(&'static self, future: FutFn) -> SpawnToken - where - FutFn: FnOnce() -> F, - { + pub unsafe fn __initialize_async_fn(self, future: impl FnOnce() -> F) -> SpawnToken { // When send-spawning a task, we construct the future in this thread, and effectively // "send" it to the executor thread by enqueuing it in its queue. Therefore, in theory, // send-spawning should require the future `F` to be `Send`. @@ -279,66 +249,73 @@ impl TaskPool { // // This ONLY holds for `async fn` futures. The other `spawn` methods can be called directly // by the user, with arbitrary hand-implemented futures. This is why these return `SpawnToken`. + self.initialize_impl::(future) + } +} - let task = self.pool.iter().find_map(AvailableTask::claim); - match task { - Some(task) => { - let task = task.initialize(future); - unsafe { SpawnToken::::new(task) } - } +/// Raw storage that can hold up to N tasks of the same type. +/// +/// This is essentially a `[TaskStorage; N]`. +pub struct TaskPool { + pool: [TaskStorage; N], +} + +impl TaskPool { + /// Create a new TaskPool, with all tasks in non-spawned state. + pub const fn new() -> Self { + Self { + pool: [TaskStorage::NEW; N], + } + } + + fn spawn_impl(&'static self, future: impl FnOnce() -> F) -> SpawnToken { + match self.pool.iter().find_map(AvailableTask::claim) { + Some(task) => task.initialize_impl::(future), None => SpawnToken::new_failed(), } } + + /// Try to spawn a task in the pool. + /// + /// See [`TaskStorage::spawn()`] for details. + /// + /// This will loop over the pool and spawn the task in the first storage that + /// is currently free. If none is free, a "poisoned" SpawnToken is returned, + /// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error. + pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken { + self.spawn_impl::(future) + } + + /// Like spawn(), but allows the task to be send-spawned if the args are Send even if + /// the future is !Send. + /// + /// Not covered by semver guarantees. DO NOT call this directly. Intended to be used + /// by the Embassy macros ONLY. + /// + /// SAFETY: `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn` + /// is an `async fn`, NOT a hand-written `Future`. + #[doc(hidden)] + pub unsafe fn _spawn_async_fn(&'static self, future: FutFn) -> SpawnToken + where + FutFn: FnOnce() -> F, + { + // See the comment in AvailableTask::__initialize_async_fn for explanation. + self.spawn_impl::(future) + } } #[derive(Clone, Copy)] -pub(crate) enum PenderInner { - #[cfg(feature = "executor-thread")] - Thread(crate::arch::ThreadPender), - #[cfg(feature = "executor-interrupt")] - Interrupt(crate::arch::InterruptPender), - #[cfg(feature = "pender-callback")] - Callback { func: fn(*mut ()), context: *mut () }, -} +pub(crate) struct Pender(*mut ()); -unsafe impl Send for PenderInner {} -unsafe impl Sync for PenderInner {} - -/// Platform/architecture-specific action executed when an executor has pending work. -/// -/// When a task within an executor is woken, the `Pender` is called. This does a -/// platform/architecture-specific action to signal there is pending work in the executor. -/// When this happens, you must arrange for [`Executor::poll`] to be called. -/// -/// You can think of it as a waker, but for the whole executor. -pub struct Pender(pub(crate) PenderInner); +unsafe impl Send for Pender {} +unsafe impl Sync for Pender {} impl Pender { - /// Create a `Pender` that will call an arbitrary function pointer. - /// - /// # Arguments - /// - /// - `func`: The function pointer to call. - /// - `context`: Opaque context pointer, that will be passed to the function pointer. - #[cfg(feature = "pender-callback")] - pub fn new_from_callback(func: fn(*mut ()), context: *mut ()) -> Self { - Self(PenderInner::Callback { - func, - context: context.into(), - }) - } -} - -impl Pender { - pub(crate) fn pend(&self) { - match self.0 { - #[cfg(feature = "executor-thread")] - PenderInner::Thread(x) => x.pend(), - #[cfg(feature = "executor-interrupt")] - PenderInner::Interrupt(x) => x.pend(), - #[cfg(feature = "pender-callback")] - PenderInner::Callback { func, context } => func(context), + pub(crate) fn pend(self) { + extern "Rust" { + fn __pender(context: *mut ()); } + unsafe { __pender(self.0) }; } } @@ -409,7 +386,7 @@ impl SyncExecutor { #[allow(clippy::never_loop)] loop { #[cfg(feature = "integrated-timers")] - self.timer_queue.dequeue_expired(Instant::now(), |task| wake_task(task)); + self.timer_queue.dequeue_expired(Instant::now(), wake_task_no_pend); self.run_queue.dequeue_all(|p| { let task = p.header(); @@ -472,15 +449,31 @@ impl SyncExecutor { /// /// - To get the executor to do work, call `poll()`. This will poll all queued tasks (all tasks /// that "want to run"). -/// - You must supply a [`Pender`]. The executor will call it to notify you it has work -/// to do. You must arrange for `poll()` to be called as soon as possible. +/// - You must supply a pender function, as shown below. The executor will call it to notify you +/// it has work to do. You must arrange for `poll()` to be called as soon as possible. +/// - Enabling `arch-xx` features will define a pender function for you. This means that you +/// are limited to using the executors provided to you by the architecture/platform +/// implementation. If you need a different executor, you must not enable `arch-xx` features. /// -/// The [`Pender`] can be called from *any* context: any thread, any interrupt priority +/// The pender can be called from *any* context: any thread, any interrupt priority /// level, etc. It may be called synchronously from any `Executor` method call as well. /// You must deal with this correctly. /// /// In particular, you must NOT call `poll` directly from the pender callback, as this violates /// the requirement for `poll` to not be called reentrantly. +/// +/// The pender function must be exported with the name `__pender` and have the following signature: +/// +/// ```rust +/// #[export_name = "__pender"] +/// fn pender(context: *mut ()) { +/// // schedule `poll()` to be called +/// } +/// ``` +/// +/// The `context` argument is a piece of arbitrary data the executor will pass to the pender. +/// You can set the `context` when calling [`Executor::new()`]. You can use it to, for example, +/// differentiate between executors, or to pass a pointer to a callback that should be called. #[repr(transparent)] pub struct Executor { pub(crate) inner: SyncExecutor, @@ -495,12 +488,12 @@ impl Executor { /// Create a new executor. /// - /// When the executor has work to do, it will call the [`Pender`]. + /// When the executor has work to do, it will call the pender function and pass `context` to it. /// - /// See [`Executor`] docs for details on `Pender`. - pub fn new(pender: Pender) -> Self { + /// See [`Executor`] docs for details on the pender. + pub fn new(context: *mut ()) -> Self { Self { - inner: SyncExecutor::new(pender), + inner: SyncExecutor::new(Pender(context)), _not_sync: PhantomData, } } @@ -523,16 +516,16 @@ impl Executor { /// This loops over all tasks that are queued to be polled (i.e. they're /// freshly spawned or they've been woken). Other tasks are not polled. /// - /// You must call `poll` after receiving a call to the [`Pender`]. It is OK - /// to call `poll` even when not requested by the `Pender`, but it wastes + /// You must call `poll` after receiving a call to the pender. It is OK + /// to call `poll` even when not requested by the pender, but it wastes /// energy. /// /// # Safety /// /// You must NOT call `poll` reentrantly on the same executor. /// - /// In particular, note that `poll` may call the `Pender` synchronously. Therefore, you - /// must NOT directly call `poll()` from the `Pender` callback. Instead, the callback has to + /// In particular, note that `poll` may call the pender synchronously. Therefore, you + /// must NOT directly call `poll()` from the pender callback. Instead, the callback has to /// somehow schedule for `poll()` to be called later, at a time you know for sure there's /// no `poll()` already running. pub unsafe fn poll(&'static self) { @@ -573,6 +566,31 @@ pub fn wake_task(task: TaskRef) { } } +/// Wake a task by `TaskRef` without calling pend. +/// +/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`]. +pub fn wake_task_no_pend(task: TaskRef) { + let header = task.header(); + + let res = header.state.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |state| { + // If already scheduled, or if not started, + if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) { + None + } else { + // Mark it as scheduled + Some(state | STATE_RUN_QUEUED) + } + }); + + if res.is_ok() { + // We have just marked the task as scheduled, so enqueue it. + unsafe { + let executor = header.executor.get().unwrap_unchecked(); + executor.run_queue.enqueue(task); + } + } +} + #[cfg(feature = "integrated-timers")] struct TimerQueue; diff --git a/embassy-executor/src/spawner.rs b/embassy-executor/src/spawner.rs index 2b622404..5a3a0dee 100644 --- a/embassy-executor/src/spawner.rs +++ b/embassy-executor/src/spawner.rs @@ -33,7 +33,8 @@ impl SpawnToken { } } - pub(crate) fn new_failed() -> Self { + /// Return a SpawnToken that represents a failed spawn. + pub fn new_failed() -> Self { Self { raw_task: None, phantom: PhantomData, diff --git a/embassy-lora/Cargo.toml b/embassy-lora/Cargo.toml index 402ad2d7..feea0658 100644 --- a/embassy-lora/Cargo.toml +++ b/embassy-lora/Cargo.toml @@ -23,9 +23,12 @@ log = { version = "0.4.14", optional = true } embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true } embassy-sync = { version = "0.2.0", path = "../embassy-sync" } embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false, optional = true } -embedded-hal-async = { version = "=0.2.0-alpha.2" } +embedded-hal-async = { version = "=1.0.0-rc.1" } embedded-hal = { version = "0.2", features = ["unproven"] } futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] } lora-phy = { version = "1" } lorawan-device = { version = "0.10.0", default-features = false, features = ["async"], optional = true } + +[patch.crates-io] +lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "1323eccc1c470d4259f95f4f315d1be830d572a3"} \ No newline at end of file diff --git a/embassy-net-driver-channel/README.md b/embassy-net-driver-channel/README.md index dd90e7ad..8f904ce9 100644 --- a/embassy-net-driver-channel/README.md +++ b/embassy-net-driver-channel/README.md @@ -76,7 +76,7 @@ These `embassy-net` drivers are implemented using this crate. You can look at th - [`cyw43`](https://github.com/embassy-rs/embassy/tree/main/cyw43) for WiFi on CYW43xx chips, used in the Raspberry Pi Pico W - [`embassy-usb`](https://github.com/embassy-rs/embassy/tree/main/embassy-usb) for Ethernet-over-USB (CDC NCM) support. -- [`embassy-net-w5500`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-w5500) for Wiznet W5500 SPI Ethernet MAC+PHY chip. +- [`embassy-net-wiznet`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-wiznet) for Wiznet SPI Ethernet MAC+PHY chips. - [`embassy-net-esp-hosted`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-esp-hosted) for using ESP32 chips with the [`esp-hosted`](https://github.com/espressif/esp-hosted) firmware as WiFi adapters for another non-ESP32 MCU. diff --git a/embassy-net-enc28j60/Cargo.toml b/embassy-net-enc28j60/Cargo.toml new file mode 100644 index 00000000..e02c984e --- /dev/null +++ b/embassy-net-enc28j60/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "embassy-net-enc28j60" +version = "0.1.0" +description = "embassy-net driver for the ENC28J60 ethernet chip" +keywords = ["embedded", "enc28j60", "embassy-net", "embedded-hal-async", "ethernet", "async"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"] +license = "MIT OR Apache-2.0" +edition = "2021" + +[dependencies] +embedded-hal = { version = "1.0.0-rc.1" } +embedded-hal-async = { version = "=1.0.0-rc.1" } +embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" } +embassy-time = { version = "0.1.2", path = "../embassy-time" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } + +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-enc28j60-v$VERSION/embassy-net-enc28j60/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-enc28j60/src/" +target = "thumbv7em-none-eabi" \ No newline at end of file diff --git a/embassy-net-enc28j60/README.md b/embassy-net-enc28j60/README.md new file mode 100644 index 00000000..39011ca1 --- /dev/null +++ b/embassy-net-enc28j60/README.md @@ -0,0 +1,19 @@ +# `embassy-net-enc28j60` + +[`embassy-net`](https://crates.io/crates/embassy-net) integration for the Microchip ENC28J60 Ethernet chip. + +Based on [@japaric](https://github.com/japaric)'s [`enc28j60`](https://github.com/japaric/enc28j60) crate. + +## Interoperability + +This crate can run on any executor. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. diff --git a/embassy-net-enc28j60/src/bank0.rs b/embassy-net-enc28j60/src/bank0.rs new file mode 100644 index 00000000..1c1b3a7f --- /dev/null +++ b/embassy-net-enc28j60/src/bank0.rs @@ -0,0 +1,69 @@ +#[allow(dead_code)] +#[derive(Clone, Copy)] +pub enum Register { + ERDPTL = 0x00, + ERDPTH = 0x01, + EWRPTL = 0x02, + EWRPTH = 0x03, + ETXSTL = 0x04, + ETXSTH = 0x05, + ETXNDL = 0x06, + ETXNDH = 0x07, + ERXSTL = 0x08, + ERXSTH = 0x09, + ERXNDL = 0x0a, + ERXNDH = 0x0b, + ERXRDPTL = 0x0c, + ERXRDPTH = 0x0d, + ERXWRPTL = 0x0e, + ERXWRPTH = 0x0f, + EDMASTL = 0x10, + EDMASTH = 0x11, + EDMANDL = 0x12, + EDMANDH = 0x13, + EDMADSTL = 0x14, + EDMADSTH = 0x15, + EDMACSL = 0x16, + EDMACSH = 0x17, +} + +impl Register { + pub(crate) fn addr(&self) -> u8 { + *self as u8 + } + + pub(crate) fn is_eth_register(&self) -> bool { + match *self { + Register::ERDPTL => true, + Register::ERDPTH => true, + Register::EWRPTL => true, + Register::EWRPTH => true, + Register::ETXSTL => true, + Register::ETXSTH => true, + Register::ETXNDL => true, + Register::ETXNDH => true, + Register::ERXSTL => true, + Register::ERXSTH => true, + Register::ERXNDL => true, + Register::ERXNDH => true, + Register::ERXRDPTL => true, + Register::ERXRDPTH => true, + Register::ERXWRPTL => true, + Register::ERXWRPTH => true, + Register::EDMASTL => true, + Register::EDMASTH => true, + Register::EDMANDL => true, + Register::EDMANDH => true, + Register::EDMADSTL => true, + Register::EDMADSTH => true, + Register::EDMACSL => true, + Register::EDMACSH => true, + } + } +} + +impl Into for Register { + fn into(self) -> super::Register { + super::Register::Bank0(self) + } +} diff --git a/embassy-net-enc28j60/src/bank1.rs b/embassy-net-enc28j60/src/bank1.rs new file mode 100644 index 00000000..30560edf --- /dev/null +++ b/embassy-net-enc28j60/src/bank1.rs @@ -0,0 +1,84 @@ +#[allow(dead_code)] +#[derive(Clone, Copy)] +pub enum Register { + EHT0 = 0x00, + EHT1 = 0x01, + EHT2 = 0x02, + EHT3 = 0x03, + EHT4 = 0x04, + EHT5 = 0x05, + EHT6 = 0x06, + EHT7 = 0x07, + EPMM0 = 0x08, + EPMM1 = 0x09, + EPMM2 = 0x0a, + EPMM3 = 0x0b, + EPMM4 = 0x0c, + EPMM5 = 0x0d, + EPMM6 = 0x0e, + EPMM7 = 0x0f, + EPMCSL = 0x10, + EPMCSH = 0x11, + EPMOL = 0x14, + EPMOH = 0x15, + ERXFCON = 0x18, + EPKTCNT = 0x19, +} + +impl Register { + pub(crate) fn addr(&self) -> u8 { + *self as u8 + } + + pub(crate) fn is_eth_register(&self) -> bool { + match *self { + Register::EHT0 => true, + Register::EHT1 => true, + Register::EHT2 => true, + Register::EHT3 => true, + Register::EHT4 => true, + Register::EHT5 => true, + Register::EHT6 => true, + Register::EHT7 => true, + Register::EPMM0 => true, + Register::EPMM1 => true, + Register::EPMM2 => true, + Register::EPMM3 => true, + Register::EPMM4 => true, + Register::EPMM5 => true, + Register::EPMM6 => true, + Register::EPMM7 => true, + Register::EPMCSL => true, + Register::EPMCSH => true, + Register::EPMOL => true, + Register::EPMOH => true, + Register::ERXFCON => true, + Register::EPKTCNT => true, + } + } +} + +impl Into for Register { + fn into(self) -> super::Register { + super::Register::Bank1(self) + } +} + +register!(ERXFCON, 0b1010_0001, u8, { + #[doc = "Broadcast Filter Enable bit"] + bcen @ 0, + #[doc = "Multicast Filter Enable bit"] + mcen @ 1, + #[doc = "Hash Table Filter Enable bit"] + hten @ 2, + #[doc = "Magic Packetâ„¢ Filter Enable bit"] + mpen @ 3, + #[doc = "Pattern Match Filter Enable bit"] + pmen @ 4, + #[doc = "Post-Filter CRC Check Enable bit"] + crcen @ 5, + #[doc = "AND/OR Filter Select bit"] + andor @ 6, + #[doc = "Unicast Filter Enable bit"] + ucen @ 7, +}); diff --git a/embassy-net-enc28j60/src/bank2.rs b/embassy-net-enc28j60/src/bank2.rs new file mode 100644 index 00000000..74a1d245 --- /dev/null +++ b/embassy-net-enc28j60/src/bank2.rs @@ -0,0 +1,86 @@ +#[allow(dead_code)] +#[derive(Clone, Copy)] +pub enum Register { + MACON1 = 0x00, + MACON3 = 0x02, + MACON4 = 0x03, + MABBIPG = 0x04, + MAIPGL = 0x06, + MAIPGH = 0x07, + MACLCON1 = 0x08, + MACLCON2 = 0x09, + MAMXFLL = 0x0a, + MAMXFLH = 0x0b, + MICMD = 0x12, + MIREGADR = 0x14, + MIWRL = 0x16, + MIWRH = 0x17, + MIRDL = 0x18, + MIRDH = 0x19, +} + +impl Register { + pub(crate) fn addr(&self) -> u8 { + *self as u8 + } + + pub(crate) fn is_eth_register(&self) -> bool { + match *self { + Register::MACON1 => false, + Register::MACON3 => false, + Register::MACON4 => false, + Register::MABBIPG => false, + Register::MAIPGL => false, + Register::MAIPGH => false, + Register::MACLCON1 => false, + Register::MACLCON2 => false, + Register::MAMXFLL => false, + Register::MAMXFLH => false, + Register::MICMD => false, + Register::MIREGADR => false, + Register::MIWRL => false, + Register::MIWRH => false, + Register::MIRDL => false, + Register::MIRDH => false, + } + } +} + +impl Into for Register { + fn into(self) -> super::Register { + super::Register::Bank2(self) + } +} + +register!(MACON1, 0, u8, { + #[doc = "Enable packets to be received by the MAC"] + marxen @ 0, + #[doc = "Control frames will be discarded after being processed by the MAC"] + passall @ 1, + #[doc = "Inhibit transmissions when pause control frames are received"] + rxpaus @ 2, + #[doc = "Allow the MAC to transmit pause control frames"] + txpaus @ 3, +}); + +register!(MACON3, 0, u8, { + #[doc = "MAC will operate in Full-Duplex mode"] + fuldpx @ 0, + #[doc = "The type/length field of transmitted and received frames will be checked"] + frmlnen @ 1, + #[doc = "Frames bigger than MAMXFL will be aborted when transmitted or received"] + hfrmen @ 2, + #[doc = "No proprietary header is present"] + phdren @ 3, + #[doc = "MAC will append a valid CRC to all frames transmitted regardless of PADCFG bit"] + txcrcen @ 4, + #[doc = "All short frames will be zero-padded to 64 bytes and a valid CRC will then be appended"] + padcfg @ 5..7, +}); + +register!(MICMD, 0, u8, { + #[doc = "MII Read Enable bit"] + miird @ 0, + #[doc = "MII Scan Enable bit"] + miiscan @ 1, +}); diff --git a/embassy-net-enc28j60/src/bank3.rs b/embassy-net-enc28j60/src/bank3.rs new file mode 100644 index 00000000..4f7eb940 --- /dev/null +++ b/embassy-net-enc28j60/src/bank3.rs @@ -0,0 +1,53 @@ +#[allow(dead_code)] +#[derive(Clone, Copy)] +pub enum Register { + MAADR5 = 0x00, + MAADR6 = 0x01, + MAADR3 = 0x02, + MAADR4 = 0x03, + MAADR1 = 0x04, + MAADR2 = 0x05, + EBSTSD = 0x06, + EBSTCON = 0x07, + EBSTCSL = 0x08, + EBSTCSH = 0x09, + MISTAT = 0x0a, + EREVID = 0x12, + ECOCON = 0x15, + EFLOCON = 0x17, + EPAUSL = 0x18, + EPAUSH = 0x19, +} + +impl Register { + pub(crate) fn addr(&self) -> u8 { + *self as u8 + } + + pub(crate) fn is_eth_register(&self) -> bool { + match *self { + Register::MAADR5 => false, + Register::MAADR6 => false, + Register::MAADR3 => false, + Register::MAADR4 => false, + Register::MAADR1 => false, + Register::MAADR2 => false, + Register::EBSTSD => true, + Register::EBSTCON => true, + Register::EBSTCSL => true, + Register::EBSTCSH => true, + Register::MISTAT => false, + Register::EREVID => true, + Register::ECOCON => true, + Register::EFLOCON => true, + Register::EPAUSL => true, + Register::EPAUSH => true, + } + } +} + +impl Into for Register { + fn into(self) -> super::Register { + super::Register::Bank3(self) + } +} diff --git a/embassy-net-enc28j60/src/common.rs b/embassy-net-enc28j60/src/common.rs new file mode 100644 index 00000000..ef339dd2 --- /dev/null +++ b/embassy-net-enc28j60/src/common.rs @@ -0,0 +1,106 @@ +#[allow(dead_code)] +#[derive(Clone, Copy)] +pub enum Register { + ECON1 = 0x1f, + ECON2 = 0x1e, + EIE = 0x1b, + EIR = 0x1c, + ESTAT = 0x1d, +} + +impl Register { + pub(crate) fn addr(&self) -> u8 { + *self as u8 + } + + pub(crate) fn is_eth_register(&self) -> bool { + match *self { + Register::ECON1 => true, + Register::ECON2 => true, + Register::EIE => true, + Register::EIR => true, + Register::ESTAT => true, + } + } +} + +impl Into for Register { + fn into(self) -> super::Register { + super::Register::Common(self) + } +} + +register!(EIE, 0, u8, { + #[doc = "Receive Error Interrupt Enable bit"] + rxerie @ 0, + #[doc = "Transmit Error Interrupt Enable bit"] + txerie @ 1, + #[doc = "Transmit Enable bit"] + txie @ 3, + #[doc = "Link Status Change Interrupt Enable bit"] + linkie @ 4, + #[doc = "DMA Interrupt Enable bit"] + dmaie @ 5, + #[doc = "Receive Packet Pending Interrupt Enable bit"] + pktie @ 6, + #[doc = "Global INT Interrupt Enable bit"] + intie @ 7, +}); + +register!(EIR, 0, u8, { + #[doc = "Receive Error Interrupt Flag bit"] + rxerif @ 0, + #[doc = "Transmit Error Interrupt Flag bit"] + txerif @ 1, + #[doc = "Transmit Interrupt Flag bit"] + txif @ 3, + #[doc = "Link Change Interrupt Flag bit"] + linkif @ 4, + #[doc = "DMA Interrupt Flag bit"] + dmaif @ 5, + #[doc = "Receive Packet Pending Interrupt Flag bit"] + pktif @ 6, +}); + +register!(ESTAT, 0, u8, { + #[doc = "Clock Ready bit"] + clkrdy @ 0, + #[doc = "Transmit Abort Error bit"] + txabrt @ 1, + #[doc = "Receive Busy bit"] + rxbusy @ 2, + #[doc = "Late Collision Error bit"] + latecol @ 4, + #[doc = "Ethernet Buffer Error Status bit"] + bufer @ 6, + #[doc = "INT Interrupt Flag bit"] + int @ 7, +}); + +register!(ECON2, 0b1000_0000, u8, { + #[doc = "Voltage Regulator Power Save Enable bit"] + vrps @ 3, + #[doc = "Power Save Enable bit"] + pwrsv @ 5, + #[doc = "Packet Decrement bit"] + pktdec @ 6, + #[doc = "Automatic Buffer Pointer Increment Enable bit"] + autoinc @ 7, +}); + +register!(ECON1, 0, u8, { + #[doc = "Bank Select bits"] + bsel @ 0..1, + #[doc = "Receive Enable bi"] + rxen @ 2, + #[doc = "Transmit Request to Send bit"] + txrts @ 3, + #[doc = "DMA Checksum Enable bit"] + csumen @ 4, + #[doc = "DMA Start and Busy Status bit"] + dmast @ 5, + #[doc = "Receive Logic Reset bit"] + rxrst @ 6, + #[doc = "Transmit Logic Reset bit"] + txrst @ 7, +}); diff --git a/embassy-net-enc28j60/src/fmt.rs b/embassy-net-enc28j60/src/fmt.rs new file mode 100644 index 00000000..06697081 --- /dev/null +++ b/embassy-net-enc28j60/src/fmt.rs @@ -0,0 +1,225 @@ +#![macro_use] +#![allow(unused_macros)] + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} diff --git a/embassy-net-enc28j60/src/header.rs b/embassy-net-enc28j60/src/header.rs new file mode 100644 index 00000000..c2d4e468 --- /dev/null +++ b/embassy-net-enc28j60/src/header.rs @@ -0,0 +1,30 @@ +register!(RxStatus, 0, u32, { + #[doc = "Indicates length of the received frame"] + byte_count @ 0..15, + #[doc = "Indicates a packet over 50,000 bit times occurred or that a packet was dropped since the last receive"] + long_event @ 16, + #[doc = "Indicates that at some time since the last receive, a carrier event was detected"] + carrier_event @ 18, + #[doc = "Indicates that frame CRC field value does not match the CRC calculated by the MAC"] + crc_error @ 20, + #[doc = "Indicates that frame length field value in the packet does not match the actual data byte length and specifies a valid length"] + length_check_error @ 21, + #[doc = "Indicates that frame type/length field was larger than 1500 bytes (type field)"] + length_out_of_range @ 22, + #[doc = "Indicates that at the packet had a valid CRC and no symbol errors"] + received_ok @ 23, + #[doc = "Indicates packet received had a valid Multicast address"] + multicast @ 24, + #[doc = "Indicates packet received had a valid Broadcast address."] + broadcast @ 25, + #[doc = "Indicates that after the end of this packet, an additional 1 to 7 bits were received"] + dribble_nibble @ 26, + #[doc = "Current frame was recognized as a control frame for having a valid type/length designating it as a control frame"] + receive_control_frame @ 27, + #[doc = "Current frame was recognized as a control frame containing a valid pause frame opcode and a valid destination address"] + receive_pause_control_frame @ 28, + #[doc = "Current frame was recognized as a control frame but it contained an unknown opcode"] + receive_unknown_opcode @ 29, + #[doc = "Current frame was recognized as a VLAN tagged frame"] + receive_vlan_type_detected @ 30, +}); diff --git a/embassy-net-enc28j60/src/lib.rs b/embassy-net-enc28j60/src/lib.rs new file mode 100644 index 00000000..b44cefaf --- /dev/null +++ b/embassy-net-enc28j60/src/lib.rs @@ -0,0 +1,717 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +// must go first. +mod fmt; + +#[macro_use] +mod macros; +mod bank0; +mod bank1; +mod bank2; +mod bank3; +mod common; +mod header; +mod phy; +mod traits; + +use core::cmp; +use core::convert::TryInto; + +use embassy_net_driver::{Capabilities, HardwareAddress, LinkState, Medium}; +use embassy_time::Duration; +use embedded_hal::digital::OutputPin; +use embedded_hal::spi::{Operation, SpiDevice}; +use traits::U16Ext; + +// Total buffer size (see section 3.2) +const BUF_SZ: u16 = 8 * 1024; + +// Maximum frame length +const MAX_FRAME_LENGTH: u16 = 1518; // value recommended in the data sheet + +// Size of the Frame check sequence (32-bit CRC) +const CRC_SZ: u16 = 4; + +// define the boundaries of the TX and RX buffers +// to workaround errata #5 we do the opposite of what section 6.1 of the data sheet +// says: we place the RX buffer at address 0 and the TX buffer after it +const RXST: u16 = 0x0000; +const RXND: u16 = 0x19ff; +const TXST: u16 = 0x1a00; +const _TXND: u16 = 0x1fff; + +const MTU: usize = 1514; // 1500 IP + 14 ethernet header + +/// ENC28J60 embassy-net driver +pub struct Enc28j60 { + mac_addr: [u8; 6], + + spi: S, + rst: Option, + + bank: Bank, + + // address of the next packet in buffer memory + next_packet: u16, +} + +impl Enc28j60 +where + S: SpiDevice, + O: OutputPin, +{ + /// Create a new ENC28J60 driver instance. + /// + /// The RST pin is optional. If None, reset will be done with a SPI + /// soft reset command, instead of via the RST pin. + pub fn new(spi: S, rst: Option, mac_addr: [u8; 6]) -> Self { + let mut res = Self { + mac_addr, + spi, + rst, + + bank: Bank::Bank0, + next_packet: RXST, + }; + res.init(); + res + } + + fn init(&mut self) { + if let Some(rst) = &mut self.rst { + rst.set_low().unwrap(); + embassy_time::block_for(Duration::from_millis(5)); + rst.set_high().unwrap(); + embassy_time::block_for(Duration::from_millis(5)); + } else { + embassy_time::block_for(Duration::from_millis(5)); + self.soft_reset(); + embassy_time::block_for(Duration::from_millis(5)); + } + + debug!( + "enc28j60: erevid {=u8:x}", + self.read_control_register(bank3::Register::EREVID) + ); + debug!("enc28j60: waiting for clk"); + while common::ESTAT(self.read_control_register(common::Register::ESTAT)).clkrdy() == 0 {} + debug!("enc28j60: clk ok"); + + if self.read_control_register(bank3::Register::EREVID) == 0 { + panic!("ErevidIsZero"); + } + + // disable CLKOUT output + self.write_control_register(bank3::Register::ECOCON, 0); + + self.init_rx(); + + // TX start + // "It is recommended that an even address be used for ETXST" + debug_assert_eq!(TXST % 2, 0); + self.write_control_register(bank0::Register::ETXSTL, TXST.low()); + self.write_control_register(bank0::Register::ETXSTH, TXST.high()); + + // TX end is set in `transmit` + + // MAC initialization (see section 6.5) + // 1. Set the MARXEN bit in MACON1 to enable the MAC to receive frames. + self.write_control_register( + bank2::Register::MACON1, + bank2::MACON1::default().marxen(1).passall(0).rxpaus(1).txpaus(1).bits(), + ); + + // 2. Configure the PADCFG, TXCRCEN and FULDPX bits of MACON3. + self.write_control_register( + bank2::Register::MACON3, + bank2::MACON3::default().frmlnen(1).txcrcen(1).padcfg(0b001).bits(), + ); + + // 4. Program the MAMXFL registers with the maximum frame length to be permitted to be + // received or transmitted + self.write_control_register(bank2::Register::MAMXFLL, MAX_FRAME_LENGTH.low()); + self.write_control_register(bank2::Register::MAMXFLH, MAX_FRAME_LENGTH.high()); + + // 5. Configure the Back-to-Back Inter-Packet Gap register, MABBIPG. + // Use recommended value of 0x12 + self.write_control_register(bank2::Register::MABBIPG, 0x12); + + // 6. Configure the Non-Back-to-Back Inter-Packet Gap register low byte, MAIPGL. + // Use recommended value of 0x12 + self.write_control_register(bank2::Register::MAIPGL, 0x12); + self.write_control_register(bank2::Register::MAIPGH, 0x0c); + + // 9. Program the local MAC address into the MAADR1:MAADR6 registers + self.write_control_register(bank3::Register::MAADR1, self.mac_addr[0]); + self.write_control_register(bank3::Register::MAADR2, self.mac_addr[1]); + self.write_control_register(bank3::Register::MAADR3, self.mac_addr[2]); + self.write_control_register(bank3::Register::MAADR4, self.mac_addr[3]); + self.write_control_register(bank3::Register::MAADR5, self.mac_addr[4]); + self.write_control_register(bank3::Register::MAADR6, self.mac_addr[5]); + + // Set the PHCON2.HDLDIS bit to prevent automatic loopback of the data which is transmitted + self.write_phy_register(phy::Register::PHCON2, phy::PHCON2::default().hdldis(1).bits()); + + // Globally enable interrupts + //self.bit_field_set(common::Register::EIE, common::EIE::mask().intie()); + + // Set the per packet control byte; we'll always use the value 0 + self.write_buffer_memory(Some(TXST), &mut [0]); + + // Enable reception + self.bit_field_set(common::Register::ECON1, common::ECON1::mask().rxen()); + } + + fn init_rx(&mut self) { + // RX start + // "It is recommended that the ERXST Pointer be programmed with an even address" + self.write_control_register(bank0::Register::ERXSTL, RXST.low()); + self.write_control_register(bank0::Register::ERXSTH, RXST.high()); + + // RX read pointer + // NOTE Errata #14 so we are using an *odd* address here instead of ERXST + self.write_control_register(bank0::Register::ERXRDPTL, RXND.low()); + self.write_control_register(bank0::Register::ERXRDPTH, RXND.high()); + + // RX end + self.write_control_register(bank0::Register::ERXNDL, RXND.low()); + self.write_control_register(bank0::Register::ERXNDH, RXND.high()); + + // decrease the packet count to 0 + while self.read_control_register(bank1::Register::EPKTCNT) != 0 { + self.bit_field_set(common::Register::ECON2, common::ECON2::mask().pktdec()); + } + + self.next_packet = RXST; + } + + fn reset_rx(&mut self) { + self.bit_field_set(common::Register::ECON1, common::ECON1::mask().rxrst()); + self.bit_field_clear(common::Register::ECON1, common::ECON1::mask().rxrst()); + self.init_rx(); + self.bit_field_set(common::Register::ECON1, common::ECON1::mask().rxen()); + } + + /// Flushes the transmit buffer, ensuring all pending transmissions have completed + /// NOTE: The returned packet *must* be `read` or `ignore`-d, otherwise this method will always + /// return `None` on subsequent invocations + pub fn receive<'a>(&mut self, buf: &'a mut [u8]) -> Option<&'a mut [u8]> { + if self.pending_packets() == 0 { + // Errata #6: we can't rely on PKTIF so we check PKTCNT + return None; + } + + let curr_packet = self.next_packet; + + // read out the first 6 bytes + let mut temp_buf = [0; 6]; + self.read_buffer_memory(Some(curr_packet), &mut temp_buf); + + // next packet pointer + let next_packet = u16::from_parts(temp_buf[0], temp_buf[1]); + // status vector + let status = header::RxStatus(u32::from_le_bytes(temp_buf[2..].try_into().unwrap())); + let len_with_crc = status.byte_count() as u16; + + if len_with_crc < CRC_SZ || len_with_crc > 1600 || next_packet > RXND { + warn!("RX buffer corrupted, resetting RX logic to recover..."); + self.reset_rx(); + return None; + } + + let len = len_with_crc - CRC_SZ; + self.read_buffer_memory(None, &mut buf[..len as usize]); + + // update ERXRDPT + // due to Errata #14 we must write an odd address to ERXRDPT + // we know that ERXST = 0, that ERXND is odd and that next_packet is even + let rxrdpt = if self.next_packet < 1 || self.next_packet > RXND + 1 { + RXND + } else { + self.next_packet - 1 + }; + // "To move ERXRDPT, the host controller must write to ERXRDPTL first." + self.write_control_register(bank0::Register::ERXRDPTL, rxrdpt.low()); + self.write_control_register(bank0::Register::ERXRDPTH, rxrdpt.high()); + + // decrease the packet count + self.bit_field_set(common::Register::ECON2, common::ECON2::mask().pktdec()); + + self.next_packet = next_packet; + + Some(&mut buf[..len as usize]) + } + + fn wait_tx_ready(&mut self) { + for _ in 0u32..10000 { + if common::ECON1(self.read_control_register(common::Register::ECON1)).txrts() == 0 { + return; + } + } + + // work around errata #12 by resetting the transmit logic before every new + // transmission + self.bit_field_set(common::Register::ECON1, common::ECON1::mask().txrst()); + self.bit_field_clear(common::Register::ECON1, common::ECON1::mask().txrst()); + //self.bit_field_clear(common::Register::EIR, { + // let mask = common::EIR::mask(); + // mask.txerif() | mask.txif() + //}); + } + + /// Starts the transmission of `bytes` + /// + /// It's up to the caller to ensure that `bytes` is a valid Ethernet frame. The interface will + /// take care of appending a (4 byte) CRC to the frame and of padding the frame to the minimum + /// size allowed by the Ethernet specification (64 bytes, or 46 bytes of payload). + /// + /// NOTE This method will flush any previous transmission that's in progress + /// + /// # Panics + /// + /// If `bytes` length is greater than 1514, the maximum frame length allowed by the interface, + /// or greater than the transmit buffer + pub fn transmit(&mut self, bytes: &[u8]) { + assert!(bytes.len() <= self.mtu() as usize); + + self.wait_tx_ready(); + + // NOTE the plus one is to not overwrite the per packet control byte + let wrpt = TXST + 1; + + // 1. ETXST was set during initialization + + // 2. write the frame to the IC memory + self.write_buffer_memory(Some(wrpt), bytes); + + let txnd = wrpt + bytes.len() as u16 - 1; + + // 3. Set the end address of the transmit buffer + self.write_control_register(bank0::Register::ETXNDL, txnd.low()); + self.write_control_register(bank0::Register::ETXNDH, txnd.high()); + + // 4. reset interrupt flag + //self.bit_field_clear(common::Register::EIR, common::EIR::mask().txif()); + + // 5. start transmission + self.bit_field_set(common::Register::ECON1, common::ECON1::mask().txrts()); + + // Wait until transmission finishes + //while common::ECON1(self.read_control_register(common::Register::ECON1)).txrts() == 1 {} + + /* + // read the transmit status vector + let mut tx_stat = [0; 7]; + self.read_buffer_memory(None, &mut tx_stat); + + let stat = common::ESTAT(self.read_control_register(common::Register::ESTAT)); + + if stat.txabrt() == 1 { + // work around errata #12 by reading the transmit status vector + if stat.latecol() == 1 || (tx_stat[2] & (1 << 5)) != 0 { + panic!("LateCollision") + } else { + panic!("TransmitAbort") + } + }*/ + } + + /// Get whether the link is up + pub fn is_link_up(&mut self) -> bool { + let bits = self.read_phy_register(phy::Register::PHSTAT2); + phy::PHSTAT2(bits).lstat() == 1 + } + + /// Returns the interface Maximum Transmission Unit (MTU) + /// + /// The value returned by this function will never exceed 1514 bytes. The actual value depends + /// on the memory assigned to the transmission buffer when initializing the device + pub fn mtu(&self) -> u16 { + cmp::min(BUF_SZ - RXND - 1, MAX_FRAME_LENGTH - CRC_SZ) + } + + /* Miscellaneous */ + /// Returns the number of packets that have been received but have not been processed yet + pub fn pending_packets(&mut self) -> u8 { + self.read_control_register(bank1::Register::EPKTCNT) + } + + /// Adjusts the receive filter to *accept* these packet types + pub fn accept(&mut self, packets: &[Packet]) { + let mask = bank1::ERXFCON::mask(); + let mut val = 0; + for packet in packets { + match packet { + Packet::Broadcast => val |= mask.bcen(), + Packet::Multicast => val |= mask.mcen(), + Packet::Unicast => val |= mask.ucen(), + } + } + + self.bit_field_set(bank1::Register::ERXFCON, val) + } + + /// Adjusts the receive filter to *ignore* these packet types + pub fn ignore(&mut self, packets: &[Packet]) { + let mask = bank1::ERXFCON::mask(); + let mut val = 0; + for packet in packets { + match packet { + Packet::Broadcast => val |= mask.bcen(), + Packet::Multicast => val |= mask.mcen(), + Packet::Unicast => val |= mask.ucen(), + } + } + + self.bit_field_clear(bank1::Register::ERXFCON, val) + } + + /* Private */ + /* Read */ + fn read_control_register(&mut self, register: R) -> u8 + where + R: Into, + { + self._read_control_register(register.into()) + } + + fn _read_control_register(&mut self, register: Register) -> u8 { + self.change_bank(register); + + if register.is_eth_register() { + let mut buffer = [Instruction::RCR.opcode() | register.addr(), 0]; + self.spi.transfer_in_place(&mut buffer).unwrap(); + buffer[1] + } else { + // MAC, MII regs need a dummy byte. + let mut buffer = [Instruction::RCR.opcode() | register.addr(), 0, 0]; + self.spi.transfer_in_place(&mut buffer).unwrap(); + buffer[2] + } + } + + fn read_phy_register(&mut self, register: phy::Register) -> u16 { + // set PHY register address + self.write_control_register(bank2::Register::MIREGADR, register.addr()); + + // start read operation + self.write_control_register(bank2::Register::MICMD, bank2::MICMD::default().miird(1).bits()); + + // wait until the read operation finishes + while self.read_control_register(bank3::Register::MISTAT) & 0b1 != 0 {} + + self.write_control_register(bank2::Register::MICMD, bank2::MICMD::default().miird(0).bits()); + + let l = self.read_control_register(bank2::Register::MIRDL); + let h = self.read_control_register(bank2::Register::MIRDH); + (l as u16) | (h as u16) << 8 + } + + /* Write */ + fn _write_control_register(&mut self, register: Register, value: u8) { + self.change_bank(register); + + let buffer = [Instruction::WCR.opcode() | register.addr(), value]; + self.spi.write(&buffer).unwrap(); + } + + fn write_control_register(&mut self, register: R, value: u8) + where + R: Into, + { + self._write_control_register(register.into(), value) + } + + fn write_phy_register(&mut self, register: phy::Register, value: u16) { + // set PHY register address + self.write_control_register(bank2::Register::MIREGADR, register.addr()); + + self.write_control_register(bank2::Register::MIWRL, (value & 0xff) as u8); + // this starts the write operation + self.write_control_register(bank2::Register::MIWRH, (value >> 8) as u8); + + // wait until the write operation finishes + while self.read_control_register(bank3::Register::MISTAT) & 0b1 != 0 {} + } + + /* RMW */ + fn modify_control_register(&mut self, register: R, f: F) + where + F: FnOnce(u8) -> u8, + R: Into, + { + self._modify_control_register(register.into(), f) + } + + fn _modify_control_register(&mut self, register: Register, f: F) + where + F: FnOnce(u8) -> u8, + { + let r = self._read_control_register(register); + self._write_control_register(register, f(r)) + } + + /* Auxiliary */ + fn change_bank(&mut self, register: Register) { + let bank = register.bank(); + + if let Some(bank) = bank { + if self.bank == bank { + // already on the register bank + return; + } + + // change bank + self.bank = bank; + match bank { + Bank::Bank0 => self.bit_field_clear(common::Register::ECON1, 0b11), + Bank::Bank1 => self.modify_control_register(common::Register::ECON1, |r| (r & !0b11) | 0b01), + Bank::Bank2 => self.modify_control_register(common::Register::ECON1, |r| (r & !0b11) | 0b10), + Bank::Bank3 => self.bit_field_set(common::Register::ECON1, 0b11), + } + } else { + // common register + } + } + + /* Primitive operations */ + fn bit_field_clear(&mut self, register: R, mask: u8) + where + R: Into, + { + self._bit_field_clear(register.into(), mask) + } + + fn _bit_field_clear(&mut self, register: Register, mask: u8) { + debug_assert!(register.is_eth_register()); + + self.change_bank(register); + + self.spi + .write(&[Instruction::BFC.opcode() | register.addr(), mask]) + .unwrap(); + } + + fn bit_field_set(&mut self, register: R, mask: u8) + where + R: Into, + { + self._bit_field_set(register.into(), mask) + } + + fn _bit_field_set(&mut self, register: Register, mask: u8) { + debug_assert!(register.is_eth_register()); + + self.change_bank(register); + + self.spi + .write(&[Instruction::BFS.opcode() | register.addr(), mask]) + .unwrap(); + } + + fn read_buffer_memory(&mut self, addr: Option, buf: &mut [u8]) { + if let Some(addr) = addr { + self.write_control_register(bank0::Register::ERDPTL, addr.low()); + self.write_control_register(bank0::Register::ERDPTH, addr.high()); + } + + self.spi + .transaction(&mut [Operation::Write(&[Instruction::RBM.opcode()]), Operation::Read(buf)]) + .unwrap(); + } + + fn soft_reset(&mut self) { + self.spi.write(&[Instruction::SRC.opcode()]).unwrap(); + } + + fn write_buffer_memory(&mut self, addr: Option, buffer: &[u8]) { + if let Some(addr) = addr { + self.write_control_register(bank0::Register::EWRPTL, addr.low()); + self.write_control_register(bank0::Register::EWRPTH, addr.high()); + } + + self.spi + .transaction(&mut [Operation::Write(&[Instruction::WBM.opcode()]), Operation::Write(buffer)]) + .unwrap(); + } +} + +#[derive(Clone, Copy, PartialEq)] +enum Bank { + Bank0, + Bank1, + Bank2, + Bank3, +} + +#[derive(Clone, Copy)] +enum Instruction { + /// Read Control Register + RCR = 0b000_00000, + /// Read Buffer Memory + RBM = 0b001_11010, + /// Write Control Register + WCR = 0b010_00000, + /// Write Buffer Memory + WBM = 0b011_11010, + /// Bit Field Set + BFS = 0b100_00000, + /// Bit Field Clear + BFC = 0b101_00000, + /// System Reset Command + SRC = 0b111_11111, +} + +impl Instruction { + fn opcode(&self) -> u8 { + *self as u8 + } +} + +#[derive(Clone, Copy)] +enum Register { + Bank0(bank0::Register), + Bank1(bank1::Register), + Bank2(bank2::Register), + Bank3(bank3::Register), + Common(common::Register), +} + +impl Register { + fn addr(&self) -> u8 { + match *self { + Register::Bank0(r) => r.addr(), + Register::Bank1(r) => r.addr(), + Register::Bank2(r) => r.addr(), + Register::Bank3(r) => r.addr(), + Register::Common(r) => r.addr(), + } + } + + fn bank(&self) -> Option { + Some(match *self { + Register::Bank0(_) => Bank::Bank0, + Register::Bank1(_) => Bank::Bank1, + Register::Bank2(_) => Bank::Bank2, + Register::Bank3(_) => Bank::Bank3, + Register::Common(_) => return None, + }) + } + + fn is_eth_register(&self) -> bool { + match *self { + Register::Bank0(r) => r.is_eth_register(), + Register::Bank1(r) => r.is_eth_register(), + Register::Bank2(r) => r.is_eth_register(), + Register::Bank3(r) => r.is_eth_register(), + Register::Common(r) => r.is_eth_register(), + } + } +} + +/// Packet type, used to configure receive filters +#[non_exhaustive] +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum Packet { + /// Broadcast packets + Broadcast, + /// Multicast packets + Multicast, + /// Unicast packets + Unicast, +} + +static mut TX_BUF: [u8; MTU] = [0; MTU]; +static mut RX_BUF: [u8; MTU] = [0; MTU]; + +impl embassy_net_driver::Driver for Enc28j60 +where + S: SpiDevice, + O: OutputPin, +{ + type RxToken<'a> = RxToken<'a> + where + Self: 'a; + + type TxToken<'a> = TxToken<'a, S, O> + where + Self: 'a; + + fn receive(&mut self, cx: &mut core::task::Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + let rx_buf = unsafe { &mut RX_BUF }; + let tx_buf = unsafe { &mut TX_BUF }; + if let Some(pkt) = self.receive(rx_buf) { + let n = pkt.len(); + Some((RxToken { buf: &mut pkt[..n] }, TxToken { buf: tx_buf, eth: self })) + } else { + cx.waker().wake_by_ref(); + None + } + } + + fn transmit(&mut self, _cx: &mut core::task::Context) -> Option> { + let tx_buf = unsafe { &mut TX_BUF }; + Some(TxToken { buf: tx_buf, eth: self }) + } + + fn link_state(&mut self, cx: &mut core::task::Context) -> LinkState { + cx.waker().wake_by_ref(); + match self.is_link_up() { + true => LinkState::Up, + false => LinkState::Down, + } + } + + fn capabilities(&self) -> Capabilities { + let mut caps = Capabilities::default(); + caps.max_transmission_unit = MTU; + caps.medium = Medium::Ethernet; + caps + } + + fn hardware_address(&self) -> HardwareAddress { + HardwareAddress::Ethernet(self.mac_addr) + } +} + +/// embassy-net RX token. +pub struct RxToken<'a> { + buf: &'a mut [u8], +} + +impl<'a> embassy_net_driver::RxToken for RxToken<'a> { + fn consume(self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + f(self.buf) + } +} + +/// embassy-net TX token. +pub struct TxToken<'a, S, O> +where + S: SpiDevice, + O: OutputPin, +{ + eth: &'a mut Enc28j60, + buf: &'a mut [u8], +} + +impl<'a, S, O> embassy_net_driver::TxToken for TxToken<'a, S, O> +where + S: SpiDevice, + O: OutputPin, +{ + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + assert!(len <= self.buf.len()); + let r = f(&mut self.buf[..len]); + self.eth.transmit(&self.buf[..len]); + r + } +} diff --git a/embassy-net-enc28j60/src/macros.rs b/embassy-net-enc28j60/src/macros.rs new file mode 100644 index 00000000..8d064957 --- /dev/null +++ b/embassy-net-enc28j60/src/macros.rs @@ -0,0 +1,89 @@ +macro_rules! register { + ($REGISTER:ident, $reset_value:expr, $uxx:ty, { + $(#[$($attr:tt)*] $bitfield:ident @ $range:expr,)+ + }) => { + #[derive(Clone, Copy)] + pub(crate) struct $REGISTER { + bits: $uxx, + _mode: ::core::marker::PhantomData, + } + + impl $REGISTER { + #[allow(dead_code)] + pub(crate) fn mask() -> $REGISTER { + $REGISTER { bits: 0, _mode: ::core::marker::PhantomData } + } + + $( + #[allow(dead_code)] + pub(crate) fn $bitfield(&self) -> $uxx { + use super::traits::OffsetSize; + + let size = $range.size(); + let offset = $range.offset(); + ((1 << size) - 1) << offset + } + )+ + } + + impl ::core::default::Default for $REGISTER { + fn default() -> Self { + $REGISTER { bits: $reset_value, _mode: ::core::marker::PhantomData } + } + } + + #[allow(non_snake_case)] + #[allow(dead_code)] + pub(crate) fn $REGISTER(bits: $uxx) -> $REGISTER { + $REGISTER { bits, _mode: ::core::marker::PhantomData } + } + + impl $REGISTER { + #[allow(dead_code)] + pub(crate) fn modify(self) -> $REGISTER { + $REGISTER { bits: self.bits, _mode: ::core::marker::PhantomData } + } + + $( + #[$($attr)*] + #[allow(dead_code)] + pub(crate) fn $bitfield(&self) -> $uxx { + use super::traits::OffsetSize; + + let offset = $range.offset(); + let size = $range.size(); + let mask = (1 << size) - 1; + + (self.bits >> offset) & mask + } + )+ + } + + impl $REGISTER { + #[allow(dead_code)] + pub(crate) fn bits(self) -> $uxx { + self.bits + } + + $( + #[$($attr)*] + #[allow(dead_code)] + pub(crate) fn $bitfield(&mut self, mut bits: $uxx) -> &mut Self { + use super::traits::OffsetSize; + + let offset = $range.offset(); + let size = $range.size(); + let mask = (1 << size) - 1; + + debug_assert!(bits <= mask); + bits &= mask; + + self.bits &= !(mask << offset); + self.bits |= bits << offset; + + self + } + )+ + } + } +} diff --git a/embassy-net-enc28j60/src/phy.rs b/embassy-net-enc28j60/src/phy.rs new file mode 100644 index 00000000..89144ada --- /dev/null +++ b/embassy-net-enc28j60/src/phy.rs @@ -0,0 +1,35 @@ +#[allow(dead_code)] +#[derive(Clone, Copy)] +pub enum Register { + PHCON1 = 0x00, + PHSTAT1 = 0x01, + PHID1 = 0x02, + PHID2 = 0x03, + PHCON2 = 0x10, + PHSTAT2 = 0x11, + PHIE = 0x12, + PHIR = 0x13, + PHLCON = 0x14, +} + +impl Register { + pub(crate) fn addr(&self) -> u8 { + *self as u8 + } +} + +register!(PHCON2, 0, u16, { + #[doc = "PHY Half-Duplex Loopback Disable bit"] + hdldis @ 8, + #[doc = "Jabber Correction Disable bit"] + jabber @ 10, + #[doc = "Twisted-Pair Transmitter Disable bit"] + txdis @ 13, + #[doc = "PHY Force Linkup bit"] + frclnk @ 14, +}); + +register!(PHSTAT2, 0, u16, { + #[doc = "Link Status bit"] + lstat @ 10, +}); diff --git a/embassy-net-enc28j60/src/traits.rs b/embassy-net-enc28j60/src/traits.rs new file mode 100644 index 00000000..08f94045 --- /dev/null +++ b/embassy-net-enc28j60/src/traits.rs @@ -0,0 +1,57 @@ +use core::ops::Range; + +pub(crate) trait OffsetSize { + fn offset(self) -> u8; + fn size(self) -> u8; +} + +impl OffsetSize for u8 { + fn offset(self) -> u8 { + self + } + + fn size(self) -> u8 { + 1 + } +} + +impl OffsetSize for Range { + fn offset(self) -> u8 { + self.start + } + + fn size(self) -> u8 { + self.end - self.start + } +} + +pub(crate) trait U16Ext { + fn from_parts(low: u8, high: u8) -> Self; + + fn low(self) -> u8; + + fn high(self) -> u8; +} + +impl U16Ext for u16 { + fn from_parts(low: u8, high: u8) -> u16 { + ((high as u16) << 8) + low as u16 + } + + fn low(self) -> u8 { + (self & 0xff) as u8 + } + + fn high(self) -> u8 { + (self >> 8) as u8 + } +} + +#[derive(Clone, Copy)] +pub struct Mask; + +#[derive(Clone, Copy)] +pub struct R; + +#[derive(Clone, Copy)] +pub struct W; diff --git a/embassy-net-esp-hosted/Cargo.toml b/embassy-net-esp-hosted/Cargo.toml index 0053c49a..d334cf3f 100644 --- a/embassy-net-esp-hosted/Cargo.toml +++ b/embassy-net-esp-hosted/Cargo.toml @@ -12,8 +12,8 @@ embassy-sync = { version = "0.2.0", path = "../embassy-sync"} embassy-futures = { version = "0.1.0", path = "../embassy-futures"} embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"} -embedded-hal = { version = "1.0.0-alpha.11" } -embedded-hal-async = { version = "=0.2.0-alpha.2" } +embedded-hal = { version = "1.0.0-rc.1" } +embedded-hal-async = { version = "=1.0.0-rc.1" } noproto = { git="https://github.com/embassy-rs/noproto", default-features = false, features = ["derive"] } #noproto = { version = "0.1", path = "/home/dirbaio/noproto", default-features = false, features = ["derive"] } diff --git a/embassy-net-esp-hosted/src/control.rs b/embassy-net-esp-hosted/src/control.rs index 37f220da..b8026611 100644 --- a/embassy-net-esp-hosted/src/control.rs +++ b/embassy-net-esp-hosted/src/control.rs @@ -5,9 +5,12 @@ use heapless::String; use crate::ioctl::Shared; use crate::proto::{self, CtrlMsg}; -#[derive(Debug)] -pub struct Error { - pub status: u32, +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + Failed(u32), + Timeout, + Internal, } pub struct Control<'a> { @@ -23,58 +26,78 @@ enum WifiMode { ApSta = 3, } +macro_rules! ioctl { + ($self:ident, $req_variant:ident, $resp_variant:ident, $req:ident, $resp:ident) => { + let mut msg = proto::CtrlMsg { + msg_id: proto::CtrlMsgId::$req_variant as _, + msg_type: proto::CtrlMsgType::Req as _, + payload: Some(proto::CtrlMsgPayload::$req_variant($req)), + }; + $self.ioctl(&mut msg).await?; + let Some(proto::CtrlMsgPayload::$resp_variant($resp)) = msg.payload else { + warn!("unexpected response variant"); + return Err(Error::Internal); + }; + if $resp.resp != 0 { + return Err(Error::Failed($resp.resp)); + } + }; +} + impl<'a> Control<'a> { pub(crate) fn new(state_ch: ch::StateRunner<'a>, shared: &'a Shared) -> Self { Self { state_ch, shared } } - pub async fn init(&mut self) { + pub async fn init(&mut self) -> Result<(), Error> { debug!("wait for init event..."); self.shared.init_wait().await; - debug!("set wifi mode"); - self.set_wifi_mode(WifiMode::Sta as _).await; + debug!("set heartbeat"); + self.set_heartbeat(10).await?; - let mac_addr = self.get_mac_addr().await; + debug!("set wifi mode"); + self.set_wifi_mode(WifiMode::Sta as _).await?; + + let mac_addr = self.get_mac_addr().await?; debug!("mac addr: {:02x}", mac_addr); self.state_ch.set_ethernet_address(mac_addr); + + Ok(()) } - pub async fn join(&mut self, ssid: &str, password: &str) { - let req = proto::CtrlMsg { - msg_id: proto::CtrlMsgId::ReqConnectAp as _, - msg_type: proto::CtrlMsgType::Req as _, - payload: Some(proto::CtrlMsgPayload::ReqConnectAp(proto::CtrlMsgReqConnectAp { - ssid: String::from(ssid), - pwd: String::from(password), - bssid: String::new(), - listen_interval: 3, - is_wpa3_supported: false, - })), + pub async fn connect(&mut self, ssid: &str, password: &str) -> Result<(), Error> { + let req = proto::CtrlMsgReqConnectAp { + ssid: String::from(ssid), + pwd: String::from(password), + bssid: String::new(), + listen_interval: 3, + is_wpa3_supported: false, }; - let resp = self.ioctl(req).await; - let proto::CtrlMsgPayload::RespConnectAp(resp) = resp.payload.unwrap() else { - panic!("unexpected resp") - }; - assert_eq!(resp.resp, 0); + ioctl!(self, ReqConnectAp, RespConnectAp, req, resp); self.state_ch.set_link_state(LinkState::Up); + Ok(()) } - async fn get_mac_addr(&mut self) -> [u8; 6] { - let req = proto::CtrlMsg { - msg_id: proto::CtrlMsgId::ReqGetMacAddress as _, - msg_type: proto::CtrlMsgType::Req as _, - payload: Some(proto::CtrlMsgPayload::ReqGetMacAddress( - proto::CtrlMsgReqGetMacAddress { - mode: WifiMode::Sta as _, - }, - )), + pub async fn disconnect(&mut self) -> Result<(), Error> { + let req = proto::CtrlMsgReqGetStatus {}; + ioctl!(self, ReqDisconnectAp, RespDisconnectAp, req, resp); + self.state_ch.set_link_state(LinkState::Up); + Ok(()) + } + + /// duration in seconds, clamped to [10, 3600] + async fn set_heartbeat(&mut self, duration: u32) -> Result<(), Error> { + let req = proto::CtrlMsgReqConfigHeartbeat { enable: true, duration }; + ioctl!(self, ReqConfigHeartbeat, RespConfigHeartbeat, req, resp); + Ok(()) + } + + async fn get_mac_addr(&mut self) -> Result<[u8; 6], Error> { + let req = proto::CtrlMsgReqGetMacAddress { + mode: WifiMode::Sta as _, }; - let resp = self.ioctl(req).await; - let proto::CtrlMsgPayload::RespGetMacAddress(resp) = resp.payload.unwrap() else { - panic!("unexpected resp") - }; - assert_eq!(resp.resp, 0); + ioctl!(self, ReqGetMacAddress, RespGetMacAddress, req, resp); // WHY IS THIS A STRING? WHYYYY fn nibble_from_hex(b: u8) -> u8 { @@ -88,32 +111,32 @@ impl<'a> Control<'a> { let mac = resp.mac.as_bytes(); let mut res = [0; 6]; - assert_eq!(mac.len(), 17); + if mac.len() != 17 { + warn!("unexpected MAC respnse length"); + return Err(Error::Internal); + } for (i, b) in res.iter_mut().enumerate() { *b = (nibble_from_hex(mac[i * 3]) << 4) | nibble_from_hex(mac[i * 3 + 1]) } - res + Ok(res) } - async fn set_wifi_mode(&mut self, mode: u32) { - let req = proto::CtrlMsg { - msg_id: proto::CtrlMsgId::ReqSetWifiMode as _, - msg_type: proto::CtrlMsgType::Req as _, - payload: Some(proto::CtrlMsgPayload::ReqSetWifiMode(proto::CtrlMsgReqSetMode { mode })), - }; - let resp = self.ioctl(req).await; - let proto::CtrlMsgPayload::RespSetWifiMode(resp) = resp.payload.unwrap() else { - panic!("unexpected resp") - }; - assert_eq!(resp.resp, 0); + async fn set_wifi_mode(&mut self, mode: u32) -> Result<(), Error> { + let req = proto::CtrlMsgReqSetMode { mode }; + ioctl!(self, ReqSetWifiMode, RespSetWifiMode, req, resp); + + Ok(()) } - async fn ioctl(&mut self, req: CtrlMsg) -> CtrlMsg { - debug!("ioctl req: {:?}", &req); + async fn ioctl(&mut self, msg: &mut CtrlMsg) -> Result<(), Error> { + debug!("ioctl req: {:?}", &msg); let mut buf = [0u8; 128]; - let req_len = noproto::write(&req, &mut buf).unwrap(); + let req_len = noproto::write(msg, &mut buf).map_err(|_| { + warn!("failed to serialize control request"); + Error::Internal + })?; struct CancelOnDrop<'a>(&'a Shared); @@ -135,9 +158,12 @@ impl<'a> Control<'a> { ioctl.defuse(); - let res = noproto::read(&buf[..resp_len]).unwrap(); - debug!("ioctl resp: {:?}", &res); + *msg = noproto::read(&buf[..resp_len]).map_err(|_| { + warn!("failed to serialize control request"); + Error::Internal + })?; + debug!("ioctl resp: {:?}", msg); - res + Ok(()) } } diff --git a/embassy-net-esp-hosted/src/lib.rs b/embassy-net-esp-hosted/src/lib.rs index 96fddce5..4a318b20 100644 --- a/embassy-net-esp-hosted/src/lib.rs +++ b/embassy-net-esp-hosted/src/lib.rs @@ -1,17 +1,15 @@ #![no_std] -use control::Control; -use embassy_futures::select::{select3, Either3}; +use embassy_futures::select::{select4, Either4}; use embassy_net_driver_channel as ch; +use embassy_net_driver_channel::driver::LinkState; use embassy_time::{Duration, Instant, Timer}; use embedded_hal::digital::{InputPin, OutputPin}; use embedded_hal_async::digital::Wait; use embedded_hal_async::spi::SpiDevice; -use ioctl::Shared; -use proto::CtrlMsg; -use crate::ioctl::PendingIoctl; -use crate::proto::CtrlMsgPayload; +use crate::ioctl::{PendingIoctl, Shared}; +use crate::proto::{CtrlMsg, CtrlMsgPayload}; mod proto; @@ -21,6 +19,8 @@ mod fmt; mod control; mod ioctl; +pub use control::*; + const MTU: usize = 1514; macro_rules! impl_bytes { @@ -95,6 +95,7 @@ enum InterfaceType { } const MAX_SPI_BUFFER_SIZE: usize = 1600; +const HEARTBEAT_MAX_GAP: Duration = Duration::from_secs(20); pub struct State { shared: Shared, @@ -129,12 +130,14 @@ where let mut runner = Runner { ch: ch_runner, + state_ch, shared: &state.shared, next_seq: 1, handshake, ready, reset, spi, + heartbeat_deadline: Instant::now() + HEARTBEAT_MAX_GAP, }; runner.init().await; @@ -143,9 +146,11 @@ where pub struct Runner<'a, SPI, IN, OUT> { ch: ch::Runner<'a, MTU>, + state_ch: ch::StateRunner<'a>, shared: &'a Shared, next_seq: u16, + heartbeat_deadline: Instant, spi: SPI, handshake: IN, @@ -177,9 +182,10 @@ where let ioctl = self.shared.ioctl_wait_pending(); let tx = self.ch.tx_buf(); let ev = async { self.ready.wait_for_high().await.unwrap() }; + let hb = Timer::at(self.heartbeat_deadline); - match select3(ioctl, tx, ev).await { - Either3::First(PendingIoctl { buf, req_len }) => { + match select4(ioctl, tx, ev, hb).await { + Either4::First(PendingIoctl { buf, req_len }) => { tx_buf[12..24].copy_from_slice(b"\x01\x08\x00ctrlResp\x02"); tx_buf[24..26].copy_from_slice(&(req_len as u16).to_le_bytes()); tx_buf[26..][..req_len].copy_from_slice(&unsafe { &*buf }[..req_len]); @@ -198,7 +204,7 @@ where header.checksum = checksum(&tx_buf[..26 + req_len]); tx_buf[0..12].copy_from_slice(&header.to_bytes()); } - Either3::Second(packet) => { + Either4::Second(packet) => { tx_buf[12..][..packet.len()].copy_from_slice(packet); let mut header = PayloadHeader { @@ -217,9 +223,12 @@ where self.ch.tx_done(); } - Either3::Third(()) => { + Either4::Third(()) => { tx_buf[..PayloadHeader::SIZE].fill(0); } + Either4::Fourth(()) => { + panic!("heartbeat from esp32 stopped") + } } if tx_buf[0] != 0 { @@ -308,7 +317,7 @@ where } } - fn handle_event(&self, data: &[u8]) { + fn handle_event(&mut self, data: &[u8]) { let Ok(event) = noproto::read::(data) else { warn!("failed to parse event"); return; @@ -323,6 +332,11 @@ where match payload { CtrlMsgPayload::EventEspInit(_) => self.shared.init_done(), + CtrlMsgPayload::EventHeartbeat(_) => self.heartbeat_deadline = Instant::now() + HEARTBEAT_MAX_GAP, + CtrlMsgPayload::EventStationDisconnectFromAp(e) => { + info!("disconnected, code {}", e.resp); + self.state_ch.set_link_state(LinkState::Down); + } _ => {} } } diff --git a/embassy-net-w5500/README.md b/embassy-net-w5500/README.md deleted file mode 100644 index 9eaf4b70..00000000 --- a/embassy-net-w5500/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# WIZnet W5500 `embassy-net` integration - -[`embassy-net`](https://crates.io/crates/embassy-net) integration for the WIZnet W5500 SPI ethernet chip, operating in MACRAW mode. - -Supports any SPI driver implementing [`embedded-hal-async`](https://crates.io/crates/embedded-hal-async) - -See [`examples`](https://github.com/kalkyl/embassy-net-w5500/tree/main/examples) directory for usage examples with the rp2040 [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) module. \ No newline at end of file diff --git a/embassy-net-w5500/src/device.rs b/embassy-net-w5500/src/device.rs deleted file mode 100644 index 9874df0d..00000000 --- a/embassy-net-w5500/src/device.rs +++ /dev/null @@ -1,131 +0,0 @@ -use embedded_hal_async::spi::SpiDevice; - -use crate::socket; -use crate::spi::SpiInterface; - -pub const MODE: u16 = 0x00; -pub const MAC: u16 = 0x09; -pub const SOCKET_INTR: u16 = 0x18; -pub const PHY_CFG: u16 = 0x2E; - -#[repr(u8)] -pub enum RegisterBlock { - Common = 0x00, - Socket0 = 0x01, - TxBuf = 0x02, - RxBuf = 0x03, -} - -/// W5500 in MACRAW mode -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct W5500 { - bus: SpiInterface, -} - -impl W5500 { - /// Create and initialize the W5500 driver - pub async fn new(spi: SPI, mac_addr: [u8; 6]) -> Result, SPI::Error> { - let mut bus = SpiInterface(spi); - // Reset device - bus.write_frame(RegisterBlock::Common, MODE, &[0x80]).await?; - - // Enable interrupt pin - bus.write_frame(RegisterBlock::Common, SOCKET_INTR, &[0x01]).await?; - // Enable receive interrupt - bus.write_frame( - RegisterBlock::Socket0, - socket::SOCKET_INTR_MASK, - &[socket::Interrupt::Receive as u8], - ) - .await?; - - // Set MAC address - bus.write_frame(RegisterBlock::Common, MAC, &mac_addr).await?; - - // Set the raw socket RX/TX buffer sizes to 16KB - bus.write_frame(RegisterBlock::Socket0, socket::TXBUF_SIZE, &[16]) - .await?; - bus.write_frame(RegisterBlock::Socket0, socket::RXBUF_SIZE, &[16]) - .await?; - - // MACRAW mode with MAC filtering. - let mode: u8 = (1 << 2) | (1 << 7); - bus.write_frame(RegisterBlock::Socket0, socket::MODE, &[mode]).await?; - socket::command(&mut bus, socket::Command::Open).await?; - - Ok(Self { bus }) - } - - /// Read bytes from the RX buffer. Returns the number of bytes read. - async fn read_bytes(&mut self, buffer: &mut [u8], offset: u16) -> Result { - let rx_size = socket::get_rx_size(&mut self.bus).await? as usize; - - let read_buffer = if rx_size > buffer.len() + offset as usize { - buffer - } else { - &mut buffer[..rx_size - offset as usize] - }; - - let read_ptr = socket::get_rx_read_ptr(&mut self.bus).await?.wrapping_add(offset); - self.bus.read_frame(RegisterBlock::RxBuf, read_ptr, read_buffer).await?; - socket::set_rx_read_ptr(&mut self.bus, read_ptr.wrapping_add(read_buffer.len() as u16)).await?; - - Ok(read_buffer.len()) - } - - /// Read an ethernet frame from the device. Returns the number of bytes read. - pub async fn read_frame(&mut self, frame: &mut [u8]) -> Result { - let rx_size = socket::get_rx_size(&mut self.bus).await? as usize; - if rx_size == 0 { - return Ok(0); - } - - socket::reset_interrupt(&mut self.bus, socket::Interrupt::Receive).await?; - - // First two bytes gives the size of the received ethernet frame - let expected_frame_size: usize = { - let mut frame_bytes = [0u8; 2]; - assert!(self.read_bytes(&mut frame_bytes[..], 0).await? == 2); - u16::from_be_bytes(frame_bytes) as usize - 2 - }; - - // Read the ethernet frame - let read_buffer = if frame.len() > expected_frame_size { - &mut frame[..expected_frame_size] - } else { - frame - }; - - let recvd_frame_size = self.read_bytes(read_buffer, 2).await?; - - // Register RX as completed - socket::command(&mut self.bus, socket::Command::Receive).await?; - - // If the whole frame wasn't read, drop it - if recvd_frame_size < expected_frame_size { - Ok(0) - } else { - Ok(recvd_frame_size) - } - } - - /// Write an ethernet frame to the device. Returns number of bytes written - pub async fn write_frame(&mut self, frame: &[u8]) -> Result { - while socket::get_tx_free_size(&mut self.bus).await? < frame.len() as u16 {} - let write_ptr = socket::get_tx_write_ptr(&mut self.bus).await?; - self.bus.write_frame(RegisterBlock::TxBuf, write_ptr, frame).await?; - socket::set_tx_write_ptr(&mut self.bus, write_ptr.wrapping_add(frame.len() as u16)).await?; - socket::command(&mut self.bus, socket::Command::Send).await?; - Ok(frame.len()) - } - - pub async fn is_link_up(&mut self) -> bool { - let mut link = [0]; - self.bus - .read_frame(RegisterBlock::Common, PHY_CFG, &mut link) - .await - .ok(); - link[0] & 1 == 1 - } -} diff --git a/embassy-net-w5500/src/socket.rs b/embassy-net-w5500/src/socket.rs deleted file mode 100644 index 3d65583c..00000000 --- a/embassy-net-w5500/src/socket.rs +++ /dev/null @@ -1,80 +0,0 @@ -use embedded_hal_async::spi::SpiDevice; - -use crate::device::RegisterBlock; -use crate::spi::SpiInterface; - -pub const MODE: u16 = 0x00; -pub const COMMAND: u16 = 0x01; -pub const RXBUF_SIZE: u16 = 0x1E; -pub const TXBUF_SIZE: u16 = 0x1F; -pub const TX_FREE_SIZE: u16 = 0x20; -pub const TX_DATA_WRITE_PTR: u16 = 0x24; -pub const RECVD_SIZE: u16 = 0x26; -pub const RX_DATA_READ_PTR: u16 = 0x28; -pub const SOCKET_INTR_MASK: u16 = 0x2C; - -#[repr(u8)] -pub enum Command { - Open = 0x01, - Send = 0x20, - Receive = 0x40, -} - -pub const INTR: u16 = 0x02; -#[repr(u8)] -pub enum Interrupt { - Receive = 0b00100_u8, -} - -pub async fn reset_interrupt(bus: &mut SpiInterface, code: Interrupt) -> Result<(), SPI::Error> { - let data = [code as u8]; - bus.write_frame(RegisterBlock::Socket0, INTR, &data).await -} - -pub async fn get_tx_write_ptr(bus: &mut SpiInterface) -> Result { - let mut data = [0u8; 2]; - bus.read_frame(RegisterBlock::Socket0, TX_DATA_WRITE_PTR, &mut data) - .await?; - Ok(u16::from_be_bytes(data)) -} - -pub async fn set_tx_write_ptr(bus: &mut SpiInterface, ptr: u16) -> Result<(), SPI::Error> { - let data = ptr.to_be_bytes(); - bus.write_frame(RegisterBlock::Socket0, TX_DATA_WRITE_PTR, &data).await -} - -pub async fn get_rx_read_ptr(bus: &mut SpiInterface) -> Result { - let mut data = [0u8; 2]; - bus.read_frame(RegisterBlock::Socket0, RX_DATA_READ_PTR, &mut data) - .await?; - Ok(u16::from_be_bytes(data)) -} - -pub async fn set_rx_read_ptr(bus: &mut SpiInterface, ptr: u16) -> Result<(), SPI::Error> { - let data = ptr.to_be_bytes(); - bus.write_frame(RegisterBlock::Socket0, RX_DATA_READ_PTR, &data).await -} - -pub async fn command(bus: &mut SpiInterface, command: Command) -> Result<(), SPI::Error> { - let data = [command as u8]; - bus.write_frame(RegisterBlock::Socket0, COMMAND, &data).await -} - -pub async fn get_rx_size(bus: &mut SpiInterface) -> Result { - loop { - // Wait until two sequential reads are equal - let mut res0 = [0u8; 2]; - bus.read_frame(RegisterBlock::Socket0, RECVD_SIZE, &mut res0).await?; - let mut res1 = [0u8; 2]; - bus.read_frame(RegisterBlock::Socket0, RECVD_SIZE, &mut res1).await?; - if res0 == res1 { - break Ok(u16::from_be_bytes(res0)); - } - } -} - -pub async fn get_tx_free_size(bus: &mut SpiInterface) -> Result { - let mut data = [0; 2]; - bus.read_frame(RegisterBlock::Socket0, TX_FREE_SIZE, &mut data).await?; - Ok(u16::from_be_bytes(data)) -} diff --git a/embassy-net-w5500/src/spi.rs b/embassy-net-w5500/src/spi.rs deleted file mode 100644 index 07749d6b..00000000 --- a/embassy-net-w5500/src/spi.rs +++ /dev/null @@ -1,32 +0,0 @@ -use embedded_hal_async::spi::{Operation, SpiDevice}; - -use crate::device::RegisterBlock; - -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct SpiInterface(pub SPI); - -impl SpiInterface { - pub async fn read_frame(&mut self, block: RegisterBlock, address: u16, data: &mut [u8]) -> Result<(), SPI::Error> { - let address_phase = address.to_be_bytes(); - let control_phase = [(block as u8) << 3]; - let operations = &mut [ - Operation::Write(&address_phase), - Operation::Write(&control_phase), - Operation::TransferInPlace(data), - ]; - self.0.transaction(operations).await - } - - pub async fn write_frame(&mut self, block: RegisterBlock, address: u16, data: &[u8]) -> Result<(), SPI::Error> { - let address_phase = address.to_be_bytes(); - let control_phase = [(block as u8) << 3 | 0b0000_0100]; - let data_phase = data; - let operations = &mut [ - Operation::Write(&address_phase[..]), - Operation::Write(&control_phase), - Operation::Write(&data_phase), - ]; - self.0.transaction(operations).await - } -} diff --git a/embassy-net-w5500/Cargo.toml b/embassy-net-wiznet/Cargo.toml similarity index 60% rename from embassy-net-w5500/Cargo.toml rename to embassy-net-wiznet/Cargo.toml index 8972b814..adf0b45f 100644 --- a/embassy-net-w5500/Cargo.toml +++ b/embassy-net-wiznet/Cargo.toml @@ -1,21 +1,22 @@ [package] -name = "embassy-net-w5500" +name = "embassy-net-wiznet" version = "0.1.0" -description = "embassy-net driver for the W5500 ethernet chip" -keywords = ["embedded", "w5500", "embassy-net", "embedded-hal-async", "ethernet", "async"] +description = "embassy-net driver for WIZnet SPI Ethernet chips" +keywords = ["embedded", "wiznet", "embassy-net", "embedded-hal-async", "ethernet", "async"] categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"] license = "MIT OR Apache-2.0" edition = "2021" [dependencies] -embedded-hal = { version = "1.0.0-alpha.11" } -embedded-hal-async = { version = "=0.2.0-alpha.2" } +embedded-hal = { version = "1.0.0-rc.1" } +embedded-hal-async = { version = "=1.0.0-rc.1" } embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel" } embassy-time = { version = "0.1.2", path = "../embassy-time" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } defmt = { version = "0.3", optional = true } [package.metadata.embassy_docs] -src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-w5500-v$VERSION/embassy-net-w5500/src/" -src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-w5500/src/" -target = "thumbv7em-none-eabi" \ No newline at end of file +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-wiznet-v$VERSION/embassy-net-wiznet/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-wiznet/src/" +target = "thumbv7em-none-eabi" +features = ["defmt"] \ No newline at end of file diff --git a/embassy-net-wiznet/README.md b/embassy-net-wiznet/README.md new file mode 100644 index 00000000..b8e4bdc8 --- /dev/null +++ b/embassy-net-wiznet/README.md @@ -0,0 +1,27 @@ +# WIZnet `embassy-net` integration + +[`embassy-net`](https://crates.io/crates/embassy-net) integration for the WIZnet SPI ethernet chips, operating in MACRAW mode. + +See [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/rp) directory for usage examples with the rp2040 [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) module. + +## Supported chips + +- W5500 +- W5100S + +## Interoperability + +This crate can run on any executor. + +It supports any SPI driver implementing [`embedded-hal-async`](https://crates.io/crates/embedded-hal-async). + + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. diff --git a/embassy-net-wiznet/src/chip/mod.rs b/embassy-net-wiznet/src/chip/mod.rs new file mode 100644 index 00000000..562db515 --- /dev/null +++ b/embassy-net-wiznet/src/chip/mod.rs @@ -0,0 +1,48 @@ +mod w5500; +pub use w5500::W5500; +mod w5100s; +pub use w5100s::W5100S; + +pub(crate) mod sealed { + use embedded_hal_async::spi::SpiDevice; + + pub trait Chip { + type Address; + + const COMMON_MODE: Self::Address; + const COMMON_MAC: Self::Address; + const COMMON_SOCKET_INTR: Self::Address; + const COMMON_PHY_CFG: Self::Address; + const SOCKET_MODE: Self::Address; + const SOCKET_COMMAND: Self::Address; + const SOCKET_RXBUF_SIZE: Self::Address; + const SOCKET_TXBUF_SIZE: Self::Address; + const SOCKET_TX_FREE_SIZE: Self::Address; + const SOCKET_TX_DATA_WRITE_PTR: Self::Address; + const SOCKET_RECVD_SIZE: Self::Address; + const SOCKET_RX_DATA_READ_PTR: Self::Address; + const SOCKET_INTR_MASK: Self::Address; + const SOCKET_INTR: Self::Address; + + const SOCKET_MODE_VALUE: u8; + + const BUF_SIZE: u16; + const AUTO_WRAP: bool; + + fn rx_addr(addr: u16) -> Self::Address; + fn tx_addr(addr: u16) -> Self::Address; + + async fn bus_read( + spi: &mut SPI, + address: Self::Address, + data: &mut [u8], + ) -> Result<(), SPI::Error>; + async fn bus_write( + spi: &mut SPI, + address: Self::Address, + data: &[u8], + ) -> Result<(), SPI::Error>; + } +} + +pub trait Chip: sealed::Chip {} diff --git a/embassy-net-wiznet/src/chip/w5100s.rs b/embassy-net-wiznet/src/chip/w5100s.rs new file mode 100644 index 00000000..07a84037 --- /dev/null +++ b/embassy-net-wiznet/src/chip/w5100s.rs @@ -0,0 +1,61 @@ +use embedded_hal_async::spi::{Operation, SpiDevice}; + +const SOCKET_BASE: u16 = 0x400; +const TX_BASE: u16 = 0x4000; +const RX_BASE: u16 = 0x6000; + +pub enum W5100S {} + +impl super::Chip for W5100S {} +impl super::sealed::Chip for W5100S { + type Address = u16; + + const COMMON_MODE: Self::Address = 0x00; + const COMMON_MAC: Self::Address = 0x09; + const COMMON_SOCKET_INTR: Self::Address = 0x16; + const COMMON_PHY_CFG: Self::Address = 0x3c; + + const SOCKET_MODE: Self::Address = SOCKET_BASE + 0x00; + const SOCKET_COMMAND: Self::Address = SOCKET_BASE + 0x01; + const SOCKET_RXBUF_SIZE: Self::Address = SOCKET_BASE + 0x1E; + const SOCKET_TXBUF_SIZE: Self::Address = SOCKET_BASE + 0x1F; + const SOCKET_TX_FREE_SIZE: Self::Address = SOCKET_BASE + 0x20; + const SOCKET_TX_DATA_WRITE_PTR: Self::Address = SOCKET_BASE + 0x24; + const SOCKET_RECVD_SIZE: Self::Address = SOCKET_BASE + 0x26; + const SOCKET_RX_DATA_READ_PTR: Self::Address = SOCKET_BASE + 0x28; + const SOCKET_INTR_MASK: Self::Address = SOCKET_BASE + 0x2C; + const SOCKET_INTR: Self::Address = SOCKET_BASE + 0x02; + + const SOCKET_MODE_VALUE: u8 = (1 << 2) | (1 << 6); + + const BUF_SIZE: u16 = 0x2000; + const AUTO_WRAP: bool = false; + + fn rx_addr(addr: u16) -> Self::Address { + RX_BASE + addr + } + + fn tx_addr(addr: u16) -> Self::Address { + TX_BASE + addr + } + + async fn bus_read( + spi: &mut SPI, + address: Self::Address, + data: &mut [u8], + ) -> Result<(), SPI::Error> { + spi.transaction(&mut [ + Operation::Write(&[0x0F, (address >> 8) as u8, address as u8]), + Operation::Read(data), + ]) + .await + } + + async fn bus_write(spi: &mut SPI, address: Self::Address, data: &[u8]) -> Result<(), SPI::Error> { + spi.transaction(&mut [ + Operation::Write(&[0xF0, (address >> 8) as u8, address as u8]), + Operation::Write(data), + ]) + .await + } +} diff --git a/embassy-net-wiznet/src/chip/w5500.rs b/embassy-net-wiznet/src/chip/w5500.rs new file mode 100644 index 00000000..61e51294 --- /dev/null +++ b/embassy-net-wiznet/src/chip/w5500.rs @@ -0,0 +1,72 @@ +use embedded_hal_async::spi::{Operation, SpiDevice}; + +#[repr(u8)] +pub enum RegisterBlock { + Common = 0x00, + Socket0 = 0x01, + TxBuf = 0x02, + RxBuf = 0x03, +} + +pub enum W5500 {} + +impl super::Chip for W5500 {} +impl super::sealed::Chip for W5500 { + type Address = (RegisterBlock, u16); + + const COMMON_MODE: Self::Address = (RegisterBlock::Common, 0x00); + const COMMON_MAC: Self::Address = (RegisterBlock::Common, 0x09); + const COMMON_SOCKET_INTR: Self::Address = (RegisterBlock::Common, 0x18); + const COMMON_PHY_CFG: Self::Address = (RegisterBlock::Common, 0x2E); + + const SOCKET_MODE: Self::Address = (RegisterBlock::Socket0, 0x00); + const SOCKET_COMMAND: Self::Address = (RegisterBlock::Socket0, 0x01); + const SOCKET_RXBUF_SIZE: Self::Address = (RegisterBlock::Socket0, 0x1E); + const SOCKET_TXBUF_SIZE: Self::Address = (RegisterBlock::Socket0, 0x1F); + const SOCKET_TX_FREE_SIZE: Self::Address = (RegisterBlock::Socket0, 0x20); + const SOCKET_TX_DATA_WRITE_PTR: Self::Address = (RegisterBlock::Socket0, 0x24); + const SOCKET_RECVD_SIZE: Self::Address = (RegisterBlock::Socket0, 0x26); + const SOCKET_RX_DATA_READ_PTR: Self::Address = (RegisterBlock::Socket0, 0x28); + const SOCKET_INTR_MASK: Self::Address = (RegisterBlock::Socket0, 0x2C); + const SOCKET_INTR: Self::Address = (RegisterBlock::Socket0, 0x02); + + const SOCKET_MODE_VALUE: u8 = (1 << 2) | (1 << 7); + + const BUF_SIZE: u16 = 0x4000; + const AUTO_WRAP: bool = true; + + fn rx_addr(addr: u16) -> Self::Address { + (RegisterBlock::RxBuf, addr) + } + + fn tx_addr(addr: u16) -> Self::Address { + (RegisterBlock::TxBuf, addr) + } + + async fn bus_read( + spi: &mut SPI, + address: Self::Address, + data: &mut [u8], + ) -> Result<(), SPI::Error> { + let address_phase = address.1.to_be_bytes(); + let control_phase = [(address.0 as u8) << 3]; + let operations = &mut [ + Operation::Write(&address_phase), + Operation::Write(&control_phase), + Operation::TransferInPlace(data), + ]; + spi.transaction(operations).await + } + + async fn bus_write(spi: &mut SPI, address: Self::Address, data: &[u8]) -> Result<(), SPI::Error> { + let address_phase = address.1.to_be_bytes(); + let control_phase = [(address.0 as u8) << 3 | 0b0000_0100]; + let data_phase = data; + let operations = &mut [ + Operation::Write(&address_phase[..]), + Operation::Write(&control_phase), + Operation::Write(&data_phase), + ]; + spi.transaction(operations).await + } +} diff --git a/embassy-net-wiznet/src/device.rs b/embassy-net-wiznet/src/device.rs new file mode 100644 index 00000000..43f9512a --- /dev/null +++ b/embassy-net-wiznet/src/device.rs @@ -0,0 +1,195 @@ +use core::marker::PhantomData; + +use embedded_hal_async::spi::SpiDevice; + +use crate::chip::Chip; + +#[repr(u8)] +enum Command { + Open = 0x01, + Send = 0x20, + Receive = 0x40, +} + +#[repr(u8)] +enum Interrupt { + Receive = 0b00100_u8, +} + +/// Wiznet chip in MACRAW mode +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct WiznetDevice { + spi: SPI, + _phantom: PhantomData, +} + +impl WiznetDevice { + /// Create and initialize the driver + pub async fn new(spi: SPI, mac_addr: [u8; 6]) -> Result { + let mut this = Self { + spi, + _phantom: PhantomData, + }; + + // Reset device + this.bus_write(C::COMMON_MODE, &[0x80]).await?; + + // Enable interrupt pin + this.bus_write(C::COMMON_SOCKET_INTR, &[0x01]).await?; + // Enable receive interrupt + this.bus_write(C::SOCKET_INTR_MASK, &[Interrupt::Receive as u8]).await?; + + // Set MAC address + this.bus_write(C::COMMON_MAC, &mac_addr).await?; + + // Set the raw socket RX/TX buffer sizes. + let buf_kbs = (C::BUF_SIZE / 1024) as u8; + this.bus_write(C::SOCKET_TXBUF_SIZE, &[buf_kbs]).await?; + this.bus_write(C::SOCKET_RXBUF_SIZE, &[buf_kbs]).await?; + + // MACRAW mode with MAC filtering. + this.bus_write(C::SOCKET_MODE, &[C::SOCKET_MODE_VALUE]).await?; + this.command(Command::Open).await?; + + Ok(this) + } + + async fn bus_read(&mut self, address: C::Address, data: &mut [u8]) -> Result<(), SPI::Error> { + C::bus_read(&mut self.spi, address, data).await + } + + async fn bus_write(&mut self, address: C::Address, data: &[u8]) -> Result<(), SPI::Error> { + C::bus_write(&mut self.spi, address, data).await + } + + async fn reset_interrupt(&mut self, code: Interrupt) -> Result<(), SPI::Error> { + let data = [code as u8]; + self.bus_write(C::SOCKET_INTR, &data).await + } + + async fn get_tx_write_ptr(&mut self) -> Result { + let mut data = [0u8; 2]; + self.bus_read(C::SOCKET_TX_DATA_WRITE_PTR, &mut data).await?; + Ok(u16::from_be_bytes(data)) + } + + async fn set_tx_write_ptr(&mut self, ptr: u16) -> Result<(), SPI::Error> { + let data = ptr.to_be_bytes(); + self.bus_write(C::SOCKET_TX_DATA_WRITE_PTR, &data).await + } + + async fn get_rx_read_ptr(&mut self) -> Result { + let mut data = [0u8; 2]; + self.bus_read(C::SOCKET_RX_DATA_READ_PTR, &mut data).await?; + Ok(u16::from_be_bytes(data)) + } + + async fn set_rx_read_ptr(&mut self, ptr: u16) -> Result<(), SPI::Error> { + let data = ptr.to_be_bytes(); + self.bus_write(C::SOCKET_RX_DATA_READ_PTR, &data).await + } + + async fn command(&mut self, command: Command) -> Result<(), SPI::Error> { + let data = [command as u8]; + self.bus_write(C::SOCKET_COMMAND, &data).await + } + + async fn get_rx_size(&mut self) -> Result { + loop { + // Wait until two sequential reads are equal + let mut res0 = [0u8; 2]; + self.bus_read(C::SOCKET_RECVD_SIZE, &mut res0).await?; + let mut res1 = [0u8; 2]; + self.bus_read(C::SOCKET_RECVD_SIZE, &mut res1).await?; + if res0 == res1 { + break Ok(u16::from_be_bytes(res0)); + } + } + } + + async fn get_tx_free_size(&mut self) -> Result { + let mut data = [0; 2]; + self.bus_read(C::SOCKET_TX_FREE_SIZE, &mut data).await?; + Ok(u16::from_be_bytes(data)) + } + + /// Read bytes from the RX buffer. + async fn read_bytes(&mut self, read_ptr: &mut u16, buffer: &mut [u8]) -> Result<(), SPI::Error> { + if C::AUTO_WRAP { + self.bus_read(C::rx_addr(*read_ptr), buffer).await?; + } else { + let addr = *read_ptr % C::BUF_SIZE; + if addr as usize + buffer.len() <= C::BUF_SIZE as usize { + self.bus_read(C::rx_addr(addr), buffer).await?; + } else { + let n = C::BUF_SIZE - addr; + self.bus_read(C::rx_addr(addr), &mut buffer[..n as usize]).await?; + self.bus_read(C::rx_addr(0), &mut buffer[n as usize..]).await?; + } + } + + *read_ptr = (*read_ptr).wrapping_add(buffer.len() as u16); + + Ok(()) + } + + /// Read an ethernet frame from the device. Returns the number of bytes read. + pub async fn read_frame(&mut self, frame: &mut [u8]) -> Result { + let rx_size = self.get_rx_size().await? as usize; + if rx_size == 0 { + return Ok(0); + } + + self.reset_interrupt(Interrupt::Receive).await?; + + let mut read_ptr = self.get_rx_read_ptr().await?; + + // First two bytes gives the size of the received ethernet frame + let expected_frame_size: usize = { + let mut frame_bytes = [0u8; 2]; + self.read_bytes(&mut read_ptr, &mut frame_bytes).await?; + u16::from_be_bytes(frame_bytes) as usize - 2 + }; + + // Read the ethernet frame + self.read_bytes(&mut read_ptr, &mut frame[..expected_frame_size]) + .await?; + + // Register RX as completed + self.set_rx_read_ptr(read_ptr).await?; + self.command(Command::Receive).await?; + + Ok(expected_frame_size) + } + + /// Write an ethernet frame to the device. Returns number of bytes written + pub async fn write_frame(&mut self, frame: &[u8]) -> Result { + while self.get_tx_free_size().await? < frame.len() as u16 {} + let write_ptr = self.get_tx_write_ptr().await?; + + if C::AUTO_WRAP { + self.bus_write(C::tx_addr(write_ptr), frame).await?; + } else { + let addr = write_ptr % C::BUF_SIZE; + if addr as usize + frame.len() <= C::BUF_SIZE as usize { + self.bus_write(C::tx_addr(addr), frame).await?; + } else { + let n = C::BUF_SIZE - addr; + self.bus_write(C::tx_addr(addr), &frame[..n as usize]).await?; + self.bus_write(C::tx_addr(0), &frame[n as usize..]).await?; + } + } + + self.set_tx_write_ptr(write_ptr.wrapping_add(frame.len() as u16)) + .await?; + self.command(Command::Send).await?; + Ok(frame.len()) + } + + pub async fn is_link_up(&mut self) -> bool { + let mut link = [0]; + self.bus_read(C::COMMON_PHY_CFG, &mut link).await.ok(); + link[0] & 1 == 1 + } +} diff --git a/embassy-net-w5500/src/lib.rs b/embassy-net-wiznet/src/lib.rs similarity index 67% rename from embassy-net-w5500/src/lib.rs rename to embassy-net-wiznet/src/lib.rs index 52494b44..3030dfb9 100644 --- a/embassy-net-w5500/src/lib.rs +++ b/embassy-net-wiznet/src/lib.rs @@ -1,9 +1,9 @@ -//! [`embassy-net`](https://crates.io/crates/embassy-net) driver for the WIZnet W5500 ethernet chip. +//! [`embassy-net`](https://crates.io/crates/embassy-net) driver for WIZnet ethernet chips. #![no_std] +#![feature(async_fn_in_trait)] +pub mod chip; mod device; -mod socket; -mod spi; use embassy_futures::select::{select, Either}; use embassy_net_driver_channel as ch; @@ -13,10 +13,12 @@ use embedded_hal::digital::OutputPin; use embedded_hal_async::digital::Wait; use embedded_hal_async::spi::SpiDevice; -use crate::device::W5500; +use crate::chip::Chip; +use crate::device::WiznetDevice; + const MTU: usize = 1514; -/// Type alias for the embassy-net driver for W5500 +/// Type alias for the embassy-net driver. pub type Device<'d> = embassy_net_driver_channel::Device<'d, MTU>; /// Internal state for the embassy-net integration. @@ -33,18 +35,18 @@ impl State { } } -/// Background runner for the W5500. +/// Background runner for the driver. /// -/// You must call `.run()` in a background task for the W5500 to operate. -pub struct Runner<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> { - mac: W5500, +/// You must call `.run()` in a background task for the driver to operate. +pub struct Runner<'d, C: Chip, SPI: SpiDevice, INT: Wait, RST: OutputPin> { + mac: WiznetDevice, ch: ch::Runner<'d, MTU>, int: INT, _reset: RST, } -/// You must call this in a background task for the W5500 to operate. -impl<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, SPI, INT, RST> { +/// You must call this in a background task for the driver to operate. +impl<'d, C: Chip, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, C, SPI, INT, RST> { pub async fn run(mut self) -> ! { let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); loop { @@ -78,23 +80,29 @@ impl<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, SPI, INT, RST> { } } -/// Obtain a driver for using the W5500 with [`embassy-net`](https://crates.io/crates/embassy-net). -pub async fn new<'a, const N_RX: usize, const N_TX: usize, SPI: SpiDevice, INT: Wait, RST: OutputPin>( +/// Create a Wiznet ethernet chip driver for [`embassy-net`](https://crates.io/crates/embassy-net). +/// +/// This returns two structs: +/// - a `Device` that you must pass to the `embassy-net` stack. +/// - a `Runner`. You must call `.run()` on it in a background task. +pub async fn new<'a, const N_RX: usize, const N_TX: usize, C: Chip, SPI: SpiDevice, INT: Wait, RST: OutputPin>( mac_addr: [u8; 6], state: &'a mut State, spi_dev: SPI, int: INT, mut reset: RST, -) -> (Device<'a>, Runner<'a, SPI, INT, RST>) { - // Reset the W5500. +) -> (Device<'a>, Runner<'a, C, SPI, INT, RST>) { + // Reset the chip. reset.set_low().ok(); // Ensure the reset is registered. Timer::after(Duration::from_millis(1)).await; reset.set_high().ok(); - // Wait for the W5500 to achieve PLL lock. - Timer::after(Duration::from_millis(2)).await; - let mac = W5500::new(spi_dev, mac_addr).await.unwrap(); + // Wait for PLL lock. Some chips are slower than others. + // Slowest is w5100s which is 100ms, so let's just wait that. + Timer::after(Duration::from_millis(100)).await; + + let mac = WiznetDevice::new(spi_dev, mac_addr).await.unwrap(); let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ethernet(mac_addr)); ( diff --git a/embassy-net/README.md b/embassy-net/README.md index 811321ca..7bb2283c 100644 --- a/embassy-net/README.md +++ b/embassy-net/README.md @@ -22,7 +22,7 @@ unimplemented features of the network protocols. - [`cyw43`](https://github.com/embassy-rs/embassy/tree/main/cyw43) for WiFi on CYW43xx chips, used in the Raspberry Pi Pico W - [`embassy-usb`](https://github.com/embassy-rs/embassy/tree/main/embassy-usb) for Ethernet-over-USB (CDC NCM) support. - [`embassy-stm32`](https://github.com/embassy-rs/embassy/tree/main/embassy-stm32) for the builtin Ethernet MAC in all STM32 chips (STM32F1, STM32F2, STM32F4, STM32F7, STM32H7, STM32H5). -- [`embassy-net-w5500`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-w5500) for Wiznet W5500 SPI Ethernet MAC+PHY chip. +- [`embassy-net-wiznet`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-wiznet) for Wiznet SPI Ethernet MAC+PHY chips (W5100S, W5500) - [`embassy-net-esp-hosted`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-esp-hosted) for using ESP32 chips with the [`esp-hosted`](https://github.com/espressif/esp-hosted) firmware as WiFi adapters for another non-ESP32 MCU. ## Examples diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index c903fb24..c92ad2d2 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs @@ -82,6 +82,22 @@ impl<'a> TcpReader<'a> { pub async fn read(&mut self, buf: &mut [u8]) -> Result { self.io.read(buf).await } + + /// Call `f` with the largest contiguous slice of octets in the receive buffer, + /// and dequeue the amount of elements returned by `f`. + /// + /// If no data is available, it waits until there is at least one byte available. + pub async fn read_with(&mut self, f: F) -> Result + where + F: FnOnce(&mut [u8]) -> (usize, R), + { + self.io.read_with(f).await + } + + /// Return the maximum number of bytes inside the transmit buffer. + pub fn recv_capacity(&self) -> usize { + self.io.recv_capacity() + } } impl<'a> TcpWriter<'a> { @@ -100,6 +116,22 @@ impl<'a> TcpWriter<'a> { pub async fn flush(&mut self) -> Result<(), Error> { self.io.flush().await } + + /// Call `f` with the largest contiguous slice of octets in the transmit buffer, + /// and enqueue the amount of elements returned by `f`. + /// + /// If the socket is not ready to accept data, it waits until it is. + pub async fn write_with(&mut self, f: F) -> Result + where + F: FnOnce(&mut [u8]) -> (usize, R), + { + self.io.write_with(f).await + } + + /// Return the maximum number of bytes inside the transmit buffer. + pub fn send_capacity(&self) -> usize { + self.io.send_capacity() + } } impl<'a> TcpSocket<'a> { @@ -121,6 +153,38 @@ impl<'a> TcpSocket<'a> { } } + /// Return the maximum number of bytes inside the recv buffer. + pub fn recv_capacity(&self) -> usize { + self.io.recv_capacity() + } + + /// Return the maximum number of bytes inside the transmit buffer. + pub fn send_capacity(&self) -> usize { + self.io.send_capacity() + } + + /// Call `f` with the largest contiguous slice of octets in the transmit buffer, + /// and enqueue the amount of elements returned by `f`. + /// + /// If the socket is not ready to accept data, it waits until it is. + pub async fn write_with(&mut self, f: F) -> Result + where + F: FnOnce(&mut [u8]) -> (usize, R), + { + self.io.write_with(f).await + } + + /// Call `f` with the largest contiguous slice of octets in the receive buffer, + /// and dequeue the amount of elements returned by `f`. + /// + /// If no data is available, it waits until there is at least one byte available. + pub async fn read_with(&mut self, f: F) -> Result + where + F: FnOnce(&mut [u8]) -> (usize, R), + { + self.io.read_with(f).await + } + /// Split the socket into reader and a writer halves. pub fn split(&mut self) -> (TcpReader<'_>, TcpWriter<'_>) { (TcpReader { io: self.io }, TcpWriter { io: self.io }) @@ -359,6 +423,64 @@ impl<'d> TcpIo<'d> { .await } + async fn write_with(&mut self, f: F) -> Result + where + F: FnOnce(&mut [u8]) -> (usize, R), + { + let mut f = Some(f); + poll_fn(move |cx| { + self.with_mut(|s, _| { + if !s.can_send() { + if s.may_send() { + // socket buffer is full wait until it has atleast one byte free + s.register_send_waker(cx.waker()); + Poll::Pending + } else { + // if we can't transmit because the transmit half of the duplex connection is closed then return an error + Poll::Ready(Err(Error::ConnectionReset)) + } + } else { + Poll::Ready(match s.send(f.take().unwrap()) { + // Connection reset. TODO: this can also be timeouts etc, investigate. + Err(tcp::SendError::InvalidState) => Err(Error::ConnectionReset), + Ok(r) => Ok(r), + }) + } + }) + }) + .await + } + + async fn read_with(&mut self, f: F) -> Result + where + F: FnOnce(&mut [u8]) -> (usize, R), + { + let mut f = Some(f); + poll_fn(move |cx| { + self.with_mut(|s, _| { + if !s.can_recv() { + if s.may_recv() { + // socket buffer is empty wait until it has atleast one byte has arrived + s.register_recv_waker(cx.waker()); + Poll::Pending + } else { + // if we can't receive because the recieve half of the duplex connection is closed then return an error + Poll::Ready(Err(Error::ConnectionReset)) + } + } else { + Poll::Ready(match s.recv(f.take().unwrap()) { + // Connection reset. TODO: this can also be timeouts etc, investigate. + Err(tcp::RecvError::Finished) | Err(tcp::RecvError::InvalidState) => { + Err(Error::ConnectionReset) + } + Ok(r) => Ok(r), + }) + } + }) + }) + .await + } + async fn flush(&mut self) -> Result<(), Error> { poll_fn(move |cx| { self.with_mut(|s, _| { @@ -376,6 +498,14 @@ impl<'d> TcpIo<'d> { }) .await } + + fn recv_capacity(&self) -> usize { + self.with(|s, _| s.recv_capacity()) + } + + fn send_capacity(&self) -> usize { + self.with(|s, _| s.send_capacity()) + } } #[cfg(feature = "nightly")] @@ -384,13 +514,20 @@ mod embedded_io_impls { impl embedded_io_async::Error for ConnectError { fn kind(&self) -> embedded_io_async::ErrorKind { - embedded_io_async::ErrorKind::Other + match self { + ConnectError::ConnectionReset => embedded_io_async::ErrorKind::ConnectionReset, + ConnectError::TimedOut => embedded_io_async::ErrorKind::TimedOut, + ConnectError::NoRoute => embedded_io_async::ErrorKind::NotConnected, + ConnectError::InvalidState => embedded_io_async::ErrorKind::Other, + } } } impl embedded_io_async::Error for Error { fn kind(&self) -> embedded_io_async::ErrorKind { - embedded_io_async::ErrorKind::Other + match self { + Error::ConnectionReset => embedded_io_async::ErrorKind::ConnectionReset, + } } } diff --git a/embassy-net/src/udp.rs b/embassy-net/src/udp.rs index 0d97b6db..0a5a7b8f 100644 --- a/embassy-net/src/udp.rs +++ b/embassy-net/src/udp.rs @@ -184,6 +184,26 @@ impl<'a> UdpSocket<'a> { pub fn may_recv(&self) -> bool { self.with(|s, _| s.can_recv()) } + + /// Return the maximum number packets the socket can receive. + pub fn packet_recv_capacity(&self) -> usize { + self.with(|s, _| s.packet_recv_capacity()) + } + + /// Return the maximum number packets the socket can receive. + pub fn packet_send_capacity(&self) -> usize { + self.with(|s, _| s.packet_send_capacity()) + } + + /// Return the maximum number of bytes inside the recv buffer. + pub fn payload_recv_capacity(&self) -> usize { + self.with(|s, _| s.payload_recv_capacity()) + } + + /// Return the maximum number of bytes inside the transmit buffer. + pub fn payload_send_capacity(&self) -> usize { + self.with(|s, _| s.payload_send_capacity()) + } } impl Drop for UdpSocket<'_> { diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index da1cd38e..67ec4eb9 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml @@ -32,7 +32,7 @@ rt = [ time = ["dep:embassy-time"] -defmt = ["dep:defmt", "embassy-sync/defmt", "embassy-usb-driver?/defmt", "embassy-embedded-hal/defmt"] +defmt = ["dep:defmt", "embassy-hal-internal/defmt", "embassy-sync/defmt", "embassy-usb-driver?/defmt", "embassy-embedded-hal/defmt"] # Enable nightly-only features nightly = ["embedded-hal-1", "embedded-hal-async", "dep:embassy-usb-driver", "embedded-storage-async", "dep:embedded-io-async", "embassy-embedded-hal/nightly"] @@ -98,8 +98,8 @@ embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" } embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver", optional=true } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } -embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11", optional = true} -embedded-hal-async = { version = "=0.2.0-alpha.2", optional = true} +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1", optional = true} +embedded-hal-async = { version = "=1.0.0-rc.1", optional = true} embedded-io = { version = "0.5.0" } embedded-io-async = { version = "0.5.0", optional = true } diff --git a/embassy-nrf/src/spim.rs b/embassy-nrf/src/spim.rs index 4673a017..d131a43d 100644 --- a/embassy-nrf/src/spim.rs +++ b/embassy-nrf/src/spim.rs @@ -378,6 +378,9 @@ impl<'d, T: Instance> Drop for Spim<'d, T> { gpio::deconfigure_pin(r.psel.miso.read().bits()); gpio::deconfigure_pin(r.psel.mosi.read().bits()); + // Disable all events interrupts + T::Interrupt::disable(); + trace!("spim drop: done"); } } diff --git a/embassy-nrf/src/uarte.rs b/embassy-nrf/src/uarte.rs index e79df356..95434e7a 100644 --- a/embassy-nrf/src/uarte.rs +++ b/embassy-nrf/src/uarte.rs @@ -949,51 +949,3 @@ mod eh02 { } } } - -#[cfg(feature = "unstable-traits")] -mod eh1 { - use super::*; - - impl embedded_hal_1::serial::Error for Error { - fn kind(&self) -> embedded_hal_1::serial::ErrorKind { - match *self { - Self::BufferTooLong => embedded_hal_1::serial::ErrorKind::Other, - Self::BufferNotInRAM => embedded_hal_1::serial::ErrorKind::Other, - } - } - } - - // ===================== - - impl<'d, T: Instance> embedded_hal_1::serial::ErrorType for Uarte<'d, T> { - type Error = Error; - } - - impl<'d, T: Instance> embedded_hal_1::serial::Write for Uarte<'d, T> { - fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(buffer) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - Ok(()) - } - } - - impl<'d, T: Instance> embedded_hal_1::serial::ErrorType for UarteTx<'d, T> { - type Error = Error; - } - - impl<'d, T: Instance> embedded_hal_1::serial::Write for UarteTx<'d, T> { - fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(buffer) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - Ok(()) - } - } - - impl<'d, T: Instance> embedded_hal_1::serial::ErrorType for UarteRx<'d, T> { - type Error = Error; - } -} diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml index b1680d48..b9ebcc86 100644 --- a/embassy-rp/Cargo.toml +++ b/embassy-rp/Cargo.toml @@ -85,9 +85,9 @@ fixed = "1.23.1" rp-pac = { version = "6" } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } -embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11", optional = true} -embedded-hal-async = { version = "=0.2.0-alpha.2", optional = true} -embedded-hal-nb = { version = "=1.0.0-alpha.3", optional = true} +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1", optional = true} +embedded-hal-async = { version = "=1.0.0-rc.1", optional = true} +embedded-hal-nb = { version = "=1.0.0-rc.1", optional = true} paste = "1.0" pio-proc = {version= "0.2" } diff --git a/embassy-rp/src/dma.rs b/embassy-rp/src/dma.rs index c8f74180..45ca21a7 100644 --- a/embassy-rp/src/dma.rs +++ b/embassy-rp/src/dma.rs @@ -76,7 +76,8 @@ pub unsafe fn write<'a, C: Channel, W: Word>( ) } -static DUMMY: u32 = 0; +// static mut so that this is allocated in RAM. +static mut DUMMY: u32 = 0; pub unsafe fn write_repeated<'a, C: Channel, W: Word>( ch: impl Peripheral

+ 'a, @@ -86,7 +87,7 @@ pub unsafe fn write_repeated<'a, C: Channel, W: Word>( ) -> Transfer<'a, C> { copy_inner( ch, - &DUMMY as *const u32, + &mut DUMMY as *const u32, to as *mut u32, len, W::size(), diff --git a/embassy-rp/src/flash.rs b/embassy-rp/src/flash.rs index 70d86731..1c1c2449 100644 --- a/embassy-rp/src/flash.rs +++ b/embassy-rp/src/flash.rs @@ -102,7 +102,7 @@ pub struct Flash<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> { } impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SIZE> { - pub fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + pub fn blocking_read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { trace!( "Reading from 0x{:x} to 0x{:x}", FLASH_BASE as u32 + offset, @@ -120,7 +120,7 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI FLASH_SIZE } - pub fn erase(&mut self, from: u32, to: u32) -> Result<(), Error> { + pub fn blocking_erase(&mut self, from: u32, to: u32) -> Result<(), Error> { check_erase(self, from, to)?; trace!( @@ -136,7 +136,7 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI Ok(()) } - pub fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> { + pub fn blocking_write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> { check_write(self, offset, bytes.len())?; trace!("Writing {:?} bytes to 0x{:x}", bytes.len(), FLASH_BASE as u32 + offset); @@ -233,13 +233,13 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI } /// Read SPI flash unique ID - pub fn unique_id(&mut self, uid: &mut [u8]) -> Result<(), Error> { + pub fn blocking_unique_id(&mut self, uid: &mut [u8]) -> Result<(), Error> { unsafe { self.in_ram(|| ram_helpers::flash_unique_id(uid))? }; Ok(()) } /// Read SPI flash JEDEC ID - pub fn jedec_id(&mut self) -> Result { + pub fn blocking_jedec_id(&mut self) -> Result { let mut jedec = None; unsafe { self.in_ram(|| { @@ -251,7 +251,7 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI } impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, Blocking, FLASH_SIZE> { - pub fn new(_flash: impl Peripheral

+ 'd) -> Self { + pub fn new_blocking(_flash: impl Peripheral

+ 'd) -> Self { Self { dma: None, phantom: PhantomData, @@ -310,47 +310,8 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, Async, FLASH_SIZE> { transfer, }) } -} -impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> ErrorType for Flash<'d, T, M, FLASH_SIZE> { - type Error = Error; -} - -impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> ReadNorFlash for Flash<'d, T, M, FLASH_SIZE> { - const READ_SIZE: usize = READ_SIZE; - - fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - self.read(offset, bytes) - } - - fn capacity(&self) -> usize { - self.capacity() - } -} - -impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> MultiwriteNorFlash for Flash<'d, T, M, FLASH_SIZE> {} - -impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> NorFlash for Flash<'d, T, M, FLASH_SIZE> { - const WRITE_SIZE: usize = WRITE_SIZE; - - const ERASE_SIZE: usize = ERASE_SIZE; - - fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - self.erase(from, to) - } - - fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - self.write(offset, bytes) - } -} - -#[cfg(feature = "nightly")] -impl<'d, T: Instance, const FLASH_SIZE: usize> embedded_storage_async::nor_flash::ReadNorFlash - for Flash<'d, T, Async, FLASH_SIZE> -{ - const READ_SIZE: usize = ASYNC_READ_SIZE; - - async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + pub async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { use core::mem::MaybeUninit; // Checked early to simplify address validity checks @@ -389,6 +350,49 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> embedded_storage_async::nor_flash Ok(()) } +} + +impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> ErrorType for Flash<'d, T, M, FLASH_SIZE> { + type Error = Error; +} + +impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> ReadNorFlash for Flash<'d, T, M, FLASH_SIZE> { + const READ_SIZE: usize = READ_SIZE; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(offset, bytes) + } + + fn capacity(&self) -> usize { + self.capacity() + } +} + +impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> MultiwriteNorFlash for Flash<'d, T, M, FLASH_SIZE> {} + +impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> NorFlash for Flash<'d, T, M, FLASH_SIZE> { + const WRITE_SIZE: usize = WRITE_SIZE; + + const ERASE_SIZE: usize = ERASE_SIZE; + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.blocking_erase(from, to) + } + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(offset, bytes) + } +} + +#[cfg(feature = "nightly")] +impl<'d, T: Instance, const FLASH_SIZE: usize> embedded_storage_async::nor_flash::ReadNorFlash + for Flash<'d, T, Async, FLASH_SIZE> +{ + const READ_SIZE: usize = ASYNC_READ_SIZE; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes).await + } fn capacity(&self) -> usize { self.capacity() @@ -404,11 +408,11 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> embedded_storage_async::nor_flash const ERASE_SIZE: usize = ERASE_SIZE; async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - self.erase(from, to) + self.blocking_erase(from, to) } async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - self.write(offset, bytes) + self.blocking_write(offset, bytes) } } diff --git a/embassy-rp/src/uart/buffered.rs b/embassy-rp/src/uart/buffered.rs index 58dc0bf1..e57b7259 100644 --- a/embassy-rp/src/uart/buffered.rs +++ b/embassy-rp/src/uart/buffered.rs @@ -749,15 +749,15 @@ mod eh02 { mod eh1 { use super::*; - impl<'d, T: Instance> embedded_hal_1::serial::ErrorType for BufferedUart<'d, T> { + impl<'d, T: Instance> embedded_hal_nb::serial::ErrorType for BufferedUartRx<'d, T> { type Error = Error; } - impl<'d, T: Instance> embedded_hal_1::serial::ErrorType for BufferedUartTx<'d, T> { + impl<'d, T: Instance> embedded_hal_nb::serial::ErrorType for BufferedUartTx<'d, T> { type Error = Error; } - impl<'d, T: Instance> embedded_hal_1::serial::ErrorType for BufferedUartRx<'d, T> { + impl<'d, T: Instance> embedded_hal_nb::serial::ErrorType for BufferedUart<'d, T> { type Error = Error; } @@ -767,16 +767,6 @@ mod eh1 { } } - impl<'d, T: Instance> embedded_hal_1::serial::Write for BufferedUartTx<'d, T> { - fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(buffer).map(drop) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - self.blocking_flush() - } - } - impl<'d, T: Instance> embedded_hal_nb::serial::Write for BufferedUartTx<'d, T> { fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { self.blocking_write(&[char]).map(drop).map_err(nb::Error::Other) @@ -793,16 +783,6 @@ mod eh1 { } } - impl<'d, T: Instance> embedded_hal_1::serial::Write for BufferedUart<'d, T> { - fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(buffer).map(drop) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - self.blocking_flush() - } - } - impl<'d, T: Instance> embedded_hal_nb::serial::Write for BufferedUart<'d, T> { fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { self.blocking_write(&[char]).map(drop).map_err(nb::Error::Other) diff --git a/embassy-rp/src/uart/mod.rs b/embassy-rp/src/uart/mod.rs index 00070b80..202b0883 100644 --- a/embassy-rp/src/uart/mod.rs +++ b/embassy-rp/src/uart/mod.rs @@ -807,26 +807,26 @@ mod eh02 { mod eh1 { use super::*; - impl embedded_hal_1::serial::Error for Error { - fn kind(&self) -> embedded_hal_1::serial::ErrorKind { + impl embedded_hal_nb::serial::Error for Error { + fn kind(&self) -> embedded_hal_nb::serial::ErrorKind { match *self { - Self::Framing => embedded_hal_1::serial::ErrorKind::FrameFormat, - Self::Break => embedded_hal_1::serial::ErrorKind::Other, - Self::Overrun => embedded_hal_1::serial::ErrorKind::Overrun, - Self::Parity => embedded_hal_1::serial::ErrorKind::Parity, + Self::Framing => embedded_hal_nb::serial::ErrorKind::FrameFormat, + Self::Break => embedded_hal_nb::serial::ErrorKind::Other, + Self::Overrun => embedded_hal_nb::serial::ErrorKind::Overrun, + Self::Parity => embedded_hal_nb::serial::ErrorKind::Parity, } } } - impl<'d, T: Instance, M: Mode> embedded_hal_1::serial::ErrorType for Uart<'d, T, M> { + impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::ErrorType for UartRx<'d, T, M> { type Error = Error; } - impl<'d, T: Instance, M: Mode> embedded_hal_1::serial::ErrorType for UartTx<'d, T, M> { + impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::ErrorType for UartTx<'d, T, M> { type Error = Error; } - impl<'d, T: Instance, M: Mode> embedded_hal_1::serial::ErrorType for UartRx<'d, T, M> { + impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::ErrorType for Uart<'d, T, M> { type Error = Error; } @@ -851,16 +851,6 @@ mod eh1 { } } - impl<'d, T: Instance, M: Mode> embedded_hal_1::serial::Write for UartTx<'d, T, M> { - fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(buffer) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - self.blocking_flush() - } - } - impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Write for UartTx<'d, T, M> { fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { self.blocking_write(&[char]).map_err(nb::Error::Other) @@ -877,16 +867,6 @@ mod eh1 { } } - impl<'d, T: Instance, M: Mode> embedded_hal_1::serial::Write for Uart<'d, T, M> { - fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(buffer) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - self.blocking_flush() - } - } - impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Write for Uart<'d, T, M> { fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { self.blocking_write(&[char]).map_err(nb::Error::Other) diff --git a/embassy-stm32-wpan/src/mac/driver.rs b/embassy-stm32-wpan/src/mac/driver.rs index f8e3a2b0..bfc4f1ee 100644 --- a/embassy-stm32-wpan/src/mac/driver.rs +++ b/embassy-stm32-wpan/src/mac/driver.rs @@ -28,7 +28,9 @@ impl<'d> embassy_net_driver::Driver for Driver<'d> { type TxToken<'a> = TxToken<'d> where Self: 'a; fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { - if self.runner.rx_channel.poll_ready_to_receive(cx) && self.runner.tx_buf_channel.poll_ready_to_receive(cx) { + if self.runner.rx_channel.poll_ready_to_receive(cx).is_ready() + && self.runner.tx_buf_channel.poll_ready_to_receive(cx).is_ready() + { Some(( RxToken { rx: &self.runner.rx_channel, @@ -44,7 +46,7 @@ impl<'d> embassy_net_driver::Driver for Driver<'d> { } fn transmit(&mut self, cx: &mut Context) -> Option> { - if self.runner.tx_buf_channel.poll_ready_to_receive(cx) { + if self.runner.tx_buf_channel.poll_ready_to_receive(cx).is_ready() { Some(TxToken { tx: &self.runner.tx_channel, tx_buf: &self.runner.tx_buf_channel, @@ -91,7 +93,7 @@ impl<'d> embassy_net_driver::RxToken for RxToken<'d> { { // Only valid data events should be put into the queue - let data_event = match self.rx.try_recv().unwrap() { + let data_event = match self.rx.try_receive().unwrap() { MacEvent::McpsDataInd(data_event) => data_event, _ => unreachable!(), }; @@ -111,7 +113,7 @@ impl<'d> embassy_net_driver::TxToken for TxToken<'d> { F: FnOnce(&mut [u8]) -> R, { // Only valid tx buffers should be put into the queue - let buf = self.tx_buf.try_recv().unwrap(); + let buf = self.tx_buf.try_receive().unwrap(); let r = f(&mut buf[..len]); // The tx channel should always be of equal capacity to the tx_buf channel diff --git a/embassy-stm32-wpan/src/mac/runner.rs b/embassy-stm32-wpan/src/mac/runner.rs index 1be6df8a..d3099b6b 100644 --- a/embassy-stm32-wpan/src/mac/runner.rs +++ b/embassy-stm32-wpan/src/mac/runner.rs @@ -73,7 +73,7 @@ impl<'a> Runner<'a> { let mut msdu_handle = 0x02; loop { - let (buf, len) = self.tx_channel.recv().await; + let (buf, len) = self.tx_channel.receive().await; let _wm = self.write_mutex.lock().await; // The mutex should be dropped on the next loop iteration diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 0d713f60..fe5dc443 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -40,9 +40,9 @@ embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" } embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver", optional = true } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } -embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11", optional = true} -embedded-hal-async = { version = "=0.2.0-alpha.2", optional = true} -embedded-hal-nb = { version = "=1.0.0-alpha.3", optional = true} +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1", optional = true} +embedded-hal-async = { version = "=1.0.0-rc.1", optional = true} +embedded-hal-nb = { version = "=1.0.0-rc.1", optional = true} embedded-storage = "0.3.0" embedded-storage-async = { version = "0.4.0", optional = true } @@ -57,7 +57,7 @@ sdio-host = "0.5.0" embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "a4f293d3a6f72158385f79c98634cb8a14d0d2fc", optional = true } critical-section = "1.1" atomic-polyfill = "1.0.1" -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-82a24863823a3daf0ca664c7fdf008379d0a0d42" } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-2b87e34c661e19ff6dc603fabfe7fe99ab7261f7" } vcell = "0.1.3" bxcan = "0.7.0" nb = "1.0.0" @@ -76,7 +76,7 @@ critical-section = { version = "1.1", features = ["std"] } [build-dependencies] proc-macro2 = "1.0.36" quote = "1.0.15" -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-82a24863823a3daf0ca664c7fdf008379d0a0d42", default-features = false, features = ["metadata"]} +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-2b87e34c661e19ff6dc603fabfe7fe99ab7261f7", default-features = false, features = ["metadata"]} [features] default = ["rt"] diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 30e25aef..8a731620 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -310,7 +310,11 @@ fn main() { for p in METADATA.peripherals { // generating RccPeripheral impl for H7 ADC3 would result in bad frequency - if !singletons.contains(&p.name.to_string()) || (p.name == "ADC3" && METADATA.line.starts_with("STM32H7")) { + if !singletons.contains(&p.name.to_string()) + || (p.name == "ADC3" && METADATA.line.starts_with("STM32H7")) + || (p.name.starts_with("ADC") && p.registers.as_ref().map_or(false, |r| r.version == "f3")) + || (p.name.starts_with("ADC") && p.registers.as_ref().map_or(false, |r| r.version == "v4")) + { continue; } diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs index 94a8538b..e57889aa 100644 --- a/embassy-stm32/src/adc/mod.rs +++ b/embassy-stm32/src/adc/mod.rs @@ -1,6 +1,6 @@ #![macro_use] -#[cfg(not(adc_f3))] +#[cfg(not(any(adc_f3, adc_f3_v2)))] #[cfg_attr(adc_f1, path = "f1.rs")] #[cfg_attr(adc_v1, path = "v1.rs")] #[cfg_attr(adc_v2, path = "v2.rs")] @@ -8,16 +8,16 @@ #[cfg_attr(adc_v4, path = "v4.rs")] mod _version; -#[cfg(not(any(adc_f1, adc_f3)))] +#[cfg(not(any(adc_f1, adc_f3, adc_f3_v2)))] mod resolution; mod sample_time; -#[cfg(not(adc_f3))] +#[cfg(not(any(adc_f3, adc_f3_v2)))] #[allow(unused)] pub use _version::*; -#[cfg(not(any(adc_f1, adc_f3)))] +#[cfg(not(any(adc_f1, adc_f3, adc_f3_v2)))] pub use resolution::Resolution; -#[cfg(not(adc_f3))] +#[cfg(not(any(adc_f3, adc_f3_v2)))] pub use sample_time::SampleTime; use crate::peripherals; @@ -25,14 +25,14 @@ use crate::peripherals; pub struct Adc<'d, T: Instance> { #[allow(unused)] adc: crate::PeripheralRef<'d, T>, - #[cfg(not(adc_f3))] + #[cfg(not(any(adc_f3, adc_f3_v2)))] sample_time: SampleTime, } pub(crate) mod sealed { pub trait Instance { fn regs() -> crate::pac::adc::Adc; - #[cfg(not(any(adc_f1, adc_v1, adc_f3)))] + #[cfg(not(any(adc_f1, adc_v1, adc_f3, adc_f3_v2)))] fn common_regs() -> crate::pac::adccommon::AdcCommon; } @@ -60,7 +60,7 @@ foreach_peripheral!( fn regs() -> crate::pac::adc::Adc { crate::pac::$inst } - #[cfg(not(any(adc_f1, adc_v1, adc_f3)))] + #[cfg(not(any(adc_f1, adc_v1, adc_f3, adc_f3_v2)))] fn common_regs() -> crate::pac::adccommon::AdcCommon { foreach_peripheral!{ (adccommon, $common_inst:ident) => { diff --git a/embassy-stm32/src/adc/sample_time.rs b/embassy-stm32/src/adc/sample_time.rs index df052556..5480e7a7 100644 --- a/embassy-stm32/src/adc/sample_time.rs +++ b/embassy-stm32/src/adc/sample_time.rs @@ -1,4 +1,4 @@ -#[cfg(not(adc_f3))] +#[cfg(not(any(adc_f3, adc_f3_v2)))] macro_rules! impl_sample_time { ($default_doc:expr, $default:ident, ($(($doc:expr, $variant:ident, $pac_variant:ident)),*)) => { #[doc = concat!("ADC sample time\n\nThe default setting is ", $default_doc, " ADC clock cycles.")] diff --git a/embassy-stm32/src/can/bxcan.rs b/embassy-stm32/src/can/bxcan.rs index fb223e4a..7ad13cec 100644 --- a/embassy-stm32/src/can/bxcan.rs +++ b/embassy-stm32/src/can/bxcan.rs @@ -478,7 +478,7 @@ impl<'c, 'd, T: Instance> CanRx<'c, 'd, T> { pub async fn read(&mut self) -> Result { poll_fn(|cx| { T::state().err_waker.register(cx.waker()); - if let Poll::Ready(envelope) = T::state().rx_queue.recv().poll_unpin(cx) { + if let Poll::Ready(envelope) = T::state().rx_queue.receive().poll_unpin(cx) { return Poll::Ready(Ok(envelope)); } else if let Some(err) = self.curr_error() { return Poll::Ready(Err(err)); @@ -493,7 +493,7 @@ impl<'c, 'd, T: Instance> CanRx<'c, 'd, T> { /// /// Returns [Err(TryReadError::Empty)] if there are no frames in the rx queue. pub fn try_read(&mut self) -> Result { - if let Ok(envelope) = T::state().rx_queue.try_recv() { + if let Ok(envelope) = T::state().rx_queue.try_receive() { return Ok(envelope); } @@ -506,14 +506,7 @@ impl<'c, 'd, T: Instance> CanRx<'c, 'd, T> { /// Waits while receive queue is empty. pub async fn wait_not_empty(&mut self) { - poll_fn(|cx| { - if T::state().rx_queue.poll_ready_to_receive(cx) { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await + poll_fn(|cx| T::state().rx_queue.poll_ready_to_receive(cx)).await } fn curr_error(&self) -> Option { diff --git a/embassy-stm32/src/dma/bdma.rs b/embassy-stm32/src/dma/bdma.rs index 60f4fbd0..20ff29be 100644 --- a/embassy-stm32/src/dma/bdma.rs +++ b/embassy-stm32/src/dma/bdma.rs @@ -393,6 +393,10 @@ impl<'a, C: Channel> DmaCtrl for DmaCtrlImpl<'a, C> { fn reset_complete_count(&mut self) -> usize { STATE.complete_count[self.0.index()].swap(0, Ordering::AcqRel) } + + fn set_waker(&mut self, waker: &Waker) { + STATE.ch_wakers[self.0.index()].register(waker); + } } pub struct ReadableRingBuffer<'a, C: Channel, W: Word> { @@ -463,7 +467,7 @@ impl<'a, C: Channel, W: Word> ReadableRingBuffer<'a, C, W> { } pub fn clear(&mut self) { - self.ringbuf.clear(DmaCtrlImpl(self.channel.reborrow())); + self.ringbuf.clear(&mut DmaCtrlImpl(self.channel.reborrow())); } /// Read elements from the ring buffer @@ -472,7 +476,7 @@ impl<'a, C: Channel, W: Word> ReadableRingBuffer<'a, C, W> { /// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the read /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. pub fn read(&mut self, buf: &mut [W]) -> Result<(usize, usize), OverrunError> { - self.ringbuf.read(DmaCtrlImpl(self.channel.reborrow()), buf) + self.ringbuf.read(&mut DmaCtrlImpl(self.channel.reborrow()), buf) } /// Read an exact number of elements from the ringbuffer. @@ -487,39 +491,18 @@ impl<'a, C: Channel, W: Word> ReadableRingBuffer<'a, C, W> { /// - If M equals N/2 or N/2 divides evenly into M, this function will return every N/2 elements read on the DMA source. /// - Otherwise, this function may need up to N/2 extra elements to arrive before returning. pub async fn read_exact(&mut self, buffer: &mut [W]) -> Result { - use core::future::poll_fn; - use core::sync::atomic::compiler_fence; - - let mut read_data = 0; - let buffer_len = buffer.len(); - - poll_fn(|cx| { - self.set_waker(cx.waker()); - - compiler_fence(Ordering::SeqCst); - - match self.read(&mut buffer[read_data..buffer_len]) { - Ok((len, remaining)) => { - read_data += len; - if read_data == buffer_len { - Poll::Ready(Ok(remaining)) - } else { - Poll::Pending - } - } - Err(e) => Poll::Ready(Err(e)), - } - }) - .await + self.ringbuf + .read_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) + .await } /// The capacity of the ringbuffer. - pub fn cap(&self) -> usize { + pub const fn cap(&self) -> usize { self.ringbuf.cap() } pub fn set_waker(&mut self, waker: &Waker) { - STATE.ch_wakers[self.channel.index()].register(waker); + DmaCtrlImpl(self.channel.reborrow()).set_waker(waker); } fn clear_irqs(&mut self) { @@ -628,50 +611,29 @@ impl<'a, C: Channel, W: Word> WritableRingBuffer<'a, C, W> { } pub fn clear(&mut self) { - self.ringbuf.clear(DmaCtrlImpl(self.channel.reborrow())); + self.ringbuf.clear(&mut DmaCtrlImpl(self.channel.reborrow())); } /// Write elements to the ring buffer /// Return a tuple of the length written and the length remaining in the buffer pub fn write(&mut self, buf: &[W]) -> Result<(usize, usize), OverrunError> { - self.ringbuf.write(DmaCtrlImpl(self.channel.reborrow()), buf) + self.ringbuf.write(&mut DmaCtrlImpl(self.channel.reborrow()), buf) } /// Write an exact number of elements to the ringbuffer. pub async fn write_exact(&mut self, buffer: &[W]) -> Result { - use core::future::poll_fn; - use core::sync::atomic::compiler_fence; - - let mut written_data = 0; - let buffer_len = buffer.len(); - - poll_fn(|cx| { - self.set_waker(cx.waker()); - - compiler_fence(Ordering::SeqCst); - - match self.write(&buffer[written_data..buffer_len]) { - Ok((len, remaining)) => { - written_data += len; - if written_data == buffer_len { - Poll::Ready(Ok(remaining)) - } else { - Poll::Pending - } - } - Err(e) => Poll::Ready(Err(e)), - } - }) - .await + self.ringbuf + .write_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) + .await } /// The capacity of the ringbuffer. - pub fn cap(&self) -> usize { + pub const fn cap(&self) -> usize { self.ringbuf.cap() } pub fn set_waker(&mut self, waker: &Waker) { - STATE.ch_wakers[self.channel.index()].register(waker); + DmaCtrlImpl(self.channel.reborrow()).set_waker(waker); } fn clear_irqs(&mut self) { diff --git a/embassy-stm32/src/dma/dma.rs b/embassy-stm32/src/dma/dma.rs index 9cd7aa8d..5033ae47 100644 --- a/embassy-stm32/src/dma/dma.rs +++ b/embassy-stm32/src/dma/dma.rs @@ -623,6 +623,10 @@ impl<'a, C: Channel> DmaCtrl for DmaCtrlImpl<'a, C> { fn reset_complete_count(&mut self) -> usize { STATE.complete_count[self.0.index()].swap(0, Ordering::AcqRel) } + + fn set_waker(&mut self, waker: &Waker) { + STATE.ch_wakers[self.0.index()].register(waker); + } } pub struct ReadableRingBuffer<'a, C: Channel, W: Word> { @@ -708,7 +712,7 @@ impl<'a, C: Channel, W: Word> ReadableRingBuffer<'a, C, W> { } pub fn clear(&mut self) { - self.ringbuf.clear(DmaCtrlImpl(self.channel.reborrow())); + self.ringbuf.clear(&mut DmaCtrlImpl(self.channel.reborrow())); } /// Read elements from the ring buffer @@ -717,7 +721,7 @@ impl<'a, C: Channel, W: Word> ReadableRingBuffer<'a, C, W> { /// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the read /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. pub fn read(&mut self, buf: &mut [W]) -> Result<(usize, usize), OverrunError> { - self.ringbuf.read(DmaCtrlImpl(self.channel.reborrow()), buf) + self.ringbuf.read(&mut DmaCtrlImpl(self.channel.reborrow()), buf) } /// Read an exact number of elements from the ringbuffer. @@ -732,39 +736,18 @@ impl<'a, C: Channel, W: Word> ReadableRingBuffer<'a, C, W> { /// - If M equals N/2 or N/2 divides evenly into M, this function will return every N/2 elements read on the DMA source. /// - Otherwise, this function may need up to N/2 extra elements to arrive before returning. pub async fn read_exact(&mut self, buffer: &mut [W]) -> Result { - use core::future::poll_fn; - use core::sync::atomic::compiler_fence; - - let mut read_data = 0; - let buffer_len = buffer.len(); - - poll_fn(|cx| { - self.set_waker(cx.waker()); - - compiler_fence(Ordering::SeqCst); - - match self.read(&mut buffer[read_data..buffer_len]) { - Ok((len, remaining)) => { - read_data += len; - if read_data == buffer_len { - Poll::Ready(Ok(remaining)) - } else { - Poll::Pending - } - } - Err(e) => Poll::Ready(Err(e)), - } - }) - .await + self.ringbuf + .read_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) + .await } // The capacity of the ringbuffer - pub fn cap(&self) -> usize { + pub const fn cap(&self) -> usize { self.ringbuf.cap() } pub fn set_waker(&mut self, waker: &Waker) { - STATE.ch_wakers[self.channel.index()].register(waker); + DmaCtrlImpl(self.channel.reborrow()).set_waker(waker); } fn clear_irqs(&mut self) { @@ -890,50 +873,29 @@ impl<'a, C: Channel, W: Word> WritableRingBuffer<'a, C, W> { } pub fn clear(&mut self) { - self.ringbuf.clear(DmaCtrlImpl(self.channel.reborrow())); + self.ringbuf.clear(&mut DmaCtrlImpl(self.channel.reborrow())); } /// Write elements from the ring buffer /// Return a tuple of the length written and the length remaining in the buffer pub fn write(&mut self, buf: &[W]) -> Result<(usize, usize), OverrunError> { - self.ringbuf.write(DmaCtrlImpl(self.channel.reborrow()), buf) + self.ringbuf.write(&mut DmaCtrlImpl(self.channel.reborrow()), buf) } /// Write an exact number of elements to the ringbuffer. pub async fn write_exact(&mut self, buffer: &[W]) -> Result { - use core::future::poll_fn; - use core::sync::atomic::compiler_fence; - - let mut written_data = 0; - let buffer_len = buffer.len(); - - poll_fn(|cx| { - self.set_waker(cx.waker()); - - compiler_fence(Ordering::SeqCst); - - match self.write(&buffer[written_data..buffer_len]) { - Ok((len, remaining)) => { - written_data += len; - if written_data == buffer_len { - Poll::Ready(Ok(remaining)) - } else { - Poll::Pending - } - } - Err(e) => Poll::Ready(Err(e)), - } - }) - .await + self.ringbuf + .write_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) + .await } // The capacity of the ringbuffer - pub fn cap(&self) -> usize { + pub const fn cap(&self) -> usize { self.ringbuf.cap() } pub fn set_waker(&mut self, waker: &Waker) { - STATE.ch_wakers[self.channel.index()].register(waker); + DmaCtrlImpl(self.channel.reborrow()).set_waker(waker); } fn clear_irqs(&mut self) { diff --git a/embassy-stm32/src/dma/ringbuffer.rs b/embassy-stm32/src/dma/ringbuffer.rs index 6ce59e45..c9f7a302 100644 --- a/embassy-stm32/src/dma/ringbuffer.rs +++ b/embassy-stm32/src/dma/ringbuffer.rs @@ -1,7 +1,9 @@ #![cfg_attr(gpdma, allow(unused))] +use core::future::poll_fn; use core::ops::Range; use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::{Poll, Waker}; use super::word::Word; @@ -49,6 +51,9 @@ pub trait DmaCtrl { /// Reset the transfer completed counter to 0 and return the value just prior to the reset. fn reset_complete_count(&mut self) -> usize; + + /// Set the waker for a running poll_fn + fn set_waker(&mut self, waker: &Waker); } impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { @@ -57,7 +62,7 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { } /// Reset the ring buffer to its initial state - pub fn clear(&mut self, mut dma: impl DmaCtrl) { + pub fn clear(&mut self, dma: &mut impl DmaCtrl) { self.start = 0; dma.reset_complete_count(); } @@ -68,8 +73,43 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { } /// The current position of the ringbuffer - fn pos(&self, remaining_transfers: usize) -> usize { - self.cap() - remaining_transfers + fn pos(&self, dma: &mut impl DmaCtrl) -> usize { + self.cap() - dma.get_remaining_transfers() + } + + /// Read an exact number of elements from the ringbuffer. + /// + /// Returns the remaining number of elements available for immediate reading. + /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. + /// + /// Async/Wake Behavior: + /// The underlying DMA peripheral only can wake us when its buffer pointer has reached the halfway point, + /// and when it wraps around. This means that when called with a buffer of length 'M', when this + /// ring buffer was created with a buffer of size 'N': + /// - If M equals N/2 or N/2 divides evenly into M, this function will return every N/2 elements read on the DMA source. + /// - Otherwise, this function may need up to N/2 extra elements to arrive before returning. + pub async fn read_exact(&mut self, dma: &mut impl DmaCtrl, buffer: &mut [W]) -> Result { + let mut read_data = 0; + let buffer_len = buffer.len(); + + poll_fn(|cx| { + dma.set_waker(cx.waker()); + + compiler_fence(Ordering::SeqCst); + + match self.read(dma, &mut buffer[read_data..buffer_len]) { + Ok((len, remaining)) => { + read_data += len; + if read_data == buffer_len { + Poll::Ready(Ok(remaining)) + } else { + Poll::Pending + } + } + Err(e) => Poll::Ready(Err(e)), + } + }) + .await } /// Read elements from the ring buffer @@ -77,7 +117,7 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { /// If not all of the elements were read, then there will be some elements in the buffer remaining /// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the read /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. - pub fn read(&mut self, mut dma: impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), OverrunError> { + pub fn read(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), OverrunError> { /* This algorithm is optimistic: we assume we haven't overrun more than a full buffer and then check after we've done our work to see we have. This is because on stm32, an interrupt is not guaranteed @@ -93,7 +133,7 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { rather than the data we actually copied because it costs nothing and confirms an error condition earlier. */ - let end = self.pos(dma.get_remaining_transfers()); + let end = self.pos(dma); if self.start == end && dma.get_complete_count() == 0 { // No elements are available in the buffer Ok((0, self.cap())) @@ -114,8 +154,7 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { then, get the current position of of the dma write and check if it's inside data we could have copied */ - let (pos, complete_count) = - critical_section::with(|_| (self.pos(dma.get_remaining_transfers()), dma.get_complete_count())); + let (pos, complete_count) = critical_section::with(|_| (self.pos(dma), dma.get_complete_count())); if (pos >= self.start && pos < end) || (complete_count > 0 && pos >= end) || complete_count > 1 { Err(OverrunError) } else { @@ -141,7 +180,7 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { then, get the current position of of the dma write and check if it's inside data we could have copied */ - let pos = self.pos(dma.get_remaining_transfers()); + let pos = self.pos(dma); if pos > self.start || pos < end || dma.get_complete_count() > 1 { Err(OverrunError) } else { @@ -169,7 +208,7 @@ impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { then, get the current position of of the dma write and check if it's inside data we could have copied */ - let pos = self.pos(dma.get_remaining_transfers()); + let pos = self.pos(dma); if pos > self.start || pos < end || dma.reset_complete_count() > 1 { Err(OverrunError) } else { @@ -209,7 +248,7 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { } /// Reset the ring buffer to its initial state - pub fn clear(&mut self, mut dma: impl DmaCtrl) { + pub fn clear(&mut self, dma: &mut impl DmaCtrl) { self.end = 0; dma.reset_complete_count(); } @@ -220,14 +259,39 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { } /// The current position of the ringbuffer - fn pos(&self, remaining_transfers: usize) -> usize { - self.cap() - remaining_transfers + fn pos(&self, dma: &mut impl DmaCtrl) -> usize { + self.cap() - dma.get_remaining_transfers() + } + + /// Write an exact number of elements to the ringbuffer. + pub async fn write_exact(&mut self, dma: &mut impl DmaCtrl, buffer: &[W]) -> Result { + let mut written_data = 0; + let buffer_len = buffer.len(); + + poll_fn(|cx| { + dma.set_waker(cx.waker()); + + compiler_fence(Ordering::SeqCst); + + match self.write(dma, &buffer[written_data..buffer_len]) { + Ok((len, remaining)) => { + written_data += len; + if written_data == buffer_len { + Poll::Ready(Ok(remaining)) + } else { + Poll::Pending + } + } + Err(e) => Poll::Ready(Err(e)), + } + }) + .await } /// Write elements from the ring buffer /// Return a tuple of the length written and the capacity remaining to be written in the buffer - pub fn write(&mut self, mut dma: impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), OverrunError> { - let start = self.pos(dma.get_remaining_transfers()); + pub fn write(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), OverrunError> { + let start = self.pos(dma); if start > self.end { // The occupied portion in the ring buffer DOES wrap let len = self.copy_from(buf, self.end..start); @@ -235,8 +299,7 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { compiler_fence(Ordering::SeqCst); // Confirm that the DMA is not inside data we could have written - let (pos, complete_count) = - critical_section::with(|_| (self.pos(dma.get_remaining_transfers()), dma.get_complete_count())); + let (pos, complete_count) = critical_section::with(|_| (self.pos(dma), dma.get_complete_count())); if (pos >= self.end && pos < start) || (complete_count > 0 && pos >= start) || complete_count > 1 { Err(OverrunError) } else { @@ -256,7 +319,7 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { compiler_fence(Ordering::SeqCst); // Confirm that the DMA is not inside data we could have written - let pos = self.pos(dma.get_remaining_transfers()); + let pos = self.pos(dma); if pos > self.end || pos < start || dma.get_complete_count() > 1 { Err(OverrunError) } else { @@ -274,7 +337,7 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { compiler_fence(Ordering::SeqCst); // Confirm that the DMA is not inside data we could have written - let pos = self.pos(dma.get_remaining_transfers()); + let pos = self.pos(dma); if pos > self.end || pos < start || dma.reset_complete_count() > 1 { Err(OverrunError) } else { @@ -323,7 +386,7 @@ mod tests { requests: cell::RefCell>, } - impl DmaCtrl for &mut TestCircularTransfer { + impl DmaCtrl for TestCircularTransfer { fn get_remaining_transfers(&self) -> usize { match self.requests.borrow_mut().pop().unwrap() { TestCircularTransferRequest::PositionRequest(pos) => { @@ -350,6 +413,8 @@ mod tests { _ => unreachable!(), } } + + fn set_waker(&mut self, waker: &Waker) {} } impl TestCircularTransfer { diff --git a/embassy-stm32/src/flash/h7.rs b/embassy-stm32/src/flash/h7.rs index bb429d77..625bf13f 100644 --- a/embassy-stm32/src/flash/h7.rs +++ b/embassy-stm32/src/flash/h7.rs @@ -11,7 +11,7 @@ pub const fn is_default_layout() -> bool { } const fn is_dual_bank() -> bool { - FLASH_REGIONS.len() == 2 + FLASH_REGIONS.len() >= 2 } pub fn get_flash_regions() -> &'static [&'static FlashRegion] { @@ -49,6 +49,7 @@ pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) }; bank.cr().write(|w| { w.set_pg(true); + #[cfg(flash_h7)] w.set_psize(2); // 32 bits at once }); cortex_m::asm::isb(); @@ -85,7 +86,10 @@ pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), E let bank = pac::FLASH.bank(sector.bank as usize); bank.cr().modify(|w| { w.set_ser(true); - w.set_snb(sector.index_in_bank) + #[cfg(flash_h7)] + w.set_snb(sector.index_in_bank); + #[cfg(flash_h7ab)] + w.set_ssn(sector.index_in_bank); }); bank.cr().modify(|w| { @@ -126,6 +130,10 @@ unsafe fn blocking_wait_ready(bank: pac::flash::Bank) -> Result<(), Error> { error!("incerr"); return Err(Error::Seq); } + if sr.crcrderr() { + error!("crcrderr"); + return Err(Error::Seq); + } if sr.operr() { return Err(Error::Prog); } diff --git a/embassy-stm32/src/flash/mod.rs b/embassy-stm32/src/flash/mod.rs index 4308037f..fb20dcd3 100644 --- a/embassy-stm32/src/flash/mod.rs +++ b/embassy-stm32/src/flash/mod.rs @@ -65,9 +65,11 @@ impl FlashRegion { #[cfg_attr(flash_f7, path = "f7.rs")] #[cfg_attr(flash_g0, path = "g0.rs")] #[cfg_attr(flash_h7, path = "h7.rs")] +#[cfg_attr(flash_h7ab, path = "h7.rs")] #[cfg_attr( not(any( - flash_l0, flash_l1, flash_l4, flash_wl, flash_wb, flash_f0, flash_f3, flash_f4, flash_f7, flash_g0, flash_h7 + flash_l0, flash_l1, flash_l4, flash_wl, flash_wb, flash_f0, flash_f3, flash_f4, flash_f7, flash_g0, flash_h7, + flash_h7ab )), path = "other.rs" )] diff --git a/embassy-stm32/src/rcc/f2.rs b/embassy-stm32/src/rcc/f2.rs index d016d1de..ec4ed99b 100644 --- a/embassy-stm32/src/rcc/f2.rs +++ b/embassy-stm32/src/rcc/f2.rs @@ -378,22 +378,6 @@ pub(crate) unsafe fn init(config: Config) { // Reference: STM32F215xx/217xx datasheet Table 13. General operating conditions assert!(ahb_freq <= Hertz(120_000_000)); - let flash_ws = unwrap!(config.voltage.wait_states(ahb_freq)); - FLASH.acr().modify(|w| w.set_latency(flash_ws)); - - RCC.cfgr().modify(|w| { - w.set_sw(sw.into()); - w.set_hpre(config.ahb_pre.into()); - w.set_ppre1(config.apb1_pre.into()); - w.set_ppre2(config.apb2_pre.into()); - }); - while RCC.cfgr().read().sws().to_bits() != sw.to_bits() {} - - // Turn off HSI to save power if we don't need it - if !config.hsi { - RCC.cr().modify(|w| w.set_hsion(false)); - } - let (apb1_freq, apb1_tim_freq) = match config.apb1_pre { APBPrescaler::NotDivided => (ahb_freq, ahb_freq), pre => { @@ -414,6 +398,22 @@ pub(crate) unsafe fn init(config: Config) { // Reference: STM32F215xx/217xx datasheet Table 13. General operating conditions assert!(apb2_freq <= Hertz(60_000_000)); + let flash_ws = unwrap!(config.voltage.wait_states(ahb_freq)); + FLASH.acr().modify(|w| w.set_latency(flash_ws)); + + RCC.cfgr().modify(|w| { + w.set_sw(sw.into()); + w.set_hpre(config.ahb_pre.into()); + w.set_ppre1(config.apb1_pre.into()); + w.set_ppre2(config.apb2_pre.into()); + }); + while RCC.cfgr().read().sws().to_bits() != sw.to_bits() {} + + // Turn off HSI to save power if we don't need it + if !config.hsi { + RCC.cr().modify(|w| w.set_hsion(false)); + } + set_freqs(Clocks { sys: sys_clk, ahb1: ahb_freq, diff --git a/embassy-stm32/src/rcc/h7.rs b/embassy-stm32/src/rcc/h7.rs index 0788b064..7fb4fb95 100644 --- a/embassy-stm32/src/rcc/h7.rs +++ b/embassy-stm32/src/rcc/h7.rs @@ -200,6 +200,7 @@ fn flash_setup(rcc_aclk: u32, vos: VoltageScale) { // See RM0433 Rev 7 Table 17. FLASH recommended number of wait // states and programming delay + #[cfg(flash_h7)] let (wait_states, progr_delay) = match vos { // VOS 0 range VCORE 1.26V - 1.40V VoltageScale::Scale0 => match rcc_aclk_mhz { @@ -239,6 +240,50 @@ fn flash_setup(rcc_aclk: u32, vos: VoltageScale) { }, }; + // See RM0455 Rev 10 Table 16. FLASH recommended number of wait + // states and programming delay + #[cfg(flash_h7ab)] + let (wait_states, progr_delay) = match vos { + // VOS 0 range VCORE 1.25V - 1.35V + VoltageScale::Scale0 => match rcc_aclk_mhz { + 0..=42 => (0, 0), + 43..=84 => (1, 0), + 85..=126 => (2, 1), + 127..=168 => (3, 1), + 169..=210 => (4, 2), + 211..=252 => (5, 2), + 253..=280 => (6, 3), + _ => (7, 3), + }, + // VOS 1 range VCORE 1.15V - 1.25V + VoltageScale::Scale1 => match rcc_aclk_mhz { + 0..=38 => (0, 0), + 39..=76 => (1, 0), + 77..=114 => (2, 1), + 115..=152 => (3, 1), + 153..=190 => (4, 2), + 191..=225 => (5, 2), + _ => (7, 3), + }, + // VOS 2 range VCORE 1.05V - 1.15V + VoltageScale::Scale2 => match rcc_aclk_mhz { + 0..=34 => (0, 0), + 35..=68 => (1, 0), + 69..=102 => (2, 1), + 103..=136 => (3, 1), + 137..=160 => (4, 2), + _ => (7, 3), + }, + // VOS 3 range VCORE 0.95V - 1.05V + VoltageScale::Scale3 => match rcc_aclk_mhz { + 0..=22 => (0, 0), + 23..=44 => (1, 0), + 45..=66 => (2, 1), + 67..=88 => (3, 1), + _ => (7, 3), + }, + }; + FLASH.acr().write(|w| { w.set_wrhighfreq(progr_delay); w.set_latency(wait_states) @@ -538,8 +583,6 @@ pub(crate) unsafe fn init(mut config: Config) { let requested_pclk4 = config.pclk4.map(|v| v.0).unwrap_or_else(|| pclk_max.min(rcc_hclk / 2)); let (rcc_pclk4, ppre4_bits, ppre4, _) = ppre_calculate(requested_pclk4, rcc_hclk, pclk_max, None); - flash_setup(rcc_aclk, pwr_vos); - // Start switching clocks ------------------- // Ensure CSI is on and stable @@ -595,6 +638,8 @@ pub(crate) unsafe fn init(mut config: Config) { // core voltage while RCC.d1cfgr().read().d1cpre().to_bits() != d1cpre_bits {} + flash_setup(rcc_aclk, pwr_vos); + // APB1 / APB2 Prescaler RCC.d2cfgr().modify(|w| { w.set_d2ppre1(Dppre::from_bits(ppre1_bits)); diff --git a/embassy-stm32/src/rcc/l4.rs b/embassy-stm32/src/rcc/l4.rs index b2828e58..b34b8caa 100644 --- a/embassy-stm32/src/rcc/l4.rs +++ b/embassy-stm32/src/rcc/l4.rs @@ -410,10 +410,11 @@ pub(crate) unsafe fn init(config: Config) { while RCC.cfgr().read().sws() != Sw::MSI {} } + RCC.apb1enr1().modify(|w| w.set_pwren(true)); + match config.rtc_mux { RtcClockSource::LSE32 => { // 1. Unlock the backup domain - RCC.apb1enr1().modify(|w| w.set_pwren(true)); PWR.cr1().modify(|w| w.set_dbp(true)); // 2. Setup the LSE diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index 4d4c6b61..ac9ae9c6 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs @@ -9,7 +9,7 @@ use crate::time::Hertz; #[cfg_attr(rcc_f0, path = "f0.rs")] #[cfg_attr(any(rcc_f1, rcc_f100, rcc_f1cl), path = "f1.rs")] #[cfg_attr(rcc_f2, path = "f2.rs")] -#[cfg_attr(rcc_f3, path = "f3.rs")] +#[cfg_attr(any(rcc_f3, rcc_f3_v2), path = "f3.rs")] #[cfg_attr(any(rcc_f4, rcc_f410), path = "f4.rs")] #[cfg_attr(rcc_f7, path = "f7.rs")] #[cfg_attr(rcc_c0, path = "c0.rs")] diff --git a/embassy-stm32/src/rcc/wl.rs b/embassy-stm32/src/rcc/wl.rs index ea6e8dde..f1dd2bd7 100644 --- a/embassy-stm32/src/rcc/wl.rs +++ b/embassy-stm32/src/rcc/wl.rs @@ -2,6 +2,7 @@ pub use super::common::{AHBPrescaler, APBPrescaler, VoltageScale}; use crate::pac::pwr::vals::Dbp; use crate::pac::{FLASH, PWR, RCC}; use crate::rcc::{set_freqs, Clocks}; +use crate::rtc::{Rtc, RtcClockSource as RCS}; use crate::time::Hertz; /// Most of clock setup is copied from stm32l0xx-hal, and adopted to the generated PAC, @@ -229,6 +230,8 @@ pub(crate) unsafe fn init(config: Config) { // Wait until LSE is running while !RCC.bdcr().read().lserdy() {} + + Rtc::set_clock_source(RCS::LSE); } RtcClockSource::LSI32 => { // Turn on the internal 32 kHz LSI oscillator @@ -236,6 +239,8 @@ pub(crate) unsafe fn init(config: Config) { // Wait until LSI is running while !RCC.csr().read().lsirdy() {} + + Rtc::set_clock_source(RCS::LSI); } } diff --git a/embassy-stm32/src/rng.rs b/embassy-stm32/src/rng.rs index 2a4978ec..30816e43 100644 --- a/embassy-stm32/src/rng.rs +++ b/embassy-stm32/src/rng.rs @@ -73,15 +73,20 @@ impl<'d, T: Instance> Rng<'d, T> { #[cfg(not(rng_v1))] pub fn reset(&mut self) { T::regs().cr().write(|reg| { - reg.set_rngen(false); reg.set_condrst(true); + reg.set_nistc(pac::rng::vals::Nistc::CUSTOM); // set RNG config "A" according to reference manual // this has to be written within the same write access as setting the CONDRST bit - reg.set_nistc(pac::rng::vals::Nistc::DEFAULT); reg.set_rng_config1(pac::rng::vals::RngConfig1::CONFIGA); + reg.set_clkdiv(pac::rng::vals::Clkdiv::NODIV); reg.set_rng_config2(pac::rng::vals::RngConfig2::CONFIGA_B); reg.set_rng_config3(pac::rng::vals::RngConfig3::CONFIGA); - reg.set_clkdiv(pac::rng::vals::Clkdiv::NODIV); + reg.set_ced(true); + reg.set_ie(false); + reg.set_rngen(true); + }); + T::regs().cr().write(|reg| { + reg.set_ced(false); }); // wait for CONDRST to be set while !T::regs().cr().read().condrst() {} diff --git a/embassy-stm32/src/rtc/v2.rs b/embassy-stm32/src/rtc/v2.rs index 4a166e19..744e9a11 100644 --- a/embassy-stm32/src/rtc/v2.rs +++ b/embassy-stm32/src/rtc/v2.rs @@ -72,6 +72,7 @@ impl core::ops::Sub for RtcInstant { } } +#[allow(dead_code)] #[derive(Clone, Copy)] pub(crate) enum WakeupPrescaler { Div2, @@ -120,6 +121,7 @@ impl From for u32 { } } +#[allow(dead_code)] impl WakeupPrescaler { pub fn compute_min(val: u32) -> Self { *[ diff --git a/embassy-stm32/src/timer/complementary_pwm.rs b/embassy-stm32/src/timer/complementary_pwm.rs index acd67048..4f033e3a 100644 --- a/embassy-stm32/src/timer/complementary_pwm.rs +++ b/embassy-stm32/src/timer/complementary_pwm.rs @@ -100,14 +100,19 @@ impl<'d, T: ComplementaryCaptureCompare16bitInstance> ComplementaryPwm<'d, T> { } pub fn get_max_duty(&self) -> u16 { - self.inner.get_max_compare_value() + self.inner.get_max_compare_value() + 1 } pub fn set_duty(&mut self, channel: Channel, duty: u16) { - assert!(duty < self.get_max_duty()); + assert!(duty <= self.get_max_duty()); self.inner.set_compare_value(channel, duty) } + pub fn set_polarity(&mut self, channel: Channel, polarity: OutputPolarity) { + self.inner.set_output_polarity(channel, polarity); + self.inner.set_complementary_output_polarity(channel, polarity); + } + /// Set the dead time as a proportion of max_duty pub fn set_dead_time(&mut self, value: u16) { let (ckd, value) = compute_dead_time_value(value); diff --git a/embassy-stm32/src/timer/mod.rs b/embassy-stm32/src/timer/mod.rs index 6c2d6d82..4ffb2a28 100644 --- a/embassy-stm32/src/timer/mod.rs +++ b/embassy-stm32/src/timer/mod.rs @@ -53,6 +53,8 @@ pub(crate) mod sealed { fn set_output_compare_mode(&mut self, channel: Channel, mode: OutputCompareMode); + fn set_output_polarity(&mut self, channel: Channel, polarity: OutputPolarity); + fn enable_channel(&mut self, channel: Channel, enable: bool); fn set_compare_value(&mut self, channel: Channel, value: u16); @@ -61,6 +63,8 @@ pub(crate) mod sealed { } pub trait ComplementaryCaptureCompare16bitInstance: CaptureCompare16bitInstance { + fn set_complementary_output_polarity(&mut self, channel: Channel, polarity: OutputPolarity); + fn set_dead_time_clock_division(&mut self, value: vals::Ckd); fn set_dead_time_value(&mut self, value: u8); @@ -71,6 +75,8 @@ pub(crate) mod sealed { pub trait CaptureCompare32bitInstance: GeneralPurpose32bitInstance { fn set_output_compare_mode(&mut self, channel: Channel, mode: OutputCompareMode); + fn set_output_polarity(&mut self, channel: Channel, polarity: OutputPolarity); + fn enable_channel(&mut self, channel: Channel, enable: bool); fn set_compare_value(&mut self, channel: Channel, value: u32); @@ -125,6 +131,21 @@ impl From for stm32_metapac::timer::vals::Ocm { } } +#[derive(Clone, Copy)] +pub enum OutputPolarity { + ActiveHigh, + ActiveLow, +} + +impl From for bool { + fn from(mode: OutputPolarity) -> Self { + match mode { + OutputPolarity::ActiveHigh => false, + OutputPolarity::ActiveLow => true, + } + } +} + pub trait Basic16bitInstance: sealed::Basic16bitInstance + 'static {} pub trait GeneralPurpose16bitInstance: sealed::GeneralPurpose16bitInstance + 'static {} @@ -265,6 +286,13 @@ macro_rules! impl_compare_capable_16bit { .modify(|w| w.set_ocm(raw_channel % 2, mode.into())); } + fn set_output_polarity(&mut self, channel: Channel, polarity: OutputPolarity) { + use sealed::GeneralPurpose16bitInstance; + Self::regs_gp16() + .ccer() + .modify(|w| w.set_ccp(channel.raw(), polarity.into())); + } + fn enable_channel(&mut self, channel: Channel, enable: bool) { use sealed::GeneralPurpose16bitInstance; Self::regs_gp16() @@ -325,6 +353,13 @@ foreach_interrupt! { Self::regs_gp32().ccmr_output(raw_channel / 2).modify(|w| w.set_ocm(raw_channel % 2, mode.into())); } + fn set_output_polarity(&mut self, channel: Channel, polarity: OutputPolarity) { + use crate::timer::sealed::GeneralPurpose32bitInstance; + Self::regs_gp32() + .ccer() + .modify(|w| w.set_ccp(channel.raw(), polarity.into())); + } + fn enable_channel(&mut self, channel: Channel, enable: bool) { use crate::timer::sealed::GeneralPurpose32bitInstance; Self::regs_gp32().ccer().modify(|w| w.set_cce(channel.raw(), enable)); @@ -388,6 +423,13 @@ foreach_interrupt! { .modify(|w| w.set_ocm(raw_channel % 2, mode.into())); } + fn set_output_polarity(&mut self, channel: Channel, polarity: OutputPolarity) { + use crate::timer::sealed::AdvancedControlInstance; + Self::regs_advanced() + .ccer() + .modify(|w| w.set_ccp(channel.raw(), polarity.into())); + } + fn enable_channel(&mut self, channel: Channel, enable: bool) { use crate::timer::sealed::AdvancedControlInstance; Self::regs_advanced() @@ -409,6 +451,13 @@ foreach_interrupt! { } impl sealed::ComplementaryCaptureCompare16bitInstance for crate::peripherals::$inst { + fn set_complementary_output_polarity(&mut self, channel: Channel, polarity: OutputPolarity) { + use crate::timer::sealed::AdvancedControlInstance; + Self::regs_advanced() + .ccer() + .modify(|w| w.set_ccnp(channel.raw(), polarity.into())); + } + fn set_dead_time_clock_division(&mut self, value: vals::Ckd) { use crate::timer::sealed::AdvancedControlInstance; Self::regs_advanced().cr1().modify(|w| w.set_ckd(value)); diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index e0a81792..9e28878b 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -97,11 +97,15 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> { } pub fn get_max_duty(&self) -> u16 { - self.inner.get_max_compare_value() + self.inner.get_max_compare_value() + 1 } pub fn set_duty(&mut self, channel: Channel, duty: u16) { - assert!(duty < self.get_max_duty()); + assert!(duty <= self.get_max_duty()); self.inner.set_compare_value(channel, duty) } + + pub fn set_polarity(&mut self, channel: Channel, polarity: OutputPolarity) { + self.inner.set_output_polarity(channel, polarity); + } } diff --git a/embassy-stm32/src/usart/buffered.rs b/embassy-stm32/src/usart/buffered.rs index 329fc7da..596d40bf 100644 --- a/embassy-stm32/src/usart/buffered.rs +++ b/embassy-stm32/src/usart/buffered.rs @@ -584,15 +584,15 @@ mod eh02 { mod eh1 { use super::*; - impl<'d, T: BasicInstance> embedded_hal_1::serial::ErrorType for BufferedUart<'d, T> { + impl<'d, T: BasicInstance> embedded_hal_nb::serial::ErrorType for BufferedUart<'d, T> { type Error = Error; } - impl<'d, T: BasicInstance> embedded_hal_1::serial::ErrorType for BufferedUartTx<'d, T> { + impl<'d, T: BasicInstance> embedded_hal_nb::serial::ErrorType for BufferedUartTx<'d, T> { type Error = Error; } - impl<'d, T: BasicInstance> embedded_hal_1::serial::ErrorType for BufferedUartRx<'d, T> { + impl<'d, T: BasicInstance> embedded_hal_nb::serial::ErrorType for BufferedUartRx<'d, T> { type Error = Error; } @@ -602,16 +602,6 @@ mod eh1 { } } - impl<'d, T: BasicInstance> embedded_hal_1::serial::Write for BufferedUartTx<'d, T> { - fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(buffer).map(drop) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - self.blocking_flush() - } - } - impl<'d, T: BasicInstance> embedded_hal_nb::serial::Write for BufferedUartTx<'d, T> { fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { self.blocking_write(&[char]).map(drop).map_err(nb::Error::Other) @@ -628,16 +618,6 @@ mod eh1 { } } - impl<'d, T: BasicInstance> embedded_hal_1::serial::Write for BufferedUart<'d, T> { - fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { - self.tx.blocking_write(buffer).map(drop) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - self.tx.blocking_flush() - } - } - impl<'d, T: BasicInstance> embedded_hal_nb::serial::Write for BufferedUart<'d, T> { fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { self.tx.blocking_write(&[char]).map(drop).map_err(nb::Error::Other) diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index 0c69a488..255ddfd4 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -809,45 +809,57 @@ fn configure(r: Regs, config: &Config, pclk_freq: Hertz, kind: Kind, enable_rx: Kind::Uart => (1, 0x10, 0x1_0000), }; + fn calculate_brr(baud: u32, pclk: u32, presc: u32, mul: u32) -> u32 { + // The calculation to be done to get the BRR is `mul * pclk / presc / baud` + // To do this in 32-bit only we can't multiply `mul` and `pclk` + let clock = pclk / presc; + + // The mul is applied as the last operation to prevent overflow + let brr = clock / baud * mul; + + // The BRR calculation will be a bit off because of integer rounding. + // Because we multiplied our inaccuracy with mul, our rounding now needs to be in proportion to mul. + let rounding = ((clock % baud) * mul + (baud / 2)) / baud; + + brr + rounding + } + #[cfg(not(usart_v1))] let mut over8 = false; - let mut found = None; + let mut found_brr = None; for &(presc, _presc_val) in &DIVS { - let denom = (config.baudrate * presc as u32) as u64; - let div = (pclk_freq.0 as u64 * mul + (denom / 2)) / denom; + let brr = calculate_brr(config.baudrate, pclk_freq.0, presc as u32, mul); trace!( "USART: presc={}, div=0x{:08x} (mantissa = {}, fraction = {})", presc, - div, - div >> 4, - div & 0x0F + brr, + brr >> 4, + brr & 0x0F ); - if div < brr_min { + if brr < brr_min { #[cfg(not(usart_v1))] - if div * 2 >= brr_min && kind == Kind::Uart && !cfg!(usart_v1) { + if brr * 2 >= brr_min && kind == Kind::Uart && !cfg!(usart_v1) { over8 = true; - let div = div as u32; - r.brr().write_value(regs::Brr(((div << 1) & !0xF) | (div & 0x07))); + r.brr().write_value(regs::Brr(((brr << 1) & !0xF) | (brr & 0x07))); #[cfg(usart_v4)] r.presc().write(|w| w.set_prescaler(_presc_val)); - found = Some(div); + found_brr = Some(brr); break; } panic!("USART: baudrate too high"); } - if div < brr_max { - let div = div as u32; - r.brr().write_value(regs::Brr(div)); + if brr < brr_max { + r.brr().write_value(regs::Brr(brr)); #[cfg(usart_v4)] r.presc().write(|w| w.set_prescaler(_presc_val)); - found = Some(div); + found_brr = Some(brr); break; } } - let div = found.expect("USART: baudrate too low"); + let brr = found_brr.expect("USART: baudrate too low"); #[cfg(not(usart_v1))] let oversampling = if over8 { "8 bit" } else { "16 bit" }; @@ -857,7 +869,7 @@ fn configure(r: Regs, config: &Config, pclk_freq: Hertz, kind: Kind, enable_rx: "Using {} oversampling, desired baudrate: {}, actual baudrate: {}", oversampling, config.baudrate, - (pclk_freq.0 * mul as u32) / div + pclk_freq.0 / brr * mul ); r.cr2().write(|w| { @@ -943,27 +955,27 @@ mod eh02 { mod eh1 { use super::*; - impl embedded_hal_1::serial::Error for Error { - fn kind(&self) -> embedded_hal_1::serial::ErrorKind { + impl embedded_hal_nb::serial::Error for Error { + fn kind(&self) -> embedded_hal_nb::serial::ErrorKind { match *self { - Self::Framing => embedded_hal_1::serial::ErrorKind::FrameFormat, - Self::Noise => embedded_hal_1::serial::ErrorKind::Noise, - Self::Overrun => embedded_hal_1::serial::ErrorKind::Overrun, - Self::Parity => embedded_hal_1::serial::ErrorKind::Parity, - Self::BufferTooLong => embedded_hal_1::serial::ErrorKind::Other, + Self::Framing => embedded_hal_nb::serial::ErrorKind::FrameFormat, + Self::Noise => embedded_hal_nb::serial::ErrorKind::Noise, + Self::Overrun => embedded_hal_nb::serial::ErrorKind::Overrun, + Self::Parity => embedded_hal_nb::serial::ErrorKind::Parity, + Self::BufferTooLong => embedded_hal_nb::serial::ErrorKind::Other, } } } - impl<'d, T: BasicInstance, TxDma, RxDma> embedded_hal_1::serial::ErrorType for Uart<'d, T, TxDma, RxDma> { + impl<'d, T: BasicInstance, TxDma, RxDma> embedded_hal_nb::serial::ErrorType for Uart<'d, T, TxDma, RxDma> { type Error = Error; } - impl<'d, T: BasicInstance, TxDma> embedded_hal_1::serial::ErrorType for UartTx<'d, T, TxDma> { + impl<'d, T: BasicInstance, TxDma> embedded_hal_nb::serial::ErrorType for UartTx<'d, T, TxDma> { type Error = Error; } - impl<'d, T: BasicInstance, RxDma> embedded_hal_1::serial::ErrorType for UartRx<'d, T, RxDma> { + impl<'d, T: BasicInstance, RxDma> embedded_hal_nb::serial::ErrorType for UartRx<'d, T, RxDma> { type Error = Error; } @@ -973,16 +985,6 @@ mod eh1 { } } - impl<'d, T: BasicInstance, TxDma> embedded_hal_1::serial::Write for UartTx<'d, T, TxDma> { - fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(buffer) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - self.blocking_flush() - } - } - impl<'d, T: BasicInstance, TxDma> embedded_hal_nb::serial::Write for UartTx<'d, T, TxDma> { fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { self.blocking_write(&[char]).map_err(nb::Error::Other) @@ -999,16 +1001,6 @@ mod eh1 { } } - impl<'d, T: BasicInstance, TxDma, RxDma> embedded_hal_1::serial::Write for Uart<'d, T, TxDma, RxDma> { - fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(buffer) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - self.blocking_flush() - } - } - impl<'d, T: BasicInstance, TxDma, RxDma> embedded_hal_nb::serial::Write for Uart<'d, T, TxDma, RxDma> { fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { self.blocking_write(&[char]).map_err(nb::Error::Other) diff --git a/embassy-sync/src/blocking_mutex/raw.rs b/embassy-sync/src/blocking_mutex/raw.rs index 15796f1b..a8afcad3 100644 --- a/embassy-sync/src/blocking_mutex/raw.rs +++ b/embassy-sync/src/blocking_mutex/raw.rs @@ -11,7 +11,7 @@ use core::marker::PhantomData; /// /// Note that, unlike other mutexes, implementations only guarantee no /// concurrent access from other threads: concurrent access from the current -/// thread is allwed. For example, it's possible to lock the same mutex multiple times reentrantly. +/// thread is allowed. For example, it's possible to lock the same mutex multiple times reentrantly. /// /// Therefore, locking a `RawMutex` is only enough to guarantee safe shared (`&`) access /// to the data, it is not enough to guarantee exclusive (`&mut`) access. diff --git a/embassy-sync/src/channel.rs b/embassy-sync/src/channel.rs index d6f36f53..62ea1307 100644 --- a/embassy-sync/src/channel.rs +++ b/embassy-sync/src/channel.rs @@ -65,6 +65,13 @@ where pub fn try_send(&self, message: T) -> Result<(), TrySendError> { self.channel.try_send(message) } + + /// Allows a poll_fn to poll until the channel is ready to send + /// + /// See [`Channel::poll_ready_to_send()`] + pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_send(cx) + } } /// Send-only access to a [`Channel`] without knowing channel size. @@ -106,6 +113,13 @@ impl<'ch, T> DynamicSender<'ch, T> { pub fn try_send(&self, message: T) -> Result<(), TrySendError> { self.channel.try_send_with_context(message, None) } + + /// Allows a poll_fn to poll until the channel is ready to send + /// + /// See [`Channel::poll_ready_to_send()`] + pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_send(cx) + } } /// Receive-only access to a [`Channel`]. @@ -133,16 +147,30 @@ where { /// Receive the next value. /// - /// See [`Channel::recv()`]. - pub fn recv(&self) -> RecvFuture<'_, M, T, N> { - self.channel.recv() + /// See [`Channel::receive()`]. + pub fn receive(&self) -> ReceiveFuture<'_, M, T, N> { + self.channel.receive() } /// Attempt to immediately receive the next value. /// - /// See [`Channel::try_recv()`] - pub fn try_recv(&self) -> Result { - self.channel.try_recv() + /// See [`Channel::try_receive()`] + pub fn try_receive(&self) -> Result { + self.channel.try_receive() + } + + /// Allows a poll_fn to poll until the channel is ready to receive + /// + /// See [`Channel::poll_ready_to_receive()`] + pub fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_receive(cx) + } + + /// Poll the channel for the next item + /// + /// See [`Channel::poll_receive()`] + pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { + self.channel.poll_receive(cx) } } @@ -162,16 +190,30 @@ impl<'ch, T> Copy for DynamicReceiver<'ch, T> {} impl<'ch, T> DynamicReceiver<'ch, T> { /// Receive the next value. /// - /// See [`Channel::recv()`]. - pub fn recv(&self) -> DynamicRecvFuture<'_, T> { - DynamicRecvFuture { channel: self.channel } + /// See [`Channel::receive()`]. + pub fn receive(&self) -> DynamicReceiveFuture<'_, T> { + DynamicReceiveFuture { channel: self.channel } } /// Attempt to immediately receive the next value. /// - /// See [`Channel::try_recv()`] - pub fn try_recv(&self) -> Result { - self.channel.try_recv_with_context(None) + /// See [`Channel::try_receive()`] + pub fn try_receive(&self) -> Result { + self.channel.try_receive_with_context(None) + } + + /// Allows a poll_fn to poll until the channel is ready to receive + /// + /// See [`Channel::poll_ready_to_receive()`] + pub fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_receive(cx) + } + + /// Poll the channel for the next item + /// + /// See [`Channel::poll_receive()`] + pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { + self.channel.poll_receive(cx) } } @@ -184,42 +226,39 @@ where } } -/// Future returned by [`Channel::recv`] and [`Receiver::recv`]. +/// Future returned by [`Channel::receive`] and [`Receiver::receive`]. #[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct RecvFuture<'ch, M, T, const N: usize> +pub struct ReceiveFuture<'ch, M, T, const N: usize> where M: RawMutex, { channel: &'ch Channel, } -impl<'ch, M, T, const N: usize> Future for RecvFuture<'ch, M, T, N> +impl<'ch, M, T, const N: usize> Future for ReceiveFuture<'ch, M, T, N> where M: RawMutex, { type Output = T; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.channel.try_recv_with_context(Some(cx)) { - Ok(v) => Poll::Ready(v), - Err(TryRecvError::Empty) => Poll::Pending, - } + self.channel.poll_receive(cx) } } -/// Future returned by [`DynamicReceiver::recv`]. +/// Future returned by [`DynamicReceiver::receive`]. #[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct DynamicRecvFuture<'ch, T> { +pub struct DynamicReceiveFuture<'ch, T> { channel: &'ch dyn DynamicChannel, } -impl<'ch, T> Future for DynamicRecvFuture<'ch, T> { +impl<'ch, T> Future for DynamicReceiveFuture<'ch, T> { type Output = T; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.channel.try_recv_with_context(Some(cx)) { + match self.channel.try_receive_with_context(Some(cx)) { Ok(v) => Poll::Ready(v), - Err(TryRecvError::Empty) => Poll::Pending, + Err(TryReceiveError::Empty) => Poll::Pending, } } } @@ -285,13 +324,18 @@ impl<'ch, T> Unpin for DynamicSendFuture<'ch, T> {} trait DynamicChannel { fn try_send_with_context(&self, message: T, cx: Option<&mut Context<'_>>) -> Result<(), TrySendError>; - fn try_recv_with_context(&self, cx: Option<&mut Context<'_>>) -> Result; + fn try_receive_with_context(&self, cx: Option<&mut Context<'_>>) -> Result; + + fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()>; + fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()>; + + fn poll_receive(&self, cx: &mut Context<'_>) -> Poll; } -/// Error returned by [`try_recv`](Channel::try_recv). +/// Error returned by [`try_receive`](Channel::try_receive). #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum TryRecvError { +pub enum TryReceiveError { /// A message could not be received because the channel is empty. Empty, } @@ -320,11 +364,11 @@ impl ChannelState { } } - fn try_recv(&mut self) -> Result { - self.try_recv_with_context(None) + fn try_receive(&mut self) -> Result { + self.try_receive_with_context(None) } - fn try_recv_with_context(&mut self, cx: Option<&mut Context<'_>>) -> Result { + fn try_receive_with_context(&mut self, cx: Option<&mut Context<'_>>) -> Result { if self.queue.is_full() { self.senders_waker.wake(); } @@ -335,14 +379,31 @@ impl ChannelState { if let Some(cx) = cx { self.receiver_waker.register(cx.waker()); } - Err(TryRecvError::Empty) + Err(TryReceiveError::Empty) } } - fn poll_ready_to_receive(&mut self, cx: &mut Context<'_>) -> bool { + fn poll_receive(&mut self, cx: &mut Context<'_>) -> Poll { + if self.queue.is_full() { + self.senders_waker.wake(); + } + + if let Some(message) = self.queue.pop_front() { + Poll::Ready(message) + } else { + self.receiver_waker.register(cx.waker()); + Poll::Pending + } + } + + fn poll_ready_to_receive(&mut self, cx: &mut Context<'_>) -> Poll<()> { self.receiver_waker.register(cx.waker()); - !self.queue.is_empty() + if !self.queue.is_empty() { + Poll::Ready(()) + } else { + Poll::Pending + } } fn try_send(&mut self, message: T) -> Result<(), TrySendError> { @@ -364,10 +425,14 @@ impl ChannelState { } } - fn poll_ready_to_send(&mut self, cx: &mut Context<'_>) -> bool { + fn poll_ready_to_send(&mut self, cx: &mut Context<'_>) -> Poll<()> { self.senders_waker.register(cx.waker()); - !self.queue.is_full() + if !self.queue.is_full() { + Poll::Ready(()) + } else { + Poll::Pending + } } } @@ -409,8 +474,13 @@ where self.inner.lock(|rc| f(&mut *rc.borrow_mut())) } - fn try_recv_with_context(&self, cx: Option<&mut Context<'_>>) -> Result { - self.lock(|c| c.try_recv_with_context(cx)) + fn try_receive_with_context(&self, cx: Option<&mut Context<'_>>) -> Result { + self.lock(|c| c.try_receive_with_context(cx)) + } + + /// Poll the channel for the next message + pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { + self.lock(|c| c.poll_receive(cx)) } fn try_send_with_context(&self, m: T, cx: Option<&mut Context<'_>>) -> Result<(), TrySendError> { @@ -418,12 +488,12 @@ where } /// Allows a poll_fn to poll until the channel is ready to receive - pub fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> bool { + pub fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()> { self.lock(|c| c.poll_ready_to_receive(cx)) } /// Allows a poll_fn to poll until the channel is ready to send - pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> bool { + pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { self.lock(|c| c.poll_ready_to_send(cx)) } @@ -466,16 +536,16 @@ where /// /// If there are no messages in the channel's buffer, this method will /// wait until a message is sent. - pub fn recv(&self) -> RecvFuture<'_, M, T, N> { - RecvFuture { channel: self } + pub fn receive(&self) -> ReceiveFuture<'_, M, T, N> { + ReceiveFuture { channel: self } } /// Attempt to immediately receive a message. /// /// This method will either receive a message from the channel immediately or return an error /// if the channel is empty. - pub fn try_recv(&self) -> Result { - self.lock(|c| c.try_recv()) + pub fn try_receive(&self) -> Result { + self.lock(|c| c.try_receive()) } } @@ -489,8 +559,20 @@ where Channel::try_send_with_context(self, m, cx) } - fn try_recv_with_context(&self, cx: Option<&mut Context<'_>>) -> Result { - Channel::try_recv_with_context(self, cx) + fn try_receive_with_context(&self, cx: Option<&mut Context<'_>>) -> Result { + Channel::try_receive_with_context(self, cx) + } + + fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { + Channel::poll_ready_to_send(self, cx) + } + + fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()> { + Channel::poll_ready_to_receive(self, cx) + } + + fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { + Channel::poll_receive(self, cx) } } @@ -534,15 +616,15 @@ mod tests { fn receiving_once_with_one_send() { let mut c = ChannelState::::new(); assert!(c.try_send(1).is_ok()); - assert_eq!(c.try_recv().unwrap(), 1); + assert_eq!(c.try_receive().unwrap(), 1); assert_eq!(capacity(&c), 3); } #[test] fn receiving_when_empty() { let mut c = ChannelState::::new(); - match c.try_recv() { - Err(TryRecvError::Empty) => assert!(true), + match c.try_receive() { + Err(TryReceiveError::Empty) => assert!(true), _ => assert!(false), } assert_eq!(capacity(&c), 3); @@ -552,7 +634,7 @@ mod tests { fn simple_send_and_receive() { let c = Channel::::new(); assert!(c.try_send(1).is_ok()); - assert_eq!(c.try_recv().unwrap(), 1); + assert_eq!(c.try_receive().unwrap(), 1); } #[test] @@ -572,7 +654,7 @@ mod tests { let r: DynamicReceiver<'_, u32> = c.receiver().into(); assert!(s.try_send(1).is_ok()); - assert_eq!(r.try_recv().unwrap(), 1); + assert_eq!(r.try_receive().unwrap(), 1); } #[futures_test::test] @@ -587,14 +669,14 @@ mod tests { assert!(c2.try_send(1).is_ok()); }) .is_ok()); - assert_eq!(c.recv().await, 1); + assert_eq!(c.receive().await, 1); } #[futures_test::test] async fn sender_send_completes_if_capacity() { let c = Channel::::new(); c.send(1).await; - assert_eq!(c.recv().await, 1); + assert_eq!(c.receive().await, 1); } #[futures_test::test] @@ -612,11 +694,11 @@ mod tests { // Wish I could think of a means of determining that the async send is waiting instead. // However, I've used the debugger to observe that the send does indeed wait. Delay::new(Duration::from_millis(500)).await; - assert_eq!(c.recv().await, 1); + assert_eq!(c.receive().await, 1); assert!(executor .spawn(async move { loop { - c.recv().await; + c.receive().await; } }) .is_ok()); diff --git a/embassy-time/Cargo.toml b/embassy-time/Cargo.toml index 0afb1103..ec1f2ec3 100644 --- a/embassy-time/Cargo.toml +++ b/embassy-time/Cargo.toml @@ -152,8 +152,8 @@ defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6" } -embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11", optional = true} -embedded-hal-async = { version = "=0.2.0-alpha.2", optional = true} +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1", optional = true} +embedded-hal-async = { version = "=1.0.0-rc.1", optional = true} futures-util = { version = "0.3.17", default-features = false } atomic-polyfill = "1.0.1" diff --git a/embassy-usb/src/class/cdc_ncm/mod.rs b/embassy-usb/src/class/cdc_ncm/mod.rs index fcfa0bfc..830e9b76 100644 --- a/embassy-usb/src/class/cdc_ncm/mod.rs +++ b/embassy-usb/src/class/cdc_ncm/mod.rs @@ -248,6 +248,8 @@ pub struct CdcNcmClass<'d, D: Driver<'d>> { write_ep: D::EndpointIn, _control: &'d ControlShared, + + max_packet_size: usize, } impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { @@ -338,6 +340,7 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { read_ep, write_ep, _control: &state.shared, + max_packet_size: max_packet_size as usize, } } @@ -349,6 +352,7 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { Sender { write_ep: self.write_ep, seq: 0, + max_packet_size: self.max_packet_size, }, Receiver { data_if: self.data_if, @@ -365,6 +369,7 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { pub struct Sender<'d, D: Driver<'d>> { write_ep: D::EndpointIn, seq: u16, + max_packet_size: usize, } impl<'d, D: Driver<'d>> Sender<'d, D> { @@ -375,8 +380,8 @@ impl<'d, D: Driver<'d>> Sender<'d, D> { let seq = self.seq; self.seq = self.seq.wrapping_add(1); - const MAX_PACKET_SIZE: usize = 64; // TODO unhardcode const OUT_HEADER_LEN: usize = 28; + const ABS_MAX_PACKET_SIZE: usize = 512; let header = NtbOutHeader { nth_sig: SIG_NTH, @@ -395,27 +400,27 @@ impl<'d, D: Driver<'d>> Sender<'d, D> { }; // Build first packet on a buffer, send next packets straight from `data`. - let mut buf = [0; MAX_PACKET_SIZE]; + let mut buf = [0; ABS_MAX_PACKET_SIZE]; let n = byteify(&mut buf, header); assert_eq!(n.len(), OUT_HEADER_LEN); - if OUT_HEADER_LEN + data.len() < MAX_PACKET_SIZE { + if OUT_HEADER_LEN + data.len() < self.max_packet_size { // First packet is not full, just send it. // No need to send ZLP because it's short for sure. buf[OUT_HEADER_LEN..][..data.len()].copy_from_slice(data); self.write_ep.write(&buf[..OUT_HEADER_LEN + data.len()]).await?; } else { - let (d1, d2) = data.split_at(MAX_PACKET_SIZE - OUT_HEADER_LEN); + let (d1, d2) = data.split_at(self.max_packet_size - OUT_HEADER_LEN); - buf[OUT_HEADER_LEN..].copy_from_slice(d1); - self.write_ep.write(&buf).await?; + buf[OUT_HEADER_LEN..self.max_packet_size].copy_from_slice(d1); + self.write_ep.write(&buf[..self.max_packet_size]).await?; - for chunk in d2.chunks(MAX_PACKET_SIZE) { + for chunk in d2.chunks(self.max_packet_size) { self.write_ep.write(&chunk).await?; } // Send ZLP if needed. - if d2.len() % MAX_PACKET_SIZE == 0 { + if d2.len() % self.max_packet_size == 0 { self.write_ep.write(&[]).await?; } } diff --git a/embassy-usb/src/msos.rs b/embassy-usb/src/msos.rs index 187b2ff8..847338e5 100644 --- a/embassy-usb/src/msos.rs +++ b/embassy-usb/src/msos.rs @@ -526,7 +526,7 @@ impl<'a> PropertyData<'a> { PropertyData::Binary(val) => val.len(), PropertyData::DwordLittleEndian(val) | PropertyData::DwordBigEndian(val) => core::mem::size_of_val(val), PropertyData::RegMultiSz(val) => { - core::mem::size_of::() * val.iter().map(|x| x.encode_utf16().count() + 1).sum::() + 1 + core::mem::size_of::() * (val.iter().map(|x| x.encode_utf16().count() + 1).sum::() + 1) } } } diff --git a/examples/boot/application/rp/src/bin/a.rs b/examples/boot/application/rp/src/bin/a.rs index f0dda39d..15fdaca8 100644 --- a/examples/boot/application/rp/src/bin/a.rs +++ b/examples/boot/application/rp/src/bin/a.rs @@ -7,7 +7,7 @@ use core::cell::RefCell; use defmt_rtt as _; use embassy_boot_rp::*; use embassy_executor::Spawner; -use embassy_rp::flash::{self, Flash}; +use embassy_rp::flash::Flash; use embassy_rp::gpio::{Level, Output}; use embassy_rp::watchdog::Watchdog; use embassy_sync::blocking_mutex::Mutex; @@ -34,7 +34,7 @@ async fn main(_s: Spawner) { let mut watchdog = Watchdog::new(p.WATCHDOG); watchdog.start(Duration::from_secs(8)); - let flash = Flash::<_, flash::Blocking, FLASH_SIZE>::new(p.FLASH); + let flash = Flash::<_, _, FLASH_SIZE>::new_blocking(p.FLASH); let flash = Mutex::new(RefCell::new(flash)); let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash); diff --git a/examples/boot/bootloader/nrf/src/main.rs b/examples/boot/bootloader/nrf/src/main.rs index 72c95c02..74e2e293 100644 --- a/examples/boot/bootloader/nrf/src/main.rs +++ b/examples/boot/bootloader/nrf/src/main.rs @@ -33,9 +33,7 @@ fn main() -> ! { let config = BootLoaderConfig::from_linkerfile_blocking(&flash); let active_offset = config.active.offset(); - let mut bl: BootLoader<_, _, _> = BootLoader::new(config); - - bl.prepare(); + let bl: BootLoader = BootLoader::prepare(config); unsafe { bl.load(active_offset) } } diff --git a/examples/boot/bootloader/rp/src/main.rs b/examples/boot/bootloader/rp/src/main.rs index 6a81db80..c0e75d1e 100644 --- a/examples/boot/bootloader/rp/src/main.rs +++ b/examples/boot/bootloader/rp/src/main.rs @@ -29,9 +29,7 @@ fn main() -> ! { let config = BootLoaderConfig::from_linkerfile_blocking(&flash); let active_offset = config.active.offset(); - let mut bl: BootLoader<_, _, _> = BootLoader::new(config); - - bl.prepare(); + let bl: BootLoader = BootLoader::prepare(config); unsafe { bl.load(embassy_rp::flash::FLASH_BASE as u32 + active_offset) } } diff --git a/examples/boot/bootloader/stm32/src/main.rs b/examples/boot/bootloader/stm32/src/main.rs index 262eed20..5fd9ea58 100644 --- a/examples/boot/bootloader/stm32/src/main.rs +++ b/examples/boot/bootloader/stm32/src/main.rs @@ -27,9 +27,7 @@ fn main() -> ! { let config = BootLoaderConfig::from_linkerfile_blocking(&flash); let active_offset = config.active.offset(); - let mut bl: BootLoader<_, _, _, 2048> = BootLoader::new(config); - - bl.prepare(); + let bl = BootLoader::prepare::<_, _, _, 2048>(config); unsafe { bl.load(BANK1_REGION.base + active_offset) } } diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml index 15fe22d3..2ce44b51 100644 --- a/examples/nrf52840/Cargo.toml +++ b/examples/nrf52840/Cargo.toml @@ -12,12 +12,14 @@ nightly = [ "embassy-nrf/nightly", "embassy-net/nightly", "embassy-net-esp-hosted", + "embassy-net-enc28j60", "embassy-nrf/unstable-traits", "embassy-time/nightly", "embassy-time/unstable-traits", "static_cell/nightly", "embassy-usb", "embedded-io-async", + "embedded-hal-bus/async", "embassy-net", "embassy-lora", "lora-phy", @@ -40,6 +42,7 @@ lora-phy = { version = "1", optional = true } lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"], optional = true } lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"], optional = true } embassy-net-esp-hosted = { version = "0.1.0", path = "../../embassy-net-esp-hosted", features = ["defmt"], optional = true } +embassy-net-enc28j60 = { version = "0.1.0", path = "../../embassy-net-enc28j60", features = ["defmt"], optional = true } defmt = "0.3" defmt-rtt = "0.4" @@ -54,9 +57,14 @@ rand = { version = "0.8.4", default-features = false } embedded-storage = "0.3.0" usbd-hid = "0.6.0" serde = { version = "1.0.136", default-features = false } -embedded-hal-async = { version = "0.2.0-alpha.2", optional = true } +embedded-hal = { version = "1.0.0-rc.1" } +embedded-hal-async = { version = "1.0.0-rc.1", optional = true } +embedded-hal-bus = { version = "0.1.0-rc.1" } num-integer = { version = "0.1.45", default-features = false } microfft = "0.5.0" [profile.release] debug = 2 + +[patch.crates-io] +lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "1323eccc1c470d4259f95f4f315d1be830d572a3"} \ No newline at end of file diff --git a/examples/nrf52840/src/bin/channel.rs b/examples/nrf52840/src/bin/channel.rs index d782a79e..bd9c909d 100644 --- a/examples/nrf52840/src/bin/channel.rs +++ b/examples/nrf52840/src/bin/channel.rs @@ -35,7 +35,7 @@ async fn main(spawner: Spawner) { unwrap!(spawner.spawn(my_task())); loop { - match CHANNEL.recv().await { + match CHANNEL.receive().await { LedState::On => led.set_high(), LedState::Off => led.set_low(), } diff --git a/examples/nrf52840/src/bin/channel_sender_receiver.rs b/examples/nrf52840/src/bin/channel_sender_receiver.rs index fcccdaed..ec4f1d80 100644 --- a/examples/nrf52840/src/bin/channel_sender_receiver.rs +++ b/examples/nrf52840/src/bin/channel_sender_receiver.rs @@ -33,7 +33,7 @@ async fn recv_task(led: AnyPin, receiver: Receiver<'static, NoopRawMutex, LedSta let mut led = Output::new(led, Level::Low, OutputDrive::Standard); loop { - match receiver.recv().await { + match receiver.receive().await { LedState::On => led.set_high(), LedState::Off => led.set_low(), } diff --git a/examples/nrf52840/src/bin/ethernet_enc28j60.rs b/examples/nrf52840/src/bin/ethernet_enc28j60.rs new file mode 100644 index 00000000..d1b796fa --- /dev/null +++ b/examples/nrf52840/src/bin/ethernet_enc28j60.rs @@ -0,0 +1,124 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Stack, StackResources}; +use embassy_net_enc28j60::Enc28j60; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_nrf::rng::Rng; +use embassy_nrf::spim::Spim; +use embassy_nrf::{bind_interrupts, peripherals, spim}; +use embassy_time::Delay; +use embedded_hal_bus::spi::ExclusiveDevice; +use embedded_io_async::Write; +use static_cell::make_static; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + SPIM3 => spim::InterruptHandler; + RNG => embassy_nrf::rng::InterruptHandler; +}); + +#[embassy_executor::task] +async fn net_task( + stack: &'static Stack< + Enc28j60< + ExclusiveDevice, Output<'static, peripherals::P0_15>, Delay>, + Output<'static, peripherals::P0_13>, + >, + >, +) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + info!("running!"); + + let eth_sck = p.P0_20; + let eth_mosi = p.P0_22; + let eth_miso = p.P0_24; + let eth_cs = p.P0_15; + let eth_rst = p.P0_13; + let _eth_irq = p.P0_12; + + let mut config = spim::Config::default(); + config.frequency = spim::Frequency::M16; + let spi = spim::Spim::new(p.SPI3, Irqs, eth_sck, eth_miso, eth_mosi, config); + let cs = Output::new(eth_cs, Level::High, OutputDrive::Standard); + let spi = ExclusiveDevice::new(spi, cs, Delay); + + let rst = Output::new(eth_rst, Level::High, OutputDrive::Standard); + let mac_addr = [2, 3, 4, 5, 6, 7]; + let device = Enc28j60::new(spi, Some(rst), mac_addr); + + let config = embassy_net::Config::dhcpv4(Default::default()); + // let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + // }); + + // Generate random seed + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + rng.blocking_fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + let stack = &*make_static!(Stack::new( + device, + config, + make_static!(StackResources::<2>::new()), + seed + )); + + unwrap!(spawner.spawn(net_task(stack))); + + // And now we can use it! + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); + + info!("Listening on TCP:1234..."); + if let Err(e) = socket.accept(1234).await { + warn!("accept error: {:?}", e); + continue; + } + + info!("Received connection from {:?}", socket.remote_endpoint()); + + loop { + let n = match socket.read(&mut buf).await { + Ok(0) => { + warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + warn!("read error: {:?}", e); + break; + } + }; + + info!("rxd {:02x}", &buf[..n]); + + match socket.write_all(&buf[..n]).await { + Ok(()) => {} + Err(e) => { + warn!("write error: {:?}", e); + break; + } + }; + } + } +} diff --git a/examples/nrf52840/src/bin/uart_split.rs b/examples/nrf52840/src/bin/uart_split.rs index 9979a1d5..b748bfcd 100644 --- a/examples/nrf52840/src/bin/uart_split.rs +++ b/examples/nrf52840/src/bin/uart_split.rs @@ -46,7 +46,7 @@ async fn main(spawner: Spawner) { // back out the buffer we receive from the read // task. loop { - let buf = CHANNEL.recv().await; + let buf = CHANNEL.receive().await; info!("writing..."); unwrap!(tx.write(&buf).await); } diff --git a/examples/nrf52840/src/bin/wifi_esp_hosted.rs b/examples/nrf52840/src/bin/wifi_esp_hosted.rs index e3b80d82..a60822fd 100644 --- a/examples/nrf52840/src/bin/wifi_esp_hosted.rs +++ b/examples/nrf52840/src/bin/wifi_esp_hosted.rs @@ -11,7 +11,7 @@ use embassy_nrf::rng::Rng; use embassy_nrf::spim::{self, Spim}; use embassy_nrf::{bind_interrupts, peripherals}; use embassy_time::Delay; -use embedded_hal_async::spi::ExclusiveDevice; +use embedded_hal_bus::spi::ExclusiveDevice; use embedded_io_async::Write; use static_cell::make_static; use {defmt_rtt as _, embassy_net_esp_hosted as hosted, panic_probe as _}; @@ -72,8 +72,8 @@ async fn main(spawner: Spawner) { unwrap!(spawner.spawn(wifi_task(runner))); - control.init().await; - control.join(WIFI_NETWORK, WIFI_PASSWORD).await; + unwrap!(control.init().await); + unwrap!(control.connect(WIFI_NETWORK, WIFI_PASSWORD).await); let config = embassy_net::Config::dhcpv4(Default::default()); // let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index d83e370d..102611bc 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -13,7 +13,7 @@ embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["ni embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver", "critical-section-impl"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "udp", "dhcpv4", "medium-ethernet"] } -embassy-net-w5500 = { version = "0.1.0", path = "../../embassy-net-w5500", features = ["defmt"] } +embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-usb-logger = { version = "0.1.0", path = "../../embassy-usb-logger" } embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["time", "defmt"] } @@ -42,8 +42,9 @@ smart-leds = "0.3.0" heapless = "0.7.15" usbd-hid = "0.6.1" -embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" } -embedded-hal-async = "0.2.0-alpha.2" +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1" } +embedded-hal-async = "1.0.0-rc.1" +embedded-hal-bus = { version = "0.1.0-rc.1", features = ["async"] } embedded-io-async = { version = "0.5.0", features = ["defmt-03"] } embedded-storage = { version = "0.3" } static_cell = { version = "1.1", features = ["nightly"]} @@ -53,4 +54,7 @@ pio = "0.2.1" rand = { version = "0.8.5", default-features = false } [profile.release] -debug = 2 \ No newline at end of file +debug = 2 + +[patch.crates-io] +lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "1323eccc1c470d4259f95f4f315d1be830d572a3"} \ No newline at end of file diff --git a/examples/rp/src/bin/ethernet_w5500_multisocket.rs b/examples/rp/src/bin/ethernet_w5500_multisocket.rs index 9f800d0d..c0fde62a 100644 --- a/examples/rp/src/bin/ethernet_w5500_multisocket.rs +++ b/examples/rp/src/bin/ethernet_w5500_multisocket.rs @@ -10,13 +10,14 @@ use defmt::*; use embassy_executor::Spawner; use embassy_futures::yield_now; use embassy_net::{Stack, StackResources}; -use embassy_net_w5500::*; +use embassy_net_wiznet::chip::W5500; +use embassy_net_wiznet::*; use embassy_rp::clocks::RoscRng; use embassy_rp::gpio::{Input, Level, Output, Pull}; use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0}; use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; use embassy_time::{Delay, Duration}; -use embedded_hal_async::spi::ExclusiveDevice; +use embedded_hal_bus::spi::ExclusiveDevice; use embedded_io_async::Write; use rand::RngCore; use static_cell::make_static; @@ -26,6 +27,7 @@ use {defmt_rtt as _, panic_probe as _}; async fn ethernet_task( runner: Runner< 'static, + W5500, ExclusiveDevice, Output<'static, PIN_17>, Delay>, Input<'static, PIN_21>, Output<'static, PIN_20>, @@ -54,7 +56,7 @@ async fn main(spawner: Spawner) { let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; let state = make_static!(State::<8, 8>::new()); - let (device, runner) = embassy_net_w5500::new( + let (device, runner) = embassy_net_wiznet::new( mac_addr, state, ExclusiveDevice::new(spi, cs, Delay), diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_client.rs b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs index fee84b61..e593acae 100644 --- a/examples/rp/src/bin/ethernet_w5500_tcp_client.rs +++ b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs @@ -12,13 +12,14 @@ use defmt::*; use embassy_executor::Spawner; use embassy_futures::yield_now; use embassy_net::{Stack, StackResources}; -use embassy_net_w5500::*; +use embassy_net_wiznet::chip::W5500; +use embassy_net_wiznet::*; use embassy_rp::clocks::RoscRng; use embassy_rp::gpio::{Input, Level, Output, Pull}; use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0}; use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; use embassy_time::{Delay, Duration, Timer}; -use embedded_hal_async::spi::ExclusiveDevice; +use embedded_hal_bus::spi::ExclusiveDevice; use embedded_io_async::Write; use rand::RngCore; use static_cell::make_static; @@ -28,6 +29,7 @@ use {defmt_rtt as _, panic_probe as _}; async fn ethernet_task( runner: Runner< 'static, + W5500, ExclusiveDevice, Output<'static, PIN_17>, Delay>, Input<'static, PIN_21>, Output<'static, PIN_20>, @@ -57,7 +59,7 @@ async fn main(spawner: Spawner) { let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; let state = make_static!(State::<8, 8>::new()); - let (device, runner) = embassy_net_w5500::new( + let (device, runner) = embassy_net_wiznet::new( mac_addr, state, ExclusiveDevice::new(spi, cs, Delay), diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_server.rs b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs index 02457426..c62caed7 100644 --- a/examples/rp/src/bin/ethernet_w5500_tcp_server.rs +++ b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs @@ -11,21 +11,24 @@ use defmt::*; use embassy_executor::Spawner; use embassy_futures::yield_now; use embassy_net::{Stack, StackResources}; -use embassy_net_w5500::*; +use embassy_net_wiznet::chip::W5500; +use embassy_net_wiznet::*; use embassy_rp::clocks::RoscRng; use embassy_rp::gpio::{Input, Level, Output, Pull}; use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0}; use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; use embassy_time::{Delay, Duration}; -use embedded_hal_async::spi::ExclusiveDevice; +use embedded_hal_bus::spi::ExclusiveDevice; use embedded_io_async::Write; use rand::RngCore; use static_cell::make_static; use {defmt_rtt as _, panic_probe as _}; + #[embassy_executor::task] async fn ethernet_task( runner: Runner< 'static, + W5500, ExclusiveDevice, Output<'static, PIN_17>, Delay>, Input<'static, PIN_21>, Output<'static, PIN_20>, @@ -55,7 +58,7 @@ async fn main(spawner: Spawner) { let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; let state = make_static!(State::<8, 8>::new()); - let (device, runner) = embassy_net_w5500::new( + let (device, runner) = embassy_net_wiznet::new( mac_addr, state, ExclusiveDevice::new(spi, cs, Delay), diff --git a/examples/rp/src/bin/ethernet_w5500_udp.rs b/examples/rp/src/bin/ethernet_w5500_udp.rs index 038432b1..76dabce1 100644 --- a/examples/rp/src/bin/ethernet_w5500_udp.rs +++ b/examples/rp/src/bin/ethernet_w5500_udp.rs @@ -11,20 +11,23 @@ use embassy_executor::Spawner; use embassy_futures::yield_now; use embassy_net::udp::{PacketMetadata, UdpSocket}; use embassy_net::{Stack, StackResources}; -use embassy_net_w5500::*; +use embassy_net_wiznet::chip::W5500; +use embassy_net_wiznet::*; use embassy_rp::clocks::RoscRng; use embassy_rp::gpio::{Input, Level, Output, Pull}; use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0}; use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; use embassy_time::Delay; -use embedded_hal_async::spi::ExclusiveDevice; +use embedded_hal_bus::spi::ExclusiveDevice; use rand::RngCore; use static_cell::make_static; use {defmt_rtt as _, panic_probe as _}; + #[embassy_executor::task] async fn ethernet_task( runner: Runner< 'static, + W5500, ExclusiveDevice, Output<'static, PIN_17>, Delay>, Input<'static, PIN_21>, Output<'static, PIN_20>, @@ -53,7 +56,7 @@ async fn main(spawner: Spawner) { let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; let state = make_static!(State::<8, 8>::new()); - let (device, runner) = embassy_net_w5500::new( + let (device, runner) = embassy_net_wiznet::new( mac_addr, state, ExclusiveDevice::new(spi, cs, Delay), diff --git a/examples/rp/src/bin/flash.rs b/examples/rp/src/bin/flash.rs index 88bb931d..911a657e 100644 --- a/examples/rp/src/bin/flash.rs +++ b/examples/rp/src/bin/flash.rs @@ -28,12 +28,12 @@ async fn main(_spawner: Spawner) { let mut flash = embassy_rp::flash::Flash::<_, Async, FLASH_SIZE>::new(p.FLASH, p.DMA_CH0); // Get JEDEC id - let jedec = flash.jedec_id().unwrap(); + let jedec = flash.blocking_jedec_id().unwrap(); info!("jedec id: 0x{:x}", jedec); // Get unique id let mut uid = [0; 8]; - flash.unique_id(&mut uid).unwrap(); + flash.blocking_unique_id(&mut uid).unwrap(); info!("unique id: {:?}", uid); erase_write_sector(&mut flash, 0x00); @@ -48,25 +48,25 @@ async fn main(_spawner: Spawner) { fn multiwrite_bytes(flash: &mut embassy_rp::flash::Flash<'_, FLASH, Async, FLASH_SIZE>, offset: u32) { info!(">>>> [multiwrite_bytes]"); let mut read_buf = [0u8; ERASE_SIZE]; - defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut read_buf)); + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut read_buf)); info!("Addr of flash block is {:x}", ADDR_OFFSET + offset + FLASH_BASE as u32); info!("Contents start with {=[u8]}", read_buf[0..4]); - defmt::unwrap!(flash.erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); + defmt::unwrap!(flash.blocking_erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); - defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut read_buf)); + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut read_buf)); info!("Contents after erase starts with {=[u8]}", read_buf[0..4]); if read_buf.iter().any(|x| *x != 0xFF) { defmt::panic!("unexpected"); } - defmt::unwrap!(flash.write(ADDR_OFFSET + offset, &[0x01])); - defmt::unwrap!(flash.write(ADDR_OFFSET + offset + 1, &[0x02])); - defmt::unwrap!(flash.write(ADDR_OFFSET + offset + 2, &[0x03])); - defmt::unwrap!(flash.write(ADDR_OFFSET + offset + 3, &[0x04])); + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset, &[0x01])); + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset + 1, &[0x02])); + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset + 2, &[0x03])); + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset + 3, &[0x04])); - defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut read_buf)); + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut read_buf)); info!("Contents after write starts with {=[u8]}", read_buf[0..4]); if &read_buf[0..4] != &[0x01, 0x02, 0x03, 0x04] { defmt::panic!("unexpected"); @@ -76,14 +76,14 @@ fn multiwrite_bytes(flash: &mut embassy_rp::flash::Flash<'_, FLASH, Async, FLASH fn erase_write_sector(flash: &mut embassy_rp::flash::Flash<'_, FLASH, Async, FLASH_SIZE>, offset: u32) { info!(">>>> [erase_write_sector]"); let mut buf = [0u8; ERASE_SIZE]; - defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut buf)); + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut buf)); info!("Addr of flash block is {:x}", ADDR_OFFSET + offset + FLASH_BASE as u32); info!("Contents start with {=[u8]}", buf[0..4]); - defmt::unwrap!(flash.erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); + defmt::unwrap!(flash.blocking_erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); - defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut buf)); + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut buf)); info!("Contents after erase starts with {=[u8]}", buf[0..4]); if buf.iter().any(|x| *x != 0xFF) { defmt::panic!("unexpected"); @@ -93,9 +93,9 @@ fn erase_write_sector(flash: &mut embassy_rp::flash::Flash<'_, FLASH, Async, FLA *b = 0xDA; } - defmt::unwrap!(flash.write(ADDR_OFFSET + offset, &buf)); + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset, &buf)); - defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut buf)); + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut buf)); info!("Contents after write starts with {=[u8]}", buf[0..4]); if buf.iter().any(|x| *x != 0xDA) { defmt::panic!("unexpected"); @@ -111,7 +111,7 @@ async fn background_read(flash: &mut embassy_rp::flash::Flash<'_, FLASH, Async, info!("Addr of flash block is {:x}", ADDR_OFFSET + offset + FLASH_BASE as u32); info!("Contents start with {=u32:x}", buf[0]); - defmt::unwrap!(flash.erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); + defmt::unwrap!(flash.blocking_erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); defmt::unwrap!(flash.background_read(ADDR_OFFSET + offset, &mut buf)).await; info!("Contents after erase starts with {=u32:x}", buf[0]); @@ -123,7 +123,7 @@ async fn background_read(flash: &mut embassy_rp::flash::Flash<'_, FLASH, Async, *b = 0xDABA1234; } - defmt::unwrap!(flash.write(ADDR_OFFSET + offset, unsafe { + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset, unsafe { core::slice::from_raw_parts(buf.as_ptr() as *const u8, buf.len() * 4) })); diff --git a/examples/rp/src/bin/lora_p2p_send_multicore.rs b/examples/rp/src/bin/lora_p2p_send_multicore.rs index 89a62818..b54cc92f 100644 --- a/examples/rp/src/bin/lora_p2p_send_multicore.rs +++ b/examples/rp/src/bin/lora_p2p_send_multicore.rs @@ -113,7 +113,7 @@ async fn core1_task( }; loop { - let buffer: [u8; 3] = CHANNEL.recv().await; + let buffer: [u8; 3] = CHANNEL.receive().await; match lora.prepare_for_tx(&mdltn_params, 20, false).await { Ok(()) => {} Err(err) => { diff --git a/examples/rp/src/bin/multicore.rs b/examples/rp/src/bin/multicore.rs index 893b724b..bf017f6a 100644 --- a/examples/rp/src/bin/multicore.rs +++ b/examples/rp/src/bin/multicore.rs @@ -56,7 +56,7 @@ async fn core0_task() { async fn core1_task(mut led: Output<'static, PIN_25>) { info!("Hello from core 1"); loop { - match CHANNEL.recv().await { + match CHANNEL.receive().await { LedState::On => led.set_high(), LedState::Off => led.set_low(), } diff --git a/examples/stm32f3/src/bin/button_events.rs b/examples/stm32f3/src/bin/button_events.rs index 02c475f6..8e97e85e 100644 --- a/examples/stm32f3/src/bin/button_events.rs +++ b/examples/stm32f3/src/bin/button_events.rs @@ -49,12 +49,12 @@ impl<'a> Leds<'a> { async fn show(&mut self) { self.leds[self.current_led].set_high(); - if let Ok(new_message) = with_timeout(Duration::from_millis(500), CHANNEL.recv()).await { + if let Ok(new_message) = with_timeout(Duration::from_millis(500), CHANNEL.receive()).await { self.leds[self.current_led].set_low(); self.process_event(new_message).await; } else { self.leds[self.current_led].set_low(); - if let Ok(new_message) = with_timeout(Duration::from_millis(200), CHANNEL.recv()).await { + if let Ok(new_message) = with_timeout(Duration::from_millis(200), CHANNEL.receive()).await { self.process_event(new_message).await; } } diff --git a/examples/stm32h5/Cargo.toml b/examples/stm32h5/Cargo.toml index 44d0a957..5d73e435 100644 --- a/examples/stm32h5/Cargo.toml +++ b/examples/stm32h5/Cargo.toml @@ -20,8 +20,8 @@ defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" } -embedded-hal-async = { version = "=0.2.0-alpha.2" } +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1" } +embedded-hal-async = { version = "=1.0.0-rc.1" } embedded-nal-async = { version = "0.5.0" } panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } diff --git a/examples/stm32h5/src/bin/usart_split.rs b/examples/stm32h5/src/bin/usart_split.rs index debd6f45..a6b2e690 100644 --- a/examples/stm32h5/src/bin/usart_split.rs +++ b/examples/stm32h5/src/bin/usart_split.rs @@ -44,7 +44,7 @@ async fn main(spawner: Spawner) -> ! { unwrap!(spawner.spawn(reader(rx))); loop { - let buf = CHANNEL.recv().await; + let buf = CHANNEL.receive().await; info!("writing..."); unwrap!(tx.write(&buf).await); } diff --git a/examples/stm32h7/Cargo.toml b/examples/stm32h7/Cargo.toml index e2e5f936..c78c4c60 100644 --- a/examples/stm32h7/Cargo.toml +++ b/examples/stm32h7/Cargo.toml @@ -20,8 +20,8 @@ defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" } -embedded-hal-async = { version = "=0.2.0-alpha.2" } +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1" } +embedded-hal-async = { version = "=1.0.0-rc.1" } embedded-nal-async = { version = "0.5.0" } panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } diff --git a/examples/stm32h7/src/bin/usart_split.rs b/examples/stm32h7/src/bin/usart_split.rs index 330d1ce0..aa075345 100644 --- a/examples/stm32h7/src/bin/usart_split.rs +++ b/examples/stm32h7/src/bin/usart_split.rs @@ -44,7 +44,7 @@ async fn main(spawner: Spawner) -> ! { unwrap!(spawner.spawn(reader(rx))); loop { - let buf = CHANNEL.recv().await; + let buf = CHANNEL.receive().await; info!("writing..."); unwrap!(tx.write(&buf).await); } diff --git a/examples/stm32l0/Cargo.toml b/examples/stm32l0/Cargo.toml index 33aa05e6..332a6c5e 100644 --- a/examples/stm32l0/Cargo.toml +++ b/examples/stm32l0/Cargo.toml @@ -37,3 +37,6 @@ static_cell = "1.1" [profile.release] debug = 2 + +[patch.crates-io] +lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "1323eccc1c470d4259f95f4f315d1be830d572a3"} \ No newline at end of file diff --git a/examples/stm32l4/Cargo.toml b/examples/stm32l4/Cargo.toml index 3b27d8e8..944c8c27 100644 --- a/examples/stm32l4/Cargo.toml +++ b/examples/stm32l4/Cargo.toml @@ -19,8 +19,8 @@ defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" } -embedded-hal-async = { version = "=0.2.0-alpha.2" } +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1" } +embedded-hal-async = { version = "=1.0.0-rc.1" } panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } heapless = { version = "0.7.5", default-features = false } diff --git a/examples/stm32wl/Cargo.toml b/examples/stm32wl/Cargo.toml index 48b69c8d..5440807f 100644 --- a/examples/stm32wl/Cargo.toml +++ b/examples/stm32wl/Cargo.toml @@ -30,3 +30,6 @@ chrono = { version = "^0.4", default-features = false } [profile.release] debug = 2 + +[patch.crates-io] +lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "1323eccc1c470d4259f95f4f315d1be830d572a3"} \ No newline at end of file diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 179ed1d6..7b34afa2 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,7 +1,7 @@ # Before upgrading check that everything is available on all tier1 targets here: # https://rust-lang.github.io/rustup-components-history [toolchain] -channel = "nightly-2023-06-28" +channel = "nightly-2023-08-19" components = [ "rust-src", "rustfmt", "llvm-tools-preview" ] targets = [ "thumbv7em-none-eabi", diff --git a/tests/nrf/Cargo.toml b/tests/nrf/Cargo.toml index 974a9413..034ed85e 100644 --- a/tests/nrf/Cargo.toml +++ b/tests/nrf/Cargo.toml @@ -15,7 +15,9 @@ embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defm embedded-io-async = { version = "0.5.0" } embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "nightly"] } embassy-net-esp-hosted = { version = "0.1.0", path = "../../embassy-net-esp-hosted", features = ["defmt"] } -embedded-hal-async = { version = "0.2.0-alpha.2" } +embassy-net-enc28j60 = { version = "0.1.0", path = "../../embassy-net-enc28j60", features = ["defmt"] } +embedded-hal-async = { version = "1.0.0-rc.1" } +embedded-hal-bus = { version = "0.1.0-rc.1", features = ["async"] } static_cell = { version = "1.1", features = [ "nightly" ] } defmt = "0.3" diff --git a/tests/nrf/src/bin/buffered_uart.rs b/tests/nrf/src/bin/buffered_uart.rs index 72a4cb4e..932e5926 100644 --- a/tests/nrf/src/bin/buffered_uart.rs +++ b/tests/nrf/src/bin/buffered_uart.rs @@ -1,8 +1,7 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"nrf52840-dk"); use defmt::{assert_eq, *}; use embassy_executor::Spawner; diff --git a/tests/nrf/src/bin/buffered_uart_spam.rs b/tests/nrf/src/bin/buffered_uart_spam.rs index 50960206..8abeae6d 100644 --- a/tests/nrf/src/bin/buffered_uart_spam.rs +++ b/tests/nrf/src/bin/buffered_uart_spam.rs @@ -1,8 +1,7 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"nrf52840-dk"); use core::mem; use core::ptr::NonNull; diff --git a/tests/nrf/src/bin/ethernet_enc28j60_perf.rs b/tests/nrf/src/bin/ethernet_enc28j60_perf.rs new file mode 100644 index 00000000..0446d39a --- /dev/null +++ b/tests/nrf/src/bin/ethernet_enc28j60_perf.rs @@ -0,0 +1,250 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +teleprobe_meta::target!(b"ak-gwe-r7"); +teleprobe_meta::timeout!(120); + +use defmt::{error, info, unwrap}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Ipv4Address, Stack, StackResources}; +use embassy_net_enc28j60::Enc28j60; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_nrf::rng::Rng; +use embassy_nrf::spim::{self, Spim}; +use embassy_nrf::{bind_interrupts, peripherals}; +use embassy_time::{with_timeout, Delay, Duration, Timer}; +use embedded_hal_bus::spi::ExclusiveDevice; +use static_cell::make_static; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + SPIM3 => spim::InterruptHandler; + RNG => embassy_nrf::rng::InterruptHandler; +}); + +type MyDriver = Enc28j60< + ExclusiveDevice, Output<'static, peripherals::P0_15>, Delay>, + Output<'static, peripherals::P0_13>, +>; + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + info!("running!"); + + let eth_sck = p.P0_20; + let eth_mosi = p.P0_22; + let eth_miso = p.P0_24; + let eth_cs = p.P0_15; + let eth_rst = p.P0_13; + let _eth_irq = p.P0_12; + + let mut config = spim::Config::default(); + config.frequency = spim::Frequency::M16; + let spi = spim::Spim::new(p.SPI3, Irqs, eth_sck, eth_miso, eth_mosi, config); + let cs = Output::new(eth_cs, Level::High, OutputDrive::Standard); + let spi = ExclusiveDevice::new(spi, cs, Delay); + + let rst = Output::new(eth_rst, Level::High, OutputDrive::Standard); + let mac_addr = [2, 3, 4, 5, 6, 7]; + let device = Enc28j60::new(spi, Some(rst), mac_addr); + + let config = embassy_net::Config::dhcpv4(Default::default()); + // let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + // }); + + // Generate random seed + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + rng.blocking_fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + let stack = &*make_static!(Stack::new( + device, + config, + make_static!(StackResources::<2>::new()), + seed + )); + + unwrap!(spawner.spawn(net_task(stack))); + + info!("Waiting for DHCP up..."); + while stack.config_v4().is_none() { + Timer::after(Duration::from_millis(100)).await; + } + info!("IP addressing up!"); + + let down = test_download(stack).await; + let up = test_upload(stack).await; + let updown = test_upload_download(stack).await; + + assert!(down > TEST_EXPECTED_DOWNLOAD_KBPS); + assert!(up > TEST_EXPECTED_UPLOAD_KBPS); + assert!(updown > TEST_EXPECTED_UPLOAD_DOWNLOAD_KBPS); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +const TEST_DURATION: usize = 10; +const TEST_EXPECTED_DOWNLOAD_KBPS: usize = 200; +const TEST_EXPECTED_UPLOAD_KBPS: usize = 200; +const TEST_EXPECTED_UPLOAD_DOWNLOAD_KBPS: usize = 150; +const RX_BUFFER_SIZE: usize = 4096; +const TX_BUFFER_SIZE: usize = 4096; +const SERVER_ADDRESS: Ipv4Address = Ipv4Address::new(192, 168, 2, 2); +const DOWNLOAD_PORT: u16 = 4321; +const UPLOAD_PORT: u16 = 4322; +const UPLOAD_DOWNLOAD_PORT: u16 = 4323; + +async fn test_download(stack: &'static Stack) -> usize { + info!("Testing download..."); + + let mut rx_buffer = [0; RX_BUFFER_SIZE]; + let mut tx_buffer = [0; TX_BUFFER_SIZE]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("connecting to {:?}:{}...", SERVER_ADDRESS, DOWNLOAD_PORT); + if let Err(e) = socket.connect((SERVER_ADDRESS, DOWNLOAD_PORT)).await { + error!("connect error: {:?}", e); + return 0; + } + info!("connected, testing..."); + + let mut rx_buf = [0; 4096]; + let mut total: usize = 0; + with_timeout(Duration::from_secs(TEST_DURATION as _), async { + loop { + match socket.read(&mut rx_buf).await { + Ok(0) => { + error!("read EOF"); + return 0; + } + Ok(n) => total += n, + Err(e) => { + error!("read error: {:?}", e); + return 0; + } + } + } + }) + .await + .ok(); + + let kbps = (total + 512) / 1024 / TEST_DURATION; + info!("download: {} kB/s", kbps); + kbps +} + +async fn test_upload(stack: &'static Stack) -> usize { + info!("Testing upload..."); + + let mut rx_buffer = [0; RX_BUFFER_SIZE]; + let mut tx_buffer = [0; TX_BUFFER_SIZE]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_PORT); + if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_PORT)).await { + error!("connect error: {:?}", e); + return 0; + } + info!("connected, testing..."); + + let buf = [0; 4096]; + let mut total: usize = 0; + with_timeout(Duration::from_secs(TEST_DURATION as _), async { + loop { + match socket.write(&buf).await { + Ok(0) => { + error!("write zero?!??!?!"); + return 0; + } + Ok(n) => total += n, + Err(e) => { + error!("write error: {:?}", e); + return 0; + } + } + } + }) + .await + .ok(); + + let kbps = (total + 512) / 1024 / TEST_DURATION; + info!("upload: {} kB/s", kbps); + kbps +} + +async fn test_upload_download(stack: &'static Stack) -> usize { + info!("Testing upload+download..."); + + let mut rx_buffer = [0; RX_BUFFER_SIZE]; + let mut tx_buffer = [0; TX_BUFFER_SIZE]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT); + if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT)).await { + error!("connect error: {:?}", e); + return 0; + } + info!("connected, testing..."); + + let (mut reader, mut writer) = socket.split(); + + let tx_buf = [0; 4096]; + let mut rx_buf = [0; 4096]; + let mut total: usize = 0; + let tx_fut = async { + loop { + match writer.write(&tx_buf).await { + Ok(0) => { + error!("write zero?!??!?!"); + return 0; + } + Ok(_) => {} + Err(e) => { + error!("write error: {:?}", e); + return 0; + } + } + } + }; + + let rx_fut = async { + loop { + match reader.read(&mut rx_buf).await { + Ok(0) => { + error!("read EOF"); + return 0; + } + Ok(n) => total += n, + Err(e) => { + error!("read error: {:?}", e); + return 0; + } + } + } + }; + + with_timeout(Duration::from_secs(TEST_DURATION as _), join(tx_fut, rx_fut)) + .await + .ok(); + + let kbps = (total + 512) / 1024 / TEST_DURATION; + info!("upload+download: {} kB/s", kbps); + kbps +} diff --git a/tests/nrf/src/bin/timer.rs b/tests/nrf/src/bin/timer.rs index 607c5bbf..c00f35fd 100644 --- a/tests/nrf/src/bin/timer.rs +++ b/tests/nrf/src/bin/timer.rs @@ -1,8 +1,7 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"nrf52840-dk"); use defmt::{assert, info}; use embassy_executor::Spawner; diff --git a/tests/nrf/src/bin/wifi_esp_hosted_perf.rs b/tests/nrf/src/bin/wifi_esp_hosted_perf.rs index e2adfe0b..97ebafec 100644 --- a/tests/nrf/src/bin/wifi_esp_hosted_perf.rs +++ b/tests/nrf/src/bin/wifi_esp_hosted_perf.rs @@ -1,9 +1,8 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] - -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"nrf52840-dk"); +teleprobe_meta::timeout!(120); use defmt::{error, info, unwrap}; use embassy_executor::Spawner; @@ -15,12 +14,10 @@ use embassy_nrf::rng::Rng; use embassy_nrf::spim::{self, Spim}; use embassy_nrf::{bind_interrupts, peripherals}; use embassy_time::{with_timeout, Delay, Duration, Timer}; -use embedded_hal_async::spi::ExclusiveDevice; +use embedded_hal_bus::spi::ExclusiveDevice; use static_cell::make_static; use {defmt_rtt as _, embassy_net_esp_hosted as hosted, panic_probe as _}; -teleprobe_meta::timeout!(120); - bind_interrupts!(struct Irqs { SPIM3 => spim::InterruptHandler; RNG => embassy_nrf::rng::InterruptHandler; @@ -76,8 +73,8 @@ async fn main(spawner: Spawner) { unwrap!(spawner.spawn(wifi_task(runner))); - control.init().await; - control.join(WIFI_NETWORK, WIFI_PASSWORD).await; + unwrap!(control.init().await); + unwrap!(control.connect(WIFI_NETWORK, WIFI_PASSWORD).await); // Generate random seed let mut rng = Rng::new(p.RNG, Irqs); diff --git a/tests/nrf/src/common.rs b/tests/nrf/src/common.rs deleted file mode 100644 index 1a05ac1c..00000000 --- a/tests/nrf/src/common.rs +++ /dev/null @@ -1 +0,0 @@ -teleprobe_meta::target!(b"nrf52840-dk"); diff --git a/tests/rp/Cargo.toml b/tests/rp/Cargo.toml index 90a3bd0c..6a3df4b9 100644 --- a/tests/rp/Cargo.toml +++ b/tests/rp/Cargo.toml @@ -9,10 +9,11 @@ teleprobe-meta = "1.1" embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "nightly", "unstable-traits"] } embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "time-driver", "critical-section-impl", "intrinsics", "rom-v2-intrinsics", "run-from-ram"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "udp", "dhcpv4", "medium-ethernet"] } +embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] } cyw43 = { path = "../../cyw43", features = ["defmt", "firmware-logs"] } cyw43-pio = { path = "../../cyw43-pio", features = ["defmt", "overclock"] } @@ -22,8 +23,9 @@ defmt-rtt = "0.4" cortex-m = { version = "0.7.6" } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" } -embedded-hal-async = { version = "=0.2.0-alpha.2" } +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1" } +embedded-hal-async = { version = "=1.0.0-rc.1" } +embedded-hal-bus = { version = "=0.1.0-rc.1", features = ["async"] } panic-probe = { version = "0.3.0", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } embedded-io-async = { version = "0.5.0" } @@ -31,6 +33,7 @@ embedded-storage = { version = "0.3" } static_cell = { version = "1.1", features = ["nightly"]} pio = "0.2" pio-proc = "0.2" +rand = { version = "0.8.5", default-features = false } [profile.dev] debug = 2 diff --git a/tests/rp/src/bin/adc.rs b/tests/rp/src/bin/adc.rs index b29a3a7c..0250fd5f 100644 --- a/tests/rp/src/bin/adc.rs +++ b/tests/rp/src/bin/adc.rs @@ -1,8 +1,7 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"rpi-pico"); use defmt::*; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/cyw43-perf.rs b/tests/rp/src/bin/cyw43-perf.rs index fffdabc9..1c665f95 100644 --- a/tests/rp/src/bin/cyw43-perf.rs +++ b/tests/rp/src/bin/cyw43-perf.rs @@ -1,8 +1,7 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"rpi-pico"); use cyw43_pio::PioSpi; use defmt::{assert, panic, *}; diff --git a/tests/rp/src/bin/dma_copy_async.rs b/tests/rp/src/bin/dma_copy_async.rs index 2c0b559a..b071ed9d 100644 --- a/tests/rp/src/bin/dma_copy_async.rs +++ b/tests/rp/src/bin/dma_copy_async.rs @@ -1,8 +1,7 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"rpi-pico"); use defmt::{assert_eq, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/ethernet_w5100s_perf.rs b/tests/rp/src/bin/ethernet_w5100s_perf.rs new file mode 100644 index 00000000..faa8638c --- /dev/null +++ b/tests/rp/src/bin/ethernet_w5100s_perf.rs @@ -0,0 +1,249 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +teleprobe_meta::target!(b"w5100s-evb-pico"); +teleprobe_meta::timeout!(120); + +use defmt::{assert, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Ipv4Address, Stack, StackResources}; +use embassy_net_wiznet::chip::W5100S; +use embassy_net_wiznet::*; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0}; +use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embassy_time::{with_timeout, Delay, Duration, Timer}; +use embedded_hal_bus::spi::ExclusiveDevice; +use rand::RngCore; +use static_cell::make_static; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn ethernet_task( + runner: Runner< + 'static, + W5100S, + ExclusiveDevice, Output<'static, PIN_17>, Delay>, + Input<'static, PIN_21>, + Output<'static, PIN_20>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 50_000_000; + let (miso, mosi, clk) = (p.PIN_16, p.PIN_19, p.PIN_18); + let spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, spi_cfg); + let cs = Output::new(p.PIN_17, Level::High); + let w5500_int = Input::new(p.PIN_21, Pull::Up); + let w5500_reset = Output::new(p.PIN_20, Level::High); + + let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + let state = make_static!(State::<8, 8>::new()); + let (device, runner) = embassy_net_wiznet::new( + mac_addr, + state, + ExclusiveDevice::new(spi, cs, Delay), + w5500_int, + w5500_reset, + ) + .await; + unwrap!(spawner.spawn(ethernet_task(runner))); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + let stack = &*make_static!(Stack::new( + device, + embassy_net::Config::dhcpv4(Default::default()), + make_static!(StackResources::<2>::new()), + seed + )); + + // Launch network task + unwrap!(spawner.spawn(net_task(&stack))); + + info!("Waiting for DHCP up..."); + while stack.config_v4().is_none() { + Timer::after(Duration::from_millis(100)).await; + } + info!("IP addressing up!"); + + let down = test_download(stack).await; + let up = test_upload(stack).await; + let updown = test_upload_download(stack).await; + + assert!(down > TEST_EXPECTED_DOWNLOAD_KBPS); + assert!(up > TEST_EXPECTED_UPLOAD_KBPS); + assert!(updown > TEST_EXPECTED_UPLOAD_DOWNLOAD_KBPS); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +const TEST_DURATION: usize = 10; +const TEST_EXPECTED_DOWNLOAD_KBPS: usize = 500; +const TEST_EXPECTED_UPLOAD_KBPS: usize = 500; +const TEST_EXPECTED_UPLOAD_DOWNLOAD_KBPS: usize = 300; +const RX_BUFFER_SIZE: usize = 4096; +const TX_BUFFER_SIZE: usize = 4096; +const SERVER_ADDRESS: Ipv4Address = Ipv4Address::new(192, 168, 2, 2); +const DOWNLOAD_PORT: u16 = 4321; +const UPLOAD_PORT: u16 = 4322; +const UPLOAD_DOWNLOAD_PORT: u16 = 4323; + +async fn test_download(stack: &'static Stack>) -> usize { + info!("Testing download..."); + + let mut rx_buffer = [0; RX_BUFFER_SIZE]; + let mut tx_buffer = [0; TX_BUFFER_SIZE]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("connecting to {:?}:{}...", SERVER_ADDRESS, DOWNLOAD_PORT); + if let Err(e) = socket.connect((SERVER_ADDRESS, DOWNLOAD_PORT)).await { + error!("connect error: {:?}", e); + return 0; + } + info!("connected, testing..."); + + let mut rx_buf = [0; 4096]; + let mut total: usize = 0; + with_timeout(Duration::from_secs(TEST_DURATION as _), async { + loop { + match socket.read(&mut rx_buf).await { + Ok(0) => { + error!("read EOF"); + return 0; + } + Ok(n) => total += n, + Err(e) => { + error!("read error: {:?}", e); + return 0; + } + } + } + }) + .await + .ok(); + + let kbps = (total + 512) / 1024 / TEST_DURATION; + info!("download: {} kB/s", kbps); + kbps +} + +async fn test_upload(stack: &'static Stack>) -> usize { + info!("Testing upload..."); + + let mut rx_buffer = [0; RX_BUFFER_SIZE]; + let mut tx_buffer = [0; TX_BUFFER_SIZE]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_PORT); + if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_PORT)).await { + error!("connect error: {:?}", e); + return 0; + } + info!("connected, testing..."); + + let buf = [0; 4096]; + let mut total: usize = 0; + with_timeout(Duration::from_secs(TEST_DURATION as _), async { + loop { + match socket.write(&buf).await { + Ok(0) => { + error!("write zero?!??!?!"); + return 0; + } + Ok(n) => total += n, + Err(e) => { + error!("write error: {:?}", e); + return 0; + } + } + } + }) + .await + .ok(); + + let kbps = (total + 512) / 1024 / TEST_DURATION; + info!("upload: {} kB/s", kbps); + kbps +} + +async fn test_upload_download(stack: &'static Stack>) -> usize { + info!("Testing upload+download..."); + + let mut rx_buffer = [0; RX_BUFFER_SIZE]; + let mut tx_buffer = [0; TX_BUFFER_SIZE]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT); + if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT)).await { + error!("connect error: {:?}", e); + return 0; + } + info!("connected, testing..."); + + let (mut reader, mut writer) = socket.split(); + + let tx_buf = [0; 4096]; + let mut rx_buf = [0; 4096]; + let mut total: usize = 0; + let tx_fut = async { + loop { + match writer.write(&tx_buf).await { + Ok(0) => { + error!("write zero?!??!?!"); + return 0; + } + Ok(_) => {} + Err(e) => { + error!("write error: {:?}", e); + return 0; + } + } + } + }; + + let rx_fut = async { + loop { + match reader.read(&mut rx_buf).await { + Ok(0) => { + error!("read EOF"); + return 0; + } + Ok(n) => total += n, + Err(e) => { + error!("read error: {:?}", e); + return 0; + } + } + } + }; + + with_timeout(Duration::from_secs(TEST_DURATION as _), join(tx_fut, rx_fut)) + .await + .ok(); + + let kbps = (total + 512) / 1024 / TEST_DURATION; + info!("upload+download: {} kB/s", kbps); + kbps +} diff --git a/tests/rp/src/bin/flash.rs b/tests/rp/src/bin/flash.rs index c31d6dec..75be2bf0 100644 --- a/tests/rp/src/bin/flash.rs +++ b/tests/rp/src/bin/flash.rs @@ -1,8 +1,7 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"rpi-pico"); use defmt::*; use embassy_executor::Spawner; @@ -26,23 +25,23 @@ async fn main(_spawner: Spawner) { let mut flash = embassy_rp::flash::Flash::<_, Async, { 2 * 1024 * 1024 }>::new(p.FLASH, p.DMA_CH0); // Get JEDEC id - let jedec = defmt::unwrap!(flash.jedec_id()); + let jedec = defmt::unwrap!(flash.blocking_jedec_id()); info!("jedec id: 0x{:x}", jedec); // Get unique id let mut uid = [0; 8]; - defmt::unwrap!(flash.unique_id(&mut uid)); + defmt::unwrap!(flash.blocking_unique_id(&mut uid)); info!("unique id: {:?}", uid); let mut buf = [0u8; ERASE_SIZE]; - defmt::unwrap!(flash.read(ADDR_OFFSET, &mut buf)); + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET, &mut buf)); info!("Addr of flash block is {:x}", ADDR_OFFSET + FLASH_BASE as u32); info!("Contents start with {=[u8]}", buf[0..4]); - defmt::unwrap!(flash.erase(ADDR_OFFSET, ADDR_OFFSET + ERASE_SIZE as u32)); + defmt::unwrap!(flash.blocking_erase(ADDR_OFFSET, ADDR_OFFSET + ERASE_SIZE as u32)); - defmt::unwrap!(flash.read(ADDR_OFFSET, &mut buf)); + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET, &mut buf)); info!("Contents after erase starts with {=[u8]}", buf[0..4]); if buf.iter().any(|x| *x != 0xFF) { defmt::panic!("unexpected"); @@ -52,9 +51,9 @@ async fn main(_spawner: Spawner) { *b = 0xDA; } - defmt::unwrap!(flash.write(ADDR_OFFSET, &mut buf)); + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET, &mut buf)); - defmt::unwrap!(flash.read(ADDR_OFFSET, &mut buf)); + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET, &mut buf)); info!("Contents after write starts with {=[u8]}", buf[0..4]); if buf.iter().any(|x| *x != 0xDA) { defmt::panic!("unexpected"); diff --git a/tests/rp/src/bin/float.rs b/tests/rp/src/bin/float.rs index 0e0de85f..2874aa91 100644 --- a/tests/rp/src/bin/float.rs +++ b/tests/rp/src/bin/float.rs @@ -1,8 +1,7 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"rpi-pico"); use defmt::*; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/gpio.rs b/tests/rp/src/bin/gpio.rs index 946b7dc8..1a43a928 100644 --- a/tests/rp/src/bin/gpio.rs +++ b/tests/rp/src/bin/gpio.rs @@ -1,8 +1,7 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"rpi-pico"); use defmt::{assert, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/gpio_async.rs b/tests/rp/src/bin/gpio_async.rs index 532494de..60c65b7a 100644 --- a/tests/rp/src/bin/gpio_async.rs +++ b/tests/rp/src/bin/gpio_async.rs @@ -1,8 +1,7 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"rpi-pico"); use defmt::{assert, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/gpio_multicore.rs b/tests/rp/src/bin/gpio_multicore.rs index 780112bc..6ab7f671 100644 --- a/tests/rp/src/bin/gpio_multicore.rs +++ b/tests/rp/src/bin/gpio_multicore.rs @@ -1,8 +1,7 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"rpi-pico"); use defmt::{info, unwrap}; use embassy_executor::Executor; @@ -38,11 +37,11 @@ async fn core0_task(p: PIN_0) { let mut pin = Output::new(p, Level::Low); CHANNEL0.send(()).await; - CHANNEL1.recv().await; + CHANNEL1.receive().await; pin.set_high(); - CHANNEL1.recv().await; + CHANNEL1.receive().await; info!("Test OK"); cortex_m::asm::bkpt(); @@ -52,7 +51,7 @@ async fn core0_task(p: PIN_0) { async fn core1_task(p: PIN_1) { info!("CORE1 is running"); - CHANNEL0.recv().await; + CHANNEL0.receive().await; let mut pin = Input::new(p, Pull::Down); let wait = pin.wait_for_rising_edge(); diff --git a/tests/rp/src/bin/multicore.rs b/tests/rp/src/bin/multicore.rs index 114889de..f4188135 100644 --- a/tests/rp/src/bin/multicore.rs +++ b/tests/rp/src/bin/multicore.rs @@ -1,8 +1,7 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"rpi-pico"); use defmt::{info, unwrap}; use embassy_executor::Executor; @@ -34,7 +33,7 @@ async fn core0_task() { info!("CORE0 is running"); let ping = true; CHANNEL0.send(ping).await; - let pong = CHANNEL1.recv().await; + let pong = CHANNEL1.receive().await; assert_eq!(ping, pong); info!("Test OK"); @@ -44,6 +43,6 @@ async fn core0_task() { #[embassy_executor::task] async fn core1_task() { info!("CORE1 is running"); - let ping = CHANNEL0.recv().await; + let ping = CHANNEL0.receive().await; CHANNEL1.send(ping).await; } diff --git a/tests/rp/src/bin/pio_irq.rs b/tests/rp/src/bin/pio_irq.rs index bdea63ea..c71e55d9 100644 --- a/tests/rp/src/bin/pio_irq.rs +++ b/tests/rp/src/bin/pio_irq.rs @@ -1,8 +1,7 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"rpi-pico"); use defmt::info; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/pio_multi_load.rs b/tests/rp/src/bin/pio_multi_load.rs index 356f1679..6343ff3a 100644 --- a/tests/rp/src/bin/pio_multi_load.rs +++ b/tests/rp/src/bin/pio_multi_load.rs @@ -1,8 +1,7 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"rpi-pico"); use defmt::info; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/pwm.rs b/tests/rp/src/bin/pwm.rs index c71d21ef..8c02b844 100644 --- a/tests/rp/src/bin/pwm.rs +++ b/tests/rp/src/bin/pwm.rs @@ -1,8 +1,7 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"rpi-pico"); use defmt::{assert, assert_eq, assert_ne, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/spi.rs b/tests/rp/src/bin/spi.rs index 84dfa5a2..f347b80c 100644 --- a/tests/rp/src/bin/spi.rs +++ b/tests/rp/src/bin/spi.rs @@ -1,8 +1,7 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"rpi-pico"); use defmt::{assert_eq, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/spi_async.rs b/tests/rp/src/bin/spi_async.rs index a4080b03..80d5a714 100644 --- a/tests/rp/src/bin/spi_async.rs +++ b/tests/rp/src/bin/spi_async.rs @@ -4,8 +4,7 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"rpi-pico"); use defmt::{assert_eq, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/uart.rs b/tests/rp/src/bin/uart.rs index 2331c7d3..00f3e194 100644 --- a/tests/rp/src/bin/uart.rs +++ b/tests/rp/src/bin/uart.rs @@ -1,8 +1,7 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"rpi-pico"); use defmt::{assert_eq, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/uart_buffered.rs b/tests/rp/src/bin/uart_buffered.rs index edabf55b..6ab7de29 100644 --- a/tests/rp/src/bin/uart_buffered.rs +++ b/tests/rp/src/bin/uart_buffered.rs @@ -1,8 +1,7 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"rpi-pico"); use defmt::{assert_eq, panic, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/uart_dma.rs b/tests/rp/src/bin/uart_dma.rs index fee6c825..cd4af1ef 100644 --- a/tests/rp/src/bin/uart_dma.rs +++ b/tests/rp/src/bin/uart_dma.rs @@ -1,8 +1,7 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"rpi-pico"); use defmt::{assert_eq, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/uart_upgrade.rs b/tests/rp/src/bin/uart_upgrade.rs index effd0bc4..c07fc08c 100644 --- a/tests/rp/src/bin/uart_upgrade.rs +++ b/tests/rp/src/bin/uart_upgrade.rs @@ -1,8 +1,7 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] -#[path = "../common.rs"] -mod common; +teleprobe_meta::target!(b"rpi-pico"); use defmt::{assert_eq, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/common.rs b/tests/rp/src/common.rs deleted file mode 100644 index 955674f2..00000000 --- a/tests/rp/src/common.rs +++ /dev/null @@ -1 +0,0 @@ -teleprobe_meta::target!(b"rpi-pico"); diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml index 17320649..e2638897 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml @@ -41,8 +41,8 @@ defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" } -embedded-hal-async = { version = "=0.2.0-alpha.2" } +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1" } +embedded-hal-async = { version = "=1.0.0-rc.1" } micromath = "2.0.0" panic-probe = { version = "0.3.0", features = ["print-defmt"] } rand_core = { version = "0.6", default-features = false } diff --git a/tests/stm32/src/bin/can.rs b/tests/stm32/src/bin/can.rs index 8737ca8e..acf54521 100644 --- a/tests/stm32/src/bin/can.rs +++ b/tests/stm32/src/bin/can.rs @@ -80,8 +80,8 @@ async fn main(_spawner: Spawner) { const MIN_LATENCY: Duration = Duration::from_micros(50); const MAX_LATENCY: Duration = Duration::from_micros(150); assert!( - MIN_LATENCY < latency && latency < MAX_LATENCY, - "{} < {} < {}", + MIN_LATENCY <= latency && latency <= MAX_LATENCY, + "{} <= {} <= {}", MIN_LATENCY, latency, MAX_LATENCY