diff --git a/.github/bors.toml b/.github/bors.toml deleted file mode 100644 index 1ecc9d8d..00000000 --- a/.github/bors.toml +++ /dev/null @@ -1,4 +0,0 @@ -status = [ - "all", -] -delete_merged_branches = true diff --git a/.github/ci/doc.sh b/.github/ci/doc.sh index 57184dc1..c317a12e 100755 --- a/.github/ci/doc.sh +++ b/.github/ci/doc.sh @@ -39,6 +39,7 @@ docserver-builder -i ./embassy-net-wiznet -o webroot/crates/embassy-net-wiznet/g 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 +docserver-builder -i ./embassy-net-adin1110 -o webroot/crates/embassy-net-adin1110/git.zup export KUBECONFIG=/ci/secrets/kubeconfig.yml POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name}) diff --git a/.github/ci/test.sh b/.github/ci/test.sh index 2892bcf8..04f4fc7c 100755 --- a/.github/ci/test.sh +++ b/.github/ci/test.sh @@ -28,3 +28,5 @@ cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --featu cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f429vg,exti,time-driver-any,exti cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f732ze,exti,time-driver-any,exti cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f769ni,exti,time-driver-any,exti + +cargo test --manifest-path ./embassy-net-adin1110/Cargo.toml diff --git a/ci.sh b/ci.sh index e83b3b84..db00c406 100755 --- a/ci.sh +++ b/ci.sh @@ -19,6 +19,19 @@ cargo batch \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,log \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,defmt \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt,arch-cortex-m,executor-thread,executor-interrupt,integrated-timers \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,integrated-timers \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread,integrated-timers \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-interrupt \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-interrupt,integrated-timers \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread,executor-interrupt \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread,executor-interrupt,integrated-timers \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32 \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32,integrated-timers \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32,executor-thread \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32,executor-thread,integrated-timers \ --- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \ --- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt,defmt-timestamp-uptime,tick-hz-32_768,generic-queue-8 \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet \ diff --git a/cyw43/Cargo.toml b/cyw43/Cargo.toml index f0cbafeb..f3fc0c6a 100644 --- a/cyw43/Cargo.toml +++ b/cyw43/Cargo.toml @@ -11,7 +11,7 @@ log = ["dep:log"] firmware-logs = [] [dependencies] -embassy-time = { version = "0.1.2", path = "../embassy-time"} +embassy-time = { version = "0.1.3", path = "../embassy-time"} 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"} @@ -32,4 +32,4 @@ num_enum = { version = "0.5.7", default-features = false } src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-v$VERSION/cyw43/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43/src/" target = "thumbv6m-none-eabi" -features = ["defmt", "firmware-logs"] \ No newline at end of file +features = ["defmt", "firmware-logs"] diff --git a/cyw43/src/bus.rs b/cyw43/src/bus.rs index fc50135e..991f804c 100644 --- a/cyw43/src/bus.rs +++ b/cyw43/src/bus.rs @@ -148,7 +148,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/cyw43/src/consts.rs b/cyw43/src/consts.rs index 9798538c..b6e22e61 100644 --- a/cyw43/src/consts.rs +++ b/cyw43/src/consts.rs @@ -117,6 +117,7 @@ pub(crate) const IOCTL_CMD_UP: u32 = 2; pub(crate) const IOCTL_CMD_DOWN: u32 = 3; pub(crate) const IOCTL_CMD_SET_SSID: u32 = 26; pub(crate) const IOCTL_CMD_SET_CHANNEL: u32 = 30; +pub(crate) const IOCTL_CMD_DISASSOC: u32 = 52; pub(crate) const IOCTL_CMD_ANTDIV: u32 = 64; pub(crate) const IOCTL_CMD_SET_AP: u32 = 118; pub(crate) const IOCTL_CMD_SET_VAR: u32 = 263; diff --git a/cyw43/src/control.rs b/cyw43/src/control.rs index 9ffb75b7..87ec96c4 100644 --- a/cyw43/src/control.rs +++ b/cyw43/src/control.rs @@ -124,9 +124,9 @@ impl<'a> Control<'a> { self.set_iovar("bsscfg:event_msgs", &evts.to_bytes()).await; Timer::after(Duration::from_millis(100)).await; - + // set wifi up - self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await; + self.up().await; Timer::after(Duration::from_millis(100)).await; @@ -145,6 +145,16 @@ impl<'a> Control<'a> { debug!("cyw43 control init done"); } + /// Set the WiFi interface up. + async fn up(&mut self) { + self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await; + } + + /// Set the interface down. + async fn down(&mut self) { + self.ioctl(IoctlType::Set, IOCTL_CMD_DOWN, 0, &mut []).await; + } + pub async fn set_power_management(&mut self, mode: PowerManagementMode) { // power save mode let mode_num = mode.mode(); @@ -263,13 +273,13 @@ impl<'a> Control<'a> { } // Temporarily set wifi down - self.ioctl(IoctlType::Set, IOCTL_CMD_DOWN, 0, &mut []).await; + self.down().await; // Turn off APSTA mode self.set_iovar_u32("apsta", 0).await; // Set wifi up again - self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await; + self.up().await; // Turn on AP mode self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 1).await; @@ -430,6 +440,11 @@ impl<'a> Control<'a> { events: &self.events, } } + /// Leave the wifi, with which we are currently associated. + pub async fn leave(&mut self) { + self.ioctl(IoctlType::Set, IOCTL_CMD_DISASSOC, 0, &mut []).await; + info!("Disassociated") + } } pub struct Scanner<'a> { diff --git a/cyw43/src/fmt.rs b/cyw43/src/fmt.rs index 9534c101..78e583c1 100644 --- a/cyw43/src/fmt.rs +++ b/cyw43/src/fmt.rs @@ -83,14 +83,17 @@ macro_rules! todo { }; } +#[cfg(not(feature = "defmt"))] macro_rules! unreachable { ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::unreachable!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::unreachable!($($x)*); - } + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) }; } @@ -226,7 +229,8 @@ impl Try for Result { } } -pub struct Bytes<'a>(pub &'a [u8]); +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); impl<'a> Debug for Bytes<'a> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { diff --git a/cyw43/src/lib.rs b/cyw43/src/lib.rs index 6bd86706..530b41ad 100644 --- a/cyw43/src/lib.rs +++ b/cyw43/src/lib.rs @@ -27,7 +27,7 @@ use ioctl::IoctlState; use crate::bus::Bus; pub use crate::bus::SpiBusCyw43; -pub use crate::control::{Control, Error as ControlError}; +pub use crate::control::{Control, Error as ControlError, Scanner}; pub use crate::runner::Runner; pub use crate::structs::BssInfo; diff --git a/docs/modules/ROOT/examples/basic/Cargo.toml b/docs/modules/ROOT/examples/basic/Cargo.toml index 237ae0ac..e94358a9 100644 --- a/docs/modules/ROOT/examples/basic/Cargo.toml +++ b/docs/modules/ROOT/examples/basic/Cargo.toml @@ -6,7 +6,7 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-executor = { version = "0.2.0", path = "../../../../../embassy-executor", features = ["defmt", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] } +embassy-executor = { version = "0.3.0", path = "../../../../../embassy-executor", features = ["defmt", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] } embassy-time = { version = "0.1.0", path = "../../../../../embassy-time", features = ["defmt", "nightly"] } embassy-nrf = { version = "0.1.0", path = "../../../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "nightly"] } diff --git a/docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml b/docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml index a7236ed5..b39f02ae 100644 --- a/docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml +++ b/docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" cortex-m = "0.7" cortex-m-rt = "0.7" embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "exti"] } -embassy-executor = { version = "0.2.0", features = ["nightly", "arch-cortex-m", "executor-thread"] } +embassy-executor = { version = "0.3.0", features = ["nightly", "arch-cortex-m", "executor-thread"] } defmt = "0.3.0" defmt-rtt = "0.3.0" diff --git a/embassy-boot/boot/src/fmt.rs b/embassy-boot/boot/src/fmt.rs index 06697081..78e583c1 100644 --- a/embassy-boot/boot/src/fmt.rs +++ b/embassy-boot/boot/src/fmt.rs @@ -1,6 +1,8 @@ #![macro_use] #![allow(unused_macros)] +use core::fmt::{Debug, Display, LowerHex}; + #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); @@ -81,14 +83,17 @@ macro_rules! todo { }; } +#[cfg(not(feature = "defmt"))] macro_rules! unreachable { ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::unreachable!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::unreachable!($($x)*); - } + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) }; } @@ -223,3 +228,31 @@ impl Try for Result { self } } + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-boot/nrf/src/fmt.rs b/embassy-boot/nrf/src/fmt.rs index 06697081..78e583c1 100644 --- a/embassy-boot/nrf/src/fmt.rs +++ b/embassy-boot/nrf/src/fmt.rs @@ -1,6 +1,8 @@ #![macro_use] #![allow(unused_macros)] +use core::fmt::{Debug, Display, LowerHex}; + #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); @@ -81,14 +83,17 @@ macro_rules! todo { }; } +#[cfg(not(feature = "defmt"))] macro_rules! unreachable { ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::unreachable!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::unreachable!($($x)*); - } + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) }; } @@ -223,3 +228,31 @@ impl Try for Result { self } } + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-boot/rp/src/fmt.rs b/embassy-boot/rp/src/fmt.rs index 06697081..78e583c1 100644 --- a/embassy-boot/rp/src/fmt.rs +++ b/embassy-boot/rp/src/fmt.rs @@ -1,6 +1,8 @@ #![macro_use] #![allow(unused_macros)] +use core::fmt::{Debug, Display, LowerHex}; + #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); @@ -81,14 +83,17 @@ macro_rules! todo { }; } +#[cfg(not(feature = "defmt"))] macro_rules! unreachable { ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::unreachable!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::unreachable!($($x)*); - } + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) }; } @@ -223,3 +228,31 @@ impl Try for Result { self } } + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-boot/stm32/src/fmt.rs b/embassy-boot/stm32/src/fmt.rs index 06697081..78e583c1 100644 --- a/embassy-boot/stm32/src/fmt.rs +++ b/embassy-boot/stm32/src/fmt.rs @@ -1,6 +1,8 @@ #![macro_use] #![allow(unused_macros)] +use core::fmt::{Debug, Display, LowerHex}; + #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); @@ -81,14 +83,17 @@ macro_rules! todo { }; } +#[cfg(not(feature = "defmt"))] macro_rules! unreachable { ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::unreachable!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::unreachable!($($x)*); - } + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) }; } @@ -223,3 +228,31 @@ impl Try for Result { self } } + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-embedded-hal/Cargo.toml b/embassy-embedded-hal/Cargo.toml index fd921d27..b311b591 100644 --- a/embassy-embedded-hal/Cargo.toml +++ b/embassy-embedded-hal/Cargo.toml @@ -21,7 +21,7 @@ default = ["time"] [dependencies] embassy-futures = { version = "0.1.0", path = "../embassy-futures", optional = true } embassy-sync = { version = "0.2.0", path = "../embassy-sync" } -embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true } +embassy-time = { version = "0.1.3", path = "../embassy-time", optional = true } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [ "unproven", ] } diff --git a/embassy-executor/CHANGELOG.md b/embassy-executor/CHANGELOG.md index e2e7bce3..ccbca1eb 100644 --- a/embassy-executor/CHANGELOG.md +++ b/embassy-executor/CHANGELOG.md @@ -5,9 +5,12 @@ 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 +## 0.3.0 - 2023-08-25 - Replaced Pender. Implementations now must define an extern function called `__pender`. +- Made `raw::AvailableTask` public +- Made `SpawnToken::new_failed` public +- You can now use arbitrary expressions to specify `#[task(pool_size = X)]` ## 0.2.1 - 2023-08-10 diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index d190c95a..35944625 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-executor" -version = "0.2.1" +version = "0.3.0" edition = "2021" license = "MIT OR Apache-2.0" description = "async/await executor designed for embedded usage" @@ -58,8 +58,8 @@ log = { version = "0.4.14", optional = true } rtos-trace = { version = "0.1.2", optional = true } futures-util = { version = "0.3.17", default-features = false } -embassy-macros = { version = "0.2.0", path = "../embassy-macros" } -embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true} +embassy-macros = { version = "0.2.1", path = "../embassy-macros" } +embassy-time = { version = "0.1.3", path = "../embassy-time", optional = true} atomic-polyfill = "1.0.1" critical-section = "1.1" static_cell = "1.1" diff --git a/embassy-executor/src/arch/cortex_m.rs b/embassy-executor/src/arch/cortex_m.rs index 0806a22a..fde862f3 100644 --- a/embassy-executor/src/arch/cortex_m.rs +++ b/embassy-executor/src/arch/cortex_m.rs @@ -1,5 +1,3 @@ -const THREAD_PENDER: usize = usize::MAX; - #[export_name = "__pender"] #[cfg(any(feature = "executor-thread", feature = "executor-interrupt"))] fn __pender(context: *mut ()) { @@ -48,13 +46,14 @@ fn __pender(context: *mut ()) { pub use thread::*; #[cfg(feature = "executor-thread")] mod thread { + pub(super) const THREAD_PENDER: usize = usize::MAX; + use core::arch::asm; use core::marker::PhantomData; #[cfg(feature = "nightly")] pub use embassy_macros::main_cortex_m as main; - use crate::arch::THREAD_PENDER; use crate::{raw, Spawner}; /// Thread mode executor, using WFE/SEV. diff --git a/embassy-executor/src/fmt.rs b/embassy-executor/src/fmt.rs index 06697081..78e583c1 100644 --- a/embassy-executor/src/fmt.rs +++ b/embassy-executor/src/fmt.rs @@ -1,6 +1,8 @@ #![macro_use] #![allow(unused_macros)] +use core::fmt::{Debug, Display, LowerHex}; + #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); @@ -81,14 +83,17 @@ macro_rules! todo { }; } +#[cfg(not(feature = "defmt"))] macro_rules! unreachable { ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::unreachable!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::unreachable!($($x)*); - } + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) }; } @@ -223,3 +228,31 @@ impl Try for Result { self } } + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 7caa3302..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,16 +249,59 @@ 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)] 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-futures/src/fmt.rs b/embassy-futures/src/fmt.rs index 06697081..78e583c1 100644 --- a/embassy-futures/src/fmt.rs +++ b/embassy-futures/src/fmt.rs @@ -1,6 +1,8 @@ #![macro_use] #![allow(unused_macros)] +use core::fmt::{Debug, Display, LowerHex}; + #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); @@ -81,14 +83,17 @@ macro_rules! todo { }; } +#[cfg(not(feature = "defmt"))] macro_rules! unreachable { ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::unreachable!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::unreachable!($($x)*); - } + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) }; } @@ -223,3 +228,31 @@ impl Try for Result { self } } + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-hal-internal/src/fmt.rs b/embassy-hal-internal/src/fmt.rs index 06697081..78e583c1 100644 --- a/embassy-hal-internal/src/fmt.rs +++ b/embassy-hal-internal/src/fmt.rs @@ -1,6 +1,8 @@ #![macro_use] #![allow(unused_macros)] +use core::fmt::{Debug, Display, LowerHex}; + #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); @@ -81,14 +83,17 @@ macro_rules! todo { }; } +#[cfg(not(feature = "defmt"))] macro_rules! unreachable { ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::unreachable!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::unreachable!($($x)*); - } + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) }; } @@ -223,3 +228,31 @@ impl Try for Result { self } } + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-lora/Cargo.toml b/embassy-lora/Cargo.toml index feea0658..fa445a39 100644 --- a/embassy-lora/Cargo.toml +++ b/embassy-lora/Cargo.toml @@ -20,7 +20,7 @@ defmt = ["dep:defmt", "lorawan-device/defmt"] defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } -embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true } +embassy-time = { version = "0.1.3", 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 = "=1.0.0-rc.1" } @@ -31,4 +31,4 @@ 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 +lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "1323eccc1c470d4259f95f4f315d1be830d572a3"} diff --git a/embassy-lora/src/fmt.rs b/embassy-lora/src/fmt.rs index 06697081..78e583c1 100644 --- a/embassy-lora/src/fmt.rs +++ b/embassy-lora/src/fmt.rs @@ -1,6 +1,8 @@ #![macro_use] #![allow(unused_macros)] +use core::fmt::{Debug, Display, LowerHex}; + #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); @@ -81,14 +83,17 @@ macro_rules! todo { }; } +#[cfg(not(feature = "defmt"))] macro_rules! unreachable { ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::unreachable!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::unreachable!($($x)*); - } + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) }; } @@ -223,3 +228,31 @@ impl Try for Result { self } } + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-macros/Cargo.toml b/embassy-macros/Cargo.toml index 3b8fe8b4..a893cd30 100644 --- a/embassy-macros/Cargo.toml +++ b/embassy-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-macros" -version = "0.2.0" +version = "0.2.1" edition = "2021" license = "MIT OR Apache-2.0" description = "macros for creating the entry point and tasks for embassy-executor" diff --git a/embassy-net-adin1110/Cargo.toml b/embassy-net-adin1110/Cargo.toml new file mode 100644 index 00000000..e74fb7cd --- /dev/null +++ b/embassy-net-adin1110/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "embassy-net-adin1110" +version = "0.1.0" +description = "embassy-net driver for the ADIN1110 ethernet chip" +keywords = ["embedded", "ADIN1110", "embassy-net", "embedded-hal-async", "ethernet", "async"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"] +license = "MIT OR Apache-2.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +heapless = "0.7.16" +defmt = { version = "0.3", optional = true } +log = { version = "0.4.4", default-features = false, optional = true } +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"] } +embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel" } +embassy-time = { version = "0.1.0" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +bitfield = "0.14.0" + + +[dev-dependencies] +# reenable when https://github.com/dbrgn/embedded-hal-mock/pull/86 is merged. +#embedded-hal-mock = { git = "https://github.com/dbrgn/embedded-hal-mock", branch = "1-alpha", features = ["embedded-hal-async", "eh1"] }] } +embedded-hal-mock = { git = "https://github.com/newAM/embedded-hal-mock", branch = "eh1-rc.1", features = ["embedded-hal-async", "eh1"] } +crc = "3.0.1" +env_logger = "0.10" +critical-section = { version = "1.1.1", features = ["std"] } +futures-test = "0.3.17" + +[features] +default = [ ] +defmt = [ "dep:defmt" ] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-adin1110-v$VERSION/embassy-net-adin1110/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-adin1110/src/" +target = "thumbv7em-none-eabi" diff --git a/embassy-net-adin1110/README.md b/embassy-net-adin1110/README.md new file mode 100644 index 00000000..3c280418 --- /dev/null +++ b/embassy-net-adin1110/README.md @@ -0,0 +1,55 @@ +# SPE ADIN1110 `embassy-net` integration + +[`embassy-net`](https://crates.io/crates/embassy-net) integration for the `Analog ADIN1110` SPI SPE ethernet chips. + +## What is SPE or Single Pair Ethernet / 10 BASE-T1L + +SPE stands for Single Pair Ethernet. As the names implies, SPE uses differential signalling with 2 wires (a twisted-pair) in a cable as the physical medium. +SPE is full-duplex - it can transmit and receive ethernet packets at the same time. SPE is still ethernet, only the physical layer is different. + +SPE also supports [`PoDL (Power over Data Line)`](https://www.ti.com/lit/an/snla395/snla395.pdf), power delivery from 0.5 up to 50 Watts, similar to [`PoE`](https://en.wikipedia.org/wiki/Power_over_Ethernet), but an additional hardware and handshake protocol are needed. + +SPE has many link speeds but only `10 BASE-T1L` is able to reach cable lengths up to 1000 meters in `2.4 Vpp` transmit amplitude. +Currently in 2023, none of the standards are compatible with each other. +Thus `10 BASE-T1L` won't work with a `10 BASE-T1S`, `100 BASE-T1` or any standard `x BASE-T`. + +In the industry SPE is also called [`APL (Advanced Physical Layer)`](https://www.ethernet-apl.org), and is based on the `10 BASE-T1L` standard. + +APL can be used in [`intrinsic safety applications/explosion hazardous areas`](https://en.wikipedia.org/wiki/Electrical_equipment_in_hazardous_areas) which has its own name and standard called [`2-WISE (2-wire intrinsically safe ethernet) IEC TS 60079-47:2021`](https://webstore.iec.ch/publication/64292). + +`10 BASE-T1L` and `ADIN1110` are designed to support intrinsic safety applications. The power supply energy is fixed and PDoL is not supported. + +## Supported SPI modes + +`ADIN1110` supports two SPI modes. `Generic` and [`OPEN Alliance 10BASE-T1x MAC-PHY serial interface`](https://opensig.org/download/document/OPEN_Alliance_10BASET1x_MAC-PHY_Serial_Interface_V1.1.pdf) + +Both modes support with and without additional CRC. +Currently only `Generic` SPI with or without CRC is supported. + +*NOTE:* SPI Mode is selected by the hardware pins `SPI_CFG0` and `SPI_CFG1`. Software can't detect nor change the mode. + +## Hardware + +- Tested on [`Analog Devices EVAL-ADIN1110EBZ`](https://www.analog.com/en/design-center/evaluation-hardware-and-software/evaluation-boards-kits/eval-adin1110.html) with an `STM32L4S5QII3P`, see [`spe_adin1110_http_server`](../examples/stm32l4/src/bin/spe_adin1110_http_server.rs) dor an example. +- [`SparkFun MicroMod Single Pair Ethernet Function Board`](https://www.sparkfun.com/products/19038) or [`SparkFun MicroMod Single Pair Ethernet Kit`](https://www.sparkfun.com/products/19628), supporting multiple microcontrollers. **Make sure to check if it's a microcontroller that is supported by Embassy!** + +## Other SPE chips + +* [`Analog ADIN2111`](https://www.analog.com/en/products/adin2111.html) 2 Port SPI version. Can work with this driver. +* [`Analog ADIN1100`](https://www.analog.com/en/products/adin1100.html) RGMII version. + +## Testing + +ADIN1110 library can tested on the host with a mock SPI driver. + +$ `cargo test --target x86_64-unknown-linux-gnu` + +## 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-adin1110/src/crc32.rs b/embassy-net-adin1110/src/crc32.rs new file mode 100644 index 00000000..a3474f70 --- /dev/null +++ b/embassy-net-adin1110/src/crc32.rs @@ -0,0 +1,358 @@ +pub const CRC32R_LOOKUP_TABLE: [u32; 256] = [ + 0x0000_0000, + 0x7707_3096, + 0xEE0E_612C, + 0x9909_51BA, + 0x076D_C419, + 0x706A_F48F, + 0xE963_A535, + 0x9E64_95A3, + 0x0EDB_8832, + 0x79DC_B8A4, + 0xE0D5_E91E, + 0x97D2_D988, + 0x09B6_4C2B, + 0x7EB1_7CBD, + 0xE7B8_2D07, + 0x90BF_1D91, + 0x1DB7_1064, + 0x6AB0_20F2, + 0xF3B9_7148, + 0x84BE_41DE, + 0x1ADA_D47D, + 0x6DDD_E4EB, + 0xF4D4_B551, + 0x83D3_85C7, + 0x136C_9856, + 0x646B_A8C0, + 0xFD62_F97A, + 0x8A65_C9EC, + 0x1401_5C4F, + 0x6306_6CD9, + 0xFA0F_3D63, + 0x8D08_0DF5, + 0x3B6E_20C8, + 0x4C69_105E, + 0xD560_41E4, + 0xA267_7172, + 0x3C03_E4D1, + 0x4B04_D447, + 0xD20D_85FD, + 0xA50A_B56B, + 0x35B5_A8FA, + 0x42B2_986C, + 0xDBBB_C9D6, + 0xACBC_F940, + 0x32D8_6CE3, + 0x45DF_5C75, + 0xDCD6_0DCF, + 0xABD1_3D59, + 0x26D9_30AC, + 0x51DE_003A, + 0xC8D7_5180, + 0xBFD0_6116, + 0x21B4_F4B5, + 0x56B3_C423, + 0xCFBA_9599, + 0xB8BD_A50F, + 0x2802_B89E, + 0x5F05_8808, + 0xC60C_D9B2, + 0xB10B_E924, + 0x2F6F_7C87, + 0x5868_4C11, + 0xC161_1DAB, + 0xB666_2D3D, + 0x76DC_4190, + 0x01DB_7106, + 0x98D2_20BC, + 0xEFD5_102A, + 0x71B1_8589, + 0x06B6_B51F, + 0x9FBF_E4A5, + 0xE8B8_D433, + 0x7807_C9A2, + 0x0F00_F934, + 0x9609_A88E, + 0xE10E_9818, + 0x7F6A_0DBB, + 0x086D_3D2D, + 0x9164_6C97, + 0xE663_5C01, + 0x6B6B_51F4, + 0x1C6C_6162, + 0x8565_30D8, + 0xF262_004E, + 0x6C06_95ED, + 0x1B01_A57B, + 0x8208_F4C1, + 0xF50F_C457, + 0x65B0_D9C6, + 0x12B7_E950, + 0x8BBE_B8EA, + 0xFCB9_887C, + 0x62DD_1DDF, + 0x15DA_2D49, + 0x8CD3_7CF3, + 0xFBD4_4C65, + 0x4DB2_6158, + 0x3AB5_51CE, + 0xA3BC_0074, + 0xD4BB_30E2, + 0x4ADF_A541, + 0x3DD8_95D7, + 0xA4D1_C46D, + 0xD3D6_F4FB, + 0x4369_E96A, + 0x346E_D9FC, + 0xAD67_8846, + 0xDA60_B8D0, + 0x4404_2D73, + 0x3303_1DE5, + 0xAA0A_4C5F, + 0xDD0D_7CC9, + 0x5005_713C, + 0x2702_41AA, + 0xBE0B_1010, + 0xC90C_2086, + 0x5768_B525, + 0x206F_85B3, + 0xB966_D409, + 0xCE61_E49F, + 0x5EDE_F90E, + 0x29D9_C998, + 0xB0D0_9822, + 0xC7D7_A8B4, + 0x59B3_3D17, + 0x2EB4_0D81, + 0xB7BD_5C3B, + 0xC0BA_6CAD, + 0xEDB8_8320, + 0x9ABF_B3B6, + 0x03B6_E20C, + 0x74B1_D29A, + 0xEAD5_4739, + 0x9DD2_77AF, + 0x04DB_2615, + 0x73DC_1683, + 0xE363_0B12, + 0x9464_3B84, + 0x0D6D_6A3E, + 0x7A6A_5AA8, + 0xE40E_CF0B, + 0x9309_FF9D, + 0x0A00_AE27, + 0x7D07_9EB1, + 0xF00F_9344, + 0x8708_A3D2, + 0x1E01_F268, + 0x6906_C2FE, + 0xF762_575D, + 0x8065_67CB, + 0x196C_3671, + 0x6E6B_06E7, + 0xFED4_1B76, + 0x89D3_2BE0, + 0x10DA_7A5A, + 0x67DD_4ACC, + 0xF9B9_DF6F, + 0x8EBE_EFF9, + 0x17B7_BE43, + 0x60B0_8ED5, + 0xD6D6_A3E8, + 0xA1D1_937E, + 0x38D8_C2C4, + 0x4FDF_F252, + 0xD1BB_67F1, + 0xA6BC_5767, + 0x3FB5_06DD, + 0x48B2_364B, + 0xD80D_2BDA, + 0xAF0A_1B4C, + 0x3603_4AF6, + 0x4104_7A60, + 0xDF60_EFC3, + 0xA867_DF55, + 0x316E_8EEF, + 0x4669_BE79, + 0xCB61_B38C, + 0xBC66_831A, + 0x256F_D2A0, + 0x5268_E236, + 0xCC0C_7795, + 0xBB0B_4703, + 0x2202_16B9, + 0x5505_262F, + 0xC5BA_3BBE, + 0xB2BD_0B28, + 0x2BB4_5A92, + 0x5CB3_6A04, + 0xC2D7_FFA7, + 0xB5D0_CF31, + 0x2CD9_9E8B, + 0x5BDE_AE1D, + 0x9B64_C2B0, + 0xEC63_F226, + 0x756A_A39C, + 0x026D_930A, + 0x9C09_06A9, + 0xEB0E_363F, + 0x7207_6785, + 0x0500_5713, + 0x95BF_4A82, + 0xE2B8_7A14, + 0x7BB1_2BAE, + 0x0CB6_1B38, + 0x92D2_8E9B, + 0xE5D5_BE0D, + 0x7CDC_EFB7, + 0x0BDB_DF21, + 0x86D3_D2D4, + 0xF1D4_E242, + 0x68DD_B3F8, + 0x1FDA_836E, + 0x81BE_16CD, + 0xF6B9_265B, + 0x6FB0_77E1, + 0x18B7_4777, + 0x8808_5AE6, + 0xFF0F_6A70, + 0x6606_3BCA, + 0x1101_0B5C, + 0x8F65_9EFF, + 0xF862_AE69, + 0x616B_FFD3, + 0x166C_CF45, + 0xA00A_E278, + 0xD70D_D2EE, + 0x4E04_8354, + 0x3903_B3C2, + 0xA767_2661, + 0xD060_16F7, + 0x4969_474D, + 0x3E6E_77DB, + 0xAED1_6A4A, + 0xD9D6_5ADC, + 0x40DF_0B66, + 0x37D8_3BF0, + 0xA9BC_AE53, + 0xDEBB_9EC5, + 0x47B2_CF7F, + 0x30B5_FFE9, + 0xBDBD_F21C, + 0xCABA_C28A, + 0x53B3_9330, + 0x24B4_A3A6, + 0xBAD0_3605, + 0xCDD7_0693, + 0x54DE_5729, + 0x23D9_67BF, + 0xB366_7A2E, + 0xC461_4AB8, + 0x5D68_1B02, + 0x2A6F_2B94, + 0xB40B_BE37, + 0xC30C_8EA1, + 0x5A05_DF1B, + 0x2D02_EF8D, +]; + +#[allow(non_camel_case_types)] +#[derive(Debug)] +pub struct ETH_FSC(pub u32); + +impl ETH_FSC { + pub const CRC32_OK: u32 = 0x2144_df1c; + + #[must_use] + pub fn new(data: &[u8]) -> Self { + let fsc = data.iter().fold(u32::MAX, |crc, byte| { + let idx = u8::try_from(crc & 0xFF).unwrap() ^ byte; + CRC32R_LOOKUP_TABLE[usize::from(idx)] ^ (crc >> 8) + }) ^ u32::MAX; + Self(fsc) + } + + #[must_use] + pub fn update(self, data: &[u8]) -> Self { + let fsc = data.iter().fold(self.0 ^ u32::MAX, |crc, byte| { + let idx = u8::try_from(crc & 0xFF).unwrap() ^ byte; + CRC32R_LOOKUP_TABLE[usize::from(idx)] ^ (crc >> 8) + }) ^ u32::MAX; + Self(fsc) + } + + #[must_use] + pub fn crc_ok(&self) -> bool { + self.0 == Self::CRC32_OK + } + + #[must_use] + pub fn hton_bytes(&self) -> [u8; 4] { + self.0.to_le_bytes() + } + + #[must_use] + pub fn hton(&self) -> u32 { + self.0.to_le() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn crc32_ethernet_frame() { + let packet_a = &[ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xe0, 0x4c, 0x68, 0xee, 0xee, 0xff, 0x06, 0x00, 0x01, 0x08, 0x00, + 0x06, 0x04, 0x00, 0x01, 0x00, 0xe0, 0x4c, 0x68, 0x0e, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xc0, 0xa8, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf2, 0x65, 0x90, 0x3d, + ]; + + let packet_b = &[ + 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x00, 0xe0, 0x4c, 0x68, 0xee, 0xee, 0xdd, 0x06, 0x00, 0x01, 0x08, 0x00, + 0x06, 0x04, 0x00, 0x02, 0x00, 0xe0, 0x4c, 0x68, 0x09, 0xde, 0xc0, 0xa8, 0x01, 0x02, 0x12, 0x34, 0x56, 0x78, + 0x9a, 0xbc, 0xc0, 0xa8, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x3d, 0x67, 0x7c, + ]; + + // Packet A + let own_crc = ETH_FSC::new(&packet_a[0..60]); + let crc_bytes = own_crc.hton_bytes(); + println!("{:08x} {:02x?}", own_crc.0, crc_bytes); + assert_eq!(&crc_bytes, &packet_a[60..64]); + + let own_crc = ETH_FSC::new(packet_a); + println!("{:08x}", own_crc.0); + assert_eq!(own_crc.0, ETH_FSC::CRC32_OK); + + // Packet B + let own_crc = ETH_FSC::new(&packet_b[0..60]); + let crc_bytes = own_crc.hton_bytes(); + println!("{:08x} {:02x?}", own_crc.0, crc_bytes); + assert_eq!(&crc_bytes, &packet_b[60..64]); + + let own_crc = ETH_FSC::new(packet_b); + println!("{:08x}", own_crc.0); + assert_eq!(own_crc.0, ETH_FSC::CRC32_OK); + } + + #[test] + fn crc32_update() { + let full_data = &[ + 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x00, 0xe0, 0x4c, 0x68, 0xee, 0xee, 0xdd, 0x06, 0x00, 0x01, 0x08, 0x00, + 0x06, 0x04, 0x00, 0x02, 0x00, 0xe0, 0x4c, 0x68, 0x09, 0xde, 0xc0, 0xa8, 0x01, 0x02, 0x12, 0x34, 0x56, 0x78, + 0x9a, 0xbc, 0xc0, 0xa8, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x3d, 0x67, 0x7c, + ]; + + let (part_a, part_b) = full_data.split_at(16); + let crc_partially = ETH_FSC::new(part_a).update(part_b); + + let crc_full = ETH_FSC::new(full_data); + + assert_eq!(crc_full.0, crc_partially.0); + } +} diff --git a/embassy-net-adin1110/src/crc8.rs b/embassy-net-adin1110/src/crc8.rs new file mode 100644 index 00000000..7d20a740 --- /dev/null +++ b/embassy-net-adin1110/src/crc8.rs @@ -0,0 +1,53 @@ +/// CRC-8/ITU +const CRC8X_TABLE: [u8; 256] = [ + 0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d, 0x70, 0x77, 0x7e, + 0x79, 0x6c, 0x6b, 0x62, 0x65, 0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d, 0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, + 0xf2, 0xf5, 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd, 0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, 0xa8, + 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd, 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2, 0xff, 0xf8, 0xf1, 0xf6, + 0xe3, 0xe4, 0xed, 0xea, 0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2, 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, + 0x9a, 0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32, 0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a, 0x57, 0x50, + 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42, 0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, 0x89, 0x8e, 0x87, 0x80, 0x95, + 0x92, 0x9b, 0x9c, 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4, 0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec, + 0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4, 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c, 0x51, 0x56, 0x5f, + 0x58, 0x4d, 0x4a, 0x43, 0x44, 0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c, 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, + 0x33, 0x34, 0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, 0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d, 0x64, 0x63, 0x3e, + 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b, 0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, 0xae, 0xa9, 0xa0, 0xa7, + 0xb2, 0xb5, 0xbc, 0xbb, 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83, 0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, + 0xcb, 0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3, +]; + +/// Calculate the crc of a pease of data. +pub fn crc8(data: &[u8]) -> u8 { + data.iter().fold(0, |crc, &byte| CRC8X_TABLE[usize::from(byte ^ crc)]) +} + +#[cfg(test)] +mod tests { + use ::crc::{Crc, CRC_8_SMBUS}; + + use super::crc8; + + #[test] + fn spi_header_crc8() { + let data = &[0x80, 0x00]; + + let c = Crc::::new(&CRC_8_SMBUS); + let mut dig = c.digest(); + dig.update(data); + let sw_crc = dig.finalize(); + + let own_crc = crc8(data); + + assert_eq!(own_crc, sw_crc); + assert_eq!(own_crc, 182); + + let data = &[0x80, 0x01]; + let mut dig = c.digest(); + dig.update(data); + let sw_crc = dig.finalize(); + let own_crc = crc8(data); + + assert_eq!(own_crc, sw_crc); + assert_eq!(own_crc, 177); + } +} diff --git a/embassy-net-adin1110/src/lib.rs b/embassy-net-adin1110/src/lib.rs new file mode 100644 index 00000000..c0a9b44e --- /dev/null +++ b/embassy-net-adin1110/src/lib.rs @@ -0,0 +1,1046 @@ +#![deny(clippy::pedantic)] +#![feature(async_fn_in_trait)] +#![cfg_attr(not(any(test, feature = "std")), no_std)] +#![allow(clippy::module_name_repetitions)] +#![allow(clippy::missing_errors_doc)] +#![allow(clippy::missing_panics_doc)] +#![doc = include_str!("../README.md")] + +mod crc32; +mod crc8; +mod mdio; +mod phy; +mod regs; + +use ch::driver::LinkState; +pub use crc32::ETH_FSC; +use crc8::crc8; +use embassy_futures::select::{select, Either}; +use embassy_net_driver_channel as ch; +use embassy_time::{Duration, Timer}; +use embedded_hal_1::digital::OutputPin; +use embedded_hal_async::digital::Wait; +use embedded_hal_async::spi::{Operation, SpiDevice}; +use heapless::Vec; +pub use mdio::MdioBus; +pub use phy::{Phy10BaseT1x, RegsC22, RegsC45}; +pub use regs::{Config0, Config2, SpiRegisters as sr, Status0, Status1}; + +use crate::regs::{LedCntrl, LedFunc, LedPol, LedPolarity, SpiHeader}; + +pub const PHYID: u32 = 0x0283_BC91; + +/// Error values ADIN1110 +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow(non_camel_case_types)] +pub enum AdinError { + Spi(E), + SENDERROR, + READERROR, + CRC, + PACKET_TOO_BIG, + PACKET_TOO_SMALL, + MDIO_ACC_TIMEOUT, +} + +pub type AEResult = core::result::Result>; +pub const MDIO_PHY_ADDR: u8 = 0x01; + +/// Maximum Transmission Unit +pub const MTU: usize = 1514; + +/// Max SPI/Frame buffer size +pub const MAX_BUFF: usize = 2048; + +const DONT_CARE_BYTE: u8 = 0x00; +const TURN_AROUND_BYTE: u8 = 0x00; + +/// Packet minimal frame/packet length +const ETH_MIN_LEN: usize = 64; + +/// Ethernet `Frame Check Sequence` length +const FSC_LEN: usize = 4; +/// SPI Header, contains SPI action and register id. +const SPI_HEADER_LEN: usize = 2; +/// SPI Header CRC length +const SPI_HEADER_CRC_LEN: usize = 1; +/// Frame Header, +const FRAME_HEADER_LEN: usize = 2; + +// P1 = 0x00, P2 = 0x01 +const PORT_ID_BYTE: u8 = 0x00; + +pub type Packet = Vec; + +/// Type alias for the embassy-net driver for ADIN1110 +pub type Device<'d> = embassy_net_driver_channel::Device<'d, MTU>; + +/// Internal state for the embassy-net integration. +pub struct State { + ch_state: ch::State, +} +impl State { + /// Create a new `State`. + #[must_use] + pub const fn new() -> Self { + Self { + ch_state: ch::State::new(), + } + } +} + +#[derive(Debug)] +pub struct ADIN1110 { + /// SPI bus + spi: SPI, + /// Enable CRC on SPI transfer. + /// This must match with the hardware pin `SPI_CFG0` were low = CRC enable, high = CRC disabled. + crc: bool, +} + +impl ADIN1110 { + pub fn new(spi: SPI, crc: bool) -> Self { + Self { spi, crc } + } + + pub async fn read_reg(&mut self, reg: sr) -> AEResult { + let mut tx_buf = Vec::::new(); + + let mut spi_hdr = SpiHeader(0); + spi_hdr.set_control(true); + spi_hdr.set_addr(reg); + let _ = tx_buf.extend_from_slice(spi_hdr.0.to_be_bytes().as_slice()); + + if self.crc { + // Add CRC for header data + let _ = tx_buf.push(crc8(&tx_buf)); + } + + // Turn around byte, TODO: Unknown that this is. + let _ = tx_buf.push(TURN_AROUND_BYTE); + + let mut rx_buf = [0; 5]; + + let spi_read_len = if self.crc { rx_buf.len() } else { rx_buf.len() - 1 }; + + let mut spi_op = [Operation::Write(&tx_buf), Operation::Read(&mut rx_buf[0..spi_read_len])]; + + self.spi.transaction(&mut spi_op).await.map_err(AdinError::Spi)?; + + if self.crc { + let crc = crc8(&rx_buf[0..4]); + if crc != rx_buf[4] { + return Err(AdinError::CRC); + } + } + + let value = u32::from_be_bytes(rx_buf[0..4].try_into().unwrap()); + + #[cfg(feature = "defmt")] + defmt::trace!("REG Read {} = {:08x} SPI {:02x}", reg, value, &tx_buf); + + Ok(value) + } + + pub async fn write_reg(&mut self, reg: sr, value: u32) -> AEResult<(), SPI::Error> { + let mut tx_buf = Vec::::new(); + + let mut spi_hdr = SpiHeader(0); + spi_hdr.set_control(true); + spi_hdr.set_write(true); + spi_hdr.set_addr(reg); + let _ = tx_buf.extend_from_slice(spi_hdr.0.to_be_bytes().as_slice()); + + if self.crc { + // Add CRC for header data + let _ = tx_buf.push(crc8(&tx_buf)); + } + + let val = value.to_be_bytes(); + let _ = tx_buf.extend_from_slice(val.as_slice()); + + if self.crc { + // Add CRC for header data + let _ = tx_buf.push(crc8(val.as_slice())); + } + + #[cfg(feature = "defmt")] + defmt::trace!("REG Write {} = {:08x} SPI {:02x}", reg, value, &tx_buf); + + self.spi.write(&tx_buf).await.map_err(AdinError::Spi) + } + + /// helper function for write to `MDIO_ACC` register and wait for ready! + async fn write_mdio_acc_reg(&mut self, mdio_acc_val: u32) -> AEResult { + self.write_reg(sr::MDIO_ACC, mdio_acc_val).await?; + + // TODO: Add proper timeout! + for _ in 0..100_000 { + let val = self.read_reg(sr::MDIO_ACC).await?; + if val & 0x8000_0000 != 0 { + return Ok(val); + } + } + + Err(AdinError::MDIO_ACC_TIMEOUT) + } + + /// Read out fifo ethernet packet memory received via the wire. + pub async fn read_fifo(&mut self, packet: &mut [u8]) -> AEResult { + let mut tx_buf = Vec::::new(); + + // Size of the frame, also includes the appednded header. + let packet_size = self.read_reg(sr::RX_FSIZE).await? as usize; + + // Packet read of write to the MAC packet buffer must be a multipul of 4! + let read_size = packet_size.next_multiple_of(4); + + if packet_size < (SPI_HEADER_LEN + FSC_LEN) { + return Err(AdinError::PACKET_TOO_SMALL); + } + + if read_size > packet.len() { + #[cfg(feature = "defmt")] + defmt::trace!("MAX: {} WANT: {}", packet.len(), read_size); + return Err(AdinError::PACKET_TOO_BIG); + } + + let mut spi_hdr = SpiHeader(0); + spi_hdr.set_control(true); + spi_hdr.set_addr(sr::RX); + let _ = tx_buf.extend_from_slice(spi_hdr.0.to_be_bytes().as_slice()); + + if self.crc { + // Add CRC for header data + let _ = tx_buf.push(crc8(&tx_buf)); + } + + // Turn around byte, TODO: Unknown that this is. + let _ = tx_buf.push(TURN_AROUND_BYTE); + + let spi_packet = &mut packet[0..read_size]; + + assert_eq!(spi_packet.len() & 0x03, 0x00); + + let mut pkt_header = [0, 0]; + let mut fsc = [0, 0, 0, 0]; + + let mut spi_op = [ + Operation::Write(&tx_buf), + Operation::Read(&mut pkt_header), + Operation::Read(spi_packet), + Operation::Read(&mut fsc), + ]; + + self.spi.transaction(&mut spi_op).await.map_err(AdinError::Spi)?; + + Ok(packet_size as usize) + } + + /// Write to fifo ethernet packet memory send over the wire. + pub async fn write_fifo(&mut self, frame: &[u8]) -> AEResult<(), SPI::Error> { + const HEAD_LEN: usize = SPI_HEADER_LEN + SPI_HEADER_CRC_LEN + FRAME_HEADER_LEN; + const TAIL_LEN: usize = ETH_MIN_LEN - FSC_LEN + FSC_LEN + 1; + + if frame.len() < (6 + 6 + 2) { + return Err(AdinError::PACKET_TOO_SMALL); + } + if frame.len() > (MAX_BUFF - FRAME_HEADER_LEN) { + return Err(AdinError::PACKET_TOO_BIG); + } + + // SPI HEADER + [OPTIONAL SPI CRC] + FRAME HEADER + let mut head_data = Vec::::new(); + // [OPTIONAL PAD DATA] + FCS + [OPTINAL BYTES MAKE SPI FRAME EVEN] + let mut tail_data = Vec::::new(); + + let mut spi_hdr = SpiHeader(0); + spi_hdr.set_control(true); + spi_hdr.set_write(true); + spi_hdr.set_addr(sr::TX); + + head_data + .extend_from_slice(spi_hdr.0.to_be_bytes().as_slice()) + .map_err(|_e| AdinError::PACKET_TOO_BIG)?; + + if self.crc { + // Add CRC for header data + head_data + .push(crc8(&head_data[0..2])) + .map_err(|_| AdinError::PACKET_TOO_BIG)?; + } + + // Add port number, ADIN1110 its fixed to zero/P1, but for ADIN2111 has two ports. + head_data + .extend_from_slice(u16::from(PORT_ID_BYTE).to_be_bytes().as_slice()) + .map_err(|_e| AdinError::PACKET_TOO_BIG)?; + + let mut frame_fcs = ETH_FSC::new(frame); + + // ADIN1110 MAC and PHY don´t accept ethernet packet smaller than 64 bytes. + // So padded the data minus the FCS, FCS is automatilly added to by the MAC. + if let Some(pad_len) = (ETH_MIN_LEN - FSC_LEN).checked_sub(frame.len()) { + let _ = tail_data.resize(pad_len, 0x00); + frame_fcs = frame_fcs.update(&tail_data); + } + + // Add ethernet FCS only over the ethernet packet. + // Only usefull when `CONFIG0`, `Transmit Frame Check Sequence Validation Enable` bit is enabled. + let _ = tail_data.extend_from_slice(frame_fcs.hton_bytes().as_slice()); + + // len = frame_size + optional padding + 2 bytes Frame header + let send_len_orig = frame.len() + tail_data.len() + FRAME_HEADER_LEN; + let spi_pad_len = send_len_orig.next_multiple_of(4); + let send_len = u32::try_from(send_len_orig).map_err(|_| AdinError::PACKET_TOO_BIG)?; + + // Packet read of write to the MAC packet buffer must be a multipul of 4 bytes! + if spi_pad_len != send_len_orig { + let spi_pad_len = spi_pad_len - send_len_orig; + let _ = tail_data.extend_from_slice(&[DONT_CARE_BYTE, DONT_CARE_BYTE, DONT_CARE_BYTE][..spi_pad_len]); + } + + #[cfg(feature = "defmt")] + defmt::trace!( + "TX: hdr {} [{}] {:02x}-{:02x}-{:02x} SIZE: {}", + head_data.len(), + frame.len(), + head_data.as_slice(), + frame, + tail_data.as_slice(), + send_len, + ); + + self.write_reg(sr::TX_FSIZE, send_len).await?; + + let mut transaction = [ + Operation::Write(head_data.as_slice()), + Operation::Write(frame), + Operation::Write(tail_data.as_slice()), + ]; + + self.spi.transaction(&mut transaction).await.map_err(AdinError::Spi) + } + + /// Programs the mac address in the mac filters. + /// Also set the boardcast address. + /// The chip supports 2 priority queues but current code doesn't support this mode. + pub async fn set_mac_addr(&mut self, mac: &[u8; 6]) -> AEResult<(), SPI::Error> { + let mac_high_part = u16::from_be_bytes(mac[0..2].try_into().unwrap()); + let mac_low_part = u32::from_be_bytes(mac[2..6].try_into().unwrap()); + + // program our mac address in the mac address filter + self.write_reg(sr::ADDR_FILT_UPR0, (1 << 16) | (1 << 30) | u32::from(mac_high_part)) + .await?; + self.write_reg(sr::ADDR_FILT_LWR0, mac_low_part).await?; + + self.write_reg(sr::ADDR_MSK_UPR0, u32::from(mac_high_part)).await?; + self.write_reg(sr::ADDR_MSK_LWR0, mac_low_part).await?; + + // Also program broadcast address in the mac address filter + self.write_reg(sr::ADDR_FILT_UPR1, (1 << 16) | (1 << 30) | 0xFFFF) + .await?; + self.write_reg(sr::ADDR_FILT_LWR1, 0xFFFF_FFFF).await?; + self.write_reg(sr::ADDR_MSK_UPR1, 0xFFFF).await?; + self.write_reg(sr::ADDR_MSK_LWR1, 0xFFFF_FFFF).await?; + + Ok(()) + } +} + +impl mdio::MdioBus for ADIN1110 { + type Error = AdinError; + + /// Read from the PHY Registers as Clause 22. + async fn read_cl22(&mut self, phy_id: u8, reg: u8) -> Result { + let mdio_acc_val: u32 = + (0x1 << 28) | u32::from(phy_id & 0x1F) << 21 | u32::from(reg & 0x1F) << 16 | (0x3 << 26); + + // Result is in the lower half of the answer. + #[allow(clippy::cast_possible_truncation)] + self.write_mdio_acc_reg(mdio_acc_val).await.map(|val| val as u16) + } + + /// Read from the PHY Registers as Clause 45. + async fn read_cl45(&mut self, phy_id: u8, regc45: (u8, u16)) -> Result { + let mdio_acc_val = u32::from(phy_id & 0x1F) << 21 | u32::from(regc45.0 & 0x1F) << 16 | u32::from(regc45.1); + + self.write_mdio_acc_reg(mdio_acc_val).await?; + + let mdio_acc_val = u32::from(phy_id & 0x1F) << 21 | u32::from(regc45.0 & 0x1F) << 16 | (0x03 << 26); + + // Result is in the lower half of the answer. + #[allow(clippy::cast_possible_truncation)] + self.write_mdio_acc_reg(mdio_acc_val).await.map(|val| val as u16) + } + + /// Write to the PHY Registers as Clause 22. + async fn write_cl22(&mut self, phy_id: u8, reg: u8, val: u16) -> Result<(), Self::Error> { + let mdio_acc_val: u32 = + (0x1 << 28) | u32::from(phy_id & 0x1F) << 21 | u32::from(reg & 0x1F) << 16 | (0x1 << 26) | u32::from(val); + + self.write_mdio_acc_reg(mdio_acc_val).await.map(|_| ()) + } + + /// Write to the PHY Registers as Clause 45. + async fn write_cl45(&mut self, phy_id: u8, regc45: (u8, u16), value: u16) -> AEResult<(), SPI::Error> { + let phy_id = u32::from(phy_id & 0x1F) << 21; + let dev_addr = u32::from(regc45.0 & 0x1F) << 16; + let reg = u32::from(regc45.1); + + let mdio_acc_val: u32 = phy_id | dev_addr | reg; + self.write_mdio_acc_reg(mdio_acc_val).await?; + + let mdio_acc_val: u32 = phy_id | dev_addr | (0x01 << 26) | u32::from(value); + self.write_mdio_acc_reg(mdio_acc_val).await.map(|_| ()) + } +} + +/// Background runner for the ADIN110. +/// +/// You must call `.run()` in a background task for the ADIN1100 to operate. +pub struct Runner<'d, SPI, INT, RST> { + mac: ADIN1110, + ch: ch::Runner<'d, MTU>, + int: INT, + is_link_up: bool, + _reset: RST, +} + +impl<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, SPI, INT, RST> { + #[allow(clippy::too_many_lines)] + pub async fn run(mut self) -> ! { + loop { + let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); + + loop { + #[cfg(feature = "defmt")] + defmt::debug!("Waiting for interrupts"); + match select(self.int.wait_for_low(), tx_chan.tx_buf()).await { + Either::First(_) => { + let mut status1_clr = Status1(0); + let mut status1 = Status1(self.mac.read_reg(sr::STATUS1).await.unwrap()); + + while status1.p1_rx_rdy() { + #[cfg(feature = "defmt")] + defmt::debug!("alloc RX packet buffer"); + match select(rx_chan.rx_buf(), tx_chan.tx_buf()).await { + // Handle frames that needs to transmit from the wire. + // Note: rx_chan.rx_buf() channel don´t accept new request + // when the tx_chan is full. So these will be handled + // automaticly. + Either::First(frame) => match self.mac.read_fifo(frame).await { + Ok(n) => { + rx_chan.rx_done(n); + } + Err(e) => match e { + AdinError::PACKET_TOO_BIG => { + #[cfg(feature = "defmt")] + defmt::error!("RX Packet to big, DROP"); + self.mac.write_reg(sr::FIFO_CLR, 1).await.unwrap(); + } + AdinError::Spi(_) => { + #[cfg(feature = "defmt")] + defmt::error!("RX Spi error") + } + _ => { + #[cfg(feature = "defmt")] + defmt::error!("RX Error") + } + }, + }, + Either::Second(frame) => { + // Handle frames that needs to transmit to the wire. + self.mac.write_fifo(frame).await.unwrap(); + tx_chan.tx_done(); + } + } + status1 = Status1(self.mac.read_reg(sr::STATUS1).await.unwrap()); + } + + let status0 = Status0(self.mac.read_reg(sr::STATUS0).await.unwrap()); + if status1.0 & !0x1b != 0 { + #[cfg(feature = "defmt")] + defmt::error!("SPE CHIP STATUS 0:{:08x} 1:{:08x}", status0.0, status1.0); + } + + if status1.tx_rdy() { + status1_clr.set_tx_rdy(true); + #[cfg(feature = "defmt")] + defmt::info!("TX_DONE"); + } + + if status1.link_change() { + let link = status1.p1_link_status(); + self.is_link_up = link; + + #[cfg(feature = "defmt")] + if link { + let link_status = self + .mac + .read_cl45(MDIO_PHY_ADDR, RegsC45::DA7::AN_STATUS_EXTRA.into()) + .await + .unwrap(); + + let volt = if link_status & (0b11 << 5) == (0b11 << 5) { + "2.4" + } else { + "1.0" + }; + + let mse = self + .mac + .read_cl45(MDIO_PHY_ADDR, RegsC45::DA1::MSE_VAL.into()) + .await + .unwrap(); + + defmt::info!("LINK Changed: Link Up, Volt: {} V p-p, MSE: {:0004}", volt, mse); + } else { + defmt::info!("LINK Changed: Link Down"); + } + + state_chan.set_link_state(if link { LinkState::Up } else { LinkState::Down }); + status1_clr.set_link_change(true); + } + + if status1.tx_ecc_err() { + #[cfg(feature = "defmt")] + defmt::error!("SPI TX_ECC_ERR error, CLEAR TX FIFO"); + self.mac.write_reg(sr::FIFO_CLR, 2).await.unwrap(); + status1_clr.set_tx_ecc_err(true); + } + + if status1.rx_ecc_err() { + #[cfg(feature = "defmt")] + defmt::error!("SPI RX_ECC_ERR error"); + status1_clr.set_rx_ecc_err(true); + } + + if status1.spi_err() { + #[cfg(feature = "defmt")] + defmt::error!("SPI SPI_ERR CRC error"); + status1_clr.set_spi_err(true); + } + + if status0.phyint() { + #[cfg_attr(not(feature = "defmt"), allow(unused_variables))] + let crsm_irq_st = self + .mac + .read_cl45(MDIO_PHY_ADDR, RegsC45::DA1E::CRSM_IRQ_STATUS.into()) + .await + .unwrap(); + + #[cfg_attr(not(feature = "defmt"), allow(unused_variables))] + let phy_irq_st = self + .mac + .read_cl45(MDIO_PHY_ADDR, RegsC45::DA1F::PHY_SYBSYS_IRQ_STATUS.into()) + .await + .unwrap(); + + #[cfg(feature = "defmt")] + defmt::warn!( + "SPE CHIP PHY CRSM_IRQ_STATUS {:04x} PHY_SUBSYS_IRQ_STATUS {:04x}", + crsm_irq_st, + phy_irq_st + ); + } + + if status0.txfcse() { + #[cfg(feature = "defmt")] + defmt::error!("SPE CHIP PHY TX Frame CRC error"); + } + + // Clear status0 + self.mac.write_reg(sr::STATUS0, 0xFFF).await.unwrap(); + self.mac.write_reg(sr::STATUS1, status1_clr.0).await.unwrap(); + } + Either::Second(packet) => { + // Handle frames that needs to transmit to the wire. + self.mac.write_fifo(packet).await.unwrap(); + tx_chan.tx_done(); + } + } + } + } + } +} + +/// Obtain a driver for using the ADIN1110 with [`embassy-net`](crates.io/crates/embassy-net). +pub async fn new( + mac_addr: [u8; 6], + state: &'_ mut State, + spi_dev: SPI, + int: INT, + mut reset: RST, + crc: bool, +) -> (Device<'_>, Runner<'_, SPI, INT, RST>) { + use crate::regs::{IMask0, IMask1}; + + #[cfg(feature = "defmt")] + defmt::info!("INIT ADIN1110"); + + // Reset sequence + reset.set_low().unwrap(); + + // Wait t1: 20-43mS + Timer::after(Duration::from_millis(30)).await; + + reset.set_high().unwrap(); + + // Wait t3: 50mS + Timer::after(Duration::from_millis(50)).await; + + // Create device + let mut mac = ADIN1110::new(spi_dev, crc); + + // Check PHYID + let id = mac.read_reg(sr::PHYID).await.unwrap(); + assert_eq!(id, PHYID); + + #[cfg(feature = "defmt")] + defmt::debug!("SPE: CHIP MAC/ID: {:08x}", id); + + #[cfg(feature = "defmt")] + let adin_phy = Phy10BaseT1x::default(); + #[cfg(feature = "defmt")] + let phy_id = adin_phy.get_id(&mut mac).await.unwrap(); + #[cfg(feature = "defmt")] + defmt::debug!("SPE: CHIP: PHY ID: {:08x}", phy_id); + + let mi_control = mac.read_cl22(MDIO_PHY_ADDR, RegsC22::CONTROL as u8).await.unwrap(); + #[cfg(feature = "defmt")] + defmt::println!("SPE CHIP PHY MI_CONTROL {:04x}", mi_control); + if mi_control & 0x0800 != 0 { + let val = mi_control & !0x0800; + #[cfg(feature = "defmt")] + defmt::println!("SPE CHIP PHY MI_CONTROL Disable PowerDown"); + mac.write_cl22(MDIO_PHY_ADDR, RegsC22::CONTROL as u8, val) + .await + .unwrap(); + } + + // Config2: CRC_APPEND + let mut config2 = Config2(0x0000_0800); + config2.set_crc_append(true); + mac.write_reg(sr::CONFIG2, config2.0).await.unwrap(); + + // Pin Mux Config 1 + let led_val = (0b11 << 6) | (0b11 << 4); // | (0b00 << 1); + mac.write_cl45(MDIO_PHY_ADDR, RegsC45::DA1E::DIGIO_PINMUX.into(), led_val) + .await + .unwrap(); + + let mut led_pol = LedPolarity(0); + led_pol.set_led1_polarity(LedPol::ActiveLow); + led_pol.set_led0_polarity(LedPol::ActiveLow); + + // Led Polarity Regisgere Active Low + mac.write_cl45(MDIO_PHY_ADDR, RegsC45::DA1E::LED_POLARITY.into(), led_pol.0) + .await + .unwrap(); + + // Led Both On + let mut led_cntr = LedCntrl(0x0); + + // LED1: Yellow + led_cntr.set_led1_en(true); + led_cntr.set_led1_function(LedFunc::TxLevel2P4); + // LED0: Green + led_cntr.set_led0_en(true); + led_cntr.set_led0_function(LedFunc::LinkupTxRxActicity); + + mac.write_cl45(MDIO_PHY_ADDR, RegsC45::DA1E::LED_CNTRL.into(), led_cntr.0) + .await + .unwrap(); + + // Set ADIN1110 Interrupts, RX_READY and LINK_CHANGE + // Enable interrupts LINK_CHANGE, TX_RDY, RX_RDY(P1), SPI_ERR + // Have to clear the mask the enable it. + let mut imask0_val = IMask0(0x0000_1FBF); + imask0_val.set_txfcsem(false); + imask0_val.set_phyintm(false); + imask0_val.set_txboem(false); + imask0_val.set_rxboem(false); + imask0_val.set_txpem(false); + + mac.write_reg(sr::IMASK0, imask0_val.0).await.unwrap(); + + // Set ADIN1110 Interrupts, RX_READY and LINK_CHANGE + // Enable interrupts LINK_CHANGE, TX_RDY, RX_RDY(P1), SPI_ERR + // Have to clear the mask the enable it. + let mut imask1_val = IMask1(0x43FA_1F1A); + imask1_val.set_link_change_mask(false); + imask1_val.set_p1_rx_rdy_mask(false); + imask1_val.set_spi_err_mask(false); + imask1_val.set_tx_ecc_err_mask(false); + imask1_val.set_rx_ecc_err_mask(false); + + mac.write_reg(sr::IMASK1, imask1_val.0).await.unwrap(); + + // Program mac address but also sets mac filters. + mac.set_mac_addr(&mac_addr).await.unwrap(); + + let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ethernet(mac_addr)); + ( + device, + Runner { + ch: runner, + mac, + int, + is_link_up: false, + _reset: reset, + }, + ) +} + +#[allow(clippy::similar_names)] +#[cfg(test)] +mod tests { + use core::convert::Infallible; + + use embedded_hal_1::digital::{ErrorType, OutputPin}; + use embedded_hal_async::delay::DelayUs; + use embedded_hal_bus::spi::ExclusiveDevice; + use embedded_hal_mock::eh1::spi::{Mock as SpiMock, Transaction as SpiTransaction}; + + #[derive(Debug, Default)] + struct CsPinMock { + pub high: u32, + pub low: u32, + } + impl OutputPin for CsPinMock { + fn set_low(&mut self) -> Result<(), Self::Error> { + self.low += 1; + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + self.high += 1; + Ok(()) + } + } + impl ErrorType for CsPinMock { + type Error = Infallible; + } + + use super::*; + + // TODO: This is currently a workaround unit `ExclusiveDevice` is moved to `embedded-hal-bus` + // see https://github.com/rust-embedded/embedded-hal/pull/462#issuecomment-1560014426 + struct MockDelay {} + + impl DelayUs for MockDelay { + async fn delay_us(&mut self, _us: u32) { + todo!() + } + + async fn delay_ms(&mut self, _ms: u32) { + todo!() + } + } + + #[futures_test::test] + async fn mac_read_registers_without_crc() { + // Configure expectations + let expectations = [ + // 1st + SpiTransaction::write_vec(vec![0x80, 0x01, TURN_AROUND_BYTE]), + SpiTransaction::read_vec(vec![0x02, 0x83, 0xBC, 0x91]), + SpiTransaction::flush(), + // 2nd + SpiTransaction::write_vec(vec![0x80, 0x02, TURN_AROUND_BYTE]), + SpiTransaction::read_vec(vec![0x00, 0x00, 0x06, 0xC3]), + SpiTransaction::flush(), + ]; + let mut spi = SpiMock::new(&expectations); + + let cs = CsPinMock::default(); + let delay = MockDelay {}; + let spi_dev = ExclusiveDevice::new(spi.clone(), cs, delay); + let mut spe = ADIN1110::new(spi_dev, false); + + // Read PHIID + let val = spe.read_reg(sr::PHYID).await.expect("Error"); + assert_eq!(val, 0x0283_BC91); + + // Read CAPAVILITY + let val = spe.read_reg(sr::CAPABILITY).await.expect("Error"); + assert_eq!(val, 0x0000_06C3); + + spi.done(); + } + + #[futures_test::test] + async fn mac_read_registers_with_crc() { + // Configure expectations + let expectations = [ + // 1st + SpiTransaction::write_vec(vec![0x80, 0x01, 177, TURN_AROUND_BYTE]), + SpiTransaction::read_vec(vec![0x02, 0x83, 0xBC, 0x91, 215]), + SpiTransaction::flush(), + // 2nd + SpiTransaction::write_vec(vec![0x80, 0x02, 184, TURN_AROUND_BYTE]), + SpiTransaction::read_vec(vec![0x00, 0x00, 0x06, 0xC3, 57]), + SpiTransaction::flush(), + ]; + let mut spi = SpiMock::new(&expectations); + + let cs = CsPinMock::default(); + let delay = MockDelay {}; + let spi_dev = ExclusiveDevice::new(spi.clone(), cs, delay); + + let mut spe = ADIN1110::new(spi_dev, true); + + assert_eq!(crc8(0x0283_BC91_u32.to_be_bytes().as_slice()), 215); + assert_eq!(crc8(0x0000_06C3_u32.to_be_bytes().as_slice()), 57); + + // Read PHIID + let val = spe.read_reg(sr::PHYID).await.expect("Error"); + assert_eq!(val, 0x0283_BC91); + + // Read CAPAVILITY + let val = spe.read_reg(sr::CAPABILITY).await.expect("Error"); + assert_eq!(val, 0x0000_06C3); + + spi.done(); + } + + #[futures_test::test] + async fn mac_write_registers_without_crc() { + // Configure expectations + let expectations = [ + SpiTransaction::write_vec(vec![0xA0, 0x09, 0x12, 0x34, 0x56, 0x78]), + SpiTransaction::flush(), + ]; + let mut spi = SpiMock::new(&expectations); + + let cs = CsPinMock::default(); + let delay = MockDelay {}; + let spi_dev = ExclusiveDevice::new(spi.clone(), cs, delay); + + let mut spe = ADIN1110::new(spi_dev, false); + + // Write reg: 0x1FFF + assert!(spe.write_reg(sr::STATUS1, 0x1234_5678).await.is_ok()); + + spi.done(); + } + + #[futures_test::test] + async fn mac_write_registers_with_crc() { + // Configure expectations + let expectations = [ + SpiTransaction::write_vec(vec![0xA0, 0x09, 39, 0x12, 0x34, 0x56, 0x78, 28]), + SpiTransaction::flush(), + ]; + + // Basic test init block + let mut spi = SpiMock::new(&expectations); + let cs = CsPinMock::default(); + let delay = MockDelay {}; + let spi_dev = ExclusiveDevice::new(spi.clone(), cs, delay); + let mut spe = ADIN1110::new(spi_dev, true); + + // Write reg: 0x1FFF + assert!(spe.write_reg(sr::STATUS1, 0x1234_5678).await.is_ok()); + + spi.done(); + } + + #[futures_test::test] + async fn write_packet_to_fifo_minimal_with_crc() { + // Configure expectations + let mut expectations = vec![]; + + // Write TX_SIZE reg + expectations.push(SpiTransaction::write_vec(vec![160, 48, 136, 0, 0, 0, 66, 201])); + expectations.push(SpiTransaction::flush()); + + // Write TX reg. + // SPI Header + optional CRC + Frame Header + expectations.push(SpiTransaction::write_vec(vec![160, 49, 143, 0, 0])); + // Packet data + let packet = [0xFF_u8; 60]; + expectations.push(SpiTransaction::write_vec(packet.to_vec())); + + let mut tail = std::vec::Vec::::with_capacity(100); + // Padding + if let Some(padding_len) = (ETH_MIN_LEN - FSC_LEN).checked_sub(packet.len()) { + tail.resize(padding_len, 0x00); + } + // Packet FCS + optinal padding + tail.extend_from_slice(&[77, 241, 140, 244, DONT_CARE_BYTE, DONT_CARE_BYTE]); + + expectations.push(SpiTransaction::write_vec(tail)); + expectations.push(SpiTransaction::flush()); + + let mut spi = SpiMock::new(&expectations); + + let cs = CsPinMock::default(); + let delay = MockDelay {}; + let spi_dev = ExclusiveDevice::new(spi.clone(), cs, delay); + + let mut spe = ADIN1110::new(spi_dev, true); + + assert!(spe.write_fifo(&packet).await.is_ok()); + + spi.done(); + } + + #[futures_test::test] + async fn write_packet_to_fifo_max_mtu_with_crc() { + assert_eq!(MTU, 1514); + // Configure expectations + let mut expectations = vec![]; + + // Write TX_SIZE reg + expectations.push(SpiTransaction::write_vec(vec![160, 48, 136, 0, 0, 5, 240, 159])); + expectations.push(SpiTransaction::flush()); + + // Write TX reg. + // SPI Header + optional CRC + Frame Header + expectations.push(SpiTransaction::write_vec(vec![160, 49, 143, 0, 0])); + // Packet data + let packet = [0xAA_u8; MTU]; + expectations.push(SpiTransaction::write_vec(packet.to_vec())); + + let mut tail = std::vec::Vec::::with_capacity(100); + // Padding + if let Some(padding_len) = (ETH_MIN_LEN - FSC_LEN).checked_sub(packet.len()) { + tail.resize(padding_len, 0x00); + } + // Packet FCS + optinal padding + tail.extend_from_slice(&[49, 196, 205, 160]); + + expectations.push(SpiTransaction::write_vec(tail)); + expectations.push(SpiTransaction::flush()); + + let mut spi = SpiMock::new(&expectations); + + let cs = CsPinMock::default(); + let delay = MockDelay {}; + let spi_dev = ExclusiveDevice::new(spi.clone(), cs, delay); + + let mut spe = ADIN1110::new(spi_dev, true); + + assert!(spe.write_fifo(&packet).await.is_ok()); + + spi.done(); + } + + #[futures_test::test] + async fn write_packet_to_fifo_invalid_lengths() { + assert_eq!(MTU, 1514); + + // Configure expectations + let expectations = vec![]; + + // Max packet size = MAX_BUFF - FRAME_HEADER_LEN + let packet = [0xAA_u8; MAX_BUFF - FRAME_HEADER_LEN + 1]; + + let mut spi = SpiMock::new(&expectations); + + let cs = CsPinMock::default(); + let delay = MockDelay {}; + let spi_dev = ExclusiveDevice::new(spi.clone(), cs, delay); + + let mut spe = ADIN1110::new(spi_dev, true); + + // minimal + assert!(matches!( + spe.write_fifo(&packet[0..(6 + 6 + 2 - 1)]).await, + Err(AdinError::PACKET_TOO_SMALL) + )); + + // max + 1 + assert!(matches!(spe.write_fifo(&packet).await, Err(AdinError::PACKET_TOO_BIG))); + + spi.done(); + } + + #[futures_test::test] + async fn write_packet_to_fifo_arp_46bytes_with_crc() { + // Configure expectations + let mut expectations = vec![]; + + // Write TX_SIZE reg + expectations.push(SpiTransaction::write_vec(vec![160, 48, 136, 0, 0, 0, 66, 201])); + expectations.push(SpiTransaction::flush()); + + // Write TX reg. + // Header + expectations.push(SpiTransaction::write_vec(vec![160, 49, 143, 0, 0])); + // Packet data + let packet = [ + 34, 51, 68, 85, 102, 119, 18, 52, 86, 120, 154, 188, 8, 6, 0, 1, 8, 0, 6, 4, 0, 2, 18, 52, 86, 120, 154, + 188, 192, 168, 16, 4, 34, 51, 68, 85, 102, 119, 192, 168, 16, 1, + ]; + expectations.push(SpiTransaction::write_vec(packet.to_vec())); + + let mut tail = std::vec::Vec::::with_capacity(100); + // Padding + if let Some(padding_len) = (ETH_MIN_LEN - FSC_LEN).checked_sub(packet.len()) { + tail.resize(padding_len, 0x00); + } + // Packet FCS + optinal padding + tail.extend_from_slice(&[147, 149, 213, 68, DONT_CARE_BYTE, DONT_CARE_BYTE]); + + expectations.push(SpiTransaction::write_vec(tail)); + expectations.push(SpiTransaction::flush()); + + let mut spi = SpiMock::new(&expectations); + + let cs = CsPinMock::default(); + let delay = MockDelay {}; + let spi_dev = ExclusiveDevice::new(spi.clone(), cs, delay); + + let mut spe = ADIN1110::new(spi_dev, true); + + assert!(spe.write_fifo(&packet).await.is_ok()); + + spi.done(); + } + + #[futures_test::test] + async fn write_packet_to_fifo_arp_46bytes_without_crc() { + // Configure expectations + let mut expectations = vec![]; + + // Write TX_SIZE reg + expectations.push(SpiTransaction::write_vec(vec![160, 48, 0, 0, 0, 66])); + expectations.push(SpiTransaction::flush()); + + // Write TX reg. + // SPI Header + Frame Header + expectations.push(SpiTransaction::write_vec(vec![160, 49, 0, 0])); + // Packet data + let packet = [ + 34, 51, 68, 85, 102, 119, 18, 52, 86, 120, 154, 188, 8, 6, 0, 1, 8, 0, 6, 4, 0, 2, 18, 52, 86, 120, 154, + 188, 192, 168, 16, 4, 34, 51, 68, 85, 102, 119, 192, 168, 16, 1, + ]; + expectations.push(SpiTransaction::write_vec(packet.to_vec())); + + let mut tail = std::vec::Vec::::with_capacity(100); + // Padding + if let Some(padding_len) = (ETH_MIN_LEN - FSC_LEN).checked_sub(packet.len()) { + tail.resize(padding_len, 0x00); + } + // Packet FCS + optinal padding + tail.extend_from_slice(&[147, 149, 213, 68, DONT_CARE_BYTE, DONT_CARE_BYTE]); + + expectations.push(SpiTransaction::write_vec(tail)); + expectations.push(SpiTransaction::flush()); + + let mut spi = SpiMock::new(&expectations); + + let cs = CsPinMock::default(); + let delay = MockDelay {}; + let spi_dev = ExclusiveDevice::new(spi.clone(), cs, delay); + + let mut spe = ADIN1110::new(spi_dev, false); + + assert!(spe.write_fifo(&packet).await.is_ok()); + + spi.done(); + } +} diff --git a/embassy-net-adin1110/src/mdio.rs b/embassy-net-adin1110/src/mdio.rs new file mode 100644 index 00000000..68477006 --- /dev/null +++ b/embassy-net-adin1110/src/mdio.rs @@ -0,0 +1,175 @@ +/// PHY Address: (0..=0x1F), 5-bits long. +#[allow(dead_code)] +type PhyAddr = u8; + +/// PHY Register: (0..=0x1F), 5-bits long. +#[allow(dead_code)] +type RegC22 = u8; + +/// PHY Register Clause 45. +#[allow(dead_code)] +type RegC45 = u16; + +/// PHY Register Value +#[allow(dead_code)] +type RegVal = u16; + +#[allow(dead_code)] +const REG13: RegC22 = 13; +#[allow(dead_code)] +const REG14: RegC22 = 14; + +#[allow(dead_code)] +const PHYADDR_MASK: u8 = 0x1f; +#[allow(dead_code)] +const DEV_MASK: u8 = 0x1f; + +#[allow(dead_code)] +#[repr(u16)] +enum Reg13Op { + Addr = 0b00 << 14, + Write = 0b01 << 14, + PostReadIncAddr = 0b10 << 14, + Read = 0b11 << 14, +} +/// `MdioBus` trait +/// Driver needs to implement the Clause 22 +/// Optional Clause 45 is the device supports this. +/// +/// Claus 45 methodes are bases on +pub trait MdioBus { + type Error; + + /// Read, Clause 22 + async fn read_cl22(&mut self, phy_id: PhyAddr, reg: RegC22) -> Result; + + /// Write, Clause 22 + async fn write_cl22(&mut self, phy_id: PhyAddr, reg: RegC22, reg_val: RegVal) -> Result<(), Self::Error>; + + /// Read, Clause 45 + /// This is the default implementation. + /// Many hardware these days support direct Clause 45 operations. + /// Implement this function when your hardware supports it. + async fn read_cl45(&mut self, phy_id: PhyAddr, regc45: (u8, RegC45)) -> Result { + // Write FN + let val = (Reg13Op::Addr as RegVal) | RegVal::from(regc45.0 & DEV_MASK); + + self.write_cl22(phy_id, REG13, val).await?; + // Write Addr + self.write_cl22(phy_id, REG14, regc45.1).await?; + + // Write FN + let val = (Reg13Op::Read as RegVal) | RegVal::from(regc45.0 & DEV_MASK); + self.write_cl22(phy_id, REG13, val).await?; + // Write Addr + self.read_cl22(phy_id, REG14).await + } + + /// Write, Clause 45 + /// This is the default implementation. + /// Many hardware these days support direct Clause 45 operations. + /// Implement this function when your hardware supports it. + async fn write_cl45(&mut self, phy_id: PhyAddr, regc45: (u8, RegC45), reg_val: RegVal) -> Result<(), Self::Error> { + let dev_addr = RegVal::from(regc45.0 & DEV_MASK); + let reg = regc45.1; + + // Write FN + let val = (Reg13Op::Addr as RegVal) | dev_addr; + self.write_cl22(phy_id, REG13, val).await?; + // Write Addr + self.write_cl22(phy_id, REG14, reg).await?; + + // Write FN + let val = (Reg13Op::Write as RegVal) | dev_addr; + self.write_cl22(phy_id, REG13, val).await?; + // Write Addr + self.write_cl22(phy_id, REG14, reg_val).await + } +} + +// #[cfg(test)] +// mod tests { +// use core::convert::Infallible; + +// use super::{MdioBus, PhyAddr, RegC22, RegVal}; + +// #[derive(Debug, PartialEq, Eq)] +// enum A { +// Read(PhyAddr, RegC22), +// Write(PhyAddr, RegC22, RegVal), +// } + +// struct MockMdioBus(Vec); + +// impl MockMdioBus { +// pub fn clear(&mut self) { +// self.0.clear(); +// } +// } + +// impl MdioBus for MockMdioBus { +// type Error = Infallible; + +// fn write_cl22( +// &mut self, +// phy_id: super::PhyAddr, +// reg: super::RegC22, +// reg_val: super::RegVal, +// ) -> Result<(), Self::Error> { +// self.0.push(A::Write(phy_id, reg, reg_val)); +// Ok(()) +// } + +// fn read_cl22( +// &mut self, +// phy_id: super::PhyAddr, +// reg: super::RegC22, +// ) -> Result { +// self.0.push(A::Read(phy_id, reg)); +// Ok(0) +// } +// } + +// #[test] +// fn read_test() { +// let mut mdiobus = MockMdioBus(Vec::with_capacity(20)); + +// mdiobus.clear(); +// mdiobus.read_cl22(0x01, 0x00).unwrap(); +// assert_eq!(mdiobus.0, vec![A::Read(0x01, 0x00)]); + +// mdiobus.clear(); +// mdiobus.read_cl45(0x01, (0xBB, 0x1234)).unwrap(); +// assert_eq!( +// mdiobus.0, +// vec![ +// #[allow(clippy::identity_op)] +// A::Write(0x01, 13, (0b00 << 14) | 27), +// A::Write(0x01, 14, 0x1234), +// A::Write(0x01, 13, (0b11 << 14) | 27), +// A::Read(0x01, 14) +// ] +// ); +// } + +// #[test] +// fn write_test() { +// let mut mdiobus = MockMdioBus(Vec::with_capacity(20)); + +// mdiobus.clear(); +// mdiobus.write_cl22(0x01, 0x00, 0xABCD).unwrap(); +// assert_eq!(mdiobus.0, vec![A::Write(0x01, 0x00, 0xABCD)]); + +// mdiobus.clear(); +// mdiobus.write_cl45(0x01, (0xBB, 0x1234), 0xABCD).unwrap(); +// assert_eq!( +// mdiobus.0, +// vec![ +// A::Write(0x01, 13, 27), +// A::Write(0x01, 14, 0x1234), +// A::Write(0x01, 13, (0b01 << 14) | 27), +// A::Write(0x01, 14, 0xABCD) +// ] +// ); +// } +// } diff --git a/embassy-net-adin1110/src/phy.rs b/embassy-net-adin1110/src/phy.rs new file mode 100644 index 00000000..176ad019 --- /dev/null +++ b/embassy-net-adin1110/src/phy.rs @@ -0,0 +1,142 @@ +use crate::mdio::MdioBus; + +#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)] +#[repr(u8)] +/// Clause 22 Registers +pub enum RegsC22 { + /// MII Control Register + CONTROL = 0x00, + /// MII Status Register + STATUS = 0x01, + /// PHY Identifier 1 Register + PHY_ID1 = 0x02, + /// PHY Identifier 2 Register. + PHY_ID2 = 0x03, +} + +/// Clause 45 Registers +#[allow(non_snake_case, dead_code)] +pub mod RegsC45 { + /// Device Address: 0x01 + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[repr(u16)] + pub enum DA1 { + /// PMA/PMD Control 1 Register + PMA_PMD_CNTRL1 = 0x0000, + /// PMA/PMD Status 1 Register + PMA_PMD_STAT1 = 0x0001, + /// MSE Value Register + MSE_VAL = 0x830B, + } + + impl DA1 { + #[must_use] + pub fn into(self) -> (u8, u16) { + (0x01, self as u16) + } + } + + /// Device Address: 0x03 + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[repr(u16)] + pub enum DA3 { + /// PCS Control 1 Register + PCS_CNTRL1 = 0x0000, + /// PCS Status 1 Register + PCS_STAT1 = 0x0001, + /// PCS Status 2 Register + PCS_STAT2 = 0x0008, + } + + impl DA3 { + #[must_use] + pub fn into(self) -> (u8, u16) { + (0x03, self as u16) + } + } + + /// Device Address: 0x07 + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[repr(u16)] + pub enum DA7 { + /// Extra Autonegotiation Status Register + AN_STATUS_EXTRA = 0x8001, + } + + impl DA7 { + #[must_use] + pub fn into(self) -> (u8, u16) { + (0x07, self as u16) + } + } + + /// Device Address: 0x1E + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[repr(u16)] + pub enum DA1E { + /// System Interrupt Status Register + CRSM_IRQ_STATUS = 0x0010, + /// System Interrupt Mask Register + CRSM_IRQ_MASK = 0x0020, + /// Pin Mux Configuration 1 Register + DIGIO_PINMUX = 0x8c56, + /// LED Control Register. + LED_CNTRL = 0x8C82, + /// LED Polarity Register + LED_POLARITY = 0x8C83, + } + + impl DA1E { + #[must_use] + pub fn into(self) -> (u8, u16) { + (0x1e, self as u16) + } + } + + /// Device Address: 0x1F + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[repr(u16)] + pub enum DA1F { + /// PHY Subsystem Interrupt Status Register + PHY_SYBSYS_IRQ_STATUS = 0x0011, + /// PHY Subsystem Interrupt Mask Register + PHY_SYBSYS_IRQ_MASK = 0x0021, + } + + impl DA1F { + #[must_use] + pub fn into(self) -> (u8, u16) { + (0x1f, self as u16) + } + } +} + +pub struct Phy10BaseT1x(u8); + +impl Default for Phy10BaseT1x { + fn default() -> Self { + Self(0x01) + } +} + +impl Phy10BaseT1x { + /// Get the both parts of the PHYID. + pub async fn get_id(&self, mdiobus: &mut MDIOBUS) -> Result + where + MDIOBUS: MdioBus, + MDE: core::fmt::Debug, + { + let mut phyid = u32::from(mdiobus.read_cl22(self.0, RegsC22::PHY_ID1 as u8).await?) << 16; + phyid |= u32::from(mdiobus.read_cl22(self.0, RegsC22::PHY_ID2 as u8).await?); + Ok(phyid) + } + + /// Get the Mean Squared Error Value. + pub async fn get_sqi(&self, mdiobus: &mut MDIOBUS) -> Result + where + MDIOBUS: MdioBus, + MDE: core::fmt::Debug, + { + mdiobus.read_cl45(self.0, RegsC45::DA1::MSE_VAL.into()).await + } +} diff --git a/embassy-net-adin1110/src/regs.rs b/embassy-net-adin1110/src/regs.rs new file mode 100644 index 00000000..4557929f --- /dev/null +++ b/embassy-net-adin1110/src/regs.rs @@ -0,0 +1,408 @@ +use bitfield::{bitfield, bitfield_bitrange, bitfield_fields}; + +#[allow(non_camel_case_types)] +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u16)] +/// SPI REGISTER DETAILS +/// Table 38. +pub enum SpiRegisters { + IDVER = 0x00, + PHYID = 0x01, + CAPABILITY = 0x02, + RESET = 0x03, + CONFIG0 = 0x04, + CONFIG2 = 0x06, + STATUS0 = 0x08, + STATUS1 = 0x09, + IMASK0 = 0x0C, + IMASK1 = 0x0D, + MDIO_ACC = 0x20, + TX_FSIZE = 0x30, + TX = 0x31, + TX_SPACE = 0x32, + FIFO_CLR = 0x36, + ADDR_FILT_UPR0 = 0x50, + ADDR_FILT_LWR0 = 0x51, + ADDR_FILT_UPR1 = 0x52, + ADDR_FILT_LWR1 = 0x53, + ADDR_MSK_LWR0 = 0x70, + ADDR_MSK_UPR0 = 0x71, + ADDR_MSK_LWR1 = 0x72, + ADDR_MSK_UPR1 = 0x73, + RX_FSIZE = 0x90, + RX = 0x91, +} + +impl From for u16 { + fn from(val: SpiRegisters) -> Self { + val as u16 + } +} + +impl From for SpiRegisters { + fn from(value: u16) -> Self { + match value { + 0x00 => Self::IDVER, + 0x01 => Self::PHYID, + 0x02 => Self::CAPABILITY, + 0x03 => Self::RESET, + 0x04 => Self::CONFIG0, + 0x06 => Self::CONFIG2, + 0x08 => Self::STATUS0, + 0x09 => Self::STATUS1, + 0x0C => Self::IMASK0, + 0x0D => Self::IMASK1, + 0x20 => Self::MDIO_ACC, + 0x30 => Self::TX_FSIZE, + 0x31 => Self::TX, + 0x32 => Self::TX_SPACE, + 0x36 => Self::FIFO_CLR, + 0x50 => Self::ADDR_FILT_UPR0, + 0x51 => Self::ADDR_FILT_LWR0, + 0x52 => Self::ADDR_FILT_UPR1, + 0x53 => Self::ADDR_FILT_LWR1, + 0x70 => Self::ADDR_MSK_LWR0, + 0x71 => Self::ADDR_MSK_UPR0, + 0x72 => Self::ADDR_MSK_LWR1, + 0x73 => Self::ADDR_MSK_UPR1, + 0x90 => Self::RX_FSIZE, + 0x91 => Self::RX, + e => panic!("Unknown value {e}"), + } + } +} + +// Register definitions +bitfield! { + /// Status0 Register bits + pub struct Status0(u32); + impl Debug; + u32; + /// Control Data Protection Error + pub cdpe, _ : 12; + /// Transmit Frame Check Squence Error + pub txfcse, _: 11; + /// Transmit Time Stamp Capture Available C + pub ttscac, _ : 10; + /// Transmit Time Stamp Capture Available B + pub ttscab, _ : 9; + /// Transmit Time Stamp Capture Available A + pub ttscaa, _ : 8; + /// PHY Interrupt for Port 1 + pub phyint, _ : 7; + /// Reset Complete + pub resetc, _ : 6; + /// Header error + pub hdre, _ : 5; + /// Loss of Frame Error + pub lofe, _ : 4; + /// Receiver Buffer Overflow Error + pub rxboe, _ : 3; + /// Host Tx FIFO Under Run Error + pub txbue, _ : 2; + /// Host Tx FIFO Overflow + pub txboe, _ : 1; + /// Transmit Protocol Error + pub txpe, _ : 0; +} + +bitfield! { + /// Status1 Register bits + pub struct Status1(u32); + impl Debug; + u32; + /// ECC Error on Reading the Frame Size from a Tx FIFO + pub tx_ecc_err, set_tx_ecc_err: 12; + /// ECC Error on Reading the Frame Size from an Rx FIFO + pub rx_ecc_err, set_rx_ecc_err : 11; + /// Detected an Error on an SPI Transaction + pub spi_err, set_spi_err: 10; + /// Rx MAC Interframe Gap Error + pub p1_rx_ifg_err, set_p1_rx_ifg_err : 8; + /// Port1 Rx Ready High Priority + pub p1_rx_rdy_hi, set_p1_rx_rdy_hi : 5; + /// Port 1 Rx FIFO Contains Data + pub p1_rx_rdy, set_p1_rx_rdy : 4; + /// Tx Ready + pub tx_rdy, set_tx_rdy : 3; + /// Link Status Changed + pub link_change, set_link_change : 1; + /// Port 1 Link Status + pub p1_link_status, _ : 0; +} + +bitfield! { + /// Config0 Register bits + pub struct Config0(u32); + impl Debug; + u32; + /// Configuration Synchronization + pub sync, set_sync : 15; + /// Transmit Frame Check Sequence Validation Enable + pub txfcsve, set_txfcsve : 14; + /// !CS Align Receive Frame Enable + pub csarfe, set_csarfe : 13; + /// Zero Align Receive Frame Enable + pub zarfe, set_zarfe : 12; + /// Transmit Credit Threshold + pub tcxthresh, set_tcxthresh : 11, 10; + /// Transmit Cut Through Enable + pub txcte, set_txcte : 9; + /// Receive Cut Through Enable + pub rxcte, set_rxcte : 8; + /// Frame Time Stamp Enable + pub ftse, set_ftse : 7; + /// Receive Frame Time Stamp Select + pub ftss, set_ftss : 6; + /// Enable Control Data Read Write Protection + pub prote, set_prote : 5; + /// Enable TX Data Chunk Sequence and Retry + pub seqe, set_seqe : 4; + /// Chunk Payload Selector (N). + pub cps, set_cps : 2, 0; +} + +bitfield! { + /// Config2 Register bits + pub struct Config2(u32); + impl Debug; + u32; + /// Assert TX_RDY When the Tx FIFO is Empty + pub tx_rdy_on_empty, set_tx_rdy_on_empty : 8; + /// Determines If the SFD is Detected in the PHY or MAC + pub sdf_detect_src, set_sdf_detect_src : 7; + /// Statistics Clear on Reading + pub stats_clr_on_rd, set_stats_clr_on_rd : 6; + /// Enable CRC Append + pub crc_append, set_crc_append : 5; + /// Admit Frames with IFG Errors on Port 1 (P1) + pub p1_rcv_ifg_err_frm, set_p1_rcv_ifg_err_frm : 4; + /// Forward Frames Not Matching Any MAC Address to the Host + pub p1_fwd_unk2host, set_p1_fwd_unk2host : 2; + /// SPI to MDIO Bridge MDC Clock Speed + pub mspeed, set_mspeed : 0; +} + +bitfield! { + /// IMASK0 Register bits + pub struct IMask0(u32); + impl Debug; + u32; + /// Control Data Protection Error Mask + pub cppem, set_cppem : 12; + /// Transmit Frame Check Sequence Error Mask + pub txfcsem, set_txfcsem : 11; + /// Transmit Time Stamp Capture Available C Mask + pub ttscacm, set_ttscacm : 10; + /// Transmit Time Stamp Capture Available B Mask + pub ttscabm, set_ttscabm : 9; + /// Transmit Time Stamp Capture Available A Mask + pub ttscaam, set_ttscaam : 8; + /// Physical Layer Interrupt Mask + pub phyintm, set_phyintm : 7; + /// RESET Complete Mask + pub resetcm, set_resetcm : 6; + /// Header Error Mask + pub hdrem, set_hdrem : 5; + /// Loss of Frame Error Mask + pub lofem, set_lofem : 4; + /// Receive Buffer Overflow Error Mask + pub rxboem, set_rxboem : 3; + /// Transmit Buffer Underflow Error Mask + pub txbuem, set_txbuem : 2; + /// Transmit Buffer Overflow Error Mask + pub txboem, set_txboem : 1; + /// Transmit Protocol Error Mask + pub txpem, set_txpem : 0; +} + +bitfield! { + /// IMASK1 Register bits + pub struct IMask1(u32); + impl Debug; + u32; + /// Mask Bit for TXF_ECC_ERR + pub tx_ecc_err_mask, set_tx_ecc_err_mask : 12; + /// Mask Bit for RXF_ECC_ERR + pub rx_ecc_err_mask, set_rx_ecc_err_mask : 11; + /// Mask Bit for SPI_ERR + /// This field is only used with the generic SPI protocol + pub spi_err_mask, set_spi_err_mask : 10; + /// Mask Bit for RX_IFG_ERR + pub p1_rx_ifg_err_mask, set_p1_rx_ifg_err_mask : 8; + /// Mask Bit for P1_RX_RDY + /// This field is only used with the generic SPI protocol + pub p1_rx_rdy_mask, set_p1_rx_rdy_mask : 4; + /// Mask Bit for TX_FRM_DONE + /// This field is only used with the generic SPI protocol + pub tx_rdy_mask, set_tx_rdy_mask : 3; + /// Mask Bit for LINK_CHANGE + pub link_change_mask, set_link_change_mask : 1; +} + +/// LED Functions +#[repr(u8)] +pub enum LedFunc { + LinkupTxRxActicity = 0, + LinkupTxActicity, + LinkupRxActicity, + LinkupOnly, + TxRxActivity, + TxActivity, + RxActivity, + LinkupRxEr, + LinkupRxTxEr, + RxEr, + RxTxEr, + TxSop, + RxSop, + On, + Off, + Blink, + TxLevel2P4, + TxLevel1P0, + Master, + Slave, + IncompatiableLinkCfg, + AnLinkGood, + AnComplete, + TsTimer, + LocRcvrStatus, + RemRcvrStatus, + Clk25Ref, + TxTCLK, + Clk120MHz, +} + +impl From for u8 { + fn from(val: LedFunc) -> Self { + val as u8 + } +} + +impl From for LedFunc { + fn from(value: u8) -> Self { + match value { + 0 => LedFunc::LinkupTxRxActicity, + 1 => LedFunc::LinkupTxActicity, + 2 => LedFunc::LinkupRxActicity, + 3 => LedFunc::LinkupOnly, + 4 => LedFunc::TxRxActivity, + 5 => LedFunc::TxActivity, + 6 => LedFunc::RxActivity, + 7 => LedFunc::LinkupRxEr, + 8 => LedFunc::LinkupRxTxEr, + 9 => LedFunc::RxEr, + 10 => LedFunc::RxTxEr, + 11 => LedFunc::TxSop, + 12 => LedFunc::RxSop, + 13 => LedFunc::On, + 14 => LedFunc::Off, + 15 => LedFunc::Blink, + 16 => LedFunc::TxLevel2P4, + 17 => LedFunc::TxLevel1P0, + 18 => LedFunc::Master, + 19 => LedFunc::Slave, + 20 => LedFunc::IncompatiableLinkCfg, + 21 => LedFunc::AnLinkGood, + 22 => LedFunc::AnComplete, + 23 => LedFunc::TsTimer, + 24 => LedFunc::LocRcvrStatus, + 25 => LedFunc::RemRcvrStatus, + 26 => LedFunc::Clk25Ref, + 27 => LedFunc::TxTCLK, + 28 => LedFunc::Clk120MHz, + e => panic!("Invalid value {e}"), + } + } +} + +/// LED Control Register +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct LedCntrl(pub u16); +bitfield_bitrange! {struct LedCntrl(u16)} + +impl LedCntrl { + bitfield_fields! { + u8; + /// LED 0 Pin Function + pub from into LedFunc, led0_function, set_led0_function: 4, 0; + /// LED 0 Mode Selection + pub led0_mode, set_led0_mode: 5; + /// Qualify Certain LED 0 Options with Link Status. + pub led0_link_st_qualify, set_led0_link_st_qualify: 6; + /// LED 0 Enable + pub led0_en, set_led0_en: 7; + /// LED 1 Pin Function + pub from into LedFunc, led1_function, set_led1_function: 12, 8; + /// /// LED 1 Mode Selection + pub led1_mode, set_led1_mode: 13; + /// Qualify Certain LED 1 Options with Link Status. + pub led1_link_st_qualify, set_led1_link_st_qualify: 14; + /// LED 1 Enable + pub led1_en, set_led1_en: 15; + } + + pub fn new() -> Self { + LedCntrl(0) + } +} + +// LED Polarity +#[repr(u8)] +pub enum LedPol { + AutoSense = 0, + ActiveHigh, + ActiveLow, +} + +impl From for u8 { + fn from(val: LedPol) -> Self { + val as u8 + } +} + +impl From for LedPol { + fn from(value: u8) -> Self { + match value { + 0 => LedPol::AutoSense, + 1 => LedPol::ActiveHigh, + 2 => LedPol::ActiveLow, + e => panic!("Invalid value {e}"), + } + } +} + +/// LED Control Register +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct LedPolarity(pub u16); +bitfield_bitrange! {struct LedPolarity(u16)} + +impl LedPolarity { + bitfield_fields! { + u8; + /// LED 1 Polarity + pub from into LedPol, led1_polarity, set_led1_polarity: 3, 2; + /// LED 0 Polarity + pub from into LedPol, led0_polarity, set_led0_polarity: 1, 0; + } +} + +/// SPI Header +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct SpiHeader(pub u16); +bitfield_bitrange! {struct SpiHeader(u16)} + +impl SpiHeader { + bitfield_fields! { + u16; + /// Mask Bit for TXF_ECC_ERR + pub control, set_control : 15; + pub full_duplex, set_full_duplex : 14; + /// Read or Write to register + pub write, set_write : 13; + /// Registers ID/addr + pub from into SpiRegisters, addr, set_addr: 11, 0; + } +} diff --git a/embassy-net-driver-channel/src/fmt.rs b/embassy-net-driver-channel/src/fmt.rs index 06697081..78e583c1 100644 --- a/embassy-net-driver-channel/src/fmt.rs +++ b/embassy-net-driver-channel/src/fmt.rs @@ -1,6 +1,8 @@ #![macro_use] #![allow(unused_macros)] +use core::fmt::{Debug, Display, LowerHex}; + #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); @@ -81,14 +83,17 @@ macro_rules! todo { }; } +#[cfg(not(feature = "defmt"))] macro_rules! unreachable { ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::unreachable!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::unreachable!($($x)*); - } + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) }; } @@ -223,3 +228,31 @@ impl Try for Result { self } } + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-net-driver-channel/src/lib.rs b/embassy-net-driver-channel/src/lib.rs index 076238ba..f2aa6b25 100644 --- a/embassy-net-driver-channel/src/lib.rs +++ b/embassy-net-driver-channel/src/lib.rs @@ -8,6 +8,7 @@ use core::cell::RefCell; use core::mem::MaybeUninit; use core::task::{Context, Poll}; +use driver::HardwareAddress; pub use embassy_net_driver as driver; use embassy_net_driver::{Capabilities, LinkState, Medium}; use embassy_sync::blocking_mutex::raw::NoopRawMutex; @@ -73,6 +74,18 @@ impl<'d, const MTU: usize> Runner<'d, MTU> { ) } + pub fn borrow_split(&mut self) -> (StateRunner<'_>, RxRunner<'_, MTU>, TxRunner<'_, MTU>) { + ( + StateRunner { shared: self.shared }, + RxRunner { + rx_chan: self.rx_chan.borrow(), + }, + TxRunner { + tx_chan: self.tx_chan.borrow(), + }, + ) + } + pub fn state_runner(&self) -> StateRunner<'d> { StateRunner { shared: self.shared } } @@ -218,7 +231,11 @@ pub fn new<'d, const MTU: usize, const N_RX: usize, const N_TX: usize>( ) -> (Runner<'d, MTU>, Device<'d, MTU>) { let mut caps = Capabilities::default(); caps.max_transmission_unit = MTU; - caps.medium = Medium::Ethernet; + caps.medium = match &hardware_address { + HardwareAddress::Ethernet(_) => Medium::Ethernet, + HardwareAddress::Ieee802154(_) => Medium::Ieee802154, + HardwareAddress::Ip => Medium::Ip, + }; // safety: this is a self-referential struct, however: // - it can't move while the `'d` borrow is active. diff --git a/embassy-net-enc28j60/Cargo.toml b/embassy-net-enc28j60/Cargo.toml index e02c984e..161d055c 100644 --- a/embassy-net-enc28j60/Cargo.toml +++ b/embassy-net-enc28j60/Cargo.toml @@ -11,7 +11,7 @@ edition = "2021" 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-time = { version = "0.1.3", path = "../embassy-time" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } defmt = { version = "0.3", optional = true } @@ -20,4 +20,4 @@ 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 +target = "thumbv7em-none-eabi" diff --git a/embassy-net-enc28j60/src/fmt.rs b/embassy-net-enc28j60/src/fmt.rs index 06697081..78e583c1 100644 --- a/embassy-net-enc28j60/src/fmt.rs +++ b/embassy-net-enc28j60/src/fmt.rs @@ -1,6 +1,8 @@ #![macro_use] #![allow(unused_macros)] +use core::fmt::{Debug, Display, LowerHex}; + #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); @@ -81,14 +83,17 @@ macro_rules! todo { }; } +#[cfg(not(feature = "defmt"))] macro_rules! unreachable { ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::unreachable!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::unreachable!($($x)*); - } + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) }; } @@ -223,3 +228,31 @@ impl Try for Result { self } } + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-net-esp-hosted/Cargo.toml b/embassy-net-esp-hosted/Cargo.toml index d334cf3f..67c86936 100644 --- a/embassy-net-esp-hosted/Cargo.toml +++ b/embassy-net-esp-hosted/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } -embassy-time = { version = "0.1.2", path = "../embassy-time" } +embassy-time = { version = "0.1.3", path = "../embassy-time" } 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"} @@ -23,4 +23,4 @@ heapless = "0.7.16" src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-esp-hosted-v$VERSION/embassy-net-esp-hosted/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-esp-hosted/src/" target = "thumbv7em-none-eabi" -features = ["defmt"] \ No newline at end of file +features = ["defmt"] diff --git a/embassy-net-esp-hosted/src/control.rs b/embassy-net-esp-hosted/src/control.rs index 6cd57f68..ce6636a8 100644 --- a/embassy-net-esp-hosted/src/control.rs +++ b/embassy-net-esp-hosted/src/control.rs @@ -35,9 +35,9 @@ macro_rules! ioctl { }; $self.ioctl(&mut msg).await?; let Some(proto::CtrlMsgPayload::$resp_variant($resp)) = msg.payload else { - warn!("unexpected response variant"); - return Err(Error::Internal); - }; + warn!("unexpected response variant"); + return Err(Error::Internal); + }; if $resp.resp != 0 { return Err(Error::Failed($resp.resp)); } @@ -82,7 +82,7 @@ impl<'a> Control<'a> { 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); + self.state_ch.set_link_state(LinkState::Down); Ok(()) } diff --git a/embassy-net-esp-hosted/src/fmt.rs b/embassy-net-esp-hosted/src/fmt.rs index 91984bde..78e583c1 100644 --- a/embassy-net-esp-hosted/src/fmt.rs +++ b/embassy-net-esp-hosted/src/fmt.rs @@ -93,7 +93,7 @@ macro_rules! unreachable { #[cfg(feature = "defmt")] macro_rules! unreachable { ($($x:tt)*) => { - ::defmt::unreachable!($($x)*); + ::defmt::unreachable!($($x)*) }; } @@ -229,7 +229,8 @@ impl Try for Result { } } -pub struct Bytes<'a>(pub &'a [u8]); +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); impl<'a> Debug for Bytes<'a> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { diff --git a/embassy-net-ppp/Cargo.toml b/embassy-net-ppp/Cargo.toml new file mode 100644 index 00000000..191577b5 --- /dev/null +++ b/embassy-net-ppp/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "embassy-net-ppp" +version = "0.1.0" +description = "embassy-net driver for PPP over Serial" +keywords = ["embedded", "ppp", "embassy-net", "embedded-hal-async", "ethernet", "async"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"] +license = "MIT OR Apache-2.0" +edition = "2021" + +[features] +defmt = ["dep:defmt", "ppproto/defmt"] +log = ["dep:log", "ppproto/log"] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } + +embedded-io-async = { version = "0.5.0" } +embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +ppproto = { version = "0.1.2"} +embassy-sync = { version = "0.2.0", path = "../embassy-sync" } + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-ppp-v$VERSION/embassy-net-ppp/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-ppp/src/" +target = "thumbv7em-none-eabi" +features = ["defmt"] diff --git a/embassy-net-ppp/README.md b/embassy-net-ppp/README.md new file mode 100644 index 00000000..58d67395 --- /dev/null +++ b/embassy-net-ppp/README.md @@ -0,0 +1,19 @@ +# `embassy-net-ppp` + +[`embassy-net`](https://crates.io/crates/embassy-net) integration for PPP over Serial. + +## Interoperability + +This crate can run on any executor. + +It supports any serial port implementing [`embedded-io-async`](https://crates.io/crates/embedded-io-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-ppp/src/fmt.rs b/embassy-net-ppp/src/fmt.rs new file mode 100644 index 00000000..78e583c1 --- /dev/null +++ b/embassy-net-ppp/src/fmt.rs @@ -0,0 +1,258 @@ +#![macro_use] +#![allow(unused_macros)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[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)*); + } + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unreachable { + ($($x:tt)*) => { + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::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 + } +} + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-net-ppp/src/lib.rs b/embassy-net-ppp/src/lib.rs new file mode 100644 index 00000000..66496ee0 --- /dev/null +++ b/embassy-net-ppp/src/lib.rs @@ -0,0 +1,185 @@ +#![no_std] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] + +// must be first +mod fmt; + +use core::convert::Infallible; +use core::mem::MaybeUninit; + +use embassy_futures::select::{select, Either}; +use embassy_net_driver_channel as ch; +use embassy_net_driver_channel::driver::LinkState; +use embedded_io_async::{BufRead, Write, WriteAllError}; +use ppproto::pppos::{BufferFullError, PPPoS, PPPoSAction}; +pub use ppproto::{Config, Ipv4Status}; + +const MTU: usize = 1500; + +/// 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. +pub struct State { + ch_state: ch::State, +} + +impl State { + /// Create a new `State`. + pub const fn new() -> Self { + Self { + ch_state: ch::State::new(), + } + } +} + +/// Background runner for the driver. +/// +/// You must call `.run()` in a background task for the driver to operate. +pub struct Runner<'d> { + ch: ch::Runner<'d, MTU>, +} + +/// Error returned by [`Runner::run`]. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RunError { + /// Reading from the serial port failed. + Read(E), + /// Writing to the serial port failed. + Write(E), + /// Writing to the serial port wrote zero bytes, indicating it can't accept more data. + WriteZero, + /// Writing to the serial got EOF. + Eof, + /// PPP protocol was terminated by the peer + Terminated, +} + +impl From> for RunError { + fn from(value: WriteAllError) -> Self { + match value { + WriteAllError::Other(e) => Self::Write(e), + WriteAllError::WriteZero => Self::WriteZero, + } + } +} + +impl<'d> Runner<'d> { + /// You must call this in a background task for the driver to operate. + /// + /// If reading/writing to the underlying serial port fails, the link state + /// is set to Down and the error is returned. + /// + /// It is allowed to cancel this function's future (i.e. drop it). This will terminate + /// the PPP connection and set the link state to Down. + /// + /// After this function returns or is canceled, you can call it again to establish + /// a new PPP connection. + pub async fn run( + &mut self, + mut rw: RW, + config: ppproto::Config<'_>, + mut on_ipv4_up: impl FnMut(Ipv4Status), + ) -> Result> { + let mut ppp = PPPoS::new(config); + ppp.open().unwrap(); + + let (state_chan, mut rx_chan, mut tx_chan) = self.ch.borrow_split(); + state_chan.set_link_state(LinkState::Down); + let _ondrop = OnDrop::new(|| state_chan.set_link_state(LinkState::Down)); + + let mut rx_buf = [0; 2048]; + let mut tx_buf = [0; 2048]; + + let mut needs_poll = true; + let mut was_up = false; + + loop { + let rx_fut = async { + let buf = rx_chan.rx_buf().await; + let rx_data = match needs_poll { + true => &[][..], + false => match rw.fill_buf().await { + Ok(rx_data) if rx_data.len() == 0 => return Err(RunError::Eof), + Ok(rx_data) => rx_data, + Err(e) => return Err(RunError::Read(e)), + }, + }; + Ok((buf, rx_data)) + }; + let tx_fut = tx_chan.tx_buf(); + match select(rx_fut, tx_fut).await { + Either::First(r) => { + needs_poll = false; + + let (buf, rx_data) = r?; + let n = ppp.consume(rx_data, &mut rx_buf); + rw.consume(n); + + match ppp.poll(&mut tx_buf, &mut rx_buf) { + PPPoSAction::None => {} + PPPoSAction::Received(rg) => { + let pkt = &rx_buf[rg]; + buf[..pkt.len()].copy_from_slice(pkt); + rx_chan.rx_done(pkt.len()); + } + PPPoSAction::Transmit(n) => rw.write_all(&tx_buf[..n]).await?, + } + + let status = ppp.status(); + match status.phase { + ppproto::Phase::Dead => { + return Err(RunError::Terminated); + } + ppproto::Phase::Open => { + if !was_up { + on_ipv4_up(status.ipv4.unwrap()); + } + was_up = true; + state_chan.set_link_state(LinkState::Up); + } + _ => { + was_up = false; + state_chan.set_link_state(LinkState::Down); + } + } + } + Either::Second(pkt) => { + match ppp.send(pkt, &mut tx_buf) { + Ok(n) => rw.write_all(&tx_buf[..n]).await?, + Err(BufferFullError) => unreachable!(), + } + tx_chan.tx_done(); + } + } + } + } +} + +/// Create a PPP embassy-net driver instance. +/// +/// 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 fn new<'a, const N_RX: usize, const N_TX: usize>(state: &'a mut State) -> (Device<'a>, Runner<'a>) { + let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ip); + (device, Runner { ch: runner }) +} + +struct OnDrop { + f: MaybeUninit, +} + +impl OnDrop { + fn new(f: F) -> Self { + Self { f: MaybeUninit::new(f) } + } +} + +impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } + } +} diff --git a/embassy-net-wiznet/Cargo.toml b/embassy-net-wiznet/Cargo.toml index adf0b45f..afa0d5cd 100644 --- a/embassy-net-wiznet/Cargo.toml +++ b/embassy-net-wiznet/Cargo.toml @@ -11,7 +11,7 @@ edition = "2021" 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-time = { version = "0.1.3", path = "../embassy-time" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } defmt = { version = "0.3", optional = true } @@ -19,4 +19,4 @@ defmt = { version = "0.3", optional = true } 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 +features = ["defmt"] diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml index 0c551f20..c763d41c 100644 --- a/embassy-net/Cargo.toml +++ b/embassy-net/Cargo.toml @@ -9,6 +9,7 @@ categories = [ "embedded", "no-std", "asynchronous", + "network-programming", ] [package.metadata.embassy_docs] @@ -50,7 +51,7 @@ smoltcp = { version = "0.10.0", default-features = false, features = [ ] } embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" } -embassy-time = { version = "0.1.2", path = "../embassy-time" } +embassy-time = { version = "0.1.3", path = "../embassy-time" } embassy-sync = { version = "0.2.0", path = "../embassy-sync" } embedded-io-async = { version = "0.5.0", optional = true } diff --git a/embassy-net/src/fmt.rs b/embassy-net/src/fmt.rs index 06697081..78e583c1 100644 --- a/embassy-net/src/fmt.rs +++ b/embassy-net/src/fmt.rs @@ -1,6 +1,8 @@ #![macro_use] #![allow(unused_macros)] +use core::fmt::{Debug, Display, LowerHex}; + #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); @@ -81,14 +83,17 @@ macro_rules! todo { }; } +#[cfg(not(feature = "defmt"))] macro_rules! unreachable { ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::unreachable!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::unreachable!($($x)*); - } + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) }; } @@ -223,3 +228,31 @@ impl Try for Result { self } } + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index 2fb34f43..3a385fad 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -3,6 +3,9 @@ #![warn(missing_docs)] #![doc = include_str!("../README.md")] +#[cfg(not(any(feature = "proto-ipv4", feature = "proto-ipv6")))] +compile_error!("You must enable at least one of the following features: proto-ipv4, proto-ipv6"); + // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; @@ -20,7 +23,7 @@ use core::future::{poll_fn, Future}; use core::task::{Context, Poll}; pub use embassy_net_driver as driver; -use embassy_net_driver::{Driver, LinkState, Medium}; +use embassy_net_driver::{Driver, LinkState}; use embassy_sync::waitqueue::WakerRegistration; use embassy_time::{Instant, Timer}; use futures::pin_mut; @@ -133,6 +136,8 @@ impl Default for DhcpConfig { } /// Network stack configuration. +#[derive(Debug, Clone, Default)] +#[non_exhaustive] pub struct Config { /// IPv4 configuration #[cfg(feature = "proto-ipv4")] @@ -181,23 +186,27 @@ impl Config { /// Network stack IPv4 configuration. #[cfg(feature = "proto-ipv4")] +#[derive(Debug, Clone, Default)] pub enum ConfigV4 { + /// Do not configure IPv4. + #[default] + None, /// Use a static IPv4 address configuration. Static(StaticConfigV4), /// Use DHCP to obtain an IP address configuration. #[cfg(feature = "dhcpv4")] Dhcp(DhcpConfig), - /// Do not configure IPv6. - None, } /// Network stack IPv6 configuration. #[cfg(feature = "proto-ipv6")] +#[derive(Debug, Clone, Default)] pub enum ConfigV6 { + /// Do not configure IPv6. + #[default] + None, /// Use a static IPv6 address configuration. Static(StaticConfigV6), - /// Do not configure IPv6. - None, } /// A network stack. @@ -240,7 +249,10 @@ fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> HardwareAddress driver::HardwareAddress::Ip => HardwareAddress::Ip, #[allow(unreachable_patterns)] - _ => panic!("Unsupported address {:?}. Make sure to enable medium-ethernet or medium-ieee802154 in embassy-net's Cargo features.", addr), + _ => panic!( + "Unsupported medium {:?}. Make sure to enable the right medium feature in embassy-net's Cargo features.", + addr + ), } } @@ -276,7 +288,6 @@ impl Stack { next_local_port, }; - #[cfg_attr(feature = "medium-ieee802154", allow(unused_mut))] let mut inner = Inner { device, link_up: false, @@ -295,30 +306,11 @@ impl Stack { dns_waker: WakerRegistration::new(), }; - #[cfg(feature = "medium-ieee802154")] - let _ = config; - #[cfg(feature = "proto-ipv4")] - match config.ipv4 { - ConfigV4::Static(config) => { - inner.apply_config_v4(&mut socket, config); - } - #[cfg(feature = "dhcpv4")] - ConfigV4::Dhcp(config) => { - let mut dhcp_socket = smoltcp::socket::dhcpv4::Socket::new(); - inner.apply_dhcp_config(&mut dhcp_socket, config); - let handle = socket.sockets.add(dhcp_socket); - inner.dhcp_socket = Some(handle); - } - ConfigV4::None => {} - } + inner.set_config_v4(&mut socket, config.ipv4); #[cfg(feature = "proto-ipv6")] - match config.ipv6 { - ConfigV6::Static(config) => { - inner.apply_config_v6(&mut socket, config); - } - ConfigV6::None => {} - } + inner.set_config_v6(&mut socket, config.ipv6); + inner.apply_static_config(&mut socket); Self { socket: RefCell::new(socket), @@ -372,15 +364,36 @@ impl Stack { } /// Get the current IPv4 configuration. + /// + /// If using DHCP, this will be None if DHCP hasn't been able to + /// acquire an IP address, or Some if it has. #[cfg(feature = "proto-ipv4")] pub fn config_v4(&self) -> Option { - self.with(|_s, i| i.static_v4.clone()) + self.with(|_, i| i.static_v4.clone()) } /// Get the current IPv6 configuration. #[cfg(feature = "proto-ipv6")] pub fn config_v6(&self) -> Option { - self.with(|_s, i| i.static_v6.clone()) + self.with(|_, i| i.static_v6.clone()) + } + + /// Set the IPv4 configuration. + #[cfg(feature = "proto-ipv4")] + pub fn set_config_v4(&self, config: ConfigV4) { + self.with_mut(|s, i| { + i.set_config_v4(s, config); + i.apply_static_config(s); + }) + } + + /// Set the IPv6 configuration. + #[cfg(feature = "proto-ipv6")] + pub fn set_config_v6(&self, config: ConfigV6) { + self.with_mut(|s, i| { + i.set_config_v6(s, config); + i.apply_static_config(s); + }) } /// Run the network stack. @@ -582,166 +595,125 @@ impl SocketStack { impl Inner { #[cfg(feature = "proto-ipv4")] - fn apply_config_v4(&mut self, s: &mut SocketStack, config: StaticConfigV4) { - debug!("Acquired IP configuration:"); - - debug!(" IP address: {}", config.address); - s.iface.update_ip_addrs(|addrs| { - if let Some((index, _)) = addrs - .iter() - .enumerate() - .find(|(_, &addr)| matches!(addr, IpCidr::Ipv4(_))) - { - addrs.remove(index); - } - addrs.push(IpCidr::Ipv4(config.address)).unwrap(); - }); - - #[cfg(feature = "medium-ip")] - let skip_gateway = self.device.capabilities().medium != Medium::Ip; - #[cfg(not(feature = "medium-ip"))] - let skip_gateway = false; - - if !skip_gateway { - if let Some(gateway) = config.gateway { - debug!(" Default gateway: {}", gateway); - s.iface.routes_mut().add_default_ipv4_route(gateway).unwrap(); - } else { - debug!(" Default gateway: None"); - s.iface.routes_mut().remove_default_ipv4_route(); - } - } - for (i, s) in config.dns_servers.iter().enumerate() { - debug!(" DNS server {}: {}", i, s); - } - - self.static_v4 = Some(config); - - #[cfg(feature = "dns")] - { - self.update_dns_servers(s) - } - } - - /// Replaces the current IPv6 static configuration with a newly supplied config. - #[cfg(feature = "proto-ipv6")] - fn apply_config_v6(&mut self, s: &mut SocketStack, config: StaticConfigV6) { - #[cfg(feature = "medium-ethernet")] - let medium = self.device.capabilities().medium; - - debug!("Acquired IPv6 configuration:"); - - debug!(" IP address: {}", config.address); - s.iface.update_ip_addrs(|addrs| { - if let Some((index, _)) = addrs - .iter() - .enumerate() - .find(|(_, &addr)| matches!(addr, IpCidr::Ipv6(_))) - { - addrs.remove(index); - } - addrs.push(IpCidr::Ipv6(config.address)).unwrap(); - }); - - #[cfg(feature = "medium-ethernet")] - if Medium::Ethernet == medium { - if let Some(gateway) = config.gateway { - debug!(" Default gateway: {}", gateway); - s.iface.routes_mut().add_default_ipv6_route(gateway).unwrap(); - } else { - debug!(" Default gateway: None"); - s.iface.routes_mut().remove_default_ipv6_route(); - } - } - for (i, s) in config.dns_servers.iter().enumerate() { - debug!(" DNS server {}: {}", i, s); - } - - self.static_v6 = Some(config); - - #[cfg(feature = "dns")] - { - self.update_dns_servers(s) - } - } - - #[cfg(feature = "dns")] - fn update_dns_servers(&mut self, s: &mut SocketStack) { - let socket = s.sockets.get_mut::(self.dns_socket); - - let servers_v4; - #[cfg(feature = "proto-ipv4")] - { - servers_v4 = self - .static_v4 - .iter() - .flat_map(|cfg| cfg.dns_servers.iter().map(|c| IpAddress::Ipv4(*c))); + pub fn set_config_v4(&mut self, _s: &mut SocketStack, config: ConfigV4) { + // Handle static config. + self.static_v4 = match config.clone() { + ConfigV4::None => None, + #[cfg(feature = "dhcpv4")] + ConfigV4::Dhcp(_) => None, + ConfigV4::Static(c) => Some(c), }; - #[cfg(not(feature = "proto-ipv4"))] - { - servers_v4 = core::iter::empty(); - } - let servers_v6; - #[cfg(feature = "proto-ipv6")] - { - servers_v6 = self - .static_v6 - .iter() - .flat_map(|cfg| cfg.dns_servers.iter().map(|c| IpAddress::Ipv6(*c))); - } - #[cfg(not(feature = "proto-ipv6"))] - { - servers_v6 = core::iter::empty(); - } + // Handle DHCP config. + #[cfg(feature = "dhcpv4")] + match config { + ConfigV4::Dhcp(c) => { + // Create the socket if it doesn't exist. + if self.dhcp_socket.is_none() { + let socket = smoltcp::socket::dhcpv4::Socket::new(); + let handle = _s.sockets.add(socket); + self.dhcp_socket = Some(handle); + } - // Prefer the v6 DNS servers over the v4 servers - let servers: Vec = servers_v6.chain(servers_v4).collect(); - socket.update_servers(&servers[..]); - } - - #[cfg(feature = "dhcpv4")] - fn apply_dhcp_config(&self, socket: &mut smoltcp::socket::dhcpv4::Socket, config: DhcpConfig) { - socket.set_ignore_naks(config.ignore_naks); - socket.set_max_lease_duration(config.max_lease_duration.map(crate::time::duration_to_smoltcp)); - socket.set_ports(config.server_port, config.client_port); - socket.set_retry_config(config.retry_config); - } - - #[cfg(feature = "dhcpv4")] - fn unapply_config_v4(&mut self, s: &mut SocketStack) { - #[cfg(feature = "medium-ethernet")] - let medium = self.device.capabilities().medium; - debug!("Lost IP configuration"); - s.iface.update_ip_addrs(|ip_addrs| { - #[cfg(feature = "proto-ipv4")] - if let Some((index, _)) = ip_addrs - .iter() - .enumerate() - .find(|(_, &addr)| matches!(addr, IpCidr::Ipv4(_))) - { - ip_addrs.remove(index); + // Configure it + let socket = _s.sockets.get_mut::(self.dhcp_socket.unwrap()); + socket.set_ignore_naks(c.ignore_naks); + socket.set_max_lease_duration(c.max_lease_duration.map(crate::time::duration_to_smoltcp)); + socket.set_ports(c.server_port, c.client_port); + socket.set_retry_config(c.retry_config); + socket.reset(); } - }); - #[cfg(feature = "medium-ethernet")] - if medium == Medium::Ethernet { - #[cfg(feature = "proto-ipv4")] - { - s.iface.routes_mut().remove_default_ipv4_route(); + _ => { + // Remove DHCP socket if any. + if let Some(socket) = self.dhcp_socket { + _s.sockets.remove(socket); + self.dhcp_socket = None; + } } } + } + + #[cfg(feature = "proto-ipv6")] + pub fn set_config_v6(&mut self, _s: &mut SocketStack, config: ConfigV6) { + self.static_v6 = match config { + ConfigV6::None => None, + ConfigV6::Static(c) => Some(c), + }; + } + + fn apply_static_config(&mut self, s: &mut SocketStack) { + let mut addrs = Vec::new(); + #[cfg(feature = "dns")] + let mut dns_servers: Vec<_, 6> = Vec::new(); #[cfg(feature = "proto-ipv4")] - { - self.static_v4 = None + let mut gateway_v4 = None; + #[cfg(feature = "proto-ipv6")] + let mut gateway_v6 = None; + + #[cfg(feature = "proto-ipv4")] + if let Some(config) = &self.static_v4 { + debug!("IPv4: UP"); + debug!(" IP address: {:?}", config.address); + debug!(" Default gateway: {:?}", config.gateway); + + addrs.push(IpCidr::Ipv4(config.address)).unwrap(); + gateway_v4 = config.gateway.into(); + #[cfg(feature = "dns")] + for s in &config.dns_servers { + debug!(" DNS server: {:?}", s); + dns_servers.push(s.clone().into()).unwrap(); + } + } else { + info!("IPv4: DOWN"); } + + #[cfg(feature = "proto-ipv6")] + if let Some(config) = &self.static_v6 { + debug!("IPv6: UP"); + debug!(" IP address: {:?}", config.address); + debug!(" Default gateway: {:?}", config.gateway); + + addrs.push(IpCidr::Ipv6(config.address)).unwrap(); + gateway_v6 = config.gateway.into(); + #[cfg(feature = "dns")] + for s in &config.dns_servers { + debug!(" DNS server: {:?}", s); + dns_servers.push(s.clone().into()).unwrap(); + } + } else { + info!("IPv6: DOWN"); + } + + // Apply addresses + s.iface.update_ip_addrs(|a| *a = addrs); + + // Apply gateways + #[cfg(feature = "proto-ipv4")] + if let Some(gateway) = gateway_v4 { + s.iface.routes_mut().add_default_ipv4_route(gateway).unwrap(); + } else { + s.iface.routes_mut().remove_default_ipv4_route(); + } + #[cfg(feature = "proto-ipv6")] + if let Some(gateway) = gateway_v6 { + s.iface.routes_mut().add_default_ipv6_route(gateway).unwrap(); + } else { + s.iface.routes_mut().remove_default_ipv6_route(); + } + + // Apply DNS servers + #[cfg(feature = "dns")] + s.sockets + .get_mut::(self.dns_socket) + .update_servers(&dns_servers[..]); } fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) { s.waker.register(cx.waker()); #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] - if self.device.capabilities().medium == Medium::Ethernet - || self.device.capabilities().medium == Medium::Ieee802154 + if self.device.capabilities().medium == embassy_net_driver::Medium::Ethernet + || self.device.capabilities().medium == embassy_net_driver::Medium::Ieee802154 { s.iface .set_hardware_addr(to_smoltcp_hardware_address(self.device.hardware_address())); @@ -763,6 +735,9 @@ impl Inner { info!("link_up = {:?}", self.link_up); } + #[allow(unused_mut)] + let mut apply_config = false; + #[cfg(feature = "dhcpv4")] if let Some(dhcp_handle) = self.dhcp_socket { let socket = s.sockets.get_mut::(dhcp_handle); @@ -770,25 +745,29 @@ impl Inner { if self.link_up { match socket.poll() { None => {} - Some(dhcpv4::Event::Deconfigured) => self.unapply_config_v4(s), + Some(dhcpv4::Event::Deconfigured) => { + self.static_v4 = None; + apply_config = true; + } Some(dhcpv4::Event::Configured(config)) => { - let config = StaticConfigV4 { + self.static_v4 = Some(StaticConfigV4 { address: config.address, gateway: config.router, dns_servers: config.dns_servers, - }; - self.apply_config_v4(s, config) + }); + apply_config = true; } } } else if old_link_up { socket.reset(); - self.unapply_config_v4(s); + self.static_v4 = None; + apply_config = true; } } - //if old_link_up || self.link_up { - // self.poll_configurator(timestamp) - //} - // + + if apply_config { + self.apply_static_config(s); + } if let Some(poll_at) = s.iface.poll_at(timestamp, &mut s.sockets) { let t = Timer::at(instant_from_smoltcp(poll_at)); diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index 67ec4eb9..e7afef26 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml @@ -91,7 +91,7 @@ _dppi = [] _gpio-p1 = [] [dependencies] -embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true } +embassy-time = { version = "0.1.3", path = "../embassy-time", optional = true } embassy-sync = { version = "0.2.0", path = "../embassy-sync" } embassy-hal-internal = {version = "0.1.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] } embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" } diff --git a/embassy-nrf/src/fmt.rs b/embassy-nrf/src/fmt.rs index 06697081..78e583c1 100644 --- a/embassy-nrf/src/fmt.rs +++ b/embassy-nrf/src/fmt.rs @@ -1,6 +1,8 @@ #![macro_use] #![allow(unused_macros)] +use core::fmt::{Debug, Display, LowerHex}; + #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); @@ -81,14 +83,17 @@ macro_rules! todo { }; } +#[cfg(not(feature = "defmt"))] macro_rules! unreachable { ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::unreachable!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::unreachable!($($x)*); - } + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) }; } @@ -223,3 +228,31 @@ impl Try for Result { self } } + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml index b9ebcc86..eebb6e3b 100644 --- a/embassy-rp/Cargo.toml +++ b/embassy-rp/Cargo.toml @@ -60,7 +60,7 @@ unstable-traits = ["embedded-hal-1", "embedded-hal-nb"] [dependencies] embassy-sync = { version = "0.2.0", path = "../embassy-sync" } -embassy-time = { version = "0.1.2", path = "../embassy-time", features = [ "tick-hz-1_000_000" ] } +embassy-time = { version = "0.1.3", path = "../embassy-time", features = [ "tick-hz-1_000_000" ] } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-hal-internal = {version = "0.1.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] } embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" } @@ -95,5 +95,5 @@ pio = {version= "0.2.1" } rp2040-boot2 = "0.3" [dev-dependencies] -embassy-executor = { version = "0.2.0", path = "../embassy-executor", features = ["nightly", "arch-std", "executor-thread"] } +embassy-executor = { version = "0.3.0", path = "../embassy-executor", features = ["nightly", "arch-std", "executor-thread"] } static_cell = "1.1" diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index 7b25ecff..22066546 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -94,6 +94,7 @@ impl ClockConfig { post_div1: 6, post_div2: 5, }), + delay_multiplier: 128, }), ref_clk: RefClkConfig { src: RefClkSrc::Xosc, @@ -203,6 +204,7 @@ pub struct XoscConfig { pub hz: u32, pub sys_pll: Option, pub usb_pll: Option, + pub delay_multiplier: u32, } pub struct PllConfig { @@ -363,7 +365,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { // start XOSC // datasheet mentions support for clock inputs into XIN, but doesn't go into // how this is achieved. pico-sdk doesn't support this at all. - start_xosc(config.hz); + start_xosc(config.hz, config.delay_multiplier); let pll_sys_freq = match config.sys_pll { Some(sys_pll_config) => configure_pll(pac::PLL_SYS, config.hz, sys_pll_config), @@ -624,12 +626,12 @@ pub fn clk_rtc_freq() -> u16 { CLOCKS.rtc.load(Ordering::Relaxed) } -fn start_xosc(crystal_hz: u32) { +fn start_xosc(crystal_hz: u32, delay_multiplier: u32) { pac::XOSC .ctrl() .write(|w| w.set_freq_range(pac::xosc::vals::CtrlFreqRange::_1_15MHZ)); - let startup_delay = ((crystal_hz / 1000) + 128) / 256; + let startup_delay = (((crystal_hz / 1000) * delay_multiplier) + 128) / 256; pac::XOSC.startup().write(|w| w.set_delay(startup_delay as u16)); pac::XOSC.ctrl().write(|w| { w.set_freq_range(pac::xosc::vals::CtrlFreqRange::_1_15MHZ); diff --git a/embassy-rp/src/fmt.rs b/embassy-rp/src/fmt.rs index 06697081..78e583c1 100644 --- a/embassy-rp/src/fmt.rs +++ b/embassy-rp/src/fmt.rs @@ -1,6 +1,8 @@ #![macro_use] #![allow(unused_macros)] +use core::fmt::{Debug, Display, LowerHex}; + #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); @@ -81,14 +83,17 @@ macro_rules! todo { }; } +#[cfg(not(feature = "defmt"))] macro_rules! unreachable { ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::unreachable!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::unreachable!($($x)*); - } + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) }; } @@ -223,3 +228,31 @@ impl Try for Result { self } } + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-stm32-wpan/Cargo.toml b/embassy-stm32-wpan/Cargo.toml index 96c47484..7a4d3ec6 100644 --- a/embassy-stm32-wpan/Cargo.toml +++ b/embassy-stm32-wpan/Cargo.toml @@ -13,7 +13,7 @@ features = ["stm32wb55rg"] [dependencies] embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32" } embassy-sync = { version = "0.2.0", path = "../embassy-sync" } -embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true } +embassy-time = { version = "0.1.3", path = "../embassy-time", optional = true } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-hal-internal = { version = "0.1.0", path = "../embassy-hal-internal" } embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" } diff --git a/embassy-stm32-wpan/src/fmt.rs b/embassy-stm32-wpan/src/fmt.rs index 06697081..78e583c1 100644 --- a/embassy-stm32-wpan/src/fmt.rs +++ b/embassy-stm32-wpan/src/fmt.rs @@ -1,6 +1,8 @@ #![macro_use] #![allow(unused_macros)] +use core::fmt::{Debug, Display, LowerHex}; + #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); @@ -81,14 +83,17 @@ macro_rules! todo { }; } +#[cfg(not(feature = "defmt"))] macro_rules! unreachable { ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::unreachable!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::unreachable!($($x)*); - } + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) }; } @@ -223,3 +228,31 @@ impl Try for Result { self } } + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} 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 fe5dc443..d169107d 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" src_base = "https://github.com/embassy-rs/embassy/blob/embassy-stm32-v$VERSION/embassy-stm32/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-stm32/src/" -features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "exti", "time-driver-any", "time"] +features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "exti", "time-driver-any", "time", "low-power"] flavors = [ { regex_feature = "stm32f0.*", target = "thumbv6m-none-eabi" }, { regex_feature = "stm32f1.*", target = "thumbv7m-none-eabi" }, @@ -32,12 +32,13 @@ flavors = [ [dependencies] embassy-sync = { version = "0.2.0", path = "../embassy-sync" } -embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true } +embassy-time = { version = "0.1.3", path = "../embassy-time", optional = true } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-hal-internal = {version = "0.1.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-4"] } embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" } embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" } embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver", optional = true } +embassy-executor = { version = "0.3.0", path = "../embassy-executor", optional = true } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1", optional = true} @@ -57,7 +58,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-2b87e34c661e19ff6dc603fabfe7fe99ab7261f7" } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-9a61a1f090462df8bd1751f89951f04934fdceb3" } vcell = "0.1.3" bxcan = "0.7.0" nb = "1.0.0" @@ -76,7 +77,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-2b87e34c661e19ff6dc603fabfe7fe99ab7261f7", default-features = false, features = ["metadata"]} +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-9a61a1f090462df8bd1751f89951f04934fdceb3", default-features = false, features = ["metadata"]} [features] default = ["rt"] @@ -88,6 +89,8 @@ rt = ["stm32-metapac/rt"] defmt = ["dep:defmt", "bxcan/unstable-defmt", "embassy-sync/defmt", "embassy-embedded-hal/defmt", "embassy-hal-internal/defmt", "embedded-io-async?/defmt-03", "embassy-usb-driver?/defmt", "embassy-net-driver/defmt", "embassy-time?/defmt"] exti = [] +low-power = [ "dep:embassy-executor", "embassy-executor/arch-cortex-m" ] +embassy-executor = [] ## Automatically generate `memory.x` file using [`stm32-metapac`](https://docs.rs/stm32-metapac/) memory-x = ["stm32-metapac/memory-x"] diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 8a731620..6c364f7b 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -356,6 +356,8 @@ fn main() { } fn enable() { critical_section::with(|_| { + #[cfg(feature = "low-power")] + crate::rcc::clock_refcount_add(); crate::pac::RCC.#en_reg().modify(|w| w.#set_en_field(true)); #after_enable }) @@ -363,6 +365,8 @@ fn main() { fn disable() { critical_section::with(|_| { crate::pac::RCC.#en_reg().modify(|w| w.#set_en_field(false)); + #[cfg(feature = "low-power")] + crate::rcc::clock_refcount_sub(); }) } fn reset() { 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/fmt.rs b/embassy-stm32/src/fmt.rs index 06697081..78e583c1 100644 --- a/embassy-stm32/src/fmt.rs +++ b/embassy-stm32/src/fmt.rs @@ -1,6 +1,8 @@ #![macro_use] #![allow(unused_macros)] +use core::fmt::{Debug, Display, LowerHex}; + #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); @@ -81,14 +83,17 @@ macro_rules! todo { }; } +#[cfg(not(feature = "defmt"))] macro_rules! unreachable { ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::unreachable!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::unreachable!($($x)*); - } + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) }; } @@ -223,3 +228,31 @@ impl Try for Result { self } } + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index adb3054d..ec8648ee 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -47,6 +47,8 @@ pub mod i2c; pub mod i2s; #[cfg(stm32wb)] pub mod ipcc; +#[cfg(feature = "low-power")] +pub mod low_power; #[cfg(quadspi)] pub mod qspi; #[cfg(rng)] @@ -195,6 +197,11 @@ pub fn init(config: Config) -> Peripherals { // must be after rcc init #[cfg(feature = "_time-driver")] time_driver::init(); + + #[cfg(feature = "low-power")] + while !crate::rcc::low_power_ready() { + crate::rcc::clock_refcount_sub(); + } } p diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs new file mode 100644 index 00000000..7e678d32 --- /dev/null +++ b/embassy-stm32/src/low_power.rs @@ -0,0 +1,151 @@ +use core::arch::asm; +use core::marker::PhantomData; + +use cortex_m::peripheral::SCB; +use embassy_executor::*; + +use crate::interrupt; +use crate::interrupt::typelevel::Interrupt; +use crate::rcc::low_power_ready; +use crate::time_driver::{get_driver, RtcDriver}; + +const THREAD_PENDER: usize = usize::MAX; + +use crate::rtc::Rtc; + +static mut EXECUTOR: Option = None; + +foreach_interrupt! { + (RTC, rtc, $block:ident, WKUP, $irq:ident) => { + #[interrupt] + unsafe fn $irq() { + unsafe { EXECUTOR.as_mut().unwrap() }.on_wakeup_irq(); + } + }; +} + +// pub fn timer_driver_pause_time() { +// pause_time(); +// } + +pub fn stop_with_rtc(rtc: &'static Rtc) { + unsafe { EXECUTOR.as_mut().unwrap() }.stop_with_rtc(rtc) +} + +// pub fn start_wakeup_alarm(requested_duration: embassy_time::Duration) { +// let rtc_instant = unsafe { EXECUTOR.as_mut().unwrap() } +// .rtc +// .unwrap() +// .start_wakeup_alarm(requested_duration); +// +// unsafe { EXECUTOR.as_mut().unwrap() }.last_stop = Some(rtc_instant); +// } +// +// pub fn set_sleepdeep() { +// unsafe { EXECUTOR.as_mut().unwrap() }.scb.set_sleepdeep(); +// } +// +// pub fn stop_wakeup_alarm() -> RtcInstant { +// unsafe { EXECUTOR.as_mut().unwrap() }.rtc.unwrap().stop_wakeup_alarm() +// } + +/// Thread mode executor, using WFE/SEV. +/// +/// This is the simplest and most common kind of executor. It runs on +/// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction +/// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction +/// is executed, to make the `WFE` exit from sleep and poll the task. +/// +/// This executor allows for ultra low power consumption for chips where `WFE` +/// triggers low-power sleep without extra steps. If your chip requires extra steps, +/// you may use [`raw::Executor`] directly to program custom behavior. +pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + scb: SCB, + time_driver: &'static RtcDriver, +} + +impl Executor { + /// Create a new Executor. + pub fn take() -> &'static mut Self { + unsafe { + assert!(EXECUTOR.is_none()); + + EXECUTOR = Some(Self { + inner: raw::Executor::new(THREAD_PENDER as *mut ()), + not_send: PhantomData, + scb: cortex_m::Peripherals::steal().SCB, + time_driver: get_driver(), + }); + + EXECUTOR.as_mut().unwrap() + } + } + + unsafe fn on_wakeup_irq(&mut self) { + trace!("low power: on wakeup irq"); + + self.time_driver.resume_time(); + trace!("low power: resume time"); + } + + pub(self) fn stop_with_rtc(&mut self, rtc: &'static Rtc) { + trace!("low power: stop with rtc configured"); + + self.time_driver.set_rtc(rtc); + + crate::interrupt::typelevel::RTC_WKUP::unpend(); + unsafe { crate::interrupt::typelevel::RTC_WKUP::enable() }; + + rtc.enable_wakeup_line(); + } + + fn configure_pwr(&mut self) { + trace!("low power: configure_pwr"); + + self.scb.clear_sleepdeep(); + if !low_power_ready() { + trace!("low power: configure_pwr: low power not ready"); + return; + } + + if self.time_driver.pause_time().is_err() { + trace!("low power: configure_pwr: time driver failed to pause"); + return; + } + + trace!("low power: enter stop..."); + self.scb.set_sleepdeep(); + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(unsafe { EXECUTOR.as_mut().unwrap() }.inner.spawner()); + + loop { + unsafe { + EXECUTOR.as_mut().unwrap().inner.poll(); + self.configure_pwr(); + asm!("wfe"); + }; + } + } +} diff --git a/embassy-stm32/src/rcc/bd.rs b/embassy-stm32/src/rcc/bd.rs new file mode 100644 index 00000000..4d8ed82a --- /dev/null +++ b/embassy-stm32/src/rcc/bd.rs @@ -0,0 +1,126 @@ +#[derive(Copy, Clone, Debug, PartialEq)] +#[repr(u8)] +#[allow(dead_code)] +pub enum RtcClockSource { + /// 00: No clock + NoClock = 0b00, + /// 01: LSE oscillator clock used as RTC clock + LSE = 0b01, + /// 10: LSI oscillator clock used as RTC clock + LSI = 0b10, + /// 11: HSE oscillator clock divided by 32 used as RTC clock + HSE = 0b11, +} + +#[cfg(not(any(rtc_v2l0, rtc_v2l1, stm32c0)))] +#[allow(dead_code)] +type Bdcr = crate::pac::rcc::regs::Bdcr; + +#[cfg(any(rtc_v2l0, rtc_v2l1))] +#[allow(dead_code)] +type Bdcr = crate::pac::rcc::regs::Csr; + +#[allow(dead_code)] +pub struct BackupDomain {} + +impl BackupDomain { + #[cfg(any( + rtc_v2f0, rtc_v2f2, rtc_v2f3, rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l0, rtc_v2l1, rtc_v2l4, rtc_v2wb, rtc_v3, + rtc_v3u5 + ))] + #[allow(dead_code, unused_variables)] + fn modify(f: impl FnOnce(&mut Bdcr) -> R) -> R { + #[cfg(any(rtc_v2f2, rtc_v2f3, rtc_v2l1))] + let cr = crate::pac::PWR.cr(); + #[cfg(any(rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l4, rtc_v2wb, rtc_v3, rtc_v3u5))] + let cr = crate::pac::PWR.cr1(); + + // TODO: Missing from PAC for l0 and f0? + #[cfg(not(any(rtc_v2f0, rtc_v2l0, rtc_v3u5)))] + { + cr.modify(|w| w.set_dbp(true)); + while !cr.read().dbp() {} + } + + #[cfg(any(rtc_v2l0, rtc_v2l1))] + let cr = crate::pac::RCC.csr(); + + #[cfg(not(any(rtc_v2l0, rtc_v2l1)))] + let cr = crate::pac::RCC.bdcr(); + + cr.modify(|w| f(w)) + } + + #[cfg(any( + rtc_v2f0, rtc_v2f2, rtc_v2f3, rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l0, rtc_v2l1, rtc_v2l4, rtc_v2wb, rtc_v3, + rtc_v3u5 + ))] + #[allow(dead_code)] + fn read() -> Bdcr { + #[cfg(any(rtc_v2l0, rtc_v2l1))] + let r = crate::pac::RCC.csr().read(); + + #[cfg(not(any(rtc_v2l0, rtc_v2l1)))] + let r = crate::pac::RCC.bdcr().read(); + + r + } + + #[cfg(any( + rtc_v2f0, rtc_v2f2, rtc_v2f3, rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l0, rtc_v2l1, rtc_v2l4, rtc_v2wb, rtc_v3, + rtc_v3u5 + ))] + #[allow(dead_code, unused_variables)] + pub fn set_rtc_clock_source(clock_source: RtcClockSource) { + let clock_source = clock_source as u8; + #[cfg(any( + all(not(any(rtc_v3, rtc_v3u5)), not(rtc_v2wb)), + all(any(rtc_v3, rtc_v3u5), not(any(rcc_wl5, rcc_wle))) + ))] + let clock_source = crate::pac::rcc::vals::Rtcsel::from_bits(clock_source); + + #[cfg(not(rtc_v2wb))] + Self::modify(|w| { + // Select RTC source + w.set_rtcsel(clock_source); + }); + } + + #[cfg(any( + rtc_v2f0, rtc_v2f2, rtc_v2f3, rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l0, rtc_v2l1, rtc_v2l4, rtc_v2wb, rtc_v3, + rtc_v3u5 + ))] + #[allow(dead_code)] + pub fn enable_rtc() { + let reg = Self::read(); + + #[cfg(any(rtc_v2h7, rtc_v2l4, rtc_v2wb, rtc_v3, rtc_v3u5))] + assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet."); + + if !reg.rtcen() { + #[cfg(not(any(rtc_v2l0, rtc_v2l1, rtc_v2f2)))] + Self::modify(|w| w.set_bdrst(true)); + + Self::modify(|w| { + // Reset + #[cfg(not(any(rtc_v2l0, rtc_v2l1, rtc_v2f2)))] + w.set_bdrst(false); + + w.set_rtcen(true); + w.set_rtcsel(reg.rtcsel()); + + // Restore bcdr + #[cfg(any(rtc_v2l4, rtc_v2wb, rtc_v3, rtc_v3u5))] + w.set_lscosel(reg.lscosel()); + #[cfg(any(rtc_v2l4, rtc_v2wb, rtc_v3, rtc_v3u5))] + w.set_lscoen(reg.lscoen()); + + w.set_lseon(reg.lseon()); + + #[cfg(any(rtc_v2f0, rtc_v2f7, rtc_v2h7, rtc_v2l4, rtc_v2wb, rtc_v3, rtc_v3u5))] + w.set_lsedrv(reg.lsedrv()); + w.set_lsebyp(reg.lsebyp()); + }); + } + } +} diff --git a/embassy-stm32/src/rcc/common.rs b/embassy-stm32/src/rcc/bus.rs similarity index 100% rename from embassy-stm32/src/rcc/common.rs rename to embassy-stm32/src/rcc/bus.rs diff --git a/embassy-stm32/src/rcc/c0.rs b/embassy-stm32/src/rcc/c0.rs index 6a932634..d8579079 100644 --- a/embassy-stm32/src/rcc/c0.rs +++ b/embassy-stm32/src/rcc/c0.rs @@ -1,4 +1,4 @@ -pub use super::common::{AHBPrescaler, APBPrescaler}; +pub use super::bus::{AHBPrescaler, APBPrescaler}; use crate::pac::flash::vals::Latency; use crate::pac::rcc::vals::{Hsidiv, Ppre, Sw}; use crate::pac::{FLASH, RCC}; diff --git a/embassy-stm32/src/rcc/f2.rs b/embassy-stm32/src/rcc/f2.rs index ec4ed99b..9d9bc59f 100644 --- a/embassy-stm32/src/rcc/f2.rs +++ b/embassy-stm32/src/rcc/f2.rs @@ -1,7 +1,7 @@ use core::convert::TryFrom; use core::ops::{Div, Mul}; -pub use super::common::{AHBPrescaler, APBPrescaler}; +pub use super::bus::{AHBPrescaler, APBPrescaler}; use crate::pac::flash::vals::Latency; use crate::pac::rcc::vals::{Pllp, Pllsrc, Sw}; use crate::pac::{FLASH, RCC}; @@ -201,7 +201,7 @@ pub struct PLLClocks { pub pll48_freq: Hertz, } -pub use super::common::VoltageScale; +pub use super::bus::VoltageScale; impl VoltageScale { const fn wait_states(&self, ahb_freq: Hertz) -> Option { diff --git a/embassy-stm32/src/rcc/f3.rs b/embassy-stm32/src/rcc/f3.rs index 321270a7..7480c039 100644 --- a/embassy-stm32/src/rcc/f3.rs +++ b/embassy-stm32/src/rcc/f3.rs @@ -201,9 +201,9 @@ fn calc_pll(config: &Config, Hertz(sysclk): Hertz) -> (Hertz, PllConfig) { // Calculates the Multiplier and the Divisor to arrive at // the required System clock from PLL source frequency let get_mul_div = |sysclk, pllsrcclk| { - let common_div = gcd(sysclk, pllsrcclk); - let mut multiplier = sysclk / common_div; - let mut divisor = pllsrcclk / common_div; + let bus_div = gcd(sysclk, pllsrcclk); + let mut multiplier = sysclk / bus_div; + let mut divisor = pllsrcclk / bus_div; // Minimum PLL multiplier is two if multiplier == 1 { multiplier *= 2; diff --git a/embassy-stm32/src/rcc/f4.rs b/embassy-stm32/src/rcc/f4.rs index 2ae0d15c..c2c78a45 100644 --- a/embassy-stm32/src/rcc/f4.rs +++ b/embassy-stm32/src/rcc/f4.rs @@ -8,8 +8,8 @@ use crate::gpio::sealed::AFType; use crate::gpio::Speed; use crate::pac::rcc::vals::{Hpre, Ppre, Sw}; use crate::pac::{FLASH, PWR, RCC}; +use crate::rcc::bd::{BackupDomain, RtcClockSource}; use crate::rcc::{set_freqs, Clocks}; -use crate::rtc::{Rtc, RtcClockSource}; use crate::time::Hertz; use crate::{peripherals, Peripheral}; @@ -470,9 +470,14 @@ pub(crate) unsafe fn init(config: Config) { } config.rtc.map(|clock_source| { - Rtc::set_clock_source(clock_source); + BackupDomain::set_rtc_clock_source(clock_source); }); + let rtc = match config.rtc { + Some(RtcClockSource::LSI) => Some(LSI_FREQ), + _ => None, + }; + set_freqs(Clocks { sys: Hertz(sysclk), apb1: Hertz(pclk1), @@ -492,6 +497,9 @@ pub(crate) unsafe fn init(config: Config) { #[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))] pllsai: None, + + rtc: rtc, + rtc_hse: None, }); } diff --git a/embassy-stm32/src/rcc/g0.rs b/embassy-stm32/src/rcc/g0.rs index bf2d5199..7fdbcb00 100644 --- a/embassy-stm32/src/rcc/g0.rs +++ b/embassy-stm32/src/rcc/g0.rs @@ -1,4 +1,4 @@ -pub use super::common::{AHBPrescaler, APBPrescaler}; +pub use super::bus::{AHBPrescaler, APBPrescaler}; use crate::pac::flash::vals::Latency; use crate::pac::rcc::vals::{self, Hsidiv, Ppre, Sw}; use crate::pac::{FLASH, PWR, RCC}; diff --git a/embassy-stm32/src/rcc/g4.rs b/embassy-stm32/src/rcc/g4.rs index dff04023..3b044cd1 100644 --- a/embassy-stm32/src/rcc/g4.rs +++ b/embassy-stm32/src/rcc/g4.rs @@ -2,7 +2,7 @@ use stm32_metapac::flash::vals::Latency; use stm32_metapac::rcc::vals::{Hpre, Pllsrc, Ppre, Sw}; use stm32_metapac::FLASH; -pub use super::common::{AHBPrescaler, APBPrescaler}; +pub use super::bus::{AHBPrescaler, APBPrescaler}; use crate::pac::{PWR, RCC}; use crate::rcc::sealed::RccPeripheral; use crate::rcc::{set_freqs, Clocks}; diff --git a/embassy-stm32/src/rcc/h5.rs b/embassy-stm32/src/rcc/h5.rs index 2e72b193..5741cdf9 100644 --- a/embassy-stm32/src/rcc/h5.rs +++ b/embassy-stm32/src/rcc/h5.rs @@ -26,7 +26,7 @@ const VCO_MAX: u32 = 420_000_000; const VCO_WIDE_MIN: u32 = 128_000_000; const VCO_WIDE_MAX: u32 = 560_000_000; -pub use super::common::{AHBPrescaler, APBPrescaler, VoltageScale}; +pub use super::bus::{AHBPrescaler, APBPrescaler, VoltageScale}; pub enum HseMode { /// crystal/ceramic oscillator (HSEBYP=0) diff --git a/embassy-stm32/src/rcc/h7.rs b/embassy-stm32/src/rcc/h7.rs index 7fb4fb95..a6e69461 100644 --- a/embassy-stm32/src/rcc/h7.rs +++ b/embassy-stm32/src/rcc/h7.rs @@ -24,7 +24,7 @@ pub const HSI48_FREQ: Hertz = Hertz(48_000_000); /// LSI speed pub const LSI_FREQ: Hertz = Hertz(32_000); -pub use super::common::VoltageScale; +pub use super::bus::VoltageScale; #[derive(Clone, Copy)] pub enum AdcClockSource { diff --git a/embassy-stm32/src/rcc/l0.rs b/embassy-stm32/src/rcc/l0.rs index 46b58ca7..7f9ab01f 100644 --- a/embassy-stm32/src/rcc/l0.rs +++ b/embassy-stm32/src/rcc/l0.rs @@ -1,4 +1,4 @@ -pub use super::common::{AHBPrescaler, APBPrescaler}; +pub use super::bus::{AHBPrescaler, APBPrescaler}; use crate::pac::rcc::vals::{Hpre, Msirange, Plldiv, Pllmul, Pllsrc, Ppre, Sw}; use crate::pac::RCC; #[cfg(crs)] diff --git a/embassy-stm32/src/rcc/l1.rs b/embassy-stm32/src/rcc/l1.rs index bdfc5b87..ed949ea6 100644 --- a/embassy-stm32/src/rcc/l1.rs +++ b/embassy-stm32/src/rcc/l1.rs @@ -1,4 +1,4 @@ -pub use super::common::{AHBPrescaler, APBPrescaler}; +pub use super::bus::{AHBPrescaler, APBPrescaler}; use crate::pac::rcc::vals::{Hpre, Msirange, Plldiv, Pllmul, Pllsrc, Ppre, Sw}; use crate::pac::{FLASH, RCC}; use crate::rcc::{set_freqs, Clocks}; diff --git a/embassy-stm32/src/rcc/l4.rs b/embassy-stm32/src/rcc/l4.rs index b34b8caa..c6bccfd2 100644 --- a/embassy-stm32/src/rcc/l4.rs +++ b/embassy-stm32/src/rcc/l4.rs @@ -4,13 +4,13 @@ use embassy_hal_internal::into_ref; use stm32_metapac::rcc::regs::Cfgr; use stm32_metapac::rcc::vals::{Lsedrv, Mcopre, Mcosel}; -pub use super::common::{AHBPrescaler, APBPrescaler}; +pub use super::bus::{AHBPrescaler, APBPrescaler}; use crate::gpio::sealed::AFType; use crate::gpio::Speed; use crate::pac::rcc::vals::{Hpre, Msirange, Pllsrc, Ppre, Sw}; use crate::pac::{FLASH, PWR, RCC}; +use crate::rcc::bd::{BackupDomain, RtcClockSource}; use crate::rcc::{set_freqs, Clocks}; -use crate::rtc::{Rtc, RtcClockSource as RCS}; use crate::time::Hertz; use crate::{peripherals, Peripheral}; @@ -254,16 +254,11 @@ impl Default for Config { pllsai1: None, #[cfg(not(any(stm32l471, stm32l475, stm32l476, stm32l486)))] hsi48: false, - rtc_mux: RtcClockSource::LSI32, + rtc_mux: RtcClockSource::LSI, } } } -pub enum RtcClockSource { - LSE32, - LSI32, -} - pub enum McoClock { DIV1, DIV2, @@ -413,7 +408,7 @@ pub(crate) unsafe fn init(config: Config) { RCC.apb1enr1().modify(|w| w.set_pwren(true)); match config.rtc_mux { - RtcClockSource::LSE32 => { + RtcClockSource::LSE => { // 1. Unlock the backup domain PWR.cr1().modify(|w| w.set_dbp(true)); @@ -429,17 +424,18 @@ pub(crate) unsafe fn init(config: Config) { // Wait until LSE is running while !RCC.bdcr().read().lserdy() {} - Rtc::set_clock_source(RCS::LSE); + BackupDomain::set_rtc_clock_source(RtcClockSource::LSE); } - RtcClockSource::LSI32 => { + RtcClockSource::LSI => { // Turn on the internal 32 kHz LSI oscillator RCC.csr().modify(|w| w.set_lsion(true)); // Wait until LSI is running while !RCC.csr().read().lsirdy() {} - Rtc::set_clock_source(RCS::LSI); + BackupDomain::set_rtc_clock_source(RtcClockSource::LSI); } + _ => unreachable!(), } let (sys_clk, sw) = match config.mux { @@ -451,7 +447,7 @@ pub(crate) unsafe fn init(config: Config) { w.set_msirgsel(true); w.set_msion(true); - if let RtcClockSource::LSE32 = config.rtc_mux { + if let RtcClockSource::LSE = config.rtc_mux { // If LSE is enabled, enable calibration of MSI w.set_msipllen(true); } else { diff --git a/embassy-stm32/src/rcc/l5.rs b/embassy-stm32/src/rcc/l5.rs index a85e1488..9e4e0fc7 100644 --- a/embassy-stm32/src/rcc/l5.rs +++ b/embassy-stm32/src/rcc/l5.rs @@ -1,6 +1,6 @@ use stm32_metapac::PWR; -pub use super::common::{AHBPrescaler, APBPrescaler}; +pub use super::bus::{AHBPrescaler, APBPrescaler}; use crate::pac::rcc::vals::{Hpre, Msirange, Pllsrc, Ppre, Sw}; use crate::pac::{FLASH, RCC}; use crate::rcc::{set_freqs, Clocks}; diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index e5dd0019..0430e4a7 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs @@ -1,9 +1,10 @@ #![macro_use] -pub mod common; - +pub(crate) mod bd; +pub mod bus; use core::mem::MaybeUninit; +pub use crate::rcc::bd::RtcClockSource; use crate::time::Hertz; #[cfg_attr(rcc_f0, path = "f0.rs")] @@ -26,6 +27,8 @@ use crate::time::Hertz; #[cfg_attr(any(rcc_h5, rcc_h50), path = "h5.rs")] mod _version; pub use _version::*; +#[cfg(feature = "low-power")] +use atomic_polyfill::{AtomicU32, Ordering}; #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -74,9 +77,34 @@ pub struct Clocks { #[cfg(any(rcc_h5, rcc_h50, rcc_h7, rcc_h7ab))] pub adc: Option, - #[cfg(rcc_wb)] - /// Set only if the lsi or lse is configured + #[cfg(any(rcc_wb, rcc_f4, rcc_f410))] + /// Set only if the lsi or lse is configured, indicates stop is supported pub rtc: Option, + + #[cfg(any(rcc_wb, rcc_f4, rcc_f410))] + /// Set if the hse is configured, indicates stop is not supported + pub rtc_hse: Option, +} + +#[cfg(feature = "low-power")] +static CLOCK_REFCOUNT: AtomicU32 = AtomicU32::new(0); + +#[cfg(feature = "low-power")] +pub fn low_power_ready() -> bool { + trace!("clock refcount: {}", CLOCK_REFCOUNT.load(Ordering::SeqCst)); + + CLOCK_REFCOUNT.load(Ordering::SeqCst) == 0 +} + +#[cfg(feature = "low-power")] +pub(crate) fn clock_refcount_add() { + // We don't check for overflow because constructing more than u32 peripherals is unlikely + CLOCK_REFCOUNT.fetch_add(1, Ordering::Relaxed); +} + +#[cfg(feature = "low-power")] +pub(crate) fn clock_refcount_sub() { + assert!(CLOCK_REFCOUNT.fetch_sub(1, Ordering::Relaxed) != 0); } /// Frozen clock frequencies diff --git a/embassy-stm32/src/rcc/u5.rs b/embassy-stm32/src/rcc/u5.rs index b5feeb0c..4aca9cd3 100644 --- a/embassy-stm32/src/rcc/u5.rs +++ b/embassy-stm32/src/rcc/u5.rs @@ -1,6 +1,6 @@ use stm32_metapac::rcc::vals::{Msirange, Msirgsel, Pllm, Pllsrc, Sw}; -pub use super::common::{AHBPrescaler, APBPrescaler}; +pub use super::bus::{AHBPrescaler, APBPrescaler}; use crate::pac::{FLASH, RCC}; use crate::rcc::{set_freqs, Clocks}; use crate::time::Hertz; @@ -11,7 +11,7 @@ pub const HSI_FREQ: Hertz = Hertz(16_000_000); /// LSI speed pub const LSI_FREQ: Hertz = Hertz(32_000); -pub use super::common::VoltageScale; +pub use super::bus::VoltageScale; #[derive(Copy, Clone)] pub enum ClockSrc { diff --git a/embassy-stm32/src/rcc/wb.rs b/embassy-stm32/src/rcc/wb.rs index ae708b37..6496b41e 100644 --- a/embassy-stm32/src/rcc/wb.rs +++ b/embassy-stm32/src/rcc/wb.rs @@ -1,6 +1,6 @@ -pub use super::common::{AHBPrescaler, APBPrescaler}; +pub use super::bus::{AHBPrescaler, APBPrescaler}; +use crate::rcc::bd::{BackupDomain, RtcClockSource}; use crate::rcc::Clocks; -use crate::rtc::{Rtc, RtcClockSource}; use crate::time::{khz, mhz, Hertz}; /// Most of clock setup is copied from stm32l0xx-hal, and adopted to the generated PAC, @@ -271,6 +271,7 @@ pub(crate) fn compute_clocks(config: &Config) -> Clocks { apb1_tim: apb1_tim_clk, apb2_tim: apb2_tim_clk, rtc: rtc_clk, + rtc_hse: None, } } @@ -375,5 +376,7 @@ pub(crate) fn configure_clocks(config: &Config) { w.set_shdhpre(config.ahb3_pre.into()); }); - config.rtc.map(|clock_source| Rtc::set_clock_source(clock_source)); + config + .rtc + .map(|clock_source| BackupDomain::set_rtc_clock_source(clock_source)); } diff --git a/embassy-stm32/src/rcc/wl.rs b/embassy-stm32/src/rcc/wl.rs index f1dd2bd7..e33690d1 100644 --- a/embassy-stm32/src/rcc/wl.rs +++ b/embassy-stm32/src/rcc/wl.rs @@ -1,8 +1,7 @@ -pub use super::common::{AHBPrescaler, APBPrescaler, VoltageScale}; -use crate::pac::pwr::vals::Dbp; +pub use super::bus::{AHBPrescaler, APBPrescaler, VoltageScale}; use crate::pac::{FLASH, PWR, RCC}; +use crate::rcc::bd::{BackupDomain, RtcClockSource}; 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, @@ -130,16 +129,11 @@ impl Default for Config { apb2_pre: APBPrescaler::NotDivided, enable_lsi: false, enable_rtc_apb: false, - rtc_mux: RtcClockSource::LSI32, + rtc_mux: RtcClockSource::LSI, } } } -pub enum RtcClockSource { - LSE32, - LSI32, -} - #[repr(u8)] pub enum Lsedrv { Low = 0, @@ -215,9 +209,9 @@ pub(crate) unsafe fn init(config: Config) { while FLASH.acr().read().latency() != ws {} match config.rtc_mux { - RtcClockSource::LSE32 => { + RtcClockSource::LSE => { // 1. Unlock the backup domain - PWR.cr1().modify(|w| w.set_dbp(Dbp::ENABLED)); + PWR.cr1().modify(|w| w.set_dbp(true)); // 2. Setup the LSE RCC.bdcr().modify(|w| { @@ -231,17 +225,18 @@ pub(crate) unsafe fn init(config: Config) { // Wait until LSE is running while !RCC.bdcr().read().lserdy() {} - Rtc::set_clock_source(RCS::LSE); + BackupDomain::set_rtc_clock_source(RtcClockSource::LSE); } - RtcClockSource::LSI32 => { + RtcClockSource::LSI => { // Turn on the internal 32 kHz LSI oscillator RCC.csr().modify(|w| w.set_lsion(true)); // Wait until LSI is running while !RCC.csr().read().lsirdy() {} - Rtc::set_clock_source(RCS::LSI); + BackupDomain::set_rtc_clock_source(RtcClockSource::LSI); } + _ => unreachable!(), } match config.mux { @@ -266,7 +261,7 @@ pub(crate) unsafe fn init(config: Config) { w.set_msirange(range.into()); w.set_msion(true); - if let RtcClockSource::LSE32 = config.rtc_mux { + if let RtcClockSource::LSE = config.rtc_mux { // If LSE is enabled, enable calibration of MSI w.set_msipllen(true); } else { diff --git a/embassy-stm32/src/rng.rs b/embassy-stm32/src/rng.rs index 30816e43..0979dce8 100644 --- a/embassy-stm32/src/rng.rs +++ b/embassy-stm32/src/rng.rs @@ -119,7 +119,31 @@ impl<'d, T: Instance> Rng<'d, T> { pub async fn async_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { for chunk in dest.chunks_mut(4) { - let bits = T::regs().sr().read(); + let mut bits = T::regs().sr().read(); + if !bits.seis() && !bits.ceis() && !bits.drdy() { + // wait for interrupt + poll_fn(|cx| { + // quick check to avoid registration if already done. + let bits = T::regs().sr().read(); + if bits.drdy() || bits.seis() || bits.ceis() { + return Poll::Ready(()); + } + RNG_WAKER.register(cx.waker()); + T::regs().cr().modify(|reg| reg.set_ie(true)); + // Need to check condition **after** `register` to avoid a race + // condition that would result in lost notifications. + let bits = T::regs().sr().read(); + if bits.drdy() || bits.seis() || bits.ceis() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + // Re-read the status register after wait. + bits = T::regs().sr().read() + } if bits.seis() { // in case of noise-source or seed error we try to recover here // but we must not use the data in DR and we return an error @@ -143,26 +167,6 @@ impl<'d, T: Instance> Rng<'d, T> { for (dest, src) in chunk.iter_mut().zip(random_word.to_be_bytes().iter()) { *dest = *src } - } else { - // wait for interrupt - poll_fn(|cx| { - // quick check to avoid registration if already done. - let bits = T::regs().sr().read(); - if bits.drdy() || bits.seis() || bits.ceis() { - return Poll::Ready(()); - } - RNG_WAKER.register(cx.waker()); - T::regs().cr().modify(|reg| reg.set_ie(true)); - // Need to check condition **after** `register` to avoid a race - // condition that would result in lost notifications. - let bits = T::regs().sr().read(); - if bits.drdy() || bits.seis() || bits.ceis() { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; } } diff --git a/embassy-stm32/src/rtc/datetime.rs b/embassy-stm32/src/rtc/datetime.rs index a9c48d88..3efe9be5 100644 --- a/embassy-stm32/src/rtc/datetime.rs +++ b/embassy-stm32/src/rtc/datetime.rs @@ -89,7 +89,7 @@ pub enum DayOfWeek { #[cfg(feature = "chrono")] impl From for DayOfWeek { fn from(weekday: Weekday) -> Self { - day_of_week_from_u8(weekday.number_from_monday() as u8).unwrap() + day_of_week_from_u8(weekday.num_days_from_monday() as u8).unwrap() } } diff --git a/embassy-stm32/src/rtc/mod.rs b/embassy-stm32/src/rtc/mod.rs index 945bfafd..9db4f69c 100644 --- a/embassy-stm32/src/rtc/mod.rs +++ b/embassy-stm32/src/rtc/mod.rs @@ -1,7 +1,18 @@ //! RTC peripheral abstraction mod datetime; +#[cfg(feature = "low-power")] +use core::cell::Cell; + +#[cfg(feature = "low-power")] +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +#[cfg(feature = "low-power")] +use embassy_sync::blocking_mutex::Mutex; + pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError}; +use crate::rcc::bd::BackupDomain; +pub use crate::rcc::RtcClockSource; +use crate::time::Hertz; /// refer to AN4759 to compare features of RTC2 and RTC3 #[cfg_attr(any(rtc_v1), path = "v1.rs")] @@ -13,6 +24,7 @@ pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError}; )] #[cfg_attr(any(rtc_v3, rtc_v3u5), path = "v3.rs")] mod _version; +#[allow(unused_imports)] pub use _version::*; use embassy_hal_internal::Peripheral; @@ -29,60 +41,67 @@ pub enum RtcError { NotRunning, } -/// RTC Abstraction -pub struct Rtc { - rtc_config: RtcConfig, +#[cfg(feature = "low-power")] +/// Represents an instant in time that can be substracted to compute a duration +struct RtcInstant { + second: u8, + subsecond: u16, } -#[derive(Copy, Clone, Debug, PartialEq)] -#[repr(u8)] -pub enum RtcClockSource { - /// 00: No clock - NoClock = 0b00, - /// 01: LSE oscillator clock used as RTC clock - LSE = 0b01, - /// 10: LSI oscillator clock used as RTC clock - LSI = 0b10, - /// 11: HSE oscillator clock divided by 32 used as RTC clock - HSE = 0b11, +#[cfg(all(feature = "low-power", feature = "defmt"))] +impl defmt::Format for RtcInstant { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!( + fmt, + "{}:{}", + self.second, + RTC::regs().prer().read().prediv_s() - self.subsecond, + ) + } +} + +#[cfg(feature = "low-power")] +impl core::ops::Sub for RtcInstant { + type Output = embassy_time::Duration; + + fn sub(self, rhs: Self) -> Self::Output { + use embassy_time::{Duration, TICK_HZ}; + + let second = if self.second < rhs.second { + self.second + 60 + } else { + self.second + }; + + let psc = RTC::regs().prer().read().prediv_s() as u32; + + let self_ticks = second as u32 * (psc + 1) + (psc - self.subsecond as u32); + let other_ticks = rhs.second as u32 * (psc + 1) + (psc - rhs.subsecond as u32); + let rtc_ticks = self_ticks - other_ticks; + + Duration::from_ticks(((rtc_ticks * TICK_HZ as u32) / (psc + 1)) as u64) + } +} + +/// RTC Abstraction +pub struct Rtc { + #[cfg(feature = "low-power")] + stop_time: Mutex>>, } #[derive(Copy, Clone, PartialEq)] pub struct RtcConfig { - /// Asynchronous prescaler factor - /// This is the asynchronous division factor: - /// ck_apre frequency = RTCCLK frequency/(PREDIV_A+1) - /// ck_apre drives the subsecond register - async_prescaler: u8, - /// Synchronous prescaler factor - /// This is the synchronous division factor: - /// ck_spre frequency = ck_apre frequency/(PREDIV_S+1) - /// ck_spre must be 1Hz - sync_prescaler: u16, + /// The subsecond counter frequency; default is 256 + /// + /// A high counter frequency may impact stop power consumption + pub frequency: Hertz, } impl Default for RtcConfig { /// LSI with prescalers assuming 32.768 kHz. /// Raw sub-seconds in 1/256. fn default() -> Self { - RtcConfig { - async_prescaler: 127, - sync_prescaler: 255, - } - } -} - -impl RtcConfig { - /// Set the asynchronous prescaler of RTC config - pub fn async_prescaler(mut self, prescaler: u8) -> Self { - self.async_prescaler = prescaler; - self - } - - /// Set the synchronous prescaler of RTC config - pub fn sync_prescaler(mut self, prescaler: u16) -> Self { - self.sync_prescaler = prescaler; - self + RtcConfig { frequency: Hertz(256) } } } @@ -105,16 +124,37 @@ impl Default for RtcCalibrationCyclePeriod { impl Rtc { pub fn new(_rtc: impl Peripheral

, rtc_config: RtcConfig) -> Self { + #[cfg(any(rcc_wb, rcc_f4, rcc_f410))] + use crate::rcc::get_freqs; + RTC::enable_peripheral_clk(); + BackupDomain::enable_rtc(); - let mut rtc_struct = Self { rtc_config }; + let mut this = Self { + #[cfg(feature = "low-power")] + stop_time: Mutex::const_new(CriticalSectionRawMutex::new(), Cell::new(None)), + }; - Self::enable(); + #[cfg(any(rcc_wb, rcc_f4, rcc_f410))] + let freqs = unsafe { get_freqs() }; - rtc_struct.configure(rtc_config); - rtc_struct.rtc_config = rtc_config; + // Load the clock frequency from the rcc mod, if supported + #[cfg(any(rcc_wb, rcc_f4, rcc_f410))] + let frequency = match freqs.rtc { + Some(hertz) => hertz, + None => freqs.rtc_hse.unwrap(), + }; - rtc_struct + // Assume the default value, if not supported + #[cfg(not(any(rcc_wb, rcc_f4, rcc_f410)))] + let frequency = Hertz(32_768); + + let async_psc = ((frequency.0 / rtc_config.frequency.0) - 1) as u8; + let sync_psc = (rtc_config.frequency.0 - 1) as u16; + + this.configure(async_psc, sync_psc); + + this } /// Set the datetime to a new value. @@ -129,6 +169,20 @@ impl Rtc { Ok(()) } + #[cfg(feature = "low-power")] + /// Return the current instant. + fn instant(&self) -> RtcInstant { + let r = RTC::regs(); + let tr = r.tr().read(); + let subsecond = r.ssr().read().ss(); + let second = bcd2_to_byte((tr.st(), tr.su())); + + // Unlock the registers + r.dr().read(); + + RtcInstant { second, subsecond } + } + /// Return the current datetime. /// /// # Errors @@ -165,10 +219,6 @@ impl Rtc { }) } - pub fn get_config(&self) -> RtcConfig { - self.rtc_config - } - pub const BACKUP_REGISTER_COUNT: usize = RTC::BACKUP_REGISTER_COUNT; /// Read content of the backup register. diff --git a/embassy-stm32/src/rtc/v2.rs b/embassy-stm32/src/rtc/v2.rs index 5b896069..482b6e75 100644 --- a/embassy-stm32/src/rtc/v2.rs +++ b/embassy-stm32/src/rtc/v2.rs @@ -1,93 +1,160 @@ use stm32_metapac::rtc::vals::{Init, Osel, Pol}; -use super::{sealed, RtcClockSource, RtcConfig}; +use super::sealed; use crate::pac::rtc::Rtc; use crate::peripherals::RTC; use crate::rtc::sealed::Instance; +#[allow(dead_code)] +#[derive(Clone, Copy, Debug)] +pub(crate) enum WakeupPrescaler { + Div2, + Div4, + Div8, + Div16, +} + +#[cfg(any(stm32wb, stm32f4))] +impl From for crate::pac::rtc::vals::Wucksel { + fn from(val: WakeupPrescaler) -> Self { + use crate::pac::rtc::vals::Wucksel; + + match val { + WakeupPrescaler::Div2 => Wucksel::DIV2, + WakeupPrescaler::Div4 => Wucksel::DIV4, + WakeupPrescaler::Div8 => Wucksel::DIV8, + WakeupPrescaler::Div16 => Wucksel::DIV16, + } + } +} + +#[cfg(any(stm32wb, stm32f4))] +impl From for WakeupPrescaler { + fn from(val: crate::pac::rtc::vals::Wucksel) -> Self { + use crate::pac::rtc::vals::Wucksel; + + match val { + Wucksel::DIV2 => WakeupPrescaler::Div2, + Wucksel::DIV4 => WakeupPrescaler::Div4, + Wucksel::DIV8 => WakeupPrescaler::Div8, + Wucksel::DIV16 => WakeupPrescaler::Div16, + _ => unreachable!(), + } + } +} + +impl From for u32 { + fn from(val: WakeupPrescaler) -> Self { + match val { + WakeupPrescaler::Div2 => 2, + WakeupPrescaler::Div4 => 4, + WakeupPrescaler::Div8 => 8, + WakeupPrescaler::Div16 => 16, + } + } +} + +#[allow(dead_code)] +impl WakeupPrescaler { + pub fn compute_min(val: u32) -> Self { + *[ + WakeupPrescaler::Div2, + WakeupPrescaler::Div4, + WakeupPrescaler::Div8, + WakeupPrescaler::Div16, + ] + .iter() + .skip_while(|psc| >::into(**psc) <= val) + .next() + .unwrap_or(&WakeupPrescaler::Div16) + } +} + impl super::Rtc { - fn unlock_registers() { - #[cfg(any(rtc_v2f2, rtc_v2f3, rtc_v2l1))] - let cr = crate::pac::PWR.cr(); - #[cfg(any(rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l4, rtc_v2wb))] - let cr = crate::pac::PWR.cr1(); + #[cfg(feature = "low-power")] + /// start the wakeup alarm and wtih a duration that is as close to but less than + /// the requested duration, and record the instant the wakeup alarm was started + pub(crate) fn start_wakeup_alarm(&self, requested_duration: embassy_time::Duration) { + use embassy_time::{Duration, TICK_HZ}; - // TODO: Missing from PAC for l0 and f0? - #[cfg(not(any(rtc_v2f0, rtc_v2l0)))] - { - if !cr.read().dbp() { - cr.modify(|w| w.set_dbp(true)); - while !cr.read().dbp() {} - } + use crate::rcc::get_freqs; + + let rtc_hz = unsafe { get_freqs() }.rtc.unwrap().0 as u64; + + let rtc_ticks = requested_duration.as_ticks() * rtc_hz / TICK_HZ; + let prescaler = WakeupPrescaler::compute_min((rtc_ticks / u16::MAX as u64) as u32); + + // adjust the rtc ticks to the prescaler and subtract one rtc tick + let rtc_ticks = rtc_ticks / (>::into(prescaler) as u64); + let rtc_ticks = if rtc_ticks >= u16::MAX as u64 { + u16::MAX - 1 + } else { + rtc_ticks as u16 } - } + .saturating_sub(1); - #[allow(dead_code)] - pub(crate) fn set_clock_source(clock_source: RtcClockSource) { - #[cfg(not(rtc_v2wb))] - use stm32_metapac::rcc::vals::Rtcsel; + self.write(false, |regs| { + regs.cr().modify(|w| w.set_wute(false)); + regs.isr().modify(|w| w.set_wutf(false)); + while !regs.isr().read().wutwf() {} - #[cfg(not(any(rtc_v2l0, rtc_v2l1)))] - let cr = crate::pac::RCC.bdcr(); - #[cfg(any(rtc_v2l0, rtc_v2l1))] - let cr = crate::pac::RCC.csr(); - - Self::unlock_registers(); - - cr.modify(|w| { - // Select RTC source - #[cfg(not(rtc_v2wb))] - w.set_rtcsel(Rtcsel::from_bits(clock_source as u8)); - #[cfg(rtc_v2wb)] - w.set_rtcsel(clock_source as u8); + regs.cr().modify(|w| w.set_wucksel(prescaler.into())); + regs.wutr().write(|w| w.set_wut(rtc_ticks)); + regs.cr().modify(|w| w.set_wute(true)); + regs.cr().modify(|w| w.set_wutie(true)); }); + + trace!( + "rtc: start wakeup alarm for {} ms (psc: {}, ticks: {}) at {}", + Duration::from_ticks( + rtc_ticks as u64 * TICK_HZ * (>::into(prescaler) as u64) / rtc_hz, + ) + .as_millis(), + >::into(prescaler), + rtc_ticks, + self.instant(), + ); + + critical_section::with(|cs| assert!(self.stop_time.borrow(cs).replace(Some(self.instant())).is_none())) } - pub(super) fn enable() { - #[cfg(not(any(rtc_v2l0, rtc_v2l1)))] - let reg = crate::pac::RCC.bdcr().read(); - #[cfg(any(rtc_v2l0, rtc_v2l1))] - let reg = crate::pac::RCC.csr().read(); + #[cfg(feature = "low-power")] + pub(crate) fn enable_wakeup_line(&self) { + use crate::pac::EXTI; - #[cfg(any(rtc_v2h7, rtc_v2l4, rtc_v2wb))] - assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet."); + EXTI.rtsr(0).modify(|w| w.set_line(22, true)); + EXTI.imr(0).modify(|w| w.set_line(22, true)); + } - if !reg.rtcen() { - Self::unlock_registers(); + #[cfg(feature = "low-power")] + /// stop the wakeup alarm and return the time elapsed since `start_wakeup_alarm` + /// was called, otherwise none + pub(crate) fn stop_wakeup_alarm(&self) -> Option { + use crate::interrupt::typelevel::Interrupt; - #[cfg(not(any(rtc_v2l0, rtc_v2l1, rtc_v2f2)))] - crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); - #[cfg(not(any(rtc_v2l0, rtc_v2l1)))] - let cr = crate::pac::RCC.bdcr(); - #[cfg(any(rtc_v2l0, rtc_v2l1))] - let cr = crate::pac::RCC.csr(); + trace!("rtc: stop wakeup alarm at {}", self.instant()); - cr.modify(|w| { - // Reset - #[cfg(not(any(rtc_v2l0, rtc_v2l1)))] - w.set_bdrst(false); + self.write(false, |regs| { + regs.cr().modify(|w| w.set_wutie(false)); + regs.cr().modify(|w| w.set_wute(false)); + regs.isr().modify(|w| w.set_wutf(false)); - w.set_rtcen(true); - w.set_rtcsel(reg.rtcsel()); + crate::pac::EXTI.pr(0).modify(|w| w.set_line(22, true)); + crate::interrupt::typelevel::RTC_WKUP::unpend(); + }); - // Restore bcdr - #[cfg(any(rtc_v2l4, rtc_v2wb))] - w.set_lscosel(reg.lscosel()); - #[cfg(any(rtc_v2l4, rtc_v2wb))] - w.set_lscoen(reg.lscoen()); - - w.set_lseon(reg.lseon()); - - #[cfg(any(rtc_v2f0, rtc_v2f7, rtc_v2h7, rtc_v2l4, rtc_v2wb))] - w.set_lsedrv(reg.lsedrv()); - w.set_lsebyp(reg.lsebyp()); - }); - } + critical_section::with(|cs| { + if let Some(stop_time) = self.stop_time.borrow(cs).take() { + Some(self.instant() - stop_time) + } else { + None + } + }) } /// Applies the RTC config /// It this changes the RTC clock source the time will be reset - pub(super) fn configure(&mut self, rtc_config: RtcConfig) { + pub(super) fn configure(&mut self, async_psc: u8, sync_psc: u16) { self.write(true, |rtc| { rtc.cr().modify(|w| { #[cfg(rtc_v2f2)] @@ -99,8 +166,8 @@ impl super::Rtc { }); rtc.prer().modify(|w| { - w.set_prediv_s(rtc_config.sync_prescaler); - w.set_prediv_a(rtc_config.async_prescaler); + w.set_prediv_s(sync_psc); + w.set_prediv_a(async_psc); }); }); } @@ -170,7 +237,7 @@ impl super::Rtc { }) } - pub(super) fn write(&mut self, init_mode: bool, f: F) -> R + pub(super) fn write(&self, init_mode: bool, f: F) -> R where F: FnOnce(&crate::pac::rtc::Rtc) -> R, { diff --git a/embassy-stm32/src/rtc/v3.rs b/embassy-stm32/src/rtc/v3.rs index 3297303e..a6b2655d 100644 --- a/embassy-stm32/src/rtc/v3.rs +++ b/embassy-stm32/src/rtc/v3.rs @@ -1,77 +1,14 @@ use stm32_metapac::rtc::vals::{Calp, Calw16, Calw8, Fmt, Init, Key, Osel, Pol, TampalrmPu, TampalrmType}; -use super::{sealed, RtcCalibrationCyclePeriod, RtcClockSource, RtcConfig}; +use super::{sealed, RtcCalibrationCyclePeriod}; use crate::pac::rtc::Rtc; use crate::peripherals::RTC; use crate::rtc::sealed::Instance; impl super::Rtc { - fn unlock_registers() { - // Unlock the backup domain - #[cfg(not(any(rtc_v3u5, rcc_wl5, rcc_wle)))] - { - if !crate::pac::PWR.cr1().read().dbp() { - crate::pac::PWR.cr1().modify(|w| w.set_dbp(true)); - while !crate::pac::PWR.cr1().read().dbp() {} - } - } - #[cfg(any(rcc_wl5, rcc_wle))] - { - use crate::pac::pwr::vals::Dbp; - - if crate::pac::PWR.cr1().read().dbp() != Dbp::ENABLED { - crate::pac::PWR.cr1().modify(|w| w.set_dbp(Dbp::ENABLED)); - while crate::pac::PWR.cr1().read().dbp() != Dbp::ENABLED {} - } - } - } - - #[allow(dead_code)] - pub(crate) fn set_clock_source(clock_source: RtcClockSource) { - let clock_source = clock_source as u8; - #[cfg(not(any(rcc_wl5, rcc_wle)))] - let clock_source = crate::pac::rcc::vals::Rtcsel::from_bits(clock_source); - - Self::unlock_registers(); - - crate::pac::RCC.bdcr().modify(|w| { - // Select RTC source - w.set_rtcsel(clock_source); - }); - } - - pub(super) fn enable() { - let bdcr = crate::pac::RCC.bdcr(); - - let reg = bdcr.read(); - assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet."); - - if !reg.rtcen() { - Self::unlock_registers(); - - bdcr.modify(|w| w.set_bdrst(true)); - - bdcr.modify(|w| { - // Reset - w.set_bdrst(false); - - w.set_rtcen(true); - w.set_rtcsel(reg.rtcsel()); - - // Restore bcdr - w.set_lscosel(reg.lscosel()); - w.set_lscoen(reg.lscoen()); - - w.set_lseon(reg.lseon()); - w.set_lsedrv(reg.lsedrv()); - w.set_lsebyp(reg.lsebyp()); - }); - } - } - /// Applies the RTC config /// It this changes the RTC clock source the time will be reset - pub(super) fn configure(&mut self, rtc_config: RtcConfig) { + pub(super) fn configure(&mut self, async_psc: u8, sync_psc: u16) { self.write(true, |rtc| { rtc.cr().modify(|w| { w.set_fmt(Fmt::TWENTYFOURHOUR); @@ -80,8 +17,8 @@ impl super::Rtc { }); rtc.prer().modify(|w| { - w.set_prediv_s(rtc_config.sync_prescaler); - w.set_prediv_a(rtc_config.async_prescaler); + w.set_prediv_s(sync_psc); + w.set_prediv_a(async_psc); }); // TODO: configuration for output pins diff --git a/embassy-stm32/src/time_driver.rs b/embassy-stm32/src/time_driver.rs index 2622442f..99d423d0 100644 --- a/embassy-stm32/src/time_driver.rs +++ b/embassy-stm32/src/time_driver.rs @@ -14,6 +14,8 @@ use stm32_metapac::timer::regs; use crate::interrupt::typelevel::Interrupt; use crate::pac::timer::vals; use crate::rcc::sealed::RccPeripheral; +#[cfg(feature = "low-power")] +use crate::rtc::Rtc; use crate::timer::sealed::{Basic16bitInstance as BasicInstance, GeneralPurpose16bitInstance as Instance}; use crate::{interrupt, peripherals}; @@ -130,12 +132,14 @@ impl AlarmState { } } -struct RtcDriver { +pub(crate) struct RtcDriver { /// Number of 2^15 periods elapsed since boot. period: AtomicU32, alarm_count: AtomicU8, /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. alarms: Mutex, + #[cfg(feature = "low-power")] + rtc: Mutex>>, } const ALARM_STATE_NEW: AlarmState = AlarmState::new(); @@ -144,6 +148,8 @@ embassy_time::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver { period: AtomicU32::new(0), alarm_count: AtomicU8::new(0), alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [ALARM_STATE_NEW; ALARM_COUNT]), + #[cfg(feature = "low-power")] + rtc: Mutex::const_new(CriticalSectionRawMutex::new(), Cell::new(None)), }); impl RtcDriver { @@ -259,6 +265,117 @@ impl RtcDriver { let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) }; f(alarm.ctx.get()); } + + #[cfg(feature = "low-power")] + /// Set the rtc but panic if it's already been set + pub(crate) fn set_rtc(&self, rtc: &'static Rtc) { + critical_section::with(|cs| assert!(self.rtc.borrow(cs).replace(Some(rtc)).is_none())); + } + + #[cfg(feature = "low-power")] + /// Compute the approximate amount of time until the next alarm + fn time_until_next_alarm(&self) -> embassy_time::Duration { + critical_section::with(|cs| { + let now = self.now() + 32; + + embassy_time::Duration::from_ticks( + self.alarms + .borrow(cs) + .iter() + .map(|alarm: &AlarmState| alarm.timestamp.get().saturating_sub(now)) + .min() + .unwrap_or(u64::MAX), + ) + }) + } + + #[cfg(feature = "low-power")] + /// Add the given offset to the current time + fn add_time(&self, offset: embassy_time::Duration) { + let offset = offset.as_ticks(); + let cnt = T::regs_gp16().cnt().read().cnt() as u32; + let period = self.period.load(Ordering::SeqCst); + + // Correct the race, if it exists + let period = if period & 1 == 1 && cnt < u16::MAX as u32 / 2 { + period + 1 + } else { + period + }; + + // Normalize to the full overflow + let period = (period / 2) * 2; + + // Add the offset + let period = period + 2 * (offset / u16::MAX as u64) as u32; + let cnt = cnt + (offset % u16::MAX as u64) as u32; + + let (cnt, period) = if cnt > u16::MAX as u32 { + (cnt - u16::MAX as u32, period + 2) + } else { + (cnt, period) + }; + + let period = if cnt > u16::MAX as u32 / 2 { period + 1 } else { period }; + + self.period.store(period, Ordering::SeqCst); + T::regs_gp16().cnt().write(|w| w.set_cnt(cnt as u16)); + + // Now, recompute all alarms + critical_section::with(|cs| { + for i in 0..ALARM_COUNT { + let alarm_handle = unsafe { AlarmHandle::new(i as u8) }; + let alarm = self.get_alarm(cs, alarm_handle); + + self.set_alarm(alarm_handle, alarm.timestamp.get()); + } + }) + } + + #[cfg(feature = "low-power")] + /// Stop the wakeup alarm, if enabled, and add the appropriate offset + fn stop_wakeup_alarm(&self) { + critical_section::with(|cs| { + if let Some(offset) = self.rtc.borrow(cs).get().unwrap().stop_wakeup_alarm() { + self.add_time(offset); + } + }); + } + + #[cfg(feature = "low-power")] + /// Pause the timer if ready; return err if not + pub(crate) fn pause_time(&self) -> Result<(), ()> { + /* + If the wakeup timer is currently running, then we need to stop it and + add the elapsed time to the current time + */ + self.stop_wakeup_alarm(); + + let time_until_next_alarm = self.time_until_next_alarm(); + if time_until_next_alarm < embassy_time::Duration::from_millis(250) { + Err(()) + } else { + critical_section::with(|cs| { + self.rtc + .borrow(cs) + .get() + .unwrap() + .start_wakeup_alarm(time_until_next_alarm); + }); + + T::regs_gp16().cr1().modify(|w| w.set_cen(false)); + + Ok(()) + } + } + + #[cfg(feature = "low-power")] + /// Resume the timer with the given offset + pub(crate) fn resume_time(&self) { + self.stop_wakeup_alarm(); + + T::regs_gp16().cr1().modify(|w| w.set_cen(true)); + } } impl Driver for RtcDriver { @@ -329,6 +446,11 @@ impl Driver for RtcDriver { } } +#[cfg(feature = "low-power")] +pub(crate) fn get_driver() -> &'static RtcDriver { + &DRIVER +} + pub(crate) fn init() { DRIVER.init() } diff --git a/embassy-stm32/src/timer/mod.rs b/embassy-stm32/src/timer/mod.rs index 4ffb2a28..839548a5 100644 --- a/embassy-stm32/src/timer/mod.rs +++ b/embassy-stm32/src/timer/mod.rs @@ -1,4 +1,5 @@ pub mod complementary_pwm; +pub mod qei; pub mod simple_pwm; use stm32_metapac::timer::vals; @@ -211,6 +212,7 @@ macro_rules! impl_basic_16bit_timer { use core::convert::TryInto; let f = frequency.0; let timer_f = Self::frequency().0; + assert!(f > 0); let pclk_ticks_per_timer_period = timer_f / f; let psc: u16 = unwrap!(((pclk_ticks_per_timer_period - 1) / (1 << 16)).try_into()); let arr: u16 = unwrap!((pclk_ticks_per_timer_period / (u32::from(psc) + 1)).try_into()); @@ -255,6 +257,7 @@ macro_rules! impl_32bit_timer { fn set_frequency(&mut self, frequency: Hertz) { use core::convert::TryInto; let f = frequency.0; + assert!(f > 0); let timer_f = Self::frequency().0; let pclk_ticks_per_timer_period = (timer_f / f) as u64; let psc: u16 = unwrap!(((pclk_ticks_per_timer_period - 1) / (1 << 32)).try_into()); diff --git a/embassy-stm32/src/timer/qei.rs b/embassy-stm32/src/timer/qei.rs new file mode 100644 index 00000000..c0f9288a --- /dev/null +++ b/embassy-stm32/src/timer/qei.rs @@ -0,0 +1,96 @@ +use core::marker::PhantomData; + +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use super::*; +use crate::gpio::sealed::AFType; +use crate::gpio::AnyPin; +use crate::Peripheral; + +pub enum Direction { + Upcounting, + Downcounting, +} + +pub struct Ch1; +pub struct Ch2; + +pub struct QeiPin<'d, Perip, Channel> { + _pin: PeripheralRef<'d, AnyPin>, + phantom: PhantomData<(Perip, Channel)>, +} + +macro_rules! channel_impl { + ($new_chx:ident, $channel:ident, $pin_trait:ident) => { + impl<'d, Perip: CaptureCompare16bitInstance> QeiPin<'d, Perip, $channel> { + pub fn $new_chx(pin: impl Peripheral

> + 'd) -> Self { + into_ref!(pin); + critical_section::with(|_| { + pin.set_low(); + pin.set_as_af(pin.af_num(), AFType::Input); + #[cfg(gpio_v2)] + pin.set_speed(crate::gpio::Speed::VeryHigh); + }); + QeiPin { + _pin: pin.map_into(), + phantom: PhantomData, + } + } + } + }; +} + +channel_impl!(new_ch1, Ch1, Channel1Pin); +channel_impl!(new_ch2, Ch2, Channel2Pin); + +pub struct SimplePwm<'d, T> { + _inner: PeripheralRef<'d, T>, +} + +impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> { + pub fn new(tim: impl Peripheral

+ 'd, _ch1: QeiPin<'d, T, Ch1>, _ch2: QeiPin<'d, T, Ch2>) -> Self { + Self::new_inner(tim) + } + + fn new_inner(tim: impl Peripheral

+ 'd) -> Self { + into_ref!(tim); + + T::enable(); + ::reset(); + + // Configure TxC1 and TxC2 as captures + T::regs_gp16().ccmr_input(0).modify(|w| { + w.set_ccs(0, vals::CcmrInputCcs::TI4); + w.set_ccs(1, vals::CcmrInputCcs::TI4); + }); + + // enable and configure to capture on rising edge + T::regs_gp16().ccer().modify(|w| { + w.set_cce(0, true); + w.set_cce(1, true); + + w.set_ccp(0, false); + w.set_ccp(1, false); + }); + + T::regs_gp16().smcr().modify(|w| { + w.set_sms(vals::Sms::ENCODER_MODE_3); + }); + + T::regs_gp16().arr().modify(|w| w.set_arr(u16::MAX)); + T::regs_gp16().cr1().modify(|w| w.set_cen(true)); + + Self { _inner: tim } + } + + pub fn read_direction(&self) -> Direction { + match T::regs_gp16().cr1().read().dir() { + vals::Dir::DOWN => Direction::Downcounting, + vals::Dir::UP => Direction::Upcounting, + } + } + + pub fn count(&self) -> u16 { + T::regs_gp16().cnt().read().cnt() + } +} diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index e203336e..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| { 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-sync/src/fmt.rs b/embassy-sync/src/fmt.rs index 06697081..78e583c1 100644 --- a/embassy-sync/src/fmt.rs +++ b/embassy-sync/src/fmt.rs @@ -1,6 +1,8 @@ #![macro_use] #![allow(unused_macros)] +use core::fmt::{Debug, Display, LowerHex}; + #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); @@ -81,14 +83,17 @@ macro_rules! todo { }; } +#[cfg(not(feature = "defmt"))] macro_rules! unreachable { ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::unreachable!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::unreachable!($($x)*); - } + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) }; } @@ -223,3 +228,31 @@ impl Try for Result { self } } + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-sync/src/pipe.rs b/embassy-sync/src/pipe.rs index 21d451ea..ec0cbbf2 100644 --- a/embassy-sync/src/pipe.rs +++ b/embassy-sync/src/pipe.rs @@ -1,7 +1,8 @@ //! Async byte stream pipe. -use core::cell::RefCell; +use core::cell::{RefCell, UnsafeCell}; use core::future::Future; +use core::ops::Range; use core::pin::Pin; use core::task::{Context, Poll}; @@ -82,17 +83,6 @@ where pipe: &'p Pipe, } -impl<'p, M, const N: usize> Clone for Reader<'p, M, N> -where - M: RawMutex, -{ - fn clone(&self) -> Self { - Reader { pipe: self.pipe } - } -} - -impl<'p, M, const N: usize> Copy for Reader<'p, M, N> where M: RawMutex {} - impl<'p, M, const N: usize> Reader<'p, M, N> where M: RawMutex, @@ -110,6 +100,29 @@ where pub fn try_read(&self, buf: &mut [u8]) -> Result { self.pipe.try_read(buf) } + + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + /// + /// If no bytes are currently available to read, this function waits until at least one byte is available. + /// + /// If the reader is at end-of-file (EOF), an empty slice is returned. + pub fn fill_buf(&mut self) -> FillBufFuture<'_, M, N> { + FillBufFuture { pipe: Some(self.pipe) } + } + + /// Try returning contents of the internal buffer. + /// + /// If no bytes are currently available to read, this function returns `Err(TryReadError::Empty)`. + /// + /// If the reader is at end-of-file (EOF), an empty slice is returned. + pub fn try_fill_buf(&mut self) -> Result<&[u8], TryReadError> { + unsafe { self.pipe.try_fill_buf_with_context(None) } + } + + /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. + pub fn consume(&mut self, amt: usize) { + self.pipe.consume(amt) + } } /// Future returned by [`Pipe::read`] and [`Reader::read`]. @@ -138,6 +151,35 @@ where impl<'p, M, const N: usize> Unpin for ReadFuture<'p, M, N> where M: RawMutex {} +/// Future returned by [`Pipe::fill_buf`] and [`Reader::fill_buf`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct FillBufFuture<'p, M, const N: usize> +where + M: RawMutex, +{ + pipe: Option<&'p Pipe>, +} + +impl<'p, M, const N: usize> Future for FillBufFuture<'p, M, N> +where + M: RawMutex, +{ + type Output = &'p [u8]; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let pipe = self.pipe.take().unwrap(); + match unsafe { pipe.try_fill_buf_with_context(Some(cx)) } { + Ok(buf) => Poll::Ready(buf), + Err(TryReadError::Empty) => { + self.pipe = Some(pipe); + Poll::Pending + } + } + } +} + +impl<'p, M, const N: usize> Unpin for FillBufFuture<'p, M, N> where M: RawMutex {} + /// Error returned by [`try_read`](Pipe::try_read). #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -162,67 +204,24 @@ struct PipeState { write_waker: WakerRegistration, } -impl PipeState { - const fn new() -> Self { - PipeState { - buffer: RingBuffer::new(), - read_waker: WakerRegistration::new(), - write_waker: WakerRegistration::new(), - } +#[repr(transparent)] +struct Buffer(UnsafeCell<[u8; N]>); + +impl Buffer { + unsafe fn get<'a>(&self, r: Range) -> &'a [u8] { + let p = self.0.get() as *const u8; + core::slice::from_raw_parts(p.add(r.start), r.end - r.start) } - fn clear(&mut self) { - self.buffer.clear(); - self.write_waker.wake(); - } - - fn try_read(&mut self, buf: &mut [u8]) -> Result { - self.try_read_with_context(None, buf) - } - - fn try_read_with_context(&mut self, cx: Option<&mut Context<'_>>, buf: &mut [u8]) -> Result { - if self.buffer.is_full() { - self.write_waker.wake(); - } - - let available = self.buffer.pop_buf(); - if available.is_empty() { - if let Some(cx) = cx { - self.read_waker.register(cx.waker()); - } - return Err(TryReadError::Empty); - } - - let n = available.len().min(buf.len()); - buf[..n].copy_from_slice(&available[..n]); - self.buffer.pop(n); - Ok(n) - } - - fn try_write(&mut self, buf: &[u8]) -> Result { - self.try_write_with_context(None, buf) - } - - fn try_write_with_context(&mut self, cx: Option<&mut Context<'_>>, buf: &[u8]) -> Result { - if self.buffer.is_empty() { - self.read_waker.wake(); - } - - let available = self.buffer.push_buf(); - if available.is_empty() { - if let Some(cx) = cx { - self.write_waker.register(cx.waker()); - } - return Err(TryWriteError::Full); - } - - let n = available.len().min(buf.len()); - available[..n].copy_from_slice(&buf[..n]); - self.buffer.push(n); - Ok(n) + unsafe fn get_mut<'a>(&self, r: Range) -> &'a mut [u8] { + let p = self.0.get() as *mut u8; + core::slice::from_raw_parts_mut(p.add(r.start), r.end - r.start) } } +unsafe impl Send for Buffer {} +unsafe impl Sync for Buffer {} + /// A bounded byte-oriented pipe for communicating between asynchronous tasks /// with backpressure. /// @@ -234,6 +233,7 @@ pub struct Pipe where M: RawMutex, { + buf: Buffer, inner: Mutex>>, } @@ -252,7 +252,12 @@ where /// ``` pub const fn new() -> Self { Self { - inner: Mutex::new(RefCell::new(PipeState::new())), + buf: Buffer(UnsafeCell::new([0; N])), + inner: Mutex::new(RefCell::new(PipeState { + buffer: RingBuffer::new(), + read_waker: WakerRegistration::new(), + write_waker: WakerRegistration::new(), + })), } } @@ -261,21 +266,91 @@ where } fn try_read_with_context(&self, cx: Option<&mut Context<'_>>, buf: &mut [u8]) -> Result { - self.lock(|c| c.try_read_with_context(cx, buf)) + self.inner.lock(|rc: &RefCell>| { + let s = &mut *rc.borrow_mut(); + + if s.buffer.is_full() { + s.write_waker.wake(); + } + + let available = unsafe { self.buf.get(s.buffer.pop_buf()) }; + if available.is_empty() { + if let Some(cx) = cx { + s.read_waker.register(cx.waker()); + } + return Err(TryReadError::Empty); + } + + let n = available.len().min(buf.len()); + buf[..n].copy_from_slice(&available[..n]); + s.buffer.pop(n); + Ok(n) + }) + } + + // safety: While the returned slice is alive, + // no `read` or `consume` methods in the pipe must be called. + unsafe fn try_fill_buf_with_context(&self, cx: Option<&mut Context<'_>>) -> Result<&[u8], TryReadError> { + self.inner.lock(|rc: &RefCell>| { + let s = &mut *rc.borrow_mut(); + + if s.buffer.is_full() { + s.write_waker.wake(); + } + + let available = unsafe { self.buf.get(s.buffer.pop_buf()) }; + if available.is_empty() { + if let Some(cx) = cx { + s.read_waker.register(cx.waker()); + } + return Err(TryReadError::Empty); + } + + Ok(available) + }) + } + + fn consume(&self, amt: usize) { + self.inner.lock(|rc: &RefCell>| { + let s = &mut *rc.borrow_mut(); + let available = s.buffer.pop_buf(); + assert!(amt <= available.len()); + s.buffer.pop(amt); + }) } fn try_write_with_context(&self, cx: Option<&mut Context<'_>>, buf: &[u8]) -> Result { - self.lock(|c| c.try_write_with_context(cx, buf)) + self.inner.lock(|rc: &RefCell>| { + let s = &mut *rc.borrow_mut(); + + if s.buffer.is_empty() { + s.read_waker.wake(); + } + + let available = unsafe { self.buf.get_mut(s.buffer.push_buf()) }; + if available.is_empty() { + if let Some(cx) = cx { + s.write_waker.register(cx.waker()); + } + return Err(TryWriteError::Full); + } + + let n = available.len().min(buf.len()); + available[..n].copy_from_slice(&buf[..n]); + s.buffer.push(n); + Ok(n) + }) } - /// Get a writer for this pipe. - pub fn writer(&self) -> Writer<'_, M, N> { - Writer { pipe: self } - } - - /// Get a reader for this pipe. - pub fn reader(&self) -> Reader<'_, M, N> { - Reader { pipe: self } + /// Split this pipe into a BufRead-capable reader and a writer. + /// + /// The reader and writer borrow the current pipe mutably, so it is not + /// possible to use it directly while they exist. This is needed because + /// implementing `BufRead` requires there is a single reader. + /// + /// The writer is cloneable, the reader is not. + pub fn split(&mut self) -> (Reader<'_, M, N>, Writer<'_, M, N>) { + (Reader { pipe: self }, Writer { pipe: self }) } /// Write some bytes to the pipe. @@ -312,7 +387,7 @@ where /// or return an error if the pipe is empty. See [`write`](Self::write) for a variant /// that waits instead of returning an error. pub fn try_write(&self, buf: &[u8]) -> Result { - self.lock(|c| c.try_write(buf)) + self.try_write_with_context(None, buf) } /// Read some bytes from the pipe. @@ -339,12 +414,17 @@ where /// or return an error if the pipe is empty. See [`read`](Self::read) for a variant /// that waits instead of returning an error. pub fn try_read(&self, buf: &mut [u8]) -> Result { - self.lock(|c| c.try_read(buf)) + self.try_read_with_context(None, buf) } /// Clear the data in the pipe's buffer. pub fn clear(&self) { - self.lock(|c| c.clear()) + self.inner.lock(|rc: &RefCell>| { + let s = &mut *rc.borrow_mut(); + + s.buffer.clear(); + s.write_waker.wake(); + }) } /// Return whether the pipe is full (no free space in the buffer) @@ -433,6 +513,16 @@ mod io_impls { } } + impl embedded_io_async::BufRead for Reader<'_, M, N> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + Ok(Reader::fill_buf(self).await) + } + + fn consume(&mut self, amt: usize) { + Reader::consume(self, amt) + } + } + impl embedded_io_async::ErrorType for Writer<'_, M, N> { type Error = Infallible; } @@ -457,43 +547,39 @@ mod tests { use super::*; use crate::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; - fn capacity(c: &PipeState) -> usize { - N - c.buffer.len() - } - #[test] fn writing_once() { - let mut c = PipeState::<3>::new(); + let c = Pipe::::new(); assert!(c.try_write(&[1]).is_ok()); - assert_eq!(capacity(&c), 2); + assert_eq!(c.free_capacity(), 2); } #[test] fn writing_when_full() { - let mut c = PipeState::<3>::new(); + let c = Pipe::::new(); assert_eq!(c.try_write(&[42]), Ok(1)); assert_eq!(c.try_write(&[43]), Ok(1)); assert_eq!(c.try_write(&[44]), Ok(1)); assert_eq!(c.try_write(&[45]), Err(TryWriteError::Full)); - assert_eq!(capacity(&c), 0); + assert_eq!(c.free_capacity(), 0); } #[test] fn receiving_once_with_one_send() { - let mut c = PipeState::<3>::new(); + let c = Pipe::::new(); assert!(c.try_write(&[42]).is_ok()); let mut buf = [0; 16]; assert_eq!(c.try_read(&mut buf), Ok(1)); assert_eq!(buf[0], 42); - assert_eq!(capacity(&c), 3); + assert_eq!(c.free_capacity(), 3); } #[test] fn receiving_when_empty() { - let mut c = PipeState::<3>::new(); + let c = Pipe::::new(); let mut buf = [0; 16]; assert_eq!(c.try_read(&mut buf), Err(TryReadError::Empty)); - assert_eq!(capacity(&c), 3); + assert_eq!(c.free_capacity(), 3); } #[test] @@ -506,13 +592,37 @@ mod tests { } #[test] - fn cloning() { - let c = Pipe::::new(); - let r1 = c.reader(); - let w1 = c.writer(); + fn read_buf() { + let mut c = Pipe::::new(); + let (mut r, w) = c.split(); + assert!(w.try_write(&[42, 43]).is_ok()); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[42, 43]); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[42, 43]); + r.consume(1); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[43]); + r.consume(1); + assert_eq!(r.try_fill_buf(), Err(TryReadError::Empty)); + assert_eq!(w.try_write(&[44, 45, 46]), Ok(1)); + assert_eq!(w.try_write(&[45, 46]), Ok(2)); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[44]); // only one byte due to wraparound. + r.consume(1); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[45, 46]); + assert!(w.try_write(&[47]).is_ok()); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[45, 46, 47]); + r.consume(3); + } - let _ = r1.clone(); - let _ = w1.clone(); + #[test] + fn writer_is_cloneable() { + let mut c = Pipe::::new(); + let (_r, w) = c.split(); + let _ = w.clone(); } #[futures_test::test] diff --git a/embassy-sync/src/ring_buffer.rs b/embassy-sync/src/ring_buffer.rs index 52108402..d95ffa7c 100644 --- a/embassy-sync/src/ring_buffer.rs +++ b/embassy-sync/src/ring_buffer.rs @@ -1,5 +1,6 @@ +use core::ops::Range; + pub struct RingBuffer { - buf: [u8; N], start: usize, end: usize, empty: bool, @@ -8,27 +9,26 @@ pub struct RingBuffer { impl RingBuffer { pub const fn new() -> Self { Self { - buf: [0; N], start: 0, end: 0, empty: true, } } - pub fn push_buf(&mut self) -> &mut [u8] { + pub fn push_buf(&mut self) -> Range { if self.start == self.end && !self.empty { trace!(" ringbuf: push_buf empty"); - return &mut self.buf[..0]; + return 0..0; } let n = if self.start <= self.end { - self.buf.len() - self.end + N - self.end } else { self.start - self.end }; trace!(" ringbuf: push_buf {:?}..{:?}", self.end, self.end + n); - &mut self.buf[self.end..self.end + n] + self.end..self.end + n } pub fn push(&mut self, n: usize) { @@ -41,20 +41,20 @@ impl RingBuffer { self.empty = false; } - pub fn pop_buf(&mut self) -> &mut [u8] { + pub fn pop_buf(&mut self) -> Range { if self.empty { trace!(" ringbuf: pop_buf empty"); - return &mut self.buf[..0]; + return 0..0; } let n = if self.end <= self.start { - self.buf.len() - self.start + N - self.start } else { self.end - self.start }; trace!(" ringbuf: pop_buf {:?}..{:?}", self.start, self.start + n); - &mut self.buf[self.start..self.start + n] + self.start..self.start + n } pub fn pop(&mut self, n: usize) { @@ -93,8 +93,8 @@ impl RingBuffer { } fn wrap(&self, n: usize) -> usize { - assert!(n <= self.buf.len()); - if n == self.buf.len() { + assert!(n <= N); + if n == N { 0 } else { n @@ -110,37 +110,29 @@ mod tests { fn push_pop() { let mut rb: RingBuffer<4> = RingBuffer::new(); let buf = rb.push_buf(); - assert_eq!(4, buf.len()); - buf[0] = 1; - buf[1] = 2; - buf[2] = 3; - buf[3] = 4; + assert_eq!(0..4, buf); rb.push(4); let buf = rb.pop_buf(); - assert_eq!(4, buf.len()); - assert_eq!(1, buf[0]); + assert_eq!(0..4, buf); rb.pop(1); let buf = rb.pop_buf(); - assert_eq!(3, buf.len()); - assert_eq!(2, buf[0]); + assert_eq!(1..4, buf); rb.pop(1); let buf = rb.pop_buf(); - assert_eq!(2, buf.len()); - assert_eq!(3, buf[0]); + assert_eq!(2..4, buf); rb.pop(1); let buf = rb.pop_buf(); - assert_eq!(1, buf.len()); - assert_eq!(4, buf[0]); + assert_eq!(3..4, buf); rb.pop(1); let buf = rb.pop_buf(); - assert_eq!(0, buf.len()); + assert_eq!(0..0, buf); let buf = rb.push_buf(); - assert_eq!(4, buf.len()); + assert_eq!(0..4, buf); } } diff --git a/embassy-time/CHANGELOG.md b/embassy-time/CHANGELOG.md index 26640d93..8bf02dbc 100644 --- a/embassy-time/CHANGELOG.md +++ b/embassy-time/CHANGELOG.md @@ -5,6 +5,11 @@ 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). +## 0.1.3 - 2023-08-28 + +- Update `embedded-hal-async` to `1.0.0-rc.1` +- Update `embedded-hal v1` to `1.0.0-rc.1` + ## 0.1.2 - 2023-07-05 - Update `embedded-hal-async` to `0.2.0-alpha.2`. @@ -26,4 +31,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 0.1.0 - 2022-08-26 -- First release \ No newline at end of file +- First release diff --git a/embassy-time/Cargo.toml b/embassy-time/Cargo.toml index ec1f2ec3..03aa6ca2 100644 --- a/embassy-time/Cargo.toml +++ b/embassy-time/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-time" -version = "0.1.2" +version = "0.1.3" edition = "2021" description = "Instant and Duration for embedded no-std systems, with async timer support" repository = "https://github.com/embassy-rs/embassy" @@ -169,4 +169,4 @@ wasm-timer = { version = "0.2.5", optional = true } [dev-dependencies] serial_test = "0.9" critical-section = { version = "1.1", features = ["std"] } -embassy-executor = { version = "0.2.0", path = "../embassy-executor", features = ["nightly"] } +embassy-executor = { version = "0.3.0", path = "../embassy-executor", features = ["nightly"] } diff --git a/embassy-time/src/fmt.rs b/embassy-time/src/fmt.rs index 06697081..78e583c1 100644 --- a/embassy-time/src/fmt.rs +++ b/embassy-time/src/fmt.rs @@ -1,6 +1,8 @@ #![macro_use] #![allow(unused_macros)] +use core::fmt::{Debug, Display, LowerHex}; + #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); @@ -81,14 +83,17 @@ macro_rules! todo { }; } +#[cfg(not(feature = "defmt"))] macro_rules! unreachable { ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::unreachable!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::unreachable!($($x)*); - } + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) }; } @@ -223,3 +228,31 @@ impl Try for Result { self } } + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} 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/fmt.rs b/embassy-usb/src/fmt.rs index 06697081..78e583c1 100644 --- a/embassy-usb/src/fmt.rs +++ b/embassy-usb/src/fmt.rs @@ -1,6 +1,8 @@ #![macro_use] #![allow(unused_macros)] +use core::fmt::{Debug, Display, LowerHex}; + #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); @@ -81,14 +83,17 @@ macro_rules! todo { }; } +#[cfg(not(feature = "defmt"))] macro_rules! unreachable { ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::unreachable!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::unreachable!($($x)*); - } + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) }; } @@ -223,3 +228,31 @@ impl Try for Result { self } } + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/examples/boot/application/nrf/Cargo.toml b/examples/boot/application/nrf/Cargo.toml index 2a0cf781..49f6eb13 100644 --- a/examples/boot/application/nrf/Cargo.toml +++ b/examples/boot/application/nrf/Cargo.toml @@ -6,8 +6,8 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] } -embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly"] } +embassy-executor = { version = "0.3.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.1.3", path = "../../../../embassy-time", features = ["nightly"] } embassy-nrf = { version = "0.1.0", path = "../../../../embassy-nrf", features = ["time-driver-rtc1", "gpiote", "nightly"] } embassy-boot = { version = "0.1.0", path = "../../../../embassy-boot/boot", features = ["nightly"] } embassy-boot-nrf = { version = "0.1.0", path = "../../../../embassy-boot/nrf", features = ["nightly"] } diff --git a/examples/boot/application/rp/Cargo.toml b/examples/boot/application/rp/Cargo.toml index 95b2da95..e9d4fb32 100644 --- a/examples/boot/application/rp/Cargo.toml +++ b/examples/boot/application/rp/Cargo.toml @@ -6,8 +6,8 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] } -embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly"] } +embassy-executor = { version = "0.3.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.1.3", path = "../../../../embassy-time", features = ["nightly"] } embassy-rp = { version = "0.1.0", path = "../../../../embassy-rp", features = ["time-driver", "unstable-traits", "nightly"] } embassy-boot-rp = { version = "0.1.0", path = "../../../../embassy-boot/rp", features = ["nightly"] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } diff --git a/examples/boot/application/rp/src/bin/a.rs b/examples/boot/application/rp/src/bin/a.rs index 15fdaca8..a4602a7e 100644 --- a/examples/boot/application/rp/src/bin/a.rs +++ b/examples/boot/application/rp/src/bin/a.rs @@ -38,7 +38,7 @@ async fn main(_s: Spawner) { let flash = Mutex::new(RefCell::new(flash)); let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash); - let mut aligned = AlignedBuffer([0; 4]); + let mut aligned = AlignedBuffer([0; 1]); let mut updater = BlockingFirmwareUpdater::new(config, &mut aligned.0); Timer::after(Duration::from_secs(5)).await; diff --git a/examples/boot/application/stm32f3/Cargo.toml b/examples/boot/application/stm32f3/Cargo.toml index 3b0fc4d9..6a6f967b 100644 --- a/examples/boot/application/stm32f3/Cargo.toml +++ b/examples/boot/application/stm32f3/Cargo.toml @@ -6,8 +6,8 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } -embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32f303re", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = ["nightly"] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } diff --git a/examples/boot/application/stm32f7/Cargo.toml b/examples/boot/application/stm32f7/Cargo.toml index 323b4ab2..55a631e9 100644 --- a/examples/boot/application/stm32f7/Cargo.toml +++ b/examples/boot/application/stm32f7/Cargo.toml @@ -6,8 +6,8 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } -embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32f767zi", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = ["nightly"] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } diff --git a/examples/boot/application/stm32h7/Cargo.toml b/examples/boot/application/stm32h7/Cargo.toml index b2abdc89..ff960f22 100644 --- a/examples/boot/application/stm32h7/Cargo.toml +++ b/examples/boot/application/stm32h7/Cargo.toml @@ -6,8 +6,8 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } -embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32h743zi", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = ["nightly"] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } diff --git a/examples/boot/application/stm32l0/Cargo.toml b/examples/boot/application/stm32l0/Cargo.toml index 0b7e72d5..a0f88585 100644 --- a/examples/boot/application/stm32l0/Cargo.toml +++ b/examples/boot/application/stm32l0/Cargo.toml @@ -6,8 +6,8 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } -embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32l072cz", "time-driver-any", "exti", "memory-x"] } embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = ["nightly"] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } diff --git a/examples/boot/application/stm32l1/Cargo.toml b/examples/boot/application/stm32l1/Cargo.toml index 5f3f365c..1a251df3 100644 --- a/examples/boot/application/stm32l1/Cargo.toml +++ b/examples/boot/application/stm32l1/Cargo.toml @@ -6,8 +6,8 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } -embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32l151cb-a", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = ["nightly"] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } diff --git a/examples/boot/application/stm32l4/Cargo.toml b/examples/boot/application/stm32l4/Cargo.toml index 44eb5aba..8d1c8af1 100644 --- a/examples/boot/application/stm32l4/Cargo.toml +++ b/examples/boot/application/stm32l4/Cargo.toml @@ -6,8 +6,8 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } -embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32l475vg", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = ["nightly"] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } diff --git a/examples/boot/application/stm32wl/Cargo.toml b/examples/boot/application/stm32wl/Cargo.toml index fdad5506..441b355a 100644 --- a/examples/boot/application/stm32wl/Cargo.toml +++ b/examples/boot/application/stm32wl/Cargo.toml @@ -6,8 +6,8 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } -embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32wl55jc-cm4", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = ["nightly"] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } diff --git a/examples/nrf-rtos-trace/Cargo.toml b/examples/nrf-rtos-trace/Cargo.toml index 068474e7..ed6aca81 100644 --- a/examples/nrf-rtos-trace/Cargo.toml +++ b/examples/nrf-rtos-trace/Cargo.toml @@ -17,8 +17,8 @@ log = [ [dependencies] embassy-sync = { version = "0.2.0", path = "../../embassy-sync" } -embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "rtos-trace", "rtos-trace-interrupt", "integrated-timers"] } -embassy-time = { version = "0.1.2", path = "../../embassy-time" } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "rtos-trace", "rtos-trace-interrupt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time" } embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } diff --git a/examples/nrf52840-rtic/Cargo.toml b/examples/nrf52840-rtic/Cargo.toml index 715f1ecf..02a7f98b 100644 --- a/examples/nrf52840-rtic/Cargo.toml +++ b/examples/nrf52840-rtic/Cargo.toml @@ -9,7 +9,7 @@ rtic = { version = "2", features = ["thumbv7-backend"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["nightly", "unstable-traits", "defmt", "defmt-timestamp-uptime", "generic-queue"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["nightly", "unstable-traits", "defmt", "defmt-timestamp-uptime", "generic-queue"] } embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["nightly", "unstable-traits", "defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } defmt = "0.3" diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml index 2ce44b51..8b7121ab 100644 --- a/examples/nrf52840/Cargo.toml +++ b/examples/nrf52840/Cargo.toml @@ -30,8 +30,8 @@ nightly = [ [dependencies] embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"], optional = true } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt", "msos-descriptor",], optional = true } @@ -67,4 +67,4 @@ microfft = "0.5.0" debug = 2 [patch.crates-io] -lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "1323eccc1c470d4259f95f4f315d1be830d572a3"} \ No newline at end of file +lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "1323eccc1c470d4259f95f4f315d1be830d572a3"} 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/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/nrf5340/Cargo.toml b/examples/nrf5340/Cargo.toml index 57660f12..e423ad95 100644 --- a/examples/nrf5340/Cargo.toml +++ b/examples/nrf5340/Cargo.toml @@ -9,12 +9,12 @@ embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = [ "defmt", ] } -embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "defmt", "integrated-timers", ] } -embassy-time = { version = "0.1.2", path = "../../embassy-time", features = [ +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = [ "defmt", "defmt-timestamp-uptime", ] } diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index 600b6de7..b4f25024 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal", features = ["defmt"] } 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", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["nightly", "unstable-traits", "defmt", "defmt-timestamp-uptime"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["nightly", "unstable-traits", "defmt", "defmt-timestamp-uptime"] } 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"] } @@ -57,4 +57,4 @@ rand = { version = "0.8.5", default-features = false } debug = 2 [patch.crates-io] -lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "1323eccc1c470d4259f95f4f315d1be830d572a3"} \ No newline at end of file +lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "1323eccc1c470d4259f95f4f315d1be830d572a3"} 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/rp/src/bin/pio_uart.rs b/examples/rp/src/bin/pio_uart.rs index 707c99b7..aa9e52cb 100644 --- a/examples/rp/src/bin/pio_uart.rs +++ b/examples/rp/src/bin/pio_uart.rs @@ -91,13 +91,11 @@ async fn main(_spawner: Spawner) { let (mut uart_tx, mut uart_rx) = uart.split(); // Pipe setup - let usb_pipe: Pipe = Pipe::new(); - let mut usb_pipe_writer = usb_pipe.writer(); - let mut usb_pipe_reader = usb_pipe.reader(); + let mut usb_pipe: Pipe = Pipe::new(); + let (mut usb_pipe_reader, mut usb_pipe_writer) = usb_pipe.split(); - let uart_pipe: Pipe = Pipe::new(); - let mut uart_pipe_writer = uart_pipe.writer(); - let mut uart_pipe_reader = uart_pipe.reader(); + let mut uart_pipe: Pipe = Pipe::new(); + let (mut uart_pipe_reader, mut uart_pipe_writer) = uart_pipe.split(); let (mut usb_tx, mut usb_rx) = class.split(); diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 3e26d2e2..ca061d68 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -6,13 +6,15 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["log"] } -embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-std", "executor-thread", "log", "nightly", "integrated-timers"] } -embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["log", "std", "nightly"] } -embassy-net = { version = "0.1.0", path = "../../embassy-net", features=[ "std", "nightly", "log", "medium-ethernet", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["arch-std", "executor-thread", "log", "nightly", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["log", "std", "nightly"] } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features=[ "std", "nightly", "log", "medium-ethernet", "medium-ip", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6"] } embassy-net-tuntap = { version = "0.1.0", path = "../../embassy-net-tuntap" } +embassy-net-ppp = { version = "0.1.0", path = "../../embassy-net-ppp", features = ["log"]} embedded-io-async = { version = "0.5.0" } embedded-io-adapters = { version = "0.5.0", features = ["futures-03"] } critical-section = { version = "1.1", features = ["std"] } +smoltcp = { version = "0.10.0", features = ["dns-max-server-count-4"] } async-io = "1.6.0" env_logger = "0.9.0" diff --git a/examples/std/src/bin/net_ppp.rs b/examples/std/src/bin/net_ppp.rs new file mode 100644 index 00000000..9cf6e19d --- /dev/null +++ b/examples/std/src/bin/net_ppp.rs @@ -0,0 +1,218 @@ +//! Testing against pppd: +//! +//! echo myuser $(hostname) mypass 192.168.7.10 >> /etc/ppp/pap-secrets +//! socat -v -x PTY,link=pty1,rawer PTY,link=pty2,rawer +//! sudo pppd $PWD/pty1 115200 192.168.7.1: ms-dns 8.8.4.4 ms-dns 8.8.8.8 nodetach debug local persist silent noproxyarp +//! RUST_LOG=trace cargo run --bin net_ppp -- --device pty2 +//! ping 192.168.7.10 +//! nc 192.168.7.10 1234 + +#![feature(type_alias_impl_trait)] +#![feature(async_fn_in_trait, impl_trait_projections)] + +#[path = "../serial_port.rs"] +mod serial_port; + +use async_io::Async; +use clap::Parser; +use embassy_executor::{Executor, Spawner}; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Config, ConfigV4, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net_ppp::Runner; +use embedded_io_async::Write; +use futures::io::BufReader; +use heapless::Vec; +use log::*; +use nix::sys::termios; +use rand_core::{OsRng, RngCore}; +use static_cell::{make_static, StaticCell}; + +use crate::serial_port::SerialPort; + +#[derive(Parser)] +#[clap(version = "1.0")] +struct Opts { + /// Serial port device name + #[clap(short, long)] + device: String, +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::task] +async fn ppp_task( + stack: &'static Stack>, + mut runner: Runner<'static>, + port: SerialPort, +) -> ! { + let port = Async::new(port).unwrap(); + let port = BufReader::new(port); + let port = adapter::FromFutures::new(port); + + let config = embassy_net_ppp::Config { + username: b"myuser", + password: b"mypass", + }; + + runner + .run(port, config, |ipv4| { + let Some(addr) = ipv4.address else { + warn!("PPP did not provide an IP address."); + return; + }; + let mut dns_servers = Vec::new(); + for s in ipv4.dns_servers.iter().flatten() { + let _ = dns_servers.push(Ipv4Address::from_bytes(&s.0)); + } + let config = ConfigV4::Static(embassy_net::StaticConfigV4 { + address: Ipv4Cidr::new(Ipv4Address::from_bytes(&addr.0), 0), + gateway: None, + dns_servers, + }); + stack.set_config_v4(config); + }) + .await + .unwrap(); + unreachable!() +} + +#[embassy_executor::task] +async fn main_task(spawner: Spawner) { + let opts: Opts = Opts::parse(); + + // Open serial port + let baudrate = termios::BaudRate::B115200; + let port = SerialPort::new(opts.device.as_str(), baudrate).unwrap(); + + // Init network device + let state = make_static!(embassy_net_ppp::State::<4, 4>::new()); + let (device, runner) = embassy_net_ppp::new(state); + + // Generate random seed + let mut seed = [0; 8]; + OsRng.fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + let stack = &*make_static!(Stack::new( + device, + Config::default(), // don't configure IP yet + make_static!(StackResources::<3>::new()), + seed + )); + + // Launch network task + spawner.spawn(net_task(stack)).unwrap(); + spawner.spawn(ppp_task(stack, runner, port)).unwrap(); + + // Then 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; + } + }; + } + } +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +fn main() { + env_logger::builder() + .filter_level(log::LevelFilter::Trace) + .filter_module("polling", log::LevelFilter::Info) + .filter_module("async_io", log::LevelFilter::Info) + .format_timestamp_nanos() + .init(); + + let executor = EXECUTOR.init(Executor::new()); + executor.run(|spawner| { + spawner.spawn(main_task(spawner)).unwrap(); + }); +} + +mod adapter { + use core::future::poll_fn; + use core::pin::Pin; + + use futures::AsyncBufReadExt; + + /// Adapter from `futures::io` traits. + #[derive(Clone)] + pub struct FromFutures { + inner: T, + } + + impl FromFutures { + /// Create a new adapter. + pub fn new(inner: T) -> Self { + Self { inner } + } + } + + impl embedded_io_async::ErrorType for FromFutures { + type Error = std::io::Error; + } + + impl embedded_io_async::Read for FromFutures { + async fn read(&mut self, buf: &mut [u8]) -> Result { + poll_fn(|cx| Pin::new(&mut self.inner).poll_read(cx, buf)).await + } + } + + impl embedded_io_async::BufRead for FromFutures { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.inner.fill_buf().await + } + + fn consume(&mut self, amt: usize) { + Pin::new(&mut self.inner).consume(amt) + } + } + + impl embedded_io_async::Write for FromFutures { + async fn write(&mut self, buf: &[u8]) -> Result { + poll_fn(|cx| Pin::new(&mut self.inner).poll_write(cx, buf)).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + poll_fn(|cx| Pin::new(&mut self.inner).poll_flush(cx)).await + } + } +} diff --git a/examples/stm32c0/Cargo.toml b/examples/stm32c0/Cargo.toml index 8534921a..a691be42 100644 --- a/examples/stm32c0/Cargo.toml +++ b/examples/stm32c0/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0" # Change stm32c031c6 to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "time-driver-any", "stm32c031c6", "memory-x", "unstable-pac", "exti"] } 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", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } defmt = "0.3" defmt-rtt = "0.4" diff --git a/examples/stm32f0/Cargo.toml b/examples/stm32f0/Cargo.toml index 46b6db45..894681d0 100644 --- a/examples/stm32f0/Cargo.toml +++ b/examples/stm32f0/Cargo.toml @@ -15,8 +15,8 @@ defmt = "0.3" defmt-rtt = "0.4" panic-probe = "0.3" 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", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } static_cell = { version = "1.1", features = ["nightly"]} [profile.release] diff --git a/examples/stm32f1/Cargo.toml b/examples/stm32f1/Cargo.toml index 5d32992c..bec7bf65 100644 --- a/examples/stm32f1/Cargo.toml +++ b/examples/stm32f1/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0" # Change stm32f103c8 to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f103c8", "unstable-pac", "memory-x", "time-driver-any", "unstable-traits" ] } 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", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/examples/stm32f2/Cargo.toml b/examples/stm32f2/Cargo.toml index 9857fb63..7cc5747d 100644 --- a/examples/stm32f2/Cargo.toml +++ b/examples/stm32f2/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0" # Change stm32f207zg to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f207zg", "unstable-pac", "memory-x", "time-driver-any", "exti"] } 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", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } defmt = "0.3" defmt-rtt = "0.4" diff --git a/examples/stm32f3/Cargo.toml b/examples/stm32f3/Cargo.toml index bd594d16..ffacece5 100644 --- a/examples/stm32f3/Cargo.toml +++ b/examples/stm32f3/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0" # Change stm32f303ze to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f303ze", "unstable-pac", "memory-x", "time-driver-any", "exti"] } 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", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } 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/stm32f334/Cargo.toml b/examples/stm32f334/Cargo.toml index d8f6b8fe..65183b88 100644 --- a/examples/stm32f334/Cargo.toml +++ b/examples/stm32f334/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] 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", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f334r8", "unstable-pac", "memory-x", "time-driver-any", "exti"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } diff --git a/examples/stm32f4/Cargo.toml b/examples/stm32f4/Cargo.toml index 65a4e51f..ca517df7 100644 --- a/examples/stm32f4/Cargo.toml +++ b/examples/stm32f4/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0" # Change stm32f429zi to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-any", "exti", "embedded-sdmmc", "chrono"] } 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", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "nightly"] } diff --git a/examples/stm32f4/src/bin/rtc.rs b/examples/stm32f4/src/bin/rtc.rs index 0eca5820..23ff8ac4 100644 --- a/examples/stm32f4/src/bin/rtc.rs +++ b/examples/stm32f4/src/bin/rtc.rs @@ -5,13 +5,17 @@ use chrono::{NaiveDate, NaiveDateTime}; use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::rtc::{Rtc, RtcConfig}; +use embassy_stm32::rtc::{Rtc, RtcClockSource, RtcConfig}; +use embassy_stm32::Config; use embassy_time::{Duration, Timer}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p = embassy_stm32::init(Default::default()); + let mut config = Config::default(); + config.rcc.rtc = Option::Some(RtcClockSource::LSI); + let p = embassy_stm32::init(config); + info!("Hello World!"); let now = NaiveDate::from_ymd_opt(2020, 5, 15) @@ -23,8 +27,11 @@ async fn main(_spawner: Spawner) { rtc.set_datetime(now.into()).expect("datetime not set"); - // In reality the delay would be much longer - Timer::after(Duration::from_millis(20000)).await; + loop { + let now: NaiveDateTime = rtc.now().unwrap().into(); - let _then: NaiveDateTime = rtc.now().unwrap().into(); + info!("{}", now.timestamp()); + + Timer::after(Duration::from_millis(1000)).await; + } } diff --git a/examples/stm32f7/Cargo.toml b/examples/stm32f7/Cargo.toml index 41170f2c..f7a7fb8c 100644 --- a/examples/stm32f7/Cargo.toml +++ b/examples/stm32f7/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0" # Change stm32f767zi to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f767zi", "memory-x", "unstable-pac", "time-driver-any", "exti"] } 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", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet"] } embedded-io-async = { version = "0.5.0" } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } diff --git a/examples/stm32g0/Cargo.toml b/examples/stm32g0/Cargo.toml index b4dfe3c6..474c2af8 100644 --- a/examples/stm32g0/Cargo.toml +++ b/examples/stm32g0/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0" # Change stm32g071rb to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "time-driver-any", "stm32g071rb", "memory-x", "unstable-pac", "exti"] } 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", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } defmt = "0.3" defmt-rtt = "0.4" diff --git a/examples/stm32g4/Cargo.toml b/examples/stm32g4/Cargo.toml index cf3e2ce9..0c1cdd67 100644 --- a/examples/stm32g4/Cargo.toml +++ b/examples/stm32g4/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0" # Change stm32g491re to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "time-driver-any", "stm32g491re", "memory-x", "unstable-pac", "exti"] } 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", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } defmt = "0.3" diff --git a/examples/stm32h5/Cargo.toml b/examples/stm32h5/Cargo.toml index 5d73e435..d4b7d18c 100644 --- a/examples/stm32h5/Cargo.toml +++ b/examples/stm32h5/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0" # Change stm32h563zi to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32h563zi", "memory-x", "time-driver-any", "exti", "unstable-pac", "unstable-traits"] } 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", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] } embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6"] } embedded-io-async = { version = "0.5.0" } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } 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 c78c4c60..eda04e44 100644 --- a/examples/stm32h7/Cargo.toml +++ b/examples/stm32h7/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0" # Change stm32h743bi to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32h743bi", "time-driver-any", "exti", "memory-x", "unstable-pac", "unstable-traits"] } 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", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] } embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6"] } embedded-io-async = { version = "0.5.0" } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } 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 332a6c5e..d33e0378 100644 --- a/examples/stm32l0/Cargo.toml +++ b/examples/stm32l0/Cargo.toml @@ -13,8 +13,8 @@ nightly = ["embassy-stm32/nightly", "embassy-time/nightly", "embassy-time/unstab # Change stm32l072cz to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "time-driver-any", "exti", "unstable-traits", "memory-x"] } embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["time", "defmt"], optional = true } lora-phy = { version = "1", optional = true } lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"], optional = true } @@ -39,4 +39,4 @@ static_cell = "1.1" debug = 2 [patch.crates-io] -lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "1323eccc1c470d4259f95f4f315d1be830d572a3"} \ No newline at end of file +lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "1323eccc1c470d4259f95f4f315d1be830d572a3"} diff --git a/examples/stm32l1/Cargo.toml b/examples/stm32l1/Cargo.toml index 329d44ca..9bf633ce 100644 --- a/examples/stm32l1/Cargo.toml +++ b/examples/stm32l1/Cargo.toml @@ -6,8 +6,8 @@ license = "MIT OR Apache-2.0" [dependencies] 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", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32l151cb-a", "time-driver-any", "memory-x"] } defmt = "0.3" diff --git a/examples/stm32l4/.cargo/config.toml b/examples/stm32l4/.cargo/config.toml index 36e74e5a..db3a7cef 100644 --- a/examples/stm32l4/.cargo/config.toml +++ b/examples/stm32l4/.cargo/config.toml @@ -2,7 +2,7 @@ # replace STM32F429ZITx with your chip as listed in `probe-rs chip list` #runner = "probe-rs run --chip STM32L475VGT6" #runner = "probe-rs run --chip STM32L475VG" -runner = "probe-rs run --chip STM32L4S5VI" +runner = "probe-run --chip STM32L4S5QI" [build] target = "thumbv7em-none-eabi" diff --git a/examples/stm32l4/Cargo.toml b/examples/stm32l4/Cargo.toml index 944c8c27..5fe1499e 100644 --- a/examples/stm32l4/Cargo.toml +++ b/examples/stm32l4/Cargo.toml @@ -6,12 +6,17 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32l4s5vi to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l4s5vi", "memory-x", "time-driver-any", "exti", "unstable-traits", "chrono"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l4s5qi", "memory-x", "time-driver-any", "exti", "unstable-traits", "chrono"] } 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", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768", "unstable-traits", "nightly"] } embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-net-adin1110 = { version = "0.1.0", path = "../../embassy-net-adin1110", default-features = false } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "udp", "tcp", "dhcpv4", "medium-ethernet"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embedded-io-async = { version = "0.5.0", features = ["defmt-03"] } +embedded-io = { version = "0.5.0", features = ["defmt-03"] } defmt = "0.3" defmt-rtt = "0.4" @@ -21,10 +26,13 @@ cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" 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", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } heapless = { version = "0.7.5", default-features = false } chrono = { version = "^0.4", default-features = false } +rand = { version = "0.8.5", default-features = false } +static_cell = {version = "1.1", features = ["nightly"]} micromath = "2.0.0" diff --git a/examples/stm32l4/src/bin/rtc.rs b/examples/stm32l4/src/bin/rtc.rs index 294ea456..f3f8aa46 100644 --- a/examples/stm32l4/src/bin/rtc.rs +++ b/examples/stm32l4/src/bin/rtc.rs @@ -23,7 +23,7 @@ async fn main(_spawner: Spawner) { PLLMul::Mul20, None, ); - config.rcc.rtc_mux = rcc::RtcClockSource::LSE32; + config.rcc.rtc_mux = rcc::RtcClockSource::LSE; embassy_stm32::init(config) }; info!("Hello World!"); diff --git a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs new file mode 100644 index 00000000..0a677be7 --- /dev/null +++ b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs @@ -0,0 +1,438 @@ +#![deny(clippy::pedantic)] +#![allow(clippy::doc_markdown)] +#![no_main] +#![no_std] +// Needed unitl https://github.com/rust-lang/rust/issues/63063 is stablised. +#![feature(type_alias_impl_trait)] +#![feature(associated_type_bounds)] +#![allow(clippy::missing_errors_doc)] + +// This example works on a ANALOG DEVICE EVAL-ADIN110EBZ board. +// Settings switch S201 "HW CFG": +// - Without SPI CRC: OFF-ON-OFF-OFF-OFF +// - With SPI CRC: ON -ON-OFF-OFF-OFF +// Settings switch S303 "uC CFG": CFG0: On = static ip, Off = Dhcp +// The webserver shows the actual temperature of the onboard i2c temp sensor. + +use core::marker::PhantomData; +use core::sync::atomic::{AtomicI32, Ordering}; + +use defmt::{error, info, println, unwrap, Format}; +use defmt_rtt as _; // global logger +use embassy_executor::Spawner; +use embassy_futures::select::{select, Either}; +use embassy_futures::yield_now; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources, StaticConfigV4}; +use embassy_time::{Delay, Duration, Ticker, Timer}; +use embedded_hal_async::i2c::I2c as I2cBus; +use embedded_io::Write as bWrite; +use embedded_io_async::Write; +use hal::gpio::{Input, Level, Output, Speed}; +use hal::i2c::{self, I2c}; +use hal::rcc::{self}; +use hal::rng::{self, Rng}; +use hal::{bind_interrupts, exti, pac, peripherals}; +use heapless::Vec; +use rand::RngCore; +use static_cell::make_static; +use {embassy_stm32 as hal, panic_probe as _}; + +bind_interrupts!(struct Irqs { + I2C3_EV => i2c::InterruptHandler; + RNG => rng::InterruptHandler; +}); + +use embassy_net_adin1110::{self, Device, Runner, ADIN1110}; +use embedded_hal_bus::spi::ExclusiveDevice; +use hal::gpio::Pull; +use hal::i2c::Config as I2C_Config; +use hal::rcc::{ClockSrc, PLLClkDiv, PLLMul, PLLSource, PLLSrcDiv}; +use hal::spi::{Config as SPI_Config, Spi}; +use hal::time::Hertz; + +// Basic settings +// MAC-address used by the adin1110 +const MAC: [u8; 6] = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]; +// Static IP settings +const IP_ADDRESS: Ipv4Cidr = Ipv4Cidr::new(Ipv4Address([192, 168, 1, 5]), 24); +// Listen port for the webserver +const HTTP_LISTEN_PORT: u16 = 80; + +pub type SpeSpi = Spi<'static, peripherals::SPI2, peripherals::DMA1_CH1, peripherals::DMA1_CH2>; +pub type SpeSpiCs = ExclusiveDevice, Delay>; +pub type SpeInt = exti::ExtiInput<'static, peripherals::PB11>; +pub type SpeRst = Output<'static, peripherals::PC7>; +pub type Adin1110T = ADIN1110; +pub type TempSensI2c = I2c<'static, peripherals::I2C3, peripherals::DMA1_CH6, peripherals::DMA1_CH7>; + +static TEMP: AtomicI32 = AtomicI32::new(0); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + defmt::println!("Start main()"); + + let mut config = embassy_stm32::Config::default(); + + // 80Mhz clock (Source: 8 / SrcDiv: 1 * PLLMul 20 / ClkDiv 2) + // 80MHz highest frequency for flash 0 wait. + config.rcc.mux = ClockSrc::PLL( + PLLSource::HSE(Hertz(8_000_000)), + PLLClkDiv::Div2, + PLLSrcDiv::Div1, + PLLMul::Mul20, + None, + ); + config.rcc.hsi48 = true; // needed for rng + config.rcc.rtc_mux = rcc::RtcClockSource::LSI; + + let dp = embassy_stm32::init(config); + + // RM0432rev9, 5.1.2: Independent I/O supply rail + // After reset, the I/Os supplied by VDDIO2 are logically and electrically isolated and + // therefore are not available. The isolation must be removed before using any I/O from + // PG[15:2], by setting the IOSV bit in the PWR_CR2 register, once the VDDIO2 supply is present + pac::PWR.cr2().modify(|w| w.set_iosv(true)); + + let reset_status = pac::RCC.bdcr().read().0; + defmt::println!("bdcr before: 0x{:X}", reset_status); + + defmt::println!("Setup IO pins"); + + // Setup LEDs + let _led_uc1_green = Output::new(dp.PC13, Level::Low, Speed::Low); + let mut led_uc2_red = Output::new(dp.PE2, Level::High, Speed::Low); + let led_uc3_yellow = Output::new(dp.PE6, Level::High, Speed::Low); + let led_uc4_blue = Output::new(dp.PG15, Level::High, Speed::Low); + + // Read the uc_cfg switches + let uc_cfg0 = Input::new(dp.PB2, Pull::None); + let _uc_cfg1 = Input::new(dp.PF11, Pull::None); + let _uc_cfg2 = Input::new(dp.PG6, Pull::None); + let _uc_cfg3 = Input::new(dp.PG11, Pull::None); + + // Setup I2C pins + let temp_sens_i2c = I2c::new( + dp.I2C3, + dp.PG7, + dp.PG8, + Irqs, + dp.DMA1_CH6, + dp.DMA1_CH7, + Hertz(100_000), + I2C_Config::default(), + ); + + // Setup IO and SPI for the SPE chip + let spe_reset_n = Output::new(dp.PC7, Level::Low, Speed::Low); + let spe_cfg0 = Input::new(dp.PC8, Pull::None); + let spe_cfg1 = Input::new(dp.PC9, Pull::None); + let _spe_ts_capt = Output::new(dp.PC6, Level::Low, Speed::Low); + + let spe_int = Input::new(dp.PB11, Pull::None); + let spe_int = exti::ExtiInput::new(spe_int, dp.EXTI11); + + let spe_spi_cs_n = Output::new(dp.PB12, Level::High, Speed::High); + let spe_spi_sclk = dp.PB13; + let spe_spi_miso = dp.PB14; + let spe_spi_mosi = dp.PB15; + + // Don't turn the clock to high, clock must fit within the system clock as we get a runtime panic. + let mut spi_config = SPI_Config::default(); + spi_config.frequency = Hertz(25_000_000); + + let spe_spi: SpeSpi = Spi::new( + dp.SPI2, + spe_spi_sclk, + spe_spi_mosi, + spe_spi_miso, + dp.DMA1_CH1, + dp.DMA1_CH2, + spi_config, + ); + let spe_spi = SpeSpiCs::new(spe_spi, spe_spi_cs_n, Delay); + + let cfg0_without_crc = spe_cfg0.is_high(); + let cfg1_spi_mode = spe_cfg1.is_high(); + + defmt::println!( + "ADIN1110: CFG SPI-MODE 1-{}, CRC-bit 0-{}", + cfg1_spi_mode, + cfg0_without_crc + ); + + // Check the SPI mode selected with the "HW CFG" dip-switch + if !cfg1_spi_mode { + error!("Driver doesn´t support SPI Protolcol \"OPEN Alliance\".\nplease use the \"Generic SPI\"! Turn On \"HW CFG\": \"SPI_CFG1\""); + loop { + led_uc2_red.toggle(); + Timer::after(Duration::from_hz(10)).await; + } + }; + + let state = make_static!(embassy_net_adin1110::State::<8, 8>::new()); + + let (device, runner) = + embassy_net_adin1110::new(MAC, state, spe_spi, spe_int, spe_reset_n, !cfg0_without_crc).await; + + // Start task blink_led + unwrap!(spawner.spawn(heartbeat_led(led_uc3_yellow))); + // Start task temperature measurement + unwrap!(spawner.spawn(temp_task(temp_sens_i2c, led_uc4_blue))); + // Start ethernet task + unwrap!(spawner.spawn(ethernet_task(runner))); + + let mut rng = Rng::new(dp.RNG, Irqs); + // Generate random seed + let seed = rng.next_u64(); + + let ip_cfg = if uc_cfg0.is_low() { + println!("Waiting for DHCP..."); + let dhcp4_config = embassy_net::DhcpConfig::default(); + embassy_net::Config::dhcpv4(dhcp4_config) + } else { + embassy_net::Config::ipv4_static(StaticConfigV4 { + address: IP_ADDRESS, + gateway: None, + dns_servers: Vec::new(), + }) + }; + + // Init network stack + let stack = &*make_static!(Stack::new( + device, + ip_cfg, + make_static!(StackResources::<2>::new()), + seed + )); + + // Launch network task + unwrap!(spawner.spawn(net_task(stack))); + + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + + // Then we can use it! + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut mb_buf = [0; 4096]; + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(1))); + + info!("Listening on http://{}:{}...", local_addr, HTTP_LISTEN_PORT); + if let Err(e) = socket.accept(HTTP_LISTEN_PORT).await { + defmt::error!("accept error: {:?}", e); + continue; + } + + loop { + let _n = match socket.read(&mut mb_buf).await { + Ok(0) => { + defmt::info!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + defmt::error!("{:?}", e); + break; + } + }; + led_uc2_red.set_low(); + + let status_line = "HTTP/1.1 200 OK"; + let contents = PAGE; + let length = contents.len(); + + let _ = write!( + &mut mb_buf[..], + "{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}\r\n\0" + ); + let loc = mb_buf.iter().position(|v| *v == b'#').unwrap(); + + let temp = TEMP.load(Ordering::Relaxed); + let cel = temp / 1000; + let mcel = temp % 1000; + + info!("{}.{}", cel, mcel); + + let _ = write!(&mut mb_buf[loc..loc + 7], "{cel}.{mcel}"); + + let n = mb_buf.iter().position(|v| *v == 0).unwrap(); + + if let Err(e) = socket.write_all(&mb_buf[..n]).await { + error!("write error: {:?}", e); + break; + } + + led_uc2_red.set_high(); + } + } +} + +async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfigV4 { + loop { + if let Some(config) = stack.config_v4() { + return config; + } + yield_now().await; + } +} + +#[embassy_executor::task] +async fn heartbeat_led(mut led: Output<'static, peripherals::PE6>) { + let mut tmr = Ticker::every(Duration::from_hz(3)); + loop { + led.toggle(); + tmr.next().await; + } +} + +// ADT7422 +#[embassy_executor::task] +async fn temp_task(temp_dev_i2c: TempSensI2c, mut led: Output<'static, peripherals::PG15>) -> ! { + let mut tmr = Ticker::every(Duration::from_hz(1)); + let mut temp_sens = ADT7422::new(temp_dev_i2c, 0x48).unwrap(); + + loop { + led.set_low(); + match select(temp_sens.read_temp(), Timer::after(Duration::from_millis(500))).await { + Either::First(i2c_ret) => match i2c_ret { + Ok(value) => { + led.set_high(); + let temp = i32::from(value); + println!("TEMP: {:04x}, {}", temp, temp * 78 / 10); + TEMP.store(temp * 78 / 10, Ordering::Relaxed); + } + Err(e) => defmt::println!("ADT7422: {}", e), + }, + Either::Second(_) => println!("Timeout"), + } + + tmr.next().await; + } +} + +#[embassy_executor::task] +async fn ethernet_task(runner: Runner<'static, SpeSpiCs, SpeInt, SpeRst>) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +// same panicking *behavior* as `panic-probe` but doesn't print a panic message +// this prevents the panic message being printed *twice* when `defmt::panic` is invoked +#[defmt::panic_handler] +fn panic() -> ! { + cortex_m::asm::udf() +} + +#[allow(non_camel_case_types)] +#[repr(C)] +pub enum Registers { + Temp_MSB = 0x00, + Temp_LSB, + Status, + Cfg, + T_HIGH_MSB, + T_HIGH_LSB, + T_LOW_MSB, + T_LOW_LSB, + T_CRIT_MSB, + T_CRIT_LSB, + T_HYST, + ID, + SW_RESET = 0x2F, +} + +pub struct ADT7422<'d, BUS: I2cBus> { + addr: u8, + phantom: PhantomData<&'d ()>, + bus: BUS, +} + +#[derive(Debug, Format)] +pub enum Error { + I2c(I2cError), + Address, +} + +impl<'d, BUS> ADT7422<'d, BUS> +where + BUS: I2cBus, + BUS::Error: Format, +{ + pub fn new(bus: BUS, addr: u8) -> Result> { + if !(0x48..=0x4A).contains(&addr) { + return Err(Error::Address); + } + + Ok(Self { + bus, + phantom: PhantomData, + addr, + }) + } + + pub async fn init(&mut self) -> Result<(), Error> { + let mut cfg = 0b000_0000; + // if self.int.is_some() { + // // Set 1 SPS mode + // cfg |= 0b10 << 5; + // } else { + // One shot mode + cfg |= 0b01 << 5; + // } + + self.write_cfg(cfg).await + } + + pub async fn read(&mut self, reg: Registers) -> Result> { + let mut buffer = [0u8; 1]; + self.bus + .write_read(self.addr, &[reg as u8], &mut buffer) + .await + .map_err(Error::I2c)?; + Ok(buffer[0]) + } + + pub async fn write_cfg(&mut self, cfg: u8) -> Result<(), Error> { + let buf = [Registers::Cfg as u8, cfg]; + self.bus.write(self.addr, &buf).await.map_err(Error::I2c) + } + + pub async fn read_temp(&mut self) -> Result> { + let mut buffer = [0u8; 2]; + + // if let Some(int) = &mut self.int { + // // Wait for interrupt + // int.wait_for_low().await.unwrap(); + // } else { + // Start: One shot + let cfg = 0b01 << 5; + self.write_cfg(cfg).await?; + Timer::after(Duration::from_millis(250)).await; + self.bus + .write_read(self.addr, &[Registers::Temp_MSB as u8], &mut buffer) + .await + .map_err(Error::I2c)?; + Ok(i16::from_be_bytes(buffer)) + } +} + +// Web page +const PAGE: &str = r#" + + + + + ADIN1110 with Rust + + +

EVAL-ADIN1110EBZ

+
Temp Sensor ADT7422: #00.00 °C
+ +"#; diff --git a/examples/stm32l5/Cargo.toml b/examples/stm32l5/Cargo.toml index 9dfc08f2..d387ee62 100644 --- a/examples/stm32l5/Cargo.toml +++ b/examples/stm32l5/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0" # Change stm32l552ze to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l552ze", "time-driver-any", "exti", "unstable-traits", "memory-x"] } 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", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/examples/stm32u5/Cargo.toml b/examples/stm32u5/Cargo.toml index db251eaf..403e5714 100644 --- a/examples/stm32u5/Cargo.toml +++ b/examples/stm32u5/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0" # Change stm32u585ai to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32u585ai", "time-driver-any", "memory-x" ] } 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", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } defmt = "0.3" diff --git a/examples/stm32wb/Cargo.toml b/examples/stm32wb/Cargo.toml index 1a5aff35..81d9ec69 100644 --- a/examples/stm32wb/Cargo.toml +++ b/examples/stm32wb/Cargo.toml @@ -9,9 +9,9 @@ license = "MIT OR Apache-2.0" embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32wb55rg", "time-driver-any", "memory-x", "exti"] } embassy-stm32-wpan = { version = "0.1.0", path = "../../embassy-stm32-wpan", features = ["defmt", "stm32wb55rg"] } 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", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "udp", "medium-ieee802154", "nightly"], optional=true } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "udp", "proto-ipv6", "medium-ieee802154", "nightly"], optional=true } defmt = "0.3" defmt-rtt = "0.4" diff --git a/examples/stm32wl/Cargo.toml b/examples/stm32wl/Cargo.toml index 5440807f..3ea76cbb 100644 --- a/examples/stm32wl/Cargo.toml +++ b/examples/stm32wl/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0" # Change stm32wl55jc-cm4 to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "unstable-pac", "exti", "chrono"] } 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 = ["nightly", "unstable-traits", "defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["nightly", "unstable-traits", "defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["stm32wl", "time", "defmt"] } lora-phy = { version = "1" } @@ -32,4 +32,4 @@ chrono = { version = "^0.4", default-features = false } debug = 2 [patch.crates-io] -lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "1323eccc1c470d4259f95f4f315d1be830d572a3"} \ No newline at end of file +lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "1323eccc1c470d4259f95f4f315d1be830d572a3"} diff --git a/examples/stm32wl/src/bin/rtc.rs b/examples/stm32wl/src/bin/rtc.rs index fb1bc6e3..2be6c7b9 100644 --- a/examples/stm32wl/src/bin/rtc.rs +++ b/examples/stm32wl/src/bin/rtc.rs @@ -5,8 +5,8 @@ use chrono::{NaiveDate, NaiveDateTime}; use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::rcc::{self, ClockSrc}; -use embassy_stm32::rtc::{Rtc, RtcConfig}; +use embassy_stm32::rcc::ClockSrc; +use embassy_stm32::rtc::{Rtc, RtcClockSource, RtcConfig}; use embassy_stm32::Config; use embassy_time::{Duration, Timer}; use {defmt_rtt as _, panic_probe as _}; @@ -16,7 +16,7 @@ async fn main(_spawner: Spawner) { let p = { let mut config = Config::default(); config.rcc.mux = ClockSrc::HSE32; - config.rcc.rtc_mux = rcc::RtcClockSource::LSE32; + config.rcc.rtc_mux = RtcClockSource::LSE; config.rcc.enable_rtc_apb = true; embassy_stm32::init(config) }; diff --git a/examples/wasm/Cargo.toml b/examples/wasm/Cargo.toml index 2791cc34..2999dc52 100644 --- a/examples/wasm/Cargo.toml +++ b/examples/wasm/Cargo.toml @@ -9,8 +9,8 @@ crate-type = ["cdylib"] [dependencies] embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["log"] } -embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-wasm", "executor-thread", "log", "nightly", "integrated-timers"] } -embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["log", "wasm", "nightly"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["arch-wasm", "executor-thread", "log", "nightly", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["log", "wasm", "nightly"] } wasm-logger = "0.2.0" wasm-bindgen = "0.2" 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 034ed85e..867bf272 100644 --- a/tests/nrf/Cargo.toml +++ b/tests/nrf/Cargo.toml @@ -9,8 +9,8 @@ teleprobe-meta = "1" embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt", "nightly"] } -embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "nightly", "integrated-timers"] } -embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "nightly", "unstable-traits", "defmt-timestamp-uptime"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "nightly", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "nightly", "unstable-traits", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nightly", "unstable-traits", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } embedded-io-async = { version = "0.5.0" } embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "nightly"] } @@ -25,4 +25,4 @@ defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" -panic-probe = { version = "0.3", features = ["print-defmt"] } \ No newline at end of file +panic-probe = { version = "0.3", features = ["print-defmt"] } diff --git a/tests/riscv32/Cargo.toml b/tests/riscv32/Cargo.toml index 61f886c0..66ab34ec 100644 --- a/tests/riscv32/Cargo.toml +++ b/tests/riscv32/Cargo.toml @@ -7,8 +7,8 @@ license = "MIT OR Apache-2.0" [dependencies] critical-section = { version = "1.1.1", features = ["restore-state-bool"] } embassy-sync = { version = "0.2.0", path = "../../embassy-sync" } -embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-riscv32", "nightly", "executor-thread"] } -embassy-time = { version = "0.1.2", path = "../../embassy-time" } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["arch-riscv32", "nightly", "executor-thread"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time" } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } riscv-rt = "0.11" diff --git a/tests/rp/Cargo.toml b/tests/rp/Cargo.toml index 6a3df4b9..d71db8bb 100644 --- a/tests/rp/Cargo.toml +++ b/tests/rp/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0" 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", "nightly", "unstable-traits"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", 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"] } diff --git a/tests/rp/src/bin/gpio_multicore.rs b/tests/rp/src/bin/gpio_multicore.rs index 22be7824..6ab7f671 100644 --- a/tests/rp/src/bin/gpio_multicore.rs +++ b/tests/rp/src/bin/gpio_multicore.rs @@ -37,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(); @@ -51,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 ec794c48..f4188135 100644 --- a/tests/rp/src/bin/multicore.rs +++ b/tests/rp/src/bin/multicore.rs @@ -33,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"); @@ -43,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/stm32/Cargo.toml b/tests/stm32/Cargo.toml index e2638897..c96b5a5a 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml @@ -7,7 +7,7 @@ autobins = false [features] stm32f103c8 = ["embassy-stm32/stm32f103c8", "not-gpdma"] # Blue Pill -stm32f429zi = ["embassy-stm32/stm32f429zi", "chrono", "can", "not-gpdma", "dac-adc-pin"] # Nucleo "sdmmc" +stm32f429zi = ["embassy-stm32/stm32f429zi", "chrono", "stop", "can", "not-gpdma", "dac-adc-pin"] # Nucleo "sdmmc" stm32g071rb = ["embassy-stm32/stm32g071rb", "not-gpdma", "dac-adc-pin"] # Nucleo stm32c031c6 = ["embassy-stm32/stm32c031c6", "not-gpdma"] # Nucleo stm32g491re = ["embassy-stm32/stm32g491re", "not-gpdma"] # Nucleo @@ -17,6 +17,7 @@ stm32h563zi = ["embassy-stm32/stm32h563zi"] # Nucleo stm32u585ai = ["embassy-stm32/stm32u585ai"] # IoT board sdmmc = [] +stop = ["embassy-stm32/low-power"] chrono = ["embassy-stm32/chrono", "dep:chrono"] can = [] ble = ["dep:embassy-stm32-wpan", "embassy-stm32-wpan/ble"] @@ -29,8 +30,8 @@ dac-adc-pin = [] teleprobe-meta = "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", "tick-hz-32_768", "defmt-timestamp-uptime"] } +embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.3", path = "../../embassy-time", features = ["defmt", "tick-hz-32_768", "defmt-timestamp-uptime"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "memory-x", "time-driver-any"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-stm32-wpan = { version = "0.1.0", path = "../../embassy-stm32-wpan", optional = true, features = ["defmt", "stm32wb55rg", "ble"] } @@ -47,6 +48,7 @@ micromath = "2.0.0" panic-probe = { version = "0.3.0", features = ["print-defmt"] } rand_core = { version = "0.6", default-features = false } rand_chacha = { version = "0.3", default-features = false } +static_cell = {version = "1.1", features = ["nightly"] } chrono = { version = "^0.4", default-features = false, optional = true} @@ -87,6 +89,11 @@ name = "spi_dma" path = "src/bin/spi_dma.rs" required-features = [] +[[bin]] +name = "stop" +path = "src/bin/stop.rs" +required-features = [ "stop", "chrono",] + [[bin]] name = "timer" path = "src/bin/timer.rs" 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 diff --git a/tests/stm32/src/bin/rtc.rs b/tests/stm32/src/bin/rtc.rs index 7df415b4..1a64dd38 100644 --- a/tests/stm32/src/bin/rtc.rs +++ b/tests/stm32/src/bin/rtc.rs @@ -10,7 +10,8 @@ use chrono::{NaiveDate, NaiveDateTime}; use common::*; use defmt::assert; use embassy_executor::Spawner; -use embassy_stm32::rtc::{Rtc, RtcClockSource, RtcConfig}; +use embassy_stm32::rcc::RtcClockSource; +use embassy_stm32::rtc::{Rtc, RtcConfig}; use embassy_time::{Duration, Timer}; #[embassy_executor::main] diff --git a/tests/stm32/src/bin/stop.rs b/tests/stm32/src/bin/stop.rs new file mode 100644 index 00000000..a490d7b8 --- /dev/null +++ b/tests/stm32/src/bin/stop.rs @@ -0,0 +1,56 @@ +// required-features: stop,chrono + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use chrono::NaiveDate; +use common::*; +use cortex_m_rt::entry; +use embassy_executor::Spawner; +use embassy_stm32::low_power::{stop_with_rtc, Executor}; +use embassy_stm32::rcc::RtcClockSource; +use embassy_stm32::rtc::{Rtc, RtcConfig}; +use embassy_time::{Duration, Timer}; +use static_cell::make_static; + +#[entry] +fn main() -> ! { + let executor = Executor::take(); + executor.run(|spawner| { + unwrap!(spawner.spawn(async_main(spawner))); + }); +} + +#[embassy_executor::task] +async fn async_main(_spawner: Spawner) { + let mut config = config(); + + config.rcc.rtc = Some(RtcClockSource::LSI); + + let p = embassy_stm32::init(config); + info!("Hello World!"); + + let now = NaiveDate::from_ymd_opt(2020, 5, 15) + .unwrap() + .and_hms_opt(10, 30, 15) + .unwrap(); + + let mut rtc = Rtc::new(p.RTC, RtcConfig::default()); + + rtc.set_datetime(now.into()).expect("datetime not set"); + + let rtc = make_static!(rtc); + + stop_with_rtc(rtc); + + info!("Waiting..."); + Timer::after(Duration::from_secs(2)).await; + info!("Waiting..."); + Timer::after(Duration::from_secs(3)).await; + + info!("Test OK"); + cortex_m::asm::bkpt(); +}