From 6d79c4c81187d5f2704e2d2f72a3deba05ca449a Mon Sep 17 00:00:00 2001 From: Roy Date: Wed, 2 Jul 2025 15:24:01 +0300 Subject: [PATCH 01/15] feat: added log-to-defmt feature Signed-off-by: Roy --- ci.sh | 1 + embassy-nxp/Cargo.toml | 4 ++++ embassy-nxp/src/lib.rs | 5 +++-- examples/lpc55s69/Cargo.toml | 4 ++++ examples/lpc55s69/src/bin/log_to_defmt.rs | 18 ++++++++++++++++++ 5 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 examples/lpc55s69/src/bin/log_to_defmt.rs diff --git a/ci.sh b/ci.sh index 842215a6b..4f8845158 100755 --- a/ci.sh +++ b/ci.sh @@ -264,6 +264,7 @@ cargo batch \ --- build --release --manifest-path examples/stm32wba/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/stm32wba \ --- build --release --manifest-path examples/stm32wl/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32wl \ --- build --release --manifest-path examples/lpc55s69/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/lpc55s69 \ + --- build --release --manifest-path examples/lpc55s69/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nxp/log-to-defmt --artifact-dir out/examples/lpc55s69 \ --- build --release --manifest-path examples/mspm0g3507/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0g3507 \ --- build --release --manifest-path examples/mspm0g3519/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0g3519 \ --- build --release --manifest-path examples/mspm0l1306/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0l1306 \ diff --git a/embassy-nxp/Cargo.toml b/embassy-nxp/Cargo.toml index 01f57c4e2..9eb48be17 100644 --- a/embassy-nxp/Cargo.toml +++ b/embassy-nxp/Cargo.toml @@ -10,6 +10,8 @@ critical-section = "1.1.2" embassy-hal-internal = { version = "0.3.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] } embassy-sync = { version = "0.7.0", path = "../embassy-sync" } defmt = { version = "1", optional = true } +log = "0.4.27" +log-to-defmt = { version = "0.1.0", optional = true} ## Chip dependencies lpc55-pac = { version = "0.5.0", optional = true } @@ -30,3 +32,5 @@ unstable-pac = [] #! ### Chip selection features lpc55 = ["lpc55-pac"] +## Enable debug logs +log-to-defmt = ["dep:log-to-defmt"] diff --git a/embassy-nxp/src/lib.rs b/embassy-nxp/src/lib.rs index 433aca9e0..79c66e7f6 100644 --- a/embassy-nxp/src/lib.rs +++ b/embassy-nxp/src/lib.rs @@ -3,7 +3,6 @@ pub mod gpio; #[cfg(feature = "lpc55")] pub mod pint; - // This mod MUST go last, so that it sees all the `impl_foo!` macros #[cfg_attr(feature = "lpc55", path = "chips/lpc55.rs")] mod chip; @@ -25,8 +24,10 @@ pub fn init(_config: config::Config) -> Peripherals { { gpio::init(); pint::init(); + #[cfg(feature = "log-to-defmt")] + log_to_defmt::setup(); + log::info!("Initialization complete"); } - crate::Peripherals::take() } diff --git a/examples/lpc55s69/Cargo.toml b/examples/lpc55s69/Cargo.toml index 6ec6e51a8..7e3b82432 100644 --- a/examples/lpc55s69/Cargo.toml +++ b/examples/lpc55s69/Cargo.toml @@ -18,5 +18,9 @@ defmt-rtt = "1.0.0" panic-probe = { version = "1.0.0", features = ["print-defmt"] } panic-semihosting = "0.6.0" +[features] +## To test all-logs mode +log-to-defmt = ["embassy-nxp/log-to-defmt"] + [profile.release] debug = 2 diff --git a/examples/lpc55s69/src/bin/log_to_defmt.rs b/examples/lpc55s69/src/bin/log_to_defmt.rs new file mode 100644 index 000000000..7aaab5e54 --- /dev/null +++ b/examples/lpc55s69/src/bin/log_to_defmt.rs @@ -0,0 +1,18 @@ +/// To test log-to-defmt feature, you have to run the binary file with the corresponding flag +/// Example: cargo run --bin --feature log-to-defmt + + +#![no_std] +#![no_main] + +use log::*; +use embassy_executor::Spawner; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World"); + loop{ + info!("Another test"); + } +} From 0fc1ab290fed27301b455a039c2ae9c16ce7c30c Mon Sep 17 00:00:00 2001 From: Roi Bachynskyi Date: Mon, 21 Jul 2025 10:47:21 +0300 Subject: [PATCH 02/15] Revert "feat: added log-to-defmt feature" This reverts commit 6d79c4c81187d5f2704e2d2f72a3deba05ca449a. --- ci.sh | 1 - embassy-nxp/Cargo.toml | 4 ---- embassy-nxp/src/lib.rs | 5 ++--- examples/lpc55s69/Cargo.toml | 4 ---- examples/lpc55s69/src/bin/log_to_defmt.rs | 18 ------------------ 5 files changed, 2 insertions(+), 30 deletions(-) delete mode 100644 examples/lpc55s69/src/bin/log_to_defmt.rs diff --git a/ci.sh b/ci.sh index 4f8845158..842215a6b 100755 --- a/ci.sh +++ b/ci.sh @@ -264,7 +264,6 @@ cargo batch \ --- build --release --manifest-path examples/stm32wba/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/stm32wba \ --- build --release --manifest-path examples/stm32wl/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32wl \ --- build --release --manifest-path examples/lpc55s69/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/lpc55s69 \ - --- build --release --manifest-path examples/lpc55s69/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nxp/log-to-defmt --artifact-dir out/examples/lpc55s69 \ --- build --release --manifest-path examples/mspm0g3507/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0g3507 \ --- build --release --manifest-path examples/mspm0g3519/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0g3519 \ --- build --release --manifest-path examples/mspm0l1306/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0l1306 \ diff --git a/embassy-nxp/Cargo.toml b/embassy-nxp/Cargo.toml index 9eb48be17..01f57c4e2 100644 --- a/embassy-nxp/Cargo.toml +++ b/embassy-nxp/Cargo.toml @@ -10,8 +10,6 @@ critical-section = "1.1.2" embassy-hal-internal = { version = "0.3.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] } embassy-sync = { version = "0.7.0", path = "../embassy-sync" } defmt = { version = "1", optional = true } -log = "0.4.27" -log-to-defmt = { version = "0.1.0", optional = true} ## Chip dependencies lpc55-pac = { version = "0.5.0", optional = true } @@ -32,5 +30,3 @@ unstable-pac = [] #! ### Chip selection features lpc55 = ["lpc55-pac"] -## Enable debug logs -log-to-defmt = ["dep:log-to-defmt"] diff --git a/embassy-nxp/src/lib.rs b/embassy-nxp/src/lib.rs index 79c66e7f6..433aca9e0 100644 --- a/embassy-nxp/src/lib.rs +++ b/embassy-nxp/src/lib.rs @@ -3,6 +3,7 @@ pub mod gpio; #[cfg(feature = "lpc55")] pub mod pint; + // This mod MUST go last, so that it sees all the `impl_foo!` macros #[cfg_attr(feature = "lpc55", path = "chips/lpc55.rs")] mod chip; @@ -24,10 +25,8 @@ pub fn init(_config: config::Config) -> Peripherals { { gpio::init(); pint::init(); - #[cfg(feature = "log-to-defmt")] - log_to_defmt::setup(); - log::info!("Initialization complete"); } + crate::Peripherals::take() } diff --git a/examples/lpc55s69/Cargo.toml b/examples/lpc55s69/Cargo.toml index 7e3b82432..6ec6e51a8 100644 --- a/examples/lpc55s69/Cargo.toml +++ b/examples/lpc55s69/Cargo.toml @@ -18,9 +18,5 @@ defmt-rtt = "1.0.0" panic-probe = { version = "1.0.0", features = ["print-defmt"] } panic-semihosting = "0.6.0" -[features] -## To test all-logs mode -log-to-defmt = ["embassy-nxp/log-to-defmt"] - [profile.release] debug = 2 diff --git a/examples/lpc55s69/src/bin/log_to_defmt.rs b/examples/lpc55s69/src/bin/log_to_defmt.rs deleted file mode 100644 index 7aaab5e54..000000000 --- a/examples/lpc55s69/src/bin/log_to_defmt.rs +++ /dev/null @@ -1,18 +0,0 @@ -/// To test log-to-defmt feature, you have to run the binary file with the corresponding flag -/// Example: cargo run --bin --feature log-to-defmt - - -#![no_std] -#![no_main] - -use log::*; -use embassy_executor::Spawner; -use {defmt_rtt as _, panic_halt as _}; - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - info!("Hello World"); - loop{ - info!("Another test"); - } -} From 2a696579275a035ab56023313284f8a66ef37a45 Mon Sep 17 00:00:00 2001 From: Roi Bachynskyi Date: Mon, 21 Jul 2025 12:49:17 +0300 Subject: [PATCH 03/15] feat: fmt.rs was added --- embassy-nxp/Cargo.toml | 2 +- embassy-nxp/src/fmt.rs | 284 ++++++++++++++++++++++++++++++++++ embassy-nxp/src/gpio/lpc55.rs | 1 + embassy-nxp/src/lib.rs | 1 + embassy-nxp/src/pint.rs | 2 + examples/lpc55s69/Cargo.toml | 2 +- 6 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 embassy-nxp/src/fmt.rs diff --git a/embassy-nxp/Cargo.toml b/embassy-nxp/Cargo.toml index 01f57c4e2..56d00bfb2 100644 --- a/embassy-nxp/Cargo.toml +++ b/embassy-nxp/Cargo.toml @@ -10,6 +10,7 @@ critical-section = "1.1.2" embassy-hal-internal = { version = "0.3.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] } embassy-sync = { version = "0.7.0", path = "../embassy-sync" } defmt = { version = "1", optional = true } +log = { version = "0.4.27", optional = true } ## Chip dependencies lpc55-pac = { version = "0.5.0", optional = true } @@ -21,7 +22,6 @@ rt = ["lpc55-pac?/rt"] ## Enable [defmt support](https://docs.rs/defmt) and enables `defmt` debug-log messages and formatting in embassy drivers. defmt = ["dep:defmt", "embassy-hal-internal/defmt", "embassy-sync/defmt"] - ## Reexport the PAC for the currently enabled chip at `embassy_nxp::pac` (unstable) unstable-pac = [] # This is unstable because semver-minor (non-breaking) releases of embassy-nrf may major-bump (breaking) the PAC version. diff --git a/embassy-nxp/src/fmt.rs b/embassy-nxp/src/fmt.rs new file mode 100644 index 000000000..27d41ace6 --- /dev/null +++ b/embassy-nxp/src/fmt.rs @@ -0,0 +1,284 @@ +//! Copied from embassy-rp + +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unimplemented { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unimplemented!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unimplemented!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +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 ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +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 ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +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 ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +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 ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +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")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +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 + } +} + +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-nxp/src/gpio/lpc55.rs b/embassy-nxp/src/gpio/lpc55.rs index 94cd8b7f8..8f407bb3a 100644 --- a/embassy-nxp/src/gpio/lpc55.rs +++ b/embassy-nxp/src/gpio/lpc55.rs @@ -7,6 +7,7 @@ pub(crate) fn init() { syscon_reg() .ahbclkctrl0 .modify(|_, w| w.gpio0().enable().gpio1().enable().mux().enable().iocon().enable()); + info!("GPIO initialized"); } /// The GPIO pin level for pins set on "Digital" mode. diff --git a/embassy-nxp/src/lib.rs b/embassy-nxp/src/lib.rs index 433aca9e0..1abaca708 100644 --- a/embassy-nxp/src/lib.rs +++ b/embassy-nxp/src/lib.rs @@ -1,5 +1,6 @@ #![no_std] +pub mod fmt; pub mod gpio; #[cfg(feature = "lpc55")] pub mod pint; diff --git a/embassy-nxp/src/pint.rs b/embassy-nxp/src/pint.rs index dc117e7e3..ff414b4e6 100644 --- a/embassy-nxp/src/pint.rs +++ b/embassy-nxp/src/pint.rs @@ -101,6 +101,8 @@ pub(crate) fn init() { crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT6); crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT7); }; + + info!("Pin interrupts initialized"); } #[must_use = "futures do nothing unless you `.await` or poll them"] diff --git a/examples/lpc55s69/Cargo.toml b/examples/lpc55s69/Cargo.toml index 6ec6e51a8..1724a22d4 100644 --- a/examples/lpc55s69/Cargo.toml +++ b/examples/lpc55s69/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] -embassy-nxp = { version = "0.1.0", path = "../../embassy-nxp", features = ["lpc55", "rt"] } +embassy-nxp = { version = "0.1.0", path = "../../embassy-nxp", features = ["lpc55", "rt", "defmt"] } embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt"] } embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt"] } From de4537d000ffeb8821a58056a99e76bb12c1536e Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 21 Jul 2025 13:20:35 +0200 Subject: [PATCH 04/15] stm32: Fix build for WBA lowpower. --- embassy-stm32/src/low_power.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs index 4607eb230..d13df5a6b 100644 --- a/embassy-stm32/src/low_power.rs +++ b/embassy-stm32/src/low_power.rs @@ -132,7 +132,10 @@ impl Into for StopMode { fn into(self) -> Lpms { match self { StopMode::Stop1 => Lpms::STOP1, + #[cfg(not(stm32wba))] StopMode::Stop2 => Lpms::STOP2, + #[cfg(stm32wba)] + StopMode::Stop2 => Lpms::STOP1, // TODO: WBA has no STOP2? } } } From 4c098faef528e18e0b17e540b12487eef9acccbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kr=C3=B6ger?= Date: Mon, 21 Jul 2025 11:26:01 +0200 Subject: [PATCH 05/15] chore: Prepare release for `embassy-boot-*` crates Prepare the bootloader crates for a release with `embassy-sync@0.7.0` as dependency for compatiblity with other released crates. `embassy-sync` is used in the public api of `embassy-boot` which is reexported by the `embassy-boot-*` crates. That is why a minor version bump of all crates is required. --- embassy-boot-nrf/Cargo.toml | 4 ++-- embassy-boot-rp/Cargo.toml | 4 ++-- embassy-boot-stm32/Cargo.toml | 4 ++-- embassy-boot/Cargo.toml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/embassy-boot-nrf/Cargo.toml b/embassy-boot-nrf/Cargo.toml index 1230cbd3b..81479759c 100644 --- a/embassy-boot-nrf/Cargo.toml +++ b/embassy-boot-nrf/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "embassy-boot-nrf" -version = "0.5.0" +version = "0.6.0" description = "Bootloader lib for nRF chips" license = "MIT OR Apache-2.0" repository = "https://github.com/embassy-rs/embassy" @@ -26,7 +26,7 @@ log = { version = "0.4.17", optional = true } embassy-sync = { version = "0.7.0", path = "../embassy-sync" } embassy-nrf = { version = "0.5.0", path = "../embassy-nrf", default-features = false } -embassy-boot = { version = "0.4.0", path = "../embassy-boot" } +embassy-boot = { version = "0.5.0", path = "../embassy-boot" } cortex-m = { version = "0.7.6" } cortex-m-rt = { version = "0.7" } embedded-storage = "0.3.1" diff --git a/embassy-boot-rp/Cargo.toml b/embassy-boot-rp/Cargo.toml index 23d6c7853..8ca999f67 100644 --- a/embassy-boot-rp/Cargo.toml +++ b/embassy-boot-rp/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "embassy-boot-rp" -version = "0.5.0" +version = "0.6.0" description = "Bootloader lib for RP2040 chips" license = "MIT OR Apache-2.0" repository = "https://github.com/embassy-rs/embassy" @@ -26,7 +26,7 @@ log = { version = "0.4", optional = true } embassy-sync = { version = "0.7.0", path = "../embassy-sync" } embassy-rp = { version = "0.6.0", path = "../embassy-rp", default-features = false } -embassy-boot = { version = "0.4.0", path = "../embassy-boot" } +embassy-boot = { version = "0.5.0", path = "../embassy-boot" } embassy-time = { version = "0.4.0", path = "../embassy-time" } cortex-m = { version = "0.7.6" } diff --git a/embassy-boot-stm32/Cargo.toml b/embassy-boot-stm32/Cargo.toml index 11ad453b8..b92d06c54 100644 --- a/embassy-boot-stm32/Cargo.toml +++ b/embassy-boot-stm32/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "embassy-boot-stm32" -version = "0.3.0" +version = "0.4.0" description = "Bootloader lib for STM32 chips" license = "MIT OR Apache-2.0" repository = "https://github.com/embassy-rs/embassy" @@ -26,7 +26,7 @@ log = { version = "0.4", optional = true } embassy-sync = { version = "0.7.0", path = "../embassy-sync" } embassy-stm32 = { version = "0.2.0", path = "../embassy-stm32", default-features = false } -embassy-boot = { version = "0.4.0", path = "../embassy-boot" } +embassy-boot = { version = "0.5.0", path = "../embassy-boot" } cortex-m = { version = "0.7.6" } cortex-m-rt = { version = "0.7" } embedded-storage = "0.3.1" diff --git a/embassy-boot/Cargo.toml b/embassy-boot/Cargo.toml index 281278abb..172330ef2 100644 --- a/embassy-boot/Cargo.toml +++ b/embassy-boot/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "embassy-boot" -version = "0.4.0" +version = "0.5.0" description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks." license = "MIT OR Apache-2.0" repository = "https://github.com/embassy-rs/embassy" From fad100cfa033df7553b0754820ff94ede3376d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kr=C3=B6ger?= Date: Mon, 21 Jul 2025 13:02:56 +0200 Subject: [PATCH 06/15] chore: Update examples to new `nrf-boot-*` version --- examples/boot/application/nrf/Cargo.toml | 4 ++-- examples/boot/application/rp/Cargo.toml | 2 +- examples/boot/application/stm32f3/Cargo.toml | 2 +- examples/boot/application/stm32f7/Cargo.toml | 2 +- examples/boot/application/stm32h7/Cargo.toml | 2 +- examples/boot/application/stm32l0/Cargo.toml | 2 +- examples/boot/application/stm32l1/Cargo.toml | 2 +- examples/boot/application/stm32l4/Cargo.toml | 2 +- examples/boot/application/stm32wb-dfu/Cargo.toml | 2 +- examples/boot/application/stm32wl/Cargo.toml | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/boot/application/nrf/Cargo.toml b/examples/boot/application/nrf/Cargo.toml index 9c7fdf148..37183df97 100644 --- a/examples/boot/application/nrf/Cargo.toml +++ b/examples/boot/application/nrf/Cargo.toml @@ -9,8 +9,8 @@ embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "arch-cortex-m", "executor-thread"] } embassy-time = { version = "0.4.0", path = "../../../../embassy-time", features = [] } embassy-nrf = { version = "0.5.0", path = "../../../../embassy-nrf", features = ["time-driver-rtc1", "gpiote", ] } -embassy-boot = { version = "0.4.0", path = "../../../../embassy-boot", features = [] } -embassy-boot-nrf = { version = "0.5.0", path = "../../../../embassy-boot-nrf", features = [] } +embassy-boot = { version = "0.5.0", path = "../../../../embassy-boot", features = [] } +embassy-boot-nrf = { version = "0.6.0", path = "../../../../embassy-boot-nrf", features = [] } embassy-embedded-hal = { version = "0.3.1", path = "../../../../embassy-embedded-hal" } defmt = { version = "1.0.1", optional = true } diff --git a/examples/boot/application/rp/Cargo.toml b/examples/boot/application/rp/Cargo.toml index afb0871e6..e5568f6bb 100644 --- a/examples/boot/application/rp/Cargo.toml +++ b/examples/boot/application/rp/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread"] } embassy-time = { version = "0.4.0", path = "../../../../embassy-time", features = [] } embassy-rp = { version = "0.6.0", path = "../../../../embassy-rp", features = ["time-driver", "rp2040"] } -embassy-boot-rp = { version = "0.5.0", path = "../../../../embassy-boot-rp", features = [] } +embassy-boot-rp = { version = "0.6.0", path = "../../../../embassy-boot-rp", features = [] } embassy-embedded-hal = { version = "0.3.1", path = "../../../../embassy-embedded-hal" } defmt = "1.0.1" diff --git a/examples/boot/application/stm32f3/Cargo.toml b/examples/boot/application/stm32f3/Cargo.toml index 4cd2d1338..be8b7bff1 100644 --- a/examples/boot/application/stm32f3/Cargo.toml +++ b/examples/boot/application/stm32f3/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread"] } embassy-time = { version = "0.4.0", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32f303re", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.3.0", path = "../../../../embassy-boot-stm32" } +embassy-boot-stm32 = { version = "0.4.0", path = "../../../../embassy-boot-stm32" } embassy-embedded-hal = { version = "0.3.1", path = "../../../../embassy-embedded-hal" } defmt = { version = "1.0.1", optional = true } diff --git a/examples/boot/application/stm32f7/Cargo.toml b/examples/boot/application/stm32f7/Cargo.toml index f3d74f53a..2b0175a0c 100644 --- a/examples/boot/application/stm32f7/Cargo.toml +++ b/examples/boot/application/stm32f7/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread"] } embassy-time = { version = "0.4.0", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32f767zi", "time-driver-any", "exti", "single-bank"] } -embassy-boot-stm32 = { version = "0.3.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-boot-stm32 = { version = "0.4.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.3.1", path = "../../../../embassy-embedded-hal" } defmt = { version = "1.0.1", optional = true } diff --git a/examples/boot/application/stm32h7/Cargo.toml b/examples/boot/application/stm32h7/Cargo.toml index 427b15bcb..3c88f4241 100644 --- a/examples/boot/application/stm32h7/Cargo.toml +++ b/examples/boot/application/stm32h7/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread"] } embassy-time = { version = "0.4.0", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32h743zi", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.3.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-boot-stm32 = { version = "0.4.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.3.1", path = "../../../../embassy-embedded-hal" } defmt = { version = "1.0.1", optional = true } diff --git a/examples/boot/application/stm32l0/Cargo.toml b/examples/boot/application/stm32l0/Cargo.toml index 46e79f7ed..b4e7e090a 100644 --- a/examples/boot/application/stm32l0/Cargo.toml +++ b/examples/boot/application/stm32l0/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread"] } embassy-time = { version = "0.4.0", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32l072cz", "time-driver-any", "exti", "memory-x"] } -embassy-boot-stm32 = { version = "0.3.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-boot-stm32 = { version = "0.4.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.3.1", path = "../../../../embassy-embedded-hal" } defmt = { version = "1.0.1", optional = true } diff --git a/examples/boot/application/stm32l1/Cargo.toml b/examples/boot/application/stm32l1/Cargo.toml index ae3cd3600..394578e1a 100644 --- a/examples/boot/application/stm32l1/Cargo.toml +++ b/examples/boot/application/stm32l1/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread"] } embassy-time = { version = "0.4.0", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32l151cb-a", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.3.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-boot-stm32 = { version = "0.4.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.3.1", path = "../../../../embassy-embedded-hal" } defmt = { version = "1.0.1", optional = true } diff --git a/examples/boot/application/stm32l4/Cargo.toml b/examples/boot/application/stm32l4/Cargo.toml index a41b25562..abe0451fd 100644 --- a/examples/boot/application/stm32l4/Cargo.toml +++ b/examples/boot/application/stm32l4/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread"] } embassy-time = { version = "0.4.0", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32l475vg", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.3.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-boot-stm32 = { version = "0.4.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.3.1", path = "../../../../embassy-embedded-hal" } defmt = { version = "1.0.1", optional = true } diff --git a/examples/boot/application/stm32wb-dfu/Cargo.toml b/examples/boot/application/stm32wb-dfu/Cargo.toml index 287fcf806..bc4681f79 100644 --- a/examples/boot/application/stm32wb-dfu/Cargo.toml +++ b/examples/boot/application/stm32wb-dfu/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread"] } embassy-time = { version = "0.4.0", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32wb55rg", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.3.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-boot-stm32 = { version = "0.4.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.3.1", path = "../../../../embassy-embedded-hal" } embassy-usb = { version = "0.5.0", path = "../../../../embassy-usb" } embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["application", "cortex-m"] } diff --git a/examples/boot/application/stm32wl/Cargo.toml b/examples/boot/application/stm32wl/Cargo.toml index af49db260..0552d109a 100644 --- a/examples/boot/application/stm32wl/Cargo.toml +++ b/examples/boot/application/stm32wl/Cargo.toml @@ -9,7 +9,7 @@ embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread"] } embassy-time = { version = "0.4.0", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32wl55jc-cm4", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.3.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-boot-stm32 = { version = "0.4.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.3.1", path = "../../../../embassy-embedded-hal" } defmt = { version = "1.0.1", optional = true } From 9b3c97bcf7efc84f1943ee5de2ae251502ca071c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kr=C3=B6ger?= Date: Mon, 21 Jul 2025 13:12:48 +0200 Subject: [PATCH 07/15] chore: Bump `embassy-boot` version also for `embassy-usb-dfu` --- embassy-usb-dfu/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-usb-dfu/Cargo.toml b/embassy-usb-dfu/Cargo.toml index cdad3ce00..011046ba4 100644 --- a/embassy-usb-dfu/Cargo.toml +++ b/embassy-usb-dfu/Cargo.toml @@ -31,7 +31,7 @@ log = { version = "0.4.17", optional = true } bitflags = "2.4.1" cortex-m = { version = "0.7.7", features = ["inline-asm"], optional = true } -embassy-boot = { version = "0.4.0", path = "../embassy-boot" } +embassy-boot = { version = "0.5.0", path = "../embassy-boot" } embassy-futures = { version = "0.1.1", path = "../embassy-futures" } embassy-sync = { version = "0.7.0", path = "../embassy-sync" } embassy-time = { version = "0.4.0", path = "../embassy-time" } From 4db3910011a6a2fddc14f17bcc34a2aeb093d7b6 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 21 Jul 2025 14:17:12 +0200 Subject: [PATCH 08/15] Disable flaky test rpi-pico/cyw43-perf --- ci.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/ci.sh b/ci.sh index 229ddaae8..9d3e47a41 100755 --- a/ci.sh +++ b/ci.sh @@ -376,6 +376,7 @@ rm out/tests/pimoroni-pico-plus-2/pwm # flaky rm out/tests/rpi-pico/pwm +rm out/tests/rpi-pico/cyw43-perf if [[ -z "${TELEPROBE_TOKEN-}" ]]; then echo No teleprobe token found, skipping running HIL tests From 79fbf214ccc7be89d8d656cf0c5b5ffe5425bece Mon Sep 17 00:00:00 2001 From: clubby789 Date: Sun, 20 Jul 2025 15:38:42 +0100 Subject: [PATCH 09/15] Enable oversampling for ADC v3 --- embassy-stm32/src/adc/v3.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index 313244e19..fd74d5318 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -1,5 +1,7 @@ use cfg_if::cfg_if; use pac::adc::vals::Dmacfg; +#[cfg(adc_v3)] +use pac::adc::vals::{OversamplingRatio, OversamplingShift, Rovsm, Trovs}; use super::{ blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, @@ -470,6 +472,23 @@ impl<'d, T: Instance> Adc<'d, T> { T::regs().cfgr2().modify(|reg| reg.set_ovse(enable)); } + #[cfg(adc_v3)] + pub fn enable_regular_oversampling_mode(&mut self, mode: Rovsm, trig_mode: Trovs, enable: bool) { + T::regs().cfgr2().modify(|reg| reg.set_trovs(trig_mode)); + T::regs().cfgr2().modify(|reg| reg.set_rovsm(mode)); + T::regs().cfgr2().modify(|reg| reg.set_rovse(enable)); + } + + #[cfg(adc_v3)] + pub fn set_oversampling_ratio(&mut self, ratio: OversamplingRatio) { + T::regs().cfgr2().modify(|reg| reg.set_ovsr(ratio)); + } + + #[cfg(adc_v3)] + pub fn set_oversampling_shift(&mut self, shift: OversamplingShift) { + T::regs().cfgr2().modify(|reg| reg.set_ovss(shift)); + } + fn set_channel_sample_time(_ch: u8, sample_time: SampleTime) { cfg_if! { if #[cfg(any(adc_g0, adc_u0))] { From f96f68077b4f7d3cb9e9cd914df0d5157c17c297 Mon Sep 17 00:00:00 2001 From: Haobo Gu Date: Tue, 22 Jul 2025 10:36:29 +0800 Subject: [PATCH 10/15] chore: bump embassy-usb-synopsys-otg version Signed-off-by: Haobo Gu --- embassy-stm32/Cargo.toml | 2 +- embassy-usb-synopsys-otg/CHANGELOG.md | 4 ++++ embassy-usb-synopsys-otg/Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 1a73d84b6..43aee4e1a 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -57,7 +57,7 @@ embassy-hal-internal = { version = "0.3.0", path = "../embassy-hal-internal", fe embassy-embedded-hal = { version = "0.3.1", path = "../embassy-embedded-hal", default-features = false } embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" } embassy-usb-driver = { version = "0.2.0", path = "../embassy-usb-driver" } -embassy-usb-synopsys-otg = { version = "0.2.0", path = "../embassy-usb-synopsys-otg" } +embassy-usb-synopsys-otg = { version = "0.3.0", path = "../embassy-usb-synopsys-otg" } embassy-executor = { version = "0.7.0", path = "../embassy-executor", optional = true } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } diff --git a/embassy-usb-synopsys-otg/CHANGELOG.md b/embassy-usb-synopsys-otg/CHANGELOG.md index 293363d9a..9913ee533 100644 --- a/embassy-usb-synopsys-otg/CHANGELOG.md +++ b/embassy-usb-synopsys-otg/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## 0.3.0 - 2025-07-22 + +- Bump `embassy-usb-driver` to v0.2.0 + ## 0.2.0 - 2024-12-06 - Fix corruption in CONTROL OUT transfers (and remove `quirk_setup_late_cnak`) diff --git a/embassy-usb-synopsys-otg/Cargo.toml b/embassy-usb-synopsys-otg/Cargo.toml index 511cddacf..78cce24de 100644 --- a/embassy-usb-synopsys-otg/Cargo.toml +++ b/embassy-usb-synopsys-otg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-usb-synopsys-otg" -version = "0.2.0" +version = "0.3.0" edition = "2021" license = "MIT OR Apache-2.0" description = "`embassy-usb-driver` implementation for Synopsys OTG USB controllers" From f3cc62b77d343e0d70f4564ea7f2dadea9cf9d84 Mon Sep 17 00:00:00 2001 From: Haobo Gu Date: Tue, 22 Jul 2025 14:03:45 +0800 Subject: [PATCH 11/15] chore: bump embassy-usb-logger version Signed-off-by: Haobo Gu --- embassy-usb-logger/CHANGELOG.md | 4 ++++ embassy-usb-logger/Cargo.toml | 2 +- examples/rp/Cargo.toml | 2 +- examples/rp235x/Cargo.toml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/embassy-usb-logger/CHANGELOG.md b/embassy-usb-logger/CHANGELOG.md index 86a9fb032..79ea25839 100644 --- a/embassy-usb-logger/CHANGELOG.md +++ b/embassy-usb-logger/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## 0.5.0 - 2025-07-22 + +- Update `embassy-usb` to 0.5.0 + ## 0.4.0 - 2025-01-15 - Update `embassy-usb` to 0.4.0 diff --git a/embassy-usb-logger/Cargo.toml b/embassy-usb-logger/Cargo.toml index 6dd2637f2..68b11ad8a 100644 --- a/embassy-usb-logger/Cargo.toml +++ b/embassy-usb-logger/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-usb-logger" -version = "0.4.0" +version = "0.5.0" edition = "2021" license = "MIT OR Apache-2.0" description = "`log` implementation for USB serial using `embassy-usb`." diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index 971f99fff..eefd69315 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -15,7 +15,7 @@ embassy-usb = { version = "0.5.0", path = "../../embassy-usb", features = ["defm embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "icmp", "tcp", "udp", "raw", "dhcpv4", "medium-ethernet", "dns", "proto-ipv4", "proto-ipv6", "multicast"] } embassy-net-wiznet = { version = "0.2.0", path = "../../embassy-net-wiznet", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -embassy-usb-logger = { version = "0.4.0", path = "../../embassy-usb-logger" } +embassy-usb-logger = { version = "0.5.0", path = "../../embassy-usb-logger" } cyw43 = { version = "0.4.0", path = "../../cyw43", features = ["defmt", "firmware-logs"] } cyw43-pio = { version = "0.5.1", path = "../../cyw43-pio", features = ["defmt"] } diff --git a/examples/rp235x/Cargo.toml b/examples/rp235x/Cargo.toml index d909aca1b..4d3dc77b5 100644 --- a/examples/rp235x/Cargo.toml +++ b/examples/rp235x/Cargo.toml @@ -15,7 +15,7 @@ embassy-usb = { version = "0.5.0", path = "../../embassy-usb", features = ["defm embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "raw", "dhcpv4", "medium-ethernet", "dns"] } embassy-net-wiznet = { version = "0.2.0", path = "../../embassy-net-wiznet", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -embassy-usb-logger = { version = "0.4.0", path = "../../embassy-usb-logger" } +embassy-usb-logger = { version = "0.5.0", path = "../../embassy-usb-logger" } cyw43 = { version = "0.4.0", path = "../../cyw43", features = ["defmt", "firmware-logs"] } cyw43-pio = { version = "0.5.1", path = "../../cyw43-pio", features = ["defmt"] } From 1ad5d5a771d5109a763361454fb724b85ae25fdd Mon Sep 17 00:00:00 2001 From: i509VCB Date: Wed, 9 Jul 2025 23:08:59 -0500 Subject: [PATCH 12/15] nxp: Add MIMXRT1011 GPIO and time driver PIT is used for the time driver --- .vscode/settings.json | 1 + ci.sh | 2 + embassy-nxp/Cargo.toml | 36 +- embassy-nxp/build.rs | 136 ++++ embassy-nxp/build_common.rs | 94 +++ embassy-nxp/src/chips/mimxrt1011.rs | 113 ++++ embassy-nxp/src/gpio.rs | 2 + embassy-nxp/src/gpio/rt1xxx.rs | 895 +++++++++++++++++++++++++ embassy-nxp/src/lib.rs | 87 ++- embassy-nxp/src/time_driver/pit.rs | 187 ++++++ examples/mimxrt1011/.cargo/config.toml | 8 + examples/mimxrt1011/Cargo.toml | 29 + examples/mimxrt1011/build.rs | 14 + examples/mimxrt1011/src/bin/blinky.rs | 48 ++ examples/mimxrt1011/src/bin/button.rs | 62 ++ examples/mimxrt1011/src/lib.rs | 75 +++ 16 files changed, 1780 insertions(+), 9 deletions(-) create mode 100644 embassy-nxp/build.rs create mode 100644 embassy-nxp/build_common.rs create mode 100644 embassy-nxp/src/chips/mimxrt1011.rs create mode 100644 embassy-nxp/src/gpio/rt1xxx.rs create mode 100644 embassy-nxp/src/time_driver/pit.rs create mode 100644 examples/mimxrt1011/.cargo/config.toml create mode 100644 examples/mimxrt1011/Cargo.toml create mode 100644 examples/mimxrt1011/build.rs create mode 100644 examples/mimxrt1011/src/bin/blinky.rs create mode 100644 examples/mimxrt1011/src/bin/button.rs create mode 100644 examples/mimxrt1011/src/lib.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index e4814ff27..070e8fbd3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -36,6 +36,7 @@ // "examples/nrf52840-rtic/Cargo.toml", // "examples/nrf5340/Cargo.toml", // "examples/nrf-rtos-trace/Cargo.toml", + // "examples/mimxrt1011/Cargo.toml", // "examples/rp/Cargo.toml", // "examples/std/Cargo.toml", // "examples/stm32c0/Cargo.toml", diff --git a/ci.sh b/ci.sh index 9d3e47a41..e225bc7c9 100755 --- a/ci.sh +++ b/ci.sh @@ -181,6 +181,7 @@ cargo batch \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u073mb,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u083rc,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-nxp/Cargo.toml --target thumbv8m.main-none-eabihf --features lpc55,defmt \ + --- build --release --manifest-path embassy-nxp/Cargo.toml --target thumbv7em-none-eabihf --features mimxrt1011,defmt,time-driver-pit \ --- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0c1104dgs20,defmt,time-driver-any \ --- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0g3507pm,defmt,time-driver-any \ --- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0g3519pz,defmt,time-driver-any \ @@ -264,6 +265,7 @@ cargo batch \ --- build --release --manifest-path examples/stm32wba/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/stm32wba \ --- build --release --manifest-path examples/stm32wl/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32wl \ --- build --release --manifest-path examples/lpc55s69/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/lpc55s69 \ + --- build --release --manifest-path examples/mimxrt1011/Cargo.toml --target thumbv7em-none-eabihf --artifact-dir out/examples/mimxrt1011 \ --- build --release --manifest-path examples/mspm0g3507/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0g3507 \ --- build --release --manifest-path examples/mspm0g3519/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0g3519 \ --- build --release --manifest-path examples/mspm0l1306/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0l1306 \ diff --git a/embassy-nxp/Cargo.toml b/embassy-nxp/Cargo.toml index 56d00bfb2..625906183 100644 --- a/embassy-nxp/Cargo.toml +++ b/embassy-nxp/Cargo.toml @@ -11,22 +11,50 @@ embassy-hal-internal = { version = "0.3.0", path = "../embassy-hal-internal", fe embassy-sync = { version = "0.7.0", path = "../embassy-sync" } defmt = { version = "1", optional = true } log = { version = "0.4.27", optional = true } +embassy-time = { version = "0.4.0", path = "../embassy-time", optional = true } +embassy-time-driver = { version = "0.2", path = "../embassy-time-driver", optional = true } +embassy-time-queue-utils = { version = "0.1", path = "../embassy-time-queue-utils", optional = true } ## Chip dependencies lpc55-pac = { version = "0.5.0", optional = true } +nxp-pac = { version = "0.1.0", optional = true, git = "https://github.com/i509VCB/nxp-pac", rev = "1e010dbe75ab0e14dd908e4646391403414c8a8e" } + +imxrt-rt = { version = "0.1.7", optional = true, features = ["device"] } + +[build-dependencies] +cfg_aliases = "0.2.1" +nxp-pac = { version = "0.1.0", git = "https://github.com/i509VCB/nxp-pac", rev = "1e010dbe75ab0e14dd908e4646391403414c8a8e", features = ["metadata"], optional = true } +proc-macro2 = "1.0.95" +quote = "1.0.15" [features] default = ["rt"] -# Enable PACs as optional dependencies, since some chip families will use different pac crates. -rt = ["lpc55-pac?/rt"] +# Enable PACs as optional dependencies, since some chip families will use different pac crates (temporarily). +rt = ["lpc55-pac?/rt", "nxp-pac?/rt"] ## Enable [defmt support](https://docs.rs/defmt) and enables `defmt` debug-log messages and formatting in embassy drivers. defmt = ["dep:defmt", "embassy-hal-internal/defmt", "embassy-sync/defmt"] + +log = ["dep:log"] + +## Use Periodic Interrupt Timer (PIT) as the time driver for `embassy-time`, with a tick rate of 1 MHz +time-driver-pit = ["_time_driver", "embassy-time?/tick-hz-1_000_000"] + ## Reexport the PAC for the currently enabled chip at `embassy_nxp::pac` (unstable) unstable-pac = [] -# This is unstable because semver-minor (non-breaking) releases of embassy-nrf may major-bump (breaking) the PAC version. +# This is unstable because semver-minor (non-breaking) releases of embassy-nxp may major-bump (breaking) the PAC version. # If this is an issue for you, you're encouraged to directly depend on a fixed version of the PAC. # There are no plans to make this stable. +## internal use only +# +# This feature is unfortunately a hack around the fact that cfg_aliases cannot apply to the buildscript +# that creates the aliases. +_rt1xxx = [] + +# A timer driver is enabled. +_time_driver = ["dep:embassy-time-driver", "dep:embassy-time-queue-utils"] + #! ### Chip selection features -lpc55 = ["lpc55-pac"] +lpc55 = ["dep:lpc55-pac"] +mimxrt1011 = ["nxp-pac/mimxrt1011", "_rt1xxx", "dep:imxrt-rt"] diff --git a/embassy-nxp/build.rs b/embassy-nxp/build.rs new file mode 100644 index 000000000..6c10d0e69 --- /dev/null +++ b/embassy-nxp/build.rs @@ -0,0 +1,136 @@ +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::{env, fs}; + +use cfg_aliases::cfg_aliases; +#[cfg(feature = "_rt1xxx")] +use nxp_pac::metadata; +#[allow(unused)] +use proc_macro2::TokenStream; +#[allow(unused)] +use quote::quote; + +#[path = "./build_common.rs"] +mod common; + +fn main() { + let mut cfgs = common::CfgSet::new(); + common::set_target_cfgs(&mut cfgs); + + let chip_name = match env::vars() + .map(|(a, _)| a) + .filter(|x| x.starts_with("CARGO_FEATURE_MIMXRT") || x.starts_with("CARGO_FEATURE_LPC")) + .get_one() + { + Ok(x) => x, + Err(GetOneError::None) => panic!("No mimxrt/lpc Cargo feature enabled"), + Err(GetOneError::Multiple) => panic!("Multiple mimxrt/lpc Cargo features enabled"), + } + .strip_prefix("CARGO_FEATURE_") + .unwrap() + .to_ascii_lowercase(); + + cfg_aliases! { + rt1xxx: { feature = "mimxrt1011" }, + gpio1: { feature = "mimxrt1011" }, + gpio2: { feature = "mimxrt1011" }, + gpio5: { feature = "mimxrt1011" }, + } + + eprintln!("chip: {chip_name}"); + + generate_code(); +} + +#[cfg(feature = "_rt1xxx")] +fn generate_iomuxc() -> TokenStream { + use proc_macro2::{Ident, Span}; + + let pads = metadata::iomuxc::IOMUXC_REGISTERS.iter().map(|registers| { + let name = Ident::new(®isters.name, Span::call_site()); + let address = registers.pad_ctl; + + quote! { + pub const #name: u32 = #address; + } + }); + + let muxes = metadata::iomuxc::IOMUXC_REGISTERS.iter().map(|registers| { + let name = Ident::new(®isters.name, Span::call_site()); + let address = registers.mux_ctl; + + quote! { + pub const #name: u32 = #address; + } + }); + + quote! { + pub mod iomuxc { + pub mod pads { + #(#pads)* + } + + pub mod muxes { + #(#muxes)* + } + } + } +} + +fn generate_code() { + #[allow(unused)] + use std::fmt::Write; + + let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + #[allow(unused_mut)] + let mut output = String::new(); + + #[cfg(feature = "_rt1xxx")] + writeln!(&mut output, "{}", generate_iomuxc()).unwrap(); + + let out_file = out_dir.join("_generated.rs").to_string_lossy().to_string(); + fs::write(&out_file, output).unwrap(); + rustfmt(&out_file); +} + +/// rustfmt a given path. +/// Failures are logged to stderr and ignored. +fn rustfmt(path: impl AsRef) { + let path = path.as_ref(); + match Command::new("rustfmt").args([path]).output() { + Err(e) => { + eprintln!("failed to exec rustfmt {:?}: {:?}", path, e); + } + Ok(out) => { + if !out.status.success() { + eprintln!("rustfmt {:?} failed:", path); + eprintln!("=== STDOUT:"); + std::io::stderr().write_all(&out.stdout).unwrap(); + eprintln!("=== STDERR:"); + std::io::stderr().write_all(&out.stderr).unwrap(); + } + } + } +} + +enum GetOneError { + None, + Multiple, +} + +trait IteratorExt: Iterator { + fn get_one(self) -> Result; +} + +impl IteratorExt for T { + fn get_one(mut self) -> Result { + match self.next() { + None => Err(GetOneError::None), + Some(res) => match self.next() { + Some(_) => Err(GetOneError::Multiple), + None => Ok(res), + }, + } + } +} diff --git a/embassy-nxp/build_common.rs b/embassy-nxp/build_common.rs new file mode 100644 index 000000000..4f24e6d37 --- /dev/null +++ b/embassy-nxp/build_common.rs @@ -0,0 +1,94 @@ +// NOTE: this file is copy-pasted between several Embassy crates, because there is no +// straightforward way to share this code: +// - it cannot be placed into the root of the repo and linked from each build.rs using `#[path = +// "../build_common.rs"]`, because `cargo publish` requires that all files published with a crate +// reside in the crate's directory, +// - it cannot be symlinked from `embassy-xxx/build_common.rs` to `../build_common.rs`, because +// symlinks don't work on Windows. + +use std::collections::HashSet; +use std::env; + +/// Helper for emitting cargo instruction for enabling configs (`cargo:rustc-cfg=X`) and declaring +/// them (`cargo:rust-check-cfg=cfg(X)`). +#[derive(Debug)] +pub struct CfgSet { + enabled: HashSet, + declared: HashSet, +} + +impl CfgSet { + pub fn new() -> Self { + Self { + enabled: HashSet::new(), + declared: HashSet::new(), + } + } + + /// Enable a config, which can then be used in `#[cfg(...)]` for conditional compilation. + /// + /// All configs that can potentially be enabled should be unconditionally declared using + /// [`Self::declare()`]. + pub fn enable(&mut self, cfg: impl AsRef) { + if self.enabled.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-cfg={}", cfg.as_ref()); + } + } + + pub fn enable_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.enable(cfg.as_ref()); + } + } + + /// Declare a valid config for conditional compilation, without enabling it. + /// + /// This enables rustc to check that the configs in `#[cfg(...)]` attributes are valid. + pub fn declare(&mut self, cfg: impl AsRef) { + if self.declared.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-check-cfg=cfg({})", cfg.as_ref()); + } + } + + pub fn declare_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.declare(cfg.as_ref()); + } + } + + pub fn set(&mut self, cfg: impl Into, enable: bool) { + let cfg = cfg.into(); + if enable { + self.enable(cfg.clone()); + } + self.declare(cfg); + } +} + +/// Sets configs that describe the target platform. +pub fn set_target_cfgs(cfgs: &mut CfgSet) { + let target = env::var("TARGET").unwrap(); + + if target.starts_with("thumbv6m-") { + cfgs.enable_all(&["cortex_m", "armv6m"]); + } else if target.starts_with("thumbv7m-") { + cfgs.enable_all(&["cortex_m", "armv7m"]); + } else if target.starts_with("thumbv7em-") { + cfgs.enable_all(&["cortex_m", "armv7m", "armv7em"]); + } else if target.starts_with("thumbv8m.base") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_base"]); + } else if target.starts_with("thumbv8m.main") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_main"]); + } + cfgs.declare_all(&[ + "cortex_m", + "armv6m", + "armv7m", + "armv7em", + "armv8m", + "armv8m_base", + "armv8m_main", + ]); + + cfgs.set("has_fpu", target.ends_with("-eabihf")); +} diff --git a/embassy-nxp/src/chips/mimxrt1011.rs b/embassy-nxp/src/chips/mimxrt1011.rs new file mode 100644 index 000000000..a74d953fc --- /dev/null +++ b/embassy-nxp/src/chips/mimxrt1011.rs @@ -0,0 +1,113 @@ +// This must be imported so that __preinit is defined. +use imxrt_rt as _; +pub use nxp_pac as pac; + +embassy_hal_internal::peripherals! { + // External pins. These are not only GPIOs, they are multi-purpose pins and can be used by other + // peripheral types (e.g. I2C). + GPIO_00, + GPIO_01, + GPIO_02, + GPIO_03, + GPIO_04, + GPIO_05, + GPIO_06, + GPIO_07, + GPIO_08, + GPIO_09, + GPIO_10, + GPIO_11, + GPIO_12, + GPIO_13, + GPIO_AD_00, + GPIO_AD_01, + GPIO_AD_02, + GPIO_AD_03, + GPIO_AD_04, + GPIO_AD_05, + GPIO_AD_06, + GPIO_AD_07, + GPIO_AD_08, + GPIO_AD_09, + GPIO_AD_10, + GPIO_AD_11, + GPIO_AD_12, + GPIO_AD_13, + GPIO_AD_14, + GPIO_SD_00, + GPIO_SD_01, + GPIO_SD_02, + GPIO_SD_03, + GPIO_SD_04, + GPIO_SD_05, + GPIO_SD_06, + GPIO_SD_07, + GPIO_SD_08, + GPIO_SD_09, + GPIO_SD_10, + GPIO_SD_11, + GPIO_SD_12, + GPIO_SD_13, + PMIC_ON_REQ, +} + +impl_gpio! { + // GPIO Bank 1 + GPIO_00(Gpio1, 0); + GPIO_01(Gpio1, 1); + GPIO_02(Gpio1, 2); + GPIO_03(Gpio1, 3); + GPIO_04(Gpio1, 4); + GPIO_05(Gpio1, 5); + GPIO_06(Gpio1, 6); + GPIO_07(Gpio1, 7); + GPIO_08(Gpio1, 8); + GPIO_09(Gpio1, 9); + GPIO_10(Gpio1, 10); + GPIO_11(Gpio1, 11); + GPIO_12(Gpio1, 12); + GPIO_13(Gpio1, 13); + GPIO_AD_00(Gpio1, 14); + GPIO_AD_01(Gpio1, 15); + GPIO_AD_02(Gpio1, 16); + GPIO_AD_03(Gpio1, 17); + GPIO_AD_04(Gpio1, 18); + GPIO_AD_05(Gpio1, 19); + GPIO_AD_06(Gpio1, 20); + GPIO_AD_07(Gpio1, 21); + GPIO_AD_08(Gpio1, 22); + GPIO_AD_09(Gpio1, 23); + GPIO_AD_10(Gpio1, 24); + GPIO_AD_11(Gpio1, 25); + GPIO_AD_12(Gpio1, 26); + GPIO_AD_13(Gpio1, 27); + GPIO_AD_14(Gpio1, 28); + + // GPIO Bank 2 + GPIO_SD_00(Gpio2, 0); + GPIO_SD_01(Gpio2, 1); + GPIO_SD_02(Gpio2, 2); + GPIO_SD_03(Gpio2, 3); + GPIO_SD_04(Gpio2, 4); + GPIO_SD_05(Gpio2, 5); + GPIO_SD_06(Gpio2, 6); + GPIO_SD_07(Gpio2, 7); + GPIO_SD_08(Gpio2, 8); + GPIO_SD_09(Gpio2, 9); + GPIO_SD_10(Gpio2, 10); + GPIO_SD_11(Gpio2, 11); + GPIO_SD_12(Gpio2, 12); + GPIO_SD_13(Gpio2, 13); + + // GPIO Bank 5 + PMIC_ON_REQ(Gpio5, 0); +} + +pub(crate) mod _generated { + #![allow(dead_code)] + #![allow(unused_imports)] + #![allow(non_snake_case)] + #![allow(missing_docs)] + + include!(concat!(env!("OUT_DIR"), "/_generated.rs")); +} diff --git a/embassy-nxp/src/gpio.rs b/embassy-nxp/src/gpio.rs index 809903d97..3049cc12d 100644 --- a/embassy-nxp/src/gpio.rs +++ b/embassy-nxp/src/gpio.rs @@ -1,5 +1,7 @@ //! General purpose input/output (GPIO) driver. +#![macro_use] #[cfg_attr(feature = "lpc55", path = "./gpio/lpc55.rs")] +#[cfg_attr(rt1xxx, path = "./gpio/rt1xxx.rs")] mod inner; pub use inner::*; diff --git a/embassy-nxp/src/gpio/rt1xxx.rs b/embassy-nxp/src/gpio/rt1xxx.rs new file mode 100644 index 000000000..9c58e8a7d --- /dev/null +++ b/embassy-nxp/src/gpio/rt1xxx.rs @@ -0,0 +1,895 @@ +#![macro_use] + +use core::future::Future; +use core::ops::Not; +use core::pin::Pin as FuturePin; +use core::task::{Context, Poll}; + +use embassy_hal_internal::{impl_peripheral, Peri, PeripheralType}; +use embassy_sync::waitqueue::AtomicWaker; +use nxp_pac::gpio::vals::Icr; +use nxp_pac::iomuxc::vals::Pus; + +use crate::chip::{mux_address, pad_address}; +use crate::pac::common::{Reg, RW}; +#[cfg(feature = "rt")] +use crate::pac::interrupt; +use crate::pac::iomuxc::regs::{Ctl, MuxCtl}; +#[cfg(gpio5)] +use crate::pac::{self, gpio::Gpio}; + +/// The GPIO pin level for pins set on "Digital" mode. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Level { + /// Logical low. Corresponds to 0V. + Low, + /// Logical high. Corresponds to VDD. + High, +} + +impl From for Level { + fn from(val: bool) -> Self { + match val { + true => Self::High, + false => Self::Low, + } + } +} + +impl From for bool { + fn from(level: Level) -> bool { + match level { + Level::Low => false, + Level::High => true, + } + } +} + +impl Not for Level { + type Output = Self; + + fn not(self) -> Self::Output { + match self { + Level::Low => Level::High, + Level::High => Level::Low, + } + } +} + +/// Pull setting for a GPIO input set on "Digital" mode. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum Pull { + /// No pull. + None, + + // TODO: What Does PUE::KEEPER mean here? + + // 22 kOhm pull-up resistor. + Up22K, + + // 47 kOhm pull-up resistor. + Up47K, + + // 100 kOhm pull-up resistor. + Up100K, + + // 100 kOhm pull-down resistor. + Down100K, +} + +/// Drive strength of an output +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Drive { + Disabled, + _150R, + _75R, + _50R, + _37R, + _30R, + _25R, + _20R, +} + +/// Slew rate of an output +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SlewRate { + Slow, + + Fast, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Bank { + /// Bank 1 + #[cfg(gpio1)] + Gpio1, + + /// Bank 2 + #[cfg(gpio2)] + Gpio2, + + /// Bank 5 + #[cfg(gpio5)] + Gpio5, +} + +/// GPIO flexible pin. +/// +/// This pin can either be a disconnected, input, or output pin, or both. The level register bit will remain +/// set while not in output mode, so the pin's level will be 'remembered' when it is not in output +/// mode. +pub struct Flex<'d> { + pub(crate) pin: Peri<'d, AnyPin>, +} + +impl<'d> Flex<'d> { + /// Wrap the pin in a `Flex`. + /// + /// The pin remains disconnected. The initial output level is unspecified, but can be changed + /// before the pin is put into output mode. + #[inline] + pub fn new(pin: Peri<'d, impl Pin>) -> Self { + Self { pin: pin.into() } + } + + /// Set the pin's pull. + #[inline] + pub fn set_pull(&mut self, pull: Pull) { + let (pke, pue, pus) = match pull { + Pull::None => (false, true, Pus::PUS_0_100K_OHM_PULL_DOWN), + Pull::Up22K => (true, true, Pus::PUS_3_22K_OHM_PULL_UP), + Pull::Up47K => (true, true, Pus::PUS_1_47K_OHM_PULL_UP), + Pull::Up100K => (true, true, Pus::PUS_2_100K_OHM_PULL_UP), + Pull::Down100K => (true, true, Pus::PUS_0_100K_OHM_PULL_DOWN), + }; + + self.pin.pad().modify(|w| { + w.set_pke(pke); + w.set_pue(pue); + w.set_pus(pus); + }); + } + + // Set the pin's slew rate. + #[inline] + pub fn set_slewrate(&mut self, rate: SlewRate) { + self.pin.pad().modify(|w| { + w.set_sre(match rate { + SlewRate::Slow => false, + SlewRate::Fast => true, + }); + }); + } + + /// Set the pin's Schmitt trigger. + #[inline] + pub fn set_schmitt(&mut self, enable: bool) { + self.pin.pad().modify(|w| { + w.set_hys(enable); + }); + } + + /// Put the pin into input mode. + /// + /// The pull setting is left unchanged. + #[inline] + pub fn set_as_input(&mut self) { + self.pin.mux().modify(|w| { + w.set_mux_mode(GPIO_MUX_MODE); + }); + + // Setting direction is RMW + critical_section::with(|_cs| { + self.pin.block().gdir().modify(|w| { + w.set_gdir(self.pin.pin_number() as usize, false); + }); + }) + } + + /// Put the pin into output mode. + /// + /// The pin level will be whatever was set before (or low by default). If you want it to begin + /// at a specific level, call `set_high`/`set_low` on the pin first. + #[inline] + pub fn set_as_output(&mut self) { + self.pin.mux().modify(|w| { + w.set_mux_mode(GPIO_MUX_MODE); + }); + + // Setting direction is RMW + critical_section::with(|_cs| { + self.pin.block().gdir().modify(|w| { + w.set_gdir(self.pin.pin_number() as usize, true); + }); + }) + } + + /// Put the pin into input + open-drain output mode. + /// + /// The hardware will drive the line low if you set it to low, and will leave it floating if you set + /// it to high, in which case you can read the input to figure out whether another device + /// is driving the line low. + /// + /// The pin level will be whatever was set before (or low by default). If you want it to begin + /// at a specific level, call `set_high`/`set_low` on the pin first. + /// + /// The internal weak pull-up and pull-down resistors will be disabled. + #[inline] + pub fn set_as_input_output(&mut self) { + self.pin.pad().modify(|w| { + w.set_ode(true); + }); + } + + /// Set the pin as "disconnected", ie doing nothing and consuming the lowest + /// amount of power possible. + /// + /// This is currently the same as [`Self::set_as_analog()`] but is semantically different + /// really. Drivers should `set_as_disconnected()` pins when dropped. + /// + /// Note that this also disables the pull-up and pull-down resistors. + #[inline] + pub fn set_as_disconnected(&mut self) { + self.pin.pad().modify(|w| { + w.set_ode(false); + w.set_pke(false); + w.set_pue(false); + w.set_pus(Pus::PUS_0_100K_OHM_PULL_DOWN); + }); + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + self.pin.block().psr().read().psr(self.pin.pin_number() as usize) + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + !self.is_high() + } + + /// Returns current pin level + #[inline] + pub fn get_level(&self) -> Level { + self.is_high().into() + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + self.pin.block().dr_set().write(|w| { + w.set_dr_set(self.pin.pin_number() as usize, true); + }); + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + self.pin.block().dr_clear().write(|w| { + w.set_dr_clear(self.pin.pin_number() as usize, true); + }); + } + + /// Toggle pin output + #[inline] + pub fn toggle(&mut self) { + self.pin.block().dr_toggle().write(|w| { + w.set_dr_toggle(self.pin.pin_number() as usize, true); + }); + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + match level { + Level::Low => self.set_low(), + Level::High => self.set_high(), + } + } + + /// Get the current pin output level. + #[inline] + pub fn get_output_level(&self) -> Level { + self.is_set_high().into() + } + + /// Is the output level high? + /// + /// If the [`Flex`] is set as an input, then this is equivalent to [`Flex::is_high`]. + #[inline] + pub fn is_set_high(&self) -> bool { + self.pin.block().dr().read().dr(self.pin.pin_number() as usize) + } + + /// Is the output level low? + /// + /// If the [`Flex`] is set as an input, then this is equivalent to [`Flex::is_low`]. + #[inline] + pub fn is_set_low(&self) -> bool { + !self.is_set_high() + } + + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub async fn wait_for_high(&mut self) { + InputFuture::new(self.pin.reborrow(), InterruptConfiguration::High).await + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub async fn wait_for_low(&mut self) { + InputFuture::new(self.pin.reborrow(), InterruptConfiguration::Low).await + } + + /// Wait for the pin to undergo a transition from low to high. + #[inline] + pub async fn wait_for_rising_edge(&mut self) { + InputFuture::new(self.pin.reborrow(), InterruptConfiguration::RisingEdge).await + } + + /// Wait for the pin to undergo a transition from high to low. + #[inline] + pub async fn wait_for_falling_edge(&mut self) { + InputFuture::new(self.pin.reborrow(), InterruptConfiguration::FallingEdge).await + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. + #[inline] + pub async fn wait_for_any_edge(&mut self) { + InputFuture::new(self.pin.reborrow(), InterruptConfiguration::AnyEdge).await + } +} + +impl<'d> Drop for Flex<'d> { + fn drop(&mut self) { + self.set_as_disconnected(); + } +} + +/// GPIO input driver. +pub struct Input<'d> { + pin: Flex<'d>, +} + +impl<'d> Input<'d> { + /// Create GPIO input driver for a [Pin] with the provided [Pull] configuration. + #[inline] + pub fn new(pin: Peri<'d, impl Pin>, pull: Pull) -> Self { + let mut pin = Flex::new(pin); + pin.set_as_input(); + pin.set_pull(pull); + Self { pin } + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + self.pin.is_high() + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + self.pin.is_low() + } + + /// Get the current pin input level. + #[inline] + pub fn get_level(&self) -> Level { + self.pin.get_level() + } + + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub async fn wait_for_high(&mut self) { + self.pin.wait_for_high().await + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub async fn wait_for_low(&mut self) { + self.pin.wait_for_low().await + } + + /// Wait for the pin to undergo a transition from low to high. + #[inline] + pub async fn wait_for_rising_edge(&mut self) { + self.pin.wait_for_rising_edge().await + } + + /// Wait for the pin to undergo a transition from high to low. + #[inline] + pub async fn wait_for_falling_edge(&mut self) { + self.pin.wait_for_falling_edge().await + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. + #[inline] + pub async fn wait_for_any_edge(&mut self) { + self.pin.wait_for_any_edge().await + } +} + +/// GPIO output driver. +/// +/// Note that pins will **return to their floating state** when `Output` is dropped. +/// If pins should retain their state indefinitely, either keep ownership of the +/// `Output`, or pass it to [`core::mem::forget`]. +pub struct Output<'d> { + pin: Flex<'d>, +} + +impl<'d> Output<'d> { + /// Create GPIO output driver for a [Pin] with the provided [Level] configuration. + #[inline] + pub fn new(pin: Peri<'d, impl Pin>, initial_output: Level) -> Self { + let mut pin = Flex::new(pin); + pin.set_as_output(); + pin.set_level(initial_output); + Self { pin } + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + self.pin.set_high(); + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + self.pin.set_low(); + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + self.pin.set_level(level) + } + + /// Is the output pin set as high? + #[inline] + pub fn is_set_high(&self) -> bool { + self.pin.is_set_high() + } + + /// Is the output pin set as low? + #[inline] + pub fn is_set_low(&self) -> bool { + self.pin.is_set_low() + } + + /// What level output is set to + #[inline] + pub fn get_output_level(&self) -> Level { + self.pin.get_output_level() + } + + /// Toggle pin output + #[inline] + pub fn toggle(&mut self) { + self.pin.toggle(); + } +} + +/// GPIO output open-drain driver. +/// +/// Note that pins will **return to their floating state** when `OutputOpenDrain` is dropped. +/// If pins should retain their state indefinitely, either keep ownership of the +/// `OutputOpenDrain`, or pass it to [`core::mem::forget`]. +pub struct OutputOpenDrain<'d> { + pin: Flex<'d>, +} + +impl<'d> OutputOpenDrain<'d> { + /// Create a new GPIO open drain output driver for a [Pin] with the provided [Level]. + #[inline] + pub fn new(pin: Peri<'d, impl Pin>, initial_output: Level) -> Self { + let mut pin = Flex::new(pin); + pin.set_level(initial_output); + pin.set_as_input_output(); + Self { pin } + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + !self.pin.is_low() + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + self.pin.is_low() + } + + /// Get the current pin input level. + #[inline] + pub fn get_level(&self) -> Level { + self.pin.get_level() + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + self.pin.set_high(); + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + self.pin.set_low(); + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + self.pin.set_level(level); + } + + /// Get whether the output level is set to high. + #[inline] + pub fn is_set_high(&self) -> bool { + self.pin.is_set_high() + } + + /// Get whether the output level is set to low. + #[inline] + pub fn is_set_low(&self) -> bool { + self.pin.is_set_low() + } + + /// Get the current output level. + #[inline] + pub fn get_output_level(&self) -> Level { + self.pin.get_output_level() + } + + /// Toggle pin output + #[inline] + pub fn toggle(&mut self) { + self.pin.toggle() + } + + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub async fn wait_for_high(&mut self) { + self.pin.wait_for_high().await + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub async fn wait_for_low(&mut self) { + self.pin.wait_for_low().await + } + + /// Wait for the pin to undergo a transition from low to high. + #[inline] + pub async fn wait_for_rising_edge(&mut self) { + self.pin.wait_for_rising_edge().await + } + + /// Wait for the pin to undergo a transition from high to low. + #[inline] + pub async fn wait_for_falling_edge(&mut self) { + self.pin.wait_for_falling_edge().await + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. + #[inline] + pub async fn wait_for_any_edge(&mut self) { + self.pin.wait_for_any_edge().await + } +} + +#[allow(private_bounds)] +pub trait Pin: PeripheralType + Into + SealedPin + Sized + 'static { + /// Returns the pin number within a bank + #[inline] + fn pin(&self) -> u8 { + self.pin_number() + } + + #[inline] + fn bank(&self) -> Bank { + self._bank() + } +} + +/// Type-erased GPIO pin. +pub struct AnyPin { + pub(crate) pin_number: u8, + pub(crate) bank: Bank, +} + +impl AnyPin { + /// Unsafely create a new type-erased pin. + /// + /// # Safety + /// + /// You must ensure that you’re only using one instance of this type at a time. + pub unsafe fn steal(bank: Bank, pin_number: u8) -> Peri<'static, Self> { + Peri::new_unchecked(Self { pin_number, bank }) + } +} + +impl_peripheral!(AnyPin); + +impl Pin for AnyPin {} +impl SealedPin for AnyPin { + #[inline] + fn pin_number(&self) -> u8 { + self.pin_number + } + + #[inline] + fn _bank(&self) -> Bank { + self.bank + } +} + +// Impl details + +/// Mux mode for GPIO pins. This is constant across all RT1xxx parts. +const GPIO_MUX_MODE: u8 = 0b101; + +// FIXME: These don't always need to be 32 entries. GPIO5 on RT1101 contains a single pin and GPIO2 only 14. +#[cfg(gpio1)] +static GPIO1_WAKERS: [AtomicWaker; 32] = [const { AtomicWaker::new() }; 32]; +#[cfg(gpio2)] +static GPIO2_WAKERS: [AtomicWaker; 32] = [const { AtomicWaker::new() }; 32]; +#[cfg(gpio5)] +static GPIO5_WAKERS: [AtomicWaker; 32] = [const { AtomicWaker::new() }; 32]; + +/// Sealed trait for pins. This trait is sealed and cannot be implemented outside of this crate. +pub(crate) trait SealedPin: Sized { + fn pin_number(&self) -> u8; + + fn _bank(&self) -> Bank; + + #[inline] + fn block(&self) -> Gpio { + match self._bank() { + #[cfg(gpio1)] + Bank::Gpio1 => pac::GPIO1, + #[cfg(gpio2)] + Bank::Gpio2 => pac::GPIO2, + #[cfg(gpio5)] + Bank::Gpio5 => pac::GPIO5, + } + } + + #[inline] + fn mux(&self) -> Reg { + // SAFETY: The generated mux address table is valid since it is generated from the SVD files. + let address = unsafe { mux_address(self._bank(), self.pin_number()).unwrap_unchecked() }; + + // SAFETY: The register at the address is an instance of MuxCtl. + unsafe { Reg::from_ptr(address as *mut _) } + } + + #[inline] + fn pad(&self) -> Reg { + // SAFETY: The generated pad address table is valid since it is generated from the SVD files. + let address = unsafe { pad_address(self._bank(), self.pin_number()).unwrap_unchecked() }; + + // SAFETY: The register at the address is an instance of Ctl. + unsafe { Reg::from_ptr(address as *mut _) } + } + + fn waker(&self) -> &AtomicWaker { + match self._bank() { + #[cfg(gpio1)] + Bank::Gpio1 => &GPIO1_WAKERS[self.pin_number() as usize], + #[cfg(gpio2)] + Bank::Gpio2 => &GPIO2_WAKERS[self.pin_number() as usize], + #[cfg(gpio5)] + Bank::Gpio5 => &GPIO5_WAKERS[self.pin_number() as usize], + } + } +} + +/// This enum matches the layout of Icr. +enum InterruptConfiguration { + Low, + High, + RisingEdge, + FallingEdge, + AnyEdge, +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct InputFuture<'d> { + pin: Peri<'d, AnyPin>, +} + +impl<'d> InputFuture<'d> { + fn new(pin: Peri<'d, AnyPin>, config: InterruptConfiguration) -> Self { + let block = pin.block(); + + let (icr, edge_sel) = match config { + InterruptConfiguration::Low => (Icr::LOW_LEVEL, false), + InterruptConfiguration::High => (Icr::HIGH_LEVEL, false), + InterruptConfiguration::RisingEdge => (Icr::RISING_EDGE, false), + InterruptConfiguration::FallingEdge => (Icr::FALLING_EDGE, false), + InterruptConfiguration::AnyEdge => (Icr::FALLING_EDGE, true), + }; + + let index = if pin.pin_number() > 15 { 1 } else { 0 }; + + // Interrupt configuration performs RMW + critical_section::with(|_cs| { + // Disable interrupt so a level/edge detection change does not cause ISR to be set. + block.imr().modify(|w| { + w.set_imr(pin.pin_number() as usize, false); + }); + + block.icr(index).modify(|w| { + w.set_pin(pin.pin_number() as usize, icr); + }); + + block.edge_sel().modify(|w| { + w.set_edge_sel(pin.pin_number() as usize, edge_sel); + }); + + // Clear the previous interrupt. + block.isr().modify(|w| { + // "Status flags are cleared by writing a 1 to the corresponding bit position." + w.set_isr(pin.pin_number() as usize, true); + }); + }); + + Self { pin } + } +} + +impl<'d> Future for InputFuture<'d> { + type Output = (); + + fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // We need to register/re-register the waker for each poll because any + // calls to wake will deregister the waker. + let waker = self.pin.waker(); + waker.register(cx.waker()); + + // Enabling interrupt is RMW + critical_section::with(|_cs| { + self.pin.block().imr().modify(|w| { + w.set_imr(self.pin.pin_number() as usize, true); + }); + }); + + let isr = self.pin.block().isr().read(); + + if isr.isr(self.pin.pin_number() as usize) { + return Poll::Ready(()); + } + + Poll::Pending + } +} + +/// A macro to generate all GPIO pins. +/// +/// This generates a lookup table for IOMUX register addresses. +macro_rules! impl_gpio { + ( + $($name: ident($bank: ident, $pin_number: expr);)* + ) => { + #[inline] + pub(crate) const fn pad_address(bank: crate::gpio::Bank, pin: u8) -> Option { + match (bank, pin) { + $( + (crate::gpio::Bank::$bank, $pin_number) => Some(crate::chip::_generated::iomuxc::pads::$name), + )* + _ => None + } + } + + #[inline] + pub(crate) const fn mux_address(bank: crate::gpio::Bank, pin: u8) -> Option { + match (bank, pin) { + $( + (crate::gpio::Bank::$bank, $pin_number) => Some(crate::chip::_generated::iomuxc::muxes::$name), + )* + _ => None + } + } + + $( + impl_pin!($name, $bank, $pin_number); + )* + }; +} + +macro_rules! impl_pin { + ($name: ident, $bank: ident, $pin_num: expr) => { + impl crate::gpio::Pin for crate::peripherals::$name {} + impl crate::gpio::SealedPin for crate::peripherals::$name { + #[inline] + fn pin_number(&self) -> u8 { + $pin_num + } + + #[inline] + fn _bank(&self) -> crate::gpio::Bank { + crate::gpio::Bank::$bank + } + } + + impl From for crate::gpio::AnyPin { + fn from(val: peripherals::$name) -> Self { + use crate::gpio::SealedPin; + + Self { + pin_number: val.pin_number(), + bank: val._bank(), + } + } + } + }; +} + +pub(crate) fn init() { + #[cfg(feature = "rt")] + unsafe { + use embassy_hal_internal::interrupt::InterruptExt; + + pac::Interrupt::GPIO1_COMBINED_0_15.enable(); + pac::Interrupt::GPIO1_COMBINED_16_31.enable(); + pac::Interrupt::GPIO2_COMBINED_0_15.enable(); + pac::Interrupt::GPIO5_COMBINED_0_15.enable(); + } +} + +/// IRQ handler for GPIO pins. +/// +/// If `high_bits` is false, then the interrupt is for pins 0 through 15. If true, then the interrupt +/// is for pins 16 through 31 +#[cfg(feature = "rt")] +fn irq_handler(block: Gpio, wakers: &[AtomicWaker; 32], high_bits: bool) { + use crate::BitIter; + + let isr = block.isr().read().0; + let imr = block.imr().read().0; + let mask = if high_bits { 0xFFFF_0000 } else { 0x0000_FFFF }; + let bits = isr & imr & mask; + + for bit in BitIter(bits) { + wakers[bit as usize].wake(); + + // Disable further interrupts for this pin. The input future will check ISR (which is kept + // until reset). + block.imr().modify(|w| { + w.set_imr(bit as usize, false); + }); + } +} + +#[cfg(all(feature = "mimxrt1011", feature = "rt"))] +#[interrupt] +fn GPIO1_COMBINED_0_15() { + irq_handler(pac::GPIO1, &GPIO1_WAKERS, false); +} + +#[cfg(all(feature = "mimxrt1011", feature = "rt"))] +#[interrupt] +fn GPIO1_COMBINED_16_31() { + irq_handler(pac::GPIO1, &GPIO1_WAKERS, true); +} + +#[cfg(all(feature = "mimxrt1011", feature = "rt"))] +#[interrupt] +fn GPIO2_COMBINED_0_15() { + irq_handler(pac::GPIO2, &GPIO2_WAKERS, false); +} + +#[cfg(all(feature = "mimxrt1011", feature = "rt"))] +#[interrupt] +fn GPIO5_COMBINED_0_15() { + irq_handler(pac::GPIO5, &GPIO5_WAKERS, false); +} diff --git a/embassy-nxp/src/lib.rs b/embassy-nxp/src/lib.rs index 1abaca708..a715770c4 100644 --- a/embassy-nxp/src/lib.rs +++ b/embassy-nxp/src/lib.rs @@ -1,12 +1,19 @@ #![no_std] -pub mod fmt; +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + pub mod gpio; #[cfg(feature = "lpc55")] pub mod pint; +#[cfg(feature = "_time_driver")] +#[cfg_attr(feature = "time-driver-pit", path = "time_driver/pit.rs")] +mod time_driver; + // This mod MUST go last, so that it sees all the `impl_foo!` macros #[cfg_attr(feature = "lpc55", path = "chips/lpc55.rs")] +#[cfg_attr(feature = "mimxrt1011", path = "chips/mimxrt1011.rs")] mod chip; #[cfg(feature = "unstable-pac")] @@ -22,13 +29,66 @@ pub use embassy_hal_internal::{Peri, PeripheralType}; /// /// This should only be called once and at startup, otherwise it panics. pub fn init(_config: config::Config) -> Peripherals { - #[cfg(feature = "lpc55")] + // Do this first, so that it panics if user is calling `init` a second time + // before doing anything important. + let peripherals = Peripherals::take(); + + #[cfg(feature = "mimxrt1011")] { - gpio::init(); - pint::init(); + // The RT1010 Reference manual states that core clock root must be switched before + // reprogramming PLL2. + pac::CCM.cbcdr().modify(|w| { + w.set_periph_clk_sel(pac::ccm::vals::PeriphClkSel::PERIPH_CLK_SEL_1); + }); + + while matches!( + pac::CCM.cdhipr().read().periph_clk_sel_busy(), + pac::ccm::vals::PeriphClkSelBusy::PERIPH_CLK_SEL_BUSY_1 + ) {} + + info!("Core clock root switched"); + + // 480 * 18 / 24 = 360 + pac::CCM_ANALOG.pfd_480().modify(|x| x.set_pfd2_frac(12)); + + //480*18/24(pfd0)/4 + pac::CCM_ANALOG.pfd_480().modify(|x| x.set_pfd0_frac(24)); + pac::CCM.cscmr1().modify(|x| x.set_flexspi_podf(3.into())); + + // CPU Core + pac::CCM_ANALOG.pfd_528().modify(|x| x.set_pfd3_frac(18)); + cortex_m::asm::delay(500_000); + + // Clock core clock with PLL 2. + pac::CCM + .cbcdr() + .modify(|x| x.set_periph_clk_sel(pac::ccm::vals::PeriphClkSel::PERIPH_CLK_SEL_0)); // false + + while matches!( + pac::CCM.cdhipr().read().periph_clk_sel_busy(), + pac::ccm::vals::PeriphClkSelBusy::PERIPH_CLK_SEL_BUSY_1 + ) {} + + pac::CCM + .cbcmr() + .write(|v| v.set_pre_periph_clk_sel(pac::ccm::vals::PrePeriphClkSel::PRE_PERIPH_CLK_SEL_0)); + + // TODO: Some for USB PLLs + + // DCDC clock? + pac::CCM.ccgr6().modify(|v| v.set_cg0(1)); } - crate::Peripherals::take() + #[cfg(any(feature = "lpc55", rt1xxx))] + gpio::init(); + + #[cfg(feature = "lpc55")] + pint::init(); + + #[cfg(feature = "_time_driver")] + time_driver::init(); + + peripherals } /// HAL configuration for the NXP board. @@ -36,3 +96,20 @@ pub mod config { #[derive(Default)] pub struct Config {} } + +#[allow(unused)] +struct BitIter(u32); + +impl Iterator for BitIter { + type Item = u32; + + fn next(&mut self) -> Option { + match self.0.trailing_zeros() { + 32 => None, + b => { + self.0 &= !(1 << b); + Some(b) + } + } + } +} diff --git a/embassy-nxp/src/time_driver/pit.rs b/embassy-nxp/src/time_driver/pit.rs new file mode 100644 index 000000000..985e5e815 --- /dev/null +++ b/embassy-nxp/src/time_driver/pit.rs @@ -0,0 +1,187 @@ +//! Time driver using Periodic Interrupt Timer (PIT) +//! +//! This driver is used with the iMXRT1xxx parts. +//! +//! The PIT is run in lifetime mode. Timer 1 is chained to timer 0 to provide a free-running 64-bit timer. +//! The 64-bit timer is used to track how many ticks since boot. +//! +//! Timer 2 counts how many ticks there are within the current u32::MAX tick period. Timer 2 is restarted when +//! a new alarm is set (or every u32::MAX ticks). One caveat is that an alarm could be a few ticks late due to +//! restart. However the Cortex-M7 cores run at 500 MHz easily and the PIT will generally run at 1 MHz or lower. +//! Along with the fact that scheduling an alarm takes a critical section worst case an alarm may be a few +//! microseconds late. +//! +//! All PIT timers are clocked in lockstep, so the late start will not cause the now() count to drift. + +use core::cell::{Cell, RefCell}; +use core::task::Waker; + +use critical_section::{CriticalSection, Mutex}; +use embassy_hal_internal::interrupt::InterruptExt; +use embassy_time_driver::Driver as _; +use embassy_time_queue_utils::Queue; + +use crate::pac::{self, interrupt}; + +struct Driver { + alarm: Mutex>, + queue: Mutex>, +} + +impl embassy_time_driver::Driver for Driver { + fn now(&self) -> u64 { + loop { + // Even though reading LTMR64H will latch LTMR64L if another thread preempts between any of the + // three reads and calls now() then the value in LTMR64L will be wrong when execution returns to + // thread which was preempted. + let hi = pac::PIT.ltmr64h().read().lth(); + let lo = pac::PIT.ltmr64l().read().ltl(); + let hi2 = pac::PIT.ltmr64h().read().lth(); + + if hi == hi2 { + // PIT timers always count down. + return u64::MAX - ((hi as u64) << 32 | (lo as u64)); + } + } + } + + fn schedule_wake(&self, at: u64, waker: &Waker) { + critical_section::with(|cs| { + let mut queue = self.queue.borrow(cs).borrow_mut(); + + if queue.schedule_wake(at, waker) { + let mut next = queue.next_expiration(self.now()); + + while !self.set_alarm(cs, next) { + next = queue.next_expiration(self.now()); + } + } + }) + } +} + +impl Driver { + fn init(&'static self) { + // Disable PIT clock during mux configuration. + pac::CCM.ccgr1().modify(|r| r.set_cg6(0b00)); + + // TODO: This forces the PIT to be driven by the oscillator. However that isn't the only option as you + // could divide the clock root by up to 64. + pac::CCM.cscmr1().modify(|r| { + // 1 MHz + r.set_perclk_podf(pac::ccm::vals::PerclkPodf::DIVIDE_24); + r.set_perclk_clk_sel(pac::ccm::vals::PerclkClkSel::PERCLK_CLK_SEL_1); + }); + + pac::CCM.ccgr1().modify(|r| r.set_cg6(0b11)); + + // Disable clock during init. + // + // It is important that the PIT clock is prepared to not exceed limit (50 MHz on RT1011), or else + // you will need to recover the device with boot mode switches when using any PIT registers. + pac::PIT.mcr().modify(|w| { + w.set_mdis(true); + }); + + pac::PIT.timer(0).ldval().write_value(u32::MAX); + pac::PIT.timer(1).ldval().write_value(u32::MAX); + pac::PIT.timer(2).ldval().write_value(0); + pac::PIT.timer(3).ldval().write_value(0); + + pac::PIT.timer(1).tctrl().write(|w| { + // In lifetime mode, timer 1 is chained to timer 0 to form a 64-bit timer. + w.set_chn(true); + w.set_ten(true); + w.set_tie(false); + }); + + pac::PIT.timer(0).tctrl().write(|w| { + w.set_chn(false); + w.set_ten(true); + w.set_tie(false); + }); + + pac::PIT.timer(2).tctrl().write(|w| { + w.set_tie(true); + }); + + unsafe { interrupt::PIT.enable() }; + + pac::PIT.mcr().write(|w| { + w.set_mdis(false); + }); + } + + fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { + let alarm = self.alarm.borrow(cs); + alarm.set(timestamp); + + let timer = pac::PIT.timer(2); + let now = self.now(); + + if timestamp <= now { + alarm.set(u64::MAX); + + return false; + } + + timer.tctrl().modify(|x| x.set_ten(false)); + timer.tflg().modify(|x| x.set_tif(true)); + + // If the next alarm happens in more than u32::MAX cycles then the alarm will be restarted later. + timer.ldval().write_value((timestamp - now) as u32); + timer.tctrl().modify(|x| x.set_ten(true)); + + true + } + + fn trigger_alarm(&self, cs: CriticalSection) { + let mut next = self.queue.borrow_ref_mut(cs).next_expiration(self.now()); + + while !self.set_alarm(cs, next) { + next = self.queue.borrow_ref_mut(cs).next_expiration(self.now()); + } + } + + fn on_interrupt(&self) { + critical_section::with(|cs| { + let timer = pac::PIT.timer(2); + let alarm = self.alarm.borrow(cs); + let interrupted = timer.tflg().read().tif(); + timer.tflg().write(|r| r.set_tif(true)); + + if interrupted { + // A new load value will not apply until the next timer expiration. + // + // The expiry may be up to u32::MAX cycles away, so the timer must be restarted. + timer.tctrl().modify(|r| r.set_ten(false)); + + let now = self.now(); + let timestamp = alarm.get(); + + if timestamp <= now { + self.trigger_alarm(cs); + } else { + // The alarm is not ready. Wait for u32::MAX cycles and check again or set the next alarm. + timer.ldval().write_value((timestamp - now) as u32); + timer.tctrl().modify(|r| r.set_ten(true)); + } + } + }); + } +} + +embassy_time_driver::time_driver_impl!(static DRIVER: Driver = Driver { + alarm: Mutex::new(Cell::new(0)), + queue: Mutex::new(RefCell::new(Queue::new())) +}); + +pub(crate) fn init() { + DRIVER.init(); +} + +#[cfg(feature = "rt")] +#[interrupt] +fn PIT() { + DRIVER.on_interrupt(); +} diff --git a/examples/mimxrt1011/.cargo/config.toml b/examples/mimxrt1011/.cargo/config.toml new file mode 100644 index 000000000..12f4b27b2 --- /dev/null +++ b/examples/mimxrt1011/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv7em-none-eabihf] +runner = 'probe-rs run --chip MIMXRT1010' + +[build] +target = "thumbv7em-none-eabihf" # Cortex-M7 + +[env] +DEFMT_LOG = "trace" diff --git a/examples/mimxrt1011/Cargo.toml b/examples/mimxrt1011/Cargo.toml new file mode 100644 index 000000000..cf4e4c163 --- /dev/null +++ b/examples/mimxrt1011/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "embassy-imxrt1011-examples" +version = "0.1.0" +edition = "2021" +license = "MIT or Apache-2.0" + +[dependencies] +cortex-m = { version = "0.7.7", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.3" +defmt = "1.0.1" +defmt-rtt = "1.0.0" + +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-futures = { version = "0.1.1", path = "../../embassy-futures" } +embassy-nxp = { version = "0.1.0", path = "../../embassy-nxp", features = ["defmt", "mimxrt1011", "unstable-pac", "time-driver-pit"] } +embassy-time = { version = "0.4", path = "../../embassy-time", features = ["defmt", ] } # "defmt-timestamp-uptime" # RT1011 hard faults currently with this enabled. +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = "1.0.0" + +imxrt-boot-gen = { version = "0.3.4", features = ["imxrt1010"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +panic-semihosting = "0.6.0" + +[build-dependencies] +imxrt-rt = { version = "0.1.7", features = ["device"] } + +[profile.release] +debug = 2 diff --git a/examples/mimxrt1011/build.rs b/examples/mimxrt1011/build.rs new file mode 100644 index 000000000..99e172aba --- /dev/null +++ b/examples/mimxrt1011/build.rs @@ -0,0 +1,14 @@ +use imxrt_rt::{Family, RuntimeBuilder}; + +fn main() { + // The IMXRT1010-EVK technically has 128M of flash, but we only ever use 8MB so that the examples + // will build fine on the Adafruit Metro M7 boards. + RuntimeBuilder::from_flexspi(Family::Imxrt1010, 8 * 1024 * 1024) + .build() + .unwrap(); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + // Not link.x, as imxrt-rt needs to do some special things + println!("cargo:rustc-link-arg-bins=-Timxrt-link.x"); +} diff --git a/examples/mimxrt1011/src/bin/blinky.rs b/examples/mimxrt1011/src/bin/blinky.rs new file mode 100644 index 000000000..a5d5de6b3 --- /dev/null +++ b/examples/mimxrt1011/src/bin/blinky.rs @@ -0,0 +1,48 @@ +//! This example works on the following boards: +//! - IMXRT1010-EVK +//! - Adafruit Metro M7 (with microSD or with AirLift), requires an external button +//! - Makerdiary iMX RT1011 Nano Kit (TODO: currently untested, please change this) +//! +//! Although beware you will need to change the GPIO pins being used (scroll down). + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nxp::gpio::{Level, Output}; +use embassy_time::Timer; +// Must include `embassy_imxrt1011_examples` to ensure the FCB gets linked. +use {defmt_rtt as _, embassy_imxrt1011_examples as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let p = embassy_nxp::init(Default::default()); + info!("Hello world!"); + + /* Pick the pins to use depending on your board. */ + + // IMXRT1010-EVK + // + // LED (D25) + let led = p.GPIO_11; + + // Adafruit Metro M7 (both microSD and AirLift variants) + // + // The LED is connected to D13 on the board. + // let led = p.GPIO_03; + + // Makerdiary iMX RT1011 Nano Kit + // + // LED0 + // let led = p.GPIO_SD_04; + + let mut led = Output::new(led, Level::Low); + + loop { + Timer::after_millis(500).await; + + info!("Toggle"); + led.toggle(); + } +} diff --git a/examples/mimxrt1011/src/bin/button.rs b/examples/mimxrt1011/src/bin/button.rs new file mode 100644 index 000000000..e63d7171d --- /dev/null +++ b/examples/mimxrt1011/src/bin/button.rs @@ -0,0 +1,62 @@ +//! This example works on the following boards: +//! - IMXRT1010-EVK +//! - Adafruit Metro M7 (with microSD or with AirLift), requires an external button +//! - Makerdiary iMX RT1011 Nano Kit (TODO: currently untested, please change this) +//! +//! Although beware you will need to change the GPIO pins being used (scroll down). + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nxp::gpio::{Input, Level, Output, Pull}; +// Must include `embassy_imxrt1011_examples` to ensure the FCB gets linked. +use {defmt_rtt as _, embassy_imxrt1011_examples as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let p = embassy_nxp::init(Default::default()); + info!("Hello world!"); + + /* Pick the pins to use depending on your board. */ + + // IMXRT1010-EVK + // + // LED (D25) and user button (SW4) + let (led, button) = (p.GPIO_11, p.GPIO_SD_05); + + // Adafruit Metro M7 (both microSD and AirLift variants) + // + // The LED is connected to D13 on the board. + // + // In particular the Metro M7 has no board user buttons, so you will need to connect a button. + // Any other GPIO pin can be used. GPIO_04 is used for example since it is on pin D12. + // let (led, button) = (p.GPIO_03, p.GPIO_04); + + // Makerdiary iMX RT1011 Nano Kit + // + // LED0 and user button. + // let (led, button) = (p.GPIO_SD_04, p.GPIO_SD_03); + + let mut button = Input::new(button, Pull::Up100K); + let mut led = Output::new(led, Level::Low); + led.set_high(); + + loop { + button.wait_for_falling_edge().await; + + info!("Toggled"); + led.toggle(); + + // The RT1010EVK has a 100 nF debouncing capacitor which results in false positive events + // when listening for a falling edge in a loop, wait for the rising edge and then wait for + // stabilization. + button.wait_for_rising_edge().await; + + // Stabilization. + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + } +} diff --git a/examples/mimxrt1011/src/lib.rs b/examples/mimxrt1011/src/lib.rs new file mode 100644 index 000000000..f0391ef57 --- /dev/null +++ b/examples/mimxrt1011/src/lib.rs @@ -0,0 +1,75 @@ +//! FlexSPI configuration block (FCB) for iMXRT1011 boards. +//! +//! This is a generic FCB that should work with most QSPI flash. + +#![no_std] + +use imxrt_boot_gen::flexspi; +use imxrt_boot_gen::flexspi::opcodes::sdr::*; +use imxrt_boot_gen::flexspi::{ + ColumnAddressWidth, Command, DeviceModeConfiguration, FlashPadType, Instr, LookupTable, Pads, + ReadSampleClockSource, Sequence, SequenceBuilder, SerialClockFrequency, SerialFlashRegion, + WaitTimeConfigurationCommands, +}; +use imxrt_boot_gen::serial_flash::nor; + +/// While the IMXRT1010-EVK and Makerdiary iMX RT1011 Nano Kit have 128MBit of flash we limit to 64Mbit +/// to allow the Metro M7 boards to use the same FCB configuration. +const DENSITY_BITS: u32 = 64 * 1024 * 1024; +const DENSITY_BYTES: u32 = DENSITY_BITS / 8; + +const SEQ_READ: Sequence = SequenceBuilder::new() + .instr(Instr::new(CMD, Pads::One, 0xEB)) + .instr(Instr::new(RADDR, Pads::Four, 0x18)) + .instr(Instr::new(DUMMY, Pads::Four, 0x06)) + .instr(Instr::new(READ, Pads::Four, 0x04)) + .build(); + +const SEQ_READ_STATUS: Sequence = SequenceBuilder::new() + .instr(Instr::new(CMD, Pads::One, 0x05)) + .instr(Instr::new(READ, Pads::One, 0x01)) + .build(); + +const SEQ_WRITE_ENABLE: Sequence = SequenceBuilder::new().instr(Instr::new(CMD, Pads::One, 0x06)).build(); + +const SEQ_ERASE_SECTOR: Sequence = SequenceBuilder::new() + .instr(Instr::new(CMD, Pads::One, 0x20)) + .instr(Instr::new(RADDR, Pads::One, 0x18)) + .build(); + +const SEQ_PAGE_PROGRAM: Sequence = SequenceBuilder::new() + .instr(Instr::new(CMD, Pads::One, 0x02)) + .instr(Instr::new(RADDR, Pads::One, 0x18)) + .instr(Instr::new(WRITE, Pads::One, 0x04)) + .build(); + +const SEQ_CHIP_ERASE: Sequence = SequenceBuilder::new().instr(Instr::new(CMD, Pads::One, 0x60)).build(); + +const LUT: LookupTable = LookupTable::new() + .command(Command::Read, SEQ_READ) + .command(Command::ReadStatus, SEQ_READ_STATUS) + .command(Command::WriteEnable, SEQ_WRITE_ENABLE) + .command(Command::EraseSector, SEQ_ERASE_SECTOR) + .command(Command::PageProgram, SEQ_PAGE_PROGRAM) + .command(Command::ChipErase, SEQ_CHIP_ERASE); + +const COMMON_CONFIGURATION_BLOCK: flexspi::ConfigurationBlock = flexspi::ConfigurationBlock::new(LUT) + .read_sample_clk_src(ReadSampleClockSource::LoopbackFromDQSPad) + .cs_hold_time(0x03) + .cs_setup_time(0x03) + .column_address_width(ColumnAddressWidth::OtherDevices) + .device_mode_configuration(DeviceModeConfiguration::Disabled) + .wait_time_cfg_commands(WaitTimeConfigurationCommands::disable()) + .flash_size(SerialFlashRegion::A1, DENSITY_BYTES) + .serial_clk_freq(SerialClockFrequency::MHz120) + .serial_flash_pad_type(FlashPadType::Quad); + +pub const SERIAL_NOR_CONFIGURATION_BLOCK: nor::ConfigurationBlock = + nor::ConfigurationBlock::new(COMMON_CONFIGURATION_BLOCK) + .page_size(256) + .sector_size(4096) + .ip_cmd_serial_clk_freq(nor::SerialClockFrequency::MHz30); + +#[unsafe(no_mangle)] +#[cfg_attr(all(target_arch = "arm", target_os = "none"), link_section = ".fcb")] +pub static FLEXSPI_CONFIGURATION_BLOCK: nor::ConfigurationBlock = SERIAL_NOR_CONFIGURATION_BLOCK; From 03b86d75b6861ed8151f4f48682e9b4f5b159232 Mon Sep 17 00:00:00 2001 From: dimi Date: Tue, 22 Jul 2025 18:16:11 +0200 Subject: [PATCH 13/15] derive Copy, Clone for adc config enums --- embassy-stm32/src/adc/adc4.rs | 4 +++- embassy-stm32/src/adc/c0.rs | 3 ++- embassy-stm32/src/adc/f3_v1_1.rs | 2 ++ embassy-stm32/src/adc/v4.rs | 1 + 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/adc/adc4.rs b/embassy-stm32/src/adc/adc4.rs index 98483489f..31cbdc0d7 100644 --- a/embassy-stm32/src/adc/adc4.rs +++ b/embassy-stm32/src/adc/adc4.rs @@ -76,12 +76,14 @@ impl SealedAdcChannel for Vcore { } } +#[derive(Copy, Clone)] pub enum DacChannel { OUT1, OUT2, } /// Number of samples used for averaging. +#[derive(Copy, Clone)] pub enum Averaging { Disabled, Samples2, @@ -187,7 +189,7 @@ pub struct Adc4<'d, T: Instance> { adc: crate::Peri<'d, T>, } -#[derive(Debug)] +#[derive(Copy, Clone, Debug)] pub enum Adc4Error { InvalidSequence, DMAError, diff --git a/embassy-stm32/src/adc/c0.rs b/embassy-stm32/src/adc/c0.rs index 936ad7413..f5870801e 100644 --- a/embassy-stm32/src/adc/c0.rs +++ b/embassy-stm32/src/adc/c0.rs @@ -48,7 +48,7 @@ impl SealedAdcChannel for Temperature { } } -#[derive(Debug)] +#[derive(Copy, Clone, Debug)] pub enum Prescaler { NotDivided, DividedBy2, @@ -138,6 +138,7 @@ impl<'a> defmt::Format for Prescaler { /// Number of samples used for averaging. /// TODO: Implement hardware averaging setting. #[allow(unused)] +#[derive(Copy, Clone)] pub enum Averaging { Disabled, Samples2, diff --git a/embassy-stm32/src/adc/f3_v1_1.rs b/embassy-stm32/src/adc/f3_v1_1.rs index 944e971bb..84613078c 100644 --- a/embassy-stm32/src/adc/f3_v1_1.rs +++ b/embassy-stm32/src/adc/f3_v1_1.rs @@ -17,6 +17,7 @@ pub const VDDA_CALIB_MV: u32 = 3300; pub const ADC_MAX: u32 = (1 << 12) - 1; pub const VREF_INT: u32 = 1230; +#[derive(Copy, Clone)] pub enum AdcPowerMode { AlwaysOn, DelayOff, @@ -24,6 +25,7 @@ pub enum AdcPowerMode { DelayIdleOff, } +#[derive(Copy, Clone)] pub enum Prescaler { Div1, Div2, diff --git a/embassy-stm32/src/adc/v4.rs b/embassy-stm32/src/adc/v4.rs index 39e0d51b9..b0871019a 100644 --- a/embassy-stm32/src/adc/v4.rs +++ b/embassy-stm32/src/adc/v4.rs @@ -142,6 +142,7 @@ impl Prescaler { } /// Number of samples used for averaging. +#[derive(Copy, Clone)] pub enum Averaging { Disabled, Samples2, From 1d46f55bddf402c33143959e1ad26af59bb15855 Mon Sep 17 00:00:00 2001 From: i509VCB Date: Mon, 21 Jul 2025 20:29:34 -0500 Subject: [PATCH 14/15] nxp: Add mimxrt1062 support The examples in this case are designed for the IMXRT1060-EVK. The same chip is used in the Teensy 4.0/1, but that will probably get its own set of examples due to some differences such as the FCB. --- .vscode/settings.json | 1 + ci.sh | 4 +- embassy-nxp/Cargo.toml | 5 +- embassy-nxp/build.rs | 10 +- embassy-nxp/src/chips/mimxrt1062.rs | 282 +++++++++++++++++++++ embassy-nxp/src/gpio/rt1xxx.rs | 62 ++++- embassy-nxp/src/lib.rs | 1 + examples/mimxrt1062-evk/.cargo/config.toml | 8 + examples/mimxrt1062-evk/Cargo.toml | 29 +++ examples/mimxrt1062-evk/build.rs | 12 + examples/mimxrt1062-evk/src/bin/blinky.rs | 25 ++ examples/mimxrt1062-evk/src/bin/button.rs | 36 +++ examples/mimxrt1062-evk/src/lib.rs | 60 +++++ 13 files changed, 522 insertions(+), 13 deletions(-) create mode 100644 embassy-nxp/src/chips/mimxrt1062.rs create mode 100644 examples/mimxrt1062-evk/.cargo/config.toml create mode 100644 examples/mimxrt1062-evk/Cargo.toml create mode 100644 examples/mimxrt1062-evk/build.rs create mode 100644 examples/mimxrt1062-evk/src/bin/blinky.rs create mode 100644 examples/mimxrt1062-evk/src/bin/button.rs create mode 100644 examples/mimxrt1062-evk/src/lib.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 070e8fbd3..6edd9312a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -37,6 +37,7 @@ // "examples/nrf5340/Cargo.toml", // "examples/nrf-rtos-trace/Cargo.toml", // "examples/mimxrt1011/Cargo.toml", + // "examples/mimxrt1062-evk/Cargo.toml", // "examples/rp/Cargo.toml", // "examples/std/Cargo.toml", // "examples/stm32c0/Cargo.toml", diff --git a/ci.sh b/ci.sh index e225bc7c9..1a9a1d209 100755 --- a/ci.sh +++ b/ci.sh @@ -181,7 +181,8 @@ cargo batch \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u073mb,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u083rc,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-nxp/Cargo.toml --target thumbv8m.main-none-eabihf --features lpc55,defmt \ - --- build --release --manifest-path embassy-nxp/Cargo.toml --target thumbv7em-none-eabihf --features mimxrt1011,defmt,time-driver-pit \ + --- build --release --manifest-path embassy-nxp/Cargo.toml --target thumbv7em-none-eabihf --features mimxrt1011,rt,defmt,time-driver-pit \ + --- build --release --manifest-path embassy-nxp/Cargo.toml --target thumbv7em-none-eabihf --features mimxrt1062,rt,defmt,time-driver-pit \ --- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0c1104dgs20,defmt,time-driver-any \ --- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0g3507pm,defmt,time-driver-any \ --- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0g3519pz,defmt,time-driver-any \ @@ -266,6 +267,7 @@ cargo batch \ --- build --release --manifest-path examples/stm32wl/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32wl \ --- build --release --manifest-path examples/lpc55s69/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/lpc55s69 \ --- build --release --manifest-path examples/mimxrt1011/Cargo.toml --target thumbv7em-none-eabihf --artifact-dir out/examples/mimxrt1011 \ + --- build --release --manifest-path examples/mimxrt1062-evk/Cargo.toml --target thumbv7em-none-eabihf --artifact-dir out/examples/mimxrt1062-evk \ --- build --release --manifest-path examples/mspm0g3507/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0g3507 \ --- build --release --manifest-path examples/mspm0g3519/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0g3519 \ --- build --release --manifest-path examples/mspm0l1306/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0l1306 \ diff --git a/embassy-nxp/Cargo.toml b/embassy-nxp/Cargo.toml index 625906183..293791d34 100644 --- a/embassy-nxp/Cargo.toml +++ b/embassy-nxp/Cargo.toml @@ -17,13 +17,13 @@ embassy-time-queue-utils = { version = "0.1", path = "../embassy-time-queue-util ## Chip dependencies lpc55-pac = { version = "0.5.0", optional = true } -nxp-pac = { version = "0.1.0", optional = true, git = "https://github.com/i509VCB/nxp-pac", rev = "1e010dbe75ab0e14dd908e4646391403414c8a8e" } +nxp-pac = { version = "0.1.0", optional = true, git = "https://github.com/i509VCB/nxp-pac", rev = "be4dd0936c20d5897364a381b1d95a99514c1e7e" } imxrt-rt = { version = "0.1.7", optional = true, features = ["device"] } [build-dependencies] cfg_aliases = "0.2.1" -nxp-pac = { version = "0.1.0", git = "https://github.com/i509VCB/nxp-pac", rev = "1e010dbe75ab0e14dd908e4646391403414c8a8e", features = ["metadata"], optional = true } +nxp-pac = { version = "0.1.0", git = "https://github.com/i509VCB/nxp-pac", rev = "be4dd0936c20d5897364a381b1d95a99514c1e7e", features = ["metadata"], optional = true } proc-macro2 = "1.0.95" quote = "1.0.15" @@ -58,3 +58,4 @@ _time_driver = ["dep:embassy-time-driver", "dep:embassy-time-queue-utils"] #! ### Chip selection features lpc55 = ["dep:lpc55-pac"] mimxrt1011 = ["nxp-pac/mimxrt1011", "_rt1xxx", "dep:imxrt-rt"] +mimxrt1062 = ["nxp-pac/mimxrt1062", "_rt1xxx", "dep:imxrt-rt"] diff --git a/embassy-nxp/build.rs b/embassy-nxp/build.rs index 6c10d0e69..f3c062c87 100644 --- a/embassy-nxp/build.rs +++ b/embassy-nxp/build.rs @@ -32,10 +32,12 @@ fn main() { .to_ascii_lowercase(); cfg_aliases! { - rt1xxx: { feature = "mimxrt1011" }, - gpio1: { feature = "mimxrt1011" }, - gpio2: { feature = "mimxrt1011" }, - gpio5: { feature = "mimxrt1011" }, + rt1xxx: { any(feature = "mimxrt1011", feature = "mimxrt1062") }, + gpio1: { any(feature = "mimxrt1011", feature = "mimxrt1062") }, + gpio2: { any(feature = "mimxrt1011", feature = "mimxrt1062") }, + gpio3: { feature = "mimxrt1062" }, + gpio4: { feature = "mimxrt1062" }, + gpio5: { any(feature = "mimxrt1011", feature = "mimxrt1062") }, } eprintln!("chip: {chip_name}"); diff --git a/embassy-nxp/src/chips/mimxrt1062.rs b/embassy-nxp/src/chips/mimxrt1062.rs new file mode 100644 index 000000000..ef153bd66 --- /dev/null +++ b/embassy-nxp/src/chips/mimxrt1062.rs @@ -0,0 +1,282 @@ +// This must be imported so that __preinit is defined. +use imxrt_rt as _; +pub use nxp_pac as pac; + +embassy_hal_internal::peripherals! { + // External pins. These are not only GPIOs, they are multi-purpose pins and can be used by other + // peripheral types (e.g. I2C). + GPIO_AD_B0_00, + GPIO_AD_B0_01, + GPIO_AD_B0_02, + GPIO_AD_B0_03, + GPIO_AD_B0_04, + GPIO_AD_B0_05, + GPIO_AD_B0_06, + GPIO_AD_B0_07, + GPIO_AD_B0_08, + GPIO_AD_B0_09, + GPIO_AD_B0_10, + GPIO_AD_B0_11, + GPIO_AD_B0_12, + GPIO_AD_B0_13, + GPIO_AD_B0_14, + GPIO_AD_B0_15, + GPIO_AD_B1_00, + GPIO_AD_B1_01, + GPIO_AD_B1_02, + GPIO_AD_B1_03, + GPIO_AD_B1_04, + GPIO_AD_B1_05, + GPIO_AD_B1_06, + GPIO_AD_B1_07, + GPIO_AD_B1_08, + GPIO_AD_B1_09, + GPIO_AD_B1_10, + GPIO_AD_B1_11, + GPIO_AD_B1_12, + GPIO_AD_B1_13, + GPIO_AD_B1_14, + GPIO_AD_B1_15, + GPIO_B0_00, + GPIO_B0_01, + GPIO_B0_02, + GPIO_B0_03, + GPIO_B0_04, + GPIO_B0_05, + GPIO_B0_06, + GPIO_B0_07, + GPIO_B0_08, + GPIO_B0_09, + GPIO_B0_10, + GPIO_B0_11, + GPIO_B0_12, + GPIO_B0_13, + GPIO_B0_14, + GPIO_B0_15, + GPIO_B1_00, + GPIO_B1_01, + GPIO_B1_02, + GPIO_B1_03, + GPIO_B1_04, + GPIO_B1_05, + GPIO_B1_06, + GPIO_B1_07, + GPIO_B1_08, + GPIO_B1_09, + GPIO_B1_10, + GPIO_B1_11, + GPIO_B1_12, + GPIO_B1_13, + GPIO_B1_14, + GPIO_B1_15, + GPIO_EMC_00, + GPIO_EMC_01, + GPIO_EMC_02, + GPIO_EMC_03, + GPIO_EMC_04, + GPIO_EMC_05, + GPIO_EMC_06, + GPIO_EMC_07, + GPIO_EMC_08, + GPIO_EMC_09, + GPIO_EMC_10, + GPIO_EMC_11, + GPIO_EMC_12, + GPIO_EMC_13, + GPIO_EMC_14, + GPIO_EMC_15, + GPIO_EMC_16, + GPIO_EMC_17, + GPIO_EMC_18, + GPIO_EMC_19, + GPIO_EMC_20, + GPIO_EMC_21, + GPIO_EMC_22, + GPIO_EMC_23, + GPIO_EMC_24, + GPIO_EMC_25, + GPIO_EMC_26, + GPIO_EMC_27, + GPIO_EMC_28, + GPIO_EMC_29, + GPIO_EMC_30, + GPIO_EMC_31, + GPIO_EMC_32, + GPIO_EMC_33, + GPIO_EMC_34, + GPIO_EMC_35, + GPIO_EMC_36, + GPIO_EMC_37, + GPIO_EMC_38, + GPIO_EMC_39, + GPIO_EMC_40, + GPIO_EMC_41, + GPIO_SD_B0_00, + GPIO_SD_B0_01, + GPIO_SD_B0_02, + GPIO_SD_B0_03, + GPIO_SD_B0_04, + GPIO_SD_B0_05, + GPIO_SD_B1_00, + GPIO_SD_B1_01, + GPIO_SD_B1_02, + GPIO_SD_B1_03, + GPIO_SD_B1_04, + GPIO_SD_B1_05, + GPIO_SD_B1_06, + GPIO_SD_B1_07, + GPIO_SD_B1_08, + GPIO_SD_B1_09, + GPIO_SD_B1_10, + GPIO_SD_B1_11, + WAKEUP, + PMIC_ON_REQ, + PMIC_STBY_REQ, +} + +impl_gpio! { + // GPIO Bank 1 + GPIO_AD_B0_00(Gpio1, 0); + GPIO_AD_B0_01(Gpio1, 1); + GPIO_AD_B0_02(Gpio1, 2); + GPIO_AD_B0_03(Gpio1, 3); + GPIO_AD_B0_04(Gpio1, 4); + GPIO_AD_B0_05(Gpio1, 5); + GPIO_AD_B0_06(Gpio1, 6); + GPIO_AD_B0_07(Gpio1, 7); + GPIO_AD_B0_08(Gpio1, 8); + GPIO_AD_B0_09(Gpio1, 9); + GPIO_AD_B0_10(Gpio1, 10); + GPIO_AD_B0_11(Gpio1, 11); + GPIO_AD_B0_12(Gpio1, 12); + GPIO_AD_B0_13(Gpio1, 13); + GPIO_AD_B0_14(Gpio1, 14); + GPIO_AD_B0_15(Gpio1, 15); + GPIO_AD_B1_00(Gpio1, 16); + GPIO_AD_B1_01(Gpio1, 17); + GPIO_AD_B1_02(Gpio1, 18); + GPIO_AD_B1_03(Gpio1, 19); + GPIO_AD_B1_04(Gpio1, 20); + GPIO_AD_B1_05(Gpio1, 21); + GPIO_AD_B1_06(Gpio1, 22); + GPIO_AD_B1_07(Gpio1, 23); + GPIO_AD_B1_08(Gpio1, 24); + GPIO_AD_B1_09(Gpio1, 25); + GPIO_AD_B1_10(Gpio1, 26); + GPIO_AD_B1_11(Gpio1, 27); + GPIO_AD_B1_12(Gpio1, 28); + GPIO_AD_B1_13(Gpio1, 29); + GPIO_AD_B1_14(Gpio1, 30); + GPIO_AD_B1_15(Gpio1, 31); + + // GPIO Bank 2 + GPIO_B0_00(Gpio2, 0); + GPIO_B0_01(Gpio2, 1); + GPIO_B0_02(Gpio2, 2); + GPIO_B0_03(Gpio2, 3); + GPIO_B0_04(Gpio2, 4); + GPIO_B0_05(Gpio2, 5); + GPIO_B0_06(Gpio2, 6); + GPIO_B0_07(Gpio2, 7); + GPIO_B0_08(Gpio2, 8); + GPIO_B0_09(Gpio2, 9); + GPIO_B0_10(Gpio2, 10); + GPIO_B0_11(Gpio2, 11); + GPIO_B0_12(Gpio2, 12); + GPIO_B0_13(Gpio2, 13); + GPIO_B0_14(Gpio2, 14); + GPIO_B0_15(Gpio2, 15); + GPIO_B1_00(Gpio2, 16); + GPIO_B1_01(Gpio2, 17); + GPIO_B1_02(Gpio2, 18); + GPIO_B1_03(Gpio2, 19); + GPIO_B1_04(Gpio2, 20); + GPIO_B1_05(Gpio2, 21); + GPIO_B1_06(Gpio2, 22); + GPIO_B1_07(Gpio2, 23); + GPIO_B1_08(Gpio2, 24); + GPIO_B1_09(Gpio2, 25); + GPIO_B1_10(Gpio2, 26); + GPIO_B1_11(Gpio2, 27); + GPIO_B1_12(Gpio2, 28); + GPIO_B1_13(Gpio2, 29); + GPIO_B1_14(Gpio2, 30); + GPIO_B1_15(Gpio2, 31); + + // GPIO Bank 4 (EMC is 4, then 3) + GPIO_EMC_00(Gpio4, 0); + GPIO_EMC_01(Gpio4, 1); + GPIO_EMC_02(Gpio4, 2); + GPIO_EMC_03(Gpio4, 3); + GPIO_EMC_04(Gpio4, 4); + GPIO_EMC_05(Gpio4, 5); + GPIO_EMC_06(Gpio4, 6); + GPIO_EMC_07(Gpio4, 7); + GPIO_EMC_08(Gpio4, 8); + GPIO_EMC_09(Gpio4, 9); + GPIO_EMC_10(Gpio4, 10); + GPIO_EMC_11(Gpio4, 11); + GPIO_EMC_12(Gpio4, 12); + GPIO_EMC_13(Gpio4, 13); + GPIO_EMC_14(Gpio4, 14); + GPIO_EMC_15(Gpio4, 15); + GPIO_EMC_16(Gpio4, 16); + GPIO_EMC_17(Gpio4, 17); + GPIO_EMC_18(Gpio4, 18); + GPIO_EMC_19(Gpio4, 19); + GPIO_EMC_20(Gpio4, 20); + GPIO_EMC_21(Gpio4, 21); + GPIO_EMC_22(Gpio4, 22); + GPIO_EMC_23(Gpio4, 23); + GPIO_EMC_24(Gpio4, 24); + GPIO_EMC_25(Gpio4, 25); + GPIO_EMC_26(Gpio4, 26); + GPIO_EMC_27(Gpio4, 27); + GPIO_EMC_28(Gpio4, 28); + GPIO_EMC_29(Gpio4, 29); + GPIO_EMC_30(Gpio4, 30); + GPIO_EMC_31(Gpio4, 31); + + // GPIO Bank 3 + GPIO_EMC_32(Gpio3, 18); + GPIO_EMC_33(Gpio3, 19); + GPIO_EMC_34(Gpio3, 20); + GPIO_EMC_35(Gpio3, 21); + GPIO_EMC_36(Gpio3, 22); + GPIO_EMC_37(Gpio3, 23); + GPIO_EMC_38(Gpio3, 24); + GPIO_EMC_39(Gpio3, 25); + GPIO_EMC_40(Gpio3, 26); + GPIO_EMC_41(Gpio3, 27); + GPIO_SD_B0_00(Gpio3, 12); + GPIO_SD_B0_01(Gpio3, 13); + GPIO_SD_B0_02(Gpio3, 14); + GPIO_SD_B0_03(Gpio3, 15); + GPIO_SD_B0_04(Gpio3, 16); + GPIO_SD_B0_05(Gpio3, 17); + GPIO_SD_B1_00(Gpio3, 0); + GPIO_SD_B1_01(Gpio3, 1); + GPIO_SD_B1_02(Gpio3, 2); + GPIO_SD_B1_03(Gpio3, 3); + GPIO_SD_B1_04(Gpio3, 4); + GPIO_SD_B1_05(Gpio3, 5); + GPIO_SD_B1_06(Gpio3, 6); + GPIO_SD_B1_07(Gpio3, 7); + GPIO_SD_B1_08(Gpio3, 8); + GPIO_SD_B1_09(Gpio3, 9); + GPIO_SD_B1_10(Gpio3, 10); + GPIO_SD_B1_11(Gpio3, 11); + + WAKEUP(Gpio5, 0); + PMIC_ON_REQ(Gpio5, 1); + PMIC_STBY_REQ(Gpio5, 2); +} + +pub(crate) mod _generated { + #![allow(dead_code)] + #![allow(unused_imports)] + #![allow(non_snake_case)] + #![allow(missing_docs)] + + include!(concat!(env!("OUT_DIR"), "/_generated.rs")); +} diff --git a/embassy-nxp/src/gpio/rt1xxx.rs b/embassy-nxp/src/gpio/rt1xxx.rs index 9c58e8a7d..1d60a0d51 100644 --- a/embassy-nxp/src/gpio/rt1xxx.rs +++ b/embassy-nxp/src/gpio/rt1xxx.rs @@ -12,11 +12,11 @@ use nxp_pac::iomuxc::vals::Pus; use crate::chip::{mux_address, pad_address}; use crate::pac::common::{Reg, RW}; +use crate::pac::gpio::Gpio; #[cfg(feature = "rt")] use crate::pac::interrupt; use crate::pac::iomuxc::regs::{Ctl, MuxCtl}; -#[cfg(gpio5)] -use crate::pac::{self, gpio::Gpio}; +use crate::pac::{self}; /// The GPIO pin level for pins set on "Digital" mode. #[derive(Debug, Eq, PartialEq, Clone, Copy)] @@ -110,6 +110,14 @@ pub enum Bank { #[cfg(gpio2)] Gpio2, + /// Bank 3 + #[cfg(gpio3)] + Gpio3, + + /// Bank 4 + #[cfg(gpio4)] + Gpio4, + /// Bank 5 #[cfg(gpio5)] Gpio5, @@ -642,6 +650,10 @@ const GPIO_MUX_MODE: u8 = 0b101; static GPIO1_WAKERS: [AtomicWaker; 32] = [const { AtomicWaker::new() }; 32]; #[cfg(gpio2)] static GPIO2_WAKERS: [AtomicWaker; 32] = [const { AtomicWaker::new() }; 32]; +#[cfg(gpio3)] +static GPIO3_WAKERS: [AtomicWaker; 32] = [const { AtomicWaker::new() }; 32]; +#[cfg(gpio4)] +static GPIO4_WAKERS: [AtomicWaker; 32] = [const { AtomicWaker::new() }; 32]; #[cfg(gpio5)] static GPIO5_WAKERS: [AtomicWaker; 32] = [const { AtomicWaker::new() }; 32]; @@ -658,6 +670,10 @@ pub(crate) trait SealedPin: Sized { Bank::Gpio1 => pac::GPIO1, #[cfg(gpio2)] Bank::Gpio2 => pac::GPIO2, + #[cfg(gpio3)] + Bank::Gpio3 => pac::GPIO3, + #[cfg(gpio4)] + Bank::Gpio4 => pac::GPIO4, #[cfg(gpio5)] Bank::Gpio5 => pac::GPIO5, } @@ -687,6 +703,10 @@ pub(crate) trait SealedPin: Sized { Bank::Gpio1 => &GPIO1_WAKERS[self.pin_number() as usize], #[cfg(gpio2)] Bank::Gpio2 => &GPIO2_WAKERS[self.pin_number() as usize], + #[cfg(gpio3)] + Bank::Gpio3 => &GPIO3_WAKERS[self.pin_number() as usize], + #[cfg(gpio4)] + Bank::Gpio4 => &GPIO4_WAKERS[self.pin_number() as usize], #[cfg(gpio5)] Bank::Gpio5 => &GPIO5_WAKERS[self.pin_number() as usize], } @@ -870,25 +890,55 @@ fn irq_handler(block: Gpio, wakers: &[AtomicWaker; 32], high_bits: bool) { } } -#[cfg(all(feature = "mimxrt1011", feature = "rt"))] +#[cfg(all(any(feature = "mimxrt1011", feature = "mimxrt1062"), feature = "rt"))] #[interrupt] fn GPIO1_COMBINED_0_15() { irq_handler(pac::GPIO1, &GPIO1_WAKERS, false); } -#[cfg(all(feature = "mimxrt1011", feature = "rt"))] +#[cfg(all(any(feature = "mimxrt1011", feature = "mimxrt1062"), feature = "rt"))] #[interrupt] fn GPIO1_COMBINED_16_31() { irq_handler(pac::GPIO1, &GPIO1_WAKERS, true); } -#[cfg(all(feature = "mimxrt1011", feature = "rt"))] +#[cfg(all(any(feature = "mimxrt1011", feature = "mimxrt1062"), feature = "rt"))] #[interrupt] fn GPIO2_COMBINED_0_15() { irq_handler(pac::GPIO2, &GPIO2_WAKERS, false); } -#[cfg(all(feature = "mimxrt1011", feature = "rt"))] +#[cfg(all(feature = "mimxrt1062", feature = "rt"))] +#[interrupt] +fn GPIO2_COMBINED_16_31() { + irq_handler(pac::GPIO2, &GPIO2_WAKERS, true); +} + +#[cfg(all(feature = "mimxrt1062", feature = "rt"))] +#[interrupt] +fn GPIO3_COMBINED_0_15() { + irq_handler(pac::GPIO3, &GPIO3_WAKERS, false); +} + +#[cfg(all(feature = "mimxrt1062", feature = "rt"))] +#[interrupt] +fn GPIO3_COMBINED_16_31() { + irq_handler(pac::GPIO3, &GPIO3_WAKERS, true); +} + +#[cfg(all(feature = "mimxrt1062", feature = "rt"))] +#[interrupt] +fn GPIO4_COMBINED_0_15() { + irq_handler(pac::GPIO4, &GPIO4_WAKERS, false); +} + +#[cfg(all(feature = "mimxrt1062", feature = "rt"))] +#[interrupt] +fn GPIO4_COMBINED_16_31() { + irq_handler(pac::GPIO4, &GPIO4_WAKERS, true); +} + +#[cfg(all(any(feature = "mimxrt1011", feature = "mimxrt1062"), feature = "rt"))] #[interrupt] fn GPIO5_COMBINED_0_15() { irq_handler(pac::GPIO5, &GPIO5_WAKERS, false); diff --git a/embassy-nxp/src/lib.rs b/embassy-nxp/src/lib.rs index a715770c4..5e77fc0db 100644 --- a/embassy-nxp/src/lib.rs +++ b/embassy-nxp/src/lib.rs @@ -14,6 +14,7 @@ mod time_driver; // This mod MUST go last, so that it sees all the `impl_foo!` macros #[cfg_attr(feature = "lpc55", path = "chips/lpc55.rs")] #[cfg_attr(feature = "mimxrt1011", path = "chips/mimxrt1011.rs")] +#[cfg_attr(feature = "mimxrt1062", path = "chips/mimxrt1062.rs")] mod chip; #[cfg(feature = "unstable-pac")] diff --git a/examples/mimxrt1062-evk/.cargo/config.toml b/examples/mimxrt1062-evk/.cargo/config.toml new file mode 100644 index 000000000..ca4c606dc --- /dev/null +++ b/examples/mimxrt1062-evk/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv7em-none-eabihf] +runner = 'probe-rs run --chip MIMXRT1060' + +[build] +target = "thumbv7em-none-eabihf" # Cortex-M7 + +[env] +DEFMT_LOG = "trace" diff --git a/examples/mimxrt1062-evk/Cargo.toml b/examples/mimxrt1062-evk/Cargo.toml new file mode 100644 index 000000000..430a26b41 --- /dev/null +++ b/examples/mimxrt1062-evk/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "embassy-imxrt1062-evk-examples" +version = "0.1.0" +edition = "2021" +license = "MIT or Apache-2.0" + +[dependencies] +cortex-m = { version = "0.7.7", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.3" +defmt = "1.0.1" +defmt-rtt = "1.0.0" + +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-futures = { version = "0.1.1", path = "../../embassy-futures" } +embassy-nxp = { version = "0.1.0", path = "../../embassy-nxp", features = ["defmt", "mimxrt1062", "unstable-pac", "time-driver-pit"] } +embassy-time = { version = "0.4", path = "../../embassy-time", features = ["defmt", ] } # "defmt-timestamp-uptime" +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = "1.0.0" + +imxrt-boot-gen = { version = "0.3.4", features = ["imxrt1060"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +panic-semihosting = "0.6.0" + +[build-dependencies] +imxrt-rt = { version = "0.1.7", features = ["device"] } + +[profile.release] +debug = 2 diff --git a/examples/mimxrt1062-evk/build.rs b/examples/mimxrt1062-evk/build.rs new file mode 100644 index 000000000..e0e0d547e --- /dev/null +++ b/examples/mimxrt1062-evk/build.rs @@ -0,0 +1,12 @@ +use imxrt_rt::{Family, RuntimeBuilder}; + +fn main() { + RuntimeBuilder::from_flexspi(Family::Imxrt1060, 8 * 1024 * 1024) + .build() + .unwrap(); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + // Not link.x, as imxrt-rt needs to do some special things + println!("cargo:rustc-link-arg-bins=-Timxrt-link.x"); +} diff --git a/examples/mimxrt1062-evk/src/bin/blinky.rs b/examples/mimxrt1062-evk/src/bin/blinky.rs new file mode 100644 index 000000000..b6d90d94d --- /dev/null +++ b/examples/mimxrt1062-evk/src/bin/blinky.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nxp::gpio::{Level, Output}; +use embassy_time::Timer; +// Must include `embassy_imxrt1062_evk_examples` to ensure the FCB gets linked. +use {defmt_rtt as _, embassy_imxrt1062_evk_examples as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let p = embassy_nxp::init(Default::default()); + info!("Hello world!"); + + let led = p.GPIO_AD_B0_08; + let mut led = Output::new(led, Level::Low); + + loop { + Timer::after_millis(500).await; + + info!("Toggle"); + led.toggle(); + } +} diff --git a/examples/mimxrt1062-evk/src/bin/button.rs b/examples/mimxrt1062-evk/src/bin/button.rs new file mode 100644 index 000000000..d60fa3dac --- /dev/null +++ b/examples/mimxrt1062-evk/src/bin/button.rs @@ -0,0 +1,36 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nxp::gpio::{Input, Level, Output, Pull}; +use {defmt_rtt as _, embassy_imxrt1062_evk_examples as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let p = embassy_nxp::init(Default::default()); + info!("Hello world!"); + + // User LED (D8) + let led = p.GPIO_AD_B0_08; + // User button (SW5) + let button = p.WAKEUP; + let mut button = Input::new(button, Pull::Up100K); + let mut led = Output::new(led, Level::Low); + led.set_high(); + + loop { + button.wait_for_falling_edge().await; + + info!("Toggled"); + led.toggle(); + + // Software debounce. + button.wait_for_rising_edge().await; + + // Stabilization. + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + } +} diff --git a/examples/mimxrt1062-evk/src/lib.rs b/examples/mimxrt1062-evk/src/lib.rs new file mode 100644 index 000000000..3f99f9db3 --- /dev/null +++ b/examples/mimxrt1062-evk/src/lib.rs @@ -0,0 +1,60 @@ +//! FlexSPI configuration block (FCB) for the iMXRT1060-EVK +//! +//! This uses IS25WP QuadSPI flash. + +#![no_std] + +use imxrt_boot_gen::flexspi::opcodes::sdr::*; +use imxrt_boot_gen::flexspi::{self, FlashPadType, ReadSampleClockSource, SerialClockFrequency, SerialFlashRegion, *}; +use imxrt_boot_gen::serial_flash::*; +pub use nor::ConfigurationBlock; + +const SEQ_READ: Sequence = SequenceBuilder::new() + .instr(Instr::new(CMD, Pads::One, 0xEB)) + .instr(Instr::new(RADDR, Pads::Four, 0x18)) + .instr(Instr::new(DUMMY, Pads::Four, 0x06)) + .instr(Instr::new(READ, Pads::Four, 0x04)) + .build(); +const SEQ_READ_STATUS: Sequence = SequenceBuilder::new() + .instr(Instr::new(CMD, Pads::One, 0x05)) + .instr(Instr::new(READ, Pads::One, 0x04)) + .build(); +const SEQ_WRITE_ENABLE: Sequence = SequenceBuilder::new().instr(Instr::new(CMD, Pads::One, 0x06)).build(); +const SEQ_ERASE_SECTOR: Sequence = SequenceBuilder::new() + .instr(Instr::new(CMD, Pads::One, 0x20)) + .instr(Instr::new(RADDR, Pads::One, 0x18)) + .build(); +const SEQ_PAGE_PROGRAM: Sequence = SequenceBuilder::new() + .instr(Instr::new(CMD, Pads::One, 0x02)) + .instr(Instr::new(RADDR, Pads::One, 0x18)) + .instr(Instr::new(WRITE, Pads::One, 0x04)) + .build(); +const SEQ_CHIP_ERASE: Sequence = SequenceBuilder::new().instr(Instr::new(CMD, Pads::One, 0x60)).build(); + +const LUT: LookupTable = LookupTable::new() + .command(Command::Read, SEQ_READ) + .command(Command::ReadStatus, SEQ_READ_STATUS) + .command(Command::WriteEnable, SEQ_WRITE_ENABLE) + .command(Command::EraseSector, SEQ_ERASE_SECTOR) + .command(Command::PageProgram, SEQ_PAGE_PROGRAM) + .command(Command::ChipErase, SEQ_CHIP_ERASE); + +const COMMON_CONFIGURATION_BLOCK: flexspi::ConfigurationBlock = flexspi::ConfigurationBlock::new(LUT) + .version(Version::new(1, 4, 0)) + .read_sample_clk_src(ReadSampleClockSource::LoopbackFromDQSPad) + .cs_hold_time(3) + .cs_setup_time(3) + .controller_misc_options(0x10) + .serial_flash_pad_type(FlashPadType::Quad) + .serial_clk_freq(SerialClockFrequency::MHz133) + .flash_size(SerialFlashRegion::A1, 8 * 1024 * 1024); + +pub const SERIAL_NOR_CONFIGURATION_BLOCK: nor::ConfigurationBlock = + nor::ConfigurationBlock::new(COMMON_CONFIGURATION_BLOCK) + .page_size(256) + .sector_size(4096) + .ip_cmd_serial_clk_freq(nor::SerialClockFrequency::MHz30); + +#[no_mangle] +#[cfg_attr(all(target_arch = "arm", target_os = "none"), link_section = ".fcb")] +pub static FLEXSPI_CONFIGURATION_BLOCK: nor::ConfigurationBlock = SERIAL_NOR_CONFIGURATION_BLOCK; From a1867f0d742f597a25384e4a33209beeec7ec676 Mon Sep 17 00:00:00 2001 From: i509VCB Date: Sun, 6 Jul 2025 17:35:19 -0500 Subject: [PATCH 15/15] mspm0: add buffered uart driver And tests for G3507. --- embassy-mspm0/Cargo.toml | 4 + embassy-mspm0/src/uart/buffered.rs | 1060 ++++++++++++++++++++ embassy-mspm0/src/{uart.rs => uart/mod.rs} | 145 ++- examples/mspm0g3507/Cargo.toml | 2 + tests/mspm0/Cargo.toml | 3 + tests/mspm0/src/bin/uart_buffered.rs | 115 +++ 6 files changed, 1288 insertions(+), 41 deletions(-) create mode 100644 embassy-mspm0/src/uart/buffered.rs rename embassy-mspm0/src/{uart.rs => uart/mod.rs} (93%) create mode 100644 tests/mspm0/src/bin/uart_buffered.rs diff --git a/embassy-mspm0/Cargo.toml b/embassy-mspm0/Cargo.toml index d2adc63d7..61afcd76d 100644 --- a/embassy-mspm0/Cargo.toml +++ b/embassy-mspm0/Cargo.toml @@ -36,7 +36,10 @@ embassy-embedded-hal = { version = "0.3.1", path = "../embassy-embedded-hal", de embassy-executor = { version = "0.7.0", path = "../embassy-executor", optional = true } embedded-hal = { version = "1.0" } +embedded-hal-nb = { version = "1.0" } embedded-hal-async = { version = "1.0" } +embedded-io = "0.6.1" +embedded-io-async = "0.6.1" defmt = { version = "1.0.1", optional = true } fixed = "1.29" @@ -51,6 +54,7 @@ mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag [build-dependencies] proc-macro2 = "1.0.94" quote = "1.0.40" +cfg_aliases = "0.2.1" # mspm0-metapac = { version = "", default-features = false, features = ["metadata"] } mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag = "mspm0-data-235158ac2865d8aac3a1eceb2d62026eb12bf38f", default-features = false, features = ["metadata"] } diff --git a/embassy-mspm0/src/uart/buffered.rs b/embassy-mspm0/src/uart/buffered.rs new file mode 100644 index 000000000..cbc0b6c80 --- /dev/null +++ b/embassy-mspm0/src/uart/buffered.rs @@ -0,0 +1,1060 @@ +use core::future::{poll_fn, Future}; +use core::marker::PhantomData; +use core::slice; +use core::sync::atomic::{AtomicU8, Ordering}; +use core::task::Poll; + +use embassy_embedded_hal::SetConfig; +use embassy_hal_internal::atomic_ring_buffer::RingBuffer; +use embassy_hal_internal::interrupt::InterruptExt; +use embassy_sync::waitqueue::AtomicWaker; +use embedded_hal_nb::nb; + +use crate::gpio::{AnyPin, SealedPin}; +use crate::interrupt::typelevel::Binding; +use crate::pac::uart::Uart as Regs; +use crate::uart::{Config, ConfigError, CtsPin, Error, Info, Instance, RtsPin, RxPin, State, TxPin}; +use crate::{interrupt, Peri}; + +/// Interrupt handler. +pub struct BufferedInterruptHandler { + _uart: PhantomData, +} + +impl interrupt::typelevel::Handler for BufferedInterruptHandler { + unsafe fn on_interrupt() { + on_interrupt(T::info().regs, T::buffered_state()) + } +} + +/// Bidirectional buffered UART which acts as a combination of [`BufferedUartTx`] and [`BufferedUartRx`]. +pub struct BufferedUart<'d> { + rx: BufferedUartRx<'d>, + tx: BufferedUartTx<'d>, +} + +impl SetConfig for BufferedUart<'_> { + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.set_config(config) + } +} + +impl<'d> BufferedUart<'d> { + /// Create a new bidirectional buffered UART. + pub fn new( + uart: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + rx: Peri<'d, impl RxPin>, + _irq: impl Binding>, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Result { + Self::new_inner( + uart, + new_pin!(rx, config.rx_pf()), + new_pin!(tx, config.tx_pf()), + None, + None, + tx_buffer, + rx_buffer, + config, + ) + } + + /// Create a new bidirectional buffered UART with request-to-send and clear-to-send pins + pub fn new_with_rtscts( + uart: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + rx: Peri<'d, impl RxPin>, + rts: Peri<'d, impl RtsPin>, + cts: Peri<'d, impl CtsPin>, + _irq: impl Binding>, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Result { + Self::new_inner( + uart, + new_pin!(rx, config.rx_pf()), + new_pin!(tx, config.tx_pf()), + new_pin!(rts, config.rts_pf()), + new_pin!(cts, config.cts_pf()), + tx_buffer, + rx_buffer, + config, + ) + } + + /// Reconfigure the driver + pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { + self.tx.set_config(config)?; + self.rx.set_config(config) + } + + /// Set baudrate + pub fn set_baudrate(&mut self, baudrate: u32) -> Result<(), ConfigError> { + self.rx.set_baudrate(baudrate) + } + + /// Write to UART TX buffer, blocking execution until done. + pub fn blocking_write(&mut self, buffer: &[u8]) -> Result { + self.tx.blocking_write(buffer) + } + + /// Flush UART TX buffer, blocking execution until done. + pub fn blocking_flush(&mut self) -> Result<(), Error> { + self.tx.blocking_flush() + } + + /// Check if UART is busy. + pub fn busy(&self) -> bool { + self.tx.busy() + } + + /// Read from UART RX buffer, blocking execution until done. + pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result { + self.rx.blocking_read(buffer) + } + + /// Send break character. + pub fn send_break(&mut self) { + self.tx.send_break() + } + + /// Split into separate RX and TX handles. + pub fn split(self) -> (BufferedUartTx<'d>, BufferedUartRx<'d>) { + (self.tx, self.rx) + } + + /// Split into separate RX and TX handles. + pub fn split_ref(&mut self) -> (BufferedUartTx<'_>, BufferedUartRx<'_>) { + ( + BufferedUartTx { + info: self.tx.info, + state: self.tx.state, + tx: self.tx.tx.as_mut().map(Peri::reborrow), + cts: self.tx.cts.as_mut().map(Peri::reborrow), + reborrowed: true, + }, + BufferedUartRx { + info: self.rx.info, + state: self.rx.state, + rx: self.rx.rx.as_mut().map(Peri::reborrow), + rts: self.rx.rts.as_mut().map(Peri::reborrow), + reborrowed: true, + }, + ) + } +} + +/// Rx-only buffered UART. +/// +/// Can be obtained from [`BufferedUart::split`], or can be constructed independently, +/// if you do not need the transmitting half of the driver. +pub struct BufferedUartRx<'d> { + info: &'static Info, + state: &'static BufferedState, + rx: Option>, + rts: Option>, + reborrowed: bool, +} + +impl SetConfig for BufferedUartRx<'_> { + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.set_config(config) + } +} + +impl<'d> BufferedUartRx<'d> { + /// Create a new rx-only buffered UART with no hardware flow control. + /// + /// Useful if you only want Uart Rx. It saves 1 pin. + pub fn new( + uart: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + _irq: impl Binding>, + rx_buffer: &'d mut [u8], + config: Config, + ) -> Result { + Self::new_inner(uart, new_pin!(rx, config.rx_pf()), None, rx_buffer, config) + } + + /// Create a new rx-only buffered UART with a request-to-send pin + pub fn new_with_rts( + uart: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + rts: Peri<'d, impl RtsPin>, + _irq: impl Binding>, + rx_buffer: &'d mut [u8], + config: Config, + ) -> Result { + Self::new_inner( + uart, + new_pin!(rx, config.rx_pf()), + new_pin!(rts, config.rts_pf()), + rx_buffer, + config, + ) + } + + /// Reconfigure the driver + pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { + if let Some(ref rx) = self.rx { + rx.update_pf(config.rx_pf()); + } + + if let Some(ref rts) = self.rts { + rts.update_pf(config.rts_pf()); + } + + super::reconfigure(&self.info, &self.state.state, config) + } + + /// Set baudrate + pub fn set_baudrate(&mut self, baudrate: u32) -> Result<(), ConfigError> { + super::set_baudrate(&self.info, self.state.state.clock.load(Ordering::Relaxed), baudrate) + } + + /// Read from UART RX buffer, blocking execution until done. + pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result { + self.blocking_read_inner(buffer) + } +} + +impl Drop for BufferedUartRx<'_> { + fn drop(&mut self) { + if !self.reborrowed { + let state = self.state; + + // SAFETY: RX is being dropped (and is not reborrowed), so the ring buffer must be deinitialized + // in order to meet the requirements of init. + unsafe { + state.rx_buf.deinit(); + } + + // TX is inactive if the buffer is not available. If this is true, then disable the + // interrupt handler since we are running in RX only mode. + if state.tx_buf.len() == 0 { + self.info.interrupt.disable(); + } + + self.rx.as_ref().map(|x| x.set_as_disconnected()); + self.rts.as_ref().map(|x| x.set_as_disconnected()); + } + } +} + +/// Tx-only buffered UART. +/// +/// Can be obtained from [`BufferedUart::split`], or can be constructed independently, +/// if you do not need the receiving half of the driver. +pub struct BufferedUartTx<'d> { + info: &'static Info, + state: &'static BufferedState, + tx: Option>, + cts: Option>, + reborrowed: bool, +} + +impl SetConfig for BufferedUartTx<'_> { + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.set_config(config) + } +} + +impl<'d> BufferedUartTx<'d> { + /// Create a new tx-only buffered UART with no hardware flow control. + /// + /// Useful if you only want Uart Tx. It saves 1 pin. + pub fn new( + uart: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + _irq: impl Binding>, + tx_buffer: &'d mut [u8], + config: Config, + ) -> Result { + Self::new_inner(uart, new_pin!(tx, config.tx_pf()), None, tx_buffer, config) + } + + /// Create a new tx-only buffered UART with a clear-to-send pin + pub fn new_with_rts( + uart: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + cts: Peri<'d, impl CtsPin>, + _irq: impl Binding>, + tx_buffer: &'d mut [u8], + config: Config, + ) -> Result { + Self::new_inner( + uart, + new_pin!(tx, config.tx_pf()), + new_pin!(cts, config.cts_pf()), + tx_buffer, + config, + ) + } + + /// Reconfigure the driver + pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { + if let Some(ref tx) = self.tx { + tx.update_pf(config.tx_pf()); + } + + if let Some(ref cts) = self.cts { + cts.update_pf(config.cts_pf()); + } + + super::reconfigure(self.info, &self.state.state, config) + } + + /// Set baudrate + pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { + super::set_baudrate(&self.info, self.state.state.clock.load(Ordering::Relaxed), baudrate) + } + + /// Write to UART TX buffer, blocking execution until done. + pub fn blocking_write(&mut self, buffer: &[u8]) -> Result { + self.blocking_write_inner(buffer) + } + + /// Flush UART TX buffer, blocking execution until done. + pub fn blocking_flush(&mut self) -> Result<(), Error> { + let state = self.state; + + loop { + if state.tx_buf.is_empty() { + return Ok(()); + } + } + } + + /// Check if UART is busy. + pub fn busy(&self) -> bool { + super::busy(self.info.regs) + } + + /// Send break character + pub fn send_break(&mut self) { + let r = self.info.regs; + + r.lcrh().modify(|w| { + w.set_brk(true); + }); + } +} + +impl Drop for BufferedUartTx<'_> { + fn drop(&mut self) { + if !self.reborrowed { + let state = self.state; + + // SAFETY: TX is being dropped (and is not reborrowed), so the ring buffer must be deinitialized + // in order to meet the requirements of init. + unsafe { + state.tx_buf.deinit(); + } + + // RX is inactive if the buffer is not available. If this is true, then disable the + // interrupt handler since we are running in TX only mode. + if state.rx_buf.len() == 0 { + self.info.interrupt.disable(); + } + + self.tx.as_ref().map(|x| x.set_as_disconnected()); + self.cts.as_ref().map(|x| x.set_as_disconnected()); + } + } +} + +impl embedded_io_async::ErrorType for BufferedUart<'_> { + type Error = Error; +} + +impl embedded_io_async::ErrorType for BufferedUartRx<'_> { + type Error = Error; +} + +impl embedded_io_async::ErrorType for BufferedUartTx<'_> { + type Error = Error; +} + +impl embedded_io_async::Read for BufferedUart<'_> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.rx.read(buf).await + } +} + +impl embedded_io_async::Read for BufferedUartRx<'_> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.read_inner(buf).await + } +} + +impl embedded_io_async::ReadReady for BufferedUart<'_> { + fn read_ready(&mut self) -> Result { + self.rx.read_ready() + } +} + +impl embedded_io_async::ReadReady for BufferedUartRx<'_> { + fn read_ready(&mut self) -> Result { + self.read_ready_inner() + } +} + +impl embedded_io_async::BufRead for BufferedUart<'_> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.rx.fill_buf().await + } + + fn consume(&mut self, amt: usize) { + self.rx.consume(amt); + } +} + +impl embedded_io_async::BufRead for BufferedUartRx<'_> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.fill_buf_inner().await + } + + fn consume(&mut self, amt: usize) { + self.consume_inner(amt); + } +} + +impl embedded_io_async::Write for BufferedUart<'_> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.tx.write_inner(buf).await + } +} + +impl embedded_io_async::Write for BufferedUartTx<'_> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write_inner(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.flush_inner().await + } +} + +impl embedded_io::Read for BufferedUart<'_> { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.rx.read(buf) + } +} + +impl embedded_io::Read for BufferedUartRx<'_> { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.blocking_read_inner(buf) + } +} + +impl embedded_io::Write for BufferedUart<'_> { + fn write(&mut self, buf: &[u8]) -> Result { + self.tx.write(buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.tx.flush() + } +} + +impl embedded_io::Write for BufferedUartTx<'_> { + fn write(&mut self, buf: &[u8]) -> Result { + self.blocking_write_inner(buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl embedded_hal_nb::serial::Error for Error { + fn kind(&self) -> embedded_hal_nb::serial::ErrorKind { + match self { + Error::Framing => embedded_hal_nb::serial::ErrorKind::FrameFormat, + Error::Noise => embedded_hal_nb::serial::ErrorKind::Noise, + Error::Overrun => embedded_hal_nb::serial::ErrorKind::Overrun, + Error::Parity => embedded_hal_nb::serial::ErrorKind::Parity, + Error::Break => embedded_hal_nb::serial::ErrorKind::Other, + } + } +} + +impl embedded_hal_nb::serial::ErrorType for BufferedUart<'_> { + type Error = Error; +} + +impl embedded_hal_nb::serial::ErrorType for BufferedUartRx<'_> { + type Error = Error; +} + +impl embedded_hal_nb::serial::ErrorType for BufferedUartTx<'_> { + type Error = Error; +} + +impl embedded_hal_nb::serial::Read for BufferedUart<'_> { + fn read(&mut self) -> nb::Result { + self.rx.read() + } +} + +impl embedded_hal_nb::serial::Read for BufferedUartRx<'_> { + fn read(&mut self) -> nb::Result { + if self.info.regs.stat().read().rxfe() { + return Err(nb::Error::WouldBlock); + } + + super::read_with_error(self.info.regs).map_err(nb::Error::Other) + } +} + +impl embedded_hal_nb::serial::Write for BufferedUart<'_> { + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + self.tx.write(word) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.tx.flush() + } +} + +impl embedded_hal_nb::serial::Write for BufferedUartTx<'_> { + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[word]).map(drop).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } +} + +// Impl details + +/// Buffered UART state. +pub(crate) struct BufferedState { + /// non-buffered UART state. This is inline in order to avoid [`BufferedUartRx`]/Tx + /// needing to carry around a 2nd static reference and waste another 4 bytes. + state: State, + rx_waker: AtomicWaker, + rx_buf: RingBuffer, + tx_waker: AtomicWaker, + tx_buf: RingBuffer, + rx_error: AtomicU8, +} + +// these must match bits 8..12 in RXDATA, but shifted by 8 to the right +const RXE_NOISE: u8 = 16; +const RXE_OVERRUN: u8 = 8; +const RXE_BREAK: u8 = 4; +const RXE_PARITY: u8 = 2; +const RXE_FRAMING: u8 = 1; + +impl BufferedState { + pub const fn new() -> Self { + Self { + state: State::new(), + rx_waker: AtomicWaker::new(), + rx_buf: RingBuffer::new(), + tx_waker: AtomicWaker::new(), + tx_buf: RingBuffer::new(), + rx_error: AtomicU8::new(0), + } + } +} + +impl<'d> BufferedUart<'d> { + fn new_inner( + _peri: Peri<'d, T>, + rx: Option>, + tx: Option>, + rts: Option>, + cts: Option>, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Result { + let info = T::info(); + let state = T::buffered_state(); + + let mut this = Self { + tx: BufferedUartTx { + info, + state, + tx, + cts, + reborrowed: false, + }, + rx: BufferedUartRx { + info, + state, + rx, + rts, + reborrowed: false, + }, + }; + this.enable_and_configure(tx_buffer, rx_buffer, &config)?; + + Ok(this) + } + + fn enable_and_configure( + &mut self, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: &Config, + ) -> Result<(), ConfigError> { + let info = self.rx.info; + let state = self.rx.state; + + assert!(!tx_buffer.is_empty()); + assert!(!rx_buffer.is_empty()); + + init_buffers(info, state, Some(tx_buffer), Some(rx_buffer)); + super::enable(info.regs); + super::configure( + info, + &state.state, + config, + true, + self.rx.rts.is_some(), + true, + self.tx.cts.is_some(), + )?; + + info.interrupt.unpend(); + unsafe { info.interrupt.enable() }; + + Ok(()) + } +} + +impl<'d> BufferedUartRx<'d> { + fn new_inner( + _peri: Peri<'d, T>, + rx: Option>, + rts: Option>, + rx_buffer: &'d mut [u8], + config: Config, + ) -> Result { + let mut this = Self { + info: T::info(), + state: T::buffered_state(), + rx, + rts, + reborrowed: false, + }; + this.enable_and_configure(rx_buffer, &config)?; + + Ok(this) + } + + fn enable_and_configure(&mut self, rx_buffer: &'d mut [u8], config: &Config) -> Result<(), ConfigError> { + let info = self.info; + let state = self.state; + + init_buffers(info, state, None, Some(rx_buffer)); + super::enable(info.regs); + super::configure(info, &self.state.state, config, true, self.rts.is_some(), false, false)?; + + info.interrupt.unpend(); + unsafe { info.interrupt.enable() }; + + Ok(()) + } + + async fn read_inner(&self, buf: &mut [u8]) -> Result { + poll_fn(move |cx| { + let state = self.state; + + if let Poll::Ready(r) = self.try_read(buf) { + return Poll::Ready(r); + } + + state.rx_waker.register(cx.waker()); + Poll::Pending + }) + .await + } + + fn blocking_read_inner(&self, buffer: &mut [u8]) -> Result { + loop { + match self.try_read(buffer) { + Poll::Ready(res) => return res, + Poll::Pending => continue, + } + } + } + + fn fill_buf_inner(&self) -> impl Future> { + poll_fn(move |cx| { + let mut rx_reader = unsafe { self.state.rx_buf.reader() }; + let (p, n) = rx_reader.pop_buf(); + let result = if n == 0 { + match Self::get_rx_error(self.state) { + None => { + self.state.rx_waker.register(cx.waker()); + return Poll::Pending; + } + Some(e) => Err(e), + } + } else { + let buf = unsafe { slice::from_raw_parts(p, n) }; + Ok(buf) + }; + + Poll::Ready(result) + }) + } + + fn consume_inner(&self, amt: usize) { + let mut rx_reader = unsafe { self.state.rx_buf.reader() }; + rx_reader.pop_done(amt); + + // (Re-)Enable the interrupt to receive more data in case it was + // disabled because the buffer was full or errors were detected. + self.info.regs.cpu_int(0).imask().modify(|w| { + w.set_rxint(true); + w.set_rtout(true); + }); + } + + /// we are ready to read if there is data in the buffer + fn read_ready_inner(&self) -> Result { + Ok(!self.state.rx_buf.is_empty()) + } + + fn try_read(&self, buf: &mut [u8]) -> Poll> { + let state = self.state; + + if buf.is_empty() { + return Poll::Ready(Ok(0)); + } + + let mut rx_reader = unsafe { state.rx_buf.reader() }; + let n = rx_reader.pop(|data| { + let n = data.len().min(buf.len()); + buf[..n].copy_from_slice(&data[..n]); + n + }); + + let result = if n == 0 { + match Self::get_rx_error(state) { + None => return Poll::Pending, + Some(e) => Err(e), + } + } else { + Ok(n) + }; + + // (Re-)Enable the interrupt to receive more data in case it was + // disabled because the buffer was full or errors were detected. + self.info.regs.cpu_int(0).imask().modify(|w| { + w.set_rxint(true); + w.set_rtout(true); + }); + + Poll::Ready(result) + } + + fn get_rx_error(state: &BufferedState) -> Option { + // Cortex-M0 has does not support atomic swap, so we must do two operations. + let errs = critical_section::with(|_cs| { + let errs = state.rx_error.load(Ordering::Relaxed); + state.rx_error.store(0, Ordering::Relaxed); + + errs + }); + + if errs & RXE_NOISE != 0 { + Some(Error::Noise) + } else if errs & RXE_OVERRUN != 0 { + Some(Error::Overrun) + } else if errs & RXE_BREAK != 0 { + Some(Error::Break) + } else if errs & RXE_PARITY != 0 { + Some(Error::Parity) + } else if errs & RXE_FRAMING != 0 { + Some(Error::Framing) + } else { + None + } + } +} + +impl<'d> BufferedUartTx<'d> { + fn new_inner( + _peri: Peri<'d, T>, + tx: Option>, + cts: Option>, + tx_buffer: &'d mut [u8], + config: Config, + ) -> Result { + let mut this = Self { + info: T::info(), + state: T::buffered_state(), + tx, + cts, + reborrowed: false, + }; + + this.enable_and_configure(tx_buffer, &config)?; + + Ok(this) + } + + async fn write_inner(&self, buf: &[u8]) -> Result { + poll_fn(move |cx| { + let state = self.state; + + if buf.is_empty() { + return Poll::Ready(Ok(0)); + } + + let mut tx_writer = unsafe { state.tx_buf.writer() }; + let n = tx_writer.push(|data| { + let n = data.len().min(buf.len()); + data[..n].copy_from_slice(&buf[..n]); + n + }); + + if n == 0 { + state.tx_waker.register(cx.waker()); + return Poll::Pending; + } + + // The TX interrupt only triggers when the there was data in the + // FIFO and the number of bytes drops below a threshold. When the + // FIFO was empty we have to manually pend the interrupt to shovel + // TX data from the buffer into the FIFO. + self.info.interrupt.pend(); + Poll::Ready(Ok(n)) + }) + .await + } + + fn blocking_write_inner(&self, buffer: &[u8]) -> Result { + let state = self.state; + + loop { + let empty = state.tx_buf.is_empty(); + + // SAFETY: tx buf must be initialized if BufferedUartTx exists. + let mut tx_writer = unsafe { state.tx_buf.writer() }; + let data = tx_writer.push_slice(); + + if !data.is_empty() { + let n = data.len().min(buffer.len()); + data[..n].copy_from_slice(&buffer[..n]); + tx_writer.push_done(n); + + if empty { + self.info.interrupt.pend(); + } + + return Ok(n); + } + } + } + + async fn flush_inner(&self) -> Result<(), Error> { + poll_fn(move |cx| { + let state = self.state; + + if !state.tx_buf.is_empty() { + state.tx_waker.register(cx.waker()); + return Poll::Pending; + } + + Poll::Ready(Ok(())) + }) + .await + } + + fn enable_and_configure(&mut self, tx_buffer: &'d mut [u8], config: &Config) -> Result<(), ConfigError> { + let info = self.info; + let state = self.state; + + init_buffers(info, state, Some(tx_buffer), None); + super::enable(info.regs); + super::configure(info, &state.state, config, false, false, true, self.cts.is_some())?; + + info.interrupt.unpend(); + unsafe { info.interrupt.enable() }; + + Ok(()) + } +} + +fn init_buffers<'d>( + info: &Info, + state: &BufferedState, + tx_buffer: Option<&'d mut [u8]>, + rx_buffer: Option<&'d mut [u8]>, +) { + if let Some(tx_buffer) = tx_buffer { + let len = tx_buffer.len(); + unsafe { state.tx_buf.init(tx_buffer.as_mut_ptr(), len) }; + } + + if let Some(rx_buffer) = rx_buffer { + let len = rx_buffer.len(); + unsafe { state.rx_buf.init(rx_buffer.as_mut_ptr(), len) }; + } + + info.regs.cpu_int(0).imask().modify(|w| { + w.set_nerr(true); + w.set_frmerr(true); + w.set_parerr(true); + w.set_brkerr(true); + w.set_ovrerr(true); + }); +} + +fn on_interrupt(r: Regs, state: &'static BufferedState) { + let int = r.cpu_int(0).mis().read(); + + // Per https://github.com/embassy-rs/embassy/pull/1458, both buffered and unbuffered handlers may be bound. + if super::dma_enabled(r) { + return; + } + + // RX + if state.rx_buf.is_available() { + // SAFETY: RX must have been initialized if RXE is set. + let mut rx_writer = unsafe { state.rx_buf.writer() }; + let rx_buf = rx_writer.push_slice(); + let mut n_read = 0; + let mut error = false; + + for rx_byte in rx_buf { + let stat = r.stat().read(); + + if stat.rxfe() { + break; + } + + let data = r.rxdata().read(); + + if (data.0 >> 8) != 0 { + // Cortex-M0 does not support atomic fetch_or, must do 2 operations. + critical_section::with(|_cs| { + let mut value = state.rx_error.load(Ordering::Relaxed); + value |= (data.0 >> 8) as u8; + state.rx_error.store(value, Ordering::Relaxed); + }); + error = true; + + // only fill the buffer with valid characters. the current character is fine + // if the error is an overrun, but if we add it to the buffer we'll report + // the overrun one character too late. drop it instead and pretend we were + // a bit slower at draining the rx fifo than we actually were. + // this is consistent with blocking uart error reporting. + break; + } + + *rx_byte = data.data(); + n_read += 1; + } + + if n_read > 0 { + rx_writer.push_done(n_read); + state.rx_waker.wake(); + } else if error { + state.rx_waker.wake(); + } + + // Disable any further RX interrupts when the buffer becomes full or + // errors have occurred. This lets us buffer additional errors in the + // fifo without needing more error storage locations, and most applications + // will want to do a full reset of their uart state anyway once an error + // has happened. + if state.rx_buf.is_full() || error { + r.cpu_int(0).imask().modify(|w| { + w.set_rxint(false); + w.set_rtout(false); + }); + } + } + + if int.eot() { + r.cpu_int(0).imask().modify(|w| { + w.set_eot(false); + }); + + r.cpu_int(0).iclr().write(|w| { + w.set_eot(true); + }); + + state.tx_waker.wake(); + } + + // TX + if state.tx_buf.is_available() { + // SAFETY: TX must have been initialized if TXE is set. + let mut tx_reader = unsafe { state.tx_buf.reader() }; + let buf = tx_reader.pop_slice(); + let mut n_written = 0; + + for tx_byte in buf.iter_mut() { + let stat = r.stat().read(); + + if stat.txff() { + break; + } + + r.txdata().write(|w| { + w.set_data(*tx_byte); + }); + n_written += 1; + } + + if n_written > 0 { + // EOT will wake. + r.cpu_int(0).imask().modify(|w| { + w.set_eot(true); + }); + + tx_reader.pop_done(n_written); + } + } + + // Clear TX and error interrupt flags + // RX interrupt flags are cleared by writing to ICLR. + let mis = r.cpu_int(0).mis().read(); + r.cpu_int(0).iclr().write(|w| { + w.set_nerr(mis.nerr()); + w.set_frmerr(mis.frmerr()); + w.set_parerr(mis.parerr()); + w.set_brkerr(mis.brkerr()); + w.set_ovrerr(mis.ovrerr()); + }); + + // Errors + if mis.nerr() { + warn!("Noise error"); + } + if mis.frmerr() { + warn!("Framing error"); + } + if mis.parerr() { + warn!("Parity error"); + } + if mis.brkerr() { + warn!("Break error"); + } + if mis.ovrerr() { + warn!("Overrun error"); + } +} diff --git a/embassy-mspm0/src/uart.rs b/embassy-mspm0/src/uart/mod.rs similarity index 93% rename from embassy-mspm0/src/uart.rs rename to embassy-mspm0/src/uart/mod.rs index bc1d2e343..6599cea06 100644 --- a/embassy-mspm0/src/uart.rs +++ b/embassy-mspm0/src/uart/mod.rs @@ -1,8 +1,11 @@ #![macro_use] +mod buffered; + use core::marker::PhantomData; use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; +pub use buffered::*; use embassy_embedded_hal::SetConfig; use embassy_hal_internal::PeripheralType; @@ -128,8 +131,8 @@ pub struct Config { // pub manchester: bool, // TODO: majority voting - - // TODO: fifo level select - need power domain info in metapac + /// If true: the built-in FIFO is enabled. + pub fifo_enable: bool, // TODO: glitch suppression /// If true: invert TX pin signal values (VDD = 0/mark, Gnd = 1/idle). @@ -169,6 +172,7 @@ impl Default for Config { msb_order: BitOrder::LsbFirst, loop_back_enable: false, // manchester: false, + fifo_enable: false, invert_tx: false, invert_rx: false, invert_rts: false, @@ -185,7 +189,7 @@ impl Default for Config { /// /// ### Notes on [`embedded_io::Read`] /// -/// `embedded_io::Read` requires guarantees that the base [`UartRx`] cannot provide. +/// [`embedded_io::Read`] requires guarantees that the base [`UartRx`] cannot provide. /// /// See [`UartRx`] for more details, and see [`BufferedUart`] and [`RingBufferedUartRx`] /// as alternatives that do provide the necessary guarantees for `embedded_io::Read`. @@ -199,8 +203,7 @@ impl<'d, M: Mode> SetConfig for Uart<'d, M> { type ConfigError = ConfigError; fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { - self.tx.set_config(config)?; - self.rx.set_config(config) + self.set_config(config) } } @@ -236,6 +239,12 @@ impl core::fmt::Display for Error { impl core::error::Error for Error {} +impl embedded_io::Error for Error { + fn kind(&self) -> embedded_io::ErrorKind { + embedded_io::ErrorKind::Other + } +} + /// Rx-only UART Driver. /// /// Can be obtained from [`Uart::split`], or can be constructed independently, @@ -260,7 +269,7 @@ impl<'d, M: Mode> SetConfig for UartRx<'d, M> { impl<'d> UartRx<'d, Blocking> { /// Create a new rx-only UART with no hardware flow control. /// - /// Useful if you only want Uart Rx. It saves 1 pin . + /// Useful if you only want Uart Rx. It saves 1 pin. pub fn new_blocking( peri: Peri<'d, T>, rx: Peri<'d, impl RxPin>, @@ -286,19 +295,6 @@ impl<'d> UartRx<'d, Blocking> { } impl<'d, M: Mode> UartRx<'d, M> { - /// Reconfigure the driver - pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { - if let Some(ref rx) = self.rx { - rx.update_pf(config.rx_pf()); - } - - if let Some(ref rts) = self.rts { - rts.update_pf(config.rts_pf()); - } - - reconfigure(self.info, self.state, config) - } - /// Perform a blocking read into `buffer` pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { let r = self.info.regs; @@ -315,6 +311,19 @@ impl<'d, M: Mode> UartRx<'d, M> { Ok(()) } + /// Reconfigure the driver + pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { + if let Some(ref rx) = self.rx { + rx.update_pf(config.rx_pf()); + } + + if let Some(ref rts) = self.rts { + rts.update_pf(config.rts_pf()); + } + + reconfigure(self.info, self.state, config) + } + /// Set baudrate pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { set_baudrate(&self.info, self.state.clock.load(Ordering::Relaxed), baudrate) @@ -378,19 +387,6 @@ impl<'d> UartTx<'d, Blocking> { } impl<'d, M: Mode> UartTx<'d, M> { - /// Reconfigure the driver - pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { - if let Some(ref tx) = self.tx { - tx.update_pf(config.tx_pf()); - } - - if let Some(ref cts) = self.cts { - cts.update_pf(config.cts_pf()); - } - - reconfigure(self.info, self.state, config) - } - /// Perform a blocking UART write pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { let r = self.info.regs; @@ -427,6 +423,24 @@ impl<'d, M: Mode> UartTx<'d, M> { }); } + /// Check if UART is busy. + pub fn busy(&self) -> bool { + busy(self.info.regs) + } + + /// Reconfigure the driver + pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { + if let Some(ref tx) = self.tx { + tx.update_pf(config.tx_pf()); + } + + if let Some(ref cts) = self.cts { + cts.update_pf(config.cts_pf()); + } + + reconfigure(self.info, self.state, config) + } + /// Set baudrate pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { set_baudrate(&self.info, self.state.clock.load(Ordering::Relaxed), baudrate) @@ -489,6 +503,11 @@ impl<'d, M: Mode> Uart<'d, M> { self.tx.blocking_flush() } + /// Check if UART is busy. + pub fn busy(&self) -> bool { + self.tx.busy() + } + /// Perform a blocking read into `buffer` pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { self.rx.blocking_read(buffer) @@ -508,6 +527,12 @@ impl<'d, M: Mode> Uart<'d, M> { (&mut self.tx, &mut self.rx) } + /// Reconfigure the driver + pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { + self.tx.set_config(config)?; + self.rx.set_config(config) + } + /// Send break character pub fn send_break(&self) { self.tx.send_break(); @@ -557,8 +582,16 @@ pub(crate) struct Info { } pub(crate) struct State { - /// The clock rate of the UART. This might be configured. - pub(crate) clock: AtomicU32, + /// The clock rate of the UART in Hz. + clock: AtomicU32, +} + +impl State { + pub const fn new() -> Self { + Self { + clock: AtomicU32::new(0), + } + } } impl<'d, M: Mode> UartRx<'d, M> { @@ -746,7 +779,8 @@ fn configure( info.regs.ctl0().modify(|w| { w.set_lbe(config.loop_back_enable); - w.set_rxe(enable_rx); + // Errata UART_ERR_02, must set RXE to allow use of EOT. + w.set_rxe(enable_rx | enable_tx); w.set_txe(enable_tx); // RXD_OUT_EN and TXD_OUT_EN? w.set_menc(false); @@ -754,13 +788,18 @@ fn configure( w.set_rtsen(enable_rts); w.set_ctsen(enable_cts); // oversampling is set later - // TODO: config - w.set_fen(false); + w.set_fen(config.fifo_enable); // TODO: config w.set_majvote(false); w.set_msbfirst(matches!(config.msb_order, BitOrder::MsbFirst)); }); + info.regs.ifls().modify(|w| { + // TODO: Need power domain info for other options. + w.set_txiflsel(vals::Iflssel::AT_LEAST_ONE); + w.set_rxiflsel(vals::Iflssel::AT_LEAST_ONE); + }); + info.regs.lcrh().modify(|w| { let eps = if matches!(config.parity, Parity::ParityEven) { vals::Eps::EVEN @@ -1021,9 +1060,29 @@ fn read_with_error(r: Regs) -> Result { Ok(rx.data()) } +/// This function assumes CTL0.ENABLE is set (for errata cases). +fn busy(r: Regs) -> bool { + // Errata UART_ERR_08 + if cfg!(any( + mspm0g151x, mspm0g351x, mspm0l110x, mspm0l130x, mspm0l134x, mspm0c110x, + )) { + let stat = r.stat().read(); + // "Poll TXFIFO status and the CTL0.ENABLE register bit to identify BUSY status." + !stat.txfe() + } else { + r.stat().read().busy() + } +} + +// TODO: Implement when dma uart is implemented. +fn dma_enabled(_r: Regs) -> bool { + false +} + pub(crate) trait SealedInstance { fn info() -> &'static Info; fn state() -> &'static State; + fn buffered_state() -> &'static BufferedState; } macro_rules! impl_uart_instance { @@ -1041,12 +1100,16 @@ macro_rules! impl_uart_instance { } fn state() -> &'static crate::uart::State { - use crate::interrupt::typelevel::Interrupt; use crate::uart::State; - static STATE: State = State { - clock: core::sync::atomic::AtomicU32::new(0), - }; + static STATE: State = State::new(); + &STATE + } + + fn buffered_state() -> &'static crate::uart::BufferedState { + use crate::uart::BufferedState; + + static STATE: BufferedState = BufferedState::new(); &STATE } } diff --git a/examples/mspm0g3507/Cargo.toml b/examples/mspm0g3507/Cargo.toml index b6621c9c5..cc40b3109 100644 --- a/examples/mspm0g3507/Cargo.toml +++ b/examples/mspm0g3507/Cargo.toml @@ -17,5 +17,7 @@ defmt-rtt = "1.0.0" panic-probe = { version = "1.0.0", features = ["print-defmt"] } panic-semihosting = "0.6.0" +embedded-io-async = "0.6.1" + [profile.release] debug = 2 diff --git a/tests/mspm0/Cargo.toml b/tests/mspm0/Cargo.toml index 2d5b8cd52..5ba3e586b 100644 --- a/tests/mspm0/Cargo.toml +++ b/tests/mspm0/Cargo.toml @@ -13,6 +13,7 @@ teleprobe-meta = "1.1" embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = [ "defmt" ] } embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = [ "arch-cortex-m", "executor-thread", "defmt" ] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-time = { version = "0.4.0", path = "../../embassy-time", features = [ "defmt" ] } embassy-mspm0 = { version = "0.1.0", path = "../../embassy-mspm0", features = [ "rt", "defmt", "unstable-pac", "time-driver-any" ] } embassy-embedded-hal = { version = "0.3.1", path = "../../embassy-embedded-hal/"} @@ -24,6 +25,8 @@ cortex-m = { version = "0.7.6", features = [ "inline-asm", "critical-section-sin cortex-m-rt = "0.7.0" embedded-hal = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } +embedded-io = { version = "0.6.1", features = ["defmt-03"] } +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } panic-probe = { version = "1.0.0", features = ["print-defmt"] } static_cell = "2" portable-atomic = { version = "1.5", features = ["critical-section"] } diff --git a/tests/mspm0/src/bin/uart_buffered.rs b/tests/mspm0/src/bin/uart_buffered.rs new file mode 100644 index 000000000..135ac1287 --- /dev/null +++ b/tests/mspm0/src/bin/uart_buffered.rs @@ -0,0 +1,115 @@ +#![no_std] +#![no_main] + +#[cfg(feature = "mspm0g3507")] +teleprobe_meta::target!(b"lp-mspm0g3507"); + +use defmt::{assert_eq, unwrap, *}; +use embassy_executor::Spawner; +use embassy_mspm0::uart::{BufferedInterruptHandler, BufferedUart, Config}; +use embassy_mspm0::{bind_interrupts, peripherals}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART1 => BufferedInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_mspm0::init(Default::default()); + info!("Hello World!"); + + // TODO: Allow creating a looped-back UART (so pins are not needed). + // Do not select default UART since the virtual COM port is attached to UART0. + #[cfg(any(feature = "mspm0g3507"))] + let (mut tx, mut rx, mut uart) = (p.PA8, p.PA9, p.UART1); + + { + use embedded_io_async::{Read, Write}; + + let mut config = Config::default(); + config.loop_back_enable = true; + config.fifo_enable = false; + + let tx_buf = &mut [0u8; 16]; + let rx_buf = &mut [0u8; 16]; + let mut uart = unwrap!(BufferedUart::new( + uart.reborrow(), + tx.reborrow(), + rx.reborrow(), + Irqs, + tx_buf, + rx_buf, + config + )); + + let mut buf = [0; 16]; + for (j, b) in buf.iter_mut().enumerate() { + *b = j as u8; + } + + unwrap!(uart.write_all(&buf).await); + unwrap!(uart.flush().await); + + unwrap!(uart.read_exact(&mut buf).await); + for (j, b) in buf.iter().enumerate() { + assert_eq!(*b, j as u8); + } + + // Buffer is unclogged, should be able to write again. + unwrap!(uart.write_all(&buf).await); + unwrap!(uart.flush().await); + + unwrap!(uart.read_exact(&mut buf).await); + for (j, b) in buf.iter().enumerate() { + assert_eq!(*b, j as u8); + } + } + + info!("Blocking buffered"); + { + use embedded_io::{Read, Write}; + + let mut config = Config::default(); + config.loop_back_enable = true; + config.fifo_enable = false; + + let tx_buf = &mut [0u8; 16]; + let rx_buf = &mut [0u8; 16]; + let mut uart = unwrap!(BufferedUart::new( + uart.reborrow(), + tx.reborrow(), + rx.reborrow(), + Irqs, + tx_buf, + rx_buf, + config + )); + + let mut buf = [0; 16]; + + for (j, b) in buf.iter_mut().enumerate() { + *b = j as u8; + } + + unwrap!(uart.write_all(&buf)); + unwrap!(uart.blocking_flush()); + unwrap!(uart.read_exact(&mut buf)); + + for (j, b) in buf.iter().enumerate() { + assert_eq!(*b, j as u8); + } + + // Buffer is unclogged, should be able to write again. + unwrap!(uart.write_all(&buf)); + unwrap!(uart.blocking_flush()); + unwrap!(uart.read_exact(&mut buf)); + + for (j, b) in buf.iter().enumerate() { + assert_eq!(*b, j as u8, "at {}", j); + } + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +}