From 151b1067b09fcc64e291254c0563da04b19d12a7 Mon Sep 17 00:00:00 2001 From: Charles Guan <3221512+charlesincharge@users.noreply.github.com> Date: Thu, 2 Oct 2025 16:08:49 -0700 Subject: Enable input-buffer and enable output drive for I/O pin --- embassy-mspm0/CHANGELOG.md | 1 + embassy-mspm0/src/gpio.rs | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/embassy-mspm0/CHANGELOG.md b/embassy-mspm0/CHANGELOG.md index b846f21b1..aaa09892c 100644 --- a/embassy-mspm0/CHANGELOG.md +++ b/embassy-mspm0/CHANGELOG.md @@ -13,3 +13,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - feat: Add window watchdog implementation based on WWDT0, WWDT1 peripherals (#4574) - feat: Add MSPM0C1105/C1106 support - feat: Add adc implementation (#4646) +- fix: gpio OutputOpenDrain config (#4735) \ No newline at end of file diff --git a/embassy-mspm0/src/gpio.rs b/embassy-mspm0/src/gpio.rs index d5fd36dbf..f9a8d6b75 100644 --- a/embassy-mspm0/src/gpio.rs +++ b/embassy-mspm0/src/gpio.rs @@ -156,7 +156,12 @@ impl<'d> Flex<'d> { w.set_pf(GPIO_PF); w.set_hiz1(true); w.set_pc(true); - w.set_inena(false); + w.set_inena(true); + }); + + // Enable output driver (DOE) - required for open-drain to drive low + self.pin.block().doeset31_0().write(|w| { + w.set_dio(self.pin.bit_index(), true); }); self.set_pull(Pull::None); -- cgit From 3f9af34e5a2347076df1f860baabaaa47eeca13d Mon Sep 17 00:00:00 2001 From: i509VCB Date: Thu, 2 Oct 2025 17:13:32 -0500 Subject: mspm0: add mspm0c1106 to test matrix RGZ package chosen to match LP-MSPM0C1106 board --- embassy-mspm0/CHANGELOG.md | 3 ++- embassy-mspm0/Cargo.toml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/embassy-mspm0/CHANGELOG.md b/embassy-mspm0/CHANGELOG.md index aaa09892c..40abca38b 100644 --- a/embassy-mspm0/CHANGELOG.md +++ b/embassy-mspm0/CHANGELOG.md @@ -13,4 +13,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - feat: Add window watchdog implementation based on WWDT0, WWDT1 peripherals (#4574) - feat: Add MSPM0C1105/C1106 support - feat: Add adc implementation (#4646) -- fix: gpio OutputOpenDrain config (#4735) \ No newline at end of file +- fix: gpio OutputOpenDrain config (#4735) +- fix: add MSPM0C1106 to build test matrix \ No newline at end of file diff --git a/embassy-mspm0/Cargo.toml b/embassy-mspm0/Cargo.toml index 65019eb7c..7245cd100 100644 --- a/embassy-mspm0/Cargo.toml +++ b/embassy-mspm0/Cargo.toml @@ -15,6 +15,7 @@ publish = false [package.metadata.embassy] build = [ {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0c1104dgs20", "time-driver-any"]}, + {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0c1106rgz", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0g3507pm", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0g3519pz", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1306rhb", "time-driver-any"]}, -- cgit From c6799c2921780254319d293d37f33161f5fd1832 Mon Sep 17 00:00:00 2001 From: i509VCB Date: Thu, 2 Oct 2025 17:02:20 -0500 Subject: mspm0: add mspm0h321x support This also changes selection of GPIO interrupt type to be calculated by the buildscript. H321x is yet another exception with GPIOB being a physical interrupt rather than part of an interrupt group. H3215/6 are true 5V parts. I wonder if that is a first for an embassy hal --- embassy-mspm0/CHANGELOG.md | 3 ++- embassy-mspm0/Cargo.toml | 17 +++++++++------- embassy-mspm0/build.rs | 50 ++++++++++++++++++++++++++++++++++++++-------- embassy-mspm0/src/gpio.rs | 17 ++++++++++------ embassy-mspm0/src/i2c.rs | 4 ++-- 5 files changed, 67 insertions(+), 24 deletions(-) diff --git a/embassy-mspm0/CHANGELOG.md b/embassy-mspm0/CHANGELOG.md index 40abca38b..948f0205d 100644 --- a/embassy-mspm0/CHANGELOG.md +++ b/embassy-mspm0/CHANGELOG.md @@ -14,4 +14,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - feat: Add MSPM0C1105/C1106 support - feat: Add adc implementation (#4646) - fix: gpio OutputOpenDrain config (#4735) -- fix: add MSPM0C1106 to build test matrix \ No newline at end of file +- fix: add MSPM0C1106 to build test matrix +- feat: add MSPM0H3216 support diff --git a/embassy-mspm0/Cargo.toml b/embassy-mspm0/Cargo.toml index 7245cd100..df6176ff6 100644 --- a/embassy-mspm0/Cargo.toml +++ b/embassy-mspm0/Cargo.toml @@ -16,17 +16,18 @@ publish = false build = [ {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0c1104dgs20", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0c1106rgz", "time-driver-any"]}, + {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0g1107ycj", "time-driver-any"]}, + {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0g1505pt", "time-driver-any"]}, + {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0g1519rhb", "time-driver-any"]}, + {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0g3105rhb", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0g3507pm", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0g3519pz", "time-driver-any"]}, + {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0h3216pt", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1306rhb", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l2228pn", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1345dgs28", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1106dgs28", "time-driver-any"]}, - {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1228pm", "time-driver-any"]}, - {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0g1107ycj", "time-driver-any"]}, - {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0g3105rhb", "time-driver-any"]}, - {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0g1505pt", "time-driver-any"]}, - {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0g1519rhb", "time-driver-any"]}, + {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1228pt", "time-driver-any"]}, ] [package.metadata.embassy_docs] @@ -38,6 +39,7 @@ flavors = [ { regex_feature = "mspm0c.*", target = "thumbv6m-none-eabi" }, { regex_feature = "mspm0l.*", target = "thumbv6m-none-eabi" }, { regex_feature = "mspm0g.*", target = "thumbv6m-none-eabi" }, + { regex_feature = "mspm0h.*", target = "thumbv6m-none-eabi" }, ] [package.metadata.docs.rs] @@ -70,7 +72,7 @@ cortex-m = "0.7.6" critical-section = "1.2.0" # mspm0-metapac = { version = "" } -mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag = "mspm0-data-d7bf3d01ac0780e716a45b0474234d39443dc5cf" } +mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag = "mspm0-data-e7de4103a0713772695ffcad52c3c2f07414dc29" } [build-dependencies] proc-macro2 = "1.0.94" @@ -78,7 +80,7 @@ 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-d7bf3d01ac0780e716a45b0474234d39443dc5cf", default-features = false, features = ["metadata"] } +mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag = "mspm0-data-e7de4103a0713772695ffcad52c3c2f07414dc29", default-features = false, features = ["metadata"] } [features] default = ["rt"] @@ -244,6 +246,7 @@ mspm0g3519pn = ["mspm0-metapac/mspm0g3519pn"] mspm0g3519pz = ["mspm0-metapac/mspm0g3519pz"] mspm0g3519rgz = ["mspm0-metapac/mspm0g3519rgz"] mspm0g3519rhb = ["mspm0-metapac/mspm0g3519rhb"] +mspm0h3216pt = ["mspm0-metapac/mspm0h3216pt"] mspm0l1105dgs20 = ["mspm0-metapac/mspm0l1105dgs20"] mspm0l1105dgs28 = ["mspm0-metapac/mspm0l1105dgs28"] mspm0l1105dyy = ["mspm0-metapac/mspm0l1105dyy"] diff --git a/embassy-mspm0/build.rs b/embassy-mspm0/build.rs index 93ea81ac3..1d118ad66 100644 --- a/embassy-mspm0/build.rs +++ b/embassy-mspm0/build.rs @@ -16,14 +16,15 @@ use quote::{format_ident, quote}; mod common; fn main() { - generate_code(); - interrupt_group_linker_magic(); -} - -fn generate_code() { let mut cfgs = common::CfgSet::new(); common::set_target_cfgs(&mut cfgs); + generate_code(&mut cfgs); + select_gpio_features(&mut cfgs); + interrupt_group_linker_magic(); +} + +fn generate_code(cfgs: &mut CfgSet) { #[cfg(any(feature = "rt"))] println!( "cargo:rustc-link-search={}", @@ -53,9 +54,9 @@ fn generate_code() { cfgs.declare_all(&get_chip_cfgs(&chip)); } - let mut singletons = get_singletons(&mut cfgs); + let mut singletons = get_singletons(cfgs); - time_driver(&mut singletons, &mut cfgs); + time_driver(&mut singletons, cfgs); let mut g = TokenStream::new(); @@ -68,7 +69,7 @@ fn generate_code() { g.extend(generate_pin_trait_impls()); g.extend(generate_groups()); g.extend(generate_dma_channel_count()); - g.extend(generate_adc_constants(&mut cfgs)); + g.extend(generate_adc_constants(cfgs)); let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); let out_file = out_dir.join("_generated.rs").to_string_lossy().to_string(); @@ -115,6 +116,10 @@ fn get_chip_cfgs(chip_name: &str) -> Vec { cfgs.push("mspm0g351x".to_string()); } + if chip_name.starts_with("mspm0h321") { + cfgs.push("mspm0h321x".to_string()); + } + if chip_name.starts_with("mspm0l110") { cfgs.push("mspm0l110x".to_string()); } @@ -646,6 +651,35 @@ fn generate_pin_trait_impls() -> TokenStream { } } +fn select_gpio_features(cfgs: &mut CfgSet) { + cfgs.declare_all(&[ + "gpioa_interrupt", + "gpioa_group", + "gpiob_interrupt", + "gpiob_group", + "gpioc_group", + ]); + + for interrupt in METADATA.interrupts.iter() { + match interrupt.name { + "GPIOA" => cfgs.enable("gpioa_interrupt"), + "GPIOB" => cfgs.enable("gpiob_interrupt"), + _ => (), + } + } + + for group in METADATA.interrupt_groups.iter() { + for interrupt in group.interrupts { + match interrupt.name { + "GPIOA" => cfgs.enable("gpioa_group"), + "GPIOB" => cfgs.enable("gpiob_group"), + "GPIOC" => cfgs.enable("gpioc_group"), + _ => (), + } + } + } +} + /// rustfmt a given path. /// Failures are logged to stderr and ignored. fn rustfmt(path: impl AsRef) { diff --git a/embassy-mspm0/src/gpio.rs b/embassy-mspm0/src/gpio.rs index 13da4f30b..d8eb42dc2 100644 --- a/embassy-mspm0/src/gpio.rs +++ b/embassy-mspm0/src/gpio.rs @@ -10,7 +10,7 @@ use embassy_sync::waitqueue::AtomicWaker; use crate::pac::gpio::vals::*; use crate::pac::gpio::{self}; -#[cfg(all(feature = "rt", any(mspm0c110x, mspm0c1105_c1106, mspm0l110x)))] +#[cfg(all(feature = "rt", any(gpioa_interrupt, gpiob_interrupt)))] use crate::pac::interrupt; use crate::pac::{self}; @@ -1110,16 +1110,21 @@ fn irq_handler(gpio: gpio::Gpio, wakers: &[AtomicWaker; 32]) { } } +#[cfg(all(gpioa_interrupt, gpioa_group))] +compile_error!("gpioa_interrupt and gpioa_group are mutually exclusive cfgs"); +#[cfg(all(gpiob_interrupt, gpiob_group))] +compile_error!("gpiob_interrupt and gpiob_group are mutually exclusive cfgs"); + // C110x and L110x have a dedicated interrupts just for GPIOA. // // These chips do not have a GROUP1 interrupt. -#[cfg(all(feature = "rt", any(mspm0c110x, mspm0c1105_c1106, mspm0l110x)))] +#[cfg(all(feature = "rt", gpioa_interrupt))] #[interrupt] fn GPIOA() { irq_handler(pac::GPIOA, &PORTA_WAKERS); } -#[cfg(all(feature = "rt", mspm0c1105_c1106))] +#[cfg(all(feature = "rt", gpiob_interrupt))] #[interrupt] fn GPIOB() { irq_handler(pac::GPIOB, &PORTB_WAKERS); @@ -1129,21 +1134,21 @@ fn GPIOB() { // // Defining these as no_mangle is required so that the linker will pick these over the default handler. -#[cfg(all(feature = "rt", not(any(mspm0c110x, mspm0c1105_c1106, mspm0l110x))))] +#[cfg(all(feature = "rt", gpioa_group))] #[unsafe(no_mangle)] #[allow(non_snake_case)] fn GPIOA() { irq_handler(pac::GPIOA, &PORTA_WAKERS); } -#[cfg(all(feature = "rt", gpio_pb, not(mspm0c1105_c1106)))] +#[cfg(all(feature = "rt", gpiob_group))] #[unsafe(no_mangle)] #[allow(non_snake_case)] fn GPIOB() { irq_handler(pac::GPIOB, &PORTB_WAKERS); } -#[cfg(all(feature = "rt", gpio_pc))] +#[cfg(all(feature = "rt", gpioc_group))] #[allow(non_snake_case)] #[unsafe(no_mangle)] fn GPIOC() { diff --git a/embassy-mspm0/src/i2c.rs b/embassy-mspm0/src/i2c.rs index a12b4b4a2..192527dd2 100644 --- a/embassy-mspm0/src/i2c.rs +++ b/embassy-mspm0/src/i2c.rs @@ -206,8 +206,8 @@ impl Config { } #[cfg(any( - mspm0g110x, mspm0g150x, mspm0g151x, mspm0g310x, mspm0g350x, mspm0g351x, mspm0l110x, mspm0l122x, mspm0l130x, - mspm0l134x, mspm0l222x + mspm0g110x, mspm0g150x, mspm0g151x, mspm0g310x, mspm0g350x, mspm0g351x, mspm0h321x, mspm0l110x, mspm0l122x, + mspm0l130x, mspm0l134x, mspm0l222x ))] fn calculate_clock_source(&self) -> u32 { // Assume that BusClk has default value. -- cgit From a83cf2480671cee67e8edaa27565203aaaf6d8bc Mon Sep 17 00:00:00 2001 From: Kezi Date: Thu, 9 Oct 2025 21:00:57 +0200 Subject: remove panic on uarte overrun --- embassy-nrf/CHANGELOG.md | 1 + embassy-nrf/src/buffered_uarte.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/embassy-nrf/CHANGELOG.md b/embassy-nrf/CHANGELOG.md index 0244dedab..9e8d29f67 100644 --- a/embassy-nrf/CHANGELOG.md +++ b/embassy-nrf/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate - changed: apply trimming values from FICR.TRIMCNF on nrf53/54l +- changed: do not panic on BufferedUarte overrun ## 0.8.0 - 2025-09-30 diff --git a/embassy-nrf/src/buffered_uarte.rs b/embassy-nrf/src/buffered_uarte.rs index 4c946497d..ec104788f 100644 --- a/embassy-nrf/src/buffered_uarte.rs +++ b/embassy-nrf/src/buffered_uarte.rs @@ -87,7 +87,8 @@ impl interrupt::typelevel::Handler for Interrupt r.errorsrc().write_value(errs); if errs.overrun() { - panic!("BufferedUarte overrun"); + #[cfg(feature = "defmt")] + defmt::warn!("BufferedUarte overrun"); } } -- cgit From 2f31d7da537c754f3b1ba7b9533d1c42269721c5 Mon Sep 17 00:00:00 2001 From: Angelina-2007 <24uam107angelina@kgkite.ac.in> Date: Thu, 9 Oct 2025 18:51:15 -0700 Subject: Fix #2696: Prevent 8-bit UID reads on STM32H5 by returning [u8; 12] value --- embassy-stm32/src/uid.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/uid.rs b/embassy-stm32/src/uid.rs index 5e38532bd..2d3e2b972 100644 --- a/embassy-stm32/src/uid.rs +++ b/embassy-stm32/src/uid.rs @@ -1,8 +1,8 @@ //! Unique ID (UID) /// Get this device's unique 96-bit ID. -pub fn uid() -> &'static [u8; 12] { - unsafe { &*crate::pac::UID.uid(0).as_ptr().cast::<[u8; 12]>() } +pub fn uid() -> [u8; 12] { + unsafe { *crate::pac::UID.uid(0).as_ptr().cast::<[u8; 12]>() } } /// Get this device's unique 96-bit ID, encoded into a string of 24 hexadecimal ASCII digits. -- cgit From 94c2cc9eba3b1840063ea11a62e2eb27442bbe16 Mon Sep 17 00:00:00 2001 From: Angelina-2007 <24uam107angelina@kgkite.ac.in> Date: Thu, 9 Oct 2025 20:21:41 -0700 Subject: ci: Add changelog entry for STM32H5 UID fix --- embassy-stm32/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index a6ee5c4b8..7675567ff 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +### [Unreleased] + +* **Fix(stm32h5):** Prevent a HardFault crash on STM32H5 devices by changing `uid()` to return `[u8; 12]` by value instead of a reference. (Fixes #2696) ## Unreleased - ReleaseDate - fix: Fixed STM32H5 builds requiring time feature -- cgit From c685d80578b0fea8f66f8f8c858c9d59857586b5 Mon Sep 17 00:00:00 2001 From: crispaudio Date: Fri, 29 Aug 2025 12:32:38 +0200 Subject: mspm0-i2c-target: add i2c target with example --- embassy-mspm0/src/i2c.rs | 45 ++-- embassy-mspm0/src/i2c_target.rs | 435 ++++++++++++++++++++++++++++++ embassy-mspm0/src/lib.rs | 1 + examples/mspm0g3507/src/bin/i2c_target.rs | 60 +++++ 4 files changed, 526 insertions(+), 15 deletions(-) create mode 100644 embassy-mspm0/src/i2c_target.rs create mode 100644 examples/mspm0g3507/src/bin/i2c_target.rs diff --git a/embassy-mspm0/src/i2c.rs b/embassy-mspm0/src/i2c.rs index 192527dd2..ce5215871 100644 --- a/embassy-mspm0/src/i2c.rs +++ b/embassy-mspm0/src/i2c.rs @@ -56,19 +56,6 @@ pub enum ClockDiv { } impl ClockDiv { - fn into(self) -> vals::Ratio { - match self { - Self::DivBy1 => vals::Ratio::DIV_BY_1, - Self::DivBy2 => vals::Ratio::DIV_BY_2, - Self::DivBy3 => vals::Ratio::DIV_BY_3, - Self::DivBy4 => vals::Ratio::DIV_BY_4, - Self::DivBy5 => vals::Ratio::DIV_BY_5, - Self::DivBy6 => vals::Ratio::DIV_BY_6, - Self::DivBy7 => vals::Ratio::DIV_BY_7, - Self::DivBy8 => vals::Ratio::DIV_BY_8, - } - } - fn divider(self) -> u32 { match self { Self::DivBy1 => 1, @@ -83,6 +70,21 @@ impl ClockDiv { } } +impl From for vals::Ratio { + fn from(value: ClockDiv) -> Self { + match value { + ClockDiv::DivBy1 => Self::DIV_BY_1, + ClockDiv::DivBy2 => Self::DIV_BY_2, + ClockDiv::DivBy3 => Self::DIV_BY_3, + ClockDiv::DivBy4 => Self::DIV_BY_4, + ClockDiv::DivBy5 => Self::DIV_BY_5, + ClockDiv::DivBy6 => Self::DIV_BY_6, + ClockDiv::DivBy7 => Self::DIV_BY_7, + ClockDiv::DivBy8 => Self::DIV_BY_8, + } + } +} + /// The I2C mode. #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -133,6 +135,11 @@ pub enum ConfigError { /// /// The clock soure is not enabled is SYSCTL. ClockSourceNotEnabled, + + /// Invalid target address. + /// + /// The target address is not 7-bit. + InvalidTargetAddress, } #[non_exhaustive] @@ -140,7 +147,7 @@ pub enum ConfigError { /// Config pub struct Config { /// I2C clock source. - clock_source: ClockSel, + pub(crate) clock_source: ClockSel, /// I2C clock divider. pub clock_div: ClockDiv, @@ -159,6 +166,12 @@ pub struct Config { /// Set the pull configuration for the SCL pin. pub bus_speed: BusSpeed, + + /// 7-bit Target Address + pub target_addr: u8, + + /// Control if the target should ack to and report general calls. + pub general_call: bool, } impl Default for Config { @@ -171,6 +184,8 @@ impl Default for Config { sda_pull: Pull::None, scl_pull: Pull::None, bus_speed: BusSpeed::Standard, + target_addr: 0x48, + general_call: false, } } } @@ -209,7 +224,7 @@ impl Config { mspm0g110x, mspm0g150x, mspm0g151x, mspm0g310x, mspm0g350x, mspm0g351x, mspm0h321x, mspm0l110x, mspm0l122x, mspm0l130x, mspm0l134x, mspm0l222x ))] - fn calculate_clock_source(&self) -> u32 { + pub(crate) fn calculate_clock_source(&self) -> u32 { // Assume that BusClk has default value. // TODO: calculate BusClk more precisely. match self.clock_source { diff --git a/embassy-mspm0/src/i2c_target.rs b/embassy-mspm0/src/i2c_target.rs new file mode 100644 index 000000000..c6ef2f5d4 --- /dev/null +++ b/embassy-mspm0/src/i2c_target.rs @@ -0,0 +1,435 @@ +//! Inter-Integrated-Circuit (I2C) Target +// The following code is modified from embassy-stm32 and embassy-rp +// https://github.com/embassy-rs/embassy/tree/main/embassy-stm32 +// https://github.com/embassy-rs/embassy/tree/main/embassy-rp + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::sync::atomic::Ordering; +use core::task::Poll; + +use mspm0_metapac::i2c::vals::CpuIntIidxStat; + +use crate::gpio::{AnyPin, SealedPin}; +use crate::interrupt; +use crate::interrupt::InterruptExt; +use crate::mode::{Async, Blocking, Mode}; +use crate::pac::{self, i2c::vals}; +use crate::Peri; +// Re-use I2c controller types +use crate::i2c::{ClockSel, Config, ConfigError, Info, Instance, InterruptHandler, SclPin, SdaPin, State}; + +/// I2C error +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// User passed in a response buffer that was 0 length + InvalidResponseBufferLength, + /// The response buffer length was too short to contain the message + /// + /// The length parameter will always be the length of the buffer, and is + /// provided as a convenience for matching alongside `Command::Write`. + PartialWrite(usize), + /// The response buffer length was too short to contain the message + /// + /// The length parameter will always be the length of the buffer, and is + /// provided as a convenience for matching alongside `Command::GeneralCall`. + PartialGeneralCall(usize), +} + +/// Received command from the controller. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Command { + /// General Call Write: Controller sent the General Call address (0x00) followed by data. + /// Contains the number of bytes written by the controller. + GeneralCall(usize), + /// Read: Controller wants to read data from the target. + Read, + /// Write: Controller sent the target's address followed by data. + /// Contains the number of bytes written by the controller. + Write(usize), + /// Write followed by Read (Repeated Start): Controller wrote data, then issued a repeated + /// start and wants to read data. Contains the number of bytes written before the read. + WriteRead(usize), +} + +/// Status after responding to a controller read request. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ReadStatus { + /// Transaction completed successfully. The controller either NACKed the last byte + /// or sent a STOP condition. + Done, + /// Transaction Incomplete, controller trying to read more bytes than were provided + NeedMoreBytes, + /// Transaction Complere, but controller stopped reading bytes before we ran out + LeftoverBytes(u16), +} + +/// I2C Target driver. +// Use the same Instance, SclPin, SdaPin traits as the controller +pub struct I2cTarget<'d, M: Mode> { + info: &'static Info, + state: &'static State, + scl: Option>, + sda: Option>, + config: Config, + _phantom: PhantomData, +} + +impl<'d> I2cTarget<'d, Async> { + /// Create a new asynchronous I2C target driver using interrupts + pub fn new( + peri: Peri<'d, T>, + scl: Peri<'d, impl SclPin>, + sda: Peri<'d, impl SdaPin>, + _irq: impl interrupt::typelevel::Binding> + 'd, + config: Config, + ) -> Result { + let mut this = Self::new_inner( + peri, + new_pin!(scl, config.scl_pf()), + new_pin!(sda, config.sda_pf()), + config, + ); + this.reset()?; + Ok(this) + } + + /// Reset the i2c peripheral. If you cancel a respond_to_read, you may stall the bus. + /// You can recover the bus by calling this function, but doing so will almost certainly cause + /// an i/o error in the controller. + pub fn reset(&mut self) -> Result<(), ConfigError> { + self.init()?; + unsafe { self.info.interrupt.enable() }; + Ok(()) + } +} + +impl<'d> I2cTarget<'d, Blocking> { + /// Create a new blocking I2C target driver. + pub fn new_blocking( + peri: Peri<'d, T>, + scl: Peri<'d, impl SclPin>, + sda: Peri<'d, impl SdaPin>, + config: Config, + ) -> Result { + let mut this = Self::new_inner( + peri, + new_pin!(scl, config.scl_pf()), + new_pin!(sda, config.sda_pf()), + config, + ); + this.reset()?; + Ok(this) + } + + /// Reset the i2c peripheral. If you cancel a respond_to_read, you may stall the bus. + /// You can recover the bus by calling this function, but doing so will almost certainly cause + /// an i/o error in the controller. + pub fn reset(&mut self) -> Result<(), ConfigError> { + self.init()?; + Ok(()) + } +} + +impl<'d, M: Mode> I2cTarget<'d, M> { + fn new_inner( + _peri: Peri<'d, T>, + scl: Option>, + sda: Option>, + config: Config, + ) -> Self { + if let Some(ref scl) = scl { + let pincm = pac::IOMUX.pincm(scl._pin_cm() as usize); + pincm.modify(|w| { + w.set_hiz1(true); + }); + } + if let Some(ref sda) = sda { + let pincm = pac::IOMUX.pincm(sda._pin_cm() as usize); + pincm.modify(|w| { + w.set_hiz1(true); + }); + } + + Self { + info: T::info(), + state: T::state(), + scl, + sda, + config, + _phantom: PhantomData, + } + } + + fn init(&mut self) -> Result<(), ConfigError> { + let mut config = self.config; + let regs = self.info.regs; + + config.check_config()?; + // Target address must be 7-bit + if !(config.target_addr < 0x80) { + return Err(ConfigError::InvalidTargetAddress); + } + + regs.target(0).tctr().modify(|w| { + w.set_active(false); + }); + + // Init power for I2C + regs.gprcm(0).rstctl().write(|w| { + w.set_resetstkyclr(true); + w.set_resetassert(true); + w.set_key(vals::ResetKey::KEY); + }); + + regs.gprcm(0).pwren().write(|w| { + w.set_enable(true); + w.set_key(vals::PwrenKey::KEY); + }); + + self.info.interrupt.disable(); + + // Init delay from the M0 examples by TI in CCStudio (16 cycles) + cortex_m::asm::delay(16); + + // Select and configure the I2C clock using the CLKSEL and CLKDIV registers + regs.clksel().write(|w| match config.clock_source { + ClockSel::BusClk => { + w.set_mfclk_sel(false); + w.set_busclk_sel(true); + } + ClockSel::MfClk => { + w.set_mfclk_sel(true); + w.set_busclk_sel(false); + } + }); + regs.clkdiv().write(|w| w.set_ratio(config.clock_div.into())); + + // Configure at least one target address by writing the 7-bit address to I2Cx.SOAR register. The additional + // target address can be enabled and configured by using I2Cx.TOAR2 register. + regs.target(0).toar().modify(|w| { + w.set_oaren(true); + w.set_oar(config.target_addr as u16); + }); + + self.state + .clock + .store(config.calculate_clock_source(), Ordering::Relaxed); + + regs.target(0).tctr().modify(|w| { + w.set_gencall(config.general_call); + w.set_tclkstretch(true); + // Disable target wakeup, follow TI example. (TI note: Workaround for errata I2C_ERR_04.) + w.set_twuen(false); + w.set_txempty_on_treq(true); + }); + + // Enable the I2C target mode by setting the ACTIVE bit in I2Cx.TCTR register. + regs.target(0).tctr().modify(|w| { + w.set_active(true); + }); + + Ok(()) + } + + #[inline(always)] + fn drain_fifo(&mut self, buffer: &mut [u8], offset: &mut usize) { + let regs = self.info.regs; + + for b in &mut buffer[*offset..] { + if regs.target(0).tfifosr().read().rxfifocnt() == 0 { + break; + } + + *b = regs.target(0).trxdata().read().value(); + *offset += 1; + } + } + + /// Blocking function to empty the tx fifo + /// + /// This function can be used to empty the transmit FIFO if data remains after handling a 'read' command (LeftoverBytes). + pub fn flush_tx_fifo(&mut self) { + self.info.regs.target(0).tfifoctl().modify(|w| { + w.set_txflush(true); + }); + while self.info.regs.target(0).tfifosr().read().txfifocnt() as usize != self.info.fifo_size {} + self.info.regs.target(0).tfifoctl().modify(|w| { + w.set_txflush(false); + }); + } +} + +impl<'d> I2cTarget<'d, Async> { + /// Wait asynchronously for commands from an I2C controller. + /// `buffer` is provided in case controller does a 'write', 'write read', or 'general call' and is unused for 'read'. + pub async fn listen(&mut self, buffer: &mut [u8]) -> Result { + let regs = self.info.regs; + + let mut len = 0; + + // Set the rx fifo interrupt to avoid a fifo overflow + regs.target(0).tfifoctl().modify(|r| { + r.set_rxtrig(vals::TfifoctlRxtrig::LEVEL_6); + }); + + self.wait_on( + |me| { + // Check if address matches the General Call address (0x00) + let is_gencall = regs.target(0).tsr().read().addrmatch() == 0; + + if regs.target(0).tfifosr().read().rxfifocnt() > 0 { + me.drain_fifo(buffer, &mut len); + } + + if buffer.len() == len && regs.target(0).tfifosr().read().rxfifocnt() > 0 { + if is_gencall { + return Poll::Ready(Err(Error::PartialGeneralCall(buffer.len()))); + } else { + return Poll::Ready(Err(Error::PartialWrite(buffer.len()))); + } + } + + let iidx = regs.cpu_int(0).iidx().read().stat(); + trace!("ls:{} len:{}", iidx as u8, len); + let result = match iidx { + CpuIntIidxStat::TTXEMPTY => match len { + 0 => Poll::Ready(Ok(Command::Read)), + w => Poll::Ready(Ok(Command::WriteRead(w))), + }, + CpuIntIidxStat::TSTOPFG => match (is_gencall, len) { + (_, 0) => Poll::Pending, + (true, w) => Poll::Ready(Ok(Command::GeneralCall(w))), + (false, w) => Poll::Ready(Ok(Command::Write(w))), + }, + _ => Poll::Pending, + }; + if !result.is_pending() { + regs.cpu_int(0).imask().write(|_| {}); + } + result + }, + |_me| { + regs.cpu_int(0).imask().write(|_| {}); + regs.cpu_int(0).imask().modify(|w| { + w.set_tgencall(true); + w.set_trxfifotrg(true); + w.set_tstop(true); + w.set_ttxempty(true); + }); + }, + ) + .await + } + + /// Respond to an I2C controller 'read' command, asynchronously. + pub async fn respond_to_read(&mut self, buffer: &[u8]) -> Result { + if buffer.is_empty() { + return Err(Error::InvalidResponseBufferLength); + } + + let regs = self.info.regs; + let fifo_size = self.info.fifo_size; + let mut chunks = buffer.chunks(self.info.fifo_size); + + self.wait_on( + |_me| { + if let Some(chunk) = chunks.next() { + for byte in chunk { + regs.target(0).ttxdata().write(|w| w.set_value(*byte)); + } + + return Poll::Pending; + } + + let iidx = regs.cpu_int(0).iidx().read().stat(); + let fifo_bytes = fifo_size - regs.target(0).tfifosr().read().txfifocnt() as usize; + trace!("rs:{}, fifo:{}", iidx as u8, fifo_bytes); + + let result = match iidx { + CpuIntIidxStat::TTXEMPTY => Poll::Ready(Ok(ReadStatus::NeedMoreBytes)), + CpuIntIidxStat::TSTOPFG => match fifo_bytes { + 0 => Poll::Ready(Ok(ReadStatus::Done)), + w => Poll::Ready(Ok(ReadStatus::LeftoverBytes(w as u16))), + }, + _ => Poll::Pending, + }; + if !result.is_pending() { + regs.cpu_int(0).imask().write(|_| {}); + } + result + }, + |_me| { + regs.cpu_int(0).imask().write(|_| {}); + regs.cpu_int(0).imask().modify(|w| { + w.set_ttxempty(true); + w.set_tstop(true); + }); + }, + ) + .await + } + + /// Respond to reads with the fill byte until the controller stops asking + pub async fn respond_till_stop(&mut self, fill: u8) -> Result<(), Error> { + // The buffer size could be increased to reduce interrupt noise but has higher probability + // of LeftoverBytes + let buff = [fill]; + loop { + match self.respond_to_read(&buff).await { + Ok(ReadStatus::NeedMoreBytes) => (), + Ok(_) => break Ok(()), + Err(e) => break Err(e), + } + } + } + + /// Respond to a controller read, then fill any remaining read bytes with `fill` + pub async fn respond_and_fill(&mut self, buffer: &[u8], fill: u8) -> Result { + let resp_stat = self.respond_to_read(buffer).await?; + + if resp_stat == ReadStatus::NeedMoreBytes { + self.respond_till_stop(fill).await?; + Ok(ReadStatus::Done) + } else { + Ok(resp_stat) + } + } + + /// Calls `f` to check if we are ready or not. + /// If not, `g` is called once(to eg enable the required interrupts). + /// The waker will always be registered prior to calling `f`. + #[inline(always)] + async fn wait_on(&mut self, mut f: F, mut g: G) -> U + where + F: FnMut(&mut Self) -> Poll, + G: FnMut(&mut Self), + { + poll_fn(|cx| { + // Register prior to checking the condition + self.state.waker.register(cx.waker()); + let r = f(self); + + if r.is_pending() { + g(self); + } + + r + }) + .await + } +} + +impl<'d, M: Mode> Drop for I2cTarget<'d, M> { + fn drop(&mut self) { + // Ensure peripheral is disabled and pins are reset + self.info.regs.target(0).tctr().modify(|w| w.set_active(false)); + + self.scl.as_ref().map(|x| x.set_as_disconnected()); + self.sda.as_ref().map(|x| x.set_as_disconnected()); + } +} diff --git a/embassy-mspm0/src/lib.rs b/embassy-mspm0/src/lib.rs index 55f3f9381..7135dd9f0 100644 --- a/embassy-mspm0/src/lib.rs +++ b/embassy-mspm0/src/lib.rs @@ -18,6 +18,7 @@ pub mod adc; pub mod dma; pub mod gpio; pub mod i2c; +pub mod i2c_target; pub mod timer; pub mod uart; pub mod wwdt; diff --git a/examples/mspm0g3507/src/bin/i2c_target.rs b/examples/mspm0g3507/src/bin/i2c_target.rs new file mode 100644 index 000000000..b1336cdd2 --- /dev/null +++ b/examples/mspm0g3507/src/bin/i2c_target.rs @@ -0,0 +1,60 @@ +//! Example of using blocking I2C +//! +//! This uses the virtual COM port provided on the LP-MSPM0G3507 board. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_mspm0::i2c_target::{Command, I2cTarget, ReadStatus}; +use embassy_mspm0::peripherals::I2C1; +use embassy_mspm0::{bind_interrupts, i2c}; +use {defmt_rtt as _, panic_halt as _}; + +bind_interrupts!(struct Irqs { + I2C1 => i2c::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let p = embassy_mspm0::init(Default::default()); + + let instance = p.I2C1; + let scl = p.PB2; + let sda = p.PB3; + + let mut config = i2c::Config::default(); + config.general_call = true; + let mut i2c = I2cTarget::new(instance, scl, sda, Irqs, config).unwrap(); + + let mut read = [0u8; 8]; + let data = [8u8; 2]; + let data_wr = [9u8; 2]; + + loop { + match i2c.listen(&mut read).await { + Ok(Command::GeneralCall(_)) => info!("General call received"), + Ok(Command::Read) => { + info!("Read command received"); + match i2c.respond_to_read(&data).await.unwrap() { + ReadStatus::Done => info!("Finished reading"), + ReadStatus::NeedMoreBytes => { + info!("Read needs more bytes - will reset"); + i2c.reset().unwrap(); + } + ReadStatus::LeftoverBytes(_) => { + info!("Leftover bytes received"); + i2c.flush_tx_fifo(); + } + } + } + Ok(Command::Write(_)) => info!("Write command received"), + Ok(Command::WriteRead(_)) => { + info!("Write-Read command received"); + i2c.respond_and_fill(&data_wr, 0xFE).await.unwrap(); + } + Err(e) => info!("Got error {}", e), + } + } +} -- cgit From a12299d35d4641a8dfee27c52ec274257815cb3f Mon Sep 17 00:00:00 2001 From: crispaudio Date: Fri, 29 Aug 2025 12:44:40 +0200 Subject: mspm0-i2c-target: fix comment in example --- examples/mspm0g3507/src/bin/i2c_target.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/mspm0g3507/src/bin/i2c_target.rs b/examples/mspm0g3507/src/bin/i2c_target.rs index b1336cdd2..cb1d315fe 100644 --- a/examples/mspm0g3507/src/bin/i2c_target.rs +++ b/examples/mspm0g3507/src/bin/i2c_target.rs @@ -1,4 +1,4 @@ -//! Example of using blocking I2C +//! Example of using async I2C target //! //! This uses the virtual COM port provided on the LP-MSPM0G3507 board. -- cgit From e62ec23a389801f7a7c22db0727ae43814138fe1 Mon Sep 17 00:00:00 2001 From: crispaudio Date: Fri, 29 Aug 2025 12:49:58 +0200 Subject: mspm0-i2c-target: added to changelog --- embassy-mspm0/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/embassy-mspm0/CHANGELOG.md b/embassy-mspm0/CHANGELOG.md index 948f0205d..fcb0f9dbd 100644 --- a/embassy-mspm0/CHANGELOG.md +++ b/embassy-mspm0/CHANGELOG.md @@ -16,3 +16,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - fix: gpio OutputOpenDrain config (#4735) - fix: add MSPM0C1106 to build test matrix - feat: add MSPM0H3216 support +- feat: Add i2c target implementation (#4605) -- cgit From 7797cc0effa069b78be29ff19b81068b17f98ac2 Mon Sep 17 00:00:00 2001 From: Iooon Date: Wed, 1 Oct 2025 11:00:44 +0200 Subject: mspm0-i2c-target: add mspm0l1306 example and set target address explicitly in examples --- examples/mspm0g3507/src/bin/i2c_target.rs | 1 + examples/mspm0l1306/src/bin/i2c_target.rs | 61 +++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 examples/mspm0l1306/src/bin/i2c_target.rs diff --git a/examples/mspm0g3507/src/bin/i2c_target.rs b/examples/mspm0g3507/src/bin/i2c_target.rs index cb1d315fe..ac7083a20 100644 --- a/examples/mspm0g3507/src/bin/i2c_target.rs +++ b/examples/mspm0g3507/src/bin/i2c_target.rs @@ -25,6 +25,7 @@ async fn main(_spawner: Spawner) -> ! { let sda = p.PB3; let mut config = i2c::Config::default(); + config.target_addr = 0x48; config.general_call = true; let mut i2c = I2cTarget::new(instance, scl, sda, Irqs, config).unwrap(); diff --git a/examples/mspm0l1306/src/bin/i2c_target.rs b/examples/mspm0l1306/src/bin/i2c_target.rs new file mode 100644 index 000000000..38d309e6b --- /dev/null +++ b/examples/mspm0l1306/src/bin/i2c_target.rs @@ -0,0 +1,61 @@ +//! Example of using async I2C target +//! +//! This uses the virtual COM port provided on the LP-MSPM0L1306 board. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_mspm0::i2c_target::{Command, I2cTarget, ReadStatus}; +use embassy_mspm0::peripherals::I2C0; +use embassy_mspm0::{bind_interrupts, i2c}; +use {defmt_rtt as _, panic_halt as _}; + +bind_interrupts!(struct Irqs { + I2C0 => i2c::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let p = embassy_mspm0::init(Default::default()); + + let instance = p.I2C0; + let scl = p.PA1; + let sda = p.PA0; + + let mut config = i2c::Config::default(); + config.target_addr = 0x48; + config.general_call = true; + let mut i2c = I2cTarget::new(instance, scl, sda, Irqs, config).unwrap(); + + let mut read = [0u8; 8]; + let data = [8u8; 2]; + let data_wr = [9u8; 2]; + + loop { + match i2c.listen(&mut read).await { + Ok(Command::GeneralCall(_)) => info!("General call received"), + Ok(Command::Read) => { + info!("Read command received"); + match i2c.respond_to_read(&data).await.unwrap() { + ReadStatus::Done => info!("Finished reading"), + ReadStatus::NeedMoreBytes => { + info!("Read needs more bytes - will reset"); + i2c.reset().unwrap(); + } + ReadStatus::LeftoverBytes(_) => { + info!("Leftover bytes received"); + i2c.flush_tx_fifo(); + } + } + } + Ok(Command::Write(_)) => info!("Write command received"), + Ok(Command::WriteRead(_)) => { + info!("Write-Read command received"); + i2c.respond_and_fill(&data_wr, 0xFE).await.unwrap(); + } + Err(e) => info!("Got error {}", e), + } + } +} -- cgit From 8d13271100595c31001e0dd1078067a96e42816d Mon Sep 17 00:00:00 2001 From: Iooon Date: Wed, 1 Oct 2025 11:23:57 +0200 Subject: mspm0-i2c-target: fix spelling mistakes and revert From implementation --- embassy-mspm0/src/i2c.rs | 28 +++++++++++++--------------- embassy-mspm0/src/i2c_target.rs | 4 ++-- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/embassy-mspm0/src/i2c.rs b/embassy-mspm0/src/i2c.rs index ce5215871..0aefd19de 100644 --- a/embassy-mspm0/src/i2c.rs +++ b/embassy-mspm0/src/i2c.rs @@ -56,6 +56,19 @@ pub enum ClockDiv { } impl ClockDiv { + pub(crate) fn into(self) -> vals::Ratio { + match self { + Self::DivBy1 => vals::Ratio::DIV_BY_1, + Self::DivBy2 => vals::Ratio::DIV_BY_2, + Self::DivBy3 => vals::Ratio::DIV_BY_3, + Self::DivBy4 => vals::Ratio::DIV_BY_4, + Self::DivBy5 => vals::Ratio::DIV_BY_5, + Self::DivBy6 => vals::Ratio::DIV_BY_6, + Self::DivBy7 => vals::Ratio::DIV_BY_7, + Self::DivBy8 => vals::Ratio::DIV_BY_8, + } + } + fn divider(self) -> u32 { match self { Self::DivBy1 => 1, @@ -70,21 +83,6 @@ impl ClockDiv { } } -impl From for vals::Ratio { - fn from(value: ClockDiv) -> Self { - match value { - ClockDiv::DivBy1 => Self::DIV_BY_1, - ClockDiv::DivBy2 => Self::DIV_BY_2, - ClockDiv::DivBy3 => Self::DIV_BY_3, - ClockDiv::DivBy4 => Self::DIV_BY_4, - ClockDiv::DivBy5 => Self::DIV_BY_5, - ClockDiv::DivBy6 => Self::DIV_BY_6, - ClockDiv::DivBy7 => Self::DIV_BY_7, - ClockDiv::DivBy8 => Self::DIV_BY_8, - } - } -} - /// The I2C mode. #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] diff --git a/embassy-mspm0/src/i2c_target.rs b/embassy-mspm0/src/i2c_target.rs index c6ef2f5d4..7183280bd 100644 --- a/embassy-mspm0/src/i2c_target.rs +++ b/embassy-mspm0/src/i2c_target.rs @@ -62,9 +62,9 @@ pub enum ReadStatus { /// Transaction completed successfully. The controller either NACKed the last byte /// or sent a STOP condition. Done, - /// Transaction Incomplete, controller trying to read more bytes than were provided + /// Transaction incomplete, controller trying to read more bytes than were provided NeedMoreBytes, - /// Transaction Complere, but controller stopped reading bytes before we ran out + /// Transaction complete, but controller stopped reading bytes before we ran out LeftoverBytes(u16), } -- cgit From e6988a3acd8abacb33d6cc2f57f1ad576b1d8687 Mon Sep 17 00:00:00 2001 From: Iooon Date: Sat, 4 Oct 2025 17:03:51 +0200 Subject: mspm0-i2c-target: split i2c controller and i2c target configs --- embassy-mspm0/src/i2c.rs | 8 ---- embassy-mspm0/src/i2c_target.rs | 94 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 84 insertions(+), 18 deletions(-) diff --git a/embassy-mspm0/src/i2c.rs b/embassy-mspm0/src/i2c.rs index 0aefd19de..fb871f85d 100644 --- a/embassy-mspm0/src/i2c.rs +++ b/embassy-mspm0/src/i2c.rs @@ -164,12 +164,6 @@ pub struct Config { /// Set the pull configuration for the SCL pin. pub bus_speed: BusSpeed, - - /// 7-bit Target Address - pub target_addr: u8, - - /// Control if the target should ack to and report general calls. - pub general_call: bool, } impl Default for Config { @@ -182,8 +176,6 @@ impl Default for Config { sda_pull: Pull::None, scl_pull: Pull::None, bus_speed: BusSpeed::Standard, - target_addr: 0x48, - general_call: false, } } } diff --git a/embassy-mspm0/src/i2c_target.rs b/embassy-mspm0/src/i2c_target.rs index 7183280bd..86be91415 100644 --- a/embassy-mspm0/src/i2c_target.rs +++ b/embassy-mspm0/src/i2c_target.rs @@ -8,16 +8,36 @@ use core::marker::PhantomData; use core::sync::atomic::Ordering; use core::task::Poll; +use embassy_embedded_hal::SetConfig; use mspm0_metapac::i2c::vals::CpuIntIidxStat; use crate::gpio::{AnyPin, SealedPin}; -use crate::interrupt; use crate::interrupt::InterruptExt; use crate::mode::{Async, Blocking, Mode}; use crate::pac::{self, i2c::vals}; -use crate::Peri; +use crate::{i2c, i2c_target, interrupt, Peri}; // Re-use I2c controller types -use crate::i2c::{ClockSel, Config, ConfigError, Info, Instance, InterruptHandler, SclPin, SdaPin, State}; +use crate::i2c::{ClockSel, ConfigError, Info, Instance, InterruptHandler, SclPin, SdaPin, State}; + +#[non_exhaustive] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +/// Config +pub struct Config { + /// 7-bit Target Address + pub target_addr: u8, + + /// Control if the target should ack to and report general calls. + pub general_call: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + target_addr: 0x48, + general_call: false, + } + } +} /// I2C error #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -75,24 +95,71 @@ pub struct I2cTarget<'d, M: Mode> { state: &'static State, scl: Option>, sda: Option>, - config: Config, + config: i2c::Config, + target_config: i2c_target::Config, _phantom: PhantomData, } +impl<'d> SetConfig for I2cTarget<'d, Async> { + type Config = (i2c::Config, i2c_target::Config); + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.info.interrupt.disable(); + + if let Some(ref sda) = self.sda { + sda.update_pf(config.0.sda_pf()); + } + + if let Some(ref scl) = self.scl { + scl.update_pf(config.0.scl_pf()); + } + + self.config = config.0.clone(); + self.target_config = config.1.clone(); + + self.reset() + } +} + +impl<'d> SetConfig for I2cTarget<'d, Blocking> { + type Config = (i2c::Config, i2c_target::Config); + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + if let Some(ref sda) = self.sda { + sda.update_pf(config.0.sda_pf()); + } + + if let Some(ref scl) = self.scl { + scl.update_pf(config.0.scl_pf()); + } + + self.config = config.0.clone(); + self.target_config = config.1.clone(); + + self.reset() + } +} + impl<'d> I2cTarget<'d, Async> { /// Create a new asynchronous I2C target driver using interrupts + /// The `config` reuses the i2c controller config to setup the clock while `target_config` + /// configures i2c target specific parameters. pub fn new( peri: Peri<'d, T>, scl: Peri<'d, impl SclPin>, sda: Peri<'d, impl SdaPin>, _irq: impl interrupt::typelevel::Binding> + 'd, - config: Config, + config: i2c::Config, + target_config: i2c_target::Config, ) -> Result { let mut this = Self::new_inner( peri, new_pin!(scl, config.scl_pf()), new_pin!(sda, config.sda_pf()), config, + target_config, ); this.reset()?; Ok(this) @@ -110,17 +177,21 @@ impl<'d> I2cTarget<'d, Async> { impl<'d> I2cTarget<'d, Blocking> { /// Create a new blocking I2C target driver. + /// The `config` reuses the i2c controller config to setup the clock while `target_config` + /// configures i2c target specific parameters. pub fn new_blocking( peri: Peri<'d, T>, scl: Peri<'d, impl SclPin>, sda: Peri<'d, impl SdaPin>, - config: Config, + config: i2c::Config, + target_config: i2c_target::Config, ) -> Result { let mut this = Self::new_inner( peri, new_pin!(scl, config.scl_pf()), new_pin!(sda, config.sda_pf()), config, + target_config, ); this.reset()?; Ok(this) @@ -140,7 +211,8 @@ impl<'d, M: Mode> I2cTarget<'d, M> { _peri: Peri<'d, T>, scl: Option>, sda: Option>, - config: Config, + config: i2c::Config, + target_config: i2c_target::Config, ) -> Self { if let Some(ref scl) = scl { let pincm = pac::IOMUX.pincm(scl._pin_cm() as usize); @@ -161,17 +233,19 @@ impl<'d, M: Mode> I2cTarget<'d, M> { scl, sda, config, + target_config, _phantom: PhantomData, } } fn init(&mut self) -> Result<(), ConfigError> { let mut config = self.config; + let target_config = self.target_config; let regs = self.info.regs; config.check_config()?; // Target address must be 7-bit - if !(config.target_addr < 0x80) { + if !(target_config.target_addr < 0x80) { return Err(ConfigError::InvalidTargetAddress); } @@ -213,7 +287,7 @@ impl<'d, M: Mode> I2cTarget<'d, M> { // target address can be enabled and configured by using I2Cx.TOAR2 register. regs.target(0).toar().modify(|w| { w.set_oaren(true); - w.set_oar(config.target_addr as u16); + w.set_oar(target_config.target_addr as u16); }); self.state @@ -221,7 +295,7 @@ impl<'d, M: Mode> I2cTarget<'d, M> { .store(config.calculate_clock_source(), Ordering::Relaxed); regs.target(0).tctr().modify(|w| { - w.set_gencall(config.general_call); + w.set_gencall(target_config.general_call); w.set_tclkstretch(true); // Disable target wakeup, follow TI example. (TI note: Workaround for errata I2C_ERR_04.) w.set_twuen(false); -- cgit From 4217a264dba3a77da38897537f90e1fdfe5b9ddb Mon Sep 17 00:00:00 2001 From: crispaudio Date: Mon, 6 Oct 2025 10:06:28 +0200 Subject: mspm0-i2c-target: update examples with split config --- examples/mspm0g3507/src/bin/i2c_target.rs | 12 +++++++----- examples/mspm0l1306/src/bin/i2c_target.rs | 12 +++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/examples/mspm0g3507/src/bin/i2c_target.rs b/examples/mspm0g3507/src/bin/i2c_target.rs index ac7083a20..5dd718eaf 100644 --- a/examples/mspm0g3507/src/bin/i2c_target.rs +++ b/examples/mspm0g3507/src/bin/i2c_target.rs @@ -7,7 +7,8 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_mspm0::i2c_target::{Command, I2cTarget, ReadStatus}; +use embassy_mspm0::i2c::Config; +use embassy_mspm0::i2c_target::{Command, Config as TargetConfig, I2cTarget, ReadStatus}; use embassy_mspm0::peripherals::I2C1; use embassy_mspm0::{bind_interrupts, i2c}; use {defmt_rtt as _, panic_halt as _}; @@ -24,10 +25,11 @@ async fn main(_spawner: Spawner) -> ! { let scl = p.PB2; let sda = p.PB3; - let mut config = i2c::Config::default(); - config.target_addr = 0x48; - config.general_call = true; - let mut i2c = I2cTarget::new(instance, scl, sda, Irqs, config).unwrap(); + let config = Config::default(); + let mut target_config = TargetConfig::default(); + target_config.target_addr = 0x48; + target_config.general_call = true; + let mut i2c = I2cTarget::new(instance, scl, sda, Irqs, config, target_config).unwrap(); let mut read = [0u8; 8]; let data = [8u8; 2]; diff --git a/examples/mspm0l1306/src/bin/i2c_target.rs b/examples/mspm0l1306/src/bin/i2c_target.rs index 38d309e6b..4d147d08b 100644 --- a/examples/mspm0l1306/src/bin/i2c_target.rs +++ b/examples/mspm0l1306/src/bin/i2c_target.rs @@ -7,7 +7,8 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_mspm0::i2c_target::{Command, I2cTarget, ReadStatus}; +use embassy_mspm0::i2c::Config; +use embassy_mspm0::i2c_target::{Command, Config as TargetConfig, I2cTarget, ReadStatus}; use embassy_mspm0::peripherals::I2C0; use embassy_mspm0::{bind_interrupts, i2c}; use {defmt_rtt as _, panic_halt as _}; @@ -24,10 +25,11 @@ async fn main(_spawner: Spawner) -> ! { let scl = p.PA1; let sda = p.PA0; - let mut config = i2c::Config::default(); - config.target_addr = 0x48; - config.general_call = true; - let mut i2c = I2cTarget::new(instance, scl, sda, Irqs, config).unwrap(); + let config = Config::default(); + let mut target_config = TargetConfig::default(); + target_config.target_addr = 0x48; + target_config.general_call = true; + let mut i2c = I2cTarget::new(instance, scl, sda, Irqs, config, target_config).unwrap(); let mut read = [0u8; 8]; let data = [8u8; 2]; -- cgit From fd35ddf305fcace9faa7266529d959dc8c425a90 Mon Sep 17 00:00:00 2001 From: crispaudio Date: Fri, 10 Oct 2025 07:04:41 +0200 Subject: mspm0-i2c-target: make calculate_clock_source pub(crate) for mspm0c --- embassy-mspm0/src/i2c.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-mspm0/src/i2c.rs b/embassy-mspm0/src/i2c.rs index fb871f85d..3067f4833 100644 --- a/embassy-mspm0/src/i2c.rs +++ b/embassy-mspm0/src/i2c.rs @@ -201,7 +201,7 @@ impl Config { } #[cfg(any(mspm0c110x, mspm0c1105_c1106))] - fn calculate_clock_source(&self) -> u32 { + pub(crate) fn calculate_clock_source(&self) -> u32 { // Assume that BusClk has default value. // TODO: calculate BusClk more precisely. match self.clock_source { -- cgit From f43eb6c204b5fe8a975bd0d325b268cbbcb00f99 Mon Sep 17 00:00:00 2001 From: i509VCB Date: Mon, 13 Oct 2025 16:17:42 -0500 Subject: nxp/lpc55: eliminate match_iocon It is easier to have SealedPin provide a way to get to the Pio icon registers --- embassy-nxp/CHANGELOG.md | 1 + embassy-nxp/src/gpio/lpc55.rs | 66 ++++++++++++++---------------------------- embassy-nxp/src/usart/lpc55.rs | 38 +++++++++++------------- 3 files changed, 40 insertions(+), 65 deletions(-) diff --git a/embassy-nxp/CHANGELOG.md b/embassy-nxp/CHANGELOG.md index 0fb677cd8..295d45c2d 100644 --- a/embassy-nxp/CHANGELOG.md +++ b/embassy-nxp/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate +- LPC55: Remove internal match_iocon macro - LPC55: DMA Controller and asynchronous version of USART - Moved NXP LPC55S69 from `lpc55-pac` to `nxp-pac` - First release with changelog. diff --git a/embassy-nxp/src/gpio/lpc55.rs b/embassy-nxp/src/gpio/lpc55.rs index ac8a27d4f..6039d8ca8 100644 --- a/embassy-nxp/src/gpio/lpc55.rs +++ b/embassy-nxp/src/gpio/lpc55.rs @@ -1,7 +1,8 @@ use embassy_hal_internal::{PeripheralType, impl_peripheral}; +use crate::pac::common::{RW, Reg}; use crate::pac::iocon::vals::{PioDigimode, PioMode}; -use crate::pac::{GPIO, IOCON, SYSCON}; +use crate::pac::{GPIO, IOCON, SYSCON, iocon}; use crate::{Peri, peripherals}; pub(crate) fn init() { @@ -109,13 +110,7 @@ impl<'d> Input<'d> { /// Set the pull configuration for the pin. To disable the pull, use [Pull::None]. pub fn set_pull(&mut self, pull: Pull) { - match_iocon!(register, self.pin.pin_bank(), self.pin.pin_number(), { - register.modify(|w| match pull { - Pull::None => w.set_mode(PioMode::INACTIVE), - Pull::Up => w.set_mode(PioMode::PULL_UP), - Pull::Down => w.set_mode(PioMode::PULL_DOWN), - }); - }); + self.pin.set_pull(pull); } /// Get the current input level of the pin. @@ -193,11 +188,20 @@ impl<'d> Flex<'d> { 1 << self.pin.pin_number() } + /// Set the pull configuration for the pin. To disable the pull, use [Pull::None]. + pub fn set_pull(&mut self, pull: Pull) { + self.pin.pio().modify(|w| match pull { + Pull::None => w.set_mode(PioMode::INACTIVE), + Pull::Up => w.set_mode(PioMode::PULL_UP), + Pull::Down => w.set_mode(PioMode::PULL_DOWN), + }); + } + /// Set the pin to digital mode. This is required for using a pin as a GPIO pin. The default /// setting for pins is (usually) non-digital. fn set_as_digital(&mut self) { - match_iocon!(register, self.pin_bank(), self.pin_number(), { - register.modify(|w| w.set_digimode(PioDigimode::DIGITAL)); + self.pin.pio().modify(|w| { + w.set_digimode(PioDigimode::DIGITAL); }); } @@ -220,6 +224,14 @@ impl<'d> Flex<'d> { pub(crate) trait SealedPin: Sized { fn pin_bank(&self) -> Bank; fn pin_number(&self) -> u8; + + #[inline] + fn pio(&self) -> Reg { + match self.pin_bank() { + Bank::Bank0 => IOCON.pio0(self.pin_number() as usize), + Bank::Bank1 => IOCON.pio1(self.pin_number() as usize), + } + } } /// Interface for a Pin that can be configured by an [Input] or [Output] driver, or converted to an @@ -272,40 +284,6 @@ impl SealedPin for AnyPin { } } -/// Match the pin bank and number of a pin to the corresponding IOCON register. -/// -/// # Example -/// ``` -/// use embassy_nxp::gpio::Bank; -/// use embassy_nxp::pac_utils::{iocon_reg, match_iocon}; -/// -/// // Make pin PIO1_6 digital and set it to pull-down mode. -/// match_iocon!(register, Bank::Bank1, 6, { -/// register.modify(|w|{ -/// w.set_mode(PioMode::PULL_DOWN); -/// w.set_digimode(PioDigimode::DIGITAL); -/// -/// } -/// }); -/// ``` -macro_rules! match_iocon { - ($register:ident, $pin_bank:expr, $pin_number:expr, $action:expr) => { - match $pin_bank { - Bank::Bank0 => { - let $register = IOCON.pio0($pin_number as usize); - $action; - } - - Bank::Bank1 => { - let $register = IOCON.pio1($pin_number as usize); - $action; - } - } - }; -} - -pub(crate) use match_iocon; - macro_rules! impl_pin { ($name:ident, $bank:expr, $pin_num:expr) => { impl Pin for peripherals::$name {} diff --git a/embassy-nxp/src/usart/lpc55.rs b/embassy-nxp/src/usart/lpc55.rs index 0be5a8ce7..6cbde82a3 100644 --- a/embassy-nxp/src/usart/lpc55.rs +++ b/embassy-nxp/src/usart/lpc55.rs @@ -11,7 +11,7 @@ use embassy_sync::waitqueue::AtomicWaker; use embedded_io::{self, ErrorKind}; use crate::dma::{AnyChannel, Channel}; -use crate::gpio::{AnyPin, Bank, SealedPin, match_iocon}; +use crate::gpio::{AnyPin, SealedPin}; use crate::interrupt::Interrupt; use crate::interrupt::typelevel::{Binding, Interrupt as _}; use crate::pac::flexcomm::Flexcomm as FlexcommReg; @@ -555,29 +555,25 @@ impl<'d, M: Mode> Usart<'d, M> { fn pin_config(tx: Option>, rx: Option>) { if let Some(tx_pin) = tx { - match_iocon!(register, tx_pin.pin_bank(), tx_pin.pin_number(), { - register.modify(|w| { - w.set_func(T::tx_pin_func()); - w.set_mode(iocon::vals::PioMode::INACTIVE); - w.set_slew(iocon::vals::PioSlew::STANDARD); - w.set_invert(false); - w.set_digimode(iocon::vals::PioDigimode::DIGITAL); - w.set_od(iocon::vals::PioOd::NORMAL); - }); - }) + tx_pin.pio().modify(|w| { + w.set_func(T::tx_pin_func()); + w.set_mode(iocon::vals::PioMode::INACTIVE); + w.set_slew(iocon::vals::PioSlew::STANDARD); + w.set_invert(false); + w.set_digimode(iocon::vals::PioDigimode::DIGITAL); + w.set_od(iocon::vals::PioOd::NORMAL); + }); } if let Some(rx_pin) = rx { - match_iocon!(register, rx_pin.pin_bank(), rx_pin.pin_number(), { - register.modify(|w| { - w.set_func(T::rx_pin_func()); - w.set_mode(iocon::vals::PioMode::INACTIVE); - w.set_slew(iocon::vals::PioSlew::STANDARD); - w.set_invert(false); - w.set_digimode(iocon::vals::PioDigimode::DIGITAL); - w.set_od(iocon::vals::PioOd::NORMAL); - }); - }) + rx_pin.pio().modify(|w| { + w.set_func(T::rx_pin_func()); + w.set_mode(iocon::vals::PioMode::INACTIVE); + w.set_slew(iocon::vals::PioSlew::STANDARD); + w.set_invert(false); + w.set_digimode(iocon::vals::PioDigimode::DIGITAL); + w.set_od(iocon::vals::PioOd::NORMAL); + }); }; } -- cgit From 6fef28da94d133ce0cd36b5fb6ef2ef302c8eea0 Mon Sep 17 00:00:00 2001 From: Haobo Gu Date: Tue, 14 Oct 2025 23:39:52 +0800 Subject: feat(nrf): add rtc support for nRF54L Signed-off-by: Haobo Gu --- embassy-nrf/Cargo.toml | 2 ++ embassy-nrf/src/chips/nrf54l15_app.rs | 42 ++++++++++++++++++++++++++ embassy-nrf/src/lib.rs | 1 - examples/nrf54l15/Cargo.toml | 2 ++ examples/nrf54l15/src/bin/rtc.rs | 56 +++++++++++++++++++++++++++++++++++ 5 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 examples/nrf54l15/src/bin/rtc.rs diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index 17ffaf439..28f137d5c 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml @@ -80,6 +80,8 @@ unstable-pac = [] gpiote = [] ## Use RTC1 as the time driver for `embassy-time`, with a tick rate of 32.768khz +## +## Note: For nRF54L, it's actually RTC30 time-driver-rtc1 = ["_time-driver"] ## Enable embassy-net 802.15.4 driver diff --git a/embassy-nrf/src/chips/nrf54l15_app.rs b/embassy-nrf/src/chips/nrf54l15_app.rs index 82d30104f..901c5e7fc 100644 --- a/embassy-nrf/src/chips/nrf54l15_app.rs +++ b/embassy-nrf/src/chips/nrf54l15_app.rs @@ -249,6 +249,45 @@ embassy_hal_internal::peripherals! { P2_09, P2_10, + // RTC + RTC10, + RTC30, + + // SERIAL + SERIAL00, + SERIAL20, + SERIAL21, + SERIAL22, + SERIAL30, + + // SAADC + SAADC, + + // RADIO + RADIO, + + // TIMER + TIMER00, + TIMER10, + TIMER20, + + // PPI BRIDGE + PPIB00, + PPIB01, + PPIB10, + PPIB11, + PPIB20, + PPIB21, + PPIB22, + PPIB30, + + // GPIOTE + GPIOTE20, + GPIOTE30, + + // CRACEN + CRACEN, + #[cfg(feature = "_s")] // RRAMC RRAMC, @@ -303,6 +342,9 @@ impl_pin!(P2_08, 2, 8); impl_pin!(P2_09, 2, 9); impl_pin!(P2_10, 2, 10); +impl_rtc!(RTC10, RTC10, RTC10); +impl_rtc!(RTC30, RTC30, RTC30); + #[cfg(feature = "_ns")] impl_wdt!(WDT, WDT31, WDT31, 0); #[cfg(feature = "_s")] diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 1b7fb7e7f..705c77453 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -155,7 +155,6 @@ pub mod reset; #[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(not(any(feature = "_nrf5340-app", feature = "_nrf91")))] pub mod rng; -#[cfg(not(feature = "_nrf54l"))] // TODO pub mod rtc; #[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(not(any(feature = "_nrf51", feature = "nrf52820", feature = "_nrf5340-net")))] diff --git a/examples/nrf54l15/Cargo.toml b/examples/nrf54l15/Cargo.toml index a053dd0ec..541e79fcb 100644 --- a/examples/nrf54l15/Cargo.toml +++ b/examples/nrf54l15/Cargo.toml @@ -8,6 +8,7 @@ publish = false [dependencies] embassy-executor = { version = "0.9.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } embassy-time = { version = "0.5.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-sync = { version = "0.7.2", path = "../../embassy-sync", features = ["defmt"] } embassy-nrf = { version = "0.8.0", path = "../../embassy-nrf", features = ["defmt", "nrf54l15-app-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } defmt = "1.0.1" @@ -18,6 +19,7 @@ cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-sing cortex-m-rt = "0.7.0" embedded-storage = "0.3.1" +portable-atomic = "1" [profile.release] debug = 2 diff --git a/examples/nrf54l15/src/bin/rtc.rs b/examples/nrf54l15/src/bin/rtc.rs new file mode 100644 index 000000000..a45aaca52 --- /dev/null +++ b/examples/nrf54l15/src/bin/rtc.rs @@ -0,0 +1,56 @@ +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_nrf::interrupt; +use embassy_nrf::rtc::Rtc; +use embassy_sync::blocking_mutex::Mutex; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use portable_atomic::AtomicU64; +use {defmt_rtt as _, panic_probe as _}; + +// 64 bit counter which will never overflow. +static TICK_COUNTER: AtomicU64 = AtomicU64::new(0); +static RTC: Mutex>>> = Mutex::new(RefCell::new(None)); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + defmt::println!("nRF54L15 RTC example"); + let p = embassy_nrf::init(Default::default()); + let mut led = Output::new(p.P2_09, Level::High, OutputDrive::Standard); + // Counter resolution is 125 ms. + let mut rtc = Rtc::new(p.RTC10, (1 << 12) - 1).unwrap(); + rtc.enable_interrupt(embassy_nrf::rtc::Interrupt::Tick, true); + rtc.enable_event(embassy_nrf::rtc::Interrupt::Tick); + rtc.enable(); + RTC.lock(|r| { + let mut rtc_borrow = r.borrow_mut(); + *rtc_borrow = Some(rtc); + }); + + let mut last_counter_val = 0; + loop { + let current = TICK_COUNTER.load(core::sync::atomic::Ordering::Relaxed); + if current != last_counter_val { + led.toggle(); + last_counter_val = current; + } + } +} + +#[interrupt] +fn RTC10() { + // For 64-bit, we do not need to worry about overflowing, at least not for realistic program + // lifetimes. + TICK_COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + RTC.lock(|r| { + let mut rtc_borrow = r.borrow_mut(); + rtc_borrow + .as_mut() + .unwrap() + .reset_event(embassy_nrf::rtc::Interrupt::Tick); + }); +} -- cgit From 00aaf840352adaa31136e87efd929da20eb7afec Mon Sep 17 00:00:00 2001 From: Haobo Gu Date: Tue, 14 Oct 2025 23:41:46 +0800 Subject: doc: update changelog Signed-off-by: Haobo Gu --- embassy-nrf/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/embassy-nrf/CHANGELOG.md b/embassy-nrf/CHANGELOG.md index 0244dedab..4f17a40ed 100644 --- a/embassy-nrf/CHANGELOG.md +++ b/embassy-nrf/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate + +- added: Add basic RTC support for nRF54L - changed: apply trimming values from FICR.TRIMCNF on nrf53/54l ## 0.8.0 - 2025-09-30 -- cgit From dad58cf915c753602f6c6bcdc4db7123c31b2877 Mon Sep 17 00:00:00 2001 From: Kezi Date: Fri, 10 Oct 2025 01:36:09 +0200 Subject: return error on read when uarte buffer overrun --- embassy-nrf/src/buffered_uarte.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/embassy-nrf/src/buffered_uarte.rs b/embassy-nrf/src/buffered_uarte.rs index ec104788f..b1eb5c81a 100644 --- a/embassy-nrf/src/buffered_uarte.rs +++ b/embassy-nrf/src/buffered_uarte.rs @@ -40,6 +40,7 @@ pub(crate) struct State { rx_started_count: AtomicU8, rx_ended_count: AtomicU8, rx_ppi_ch: AtomicU8, + rx_overrun: AtomicBool, } /// UART error. @@ -47,7 +48,8 @@ pub(crate) struct State { #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { - // No errors for now + /// Buffer Overrun + Overrun, } impl State { @@ -61,6 +63,7 @@ impl State { rx_started_count: AtomicU8::new(0), rx_ended_count: AtomicU8::new(0), rx_ppi_ch: AtomicU8::new(0), + rx_overrun: AtomicBool::new(false), } } } @@ -87,8 +90,8 @@ impl interrupt::typelevel::Handler for Interrupt r.errorsrc().write_value(errs); if errs.overrun() { - #[cfg(feature = "defmt")] - defmt::warn!("BufferedUarte overrun"); + s.rx_overrun.store(true, Ordering::Release); + ss.rx_waker.wake(); } } @@ -690,6 +693,7 @@ impl<'d> BufferedUarteRx<'d> { buffered_state.rx_started_count.store(0, Ordering::Relaxed); buffered_state.rx_ended_count.store(0, Ordering::Relaxed); buffered_state.rx_started.store(false, Ordering::Relaxed); + buffered_state.rx_overrun.store(false, Ordering::Relaxed); let rx_len = rx_buffer.len().min(EASY_DMA_SIZE * 2); unsafe { buffered_state.rx_buf.init(rx_buffer.as_mut_ptr(), rx_len) }; @@ -763,6 +767,10 @@ impl<'d> BufferedUarteRx<'d> { compiler_fence(Ordering::SeqCst); //trace!("poll_read"); + if s.rx_overrun.swap(false, Ordering::Acquire) { + return Poll::Ready(Err(Error::Overrun)); + } + // Read the RXDRDY counter. timer.cc(0).capture(); let mut end = timer.cc(0).read() as usize; @@ -821,6 +829,9 @@ impl<'d> BufferedUarteRx<'d> { /// we are ready to read if there is data in the buffer fn read_ready(&self) -> Result { let state = self.buffered_state; + if state.rx_overrun.swap(false, Ordering::Acquire) { + return Err(Error::Overrun); + } Ok(!state.rx_buf.is_empty()) } } @@ -855,7 +866,9 @@ mod _embedded_io { impl embedded_io_async::Error for Error { fn kind(&self) -> embedded_io_async::ErrorKind { - match *self {} + match *self { + Error::Overrun => embedded_io_async::ErrorKind::OutOfMemory, + } } } -- cgit From ec97698085e79239b51429f59249a7f42bf04368 Mon Sep 17 00:00:00 2001 From: Maarten de Vries Date: Wed, 15 Oct 2025 11:55:40 +0200 Subject: embassy_nrf::pwm: derive more traits for public structs --- embassy-nrf/src/pwm.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index e038f44b8..1fa8f183b 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -289,6 +289,8 @@ impl<'a> Drop for SequencePwm<'a> { } /// Configuration for the PWM as a whole. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub struct Config { /// Selects up mode or up-and-down mode for the counter @@ -326,7 +328,8 @@ impl Default for Config { /// Configuration per sequence #[non_exhaustive] -#[derive(Clone)] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct SequenceConfig { /// Number of PWM periods to delay between each sequence sample pub refresh: u32, @@ -345,6 +348,8 @@ impl Default for SequenceConfig { /// A composition of a sequence buffer and its configuration. #[non_exhaustive] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Sequence<'s> { /// The words comprising the sequence. Must not exceed 32767 words. pub words: &'s [u16], @@ -496,6 +501,7 @@ impl<'d, 's> Drop for Sequencer<'d, 's> { /// How many times to run a single sequence #[derive(Debug, Eq, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum SingleSequenceMode { /// Run a single sequence n Times total. Times(u16), @@ -505,6 +511,7 @@ pub enum SingleSequenceMode { /// Which sequence to start a loop with #[derive(Debug, Eq, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum StartSequence { /// Start with Sequence 0 Zero, @@ -514,6 +521,7 @@ pub enum StartSequence { /// How many loops to run two sequences #[derive(Debug, Eq, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum SequenceMode { /// Run two sequences n loops i.e. (n * (seq0 + seq1.unwrap_or(seq0))) Loop(u16), @@ -523,6 +531,7 @@ pub enum SequenceMode { /// PWM Base clock is system clock (16MHz) divided by prescaler #[derive(Debug, Eq, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Prescaler { /// Divide by 1 Div1, @@ -544,6 +553,7 @@ pub enum Prescaler { /// How the sequence values are distributed across the channels #[derive(Debug, Eq, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum SequenceLoad { /// Provided sequence will be used across all channels Common, @@ -560,6 +570,7 @@ pub enum SequenceLoad { /// Selects up mode or up-and-down mode for the counter #[derive(Debug, Eq, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum CounterMode { /// Up counter (edge-aligned PWM duty cycle) Up, -- cgit From 5be0e0e7f9d453fc695c1a3c5b8b8148d7a4852a Mon Sep 17 00:00:00 2001 From: Maarten de Vries Date: Wed, 15 Oct 2025 11:58:46 +0200 Subject: embassy_nrf::pwm: expose duty cycle polarity for SimplePwm --- embassy-nrf/src/pwm.rs | 92 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 7 deletions(-) diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index 1fa8f183b..e47922e5a 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -17,7 +17,7 @@ use crate::{interrupt, pac}; /// to simply set a duty cycle across up to four channels. pub struct SimplePwm<'d> { r: pac::pwm::Pwm, - duty: [u16; 4], + duty: [DutyCycle; 4], ch0: Option>, ch1: Option>, ch2: Option>, @@ -578,6 +578,84 @@ pub enum CounterMode { UpAndDown, } +/// Duty value and polarity for a single channel. +/// +/// If the channel has inverted polarity, the output is set high as long as the counter is below the duty value. +#[repr(transparent)] +#[derive(Eq, PartialEq, Clone, Copy)] +pub struct DutyCycle { + /// The raw duty cycle valuea. + /// + /// This has the duty cycle in the lower 15 bits. + /// The highest bit indicates that the duty cycle has inverted polarity. + raw: u16, +} + +impl DutyCycle { + /// Make a new duty value with normal polarity. + /// + /// The value is truncated to 15 bits. + /// + /// The output is set high if the counter is at or above the duty value. + pub const fn normal(value: u16) -> Self { + let raw = value & 0x7FFF; + Self { raw } + } + + /// Make a new duty cycle with inverted polarity. + /// + /// The value is truncated to 15 bits. + /// + /// The output is set high if the counter is below the duty value. + pub const fn inverted(value: u16) -> Self { + let raw = value | 0x8000; + Self { raw } + } + + /// Adjust the polarity of the duty cycle (returns a new object). + #[must_use = "this function return a new object, it does not modify self"] + pub const fn with_inverted(self, inverted_polarity: bool) -> Self { + if inverted_polarity { + Self::inverted(self.value()) + } else { + Self::normal(self.value()) + } + } + + /// Gets the 15-bit value of the duty cycle. + pub const fn value(&self) -> u16 { + self.raw & 0x7FFF + } + + /// Checks if the duty period has inverted polarity. + /// + /// If the channel has inverted polarity, the output is set high as long as the counter is below the duty value. + pub const fn is_inverted(&self) -> bool { + self.raw & 0x8000 != 0 + } +} + +impl core::fmt::Debug for DutyCycle { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("DutyCycle") + .field("value", &self.value()) + .field("inverted", &self.is_inverted()) + .finish() + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for DutyCycle { + fn format(&self, f: defmt::Formatter) { + defmt::write!( + f, + "DutyCycle {{ value: {=u16}, inverted: {=bool} }}", + self.value(), + self.is_inverted(), + ); + } +} + impl<'d> SimplePwm<'d> { /// Create a new 1-channel PWM #[allow(unused_unsafe)] @@ -650,7 +728,7 @@ impl<'d> SimplePwm<'d> { ch1, ch2, ch3, - duty: [0; 4], + duty: [const { DutyCycle::normal(0) }; 4], }; // Disable all interrupts @@ -695,14 +773,14 @@ impl<'d> SimplePwm<'d> { self.r.enable().write(|w| w.set_enable(false)); } - /// Returns the current duty of the channel - pub fn duty(&self, channel: usize) -> u16 { + /// Returns the current duty of the channel. + pub fn duty(&self, channel: usize) -> DutyCycle { self.duty[channel] } - /// Sets duty cycle (15 bit) for a PWM channel. - pub fn set_duty(&mut self, channel: usize, duty: u16) { - self.duty[channel] = duty & 0x7FFF; + /// Sets duty cycle (15 bit) and polarity for a PWM channel. + pub fn set_duty(&mut self, channel: usize, duty: DutyCycle) { + self.duty[channel] = duty; // reload ptr in case self was moved self.r.seq(0).ptr().write_value((self.duty).as_ptr() as u32); -- cgit From b2dce7a67e0dc18c568da5758190e23778d025ef Mon Sep 17 00:00:00 2001 From: Maarten de Vries Date: Wed, 15 Oct 2025 11:59:42 +0200 Subject: embassy_nrf::pwm: allow setting all duty cycles of SimplePwm at once --- embassy-nrf/src/pwm.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index e47922e5a..7fbe9be9d 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -781,7 +781,23 @@ impl<'d> SimplePwm<'d> { /// Sets duty cycle (15 bit) and polarity for a PWM channel. pub fn set_duty(&mut self, channel: usize, duty: DutyCycle) { self.duty[channel] = duty; + self.sync_duty_cyles_to_peripheral(); + } + + /// Sets the duty cycle (15 bit) and polarity for all PWM channels. + /// + /// You can safely set the duty cycle of disabled PWM channels. + /// + /// When using this function, a single DMA transfer sets all the duty cycles. + /// If you call [`Self::set_duty()`] multiple times, + /// each duty cycle will be set by a separate DMA transfer. + pub fn set_all_duties(&mut self, duty: [DutyCycle; 4]) { + self.duty = duty; + self.sync_duty_cyles_to_peripheral(); + } + /// Transfer the duty cycles from `self` to the peripheral. + fn sync_duty_cyles_to_peripheral(&self) { // reload ptr in case self was moved self.r.seq(0).ptr().write_value((self.duty).as_ptr() as u32); -- cgit From 9c66ec1589ae2e55817e03d9e2bb8666050d054c Mon Sep 17 00:00:00 2001 From: Maarten de Vries Date: Wed, 15 Oct 2025 12:01:18 +0200 Subject: embassy_nrf::pwm: add channel idle level to config --- embassy-nrf/src/pwm.rs | 85 +++++++++++++++++++++----------------------------- 1 file changed, 35 insertions(+), 50 deletions(-) diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index 7fbe9be9d..6743674e8 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -6,7 +6,7 @@ use core::sync::atomic::{Ordering, compiler_fence}; use embassy_hal_internal::{Peri, PeripheralType}; -use crate::gpio::{AnyPin, DISCONNECTED, OutputDrive, Pin as GpioPin, PselBits, SealedPin as _, convert_drive}; +use crate::gpio::{AnyPin, DISCONNECTED, Level, OutputDrive, Pin as GpioPin, PselBits, SealedPin as _, convert_drive}; use crate::pac::gpio::vals as gpiovals; use crate::pac::pwm::vals; use crate::ppi::{Event, Task}; @@ -53,13 +53,11 @@ pub const PWM_CLK_HZ: u32 = 16_000_000; impl<'d> SequencePwm<'d> { /// Create a new 1-channel PWM - #[allow(unused_unsafe)] pub fn new_1ch(pwm: Peri<'d, T>, ch0: Peri<'d, impl GpioPin>, config: Config) -> Result { Self::new_inner(pwm, Some(ch0.into()), None, None, None, config) } /// Create a new 2-channel PWM - #[allow(unused_unsafe)] pub fn new_2ch( pwm: Peri<'d, T>, ch0: Peri<'d, impl GpioPin>, @@ -70,7 +68,6 @@ impl<'d> SequencePwm<'d> { } /// Create a new 3-channel PWM - #[allow(unused_unsafe)] pub fn new_3ch( pwm: Peri<'d, T>, ch0: Peri<'d, impl GpioPin>, @@ -82,7 +79,6 @@ impl<'d> SequencePwm<'d> { } /// Create a new 4-channel PWM - #[allow(unused_unsafe)] pub fn new_4ch( pwm: Peri<'d, T>, ch0: Peri<'d, impl GpioPin>, @@ -111,44 +107,27 @@ impl<'d> SequencePwm<'d> { ) -> Result { let r = T::regs(); - if let Some(pin) = &ch0 { - pin.set_low(); - pin.conf().write(|w| { - w.set_dir(gpiovals::Dir::OUTPUT); - w.set_input(gpiovals::Input::DISCONNECT); - convert_drive(w, config.ch0_drive); - }); - } - if let Some(pin) = &ch1 { - pin.set_low(); - pin.conf().write(|w| { - w.set_dir(gpiovals::Dir::OUTPUT); - w.set_input(gpiovals::Input::DISCONNECT); - convert_drive(w, config.ch1_drive); - }); - } - if let Some(pin) = &ch2 { - pin.set_low(); - pin.conf().write(|w| { - w.set_dir(gpiovals::Dir::OUTPUT); - w.set_input(gpiovals::Input::DISCONNECT); - convert_drive(w, config.ch2_drive); - }); - } - if let Some(pin) = &ch3 { - pin.set_low(); - pin.conf().write(|w| { - w.set_dir(gpiovals::Dir::OUTPUT); - w.set_input(gpiovals::Input::DISCONNECT); - convert_drive(w, config.ch3_drive); - }); + let channels = [ + (&ch0, config.ch0_drive, config.ch0_idle_level), + (&ch1, config.ch1_drive, config.ch1_idle_level), + (&ch2, config.ch2_drive, config.ch2_idle_level), + (&ch3, config.ch3_drive, config.ch3_idle_level), + ]; + for (i, (pin, drive, idle_level)) in channels.into_iter().enumerate() { + if let Some(pin) = pin { + match idle_level { + Level::Low => pin.set_low(), + Level::High => pin.set_high(), + } + pin.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + w.set_input(gpiovals::Input::DISCONNECT); + convert_drive(w, drive); + }); + } + r.psel().out(i).write_value(pin.psel_bits()); } - r.psel().out(0).write_value(ch0.psel_bits()); - r.psel().out(1).write_value(ch1.psel_bits()); - r.psel().out(2).write_value(ch2.psel_bits()); - r.psel().out(3).write_value(ch3.psel_bits()); - // Disable all interrupts r.intenclr().write(|w| w.0 = 0xFFFF_FFFF); r.shorts().write(|_| ()); @@ -173,13 +152,7 @@ impl<'d> SequencePwm<'d> { .write(|w| w.set_prescaler(vals::Prescaler::from_bits(config.prescaler as u8))); r.countertop().write(|w| w.set_countertop(config.max_duty)); - Ok(Self { - r: T::regs(), - ch0, - ch1, - ch2, - ch3, - }) + Ok(Self { r, ch0, ch1, ch2, ch3 }) } /// Returns reference to `Stopped` event endpoint for PPI. @@ -309,11 +282,19 @@ pub struct Config { pub ch2_drive: OutputDrive, /// Drive strength for the channel 3 line. pub ch3_drive: OutputDrive, + /// Output level for the channel 0 line when PWM if disabled. + pub ch0_idle_level: Level, + /// Output level for the channel 1 line when PWM if disabled. + pub ch1_idle_level: Level, + /// Output level for the channel 2 line when PWM if disabled. + pub ch2_idle_level: Level, + /// Output level for the channel 3 line when PWM if disabled. + pub ch3_idle_level: Level, } impl Default for Config { - fn default() -> Config { - Config { + fn default() -> Self { + Self { counter_mode: CounterMode::Up, max_duty: 1000, prescaler: Prescaler::Div16, @@ -322,6 +303,10 @@ impl Default for Config { ch1_drive: OutputDrive::Standard, ch2_drive: OutputDrive::Standard, ch3_drive: OutputDrive::Standard, + ch0_idle_level: Level::Low, + ch1_idle_level: Level::Low, + ch2_idle_level: Level::Low, + ch3_idle_level: Level::Low, } } } -- cgit From eba322b5108e16de2c57a55d96fcedee154b6303 Mon Sep 17 00:00:00 2001 From: Maarten de Vries Date: Wed, 15 Oct 2025 12:02:39 +0200 Subject: embassy_nrf::pwm: add config argument to SimplePwm constructors --- embassy-nrf/src/pwm.rs | 118 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 90 insertions(+), 28 deletions(-) diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index 6743674e8..00b3278c7 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -311,6 +311,53 @@ impl Default for Config { } } +/// Configuration for the simple PWM driver. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct SimpleConfig { + /// Selects up mode or up-and-down mode for the counter + pub counter_mode: CounterMode, + /// Top value to be compared against buffer values + pub max_duty: u16, + /// Configuration for PWM_CLK + pub prescaler: Prescaler, + /// Drive strength for the channel 0 line. + pub ch0_drive: OutputDrive, + /// Drive strength for the channel 1 line. + pub ch1_drive: OutputDrive, + /// Drive strength for the channel 2 line. + pub ch2_drive: OutputDrive, + /// Drive strength for the channel 3 line. + pub ch3_drive: OutputDrive, + /// Output level for the channel 0 line when PWM if disabled. + pub ch0_idle_level: Level, + /// Output level for the channel 1 line when PWM if disabled. + pub ch1_idle_level: Level, + /// Output level for the channel 2 line when PWM if disabled. + pub ch2_idle_level: Level, + /// Output level for the channel 3 line when PWM if disabled. + pub ch3_idle_level: Level, +} + +impl Default for SimpleConfig { + fn default() -> Self { + Self { + counter_mode: CounterMode::Up, + max_duty: 1000, + prescaler: Prescaler::Div16, + ch0_drive: OutputDrive::Standard, + ch1_drive: OutputDrive::Standard, + ch2_drive: OutputDrive::Standard, + ch3_drive: OutputDrive::Standard, + ch0_idle_level: Level::Low, + ch1_idle_level: Level::Low, + ch2_idle_level: Level::Low, + ch3_idle_level: Level::Low, + } + } +} + /// Configuration per sequence #[non_exhaustive] #[derive(Debug, Clone)] @@ -643,46 +690,48 @@ impl defmt::Format for DutyCycle { impl<'d> SimplePwm<'d> { /// Create a new 1-channel PWM - #[allow(unused_unsafe)] - pub fn new_1ch(pwm: Peri<'d, T>, ch0: Peri<'d, impl GpioPin>) -> Self { - unsafe { Self::new_inner(pwm, Some(ch0.into()), None, None, None) } + pub fn new_1ch(pwm: Peri<'d, T>, ch0: Peri<'d, impl GpioPin>, config: &SimpleConfig) -> Self { + Self::new_inner(pwm, Some(ch0.into()), None, None, None, config) } /// Create a new 2-channel PWM - #[allow(unused_unsafe)] - pub fn new_2ch(pwm: Peri<'d, T>, ch0: Peri<'d, impl GpioPin>, ch1: Peri<'d, impl GpioPin>) -> Self { - Self::new_inner(pwm, Some(ch0.into()), Some(ch1.into()), None, None) + pub fn new_2ch( + pwm: Peri<'d, T>, + ch0: Peri<'d, impl GpioPin>, + ch1: Peri<'d, impl GpioPin>, + config: &SimpleConfig, + ) -> Self { + Self::new_inner(pwm, Some(ch0.into()), Some(ch1.into()), None, None, config) } /// Create a new 3-channel PWM - #[allow(unused_unsafe)] pub fn new_3ch( pwm: Peri<'d, T>, ch0: Peri<'d, impl GpioPin>, ch1: Peri<'d, impl GpioPin>, ch2: Peri<'d, impl GpioPin>, + config: &SimpleConfig, ) -> Self { - unsafe { Self::new_inner(pwm, Some(ch0.into()), Some(ch1.into()), Some(ch2.into()), None) } + Self::new_inner(pwm, Some(ch0.into()), Some(ch1.into()), Some(ch2.into()), None, config) } /// Create a new 4-channel PWM - #[allow(unused_unsafe)] pub fn new_4ch( pwm: Peri<'d, T>, ch0: Peri<'d, impl GpioPin>, ch1: Peri<'d, impl GpioPin>, ch2: Peri<'d, impl GpioPin>, ch3: Peri<'d, impl GpioPin>, + config: &SimpleConfig, ) -> Self { - unsafe { - Self::new_inner( - pwm, - Some(ch0.into()), - Some(ch1.into()), - Some(ch2.into()), - Some(ch3.into()), - ) - } + Self::new_inner( + pwm, + Some(ch0.into()), + Some(ch1.into()), + Some(ch2.into()), + Some(ch3.into()), + config, + ) } fn new_inner( @@ -691,24 +740,33 @@ impl<'d> SimplePwm<'d> { ch1: Option>, ch2: Option>, ch3: Option>, + config: &SimpleConfig, ) -> Self { let r = T::regs(); - for (i, ch) in [&ch0, &ch1, &ch2, &ch3].into_iter().enumerate() { - if let Some(pin) = ch { - pin.set_low(); - + let channels = [ + (&ch0, config.ch0_drive, config.ch0_idle_level), + (&ch1, config.ch1_drive, config.ch1_idle_level), + (&ch2, config.ch2_drive, config.ch2_idle_level), + (&ch3, config.ch3_drive, config.ch3_idle_level), + ]; + for (i, (pin, drive, idle_level)) in channels.into_iter().enumerate() { + if let Some(pin) = pin { + match idle_level { + Level::Low => pin.set_low(), + Level::High => pin.set_high(), + } pin.conf().write(|w| { w.set_dir(gpiovals::Dir::OUTPUT); w.set_input(gpiovals::Input::DISCONNECT); - w.set_drive(gpiovals::Drive::S0S1); + convert_drive(w, drive); }); } - r.psel().out(i).write_value(ch.psel_bits()); + r.psel().out(i).write_value(pin.psel_bits()); } let pwm = Self { - r: T::regs(), + r, ch0, ch1, ch2, @@ -732,9 +790,13 @@ impl<'d> SimplePwm<'d> { w.set_load(vals::Load::INDIVIDUAL); w.set_mode(vals::Mode::REFRESH_COUNT); }); - r.mode().write(|w| w.set_updown(vals::Updown::UP)); - r.prescaler().write(|w| w.set_prescaler(vals::Prescaler::DIV_16)); - r.countertop().write(|w| w.set_countertop(1000)); + r.mode().write(|w| match config.counter_mode { + CounterMode::UpAndDown => w.set_updown(vals::Updown::UP_AND_DOWN), + CounterMode::Up => w.set_updown(vals::Updown::UP), + }); + r.prescaler() + .write(|w| w.set_prescaler(vals::Prescaler::from_bits(config.prescaler as u8))); + r.countertop().write(|w| w.set_countertop(config.max_duty)); r.loop_().write(|w| w.set_cnt(vals::LoopCnt::DISABLED)); pwm -- cgit From 3250345748cd25f209ff3426ae01bad55b2c8e9e Mon Sep 17 00:00:00 2001 From: Maarten de Vries Date: Wed, 15 Oct 2025 14:41:25 +0200 Subject: embassy_nrf: update CHANGELOG --- embassy-nrf/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/embassy-nrf/CHANGELOG.md b/embassy-nrf/CHANGELOG.md index 3df7bfd4c..8ce484646 100644 --- a/embassy-nrf/CHANGELOG.md +++ b/embassy-nrf/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - added: Add basic RTC support for nRF54L - changed: apply trimming values from FICR.TRIMCNF on nrf53/54l - changed: do not panic on BufferedUarte overrun +- added: allow configuring the idle state of GPIO pins connected to PWM channels +- changed: allow configuring the PWM peripheral in the constructor of `SimplePwm` +- changed: support setting duty cycles with inverted polarity in `SimplePwm` +- added: support setting the duty cycles of all channels at once in `SimplePwm` ## 0.8.0 - 2025-09-30 -- cgit From 369959e654d095d0e3d95597693bd64fcdb50ec5 Mon Sep 17 00:00:00 2001 From: Maarten de Vries Date: Wed, 15 Oct 2025 14:53:40 +0200 Subject: embassy_nrf: update examples --- examples/nrf52840/src/bin/i2s_monitor.rs | 9 ++++----- examples/nrf52840/src/bin/pwm.rs | 14 ++++++++------ examples/nrf52840/src/bin/pwm_servo.rs | 14 +++++++------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/examples/nrf52840/src/bin/i2s_monitor.rs b/examples/nrf52840/src/bin/i2s_monitor.rs index 66b429b09..a54659101 100644 --- a/examples/nrf52840/src/bin/i2s_monitor.rs +++ b/examples/nrf52840/src/bin/i2s_monitor.rs @@ -4,7 +4,7 @@ use defmt::{debug, error, info}; use embassy_executor::Spawner; use embassy_nrf::i2s::{self, Channels, Config, DoubleBuffering, I2S, MasterClock, Sample as _, SampleWidth}; -use embassy_nrf::pwm::{Prescaler, SimplePwm}; +use embassy_nrf::pwm::{DutyCycle, Prescaler, SimplePwm}; use embassy_nrf::{bind_interrupts, peripherals}; use {defmt_rtt as _, panic_probe as _}; @@ -34,7 +34,7 @@ async fn main(_spawner: Spawner) { I2S::new_master(p.I2S, Irqs, p.P0_25, p.P0_26, p.P0_27, master_clock, config).input(p.P0_29, buffers); // Configure the PWM to use the pins corresponding to the RGB leds - let mut pwm = SimplePwm::new_3ch(p.PWM0, p.P0_23, p.P0_22, p.P0_24); + let mut pwm = SimplePwm::new_3ch(p.PWM0, p.P0_23, p.P0_22, p.P0_24, &Default::default()); pwm.set_prescaler(Prescaler::Div1); pwm.set_max_duty(255); @@ -47,9 +47,8 @@ async fn main(_spawner: Spawner) { let rgb = rgb_from_rms(rms); debug!("RMS: {}, RGB: {:?}", rms, rgb); - for i in 0..3 { - pwm.set_duty(i, rgb[i].into()); - } + let duties = rgb.map(|byte| DutyCycle::normal(u16::from(byte))); + pwm.set_all_duties([duties[0], duties[1], duties[2], DutyCycle::normal(0)]); if let Err(err) = input_stream.receive().await { error!("{}", err); diff --git a/examples/nrf52840/src/bin/pwm.rs b/examples/nrf52840/src/bin/pwm.rs index a5bb1347a..02f9b4191 100644 --- a/examples/nrf52840/src/bin/pwm.rs +++ b/examples/nrf52840/src/bin/pwm.rs @@ -3,7 +3,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_nrf::pwm::{Prescaler, SimplePwm}; +use embassy_nrf::pwm::{DutyCycle, Prescaler, SimplePwm}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -71,7 +71,7 @@ static DUTY: [u16; 1024] = [ #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); - let mut pwm = SimplePwm::new_4ch(p.PWM0, p.P0_13, p.P0_14, p.P0_16, p.P0_15); + let mut pwm = SimplePwm::new_4ch(p.PWM0, p.P0_13, p.P0_14, p.P0_16, p.P0_15, &Default::default()); pwm.set_prescaler(Prescaler::Div1); pwm.set_max_duty(32767); info!("pwm initialized!"); @@ -79,10 +79,12 @@ async fn main(_spawner: Spawner) { let mut i = 0; loop { i += 1; - pwm.set_duty(0, DUTY[i % 1024]); - pwm.set_duty(1, DUTY[(i + 256) % 1024]); - pwm.set_duty(2, DUTY[(i + 512) % 1024]); - pwm.set_duty(3, DUTY[(i + 768) % 1024]); + pwm.set_all_duties([ + DutyCycle::normal(DUTY[i % 1024]), + DutyCycle::normal(DUTY[(i + 256) % 1024]), + DutyCycle::normal(DUTY[(i + 512) % 1024]), + DutyCycle::normal(DUTY[(i + 768) % 1024]), + ]); Timer::after_millis(3).await; } } diff --git a/examples/nrf52840/src/bin/pwm_servo.rs b/examples/nrf52840/src/bin/pwm_servo.rs index d772d2f5d..93cb984e6 100644 --- a/examples/nrf52840/src/bin/pwm_servo.rs +++ b/examples/nrf52840/src/bin/pwm_servo.rs @@ -3,14 +3,14 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_nrf::pwm::{Prescaler, SimplePwm}; +use embassy_nrf::pwm::{DutyCycle, Prescaler, SimplePwm}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); - let mut pwm = SimplePwm::new_1ch(p.PWM0, p.P0_05); + let mut pwm = SimplePwm::new_1ch(p.PWM0, p.P0_05, &Default::default()); // sg90 microervo requires 50hz or 20ms period // set_period can only set down to 125khz so we cant use it directly // Div128 is 125khz or 0.000008s or 0.008ms, 20/0.008 is 2500 is top @@ -24,23 +24,23 @@ async fn main(_spawner: Spawner) { loop { info!("45 deg"); // poor mans inverting, subtract our value from max_duty - pwm.set_duty(0, 2500 - 156); + pwm.set_duty(0, DutyCycle::normal(2500 - 156)); Timer::after_millis(5000).await; info!("90 deg"); - pwm.set_duty(0, 2500 - 187); + pwm.set_duty(0, DutyCycle::normal(2500 - 187)); Timer::after_millis(5000).await; info!("135 deg"); - pwm.set_duty(0, 2500 - 218); + pwm.set_duty(0, DutyCycle::normal(2500 - 218)); Timer::after_millis(5000).await; info!("180 deg"); - pwm.set_duty(0, 2500 - 250); + pwm.set_duty(0, DutyCycle::normal(2500 - 250)); Timer::after_millis(5000).await; info!("0 deg"); - pwm.set_duty(0, 2500 - 125); + pwm.set_duty(0, DutyCycle::normal(2500 - 125)); Timer::after_millis(5000).await; } } -- cgit From 6ba2611430e824a5d19d4d116640c8ba86c6850d Mon Sep 17 00:00:00 2001 From: Dillon Min Date: Thu, 16 Oct 2025 14:17:36 +0800 Subject: stm32: flash: fix flash erase on stm32l4xx, stm32l5xx series stm32l4xx, stm32l5xx flash layout has different pages(64/128/256) depends on BANK1_REGION.size and BANK1_REGION.erase_size. replace hardcoded 256 pages to size/erase_size. Signed-off-by: Dillon Min --- embassy-stm32/CHANGELOG.md | 1 + embassy-stm32/src/flash/l.rs | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index 7675567ff..9848daf49 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * **Fix(stm32h5):** Prevent a HardFault crash on STM32H5 devices by changing `uid()` to return `[u8; 12]` by value instead of a reference. (Fixes #2696) ## Unreleased - ReleaseDate +- fix flash erase on L4 & L5 - fix: Fixed STM32H5 builds requiring time feature - feat: Derive Clone, Copy for QSPI Config - fix: stm32/i2c in master mode (blocking): subsequent transmissions failed after a NACK was received diff --git a/embassy-stm32/src/flash/l.rs b/embassy-stm32/src/flash/l.rs index cd23cda5c..b3281f2d5 100644 --- a/embassy-stm32/src/flash/l.rs +++ b/embassy-stm32/src/flash/l.rs @@ -96,14 +96,20 @@ pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), E #[cfg(any(flash_wl, flash_wb, flash_l4, flash_l5))] { let idx = (sector.start - super::FLASH_BASE as u32) / super::BANK1_REGION.erase_size as u32; + #[cfg(any(flash_l4, flash_l5))] + let pgn = super::BANK1_REGION.size as u32 / super::BANK1_REGION.erase_size as u32; #[cfg(flash_l4)] - let (idx, bank) = if idx > 255 { (idx - 256, true) } else { (idx, false) }; + let (idx, bank) = if idx > (pgn - 1) { + (idx - pgn, true) + } else { + (idx, false) + }; #[cfg(flash_l5)] let (idx, bank) = if pac::FLASH.optr().read().dbank() { - if idx > 255 { - (idx - 256, Some(true)) + if idx > (pgn - 1) { + (idx - pgn, Some(true)) } else { (idx, Some(false)) } -- cgit From da5a563489757b9803074d6bddf0bf1b2d7f13d0 Mon Sep 17 00:00:00 2001 From: i509VCB Date: Sat, 18 Oct 2025 20:58:09 -0500 Subject: nxp/lpc55: move usart ALT pin definitions to impl_xx_pin macros --- embassy-nxp/CHANGELOG.md | 1 + embassy-nxp/src/usart/lpc55.rs | 139 ++++++++++++++++++++++++++--------------- 2 files changed, 88 insertions(+), 52 deletions(-) diff --git a/embassy-nxp/CHANGELOG.md b/embassy-nxp/CHANGELOG.md index 295d45c2d..ad8670854 100644 --- a/embassy-nxp/CHANGELOG.md +++ b/embassy-nxp/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate +- LPC55: Move ALT definitions for USART to TX/RX pin impls. - LPC55: Remove internal match_iocon macro - LPC55: DMA Controller and asynchronous version of USART - Moved NXP LPC55S69 from `lpc55-pac` to `nxp-pac` diff --git a/embassy-nxp/src/usart/lpc55.rs b/embassy-nxp/src/usart/lpc55.rs index 6cbde82a3..d54927b25 100644 --- a/embassy-nxp/src/usart/lpc55.rs +++ b/embassy-nxp/src/usart/lpc55.rs @@ -146,7 +146,8 @@ impl<'d, M: Mode> UsartTx<'d, M> { tx_dma: Peri<'d, impl Channel>, config: Config, ) -> Self { - Usart::::init::(Some(tx.into()), None, config); + let tx_func = tx.pin_func(); + Usart::::init::(Some((tx.into(), tx_func)), None, config); Self::new_inner(T::info(), Some(tx_dma.into())) } @@ -179,7 +180,8 @@ impl<'d, M: Mode> UsartTx<'d, M> { impl<'d> UsartTx<'d, Blocking> { pub fn new_blocking(_usart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, config: Config) -> Self { - Usart::::init::(Some(tx.into()), None, config); + let tx_func = tx.pin_func(); + Usart::::init::(Some((tx.into(), tx_func)), None, config); Self::new_inner(T::info(), None) } } @@ -208,7 +210,8 @@ impl<'d, M: Mode> UsartRx<'d, M> { rx_dma: Peri<'d, impl Channel>, config: Config, ) -> Self { - Usart::::init::(None, Some(rx.into()), config); + let rx_func = rx.pin_func(); + Usart::::init::(None, Some((rx.into(), rx_func)), config); Self::new_inner(T::info(), T::dma_state(), has_irq, Some(rx_dma.into())) } @@ -280,7 +283,8 @@ impl<'d, M: Mode> UsartRx<'d, M> { impl<'d> UsartRx<'d, Blocking> { pub fn new_blocking(_usart: Peri<'d, T>, rx: Peri<'d, impl RxPin>, config: Config) -> Self { - Usart::::init::(None, Some(rx.into()), config); + let rx_func = rx.pin_func(); + Usart::::init::(None, Some((rx.into(), rx_func)), config); Self::new_inner(T::info(), T::dma_state(), false, None) } } @@ -405,7 +409,10 @@ impl<'d> Usart<'d, Blocking> { rx: Peri<'d, impl RxPin>, config: Config, ) -> Self { - Self::new_inner(usart, tx.into(), rx.into(), false, None, None, config) + let tx_func = tx.pin_func(); + let rx_func = rx.pin_func(); + + Self::new_inner(usart, tx.into(), tx_func, rx.into(), rx_func, false, None, None, config) } } @@ -419,10 +426,15 @@ impl<'d> Usart<'d, Async> { rx_dma: Peri<'d, impl RxChannel>, config: Config, ) -> Self { + let tx_func = tx.pin_func(); + let rx_func = rx.pin_func(); + Self::new_inner( uart, tx.into(), + tx_func, rx.into(), + rx_func, true, Some(tx_dma.into()), Some(rx_dma.into()), @@ -435,20 +447,26 @@ impl<'d, M: Mode> Usart<'d, M> { fn new_inner( _usart: Peri<'d, T>, mut tx: Peri<'d, AnyPin>, + tx_func: PioFunc, mut rx: Peri<'d, AnyPin>, + rx_func: PioFunc, has_irq: bool, tx_dma: Option>, rx_dma: Option>, config: Config, ) -> Self { - Self::init::(Some(tx.reborrow()), Some(rx.reborrow()), config); + Self::init::(Some((tx.reborrow(), tx_func)), Some((rx.reborrow(), rx_func)), config); Self { tx: UsartTx::new_inner(T::info(), tx_dma), rx: UsartRx::new_inner(T::info(), T::dma_state(), has_irq, rx_dma), } } - fn init(tx: Option>, rx: Option>, config: Config) { + fn init( + tx: Option<(Peri<'_, AnyPin>, PioFunc)>, + rx: Option<(Peri<'_, AnyPin>, PioFunc)>, + config: Config, + ) { Self::configure_flexcomm(T::info().fc_reg, T::instance_number()); Self::configure_clock::(&config); Self::pin_config::(tx, rx); @@ -553,10 +571,10 @@ impl<'d, M: Mode> Usart<'d, M> { .modify(|w| w.set_brgval((brg_value - 1) as u16)); } - fn pin_config(tx: Option>, rx: Option>) { - if let Some(tx_pin) = tx { + fn pin_config(tx: Option<(Peri<'_, AnyPin>, PioFunc)>, rx: Option<(Peri<'_, AnyPin>, PioFunc)>) { + if let Some((tx_pin, func)) = tx { tx_pin.pio().modify(|w| { - w.set_func(T::tx_pin_func()); + w.set_func(func); w.set_mode(iocon::vals::PioMode::INACTIVE); w.set_slew(iocon::vals::PioSlew::STANDARD); w.set_invert(false); @@ -565,9 +583,9 @@ impl<'d, M: Mode> Usart<'d, M> { }); } - if let Some(rx_pin) = rx { + if let Some((rx_pin, func)) = rx { rx_pin.pio().modify(|w| { - w.set_func(T::rx_pin_func()); + w.set_func(func); w.set_mode(iocon::vals::PioMode::INACTIVE); w.set_slew(iocon::vals::PioSlew::STANDARD); w.set_invert(false); @@ -810,8 +828,6 @@ trait SealedInstance { fn info() -> &'static Info; fn dma_state() -> &'static DmaState; fn instance_number() -> usize; - fn tx_pin_func() -> PioFunc; - fn rx_pin_func() -> PioFunc; } /// UART instance. @@ -822,7 +838,7 @@ pub trait Instance: SealedInstance + PeripheralType { } macro_rules! impl_instance { - ($inst:ident, $fc:ident, $tx_pin:ident, $rx_pin:ident, $fc_num:expr) => { + ($inst:ident, $fc:ident, $fc_num:expr) => { impl $crate::usart::inner::SealedInstance for $crate::peripherals::$inst { fn info() -> &'static Info { static INFO: Info = Info { @@ -844,14 +860,6 @@ macro_rules! impl_instance { fn instance_number() -> usize { $fc_num } - #[inline] - fn tx_pin_func() -> PioFunc { - PioFunc::$tx_pin - } - #[inline] - fn rx_pin_func() -> PioFunc { - PioFunc::$rx_pin - } } impl $crate::usart::Instance for $crate::peripherals::$inst { type Interrupt = crate::interrupt::typelevel::$fc; @@ -859,45 +867,72 @@ macro_rules! impl_instance { }; } -impl_instance!(USART0, FLEXCOMM0, ALT1, ALT1, 0); -impl_instance!(USART1, FLEXCOMM1, ALT2, ALT2, 1); -impl_instance!(USART2, FLEXCOMM2, ALT1, ALT1, 2); -impl_instance!(USART3, FLEXCOMM3, ALT1, ALT1, 3); -impl_instance!(USART4, FLEXCOMM4, ALT1, ALT2, 4); -impl_instance!(USART5, FLEXCOMM5, ALT3, ALT3, 5); -impl_instance!(USART6, FLEXCOMM6, ALT2, ALT2, 6); -impl_instance!(USART7, FLEXCOMM7, ALT7, ALT7, 7); +impl_instance!(USART0, FLEXCOMM0, 0); +impl_instance!(USART1, FLEXCOMM1, 1); +impl_instance!(USART2, FLEXCOMM2, 2); +impl_instance!(USART3, FLEXCOMM3, 3); +impl_instance!(USART4, FLEXCOMM4, 4); +impl_instance!(USART5, FLEXCOMM5, 5); +impl_instance!(USART6, FLEXCOMM6, 6); +impl_instance!(USART7, FLEXCOMM7, 7); + +pub(crate) trait SealedTxPin: crate::gpio::Pin { + fn pin_func(&self) -> PioFunc; +} + +pub(crate) trait SealedRxPin: crate::gpio::Pin { + fn pin_func(&self) -> PioFunc; +} /// Trait for TX pins. -pub trait TxPin: crate::gpio::Pin {} +#[allow(private_bounds)] +pub trait TxPin: SealedTxPin + crate::gpio::Pin {} + /// Trait for RX pins. -pub trait RxPin: crate::gpio::Pin {} +#[allow(private_bounds)] +pub trait RxPin: SealedRxPin + crate::gpio::Pin {} + +macro_rules! impl_tx_pin { + ($pin:ident, $instance:ident, $func: ident) => { + impl SealedTxPin for crate::peripherals::$pin { + fn pin_func(&self) -> PioFunc { + PioFunc::$func + } + } -macro_rules! impl_pin { - ($pin:ident, $instance:ident, Tx) => { impl TxPin for crate::peripherals::$pin {} }; - ($pin:ident, $instance:ident, Rx) => { +} + +macro_rules! impl_rx_pin { + ($pin:ident, $instance:ident, $func: ident) => { + impl SealedRxPin for crate::peripherals::$pin { + fn pin_func(&self) -> PioFunc { + PioFunc::$func + } + } + impl RxPin for crate::peripherals::$pin {} }; } -impl_pin!(PIO1_6, USART0, Tx); -impl_pin!(PIO1_5, USART0, Rx); -impl_pin!(PIO1_11, USART1, Tx); -impl_pin!(PIO1_10, USART1, Rx); -impl_pin!(PIO0_27, USART2, Tx); -impl_pin!(PIO1_24, USART2, Rx); -impl_pin!(PIO0_2, USART3, Tx); -impl_pin!(PIO0_3, USART3, Rx); -impl_pin!(PIO0_16, USART4, Tx); -impl_pin!(PIO0_5, USART4, Rx); -impl_pin!(PIO0_9, USART5, Tx); -impl_pin!(PIO0_8, USART5, Rx); -impl_pin!(PIO1_16, USART6, Tx); -impl_pin!(PIO1_13, USART6, Rx); -impl_pin!(PIO0_19, USART7, Tx); -impl_pin!(PIO0_20, USART7, Rx); +impl_tx_pin!(PIO1_6, USART0, ALT1); +impl_tx_pin!(PIO1_11, USART1, ALT2); +impl_tx_pin!(PIO0_27, USART2, ALT1); +impl_tx_pin!(PIO0_2, USART3, ALT1); +impl_tx_pin!(PIO0_16, USART4, ALT1); +impl_tx_pin!(PIO0_9, USART5, ALT3); +impl_tx_pin!(PIO1_16, USART6, ALT2); +impl_tx_pin!(PIO0_19, USART7, ALT7); + +impl_rx_pin!(PIO1_5, USART0, ALT1); +impl_rx_pin!(PIO1_10, USART1, ALT2); +impl_rx_pin!(PIO1_24, USART2, ALT1); +impl_rx_pin!(PIO0_3, USART3, ALT1); +impl_rx_pin!(PIO0_5, USART4, ALT2); +impl_rx_pin!(PIO0_8, USART5, ALT3); +impl_rx_pin!(PIO1_13, USART6, ALT2); +impl_rx_pin!(PIO0_20, USART7, ALT7); /// Trait for TX DMA channels. pub trait TxChannel: crate::dma::Channel {} -- cgit From 5ed604fc0453066f0d0cf0c161823df5f4b7900f Mon Sep 17 00:00:00 2001 From: Roi Bachynskyi Date: Thu, 25 Sep 2025 15:19:45 +0300 Subject: nxp/lpc55: pwm simple --- embassy-nxp/CHANGELOG.md | 1 + embassy-nxp/Cargo.toml | 4 +- embassy-nxp/src/chips/lpc55.rs | 12 ++ embassy-nxp/src/fmt.rs | 1 - embassy-nxp/src/lib.rs | 7 +- embassy-nxp/src/pwm.rs | 5 + embassy-nxp/src/pwm/lpc55.rs | 325 +++++++++++++++++++++++++++++++++++++++ examples/lpc55s69/src/bin/pwm.rs | 18 +++ 8 files changed, 369 insertions(+), 4 deletions(-) create mode 100644 embassy-nxp/src/pwm.rs create mode 100644 embassy-nxp/src/pwm/lpc55.rs create mode 100644 examples/lpc55s69/src/bin/pwm.rs diff --git a/embassy-nxp/CHANGELOG.md b/embassy-nxp/CHANGELOG.md index ad8670854..39f5c75bd 100644 --- a/embassy-nxp/CHANGELOG.md +++ b/embassy-nxp/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate +- LPC55: PWM simple - LPC55: Move ALT definitions for USART to TX/RX pin impls. - LPC55: Remove internal match_iocon macro - LPC55: DMA Controller and asynchronous version of USART diff --git a/embassy-nxp/Cargo.toml b/embassy-nxp/Cargo.toml index 33f0f2dff..f8c63ba29 100644 --- a/embassy-nxp/Cargo.toml +++ b/embassy-nxp/Cargo.toml @@ -38,13 +38,13 @@ embassy-time-queue-utils = { version = "0.3.0", path = "../embassy-time-queue-ut embedded-io = "0.6.1" embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } ## Chip dependencies -nxp-pac = { version = "0.1.0", optional = true, git = "https://github.com/i509VCB/nxp-pac", rev = "b736e3038254d593024aaa1a5a7b1f95a5728538"} +nxp-pac = { version = "0.1.0", optional = true, git = "https://github.com/i509VCB/nxp-pac", rev = "477dfdbfd5e6c75c0730c56494b601c1b2257263"} 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 = "b736e3038254d593024aaa1a5a7b1f95a5728538", features = ["metadata"], optional = true } +nxp-pac = { version = "0.1.0", git = "https://github.com/i509VCB/nxp-pac", rev = "477dfdbfd5e6c75c0730c56494b601c1b2257263", features = ["metadata"], optional = true } proc-macro2 = "1.0.95" quote = "1.0.15" diff --git a/embassy-nxp/src/chips/lpc55.rs b/embassy-nxp/src/chips/lpc55.rs index 9f4e7269f..e9addddb6 100644 --- a/embassy-nxp/src/chips/lpc55.rs +++ b/embassy-nxp/src/chips/lpc55.rs @@ -97,6 +97,18 @@ embassy_hal_internal::peripherals! { DMA_CH21, DMA_CH22, + // Pulse-Width Modulation Outputs. + PWM_OUTPUT0, + PWM_OUTPUT1, + PWM_OUTPUT2, + PWM_OUTPUT3, + PWM_OUTPUT4, + PWM_OUTPUT5, + PWM_OUTPUT6, + PWM_OUTPUT7, + PWM_OUTPUT8, + PWM_OUTPUT9, + // Universal Synchronous/Asynchronous Receiver/Transmitter (USART) instances. USART0, USART1, diff --git a/embassy-nxp/src/fmt.rs b/embassy-nxp/src/fmt.rs index 27d41ace6..11275235e 100644 --- a/embassy-nxp/src/fmt.rs +++ b/embassy-nxp/src/fmt.rs @@ -1,5 +1,4 @@ //! Copied from embassy-rp - #![macro_use] #![allow(unused)] diff --git a/embassy-nxp/src/lib.rs b/embassy-nxp/src/lib.rs index 9576f02b1..4058881a5 100644 --- a/embassy-nxp/src/lib.rs +++ b/embassy-nxp/src/lib.rs @@ -10,6 +10,8 @@ pub mod gpio; #[cfg(feature = "lpc55-core0")] pub mod pint; #[cfg(feature = "lpc55-core0")] +pub mod pwm; +#[cfg(feature = "lpc55-core0")] pub mod usart; #[cfg(feature = "_time_driver")] @@ -154,7 +156,10 @@ pub fn init(_config: config::Config) -> Peripherals { gpio::init(); #[cfg(feature = "lpc55-core0")] - pint::init(); + { + pint::init(); + pwm::Pwm::reset(); + } #[cfg(feature = "_time_driver")] time_driver::init(); diff --git a/embassy-nxp/src/pwm.rs b/embassy-nxp/src/pwm.rs new file mode 100644 index 000000000..68980924a --- /dev/null +++ b/embassy-nxp/src/pwm.rs @@ -0,0 +1,5 @@ +//! Pulse-Width Modulation (PWM) driver. + +#[cfg_attr(feature = "lpc55-core0", path = "./pwm/lpc55.rs")] +mod inner; +pub use inner::*; diff --git a/embassy-nxp/src/pwm/lpc55.rs b/embassy-nxp/src/pwm/lpc55.rs new file mode 100644 index 000000000..197184ad6 --- /dev/null +++ b/embassy-nxp/src/pwm/lpc55.rs @@ -0,0 +1,325 @@ +use core::sync::atomic::{AtomicU8, AtomicU32, Ordering}; + +use embassy_hal_internal::{Peri, PeripheralType}; + +use crate::gpio::AnyPin; +use crate::pac::iocon::vals::{PioDigimode, PioFunc, PioMode, PioOd, PioSlew}; +use crate::pac::sct0::vals; +use crate::pac::syscon::vals::{SctRst, SctclkselSel}; +use crate::pac::{SCT0, SYSCON}; + +// Since for now the counter is shared, the TOP value has to be kept. +static TOP_VALUE: AtomicU32 = AtomicU32::new(0); +// To check if there are still active instances. +static REF_COUNT: AtomicU8 = AtomicU8::new(0); + +/// The configuration of a PWM output. +/// Note the period in clock cycles of an output can be computed as: +/// `(top + 1) * (phase_correct ? 1 : 2) * divider * prescale_factor` +/// By default, the clock used is 96 MHz. +#[non_exhaustive] +#[derive(Clone)] +pub struct Config { + /// Inverts the PWM output signal. + pub invert: bool, + /// Enables phase-correct mode for PWM operation. + /// In phase-correct mode, the PWM signal is generated in such a way that + /// the pulse is always centered regardless of the duty cycle. + /// The output frequency is halved when phase-correct mode is enabled. + pub phase_correct: bool, + /// Enables the PWM output, allowing it to generate an output. + pub enable: bool, + /// A SYSCON clock divider allows precise control over + /// the PWM output frequency by gating the PWM counter increment. + /// A higher value will result in a slower output frequency. + /// The clock is divided by `divider + 1`. + pub divider: u8, + /// Specifies the factor by which the SCT clock is prescaled to produce the unified + /// counter clock. The counter clock is clocked at the rate of the SCT clock divided by + /// `PRE + 1`. + pub prescale_factor: u8, + /// The output goes high when `compare` is higher than the + /// counter. A compare of 0 will produce an always low output, while a + /// compare of `top` will produce an always high output. + pub compare: u32, + /// The point at which the counter resets, representing the maximum possible + /// period. The counter will either wrap to 0 or reverse depending on the + /// setting of `phase_correct`. + pub top: u32, +} + +impl Config { + pub fn new(compare: u32, top: u32) -> Self { + Self { + invert: false, + phase_correct: false, + enable: true, + divider: 255, + prescale_factor: 255, + compare, + top, + } + } +} + +/// PWM driver. +pub struct Pwm<'d> { + _pin: Peri<'d, AnyPin>, + output: usize, +} + +impl<'d> Pwm<'d> { + pub(crate) fn reset() { + // Reset SCTimer => Reset counter and halt it. + // It should be done only once during the initialization of the board. + SYSCON.presetctrl1().modify(|w| w.set_sct_rst(SctRst::ASSERTED)); + SYSCON.presetctrl1().modify(|w| w.set_sct_rst(SctRst::RELEASED)); + } + fn new_inner(output: usize, channel: Peri<'d, impl OutputChannelPin>, config: Config) -> Self { + // Enable clocks (Syscon is enabled by default) + critical_section::with(|_cs| { + if !SYSCON.ahbclkctrl0().read().iocon() { + SYSCON.ahbclkctrl0().modify(|w| w.set_iocon(true)); + } + if !SYSCON.ahbclkctrl1().read().sct() { + SYSCON.ahbclkctrl1().modify(|w| w.set_sct(true)); + } + }); + + // Choose the clock for PWM. + SYSCON.sctclksel().modify(|w| w.set_sel(SctclkselSel::ENUM_0X3)); + // For now, 96 MHz. + + // IOCON Setup + channel.pio().modify(|w| { + w.set_func(channel.pin_func()); + w.set_digimode(PioDigimode::DIGITAL); + w.set_slew(PioSlew::STANDARD); + w.set_mode(PioMode::INACTIVE); + w.set_od(PioOd::NORMAL); + }); + + Self::configure(output, &config); + REF_COUNT.fetch_add(1, Ordering::Relaxed); + Self { + _pin: channel.into(), + output, + } + } + + /// Create PWM driver with a single 'a' pin as output. + #[inline] + pub fn new_output( + output: Peri<'d, T>, + channel: Peri<'d, impl OutputChannelPin>, + config: Config, + ) -> Self { + Self::new_inner(output.number(), channel, config) + } + + /// Set the PWM config. + pub fn set_config(&mut self, config: &Config) { + Self::configure(self.output, config); + } + + fn configure(output_number: usize, config: &Config) { + // Stop and reset the counter + SCT0.ctrl().modify(|w| { + if config.phase_correct { + w.set_bidir_l(vals::Bidir::UP_DOWN); + } else { + w.set_bidir_l(vals::Bidir::UP); + } + w.set_halt_l(true); // halt the counter to make new changes + w.set_clrctr_l(true); // clear the counter + }); + // Divides clock by 1-255 + SYSCON.sctclkdiv().modify(|w| w.set_div(config.divider)); + + SCT0.config().modify(|w| { + w.set_unify(vals::Unify::UNIFIED_COUNTER); + w.set_clkmode(vals::Clkmode::SYSTEM_CLOCK_MODE); + w.set_noreload_l(true); + w.set_autolimit_l(true); + }); + + // Before setting the match registers, we have to make sure that `compare` is lower or equal to `top`, + // otherwise the counter will not reach the match and, therefore, no events will happen. + assert!(config.compare <= config.top); + + if TOP_VALUE.load(Ordering::Relaxed) == 0 { + // Match 0 will reset the timer using TOP value + SCT0.match_(0).modify(|w| { + w.set_matchn_l((config.top & 0xFFFF) as u16); + w.set_matchn_h((config.top >> 16) as u16); + }); + } else { + panic!("The top value cannot be changed after the initialization."); + } + // The actual matches that are used for event logic + SCT0.match_(output_number + 1).modify(|w| { + w.set_matchn_l((config.compare & 0xFFFF) as u16); + w.set_matchn_h((config.compare >> 16) as u16); + }); + + SCT0.match_(15).modify(|w| { + w.set_matchn_l(0); + w.set_matchn_h(0); + }); + + // Event configuration + critical_section::with(|_cs| { + // If it is already set, don't change + if SCT0.ev(0).ev_ctrl().read().matchsel() != 15 { + SCT0.ev(0).ev_ctrl().modify(|w| { + w.set_matchsel(15); + w.set_combmode(vals::Combmode::MATCH); + // STATE + statev, where STATE is a on-board variable. + w.set_stateld(vals::Stateld::ADD); + w.set_statev(0); + }); + } + }); + SCT0.ev(output_number + 1).ev_ctrl().modify(|w| { + w.set_matchsel((output_number + 1) as u8); + w.set_combmode(vals::Combmode::MATCH); + w.set_stateld(vals::Stateld::ADD); + // STATE + statev, where STATE is a on-board variable. + w.set_statev(0); + }); + + // Assign events to states + SCT0.ev(0).ev_state().modify(|w| w.set_statemskn(1 << 0)); + SCT0.ev(output_number + 1) + .ev_state() + .modify(|w| w.set_statemskn(1 << 0)); + // TODO(frihetselsker): optimize nxp-pac so that `set_clr` and `set_set` are turned into a bit array. + if config.invert { + // Low when event 0 is active + SCT0.out(output_number).out_clr().modify(|w| w.set_clr(1 << 0)); + // High when event `output_number + 1` is active + SCT0.out(output_number) + .out_set() + .modify(|w| w.set_set(1 << (output_number + 1))); + } else { + // High when event 0 is active + SCT0.out(output_number).out_set().modify(|w| w.set_set(1 << 0)); + // Low when event `output_number + 1` is active + SCT0.out(output_number) + .out_clr() + .modify(|w| w.set_clr(1 << (output_number + 1))); + } + + if config.phase_correct { + // Take into account the set matches and reverse their actions while counting back. + SCT0.outputdirctrl() + .modify(|w| w.set_setclr(output_number, vals::Setclr::L_REVERSED)); + } + + // State 0 by default + SCT0.state().modify(|w| w.set_state_l(0)); + // Remove halt and start the actual counter + SCT0.ctrl().modify(|w| { + w.set_halt_l(!config.enable); + }); + } + + /// Read PWM counter. + #[inline] + pub fn counter(&self) -> u32 { + SCT0.count().read().0 + } +} + +impl<'d> Drop for Pwm<'d> { + fn drop(&mut self) { + REF_COUNT.fetch_sub(1, Ordering::AcqRel); + if REF_COUNT.load(Ordering::Acquire) == 0 { + TOP_VALUE.store(0, Ordering::Release); + } + } +} + +trait SealedOutput { + /// Output number. + fn number(&self) -> usize; +} + +/// PWM Output. +#[allow(private_bounds)] +pub trait Output: PeripheralType + SealedOutput {} + +macro_rules! output { + ($name:ident, $num:expr) => { + impl SealedOutput for crate::peripherals::$name { + fn number(&self) -> usize { + $num + } + } + impl Output for crate::peripherals::$name {} + }; +} + +output!(PWM_OUTPUT0, 0); +output!(PWM_OUTPUT1, 1); +output!(PWM_OUTPUT2, 2); +output!(PWM_OUTPUT3, 3); +output!(PWM_OUTPUT4, 4); +output!(PWM_OUTPUT5, 5); +output!(PWM_OUTPUT6, 6); +output!(PWM_OUTPUT7, 7); +output!(PWM_OUTPUT8, 8); +output!(PWM_OUTPUT9, 9); + +/// PWM Output Channel. +pub trait OutputChannelPin: crate::gpio::Pin { + fn pin_func(&self) -> PioFunc; +} + +macro_rules! impl_pin { + ($pin:ident, $output:ident, $func:ident) => { + impl crate::pwm::inner::OutputChannelPin for crate::peripherals::$pin { + fn pin_func(&self) -> PioFunc { + PioFunc::$func + } + } + }; +} + +impl_pin!(PIO0_2, PWM_OUTPUT0, ALT3); +impl_pin!(PIO0_17, PWM_OUTPUT0, ALT4); +impl_pin!(PIO1_4, PWM_OUTPUT0, ALT4); +impl_pin!(PIO1_23, PWM_OUTPUT0, ALT2); + +impl_pin!(PIO0_3, PWM_OUTPUT1, ALT3); +impl_pin!(PIO0_18, PWM_OUTPUT1, ALT4); +impl_pin!(PIO1_8, PWM_OUTPUT1, ALT4); +impl_pin!(PIO1_24, PWM_OUTPUT1, ALT2); + +impl_pin!(PIO0_10, PWM_OUTPUT2, ALT5); +impl_pin!(PIO0_15, PWM_OUTPUT2, ALT4); +impl_pin!(PIO0_19, PWM_OUTPUT2, ALT4); +impl_pin!(PIO1_9, PWM_OUTPUT2, ALT4); +impl_pin!(PIO1_25, PWM_OUTPUT2, ALT2); + +impl_pin!(PIO0_22, PWM_OUTPUT3, ALT4); +impl_pin!(PIO0_31, PWM_OUTPUT3, ALT4); +impl_pin!(PIO1_10, PWM_OUTPUT3, ALT4); +impl_pin!(PIO1_26, PWM_OUTPUT3, ALT2); + +impl_pin!(PIO0_23, PWM_OUTPUT4, ALT4); +impl_pin!(PIO1_3, PWM_OUTPUT4, ALT4); +impl_pin!(PIO1_17, PWM_OUTPUT4, ALT4); + +impl_pin!(PIO0_26, PWM_OUTPUT5, ALT4); +impl_pin!(PIO1_18, PWM_OUTPUT5, ALT4); + +impl_pin!(PIO0_27, PWM_OUTPUT6, ALT4); +impl_pin!(PIO1_31, PWM_OUTPUT6, ALT4); + +impl_pin!(PIO0_28, PWM_OUTPUT7, ALT4); +impl_pin!(PIO1_19, PWM_OUTPUT7, ALT2); + +impl_pin!(PIO0_29, PWM_OUTPUT8, ALT4); + +impl_pin!(PIO0_30, PWM_OUTPUT9, ALT4); diff --git a/examples/lpc55s69/src/bin/pwm.rs b/examples/lpc55s69/src/bin/pwm.rs new file mode 100644 index 000000000..93b898b9d --- /dev/null +++ b/examples/lpc55s69/src/bin/pwm.rs @@ -0,0 +1,18 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nxp::pwm::{Config, Pwm}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nxp::init(Default::default()); + let pwm = Pwm::new_output(p.PWM_OUTPUT1, p.PIO0_18, Config::new(1_000_000_000, 2_000_000_000)); + loop { + info!("Counter: {}", pwm.counter()); + Timer::after_millis(50).await; + } +} -- cgit From 836d491a37094439f8bf3da3ceae075c8dbc221c Mon Sep 17 00:00:00 2001 From: Maarten de Vries Date: Thu, 23 Oct 2025 16:43:55 +0200 Subject: embassy-nrf: allow direct access to the `gpiote::InputChannel` input pin --- embassy-nrf/CHANGELOG.md | 1 + embassy-nrf/src/gpiote.rs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/embassy-nrf/CHANGELOG.md b/embassy-nrf/CHANGELOG.md index 3df7bfd4c..94fc58ca2 100644 --- a/embassy-nrf/CHANGELOG.md +++ b/embassy-nrf/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - added: Add basic RTC support for nRF54L - changed: apply trimming values from FICR.TRIMCNF on nrf53/54l - changed: do not panic on BufferedUarte overrun +- added: allow direct access to the input pin of `gpiote::InputChannel` ## 0.8.0 - 2025-09-30 diff --git a/embassy-nrf/src/gpiote.rs b/embassy-nrf/src/gpiote.rs index a490d5b60..61162b87f 100644 --- a/embassy-nrf/src/gpiote.rs +++ b/embassy-nrf/src/gpiote.rs @@ -259,6 +259,11 @@ impl<'d> InputChannel<'d> { .await; } + /// Get the associated input pin. + pub fn pin(&self) -> &Input<'_> { + &self.pin + } + /// Returns the IN event, for use with PPI. pub fn event_in(&self) -> Event<'d> { let g = regs(); -- cgit From 75ad0684f28ccb6b2a5b4772894e8c6a4f327299 Mon Sep 17 00:00:00 2001 From: Matthew Tran <0e4ef622@gmail.com> Date: Wed, 22 Oct 2025 18:59:34 -0500 Subject: nrf: use DETECTMODE_SEC in GPIOTE in secure mode DETECTMODE only applies to pins assigned to non-secure. --- embassy-nrf/CHANGELOG.md | 1 + embassy-nrf/src/gpiote.rs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/embassy-nrf/CHANGELOG.md b/embassy-nrf/CHANGELOG.md index 94fc58ca2..0280e2730 100644 --- a/embassy-nrf/CHANGELOG.md +++ b/embassy-nrf/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - changed: apply trimming values from FICR.TRIMCNF on nrf53/54l - changed: do not panic on BufferedUarte overrun - added: allow direct access to the input pin of `gpiote::InputChannel` +- bugfix: use DETECTMODE_SEC in GPIOTE in secure mode ## 0.8.0 - 2025-09-30 diff --git a/embassy-nrf/src/gpiote.rs b/embassy-nrf/src/gpiote.rs index 61162b87f..3658657c0 100644 --- a/embassy-nrf/src/gpiote.rs +++ b/embassy-nrf/src/gpiote.rs @@ -77,6 +77,9 @@ pub(crate) fn init(irq_prio: crate::interrupt::Priority) { for &p in ports { // Enable latched detection + #[cfg(feature = "_s")] + p.detectmode_sec().write(|w| w.set_detectmode(Detectmode::LDETECT)); + #[cfg(not(feature = "_s"))] p.detectmode().write(|w| w.set_detectmode(Detectmode::LDETECT)); // Clear latch p.latch().write(|w| w.0 = 0xFFFFFFFF) -- cgit From 724edcaf70494316507af2d3bc7cdbcb2b3be06d Mon Sep 17 00:00:00 2001 From: Nicolas Mattia Date: Sun, 26 Oct 2025 11:37:05 +0100 Subject: rp: fix typo in Input interrupt comment --- embassy-rp/CHANGELOG.md | 1 + embassy-rp/src/gpio.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/embassy-rp/CHANGELOG.md b/embassy-rp/CHANGELOG.md index e932bcaa3..a99d04aa4 100644 --- a/embassy-rp/CHANGELOG.md +++ b/embassy-rp/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate +- Fix typo in interrupt comment - Add PIO SPI - Add PIO I2S input - Add PIO onewire parasite power strong pullup diff --git a/embassy-rp/src/gpio.rs b/embassy-rp/src/gpio.rs index c15e0e41b..154fc1585 100644 --- a/embassy-rp/src/gpio.rs +++ b/embassy-rp/src/gpio.rs @@ -300,7 +300,7 @@ impl<'d> InputFuture<'d> { // Each INTR register is divided into 8 groups, one group for each // pin, and each group consists of LEVEL_LOW, LEVEL_HIGH, EDGE_LOW, - // and EGDE_HIGH. + // and EDGE_HIGH. pin.int_proc() .inte((pin.pin() / 8) as usize) .write_set(|w| match level { -- cgit From e349ebb72c706c99dde34ba7b624aa9d1c25af39 Mon Sep 17 00:00:00 2001 From: usedhondacivic Date: Sun, 26 Oct 2025 18:00:51 -0700 Subject: cyw43-pio: core clock speed based pio program selection --- cyw43-pio/src/lib.rs | 109 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 92 insertions(+), 17 deletions(-) diff --git a/cyw43-pio/src/lib.rs b/cyw43-pio/src/lib.rs index 41ac6816d..51d8ec3ae 100644 --- a/cyw43-pio/src/lib.rs +++ b/cyw43-pio/src/lib.rs @@ -7,11 +7,13 @@ use core::slice; use cyw43::SpiBusCyw43; use embassy_rp::Peri; +use embassy_rp::clocks::clk_sys_freq; use embassy_rp::dma::Channel; use embassy_rp::gpio::{Drive, Level, Output, Pull, SlewRate}; use embassy_rp::pio::program::pio_asm; use embassy_rp::pio::{Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine}; use fixed::FixedU32; +use fixed::traits::LosslessTryInto; use fixed::types::extra::U8; /// SPI comms driven by PIO. @@ -23,23 +25,24 @@ pub struct PioSpi<'d, PIO: Instance, const SM: usize, DMA: Channel> { wrap_target: u8, } -/// The default clock divider that works for Pico 1 and 2 W. As well as the RM2 on rp2040 devices. -/// same speed as pico-sdk, 62.5Mhz -/// This is actually the fastest we can go without overclocking. -/// According to data sheet, the theoretical maximum is 100Mhz Pio => 50Mhz SPI Freq. -/// However, the PIO uses a fractional divider, which works by introducing jitter when -/// the divider is not an integer. It does some clocks at 125mhz and others at 62.5mhz -/// so that it averages out to the desired frequency of 100mhz. The 125mhz clock cycles -/// violate the maximum from the data sheet. +/// Clock divider used for most applications +/// With default core clock configuration: +/// RP2350: 150Mhz / 2 = 75Mhz pio clock -> 37.5Mhz GSPI clock +/// RP2040: 133Mhz / 2 = 66.5Mhz pio clock -> 33.25Mhz GSPI clock pub const DEFAULT_CLOCK_DIVIDER: FixedU32 = FixedU32::from_bits(0x0200); -/// The overclock clock divider for the Pico 1 W. Does not work on any known RM2 devices. -/// 125mhz Pio => 62.5Mhz SPI Freq. 25% higher than theoretical maximum according to -/// data sheet, but seems to work fine. +/// Clock divider used to overclock the cyw43 +/// With default core clock configuration: +/// RP2350: 150Mhz / 1 = 150Mhz pio clock -> 75Mhz GSPI clock (50% greater that manufacturer +/// recommended 50Mhz) +/// RP2040: 133Mhz / 1 = 133Mhz pio clock -> 66.5Mhz GSPI clock (33% greater that manufacturer +/// recommended 50Mhz) pub const OVERCLOCK_CLOCK_DIVIDER: FixedU32 = FixedU32::from_bits(0x0100); -/// The clock divider for the RM2 module. Found to be needed for the Pimoroni Pico Plus 2 W, -/// Pico Plus 2 Non w with the RM2 breakout module, and the Pico 2 with the RM2 breakout module. +/// Clock divider used with the RM2 +/// With default core clock configuration: +/// RP2350: 150Mhz / 3 = 50Mhz pio clock -> 25Mhz GSPI clock +/// RP2040: 133Mhz / 3 = 44.33Mhz pio clock -> 22.16Mhz GSPI clock pub const RM2_CLOCK_DIVIDER: FixedU32 = FixedU32::from_bits(0x0300); impl<'d, PIO, const SM: usize, DMA> PioSpi<'d, PIO, SM, DMA> @@ -58,7 +61,40 @@ where clk: Peri<'d, impl PioPin>, dma: Peri<'d, DMA>, ) -> Self { - let loaded_program = if clock_divider < DEFAULT_CLOCK_DIVIDER { + let effective_pio_frequency = (clk_sys_freq() as f32 / clock_divider.to_num::()) as u32; + + #[cfg(feature = "defmt")] + defmt::trace!("Effective pio frequency: {}", effective_pio_frequency); + + // Non-integer pio clock dividers are achieved by introducing clock jitter resulting in a + // combination of long and short cycles. The long and short cycles average to achieve the + // requested clock speed. + // This can be a problem for peripherals that expect a consistent clock / have a clock + // speed upper bound that is violated by the short cycles. The cyw43 seems to handle the + // jitter well, but we emit a warning to recommend an integer divider anyway. + if let None = clock_divider.lossless_try_into() { + #[cfg(feature = "defmt")] + defmt::trace!( + "Configured clock divider is not a whole number. Some clock cycles may violate the maximum recommended GSPI speed. Use at your own risk." + ); + } + + // Different pio programs must be used for different pio clock speeds. + // The programs used below are based on the pico SDK: https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/pico_cyw43_driver/cyw43_bus_pio_spi.pio + // The clock speed cutoff for each program has been determined experimentally: + // > 100Mhz -> Overclock program + // [75Mhz, 100Mhz] -> High speed program + // [0, 75Mhz) -> Low speed program + let loaded_program = if effective_pio_frequency > 100_000_000 { + // Any frequency > 100Mhz is overclocking the chip (manufacturer recommends max 50Mhz GSPI + // clock) + // Example: + // * RP2040 @ 133Mhz (stock) with OVERCLOCK_CLOCK_DIVIDER (133MHz) + #[cfg(feature = "defmt")] + defmt::trace!( + "Configured clock divider results in a GSPI frequency greater than the manufacturer recommendation (50Mhz). Use at your own risk." + ); + let overclock_program = pio_asm!( ".side_set 1" @@ -69,7 +105,7 @@ where "jmp x-- lp side 1" // switch directions "set pindirs, 0 side 0" - "nop side 1" // necessary for clkdiv=1. + "nop side 1" "nop side 0" // read in y-1 bits "lp2:" @@ -83,8 +119,47 @@ where ".wrap" ); common.load_program(&overclock_program.program) + } else if effective_pio_frequency >= 75_000_000 { + // Experimentally determined cutoff. + // Notably includes the stock RP2350 configured with clk_div of 2 (150Mhz base clock / 2 = 75Mhz) + // but does not include stock RP2040 configured with clk_div of 2 (133Mhz base clock / 2 = 66.5Mhz) + // Example: + // * RP2350 @ 150Mhz (stock) with DEFAULT_CLOCK_DIVIDER (75Mhz) + // * RP2XXX @ 200Mhz with DEFAULT_CLOCK_DIVIDER (100Mhz) + #[cfg(feature = "defmt")] + defmt::trace!("Using high speed pio program."); + let high_speed_program = pio_asm!( + ".side_set 1" + + ".wrap_target" + // write out x-1 bits + "lp:" + "out pins, 1 side 0" + "jmp x-- lp side 1" + // switch directions + "set pindirs, 0 side 0" + "nop side 1" + // read in y-1 bits + "lp2:" + "in pins, 1 side 0" + "jmp y-- lp2 side 1" + + // wait for event and irq host + "wait 1 pin 0 side 0" + "irq 0 side 0" + + ".wrap" + ); + common.load_program(&high_speed_program.program) } else { - let default_program = pio_asm!( + // Low speed + // Examples: + // * RP2040 @ 133Mhz (stock) with DEFAULT_CLOCK_DIVIDER (66.5Mhz) + // * RP2040 @ 133Mhz (stock) with RM2_CLOCK_DIVIDER (44.3Mhz) + // * RP2350 @ 150Mhz (stock) with RM2_CLOCK_DIVIDER (50Mhz) + #[cfg(feature = "defmt")] + defmt::trace!("Using low speed pio program."); + let low_speed_program = pio_asm!( ".side_set 1" ".wrap_target" @@ -106,7 +181,7 @@ where ".wrap" ); - common.load_program(&default_program.program) + common.load_program(&low_speed_program.program) }; let mut pin_io: embassy_rp::pio::Pin = common.make_pio_pin(dio); -- cgit From 4d869e69730b997ff6d7c162cf1f5f3ffa868caa Mon Sep 17 00:00:00 2001 From: usedhondacivic Date: Sun, 26 Oct 2025 19:59:35 -0700 Subject: Update CHANGELOG --- cyw43-pio/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cyw43-pio/CHANGELOG.md b/cyw43-pio/CHANGELOG.md index ec38989e3..584df6689 100644 --- a/cyw43-pio/CHANGELOG.md +++ b/cyw43-pio/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate - +- Select pio program based on core clock speed #4792 ## 0.8.0 - 2025-08-28 - Bump cyw43 version -- cgit From a863aadc643d84707815e5c0f4564f2195809fec Mon Sep 17 00:00:00 2001 From: usedhondacivic Date: Sun, 26 Oct 2025 20:03:57 -0700 Subject: Fix build --- cyw43-pio/CHANGELOG.md | 2 ++ cyw43-pio/src/lib.rs | 7 +++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cyw43-pio/CHANGELOG.md b/cyw43-pio/CHANGELOG.md index 584df6689..c2d18919c 100644 --- a/cyw43-pio/CHANGELOG.md +++ b/cyw43-pio/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate + - Select pio program based on core clock speed #4792 + ## 0.8.0 - 2025-08-28 - Bump cyw43 version diff --git a/cyw43-pio/src/lib.rs b/cyw43-pio/src/lib.rs index 51d8ec3ae..c8715e662 100644 --- a/cyw43-pio/src/lib.rs +++ b/cyw43-pio/src/lib.rs @@ -13,7 +13,6 @@ use embassy_rp::gpio::{Drive, Level, Output, Pull, SlewRate}; use embassy_rp::pio::program::pio_asm; use embassy_rp::pio::{Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine}; use fixed::FixedU32; -use fixed::traits::LosslessTryInto; use fixed::types::extra::U8; /// SPI comms driven by PIO. @@ -64,7 +63,7 @@ where let effective_pio_frequency = (clk_sys_freq() as f32 / clock_divider.to_num::()) as u32; #[cfg(feature = "defmt")] - defmt::trace!("Effective pio frequency: {}", effective_pio_frequency); + defmt::trace!("Effective pio frequency: {}Hz", effective_pio_frequency); // Non-integer pio clock dividers are achieved by introducing clock jitter resulting in a // combination of long and short cycles. The long and short cycles average to achieve the @@ -72,14 +71,14 @@ where // This can be a problem for peripherals that expect a consistent clock / have a clock // speed upper bound that is violated by the short cycles. The cyw43 seems to handle the // jitter well, but we emit a warning to recommend an integer divider anyway. - if let None = clock_divider.lossless_try_into() { + if clock_divider.frac() != FixedU32::::ZERO { #[cfg(feature = "defmt")] defmt::trace!( "Configured clock divider is not a whole number. Some clock cycles may violate the maximum recommended GSPI speed. Use at your own risk." ); } - // Different pio programs must be used for different pio clock speeds. + // Different pio programs must be used for different pio clock speeds. // The programs used below are based on the pico SDK: https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/pico_cyw43_driver/cyw43_bus_pio_spi.pio // The clock speed cutoff for each program has been determined experimentally: // > 100Mhz -> Overclock program -- cgit From e86c2b3b1c41c710acd34f6c85243c8bd5b5150d Mon Sep 17 00:00:00 2001 From: Gordon Tyler Date: Mon, 27 Oct 2025 10:41:33 -0400 Subject: mspm0: group irq handlers must check for NO_INTR (#4785) In the case of spurious interrupts, the interrupt group's STAT register may be set to NO_INTR, which must be checked before attempting to calculate the interrupt index from the STAT value. --- embassy-mspm0/build.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/embassy-mspm0/build.rs b/embassy-mspm0/build.rs index 1d118ad66..6264c9177 100644 --- a/embassy-mspm0/build.rs +++ b/embassy-mspm0/build.rs @@ -194,8 +194,15 @@ fn generate_groups() -> TokenStream { use crate::pac::#group_enum; let group = crate::pac::CPUSS.int_group(#group_number); - // MUST subtract by 1 since 0 is NO_INTR - let iidx = group.iidx().read().stat().to_bits() - 1; + let stat = group.iidx().read().stat(); + + // check for spurious interrupts + if stat == crate::pac::cpuss::vals::Iidx::NO_INTR { + return; + } + + // MUST subtract by 1 because Iidx::INT0=1 + let iidx = stat.to_bits() - 1; let Ok(group) = #group_enum::try_from(iidx as u8) else { return; -- cgit From 3923f881c63a483c1593cc345079581ffcce5ff1 Mon Sep 17 00:00:00 2001 From: Gordon Tyler Date: Mon, 27 Oct 2025 10:45:47 -0400 Subject: Update changelog --- embassy-mspm0/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/embassy-mspm0/CHANGELOG.md b/embassy-mspm0/CHANGELOG.md index fcb0f9dbd..d9910a7ab 100644 --- a/embassy-mspm0/CHANGELOG.md +++ b/embassy-mspm0/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate - + - feat: Add I2C Controller (blocking & async) + examples for mspm0l1306, mspm0g3507 (tested MCUs) (#4435) - fix gpio interrupt not being set for mspm0l110x - feat: Add window watchdog implementation based on WWDT0, WWDT1 peripherals (#4574) @@ -17,3 +17,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - fix: add MSPM0C1106 to build test matrix - feat: add MSPM0H3216 support - feat: Add i2c target implementation (#4605) +- fix: group irq handlers must check for NO_INTR (#4785) -- cgit From 7ef9a6453a0a2a286741d47fcb99170d802f7d7d Mon Sep 17 00:00:00 2001 From: Rob Wells Date: Mon, 27 Oct 2025 15:23:54 +0000 Subject: embassy-rp: doc comment spelling pass All changes but one are to documentation comments, and one to an ordinary comment. --- embassy-rp/CHANGELOG.md | 2 +- embassy-rp/src/block.rs | 4 ++-- embassy-rp/src/clocks.rs | 2 +- embassy-rp/src/i2c_slave.rs | 4 ++-- embassy-rp/src/multicore.rs | 2 +- embassy-rp/src/pio/mod.rs | 2 +- embassy-rp/src/pio_programs/i2s.rs | 18 +++++++++--------- embassy-rp/src/pio_programs/pwm.rs | 2 +- embassy-rp/src/pio_programs/spi.rs | 4 ++-- embassy-rp/src/pio_programs/uart.rs | 2 +- embassy-rp/src/rom_data/rp2040.rs | 2 +- embassy-rp/src/rtc/mod.rs | 2 +- embassy-rp/src/spi.rs | 2 +- embassy-rp/src/uart/mod.rs | 2 +- 14 files changed, 25 insertions(+), 25 deletions(-) diff --git a/embassy-rp/CHANGELOG.md b/embassy-rp/CHANGELOG.md index a99d04aa4..57ec13658 100644 --- a/embassy-rp/CHANGELOG.md +++ b/embassy-rp/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate -- Fix typo in interrupt comment +- Fix several minor typos in documentation - Add PIO SPI - Add PIO I2S input - Add PIO onewire parasite power strong pullup diff --git a/embassy-rp/src/block.rs b/embassy-rp/src/block.rs index a3e1ad925..745883b83 100644 --- a/embassy-rp/src/block.rs +++ b/embassy-rp/src/block.rs @@ -240,7 +240,7 @@ impl UnpartitionedSpace { } } - /// Create a new unpartition space from run-time values. + /// Create a new unpartitioned space from run-time values. /// /// Get these from the ROM function `get_partition_table_info` with an argument of `PT_INFO`. pub const fn from_raw(permissions_and_location: u32, permissions_and_flags: u32) -> Self { @@ -714,7 +714,7 @@ impl PartitionTableBlock { new_table } - /// Add a a SHA256 hash of the Block + /// Add a SHA256 hash of the Block /// /// Adds a `HASH_DEF` covering all the previous items in the Block, and a /// `HASH_VALUE` with a SHA-256 hash of them. diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index 56892d7a2..8bfb5129a 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -267,7 +267,7 @@ impl CoreVoltage { } } -/// CLock configuration. +/// Clock configuration. #[non_exhaustive] pub struct ClockConfig { /// Ring oscillator configuration. diff --git a/embassy-rp/src/i2c_slave.rs b/embassy-rp/src/i2c_slave.rs index 770087bc8..0853709df 100644 --- a/embassy-rp/src/i2c_slave.rs +++ b/embassy-rp/src/i2c_slave.rs @@ -52,7 +52,7 @@ pub enum ReadStatus { Done, /// Transaction Incomplete, controller trying to read more bytes than were provided NeedMoreBytes, - /// Transaction Complere, but controller stopped reading bytes before we ran out + /// Transaction Complete, but controller stopped reading bytes before we ran out LeftoverBytes(u16), } @@ -240,7 +240,7 @@ impl<'d, T: Instance> I2cSlave<'d, T> { if p.ic_rxflr().read().rxflr() > 0 || me.pending_byte.is_some() { me.drain_fifo(buffer, &mut len); - // we're recieving data, set rx fifo watermark to 12 bytes (3/4 full) to reduce interrupt noise + // we're receiving data, set rx fifo watermark to 12 bytes (3/4 full) to reduce interrupt noise p.ic_rx_tl().write(|w| w.set_rx_tl(11)); } diff --git a/embassy-rp/src/multicore.rs b/embassy-rp/src/multicore.rs index 3b120e349..572d8db91 100644 --- a/embassy-rp/src/multicore.rs +++ b/embassy-rp/src/multicore.rs @@ -58,7 +58,7 @@ const PAUSE_TOKEN: u32 = 0xDEADBEEF; const RESUME_TOKEN: u32 = !0xDEADBEEF; static IS_CORE1_INIT: AtomicBool = AtomicBool::new(false); -/// Represents a partiticular CPU core (SIO_CPUID) +/// Represents a particular CPU core (SIO_CPUID) #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] diff --git a/embassy-rp/src/pio/mod.rs b/embassy-rp/src/pio/mod.rs index 38ee1f97c..92b2c603e 100644 --- a/embassy-rp/src/pio/mod.rs +++ b/embassy-rp/src/pio/mod.rs @@ -62,7 +62,7 @@ pub enum FifoJoin { #[cfg(feature = "_rp235x")] RxAsControl, /// FJOIN_RX_PUT | FJOIN_RX_GET: RX can be used as a scratch register, - /// not accesible from the CPU + /// not accessible from the CPU #[cfg(feature = "_rp235x")] PioScratch, } diff --git a/embassy-rp/src/pio_programs/i2s.rs b/embassy-rp/src/pio_programs/i2s.rs index 7e5f68ad6..5c49beecb 100644 --- a/embassy-rp/src/pio_programs/i2s.rs +++ b/embassy-rp/src/pio_programs/i2s.rs @@ -1,4 +1,4 @@ -//! Pio backed I2s output and output drivers +//! Pio backed I2S output and output drivers use fixed::traits::ToFixed; @@ -9,7 +9,7 @@ use crate::pio::{ Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, }; -/// This struct represents an i2s receiver & controller driver program +/// This struct represents an I2S receiver & controller driver program pub struct PioI2sInProgram<'d, PIO: Instance> { prg: LoadedProgram<'d, PIO>, } @@ -35,7 +35,7 @@ impl<'d, PIO: Instance> PioI2sInProgram<'d, PIO> { } } -/// Pio backed I2s input driver +/// Pio backed I2S input driver pub struct PioI2sIn<'d, P: Instance, const S: usize> { dma: Peri<'d, AnyChannel>, sm: StateMachine<'d, P, S>, @@ -50,7 +50,7 @@ impl<'d, P: Instance, const S: usize> PioI2sIn<'d, P, S> { // Whether or not to use the MCU's internal pull-down resistor, as the // Pico 2 is known to have problems with the inbuilt pulldowns, many // opt to just use an external pull down resistor to meet requirements of common - // i2s microphones such as the INMP441 + // I2S microphones such as the INMP441 data_pulldown: bool, data_pin: Peri<'d, impl PioPin>, bit_clock_pin: Peri<'d, impl PioPin>, @@ -90,13 +90,13 @@ impl<'d, P: Instance, const S: usize> PioI2sIn<'d, P, S> { Self { dma: dma.into(), sm } } - /// Return an in-prograss dma transfer future. Awaiting it will guarentee a complete transfer. + /// Return an in-progress dma transfer future. Awaiting it will guarantee a complete transfer. pub fn read<'b>(&'b mut self, buff: &'b mut [u32]) -> Transfer<'b, AnyChannel> { self.sm.rx().dma_pull(self.dma.reborrow(), buff, false) } } -/// This struct represents an i2s output driver program +/// This struct represents an I2S output driver program /// /// The sample bit-depth is set through scratch register `Y`. /// `Y` has to be set to sample bit-depth - 2. @@ -128,14 +128,14 @@ impl<'d, PIO: Instance> PioI2sOutProgram<'d, PIO> { } } -/// Pio backed I2s output driver +/// Pio backed I2S output driver pub struct PioI2sOut<'d, P: Instance, const S: usize> { dma: Peri<'d, AnyChannel>, sm: StateMachine<'d, P, S>, } impl<'d, P: Instance, const S: usize> PioI2sOut<'d, P, S> { - /// Configure a state machine to output I2s + /// Configure a state machine to output I2S pub fn new( common: &mut Common<'d, P>, mut sm: StateMachine<'d, P, S>, @@ -179,7 +179,7 @@ impl<'d, P: Instance, const S: usize> PioI2sOut<'d, P, S> { Self { dma: dma.into(), sm } } - /// Return an in-prograss dma transfer future. Awaiting it will guarentee a complete transfer. + /// Return an in-progress dma transfer future. Awaiting it will guarantee a complete transfer. pub fn write<'b>(&'b mut self, buff: &'b [u32]) -> Transfer<'b, AnyChannel> { self.sm.tx().dma_push(self.dma.reborrow(), buff, false) } diff --git a/embassy-rp/src/pio_programs/pwm.rs b/embassy-rp/src/pio_programs/pwm.rs index ba06bb3c1..e4ad4a6f0 100644 --- a/embassy-rp/src/pio_programs/pwm.rs +++ b/embassy-rp/src/pio_programs/pwm.rs @@ -67,7 +67,7 @@ impl<'d, T: Instance, const SM: usize> PioPwm<'d, T, SM> { Self { sm, pin } } - /// Enable's the PIO program, continuing the wave generation from the PIO program. + /// Enables the PIO program, continuing the wave generation from the PIO program. pub fn start(&mut self) { self.sm.set_enable(true); } diff --git a/embassy-rp/src/pio_programs/spi.rs b/embassy-rp/src/pio_programs/spi.rs index b10fc6628..765ffaa06 100644 --- a/embassy-rp/src/pio_programs/spi.rs +++ b/embassy-rp/src/pio_programs/spi.rs @@ -1,4 +1,4 @@ -//! PIO backed SPi drivers +//! PIO backed SPI drivers use core::marker::PhantomData; @@ -83,7 +83,7 @@ pub enum Error { // No errors for now } -/// PIO based Spi driver. +/// PIO based SPI driver. /// Unlike other PIO programs, the PIO SPI driver owns and holds a reference to /// the PIO memory it uses. This is so that it can be reconfigured at runtime if /// desired. diff --git a/embassy-rp/src/pio_programs/uart.rs b/embassy-rp/src/pio_programs/uart.rs index 444efb5db..d59596dd1 100644 --- a/embassy-rp/src/pio_programs/uart.rs +++ b/embassy-rp/src/pio_programs/uart.rs @@ -130,7 +130,7 @@ impl<'d, PIO: Instance> PioUartRxProgram<'d, PIO> { } } -/// PIO backed Uart reciever +/// PIO backed Uart receiver pub struct PioUartRx<'d, PIO: Instance, const SM: usize> { sm_rx: StateMachine<'d, PIO, SM>, } diff --git a/embassy-rp/src/rom_data/rp2040.rs b/embassy-rp/src/rom_data/rp2040.rs index 5a74eddd6..27a8d8981 100644 --- a/embassy-rp/src/rom_data/rp2040.rs +++ b/embassy-rp/src/rom_data/rp2040.rs @@ -30,7 +30,7 @@ const DATA_TABLE: *const u16 = 0x0000_0016 as _; /// Address of the version number of the ROM. const VERSION_NUMBER: *const u8 = 0x0000_0013 as _; -/// Retrive rom content from a table using a code. +/// Retrieve rom content from a table using a code. fn rom_table_lookup(table: *const u16, tag: RomFnTableCode) -> T { unsafe { let rom_table_lookup_ptr: *const u32 = rom_hword_as_ptr(ROM_TABLE_LOOKUP_PTR); diff --git a/embassy-rp/src/rtc/mod.rs b/embassy-rp/src/rtc/mod.rs index 68fb3b765..054572903 100644 --- a/embassy-rp/src/rtc/mod.rs +++ b/embassy-rp/src/rtc/mod.rs @@ -47,7 +47,7 @@ impl<'d, T: Instance> Rtc<'d, T> { Self { inner } } - /// Enable or disable the leap year check. The rp2040 chip will always add a Feb 29th on every year that is divisable by 4, but this may be incorrect (e.g. on century years). This function allows you to disable this check. + /// Enable or disable the leap year check. The rp2040 chip will always add a Feb 29th on every year that is divisible by 4, but this may be incorrect (e.g. on century years). This function allows you to disable this check. /// /// Leap year checking is enabled by default. pub fn set_leap_year_check(&mut self, leap_year_check_enabled: bool) { diff --git a/embassy-rp/src/spi.rs b/embassy-rp/src/spi.rs index 559b3b909..d9410e78d 100644 --- a/embassy-rp/src/spi.rs +++ b/embassy-rp/src/spi.rs @@ -157,7 +157,7 @@ impl<'d, T: Instance, M: Mode> Spi<'d, T, M> { /// Private function to apply SPI configuration (phase, polarity, frequency) settings. /// - /// Driver should be disabled before making changes and reenabled after the modifications + /// Driver should be disabled before making changes and re-enabled after the modifications /// are applied. fn apply_config(inner: &Peri<'d, T>, config: &Config) { let p = inner.regs(); diff --git a/embassy-rp/src/uart/mod.rs b/embassy-rp/src/uart/mod.rs index 43187df2d..8be87a5d2 100644 --- a/embassy-rp/src/uart/mod.rs +++ b/embassy-rp/src/uart/mod.rs @@ -315,7 +315,7 @@ impl<'d, M: Mode> UartRx<'d, M> { } /// Returns Ok(len) if no errors occurred. Returns Err((len, err)) if an error was - /// encountered. in both cases, `len` is the number of *good* bytes copied into + /// encountered. In both cases, `len` is the number of *good* bytes copied into /// `buffer`. fn drain_fifo(&mut self, buffer: &mut [u8]) -> Result { let r = self.info.regs; -- cgit From e282662f2408455d5f7e53c010c3bf0d8eeb99aa Mon Sep 17 00:00:00 2001 From: xoviat Date: Mon, 27 Oct 2025 21:10:19 -0500 Subject: timer: add output compare values --- embassy-stm32/CHANGELOG.md | 1 + embassy-stm32/src/timer/low_level.rs | 69 ++++++++++++++++++++++++++++++------ 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index 9848daf49..000d215b7 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - feat: stm32/usart: add `eager_reads` option to control if buffered readers return as soon as possible or after more data is available ([#4668](https://github.com/embassy-rs/embassy/pull/4668)) - feat: stm32/usart: add `de_assertion_time` and `de_deassertion_time` config options - change: stm32/uart: BufferedUartRx now returns all available bytes from the internal buffer +- change: timer: added output compare values ## 0.4.0 - 2025-08-26 diff --git a/embassy-stm32/src/timer/low_level.rs b/embassy-stm32/src/timer/low_level.rs index ac039bb0d..7c02e7e62 100644 --- a/embassy-stm32/src/timer/low_level.rs +++ b/embassy-stm32/src/timer/low_level.rs @@ -143,20 +143,69 @@ pub enum OutputCompareMode { /// TIMx_CNTTIMx_CCRx else inactive. PwmMode2, - // TODO: there's more modes here depending on the chip family. + + #[cfg(timer_v2)] + /// In up-counting mode, the channel is active until a trigger + /// event is detected (on tim_trgi signal). Then, a comparison is performed as in PWM + /// mode 1 and the channels becomes active again at the next update. In down-counting + /// mode, the channel is inactive until a trigger event is detected (on tim_trgi signal). + /// Then, a comparison is performed as in PWM mode 1 and the channels becomes + /// inactive again at the next update. + OnePulseMode1, + + #[cfg(timer_v2)] + /// In up-counting mode, the channel is inactive until a + /// trigger event is detected (on tim_trgi signal). Then, a comparison is performed as in + /// PWM mode 2 and the channels becomes inactive again at the next update. In down + /// counting mode, the channel is active until a trigger event is detected (on tim_trgi + /// signal). Then, a comparison is performed as in PWM mode 1 and the channels + /// becomes active again at the next update. + OnePulseMode2, + + #[cfg(timer_v2)] + /// Combined PWM mode 1 - tim_oc1ref has the same behavior as in PWM mode 1. + /// tim_oc1refc is the logical OR between tim_oc1ref and tim_oc2ref. + CombinedPwmMode1, + + #[cfg(timer_v2)] + /// Combined PWM mode 2 - tim_oc1ref has the same behavior as in PWM mode 2. + /// tim_oc1refc is the logical AND between tim_oc1ref and tim_oc2ref. + CombinedPwmMode2, + + #[cfg(timer_v2)] + /// tim_oc1ref has the same behavior as in PWM mode 1. tim_oc1refc outputs tim_oc1ref + /// when the counter is counting up, tim_oc2ref when it is counting down. + AsymmetricPwmMode1, + + #[cfg(timer_v2)] + /// tim_oc1ref has the same behavior as in PWM mode 2. tim_oc1refc outputs tim_oc1ref + /// when the counter is counting up, tim_oc2ref when it is counting down. + AsymmetricPwmMode2, } -impl From for stm32_metapac::timer::vals::Ocm { +impl From for crate::pac::timer::vals::Ocm { fn from(mode: OutputCompareMode) -> Self { match mode { - OutputCompareMode::Frozen => stm32_metapac::timer::vals::Ocm::FROZEN, - OutputCompareMode::ActiveOnMatch => stm32_metapac::timer::vals::Ocm::ACTIVE_ON_MATCH, - OutputCompareMode::InactiveOnMatch => stm32_metapac::timer::vals::Ocm::INACTIVE_ON_MATCH, - OutputCompareMode::Toggle => stm32_metapac::timer::vals::Ocm::TOGGLE, - OutputCompareMode::ForceInactive => stm32_metapac::timer::vals::Ocm::FORCE_INACTIVE, - OutputCompareMode::ForceActive => stm32_metapac::timer::vals::Ocm::FORCE_ACTIVE, - OutputCompareMode::PwmMode1 => stm32_metapac::timer::vals::Ocm::PWM_MODE1, - OutputCompareMode::PwmMode2 => stm32_metapac::timer::vals::Ocm::PWM_MODE2, + OutputCompareMode::Frozen => crate::pac::timer::vals::Ocm::FROZEN, + OutputCompareMode::ActiveOnMatch => crate::pac::timer::vals::Ocm::ACTIVE_ON_MATCH, + OutputCompareMode::InactiveOnMatch => crate::pac::timer::vals::Ocm::INACTIVE_ON_MATCH, + OutputCompareMode::Toggle => crate::pac::timer::vals::Ocm::TOGGLE, + OutputCompareMode::ForceInactive => crate::pac::timer::vals::Ocm::FORCE_INACTIVE, + OutputCompareMode::ForceActive => crate::pac::timer::vals::Ocm::FORCE_ACTIVE, + OutputCompareMode::PwmMode1 => crate::pac::timer::vals::Ocm::PWM_MODE1, + OutputCompareMode::PwmMode2 => crate::pac::timer::vals::Ocm::PWM_MODE2, + #[cfg(timer_v2)] + OutputCompareMode::OnePulseMode1 => crate::pac::timer::vals::Ocm::RETRIGERRABLE_OPM_MODE_1, + #[cfg(timer_v2)] + OutputCompareMode::OnePulseMode2 => crate::pac::timer::vals::Ocm::RETRIGERRABLE_OPM_MODE_2, + #[cfg(timer_v2)] + OutputCompareMode::CombinedPwmMode1 => crate::pac::timer::vals::Ocm::COMBINED_PWM_MODE_1, + #[cfg(timer_v2)] + OutputCompareMode::CombinedPwmMode2 => crate::pac::timer::vals::Ocm::COMBINED_PWM_MODE_2, + #[cfg(timer_v2)] + OutputCompareMode::AsymmetricPwmMode1 => crate::pac::timer::vals::Ocm::ASYMMETRIC_PWM_MODE_1, + #[cfg(timer_v2)] + OutputCompareMode::AsymmetricPwmMode2 => crate::pac::timer::vals::Ocm::ASYMMETRIC_PWM_MODE_2, } } } -- cgit From de5760cc81a00966c61d668c41f6e3e4709f0283 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Tue, 14 Oct 2025 13:23:50 +0200 Subject: feat: improve nrf54 support using new nrf-pac * Update nrf-pac to version that modifies nrf52 register layout to match nrf54 to reduce the amount of cfg needed for nrf54 support. * Make the following peripherals available on nrf54: twim, twis, spim, spis, uart, buffered uarte, dppi, gpiote, pwm, saadc * Add examples tested on the nrf54 dk Some code is based on or copied from other pull requests, modified to match the new nrf-pac layout. Co-authored-by: Dmitry Tarnyagin --- embassy-net-nrf91/CHANGELOG.md | 2 + embassy-net-nrf91/Cargo.toml | 2 +- embassy-nrf/CHANGELOG.md | 2 + embassy-nrf/Cargo.toml | 21 +- embassy-nrf/src/buffered_uarte.rs | 951 -------------------------- embassy-nrf/src/buffered_uarte/mod.rs | 14 + embassy-nrf/src/buffered_uarte/v1.rs | 951 ++++++++++++++++++++++++++ embassy-nrf/src/buffered_uarte/v2.rs | 687 +++++++++++++++++++ embassy-nrf/src/chips/nrf51.rs | 5 + embassy-nrf/src/chips/nrf52805.rs | 51 +- embassy-nrf/src/chips/nrf52810.rs | 71 +- embassy-nrf/src/chips/nrf52811.rs | 71 +- embassy-nrf/src/chips/nrf52820.rs | 71 +- embassy-nrf/src/chips/nrf52832.rs | 71 +- embassy-nrf/src/chips/nrf52833.rs | 71 +- embassy-nrf/src/chips/nrf52840.rs | 71 +- embassy-nrf/src/chips/nrf5340_app.rs | 71 +- embassy-nrf/src/chips/nrf5340_net.rs | 71 +- embassy-nrf/src/chips/nrf54l15_app.rs | 208 +++++- embassy-nrf/src/chips/nrf9120.rs | 39 +- embassy-nrf/src/chips/nrf9160.rs | 39 +- embassy-nrf/src/gpio.rs | 2 - embassy-nrf/src/gpiote.rs | 431 +++++++++--- embassy-nrf/src/lib.rs | 12 - embassy-nrf/src/ppi/dppi.rs | 11 +- embassy-nrf/src/ppi/mod.rs | 119 +++- embassy-nrf/src/pwm.rs | 59 +- embassy-nrf/src/saadc.rs | 247 ++++++- embassy-nrf/src/spim.rs | 163 ++++- embassy-nrf/src/spis.rs | 16 +- embassy-nrf/src/twim.rs | 44 +- embassy-nrf/src/twis.rs | 44 +- embassy-nrf/src/uarte.rs | 162 +++-- examples/nrf52840/src/bin/egu.rs | 15 +- examples/nrf52840/src/bin/gpiote_channel.rs | 26 +- examples/nrf52840/src/bin/ppi.rs | 34 +- examples/nrf52840/src/bin/pwm_sequence_ppi.rs | 14 +- examples/nrf5340/src/bin/gpiote_channel.rs | 26 +- examples/nrf54l15/Cargo.toml | 5 + examples/nrf54l15/src/bin/buffered_uart.rs | 49 ++ examples/nrf54l15/src/bin/gpiote_channel.rs | 49 ++ examples/nrf54l15/src/bin/gpiote_port.rs | 33 + examples/nrf54l15/src/bin/pwm.rs | 86 +++ examples/nrf54l15/src/bin/saadc.rs | 28 + examples/nrf54l15/src/bin/spim.rs | 72 ++ examples/nrf54l15/src/bin/twim.rs | 37 + examples/nrf54l15/src/bin/twis.rs | 47 ++ examples/nrf54l15/src/bin/uart.rs | 37 + tests/nrf/.cargo/config.toml | 4 +- tests/nrf/src/bin/buffered_uart_spam.rs | 10 +- 50 files changed, 3774 insertions(+), 1648 deletions(-) delete mode 100644 embassy-nrf/src/buffered_uarte.rs create mode 100644 embassy-nrf/src/buffered_uarte/mod.rs create mode 100644 embassy-nrf/src/buffered_uarte/v1.rs create mode 100644 embassy-nrf/src/buffered_uarte/v2.rs create mode 100644 examples/nrf54l15/src/bin/buffered_uart.rs create mode 100644 examples/nrf54l15/src/bin/gpiote_channel.rs create mode 100644 examples/nrf54l15/src/bin/gpiote_port.rs create mode 100644 examples/nrf54l15/src/bin/pwm.rs create mode 100644 examples/nrf54l15/src/bin/saadc.rs create mode 100644 examples/nrf54l15/src/bin/spim.rs create mode 100644 examples/nrf54l15/src/bin/twim.rs create mode 100644 examples/nrf54l15/src/bin/twis.rs create mode 100644 examples/nrf54l15/src/bin/uart.rs diff --git a/embassy-net-nrf91/CHANGELOG.md b/embassy-net-nrf91/CHANGELOG.md index 52cbf5ef3..11974ac04 100644 --- a/embassy-net-nrf91/CHANGELOG.md +++ b/embassy-net-nrf91/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate +- changed: updated to nrf-pac with nrf52/nrf53/nrf91 register layout more similar to nrf54 + ## 0.1.1 - 2025-08-14 - First release with changelog. diff --git a/embassy-net-nrf91/Cargo.toml b/embassy-net-nrf91/Cargo.toml index ecb10246a..75b7aeeb2 100644 --- a/embassy-net-nrf91/Cargo.toml +++ b/embassy-net-nrf91/Cargo.toml @@ -18,7 +18,7 @@ log = ["dep:log"] defmt = { version = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } -nrf-pac = "0.1.0" +nrf-pac = { version = "0.1.0", git = "https://github.com/embassy-rs/nrf-pac.git", rev = "58198c23bce72edc10b4e1656d1b54441fc74e7c" } cortex-m = "0.7.7" embassy-time = { version = "0.5.0", path = "../embassy-time" } diff --git a/embassy-nrf/CHANGELOG.md b/embassy-nrf/CHANGELOG.md index 89adaf2da..c23613f19 100644 --- a/embassy-nrf/CHANGELOG.md +++ b/embassy-nrf/CHANGELOG.md @@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - changed: allow configuring the PWM peripheral in the constructor of `SimplePwm` - changed: support setting duty cycles with inverted polarity in `SimplePwm` - added: support setting the duty cycles of all channels at once in `SimplePwm` +- changed: updated to nrf-pac with nrf52/nrf53/nrf91 register layout more similar to nrf54 +- added: support for nrf54l peripherals: uart, gpiote, twim, twis, spim, spis, dppi, pwm, saadc ## 0.8.0 - 2025-09-30 diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index 28f137d5c..08f4b280b 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml @@ -104,17 +104,17 @@ qspi-multiwrite-flash = [] #! ### Chip selection features ## nRF51 -nrf51 = ["nrf-pac/nrf51", "_nrf51"] +nrf51 = ["nrf-pac/nrf51", "_nrf51", "_spi-v1"] ## nRF52805 -nrf52805 = ["nrf-pac/nrf52805", "_nrf52"] +nrf52805 = ["nrf-pac/nrf52805", "_nrf52", "_spi-v1"] ## nRF52810 -nrf52810 = ["nrf-pac/nrf52810", "_nrf52"] +nrf52810 = ["nrf-pac/nrf52810", "_nrf52", "_spi-v1"] ## nRF52811 -nrf52811 = ["nrf-pac/nrf52811", "_nrf52"] +nrf52811 = ["nrf-pac/nrf52811", "_nrf52", "_spi-v1"] ## nRF52820 -nrf52820 = ["nrf-pac/nrf52820", "_nrf52"] +nrf52820 = ["nrf-pac/nrf52820", "_nrf52", "_spi-v1"] ## nRF52832 -nrf52832 = ["nrf-pac/nrf52832", "_nrf52", "_nrf52832_anomaly_109"] +nrf52832 = ["nrf-pac/nrf52832", "_nrf52", "_nrf52832_anomaly_109", "_spi-v1"] ## nRF52833 nrf52833 = ["nrf-pac/nrf52833", "_nrf52", "_gpio-p1"] ## nRF52840 @@ -154,10 +154,10 @@ _nrf54l15-app = ["_nrf54l15", "nrf-pac/nrf54l15-app"] _nrf54l15 = ["_nrf54l", "_gpio-p1", "_gpio-p2"] _nrf54l = ["_dppi"] -_nrf9160 = ["nrf-pac/nrf9160", "_dppi"] -_nrf9120 = ["nrf-pac/nrf9120", "_dppi"] +_nrf9160 = ["nrf-pac/nrf9160", "_dppi", "_spi-v1"] +_nrf9120 = ["nrf-pac/nrf9120", "_dppi", "_spi-v1"] _nrf52 = ["_ppi"] -_nrf51 = ["_ppi"] +_nrf51 = ["_ppi", "_spi-v1"] _nrf91 = [] _time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-32_768", "dep:embassy-time-queue-utils", "embassy-embedded-hal/time"] @@ -172,6 +172,7 @@ _ppi = [] _dppi = [] _gpio-p1 = [] _gpio-p2 = [] +_spi-v1 = [] # Errata workarounds _nrf52832_anomaly_109 = [] @@ -199,7 +200,7 @@ embedded-io-async = { version = "0.6.1" } rand-core-06 = { package = "rand_core", version = "0.6" } rand-core-09 = { package = "rand_core", version = "0.9" } -nrf-pac = "0.1.0" +nrf-pac = { version = "0.1.0", git = "https://github.com/embassy-rs/nrf-pac.git", rev = "58198c23bce72edc10b4e1656d1b54441fc74e7c" } defmt = { version = "1.0.1", optional = true } bitflags = "2.4.2" diff --git a/embassy-nrf/src/buffered_uarte.rs b/embassy-nrf/src/buffered_uarte.rs deleted file mode 100644 index b1eb5c81a..000000000 --- a/embassy-nrf/src/buffered_uarte.rs +++ /dev/null @@ -1,951 +0,0 @@ -//! Async buffered UART driver. -//! -//! Note that discarding a future from a read or write operation may lead to losing -//! data. For example, when using `futures_util::future::select` and completion occurs -//! on the "other" future, you should capture the incomplete future and continue to use -//! it for the next read or write. This pattern is a consideration for all IO, and not -//! just serial communications. -//! -//! Please also see [crate::uarte] to understand when [BufferedUarte] should be used. - -use core::cmp::min; -use core::future::{Future, poll_fn}; -use core::marker::PhantomData; -use core::slice; -use core::sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering, compiler_fence}; -use core::task::Poll; - -use embassy_hal_internal::Peri; -use embassy_hal_internal::atomic_ring_buffer::RingBuffer; -use pac::uarte::vals; -// Re-export SVD variants to allow user to directly set values -pub use pac::uarte::vals::{Baudrate, ConfigParity as Parity}; - -use crate::gpio::{AnyPin, Pin as GpioPin}; -use crate::interrupt::InterruptExt; -use crate::interrupt::typelevel::Interrupt; -use crate::ppi::{ - self, AnyConfigurableChannel, AnyGroup, Channel, ConfigurableChannel, Event, Group, Ppi, PpiGroup, Task, -}; -use crate::timer::{Instance as TimerInstance, Timer}; -use crate::uarte::{Config, Instance as UarteInstance, configure, configure_rx_pins, configure_tx_pins, drop_tx_rx}; -use crate::{EASY_DMA_SIZE, interrupt, pac}; - -pub(crate) struct State { - tx_buf: RingBuffer, - tx_count: AtomicUsize, - - rx_buf: RingBuffer, - rx_started: AtomicBool, - rx_started_count: AtomicU8, - rx_ended_count: AtomicU8, - rx_ppi_ch: AtomicU8, - rx_overrun: AtomicBool, -} - -/// UART error. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[non_exhaustive] -pub enum Error { - /// Buffer Overrun - Overrun, -} - -impl State { - pub(crate) const fn new() -> Self { - Self { - tx_buf: RingBuffer::new(), - tx_count: AtomicUsize::new(0), - - rx_buf: RingBuffer::new(), - rx_started: AtomicBool::new(false), - rx_started_count: AtomicU8::new(0), - rx_ended_count: AtomicU8::new(0), - rx_ppi_ch: AtomicU8::new(0), - rx_overrun: AtomicBool::new(false), - } - } -} - -/// Interrupt handler. -pub struct InterruptHandler { - _phantom: PhantomData, -} - -impl interrupt::typelevel::Handler for InterruptHandler { - unsafe fn on_interrupt() { - //trace!("irq: start"); - let r = U::regs(); - let ss = U::state(); - let s = U::buffered_state(); - - if let Some(mut rx) = unsafe { s.rx_buf.try_writer() } { - let buf_len = s.rx_buf.len(); - let half_len = buf_len / 2; - - if r.events_error().read() != 0 { - r.events_error().write_value(0); - let errs = r.errorsrc().read(); - r.errorsrc().write_value(errs); - - if errs.overrun() { - s.rx_overrun.store(true, Ordering::Release); - ss.rx_waker.wake(); - } - } - - // Received some bytes, wake task. - if r.inten().read().rxdrdy() && r.events_rxdrdy().read() != 0 { - r.intenclr().write(|w| w.set_rxdrdy(true)); - r.events_rxdrdy().write_value(0); - ss.rx_waker.wake(); - } - - if r.events_endrx().read() != 0 { - //trace!(" irq_rx: endrx"); - r.events_endrx().write_value(0); - - let val = s.rx_ended_count.load(Ordering::Relaxed); - s.rx_ended_count.store(val.wrapping_add(1), Ordering::Relaxed); - } - - if r.events_rxstarted().read() != 0 || !s.rx_started.load(Ordering::Relaxed) { - //trace!(" irq_rx: rxstarted"); - let (ptr, len) = rx.push_buf(); - if len >= half_len { - r.events_rxstarted().write_value(0); - - //trace!(" irq_rx: starting second {:?}", half_len); - - // Set up the DMA read - r.rxd().ptr().write_value(ptr as u32); - r.rxd().maxcnt().write(|w| w.set_maxcnt(half_len as _)); - - let chn = s.rx_ppi_ch.load(Ordering::Relaxed); - - // Enable endrx -> startrx PPI channel. - // From this point on, if endrx happens, startrx is automatically fired. - ppi::regs().chenset().write(|w| w.0 = 1 << chn); - - // It is possible that endrx happened BEFORE enabling the PPI. In this case - // the PPI channel doesn't trigger, and we'd hang. We have to detect this - // and manually start. - - // check again in case endrx has happened between the last check and now. - if r.events_endrx().read() != 0 { - //trace!(" irq_rx: endrx"); - r.events_endrx().write_value(0); - - let val = s.rx_ended_count.load(Ordering::Relaxed); - s.rx_ended_count.store(val.wrapping_add(1), Ordering::Relaxed); - } - - let rx_ended = s.rx_ended_count.load(Ordering::Relaxed); - let rx_started = s.rx_started_count.load(Ordering::Relaxed); - - // If we started the same amount of transfers as ended, the last rxend has - // already occured. - let rxend_happened = rx_started == rx_ended; - - // Check if the PPI channel is still enabled. The PPI channel disables itself - // when it fires, so if it's still enabled it hasn't fired. - let ppi_ch_enabled = ppi::regs().chen().read().ch(chn as _); - - // if rxend happened, and the ppi channel hasn't fired yet, the rxend got missed. - // this condition also naturally matches if `!started`, needed to kickstart the DMA. - if rxend_happened && ppi_ch_enabled { - //trace!("manually starting."); - - // disable the ppi ch, it's of no use anymore. - ppi::regs().chenclr().write(|w| w.set_ch(chn as _, true)); - - // manually start - r.tasks_startrx().write_value(1); - } - - rx.push_done(half_len); - - s.rx_started_count.store(rx_started.wrapping_add(1), Ordering::Relaxed); - s.rx_started.store(true, Ordering::Relaxed); - } else { - //trace!(" irq_rx: rxstarted no buf"); - r.intenclr().write(|w| w.set_rxstarted(true)); - } - } - } - - // ============================= - - if let Some(mut tx) = unsafe { s.tx_buf.try_reader() } { - // TX end - if r.events_endtx().read() != 0 { - r.events_endtx().write_value(0); - - let n = s.tx_count.load(Ordering::Relaxed); - //trace!(" irq_tx: endtx {:?}", n); - tx.pop_done(n); - ss.tx_waker.wake(); - s.tx_count.store(0, Ordering::Relaxed); - } - - // If not TXing, start. - if s.tx_count.load(Ordering::Relaxed) == 0 { - let (ptr, len) = tx.pop_buf(); - let len = len.min(EASY_DMA_SIZE); - if len != 0 { - //trace!(" irq_tx: starting {:?}", len); - s.tx_count.store(len, Ordering::Relaxed); - - // Set up the DMA write - r.txd().ptr().write_value(ptr as u32); - r.txd().maxcnt().write(|w| w.set_maxcnt(len as _)); - - // Start UARTE Transmit transaction - r.tasks_starttx().write_value(1); - } - } - } - - //trace!("irq: end"); - } -} - -/// Buffered UARTE driver. -pub struct BufferedUarte<'d> { - tx: BufferedUarteTx<'d>, - rx: BufferedUarteRx<'d>, -} - -impl<'d> Unpin for BufferedUarte<'d> {} - -impl<'d> BufferedUarte<'d> { - /// Create a new BufferedUarte without hardware flow control. - /// - /// # Panics - /// - /// Panics if `rx_buffer.len()` is odd. - #[allow(clippy::too_many_arguments)] - pub fn new( - uarte: Peri<'d, U>, - timer: Peri<'d, T>, - ppi_ch1: Peri<'d, impl ConfigurableChannel>, - ppi_ch2: Peri<'d, impl ConfigurableChannel>, - ppi_group: Peri<'d, impl Group>, - rxd: Peri<'d, impl GpioPin>, - txd: Peri<'d, impl GpioPin>, - _irq: impl interrupt::typelevel::Binding> + 'd, - config: Config, - rx_buffer: &'d mut [u8], - tx_buffer: &'d mut [u8], - ) -> Self { - Self::new_inner( - uarte, - timer, - ppi_ch1.into(), - ppi_ch2.into(), - ppi_group.into(), - rxd.into(), - txd.into(), - None, - None, - config, - rx_buffer, - tx_buffer, - ) - } - - /// Create a new BufferedUarte with hardware flow control (RTS/CTS) - /// - /// # Panics - /// - /// Panics if `rx_buffer.len()` is odd. - #[allow(clippy::too_many_arguments)] - pub fn new_with_rtscts( - uarte: Peri<'d, U>, - timer: Peri<'d, T>, - ppi_ch1: Peri<'d, impl ConfigurableChannel>, - ppi_ch2: Peri<'d, impl ConfigurableChannel>, - ppi_group: Peri<'d, impl Group>, - rxd: Peri<'d, impl GpioPin>, - txd: Peri<'d, impl GpioPin>, - cts: Peri<'d, impl GpioPin>, - rts: Peri<'d, impl GpioPin>, - _irq: impl interrupt::typelevel::Binding> + 'd, - config: Config, - rx_buffer: &'d mut [u8], - tx_buffer: &'d mut [u8], - ) -> Self { - Self::new_inner( - uarte, - timer, - ppi_ch1.into(), - ppi_ch2.into(), - ppi_group.into(), - rxd.into(), - txd.into(), - Some(cts.into()), - Some(rts.into()), - config, - rx_buffer, - tx_buffer, - ) - } - - #[allow(clippy::too_many_arguments)] - fn new_inner( - peri: Peri<'d, U>, - timer: Peri<'d, T>, - ppi_ch1: Peri<'d, AnyConfigurableChannel>, - ppi_ch2: Peri<'d, AnyConfigurableChannel>, - ppi_group: Peri<'d, AnyGroup>, - rxd: Peri<'d, AnyPin>, - txd: Peri<'d, AnyPin>, - cts: Option>, - rts: Option>, - config: Config, - rx_buffer: &'d mut [u8], - tx_buffer: &'d mut [u8], - ) -> Self { - let r = U::regs(); - let irq = U::Interrupt::IRQ; - let state = U::state(); - - configure(r, config, cts.is_some()); - - let tx = BufferedUarteTx::new_innerer(unsafe { peri.clone_unchecked() }, txd, cts, tx_buffer); - let rx = BufferedUarteRx::new_innerer(peri, timer, ppi_ch1, ppi_ch2, ppi_group, rxd, rts, rx_buffer); - - r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); - irq.pend(); - unsafe { irq.enable() }; - - state.tx_rx_refcount.store(2, Ordering::Relaxed); - - Self { tx, rx } - } - - /// Adjust the baud rate to the provided value. - pub fn set_baudrate(&mut self, baudrate: Baudrate) { - self.tx.set_baudrate(baudrate); - } - - /// Split the UART in reader and writer parts. - /// - /// This allows reading and writing concurrently from independent tasks. - pub fn split(self) -> (BufferedUarteRx<'d>, BufferedUarteTx<'d>) { - (self.rx, self.tx) - } - - /// Split the UART in reader and writer parts, by reference. - /// - /// The returned halves borrow from `self`, so you can drop them and go back to using - /// the "un-split" `self`. This allows temporarily splitting the UART. - pub fn split_by_ref(&mut self) -> (&mut BufferedUarteRx<'d>, &mut BufferedUarteTx<'d>) { - (&mut self.rx, &mut self.tx) - } - - /// Pull some bytes from this source into the specified buffer, returning how many bytes were read. - pub async fn read(&mut self, buf: &mut [u8]) -> Result { - self.rx.read(buf).await - } - - /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. - pub async fn fill_buf(&mut self) -> Result<&[u8], Error> { - self.rx.fill_buf().await - } - - /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. - pub fn consume(&mut self, amt: usize) { - self.rx.consume(amt) - } - - /// Write a buffer into this writer, returning how many bytes were written. - pub async fn write(&mut self, buf: &[u8]) -> Result { - self.tx.write(buf).await - } - - /// Try writing a buffer without waiting, returning how many bytes were written. - pub fn try_write(&mut self, buf: &[u8]) -> Result { - self.tx.try_write(buf) - } - - /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. - pub async fn flush(&mut self) -> Result<(), Error> { - self.tx.flush().await - } -} - -/// Reader part of the buffered UARTE driver. -pub struct BufferedUarteTx<'d> { - r: pac::uarte::Uarte, - _irq: interrupt::Interrupt, - state: &'static crate::uarte::State, - buffered_state: &'static State, - _p: PhantomData<&'d ()>, -} - -impl<'d> BufferedUarteTx<'d> { - /// Create a new BufferedUarteTx without hardware flow control. - pub fn new( - uarte: Peri<'d, U>, - txd: Peri<'d, impl GpioPin>, - _irq: impl interrupt::typelevel::Binding> + 'd, - config: Config, - tx_buffer: &'d mut [u8], - ) -> Self { - Self::new_inner(uarte, txd.into(), None, config, tx_buffer) - } - - /// Create a new BufferedUarte with hardware flow control (RTS/CTS) - /// - /// # Panics - /// - /// Panics if `rx_buffer.len()` is odd. - pub fn new_with_cts( - uarte: Peri<'d, U>, - txd: Peri<'d, impl GpioPin>, - cts: Peri<'d, impl GpioPin>, - _irq: impl interrupt::typelevel::Binding> + 'd, - config: Config, - tx_buffer: &'d mut [u8], - ) -> Self { - Self::new_inner(uarte, txd.into(), Some(cts.into()), config, tx_buffer) - } - - fn new_inner( - peri: Peri<'d, U>, - txd: Peri<'d, AnyPin>, - cts: Option>, - config: Config, - tx_buffer: &'d mut [u8], - ) -> Self { - let r = U::regs(); - let irq = U::Interrupt::IRQ; - let state = U::state(); - let _buffered_state = U::buffered_state(); - - configure(r, config, cts.is_some()); - - let this = Self::new_innerer(peri, txd, cts, tx_buffer); - - r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); - irq.pend(); - unsafe { irq.enable() }; - - state.tx_rx_refcount.store(1, Ordering::Relaxed); - - this - } - - fn new_innerer( - _peri: Peri<'d, U>, - txd: Peri<'d, AnyPin>, - cts: Option>, - tx_buffer: &'d mut [u8], - ) -> Self { - let r = U::regs(); - let irq = U::Interrupt::IRQ; - let state = U::state(); - let buffered_state = U::buffered_state(); - - configure_tx_pins(r, txd, cts); - - // Initialize state - buffered_state.tx_count.store(0, Ordering::Relaxed); - let len = tx_buffer.len(); - unsafe { buffered_state.tx_buf.init(tx_buffer.as_mut_ptr(), len) }; - - r.events_txstarted().write_value(0); - - // Enable interrupts - r.intenset().write(|w| { - w.set_endtx(true); - }); - - Self { - r, - _irq: irq, - state, - buffered_state, - _p: PhantomData, - } - } - - /// Write a buffer into this writer, returning how many bytes were written. - pub fn write<'a>(&'a mut self, buf: &'a [u8]) -> impl Future> + 'a + use<'a, 'd> { - poll_fn(move |cx| { - //trace!("poll_write: {:?}", buf.len()); - let ss = self.state; - let s = self.buffered_state; - let mut tx = unsafe { s.tx_buf.writer() }; - - let tx_buf = tx.push_slice(); - if tx_buf.is_empty() { - //trace!("poll_write: pending"); - ss.tx_waker.register(cx.waker()); - return Poll::Pending; - } - - let n = min(tx_buf.len(), buf.len()); - tx_buf[..n].copy_from_slice(&buf[..n]); - tx.push_done(n); - - //trace!("poll_write: queued {:?}", n); - - compiler_fence(Ordering::SeqCst); - self._irq.pend(); - - Poll::Ready(Ok(n)) - }) - } - - /// Try writing a buffer without waiting, returning how many bytes were written. - pub fn try_write(&mut self, buf: &[u8]) -> Result { - //trace!("poll_write: {:?}", buf.len()); - let s = self.buffered_state; - let mut tx = unsafe { s.tx_buf.writer() }; - - let tx_buf = tx.push_slice(); - if tx_buf.is_empty() { - return Ok(0); - } - - let n = min(tx_buf.len(), buf.len()); - tx_buf[..n].copy_from_slice(&buf[..n]); - tx.push_done(n); - - //trace!("poll_write: queued {:?}", n); - - compiler_fence(Ordering::SeqCst); - self._irq.pend(); - - Ok(n) - } - - /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. - pub fn flush(&mut self) -> impl Future> + '_ { - let ss = self.state; - let s = self.buffered_state; - poll_fn(move |cx| { - //trace!("poll_flush"); - if !s.tx_buf.is_empty() { - //trace!("poll_flush: pending"); - ss.tx_waker.register(cx.waker()); - return Poll::Pending; - } - - Poll::Ready(Ok(())) - }) - } - - /// Adjust the baud rate to the provided value. - pub fn set_baudrate(&mut self, baudrate: Baudrate) { - self.r.baudrate().write(|w| w.set_baudrate(baudrate)); - } -} - -impl<'a> Drop for BufferedUarteTx<'a> { - fn drop(&mut self) { - let r = self.r; - - r.intenclr().write(|w| { - w.set_txdrdy(true); - w.set_txstarted(true); - w.set_txstopped(true); - }); - r.events_txstopped().write_value(0); - r.tasks_stoptx().write_value(1); - while r.events_txstopped().read() == 0 {} - - let s = self.buffered_state; - unsafe { s.tx_buf.deinit() } - - let s = self.state; - drop_tx_rx(r, s); - } -} - -/// Reader part of the buffered UARTE driver. -pub struct BufferedUarteRx<'d> { - r: pac::uarte::Uarte, - state: &'static crate::uarte::State, - buffered_state: &'static State, - timer: Timer<'d>, - _ppi_ch1: Ppi<'d, AnyConfigurableChannel, 1, 1>, - _ppi_ch2: Ppi<'d, AnyConfigurableChannel, 1, 2>, - _ppi_group: PpiGroup<'d, AnyGroup>, - _p: PhantomData<&'d ()>, -} - -impl<'d> BufferedUarteRx<'d> { - /// Create a new BufferedUarte without hardware flow control. - /// - /// # Panics - /// - /// Panics if `rx_buffer.len()` is odd. - #[allow(clippy::too_many_arguments)] - pub fn new( - uarte: Peri<'d, U>, - timer: Peri<'d, T>, - ppi_ch1: Peri<'d, impl ConfigurableChannel>, - ppi_ch2: Peri<'d, impl ConfigurableChannel>, - ppi_group: Peri<'d, impl Group>, - _irq: impl interrupt::typelevel::Binding> + 'd, - rxd: Peri<'d, impl GpioPin>, - config: Config, - rx_buffer: &'d mut [u8], - ) -> Self { - Self::new_inner( - uarte, - timer, - ppi_ch1.into(), - ppi_ch2.into(), - ppi_group.into(), - rxd.into(), - None, - config, - rx_buffer, - ) - } - - /// Create a new BufferedUarte with hardware flow control (RTS/CTS) - /// - /// # Panics - /// - /// Panics if `rx_buffer.len()` is odd. - #[allow(clippy::too_many_arguments)] - pub fn new_with_rts( - uarte: Peri<'d, U>, - timer: Peri<'d, T>, - ppi_ch1: Peri<'d, impl ConfigurableChannel>, - ppi_ch2: Peri<'d, impl ConfigurableChannel>, - ppi_group: Peri<'d, impl Group>, - rxd: Peri<'d, impl GpioPin>, - rts: Peri<'d, impl GpioPin>, - _irq: impl interrupt::typelevel::Binding> + 'd, - config: Config, - rx_buffer: &'d mut [u8], - ) -> Self { - Self::new_inner( - uarte, - timer, - ppi_ch1.into(), - ppi_ch2.into(), - ppi_group.into(), - rxd.into(), - Some(rts.into()), - config, - rx_buffer, - ) - } - - #[allow(clippy::too_many_arguments)] - fn new_inner( - peri: Peri<'d, U>, - timer: Peri<'d, T>, - ppi_ch1: Peri<'d, AnyConfigurableChannel>, - ppi_ch2: Peri<'d, AnyConfigurableChannel>, - ppi_group: Peri<'d, AnyGroup>, - rxd: Peri<'d, AnyPin>, - rts: Option>, - config: Config, - rx_buffer: &'d mut [u8], - ) -> Self { - let r = U::regs(); - let irq = U::Interrupt::IRQ; - let state = U::state(); - let _buffered_state = U::buffered_state(); - - configure(r, config, rts.is_some()); - - let this = Self::new_innerer(peri, timer, ppi_ch1, ppi_ch2, ppi_group, rxd, rts, rx_buffer); - - r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); - irq.pend(); - unsafe { irq.enable() }; - - state.tx_rx_refcount.store(1, Ordering::Relaxed); - - this - } - - #[allow(clippy::too_many_arguments)] - fn new_innerer( - _peri: Peri<'d, U>, - timer: Peri<'d, T>, - ppi_ch1: Peri<'d, AnyConfigurableChannel>, - ppi_ch2: Peri<'d, AnyConfigurableChannel>, - ppi_group: Peri<'d, AnyGroup>, - rxd: Peri<'d, AnyPin>, - rts: Option>, - rx_buffer: &'d mut [u8], - ) -> Self { - assert!(rx_buffer.len() % 2 == 0); - - let r = U::regs(); - let state = U::state(); - let buffered_state = U::buffered_state(); - - configure_rx_pins(r, rxd, rts); - - // Initialize state - buffered_state.rx_started_count.store(0, Ordering::Relaxed); - buffered_state.rx_ended_count.store(0, Ordering::Relaxed); - buffered_state.rx_started.store(false, Ordering::Relaxed); - buffered_state.rx_overrun.store(false, Ordering::Relaxed); - let rx_len = rx_buffer.len().min(EASY_DMA_SIZE * 2); - unsafe { buffered_state.rx_buf.init(rx_buffer.as_mut_ptr(), rx_len) }; - - // clear errors - let errors = r.errorsrc().read(); - r.errorsrc().write_value(errors); - - r.events_rxstarted().write_value(0); - r.events_error().write_value(0); - r.events_endrx().write_value(0); - - // Enable interrupts - r.intenset().write(|w| { - w.set_endtx(true); - w.set_rxstarted(true); - w.set_error(true); - w.set_endrx(true); - }); - - // Configure byte counter. - let timer = Timer::new_counter(timer); - timer.cc(1).write(rx_len as u32 * 2); - timer.cc(1).short_compare_clear(); - timer.clear(); - timer.start(); - - let mut ppi_ch1 = Ppi::new_one_to_one(ppi_ch1, Event::from_reg(r.events_rxdrdy()), timer.task_count()); - ppi_ch1.enable(); - - buffered_state - .rx_ppi_ch - .store(ppi_ch2.number() as u8, Ordering::Relaxed); - let mut ppi_group = PpiGroup::new(ppi_group); - let mut ppi_ch2 = Ppi::new_one_to_two( - ppi_ch2, - Event::from_reg(r.events_endrx()), - Task::from_reg(r.tasks_startrx()), - ppi_group.task_disable_all(), - ); - ppi_ch2.disable(); - ppi_group.add_channel(&ppi_ch2); - - Self { - r, - state, - buffered_state, - timer, - _ppi_ch1: ppi_ch1, - _ppi_ch2: ppi_ch2, - _ppi_group: ppi_group, - _p: PhantomData, - } - } - - /// Pull some bytes from this source into the specified buffer, returning how many bytes were read. - pub async fn read(&mut self, buf: &mut [u8]) -> Result { - let data = self.fill_buf().await?; - let n = data.len().min(buf.len()); - buf[..n].copy_from_slice(&data[..n]); - self.consume(n); - Ok(n) - } - - /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. - pub fn fill_buf(&mut self) -> impl Future> { - let r = self.r; - let s = self.buffered_state; - let ss = self.state; - let timer = &self.timer; - poll_fn(move |cx| { - compiler_fence(Ordering::SeqCst); - //trace!("poll_read"); - - if s.rx_overrun.swap(false, Ordering::Acquire) { - return Poll::Ready(Err(Error::Overrun)); - } - - // Read the RXDRDY counter. - timer.cc(0).capture(); - let mut end = timer.cc(0).read() as usize; - //trace!(" rxdrdy count = {:?}", end); - - // We've set a compare channel that resets the counter to 0 when it reaches `len*2`. - // However, it's unclear if that's instant, or there's a small window where you can - // still read `len()*2`. - // This could happen if in one clock cycle the counter is updated, and in the next the - // clear takes effect. The docs are very sparse, they just say "Task delays: After TIMER - // is started, the CLEAR, COUNT, and STOP tasks are guaranteed to take effect within one - // clock cycle of the PCLK16M." :shrug: - // So, we wrap the counter ourselves, just in case. - if end > s.rx_buf.len() * 2 { - end = 0 - } - - // This logic mirrors `atomic_ring_buffer::Reader::pop_buf()` - let mut start = s.rx_buf.start.load(Ordering::Relaxed); - let len = s.rx_buf.len(); - if start == end { - //trace!(" empty"); - ss.rx_waker.register(cx.waker()); - r.intenset().write(|w| w.set_rxdrdy(true)); - return Poll::Pending; - } - - if start >= len { - start -= len - } - if end >= len { - end -= len - } - - let n = if end > start { end - start } else { len - start }; - assert!(n != 0); - //trace!(" uarte ringbuf: pop_buf {:?}..{:?}", start, start + n); - - let buf = s.rx_buf.buf.load(Ordering::Relaxed); - Poll::Ready(Ok(unsafe { slice::from_raw_parts(buf.add(start), n) })) - }) - } - - /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. - pub fn consume(&mut self, amt: usize) { - if amt == 0 { - return; - } - - let s = self.buffered_state; - let mut rx = unsafe { s.rx_buf.reader() }; - rx.pop_done(amt); - self.r.intenset().write(|w| w.set_rxstarted(true)); - } - - /// we are ready to read if there is data in the buffer - fn read_ready(&self) -> Result { - let state = self.buffered_state; - if state.rx_overrun.swap(false, Ordering::Acquire) { - return Err(Error::Overrun); - } - Ok(!state.rx_buf.is_empty()) - } -} - -impl<'a> Drop for BufferedUarteRx<'a> { - fn drop(&mut self) { - self._ppi_group.disable_all(); - - let r = self.r; - - self.timer.stop(); - - r.intenclr().write(|w| { - w.set_rxdrdy(true); - w.set_rxstarted(true); - w.set_rxto(true); - }); - r.events_rxto().write_value(0); - r.tasks_stoprx().write_value(1); - while r.events_rxto().read() == 0 {} - - let s = self.buffered_state; - unsafe { s.rx_buf.deinit() } - - let s = self.state; - drop_tx_rx(r, s); - } -} - -mod _embedded_io { - use super::*; - - impl embedded_io_async::Error for Error { - fn kind(&self) -> embedded_io_async::ErrorKind { - match *self { - Error::Overrun => embedded_io_async::ErrorKind::OutOfMemory, - } - } - } - - impl<'d> embedded_io_async::ErrorType for BufferedUarte<'d> { - type Error = Error; - } - - impl<'d> embedded_io_async::ErrorType for BufferedUarteRx<'d> { - type Error = Error; - } - - impl<'d> embedded_io_async::ErrorType for BufferedUarteTx<'d> { - type Error = Error; - } - - impl<'d> embedded_io_async::Read for BufferedUarte<'d> { - async fn read(&mut self, buf: &mut [u8]) -> Result { - self.read(buf).await - } - } - - impl<'d> embedded_io_async::Read for BufferedUarteRx<'d> { - async fn read(&mut self, buf: &mut [u8]) -> Result { - self.read(buf).await - } - } - - impl<'d> embedded_io_async::ReadReady for BufferedUarte<'d> { - fn read_ready(&mut self) -> Result { - self.rx.read_ready() - } - } - - impl<'d> embedded_io_async::ReadReady for BufferedUarteRx<'d> { - fn read_ready(&mut self) -> Result { - let state = self.buffered_state; - Ok(!state.rx_buf.is_empty()) - } - } - - impl<'d> embedded_io_async::BufRead for BufferedUarte<'d> { - async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { - self.fill_buf().await - } - - fn consume(&mut self, amt: usize) { - self.consume(amt) - } - } - - impl<'d> embedded_io_async::BufRead for BufferedUarteRx<'d> { - async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { - self.fill_buf().await - } - - fn consume(&mut self, amt: usize) { - self.consume(amt) - } - } - - impl<'d> embedded_io_async::Write for BufferedUarte<'d> { - async fn write(&mut self, buf: &[u8]) -> Result { - self.write(buf).await - } - - async fn flush(&mut self) -> Result<(), Self::Error> { - self.flush().await - } - } - - impl<'d> embedded_io_async::Write for BufferedUarteTx<'d> { - async fn write(&mut self, buf: &[u8]) -> Result { - self.write(buf).await - } - - async fn flush(&mut self) -> Result<(), Self::Error> { - self.flush().await - } - } -} diff --git a/embassy-nrf/src/buffered_uarte/mod.rs b/embassy-nrf/src/buffered_uarte/mod.rs new file mode 100644 index 000000000..75d84baac --- /dev/null +++ b/embassy-nrf/src/buffered_uarte/mod.rs @@ -0,0 +1,14 @@ +//! Async buffered UART driver. +//! +//! Note that discarding a future from a read or write operation may lead to losing +//! data. For example, when using `futures_util::future::select` and completion occurs +//! on the "other" future, you should capture the incomplete future and continue to use +//! it for the next read or write. This pattern is a consideration for all IO, and not +//! just serial communications. +//! +//! Please also see [crate::uarte] to understand when [BufferedUarte] should be used. +#[cfg_attr(not(feature = "_nrf54l"), path = "v1.rs")] +#[cfg_attr(feature = "_nrf54l", path = "v2.rs")] +mod _version; + +pub use _version::*; diff --git a/embassy-nrf/src/buffered_uarte/v1.rs b/embassy-nrf/src/buffered_uarte/v1.rs new file mode 100644 index 000000000..07de22717 --- /dev/null +++ b/embassy-nrf/src/buffered_uarte/v1.rs @@ -0,0 +1,951 @@ +//! Async buffered UART driver. +//! +//! Note that discarding a future from a read or write operation may lead to losing +//! data. For example, when using `futures_util::future::select` and completion occurs +//! on the "other" future, you should capture the incomplete future and continue to use +//! it for the next read or write. This pattern is a consideration for all IO, and not +//! just serial communications. +//! +//! Please also see [crate::uarte] to understand when [BufferedUarte] should be used. + +use core::cmp::min; +use core::future::{Future, poll_fn}; +use core::marker::PhantomData; +use core::slice; +use core::sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering, compiler_fence}; +use core::task::Poll; + +use embassy_hal_internal::Peri; +use embassy_hal_internal::atomic_ring_buffer::RingBuffer; +use pac::uarte::vals; +// Re-export SVD variants to allow user to directly set values +pub use pac::uarte::vals::{Baudrate, ConfigParity as Parity}; + +use crate::gpio::{AnyPin, Pin as GpioPin}; +use crate::interrupt::InterruptExt; +use crate::interrupt::typelevel::Interrupt; +use crate::ppi::{ + self, AnyConfigurableChannel, AnyGroup, Channel, ConfigurableChannel, Event, Group, Ppi, PpiGroup, Task, +}; +use crate::timer::{Instance as TimerInstance, Timer}; +use crate::uarte::{Config, Instance as UarteInstance, configure, configure_rx_pins, configure_tx_pins, drop_tx_rx}; +use crate::{EASY_DMA_SIZE, interrupt, pac}; + +pub(crate) struct State { + tx_buf: RingBuffer, + tx_count: AtomicUsize, + + rx_buf: RingBuffer, + rx_started: AtomicBool, + rx_started_count: AtomicU8, + rx_ended_count: AtomicU8, + rx_ppi_ch: AtomicU8, + rx_overrun: AtomicBool, +} + +/// UART error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// Buffer Overrun + Overrun, +} + +impl State { + pub(crate) const fn new() -> Self { + Self { + tx_buf: RingBuffer::new(), + tx_count: AtomicUsize::new(0), + + rx_buf: RingBuffer::new(), + rx_started: AtomicBool::new(false), + rx_started_count: AtomicU8::new(0), + rx_ended_count: AtomicU8::new(0), + rx_ppi_ch: AtomicU8::new(0), + rx_overrun: AtomicBool::new(false), + } + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + //trace!("irq: start"); + let r = U::regs(); + let ss = U::state(); + let s = U::buffered_state(); + + if let Some(mut rx) = unsafe { s.rx_buf.try_writer() } { + let buf_len = s.rx_buf.len(); + let half_len = buf_len / 2; + + if r.events_error().read() != 0 { + r.events_error().write_value(0); + let errs = r.errorsrc().read(); + r.errorsrc().write_value(errs); + + if errs.overrun() { + s.rx_overrun.store(true, Ordering::Release); + ss.rx_waker.wake(); + } + } + + // Received some bytes, wake task. + if r.inten().read().rxdrdy() && r.events_rxdrdy().read() != 0 { + r.intenclr().write(|w| w.set_rxdrdy(true)); + r.events_rxdrdy().write_value(0); + ss.rx_waker.wake(); + } + + if r.events_dma().rx().end().read() != 0 { + //trace!(" irq_rx: endrx"); + r.events_dma().rx().end().write_value(0); + + let val = s.rx_ended_count.load(Ordering::Relaxed); + s.rx_ended_count.store(val.wrapping_add(1), Ordering::Relaxed); + } + + if r.events_dma().rx().ready().read() != 0 || !s.rx_started.load(Ordering::Relaxed) { + //trace!(" irq_rx: rxstarted"); + let (ptr, len) = rx.push_buf(); + if len >= half_len { + r.events_dma().rx().ready().write_value(0); + + //trace!(" irq_rx: starting second {:?}", half_len); + + // Set up the DMA read + r.dma().rx().ptr().write_value(ptr as u32); + r.dma().rx().maxcnt().write(|w| w.set_maxcnt(half_len as _)); + + let chn = s.rx_ppi_ch.load(Ordering::Relaxed); + + // Enable endrx -> startrx PPI channel. + // From this point on, if endrx happens, startrx is automatically fired. + ppi::regs().chenset().write(|w| w.0 = 1 << chn); + + // It is possible that endrx happened BEFORE enabling the PPI. In this case + // the PPI channel doesn't trigger, and we'd hang. We have to detect this + // and manually start. + + // check again in case endrx has happened between the last check and now. + if r.events_dma().rx().end().read() != 0 { + //trace!(" irq_rx: endrx"); + r.events_dma().rx().end().write_value(0); + + let val = s.rx_ended_count.load(Ordering::Relaxed); + s.rx_ended_count.store(val.wrapping_add(1), Ordering::Relaxed); + } + + let rx_ended = s.rx_ended_count.load(Ordering::Relaxed); + let rx_started = s.rx_started_count.load(Ordering::Relaxed); + + // If we started the same amount of transfers as ended, the last rxend has + // already occured. + let rxend_happened = rx_started == rx_ended; + + // Check if the PPI channel is still enabled. The PPI channel disables itself + // when it fires, so if it's still enabled it hasn't fired. + let ppi_ch_enabled = ppi::regs().chen().read().ch(chn as _); + + // if rxend happened, and the ppi channel hasn't fired yet, the rxend got missed. + // this condition also naturally matches if `!started`, needed to kickstart the DMA. + if rxend_happened && ppi_ch_enabled { + //trace!("manually starting."); + + // disable the ppi ch, it's of no use anymore. + ppi::regs().chenclr().write(|w| w.set_ch(chn as _, true)); + + // manually start + r.tasks_dma().rx().start().write_value(1); + } + + rx.push_done(half_len); + + s.rx_started_count.store(rx_started.wrapping_add(1), Ordering::Relaxed); + s.rx_started.store(true, Ordering::Relaxed); + } else { + //trace!(" irq_rx: rxstarted no buf"); + r.intenclr().write(|w| w.set_dmarxready(true)); + } + } + } + + // ============================= + + if let Some(mut tx) = unsafe { s.tx_buf.try_reader() } { + // TX end + if r.events_dma().tx().end().read() != 0 { + r.events_dma().tx().end().write_value(0); + + let n = s.tx_count.load(Ordering::Relaxed); + //trace!(" irq_tx: endtx {:?}", n); + tx.pop_done(n); + ss.tx_waker.wake(); + s.tx_count.store(0, Ordering::Relaxed); + } + + // If not TXing, start. + if s.tx_count.load(Ordering::Relaxed) == 0 { + let (ptr, len) = tx.pop_buf(); + let len = len.min(EASY_DMA_SIZE); + if len != 0 { + //trace!(" irq_tx: starting {:?}", len); + s.tx_count.store(len, Ordering::Relaxed); + + // Set up the DMA write + r.dma().tx().ptr().write_value(ptr as u32); + r.dma().tx().maxcnt().write(|w| w.set_maxcnt(len as _)); + + // Start UARTE Transmit transaction + r.tasks_dma().tx().start().write_value(1); + } + } + } + + //trace!("irq: end"); + } +} + +/// Buffered UARTE driver. +pub struct BufferedUarte<'d> { + tx: BufferedUarteTx<'d>, + rx: BufferedUarteRx<'d>, +} + +impl<'d> Unpin for BufferedUarte<'d> {} + +impl<'d> BufferedUarte<'d> { + /// Create a new BufferedUarte without hardware flow control. + /// + /// # Panics + /// + /// Panics if `rx_buffer.len()` is odd. + #[allow(clippy::too_many_arguments)] + pub fn new( + uarte: Peri<'d, U>, + timer: Peri<'d, T>, + ppi_ch1: Peri<'d, impl ConfigurableChannel>, + ppi_ch2: Peri<'d, impl ConfigurableChannel>, + ppi_group: Peri<'d, impl Group>, + rxd: Peri<'d, impl GpioPin>, + txd: Peri<'d, impl GpioPin>, + _irq: impl interrupt::typelevel::Binding> + 'd, + config: Config, + rx_buffer: &'d mut [u8], + tx_buffer: &'d mut [u8], + ) -> Self { + Self::new_inner( + uarte, + timer, + ppi_ch1.into(), + ppi_ch2.into(), + ppi_group.into(), + rxd.into(), + txd.into(), + None, + None, + config, + rx_buffer, + tx_buffer, + ) + } + + /// Create a new BufferedUarte with hardware flow control (RTS/CTS) + /// + /// # Panics + /// + /// Panics if `rx_buffer.len()` is odd. + #[allow(clippy::too_many_arguments)] + pub fn new_with_rtscts( + uarte: Peri<'d, U>, + timer: Peri<'d, T>, + ppi_ch1: Peri<'d, impl ConfigurableChannel>, + ppi_ch2: Peri<'d, impl ConfigurableChannel>, + ppi_group: Peri<'d, impl Group>, + rxd: Peri<'d, impl GpioPin>, + txd: Peri<'d, impl GpioPin>, + cts: Peri<'d, impl GpioPin>, + rts: Peri<'d, impl GpioPin>, + _irq: impl interrupt::typelevel::Binding> + 'd, + config: Config, + rx_buffer: &'d mut [u8], + tx_buffer: &'d mut [u8], + ) -> Self { + Self::new_inner( + uarte, + timer, + ppi_ch1.into(), + ppi_ch2.into(), + ppi_group.into(), + rxd.into(), + txd.into(), + Some(cts.into()), + Some(rts.into()), + config, + rx_buffer, + tx_buffer, + ) + } + + #[allow(clippy::too_many_arguments)] + fn new_inner( + peri: Peri<'d, U>, + timer: Peri<'d, T>, + ppi_ch1: Peri<'d, AnyConfigurableChannel>, + ppi_ch2: Peri<'d, AnyConfigurableChannel>, + ppi_group: Peri<'d, AnyGroup>, + rxd: Peri<'d, AnyPin>, + txd: Peri<'d, AnyPin>, + cts: Option>, + rts: Option>, + config: Config, + rx_buffer: &'d mut [u8], + tx_buffer: &'d mut [u8], + ) -> Self { + let r = U::regs(); + let irq = U::Interrupt::IRQ; + let state = U::state(); + + configure(r, config, cts.is_some()); + + let tx = BufferedUarteTx::new_innerer(unsafe { peri.clone_unchecked() }, txd, cts, tx_buffer); + let rx = BufferedUarteRx::new_innerer(peri, timer, ppi_ch1, ppi_ch2, ppi_group, rxd, rts, rx_buffer); + + r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); + irq.pend(); + unsafe { irq.enable() }; + + state.tx_rx_refcount.store(2, Ordering::Relaxed); + + Self { tx, rx } + } + + /// Adjust the baud rate to the provided value. + pub fn set_baudrate(&mut self, baudrate: Baudrate) { + self.tx.set_baudrate(baudrate); + } + + /// Split the UART in reader and writer parts. + /// + /// This allows reading and writing concurrently from independent tasks. + pub fn split(self) -> (BufferedUarteRx<'d>, BufferedUarteTx<'d>) { + (self.rx, self.tx) + } + + /// Split the UART in reader and writer parts, by reference. + /// + /// The returned halves borrow from `self`, so you can drop them and go back to using + /// the "un-split" `self`. This allows temporarily splitting the UART. + pub fn split_by_ref(&mut self) -> (&mut BufferedUarteRx<'d>, &mut BufferedUarteTx<'d>) { + (&mut self.rx, &mut self.tx) + } + + /// Pull some bytes from this source into the specified buffer, returning how many bytes were read. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + self.rx.read(buf).await + } + + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + pub async fn fill_buf(&mut self) -> Result<&[u8], Error> { + self.rx.fill_buf().await + } + + /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. + pub fn consume(&mut self, amt: usize) { + self.rx.consume(amt) + } + + /// Write a buffer into this writer, returning how many bytes were written. + pub async fn write(&mut self, buf: &[u8]) -> Result { + self.tx.write(buf).await + } + + /// Try writing a buffer without waiting, returning how many bytes were written. + pub fn try_write(&mut self, buf: &[u8]) -> Result { + self.tx.try_write(buf) + } + + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. + pub async fn flush(&mut self) -> Result<(), Error> { + self.tx.flush().await + } +} + +/// Reader part of the buffered UARTE driver. +pub struct BufferedUarteTx<'d> { + r: pac::uarte::Uarte, + _irq: interrupt::Interrupt, + state: &'static crate::uarte::State, + buffered_state: &'static State, + _p: PhantomData<&'d ()>, +} + +impl<'d> BufferedUarteTx<'d> { + /// Create a new BufferedUarteTx without hardware flow control. + pub fn new( + uarte: Peri<'d, U>, + txd: Peri<'d, impl GpioPin>, + _irq: impl interrupt::typelevel::Binding> + 'd, + config: Config, + tx_buffer: &'d mut [u8], + ) -> Self { + Self::new_inner(uarte, txd.into(), None, config, tx_buffer) + } + + /// Create a new BufferedUarte with hardware flow control (RTS/CTS) + /// + /// # Panics + /// + /// Panics if `rx_buffer.len()` is odd. + pub fn new_with_cts( + uarte: Peri<'d, U>, + txd: Peri<'d, impl GpioPin>, + cts: Peri<'d, impl GpioPin>, + _irq: impl interrupt::typelevel::Binding> + 'd, + config: Config, + tx_buffer: &'d mut [u8], + ) -> Self { + Self::new_inner(uarte, txd.into(), Some(cts.into()), config, tx_buffer) + } + + fn new_inner( + peri: Peri<'d, U>, + txd: Peri<'d, AnyPin>, + cts: Option>, + config: Config, + tx_buffer: &'d mut [u8], + ) -> Self { + let r = U::regs(); + let irq = U::Interrupt::IRQ; + let state = U::state(); + let _buffered_state = U::buffered_state(); + + configure(r, config, cts.is_some()); + + let this = Self::new_innerer(peri, txd, cts, tx_buffer); + + r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); + irq.pend(); + unsafe { irq.enable() }; + + state.tx_rx_refcount.store(1, Ordering::Relaxed); + + this + } + + fn new_innerer( + _peri: Peri<'d, U>, + txd: Peri<'d, AnyPin>, + cts: Option>, + tx_buffer: &'d mut [u8], + ) -> Self { + let r = U::regs(); + let irq = U::Interrupt::IRQ; + let state = U::state(); + let buffered_state = U::buffered_state(); + + configure_tx_pins(r, txd, cts); + + // Initialize state + buffered_state.tx_count.store(0, Ordering::Relaxed); + let len = tx_buffer.len(); + unsafe { buffered_state.tx_buf.init(tx_buffer.as_mut_ptr(), len) }; + + r.events_dma().tx().ready().write_value(0); + + // Enable interrupts + r.intenset().write(|w| { + w.set_dmatxend(true); + }); + + Self { + r, + _irq: irq, + state, + buffered_state, + _p: PhantomData, + } + } + + /// Write a buffer into this writer, returning how many bytes were written. + pub fn write<'a>(&'a mut self, buf: &'a [u8]) -> impl Future> + 'a + use<'a, 'd> { + poll_fn(move |cx| { + //trace!("poll_write: {:?}", buf.len()); + let ss = self.state; + let s = self.buffered_state; + let mut tx = unsafe { s.tx_buf.writer() }; + + let tx_buf = tx.push_slice(); + if tx_buf.is_empty() { + //trace!("poll_write: pending"); + ss.tx_waker.register(cx.waker()); + return Poll::Pending; + } + + let n = min(tx_buf.len(), buf.len()); + tx_buf[..n].copy_from_slice(&buf[..n]); + tx.push_done(n); + + //trace!("poll_write: queued {:?}", n); + + compiler_fence(Ordering::SeqCst); + self._irq.pend(); + + Poll::Ready(Ok(n)) + }) + } + + /// Try writing a buffer without waiting, returning how many bytes were written. + pub fn try_write(&mut self, buf: &[u8]) -> Result { + //trace!("poll_write: {:?}", buf.len()); + let s = self.buffered_state; + let mut tx = unsafe { s.tx_buf.writer() }; + + let tx_buf = tx.push_slice(); + if tx_buf.is_empty() { + return Ok(0); + } + + let n = min(tx_buf.len(), buf.len()); + tx_buf[..n].copy_from_slice(&buf[..n]); + tx.push_done(n); + + //trace!("poll_write: queued {:?}", n); + + compiler_fence(Ordering::SeqCst); + self._irq.pend(); + + Ok(n) + } + + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. + pub fn flush(&mut self) -> impl Future> + '_ { + let ss = self.state; + let s = self.buffered_state; + poll_fn(move |cx| { + //trace!("poll_flush"); + if !s.tx_buf.is_empty() { + //trace!("poll_flush: pending"); + ss.tx_waker.register(cx.waker()); + return Poll::Pending; + } + + Poll::Ready(Ok(())) + }) + } + + /// Adjust the baud rate to the provided value. + pub fn set_baudrate(&mut self, baudrate: Baudrate) { + self.r.baudrate().write(|w| w.set_baudrate(baudrate)); + } +} + +impl<'a> Drop for BufferedUarteTx<'a> { + fn drop(&mut self) { + let r = self.r; + + r.intenclr().write(|w| { + w.set_txdrdy(true); + w.set_dmatxready(true); + w.set_txstopped(true); + }); + r.events_txstopped().write_value(0); + r.tasks_dma().tx().stop().write_value(1); + while r.events_txstopped().read() == 0 {} + + let s = self.buffered_state; + unsafe { s.tx_buf.deinit() } + + let s = self.state; + drop_tx_rx(r, s); + } +} + +/// Reader part of the buffered UARTE driver. +pub struct BufferedUarteRx<'d> { + r: pac::uarte::Uarte, + state: &'static crate::uarte::State, + buffered_state: &'static State, + timer: Timer<'d>, + _ppi_ch1: Ppi<'d, AnyConfigurableChannel, 1, 1>, + _ppi_ch2: Ppi<'d, AnyConfigurableChannel, 1, 2>, + _ppi_group: PpiGroup<'d, AnyGroup>, + _p: PhantomData<&'d ()>, +} + +impl<'d> BufferedUarteRx<'d> { + /// Create a new BufferedUarte without hardware flow control. + /// + /// # Panics + /// + /// Panics if `rx_buffer.len()` is odd. + #[allow(clippy::too_many_arguments)] + pub fn new( + uarte: Peri<'d, U>, + timer: Peri<'d, T>, + ppi_ch1: Peri<'d, impl ConfigurableChannel>, + ppi_ch2: Peri<'d, impl ConfigurableChannel>, + ppi_group: Peri<'d, impl Group>, + _irq: impl interrupt::typelevel::Binding> + 'd, + rxd: Peri<'d, impl GpioPin>, + config: Config, + rx_buffer: &'d mut [u8], + ) -> Self { + Self::new_inner( + uarte, + timer, + ppi_ch1.into(), + ppi_ch2.into(), + ppi_group.into(), + rxd.into(), + None, + config, + rx_buffer, + ) + } + + /// Create a new BufferedUarte with hardware flow control (RTS/CTS) + /// + /// # Panics + /// + /// Panics if `rx_buffer.len()` is odd. + #[allow(clippy::too_many_arguments)] + pub fn new_with_rts( + uarte: Peri<'d, U>, + timer: Peri<'d, T>, + ppi_ch1: Peri<'d, impl ConfigurableChannel>, + ppi_ch2: Peri<'d, impl ConfigurableChannel>, + ppi_group: Peri<'d, impl Group>, + rxd: Peri<'d, impl GpioPin>, + rts: Peri<'d, impl GpioPin>, + _irq: impl interrupt::typelevel::Binding> + 'd, + config: Config, + rx_buffer: &'d mut [u8], + ) -> Self { + Self::new_inner( + uarte, + timer, + ppi_ch1.into(), + ppi_ch2.into(), + ppi_group.into(), + rxd.into(), + Some(rts.into()), + config, + rx_buffer, + ) + } + + #[allow(clippy::too_many_arguments)] + fn new_inner( + peri: Peri<'d, U>, + timer: Peri<'d, T>, + ppi_ch1: Peri<'d, AnyConfigurableChannel>, + ppi_ch2: Peri<'d, AnyConfigurableChannel>, + ppi_group: Peri<'d, AnyGroup>, + rxd: Peri<'d, AnyPin>, + rts: Option>, + config: Config, + rx_buffer: &'d mut [u8], + ) -> Self { + let r = U::regs(); + let irq = U::Interrupt::IRQ; + let state = U::state(); + let _buffered_state = U::buffered_state(); + + configure(r, config, rts.is_some()); + + let this = Self::new_innerer(peri, timer, ppi_ch1, ppi_ch2, ppi_group, rxd, rts, rx_buffer); + + r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); + irq.pend(); + unsafe { irq.enable() }; + + state.tx_rx_refcount.store(1, Ordering::Relaxed); + + this + } + + #[allow(clippy::too_many_arguments)] + fn new_innerer( + _peri: Peri<'d, U>, + timer: Peri<'d, T>, + ppi_ch1: Peri<'d, AnyConfigurableChannel>, + ppi_ch2: Peri<'d, AnyConfigurableChannel>, + ppi_group: Peri<'d, AnyGroup>, + rxd: Peri<'d, AnyPin>, + rts: Option>, + rx_buffer: &'d mut [u8], + ) -> Self { + assert!(rx_buffer.len() % 2 == 0); + + let r = U::regs(); + let state = U::state(); + let buffered_state = U::buffered_state(); + + configure_rx_pins(r, rxd, rts); + + // Initialize state + buffered_state.rx_started_count.store(0, Ordering::Relaxed); + buffered_state.rx_ended_count.store(0, Ordering::Relaxed); + buffered_state.rx_started.store(false, Ordering::Relaxed); + buffered_state.rx_overrun.store(false, Ordering::Relaxed); + let rx_len = rx_buffer.len().min(EASY_DMA_SIZE * 2); + unsafe { buffered_state.rx_buf.init(rx_buffer.as_mut_ptr(), rx_len) }; + + // clear errors + let errors = r.errorsrc().read(); + r.errorsrc().write_value(errors); + + r.events_dma().rx().ready().write_value(0); + r.events_error().write_value(0); + r.events_dma().rx().end().write_value(0); + + // Enable interrupts + r.intenset().write(|w| { + w.set_dmatxend(true); + w.set_dmarxready(true); + w.set_error(true); + w.set_dmarxend(true); + }); + + // Configure byte counter. + let timer = Timer::new_counter(timer); + timer.cc(1).write(rx_len as u32 * 2); + timer.cc(1).short_compare_clear(); + timer.clear(); + timer.start(); + + let mut ppi_ch1 = Ppi::new_one_to_one(ppi_ch1, Event::from_reg(r.events_rxdrdy()), timer.task_count()); + ppi_ch1.enable(); + + buffered_state + .rx_ppi_ch + .store(ppi_ch2.number() as u8, Ordering::Relaxed); + let mut ppi_group = PpiGroup::new(ppi_group); + let mut ppi_ch2 = Ppi::new_one_to_two( + ppi_ch2, + Event::from_reg(r.events_dma().rx().end()), + Task::from_reg(r.tasks_dma().rx().start()), + ppi_group.task_disable_all(), + ); + ppi_ch2.disable(); + ppi_group.add_channel(&ppi_ch2); + + Self { + r, + state, + buffered_state, + timer, + _ppi_ch1: ppi_ch1, + _ppi_ch2: ppi_ch2, + _ppi_group: ppi_group, + _p: PhantomData, + } + } + + /// Pull some bytes from this source into the specified buffer, returning how many bytes were read. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + let data = self.fill_buf().await?; + let n = data.len().min(buf.len()); + buf[..n].copy_from_slice(&data[..n]); + self.consume(n); + Ok(n) + } + + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + pub fn fill_buf(&mut self) -> impl Future> { + let r = self.r; + let s = self.buffered_state; + let ss = self.state; + let timer = &self.timer; + poll_fn(move |cx| { + compiler_fence(Ordering::SeqCst); + //trace!("poll_read"); + + if s.rx_overrun.swap(false, Ordering::Acquire) { + return Poll::Ready(Err(Error::Overrun)); + } + + // Read the RXDRDY counter. + timer.cc(0).capture(); + let mut end = timer.cc(0).read() as usize; + //trace!(" rxdrdy count = {:?}", end); + + // We've set a compare channel that resets the counter to 0 when it reaches `len*2`. + // However, it's unclear if that's instant, or there's a small window where you can + // still read `len()*2`. + // This could happen if in one clock cycle the counter is updated, and in the next the + // clear takes effect. The docs are very sparse, they just say "Task delays: After TIMER + // is started, the CLEAR, COUNT, and STOP tasks are guaranteed to take effect within one + // clock cycle of the PCLK16M." :shrug: + // So, we wrap the counter ourselves, just in case. + if end > s.rx_buf.len() * 2 { + end = 0 + } + + // This logic mirrors `atomic_ring_buffer::Reader::pop_buf()` + let mut start = s.rx_buf.start.load(Ordering::Relaxed); + let len = s.rx_buf.len(); + if start == end { + //trace!(" empty"); + ss.rx_waker.register(cx.waker()); + r.intenset().write(|w| w.set_rxdrdy(true)); + return Poll::Pending; + } + + if start >= len { + start -= len + } + if end >= len { + end -= len + } + + let n = if end > start { end - start } else { len - start }; + assert!(n != 0); + //trace!(" uarte ringbuf: pop_buf {:?}..{:?}", start, start + n); + + let buf = s.rx_buf.buf.load(Ordering::Relaxed); + Poll::Ready(Ok(unsafe { slice::from_raw_parts(buf.add(start), n) })) + }) + } + + /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. + pub fn consume(&mut self, amt: usize) { + if amt == 0 { + return; + } + + let s = self.buffered_state; + let mut rx = unsafe { s.rx_buf.reader() }; + rx.pop_done(amt); + self.r.intenset().write(|w| w.set_dmarxready(true)); + } + + /// we are ready to read if there is data in the buffer + fn read_ready(&self) -> Result { + let state = self.buffered_state; + if state.rx_overrun.swap(false, Ordering::Acquire) { + return Err(Error::Overrun); + } + Ok(!state.rx_buf.is_empty()) + } +} + +impl<'a> Drop for BufferedUarteRx<'a> { + fn drop(&mut self) { + self._ppi_group.disable_all(); + + let r = self.r; + + self.timer.stop(); + + r.intenclr().write(|w| { + w.set_rxdrdy(true); + w.set_dmarxready(true); + w.set_rxto(true); + }); + r.events_rxto().write_value(0); + r.tasks_dma().rx().stop().write_value(1); + while r.events_rxto().read() == 0 {} + + let s = self.buffered_state; + unsafe { s.rx_buf.deinit() } + + let s = self.state; + drop_tx_rx(r, s); + } +} + +mod _embedded_io { + use super::*; + + impl embedded_io_async::Error for Error { + fn kind(&self) -> embedded_io_async::ErrorKind { + match *self { + Error::Overrun => embedded_io_async::ErrorKind::OutOfMemory, + } + } + } + + impl<'d> embedded_io_async::ErrorType for BufferedUarte<'d> { + type Error = Error; + } + + impl<'d> embedded_io_async::ErrorType for BufferedUarteRx<'d> { + type Error = Error; + } + + impl<'d> embedded_io_async::ErrorType for BufferedUarteTx<'d> { + type Error = Error; + } + + impl<'d> embedded_io_async::Read for BufferedUarte<'d> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.read(buf).await + } + } + + impl<'d> embedded_io_async::Read for BufferedUarteRx<'d> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.read(buf).await + } + } + + impl<'d> embedded_io_async::ReadReady for BufferedUarte<'d> { + fn read_ready(&mut self) -> Result { + self.rx.read_ready() + } + } + + impl<'d> embedded_io_async::ReadReady for BufferedUarteRx<'d> { + fn read_ready(&mut self) -> Result { + let state = self.buffered_state; + Ok(!state.rx_buf.is_empty()) + } + } + + impl<'d> embedded_io_async::BufRead for BufferedUarte<'d> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.fill_buf().await + } + + fn consume(&mut self, amt: usize) { + self.consume(amt) + } + } + + impl<'d> embedded_io_async::BufRead for BufferedUarteRx<'d> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.fill_buf().await + } + + fn consume(&mut self, amt: usize) { + self.consume(amt) + } + } + + impl<'d> embedded_io_async::Write for BufferedUarte<'d> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.flush().await + } + } + + impl<'d> embedded_io_async::Write for BufferedUarteTx<'d> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.flush().await + } + } +} diff --git a/embassy-nrf/src/buffered_uarte/v2.rs b/embassy-nrf/src/buffered_uarte/v2.rs new file mode 100644 index 000000000..d0d2d97d1 --- /dev/null +++ b/embassy-nrf/src/buffered_uarte/v2.rs @@ -0,0 +1,687 @@ +//! Async buffered UART driver. +//! +//! Note that discarding a future from a read or write operation may lead to losing +//! data. For example, when using `futures_util::future::select` and completion occurs +//! on the "other" future, you should capture the incomplete future and continue to use +//! it for the next read or write. This pattern is a consideration for all IO, and not +//! just serial communications. +//! +//! Please also see [crate::uarte] to understand when [BufferedUarte] should be used. +//! +//! The code is based on the generic buffered_uarte implementation but uses the nrf54l +//! frame timeout event to correctly determine the size of transferred data. +//! Counting of rxrdy events, used in the generic implementation, cannot be applied +//! to nrf54l chips, as they buffer up to 4 bytes in a single DMA transaction. +//! The only reliable way to find the number of bytes received is to stop the transfer, +//! wait for the DMA stopped event, and read the value in the rx.dma.amount register. +//! This also flushes all in-flight data to RAM. + +use core::cmp::min; +use core::future::{Future, poll_fn}; +use core::marker::PhantomData; +use core::slice; +use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering, compiler_fence}; +use core::task::Poll; + +use embassy_hal_internal::Peri; +use embassy_hal_internal::atomic_ring_buffer::RingBuffer; +use pac::uarte::vals; +// Re-export SVD variants to allow user to directly set values +pub use pac::uarte::vals::{Baudrate, ConfigParity as Parity}; + +use crate::gpio::{AnyPin, Pin as GpioPin}; +use crate::interrupt::typelevel::Interrupt; +use crate::uarte::{Config, Instance as UarteInstance, configure, configure_rx_pins, configure_tx_pins, drop_tx_rx}; +use crate::{EASY_DMA_SIZE, interrupt, pac}; + +pub(crate) struct State { + tx_buf: RingBuffer, + tx_count: AtomicUsize, + + rx_buf: RingBuffer, + rx_started: AtomicBool, +} + +/// UART error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + // No errors for now +} + +impl State { + pub(crate) const fn new() -> Self { + Self { + tx_buf: RingBuffer::new(), + tx_count: AtomicUsize::new(0), + + rx_buf: RingBuffer::new(), + rx_started: AtomicBool::new(false), + } + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + info!("irq: start"); + let r = U::regs(); + let ss = U::state(); + let s = U::buffered_state(); + + if let Some(mut rx) = unsafe { s.rx_buf.try_writer() } { + let buf_len = s.rx_buf.len(); + let half_len = buf_len / 2; + + if r.events_error().read() != 0 { + r.events_error().write_value(0); + let errs = r.errorsrc().read(); + r.errorsrc().write_value(errs); + + if errs.overrun() { + panic!("BufferedUarte UART overrun"); + } + } + + let first_run = !s.rx_started.swap(true, Ordering::Relaxed); + if r.events_dma().rx().end().read() != 0 || first_run { + //trace!(" irq_rx: endrx"); + r.events_dma().rx().end().write_value(0); + + if !first_run { + // Received some bytes, wake task. + let rxed = r.dma().rx().amount().read().amount() as usize; + rx.push_done(rxed); + ss.rx_waker.wake(); + } + + let (ptr, len) = rx.push_buf(); + if len == 0 { + panic!("BufferedUarte buffer overrun"); + } + + let len = if len > half_len { half_len } else { len }; + + // Set up the DMA read + r.dma().rx().ptr().write_value(ptr as u32); + r.dma().rx().maxcnt().write(|w| w.set_maxcnt(len as _)); + + // manually start + r.tasks_dma().rx().start().write_value(1); + } + } + + // ============================= + + if let Some(mut tx) = unsafe { s.tx_buf.try_reader() } { + // TX end + if r.events_dma().tx().end().read() != 0 { + r.events_dma().tx().end().write_value(0); + + let n = s.tx_count.load(Ordering::Relaxed); + //trace!(" irq_tx: endtx {:?}", n); + tx.pop_done(n); + ss.tx_waker.wake(); + s.tx_count.store(0, Ordering::Relaxed); + } + + // If not TXing, start. + if s.tx_count.load(Ordering::Relaxed) == 0 { + let (ptr, len) = tx.pop_buf(); + let len = len.min(EASY_DMA_SIZE); + if len != 0 { + //trace!(" irq_tx: starting {:?}", len); + s.tx_count.store(len, Ordering::Relaxed); + + // Set up the DMA write + r.dma().tx().ptr().write_value(ptr as u32); + r.dma().tx().maxcnt().write(|w| w.set_maxcnt(len as _)); + + // Start UARTE Transmit transaction + r.tasks_dma().tx().start().write_value(1); + } + } + } + + //trace!("irq: end"); + } +} + +/// Buffered UARTE driver. +pub struct BufferedUarte<'d, U: UarteInstance> { + tx: BufferedUarteTx<'d, U>, + rx: BufferedUarteRx<'d, U>, +} + +impl<'d, U: UarteInstance> Unpin for BufferedUarte<'d, U> {} + +impl<'d, U: UarteInstance> BufferedUarte<'d, U> { + /// Create a new BufferedUarte without hardware flow control. + #[allow(clippy::too_many_arguments)] + pub fn new( + uarte: Peri<'d, U>, + rxd: Peri<'d, impl GpioPin>, + txd: Peri<'d, impl GpioPin>, + _irq: impl interrupt::typelevel::Binding> + 'd, + config: Config, + rx_buffer: &'d mut [u8], + tx_buffer: &'d mut [u8], + ) -> Self { + Self::new_inner(uarte, rxd.into(), txd.into(), None, None, config, rx_buffer, tx_buffer) + } + + /// Create a new BufferedUarte with hardware flow control (RTS/CTS) + #[allow(clippy::too_many_arguments)] + pub fn new_with_rtscts( + uarte: Peri<'d, U>, + rxd: Peri<'d, impl GpioPin>, + txd: Peri<'d, impl GpioPin>, + cts: Peri<'d, impl GpioPin>, + rts: Peri<'d, impl GpioPin>, + _irq: impl interrupt::typelevel::Binding> + 'd, + config: Config, + rx_buffer: &'d mut [u8], + tx_buffer: &'d mut [u8], + ) -> Self { + Self::new_inner( + uarte, + rxd.into(), + txd.into(), + Some(cts.into()), + Some(rts.into()), + config, + rx_buffer, + tx_buffer, + ) + } + + #[allow(clippy::too_many_arguments)] + fn new_inner( + peri: Peri<'d, U>, + rxd: Peri<'d, AnyPin>, + txd: Peri<'d, AnyPin>, + cts: Option>, + rts: Option>, + config: Config, + rx_buffer: &'d mut [u8], + tx_buffer: &'d mut [u8], + ) -> Self { + configure(U::regs(), config, cts.is_some()); + + let tx = BufferedUarteTx::new_innerer(unsafe { peri.clone_unchecked() }, txd, cts, tx_buffer); + let rx = BufferedUarteRx::new_innerer(peri, rxd, rts, rx_buffer); + + U::regs().enable().write(|w| w.set_enable(vals::Enable::ENABLED)); + U::Interrupt::pend(); + unsafe { U::Interrupt::enable() }; + + U::state().tx_rx_refcount.store(2, Ordering::Relaxed); + + Self { tx, rx } + } + + /// Adjust the baud rate to the provided value. + pub fn set_baudrate(&mut self, baudrate: Baudrate) { + let r = U::regs(); + r.baudrate().write(|w| w.set_baudrate(baudrate)); + } + + /// Split the UART in reader and writer parts. + /// + /// This allows reading and writing concurrently from independent tasks. + pub fn split(self) -> (BufferedUarteRx<'d, U>, BufferedUarteTx<'d, U>) { + (self.rx, self.tx) + } + + /// Split the UART in reader and writer parts, by reference. + /// + /// The returned halves borrow from `self`, so you can drop them and go back to using + /// the "un-split" `self`. This allows temporarily splitting the UART. + pub fn split_by_ref(&mut self) -> (&mut BufferedUarteRx<'d, U>, &mut BufferedUarteTx<'d, U>) { + (&mut self.rx, &mut self.tx) + } + + /// Pull some bytes from this source into the specified buffer, returning how many bytes were read. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + self.rx.read(buf).await + } + + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + pub async fn fill_buf(&mut self) -> Result<&[u8], Error> { + self.rx.fill_buf().await + } + + /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. + pub fn consume(&mut self, amt: usize) { + self.rx.consume(amt) + } + + /// Write a buffer into this writer, returning how many bytes were written. + pub async fn write(&mut self, buf: &[u8]) -> Result { + self.tx.write(buf).await + } + + /// Try writing a buffer without waiting, returning how many bytes were written. + pub fn try_write(&mut self, buf: &[u8]) -> Result { + self.tx.try_write(buf) + } + + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. + pub async fn flush(&mut self) -> Result<(), Error> { + self.tx.flush().await + } +} + +/// Reader part of the buffered UARTE driver. +pub struct BufferedUarteTx<'d, U: UarteInstance> { + _peri: Peri<'d, U>, +} + +impl<'d, U: UarteInstance> BufferedUarteTx<'d, U> { + /// Create a new BufferedUarteTx without hardware flow control. + pub fn new( + uarte: Peri<'d, U>, + txd: Peri<'d, impl GpioPin>, + _irq: impl interrupt::typelevel::Binding> + 'd, + config: Config, + tx_buffer: &'d mut [u8], + ) -> Self { + Self::new_inner(uarte, txd.into(), None, config, tx_buffer) + } + + /// Create a new BufferedUarte with hardware flow control (RTS/CTS) + pub fn new_with_cts( + uarte: Peri<'d, U>, + txd: Peri<'d, impl GpioPin>, + cts: Peri<'d, impl GpioPin>, + _irq: impl interrupt::typelevel::Binding> + 'd, + config: Config, + tx_buffer: &'d mut [u8], + ) -> Self { + Self::new_inner(uarte, txd.into(), Some(cts.into()), config, tx_buffer) + } + + fn new_inner( + peri: Peri<'d, U>, + txd: Peri<'d, AnyPin>, + cts: Option>, + config: Config, + tx_buffer: &'d mut [u8], + ) -> Self { + configure(U::regs(), config, cts.is_some()); + + let this = Self::new_innerer(peri, txd, cts, tx_buffer); + + U::regs().enable().write(|w| w.set_enable(vals::Enable::ENABLED)); + U::Interrupt::pend(); + unsafe { U::Interrupt::enable() }; + + U::state().tx_rx_refcount.store(1, Ordering::Relaxed); + + this + } + + fn new_innerer( + peri: Peri<'d, U>, + txd: Peri<'d, AnyPin>, + cts: Option>, + tx_buffer: &'d mut [u8], + ) -> Self { + let r = U::regs(); + + configure_tx_pins(r, txd, cts); + + // Initialize state + let s = U::buffered_state(); + s.tx_count.store(0, Ordering::Relaxed); + let len = tx_buffer.len(); + unsafe { s.tx_buf.init(tx_buffer.as_mut_ptr(), len) }; + + r.events_dma().tx().ready().write_value(0); + + // Enable interrupts + r.intenset().write(|w| { + w.set_dmatxend(true); + }); + + Self { _peri: peri } + } + + /// Write a buffer into this writer, returning how many bytes were written. + pub fn write<'a>(&'a mut self, buf: &'a [u8]) -> impl Future> + 'a { + poll_fn(move |cx| { + //trace!("poll_write: {:?}", buf.len()); + let ss = U::state(); + let s = U::buffered_state(); + let mut tx = unsafe { s.tx_buf.writer() }; + + let tx_buf = tx.push_slice(); + if tx_buf.is_empty() { + //trace!("poll_write: pending"); + ss.tx_waker.register(cx.waker()); + return Poll::Pending; + } + + let n = min(tx_buf.len(), buf.len()); + tx_buf[..n].copy_from_slice(&buf[..n]); + tx.push_done(n); + + //trace!("poll_write: queued {:?}", n); + + compiler_fence(Ordering::SeqCst); + U::Interrupt::pend(); + + Poll::Ready(Ok(n)) + }) + } + + /// Try writing a buffer without waiting, returning how many bytes were written. + pub fn try_write(&mut self, buf: &[u8]) -> Result { + //trace!("poll_write: {:?}", buf.len()); + let s = U::buffered_state(); + let mut tx = unsafe { s.tx_buf.writer() }; + + let tx_buf = tx.push_slice(); + if tx_buf.is_empty() { + return Ok(0); + } + + let n = min(tx_buf.len(), buf.len()); + tx_buf[..n].copy_from_slice(&buf[..n]); + tx.push_done(n); + + //trace!("poll_write: queued {:?}", n); + + compiler_fence(Ordering::SeqCst); + U::Interrupt::pend(); + + Ok(n) + } + + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. + pub fn flush(&mut self) -> impl Future> + '_ { + poll_fn(move |cx| { + //trace!("poll_flush"); + let ss = U::state(); + let s = U::buffered_state(); + if !s.tx_buf.is_empty() { + //trace!("poll_flush: pending"); + ss.tx_waker.register(cx.waker()); + return Poll::Pending; + } + + Poll::Ready(Ok(())) + }) + } +} + +impl<'a, U: UarteInstance> Drop for BufferedUarteTx<'a, U> { + fn drop(&mut self) { + let r = U::regs(); + + r.intenclr().write(|w| { + w.set_txdrdy(true); + w.set_dmatxready(true); + w.set_txstopped(true); + }); + r.events_txstopped().write_value(0); + r.tasks_dma().tx().stop().write_value(1); + while r.events_txstopped().read() == 0 {} + + let s = U::buffered_state(); + unsafe { s.tx_buf.deinit() } + + let s = U::state(); + drop_tx_rx(r, s); + } +} + +/// Reader part of the buffered UARTE driver. +pub struct BufferedUarteRx<'d, U: UarteInstance> { + _peri: Peri<'d, U>, +} + +impl<'d, U: UarteInstance> BufferedUarteRx<'d, U> { + /// Create a new BufferedUarte without hardware flow control. + #[allow(clippy::too_many_arguments)] + pub fn new( + uarte: Peri<'d, U>, + _irq: impl interrupt::typelevel::Binding> + 'd, + rxd: Peri<'d, impl GpioPin>, + config: Config, + rx_buffer: &'d mut [u8], + ) -> Self { + Self::new_inner(uarte, rxd.into(), None, config, rx_buffer) + } + + /// Create a new BufferedUarte with hardware flow control (RTS/CTS) + #[allow(clippy::too_many_arguments)] + pub fn new_with_rts( + uarte: Peri<'d, U>, + rxd: Peri<'d, impl GpioPin>, + rts: Peri<'d, impl GpioPin>, + _irq: impl interrupt::typelevel::Binding> + 'd, + config: Config, + rx_buffer: &'d mut [u8], + ) -> Self { + Self::new_inner(uarte, rxd.into(), Some(rts.into()), config, rx_buffer) + } + + #[allow(clippy::too_many_arguments)] + fn new_inner( + peri: Peri<'d, U>, + rxd: Peri<'d, AnyPin>, + rts: Option>, + config: Config, + rx_buffer: &'d mut [u8], + ) -> Self { + configure(U::regs(), config, rts.is_some()); + + let this = Self::new_innerer(peri, rxd, rts, rx_buffer); + + U::regs().enable().write(|w| w.set_enable(vals::Enable::ENABLED)); + U::Interrupt::pend(); + unsafe { U::Interrupt::enable() }; + + U::state().tx_rx_refcount.store(1, Ordering::Relaxed); + + this + } + + #[allow(clippy::too_many_arguments)] + fn new_innerer( + peri: Peri<'d, U>, + rxd: Peri<'d, AnyPin>, + rts: Option>, + rx_buffer: &'d mut [u8], + ) -> Self { + let r = U::regs(); + + configure_rx_pins(r, rxd, rts); + + // Initialize state + let s = U::buffered_state(); + let rx_len = rx_buffer.len().min(EASY_DMA_SIZE * 2); + let rx_ptr = rx_buffer.as_mut_ptr(); + unsafe { s.rx_buf.init(rx_ptr, rx_len) }; + + // clear errors + let errors = r.errorsrc().read(); + r.errorsrc().write_value(errors); + + r.events_error().write_value(0); + r.events_dma().rx().end().write_value(0); + + // set timeout-to-stop short + r.shorts().write(|w| { + w.set_frametimeout_dma_rx_stop(true); + }); + + // set default timeout + r.frametimeout().write_value(pac::uarte::regs::Frametimeout(0x10)); + + // Enable interrupts + r.intenset().write(|w| { + w.set_dmatxend(true); + w.set_error(true); + w.set_dmarxend(true); + }); + + Self { _peri: peri } + } + + /// Pull some bytes from this source into the specified buffer, returning how many bytes were read. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + let data = self.fill_buf().await?; + let n = data.len().min(buf.len()); + buf[..n].copy_from_slice(&data[..n]); + self.consume(n); + Ok(n) + } + + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + pub fn fill_buf(&mut self) -> impl Future> { + poll_fn(move |cx| { + compiler_fence(Ordering::SeqCst); + //trace!("poll_read"); + + let s = U::buffered_state(); + let ss = U::state(); + let mut rx = unsafe { s.rx_buf.reader() }; + + let (ptr, n) = rx.pop_buf(); + if n == 0 { + //trace!(" empty"); + ss.rx_waker.register(cx.waker()); + Poll::Pending + } else { + Poll::Ready(Ok(unsafe { slice::from_raw_parts(ptr, n) })) + } + }) + } + + /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. + pub fn consume(&mut self, amt: usize) { + if amt == 0 { + return; + } + + let s = U::buffered_state(); + let mut rx = unsafe { s.rx_buf.reader() }; + rx.pop_done(amt); + } + + /// we are ready to read if there is data in the buffer + fn read_ready() -> Result { + let state = U::buffered_state(); + Ok(!state.rx_buf.is_empty()) + } +} + +impl<'a, U: UarteInstance> Drop for BufferedUarteRx<'a, U> { + fn drop(&mut self) { + let r = U::regs(); + + r.intenclr().write(|w| { + w.set_rxto(true); + }); + r.events_rxto().write_value(0); + + let s = U::buffered_state(); + unsafe { s.rx_buf.deinit() } + + let s = U::state(); + drop_tx_rx(r, s); + } +} + +mod _embedded_io { + use super::*; + + impl embedded_io_async::Error for Error { + fn kind(&self) -> embedded_io_async::ErrorKind { + match *self {} + } + } + + impl<'d, U: UarteInstance> embedded_io_async::ErrorType for BufferedUarte<'d, U> { + type Error = Error; + } + + impl<'d, U: UarteInstance> embedded_io_async::ErrorType for BufferedUarteRx<'d, U> { + type Error = Error; + } + + impl<'d, U: UarteInstance> embedded_io_async::ErrorType for BufferedUarteTx<'d, U> { + type Error = Error; + } + + impl<'d, U: UarteInstance> embedded_io_async::Read for BufferedUarte<'d, U> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.read(buf).await + } + } + + impl<'d: 'd, U: UarteInstance> embedded_io_async::Read for BufferedUarteRx<'d, U> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.read(buf).await + } + } + + impl<'d, U: UarteInstance> embedded_io_async::ReadReady for BufferedUarte<'d, U> { + fn read_ready(&mut self) -> Result { + BufferedUarteRx::<'d, U>::read_ready() + } + } + + impl<'d, U: UarteInstance> embedded_io_async::ReadReady for BufferedUarteRx<'d, U> { + fn read_ready(&mut self) -> Result { + Self::read_ready() + } + } + + impl<'d, U: UarteInstance> embedded_io_async::BufRead for BufferedUarte<'d, U> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.fill_buf().await + } + + fn consume(&mut self, amt: usize) { + self.consume(amt) + } + } + + impl<'d: 'd, U: UarteInstance> embedded_io_async::BufRead for BufferedUarteRx<'d, U> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.fill_buf().await + } + + fn consume(&mut self, amt: usize) { + self.consume(amt) + } + } + + impl<'d, U: UarteInstance> embedded_io_async::Write for BufferedUarte<'d, U> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.flush().await + } + } + + impl<'d: 'd, U: UarteInstance> embedded_io_async::Write for BufferedUarteTx<'d, U> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.flush().await + } + } +} diff --git a/embassy-nrf/src/chips/nrf51.rs b/embassy-nrf/src/chips/nrf51.rs index 3976e8ff0..1184c4409 100644 --- a/embassy-nrf/src/chips/nrf51.rs +++ b/embassy-nrf/src/chips/nrf51.rs @@ -115,6 +115,11 @@ impl_rtc!(RTC0, RTC0, RTC0); #[cfg(not(feature = "time-driver-rtc1"))] impl_rtc!(RTC1, RTC1, RTC1); +impl_ppi_group!(PPI_GROUP0, PPI, 0); +impl_ppi_group!(PPI_GROUP1, PPI, 1); +impl_ppi_group!(PPI_GROUP2, PPI, 2); +impl_ppi_group!(PPI_GROUP3, PPI, 3); + impl_pin!(P0_00, 0, 0); impl_pin!(P0_01, 0, 1); impl_pin!(P0_02, 0, 2); diff --git a/embassy-nrf/src/chips/nrf52805.rs b/embassy-nrf/src/chips/nrf52805.rs index 63ba6999a..dd2e66927 100644 --- a/embassy-nrf/src/chips/nrf52805.rs +++ b/embassy-nrf/src/chips/nrf52805.rs @@ -195,28 +195,35 @@ impl_pin!(P0_29, 0, 29); impl_pin!(P0_30, 0, 30); impl_pin!(P0_31, 0, 31); -impl_ppi_channel!(PPI_CH0, 0 => configurable); -impl_ppi_channel!(PPI_CH1, 1 => configurable); -impl_ppi_channel!(PPI_CH2, 2 => configurable); -impl_ppi_channel!(PPI_CH3, 3 => configurable); -impl_ppi_channel!(PPI_CH4, 4 => configurable); -impl_ppi_channel!(PPI_CH5, 5 => configurable); -impl_ppi_channel!(PPI_CH6, 6 => configurable); -impl_ppi_channel!(PPI_CH7, 7 => configurable); -impl_ppi_channel!(PPI_CH8, 8 => configurable); -impl_ppi_channel!(PPI_CH9, 9 => configurable); -impl_ppi_channel!(PPI_CH20, 20 => static); -impl_ppi_channel!(PPI_CH21, 21 => static); -impl_ppi_channel!(PPI_CH22, 22 => static); -impl_ppi_channel!(PPI_CH23, 23 => static); -impl_ppi_channel!(PPI_CH24, 24 => static); -impl_ppi_channel!(PPI_CH25, 25 => static); -impl_ppi_channel!(PPI_CH26, 26 => static); -impl_ppi_channel!(PPI_CH27, 27 => static); -impl_ppi_channel!(PPI_CH28, 28 => static); -impl_ppi_channel!(PPI_CH29, 29 => static); -impl_ppi_channel!(PPI_CH30, 30 => static); -impl_ppi_channel!(PPI_CH31, 31 => static); +impl_ppi_channel!(PPI_CH0, PPI, 0 => configurable); +impl_ppi_channel!(PPI_CH1, PPI, 1 => configurable); +impl_ppi_channel!(PPI_CH2, PPI, 2 => configurable); +impl_ppi_channel!(PPI_CH3, PPI, 3 => configurable); +impl_ppi_channel!(PPI_CH4, PPI, 4 => configurable); +impl_ppi_channel!(PPI_CH5, PPI, 5 => configurable); +impl_ppi_channel!(PPI_CH6, PPI, 6 => configurable); +impl_ppi_channel!(PPI_CH7, PPI, 7 => configurable); +impl_ppi_channel!(PPI_CH8, PPI, 8 => configurable); +impl_ppi_channel!(PPI_CH9, PPI, 9 => configurable); +impl_ppi_channel!(PPI_CH20, PPI, 20 => static); +impl_ppi_channel!(PPI_CH21, PPI, 21 => static); +impl_ppi_channel!(PPI_CH22, PPI, 22 => static); +impl_ppi_channel!(PPI_CH23, PPI, 23 => static); +impl_ppi_channel!(PPI_CH24, PPI, 24 => static); +impl_ppi_channel!(PPI_CH25, PPI, 25 => static); +impl_ppi_channel!(PPI_CH26, PPI, 26 => static); +impl_ppi_channel!(PPI_CH27, PPI, 27 => static); +impl_ppi_channel!(PPI_CH28, PPI, 28 => static); +impl_ppi_channel!(PPI_CH29, PPI, 29 => static); +impl_ppi_channel!(PPI_CH30, PPI, 30 => static); +impl_ppi_channel!(PPI_CH31, PPI, 31 => static); + +impl_ppi_group!(PPI_GROUP0, PPI, 0); +impl_ppi_group!(PPI_GROUP1, PPI, 1); +impl_ppi_group!(PPI_GROUP2, PPI, 2); +impl_ppi_group!(PPI_GROUP3, PPI, 3); +impl_ppi_group!(PPI_GROUP4, PPI, 4); +impl_ppi_group!(PPI_GROUP5, PPI, 5); impl_saadc_input!(P0_04, ANALOG_INPUT2); impl_saadc_input!(P0_05, ANALOG_INPUT3); diff --git a/embassy-nrf/src/chips/nrf52810.rs b/embassy-nrf/src/chips/nrf52810.rs index 7f744f9fb..7acb53a03 100644 --- a/embassy-nrf/src/chips/nrf52810.rs +++ b/embassy-nrf/src/chips/nrf52810.rs @@ -205,38 +205,45 @@ impl_pin!(P0_29, 0, 29); impl_pin!(P0_30, 0, 30); impl_pin!(P0_31, 0, 31); -impl_ppi_channel!(PPI_CH0, 0 => configurable); -impl_ppi_channel!(PPI_CH1, 1 => configurable); -impl_ppi_channel!(PPI_CH2, 2 => configurable); -impl_ppi_channel!(PPI_CH3, 3 => configurable); -impl_ppi_channel!(PPI_CH4, 4 => configurable); -impl_ppi_channel!(PPI_CH5, 5 => configurable); -impl_ppi_channel!(PPI_CH6, 6 => configurable); -impl_ppi_channel!(PPI_CH7, 7 => configurable); -impl_ppi_channel!(PPI_CH8, 8 => configurable); -impl_ppi_channel!(PPI_CH9, 9 => configurable); -impl_ppi_channel!(PPI_CH10, 10 => configurable); -impl_ppi_channel!(PPI_CH11, 11 => configurable); -impl_ppi_channel!(PPI_CH12, 12 => configurable); -impl_ppi_channel!(PPI_CH13, 13 => configurable); -impl_ppi_channel!(PPI_CH14, 14 => configurable); -impl_ppi_channel!(PPI_CH15, 15 => configurable); -impl_ppi_channel!(PPI_CH16, 16 => configurable); -impl_ppi_channel!(PPI_CH17, 17 => configurable); -impl_ppi_channel!(PPI_CH18, 18 => configurable); -impl_ppi_channel!(PPI_CH19, 19 => configurable); -impl_ppi_channel!(PPI_CH20, 20 => static); -impl_ppi_channel!(PPI_CH21, 21 => static); -impl_ppi_channel!(PPI_CH22, 22 => static); -impl_ppi_channel!(PPI_CH23, 23 => static); -impl_ppi_channel!(PPI_CH24, 24 => static); -impl_ppi_channel!(PPI_CH25, 25 => static); -impl_ppi_channel!(PPI_CH26, 26 => static); -impl_ppi_channel!(PPI_CH27, 27 => static); -impl_ppi_channel!(PPI_CH28, 28 => static); -impl_ppi_channel!(PPI_CH29, 29 => static); -impl_ppi_channel!(PPI_CH30, 30 => static); -impl_ppi_channel!(PPI_CH31, 31 => static); +impl_ppi_channel!(PPI_CH0, PPI, 0 => configurable); +impl_ppi_channel!(PPI_CH1, PPI, 1 => configurable); +impl_ppi_channel!(PPI_CH2, PPI, 2 => configurable); +impl_ppi_channel!(PPI_CH3, PPI, 3 => configurable); +impl_ppi_channel!(PPI_CH4, PPI, 4 => configurable); +impl_ppi_channel!(PPI_CH5, PPI, 5 => configurable); +impl_ppi_channel!(PPI_CH6, PPI, 6 => configurable); +impl_ppi_channel!(PPI_CH7, PPI, 7 => configurable); +impl_ppi_channel!(PPI_CH8, PPI, 8 => configurable); +impl_ppi_channel!(PPI_CH9, PPI, 9 => configurable); +impl_ppi_channel!(PPI_CH10, PPI, 10 => configurable); +impl_ppi_channel!(PPI_CH11, PPI, 11 => configurable); +impl_ppi_channel!(PPI_CH12, PPI, 12 => configurable); +impl_ppi_channel!(PPI_CH13, PPI, 13 => configurable); +impl_ppi_channel!(PPI_CH14, PPI, 14 => configurable); +impl_ppi_channel!(PPI_CH15, PPI, 15 => configurable); +impl_ppi_channel!(PPI_CH16, PPI, 16 => configurable); +impl_ppi_channel!(PPI_CH17, PPI, 17 => configurable); +impl_ppi_channel!(PPI_CH18, PPI, 18 => configurable); +impl_ppi_channel!(PPI_CH19, PPI, 19 => configurable); +impl_ppi_channel!(PPI_CH20, PPI, 20 => static); +impl_ppi_channel!(PPI_CH21, PPI, 21 => static); +impl_ppi_channel!(PPI_CH22, PPI, 22 => static); +impl_ppi_channel!(PPI_CH23, PPI, 23 => static); +impl_ppi_channel!(PPI_CH24, PPI, 24 => static); +impl_ppi_channel!(PPI_CH25, PPI, 25 => static); +impl_ppi_channel!(PPI_CH26, PPI, 26 => static); +impl_ppi_channel!(PPI_CH27, PPI, 27 => static); +impl_ppi_channel!(PPI_CH28, PPI, 28 => static); +impl_ppi_channel!(PPI_CH29, PPI, 29 => static); +impl_ppi_channel!(PPI_CH30, PPI, 30 => static); +impl_ppi_channel!(PPI_CH31, PPI, 31 => static); + +impl_ppi_group!(PPI_GROUP0, PPI, 0); +impl_ppi_group!(PPI_GROUP1, PPI, 1); +impl_ppi_group!(PPI_GROUP2, PPI, 2); +impl_ppi_group!(PPI_GROUP3, PPI, 3); +impl_ppi_group!(PPI_GROUP4, PPI, 4); +impl_ppi_group!(PPI_GROUP5, PPI, 5); impl_saadc_input!(P0_02, ANALOG_INPUT0); impl_saadc_input!(P0_03, ANALOG_INPUT1); diff --git a/embassy-nrf/src/chips/nrf52811.rs b/embassy-nrf/src/chips/nrf52811.rs index 908167e31..4178ef6cd 100644 --- a/embassy-nrf/src/chips/nrf52811.rs +++ b/embassy-nrf/src/chips/nrf52811.rs @@ -207,38 +207,45 @@ impl_pin!(P0_29, 0, 29); impl_pin!(P0_30, 0, 30); impl_pin!(P0_31, 0, 31); -impl_ppi_channel!(PPI_CH0, 0 => configurable); -impl_ppi_channel!(PPI_CH1, 1 => configurable); -impl_ppi_channel!(PPI_CH2, 2 => configurable); -impl_ppi_channel!(PPI_CH3, 3 => configurable); -impl_ppi_channel!(PPI_CH4, 4 => configurable); -impl_ppi_channel!(PPI_CH5, 5 => configurable); -impl_ppi_channel!(PPI_CH6, 6 => configurable); -impl_ppi_channel!(PPI_CH7, 7 => configurable); -impl_ppi_channel!(PPI_CH8, 8 => configurable); -impl_ppi_channel!(PPI_CH9, 9 => configurable); -impl_ppi_channel!(PPI_CH10, 10 => configurable); -impl_ppi_channel!(PPI_CH11, 11 => configurable); -impl_ppi_channel!(PPI_CH12, 12 => configurable); -impl_ppi_channel!(PPI_CH13, 13 => configurable); -impl_ppi_channel!(PPI_CH14, 14 => configurable); -impl_ppi_channel!(PPI_CH15, 15 => configurable); -impl_ppi_channel!(PPI_CH16, 16 => configurable); -impl_ppi_channel!(PPI_CH17, 17 => configurable); -impl_ppi_channel!(PPI_CH18, 18 => configurable); -impl_ppi_channel!(PPI_CH19, 19 => configurable); -impl_ppi_channel!(PPI_CH20, 20 => static); -impl_ppi_channel!(PPI_CH21, 21 => static); -impl_ppi_channel!(PPI_CH22, 22 => static); -impl_ppi_channel!(PPI_CH23, 23 => static); -impl_ppi_channel!(PPI_CH24, 24 => static); -impl_ppi_channel!(PPI_CH25, 25 => static); -impl_ppi_channel!(PPI_CH26, 26 => static); -impl_ppi_channel!(PPI_CH27, 27 => static); -impl_ppi_channel!(PPI_CH28, 28 => static); -impl_ppi_channel!(PPI_CH29, 29 => static); -impl_ppi_channel!(PPI_CH30, 30 => static); -impl_ppi_channel!(PPI_CH31, 31 => static); +impl_ppi_channel!(PPI_CH0, PPI, 0 => configurable); +impl_ppi_channel!(PPI_CH1, PPI, 1 => configurable); +impl_ppi_channel!(PPI_CH2, PPI, 2 => configurable); +impl_ppi_channel!(PPI_CH3, PPI, 3 => configurable); +impl_ppi_channel!(PPI_CH4, PPI, 4 => configurable); +impl_ppi_channel!(PPI_CH5, PPI, 5 => configurable); +impl_ppi_channel!(PPI_CH6, PPI, 6 => configurable); +impl_ppi_channel!(PPI_CH7, PPI, 7 => configurable); +impl_ppi_channel!(PPI_CH8, PPI, 8 => configurable); +impl_ppi_channel!(PPI_CH9, PPI, 9 => configurable); +impl_ppi_channel!(PPI_CH10, PPI, 10 => configurable); +impl_ppi_channel!(PPI_CH11, PPI, 11 => configurable); +impl_ppi_channel!(PPI_CH12, PPI, 12 => configurable); +impl_ppi_channel!(PPI_CH13, PPI, 13 => configurable); +impl_ppi_channel!(PPI_CH14, PPI, 14 => configurable); +impl_ppi_channel!(PPI_CH15, PPI, 15 => configurable); +impl_ppi_channel!(PPI_CH16, PPI, 16 => configurable); +impl_ppi_channel!(PPI_CH17, PPI, 17 => configurable); +impl_ppi_channel!(PPI_CH18, PPI, 18 => configurable); +impl_ppi_channel!(PPI_CH19, PPI, 19 => configurable); +impl_ppi_channel!(PPI_CH20, PPI, 20 => static); +impl_ppi_channel!(PPI_CH21, PPI, 21 => static); +impl_ppi_channel!(PPI_CH22, PPI, 22 => static); +impl_ppi_channel!(PPI_CH23, PPI, 23 => static); +impl_ppi_channel!(PPI_CH24, PPI, 24 => static); +impl_ppi_channel!(PPI_CH25, PPI, 25 => static); +impl_ppi_channel!(PPI_CH26, PPI, 26 => static); +impl_ppi_channel!(PPI_CH27, PPI, 27 => static); +impl_ppi_channel!(PPI_CH28, PPI, 28 => static); +impl_ppi_channel!(PPI_CH29, PPI, 29 => static); +impl_ppi_channel!(PPI_CH30, PPI, 30 => static); +impl_ppi_channel!(PPI_CH31, PPI, 31 => static); + +impl_ppi_group!(PPI_GROUP0, PPI, 0); +impl_ppi_group!(PPI_GROUP1, PPI, 1); +impl_ppi_group!(PPI_GROUP2, PPI, 2); +impl_ppi_group!(PPI_GROUP3, PPI, 3); +impl_ppi_group!(PPI_GROUP4, PPI, 4); +impl_ppi_group!(PPI_GROUP5, PPI, 5); impl_saadc_input!(P0_02, ANALOG_INPUT0); impl_saadc_input!(P0_03, ANALOG_INPUT1); diff --git a/embassy-nrf/src/chips/nrf52820.rs b/embassy-nrf/src/chips/nrf52820.rs index 22360575b..32304b3ea 100644 --- a/embassy-nrf/src/chips/nrf52820.rs +++ b/embassy-nrf/src/chips/nrf52820.rs @@ -207,38 +207,45 @@ impl_pin!(P0_29, 0, 29); impl_pin!(P0_30, 0, 30); impl_pin!(P0_31, 0, 31); -impl_ppi_channel!(PPI_CH0, 0 => configurable); -impl_ppi_channel!(PPI_CH1, 1 => configurable); -impl_ppi_channel!(PPI_CH2, 2 => configurable); -impl_ppi_channel!(PPI_CH3, 3 => configurable); -impl_ppi_channel!(PPI_CH4, 4 => configurable); -impl_ppi_channel!(PPI_CH5, 5 => configurable); -impl_ppi_channel!(PPI_CH6, 6 => configurable); -impl_ppi_channel!(PPI_CH7, 7 => configurable); -impl_ppi_channel!(PPI_CH8, 8 => configurable); -impl_ppi_channel!(PPI_CH9, 9 => configurable); -impl_ppi_channel!(PPI_CH10, 10 => configurable); -impl_ppi_channel!(PPI_CH11, 11 => configurable); -impl_ppi_channel!(PPI_CH12, 12 => configurable); -impl_ppi_channel!(PPI_CH13, 13 => configurable); -impl_ppi_channel!(PPI_CH14, 14 => configurable); -impl_ppi_channel!(PPI_CH15, 15 => configurable); -impl_ppi_channel!(PPI_CH16, 16 => configurable); -impl_ppi_channel!(PPI_CH17, 17 => configurable); -impl_ppi_channel!(PPI_CH18, 18 => configurable); -impl_ppi_channel!(PPI_CH19, 19 => configurable); -impl_ppi_channel!(PPI_CH20, 20 => static); -impl_ppi_channel!(PPI_CH21, 21 => static); -impl_ppi_channel!(PPI_CH22, 22 => static); -impl_ppi_channel!(PPI_CH23, 23 => static); -impl_ppi_channel!(PPI_CH24, 24 => static); -impl_ppi_channel!(PPI_CH25, 25 => static); -impl_ppi_channel!(PPI_CH26, 26 => static); -impl_ppi_channel!(PPI_CH27, 27 => static); -impl_ppi_channel!(PPI_CH28, 28 => static); -impl_ppi_channel!(PPI_CH29, 29 => static); -impl_ppi_channel!(PPI_CH30, 30 => static); -impl_ppi_channel!(PPI_CH31, 31 => static); +impl_ppi_channel!(PPI_CH0, PPI, 0 => configurable); +impl_ppi_channel!(PPI_CH1, PPI, 1 => configurable); +impl_ppi_channel!(PPI_CH2, PPI, 2 => configurable); +impl_ppi_channel!(PPI_CH3, PPI, 3 => configurable); +impl_ppi_channel!(PPI_CH4, PPI, 4 => configurable); +impl_ppi_channel!(PPI_CH5, PPI, 5 => configurable); +impl_ppi_channel!(PPI_CH6, PPI, 6 => configurable); +impl_ppi_channel!(PPI_CH7, PPI, 7 => configurable); +impl_ppi_channel!(PPI_CH8, PPI, 8 => configurable); +impl_ppi_channel!(PPI_CH9, PPI, 9 => configurable); +impl_ppi_channel!(PPI_CH10, PPI, 10 => configurable); +impl_ppi_channel!(PPI_CH11, PPI, 11 => configurable); +impl_ppi_channel!(PPI_CH12, PPI, 12 => configurable); +impl_ppi_channel!(PPI_CH13, PPI, 13 => configurable); +impl_ppi_channel!(PPI_CH14, PPI, 14 => configurable); +impl_ppi_channel!(PPI_CH15, PPI, 15 => configurable); +impl_ppi_channel!(PPI_CH16, PPI, 16 => configurable); +impl_ppi_channel!(PPI_CH17, PPI, 17 => configurable); +impl_ppi_channel!(PPI_CH18, PPI, 18 => configurable); +impl_ppi_channel!(PPI_CH19, PPI, 19 => configurable); +impl_ppi_channel!(PPI_CH20, PPI, 20 => static); +impl_ppi_channel!(PPI_CH21, PPI, 21 => static); +impl_ppi_channel!(PPI_CH22, PPI, 22 => static); +impl_ppi_channel!(PPI_CH23, PPI, 23 => static); +impl_ppi_channel!(PPI_CH24, PPI, 24 => static); +impl_ppi_channel!(PPI_CH25, PPI, 25 => static); +impl_ppi_channel!(PPI_CH26, PPI, 26 => static); +impl_ppi_channel!(PPI_CH27, PPI, 27 => static); +impl_ppi_channel!(PPI_CH28, PPI, 28 => static); +impl_ppi_channel!(PPI_CH29, PPI, 29 => static); +impl_ppi_channel!(PPI_CH30, PPI, 30 => static); +impl_ppi_channel!(PPI_CH31, PPI, 31 => static); + +impl_ppi_group!(PPI_GROUP0, PPI, 0); +impl_ppi_group!(PPI_GROUP1, PPI, 1); +impl_ppi_group!(PPI_GROUP2, PPI, 2); +impl_ppi_group!(PPI_GROUP3, PPI, 3); +impl_ppi_group!(PPI_GROUP4, PPI, 4); +impl_ppi_group!(PPI_GROUP5, PPI, 5); impl_radio!(RADIO, RADIO, RADIO); diff --git a/embassy-nrf/src/chips/nrf52832.rs b/embassy-nrf/src/chips/nrf52832.rs index 1598df3fe..06363a467 100644 --- a/embassy-nrf/src/chips/nrf52832.rs +++ b/embassy-nrf/src/chips/nrf52832.rs @@ -240,38 +240,45 @@ impl_pin!(P0_29, 0, 29); impl_pin!(P0_30, 0, 30); impl_pin!(P0_31, 0, 31); -impl_ppi_channel!(PPI_CH0, 0 => configurable); -impl_ppi_channel!(PPI_CH1, 1 => configurable); -impl_ppi_channel!(PPI_CH2, 2 => configurable); -impl_ppi_channel!(PPI_CH3, 3 => configurable); -impl_ppi_channel!(PPI_CH4, 4 => configurable); -impl_ppi_channel!(PPI_CH5, 5 => configurable); -impl_ppi_channel!(PPI_CH6, 6 => configurable); -impl_ppi_channel!(PPI_CH7, 7 => configurable); -impl_ppi_channel!(PPI_CH8, 8 => configurable); -impl_ppi_channel!(PPI_CH9, 9 => configurable); -impl_ppi_channel!(PPI_CH10, 10 => configurable); -impl_ppi_channel!(PPI_CH11, 11 => configurable); -impl_ppi_channel!(PPI_CH12, 12 => configurable); -impl_ppi_channel!(PPI_CH13, 13 => configurable); -impl_ppi_channel!(PPI_CH14, 14 => configurable); -impl_ppi_channel!(PPI_CH15, 15 => configurable); -impl_ppi_channel!(PPI_CH16, 16 => configurable); -impl_ppi_channel!(PPI_CH17, 17 => configurable); -impl_ppi_channel!(PPI_CH18, 18 => configurable); -impl_ppi_channel!(PPI_CH19, 19 => configurable); -impl_ppi_channel!(PPI_CH20, 20 => static); -impl_ppi_channel!(PPI_CH21, 21 => static); -impl_ppi_channel!(PPI_CH22, 22 => static); -impl_ppi_channel!(PPI_CH23, 23 => static); -impl_ppi_channel!(PPI_CH24, 24 => static); -impl_ppi_channel!(PPI_CH25, 25 => static); -impl_ppi_channel!(PPI_CH26, 26 => static); -impl_ppi_channel!(PPI_CH27, 27 => static); -impl_ppi_channel!(PPI_CH28, 28 => static); -impl_ppi_channel!(PPI_CH29, 29 => static); -impl_ppi_channel!(PPI_CH30, 30 => static); -impl_ppi_channel!(PPI_CH31, 31 => static); +impl_ppi_channel!(PPI_CH0, PPI, 0 => configurable); +impl_ppi_channel!(PPI_CH1, PPI, 1 => configurable); +impl_ppi_channel!(PPI_CH2, PPI, 2 => configurable); +impl_ppi_channel!(PPI_CH3, PPI, 3 => configurable); +impl_ppi_channel!(PPI_CH4, PPI, 4 => configurable); +impl_ppi_channel!(PPI_CH5, PPI, 5 => configurable); +impl_ppi_channel!(PPI_CH6, PPI, 6 => configurable); +impl_ppi_channel!(PPI_CH7, PPI, 7 => configurable); +impl_ppi_channel!(PPI_CH8, PPI, 8 => configurable); +impl_ppi_channel!(PPI_CH9, PPI, 9 => configurable); +impl_ppi_channel!(PPI_CH10, PPI, 10 => configurable); +impl_ppi_channel!(PPI_CH11, PPI, 11 => configurable); +impl_ppi_channel!(PPI_CH12, PPI, 12 => configurable); +impl_ppi_channel!(PPI_CH13, PPI, 13 => configurable); +impl_ppi_channel!(PPI_CH14, PPI, 14 => configurable); +impl_ppi_channel!(PPI_CH15, PPI, 15 => configurable); +impl_ppi_channel!(PPI_CH16, PPI, 16 => configurable); +impl_ppi_channel!(PPI_CH17, PPI, 17 => configurable); +impl_ppi_channel!(PPI_CH18, PPI, 18 => configurable); +impl_ppi_channel!(PPI_CH19, PPI, 19 => configurable); +impl_ppi_channel!(PPI_CH20, PPI, 20 => static); +impl_ppi_channel!(PPI_CH21, PPI, 21 => static); +impl_ppi_channel!(PPI_CH22, PPI, 22 => static); +impl_ppi_channel!(PPI_CH23, PPI, 23 => static); +impl_ppi_channel!(PPI_CH24, PPI, 24 => static); +impl_ppi_channel!(PPI_CH25, PPI, 25 => static); +impl_ppi_channel!(PPI_CH26, PPI, 26 => static); +impl_ppi_channel!(PPI_CH27, PPI, 27 => static); +impl_ppi_channel!(PPI_CH28, PPI, 28 => static); +impl_ppi_channel!(PPI_CH29, PPI, 29 => static); +impl_ppi_channel!(PPI_CH30, PPI, 30 => static); +impl_ppi_channel!(PPI_CH31, PPI, 31 => static); + +impl_ppi_group!(PPI_GROUP0, PPI, 0); +impl_ppi_group!(PPI_GROUP1, PPI, 1); +impl_ppi_group!(PPI_GROUP2, PPI, 2); +impl_ppi_group!(PPI_GROUP3, PPI, 3); +impl_ppi_group!(PPI_GROUP4, PPI, 4); +impl_ppi_group!(PPI_GROUP5, PPI, 5); impl_saadc_input!(P0_02, ANALOG_INPUT0); impl_saadc_input!(P0_03, ANALOG_INPUT1); diff --git a/embassy-nrf/src/chips/nrf52833.rs b/embassy-nrf/src/chips/nrf52833.rs index 6931fb064..754943d33 100644 --- a/embassy-nrf/src/chips/nrf52833.rs +++ b/embassy-nrf/src/chips/nrf52833.rs @@ -282,38 +282,45 @@ impl_pin!(P1_13, 1, 13); impl_pin!(P1_14, 1, 14); impl_pin!(P1_15, 1, 15); -impl_ppi_channel!(PPI_CH0, 0 => configurable); -impl_ppi_channel!(PPI_CH1, 1 => configurable); -impl_ppi_channel!(PPI_CH2, 2 => configurable); -impl_ppi_channel!(PPI_CH3, 3 => configurable); -impl_ppi_channel!(PPI_CH4, 4 => configurable); -impl_ppi_channel!(PPI_CH5, 5 => configurable); -impl_ppi_channel!(PPI_CH6, 6 => configurable); -impl_ppi_channel!(PPI_CH7, 7 => configurable); -impl_ppi_channel!(PPI_CH8, 8 => configurable); -impl_ppi_channel!(PPI_CH9, 9 => configurable); -impl_ppi_channel!(PPI_CH10, 10 => configurable); -impl_ppi_channel!(PPI_CH11, 11 => configurable); -impl_ppi_channel!(PPI_CH12, 12 => configurable); -impl_ppi_channel!(PPI_CH13, 13 => configurable); -impl_ppi_channel!(PPI_CH14, 14 => configurable); -impl_ppi_channel!(PPI_CH15, 15 => configurable); -impl_ppi_channel!(PPI_CH16, 16 => configurable); -impl_ppi_channel!(PPI_CH17, 17 => configurable); -impl_ppi_channel!(PPI_CH18, 18 => configurable); -impl_ppi_channel!(PPI_CH19, 19 => configurable); -impl_ppi_channel!(PPI_CH20, 20 => static); -impl_ppi_channel!(PPI_CH21, 21 => static); -impl_ppi_channel!(PPI_CH22, 22 => static); -impl_ppi_channel!(PPI_CH23, 23 => static); -impl_ppi_channel!(PPI_CH24, 24 => static); -impl_ppi_channel!(PPI_CH25, 25 => static); -impl_ppi_channel!(PPI_CH26, 26 => static); -impl_ppi_channel!(PPI_CH27, 27 => static); -impl_ppi_channel!(PPI_CH28, 28 => static); -impl_ppi_channel!(PPI_CH29, 29 => static); -impl_ppi_channel!(PPI_CH30, 30 => static); -impl_ppi_channel!(PPI_CH31, 31 => static); +impl_ppi_channel!(PPI_CH0, PPI, 0 => configurable); +impl_ppi_channel!(PPI_CH1, PPI, 1 => configurable); +impl_ppi_channel!(PPI_CH2, PPI, 2 => configurable); +impl_ppi_channel!(PPI_CH3, PPI, 3 => configurable); +impl_ppi_channel!(PPI_CH4, PPI, 4 => configurable); +impl_ppi_channel!(PPI_CH5, PPI, 5 => configurable); +impl_ppi_channel!(PPI_CH6, PPI, 6 => configurable); +impl_ppi_channel!(PPI_CH7, PPI, 7 => configurable); +impl_ppi_channel!(PPI_CH8, PPI, 8 => configurable); +impl_ppi_channel!(PPI_CH9, PPI, 9 => configurable); +impl_ppi_channel!(PPI_CH10, PPI, 10 => configurable); +impl_ppi_channel!(PPI_CH11, PPI, 11 => configurable); +impl_ppi_channel!(PPI_CH12, PPI, 12 => configurable); +impl_ppi_channel!(PPI_CH13, PPI, 13 => configurable); +impl_ppi_channel!(PPI_CH14, PPI, 14 => configurable); +impl_ppi_channel!(PPI_CH15, PPI, 15 => configurable); +impl_ppi_channel!(PPI_CH16, PPI, 16 => configurable); +impl_ppi_channel!(PPI_CH17, PPI, 17 => configurable); +impl_ppi_channel!(PPI_CH18, PPI, 18 => configurable); +impl_ppi_channel!(PPI_CH19, PPI, 19 => configurable); +impl_ppi_channel!(PPI_CH20, PPI, 20 => static); +impl_ppi_channel!(PPI_CH21, PPI, 21 => static); +impl_ppi_channel!(PPI_CH22, PPI, 22 => static); +impl_ppi_channel!(PPI_CH23, PPI, 23 => static); +impl_ppi_channel!(PPI_CH24, PPI, 24 => static); +impl_ppi_channel!(PPI_CH25, PPI, 25 => static); +impl_ppi_channel!(PPI_CH26, PPI, 26 => static); +impl_ppi_channel!(PPI_CH27, PPI, 27 => static); +impl_ppi_channel!(PPI_CH28, PPI, 28 => static); +impl_ppi_channel!(PPI_CH29, PPI, 29 => static); +impl_ppi_channel!(PPI_CH30, PPI, 30 => static); +impl_ppi_channel!(PPI_CH31, PPI, 31 => static); + +impl_ppi_group!(PPI_GROUP0, PPI, 0); +impl_ppi_group!(PPI_GROUP1, PPI, 1); +impl_ppi_group!(PPI_GROUP2, PPI, 2); +impl_ppi_group!(PPI_GROUP3, PPI, 3); +impl_ppi_group!(PPI_GROUP4, PPI, 4); +impl_ppi_group!(PPI_GROUP5, PPI, 5); impl_saadc_input!(P0_02, ANALOG_INPUT0); impl_saadc_input!(P0_03, ANALOG_INPUT1); diff --git a/embassy-nrf/src/chips/nrf52840.rs b/embassy-nrf/src/chips/nrf52840.rs index 5fa521aae..ac07cd820 100644 --- a/embassy-nrf/src/chips/nrf52840.rs +++ b/embassy-nrf/src/chips/nrf52840.rs @@ -287,38 +287,45 @@ impl_pin!(P1_13, 1, 13); impl_pin!(P1_14, 1, 14); impl_pin!(P1_15, 1, 15); -impl_ppi_channel!(PPI_CH0, 0 => configurable); -impl_ppi_channel!(PPI_CH1, 1 => configurable); -impl_ppi_channel!(PPI_CH2, 2 => configurable); -impl_ppi_channel!(PPI_CH3, 3 => configurable); -impl_ppi_channel!(PPI_CH4, 4 => configurable); -impl_ppi_channel!(PPI_CH5, 5 => configurable); -impl_ppi_channel!(PPI_CH6, 6 => configurable); -impl_ppi_channel!(PPI_CH7, 7 => configurable); -impl_ppi_channel!(PPI_CH8, 8 => configurable); -impl_ppi_channel!(PPI_CH9, 9 => configurable); -impl_ppi_channel!(PPI_CH10, 10 => configurable); -impl_ppi_channel!(PPI_CH11, 11 => configurable); -impl_ppi_channel!(PPI_CH12, 12 => configurable); -impl_ppi_channel!(PPI_CH13, 13 => configurable); -impl_ppi_channel!(PPI_CH14, 14 => configurable); -impl_ppi_channel!(PPI_CH15, 15 => configurable); -impl_ppi_channel!(PPI_CH16, 16 => configurable); -impl_ppi_channel!(PPI_CH17, 17 => configurable); -impl_ppi_channel!(PPI_CH18, 18 => configurable); -impl_ppi_channel!(PPI_CH19, 19 => configurable); -impl_ppi_channel!(PPI_CH20, 20 => static); -impl_ppi_channel!(PPI_CH21, 21 => static); -impl_ppi_channel!(PPI_CH22, 22 => static); -impl_ppi_channel!(PPI_CH23, 23 => static); -impl_ppi_channel!(PPI_CH24, 24 => static); -impl_ppi_channel!(PPI_CH25, 25 => static); -impl_ppi_channel!(PPI_CH26, 26 => static); -impl_ppi_channel!(PPI_CH27, 27 => static); -impl_ppi_channel!(PPI_CH28, 28 => static); -impl_ppi_channel!(PPI_CH29, 29 => static); -impl_ppi_channel!(PPI_CH30, 30 => static); -impl_ppi_channel!(PPI_CH31, 31 => static); +impl_ppi_channel!(PPI_CH0, PPI, 0 => configurable); +impl_ppi_channel!(PPI_CH1, PPI, 1 => configurable); +impl_ppi_channel!(PPI_CH2, PPI, 2 => configurable); +impl_ppi_channel!(PPI_CH3, PPI, 3 => configurable); +impl_ppi_channel!(PPI_CH4, PPI, 4 => configurable); +impl_ppi_channel!(PPI_CH5, PPI, 5 => configurable); +impl_ppi_channel!(PPI_CH6, PPI, 6 => configurable); +impl_ppi_channel!(PPI_CH7, PPI, 7 => configurable); +impl_ppi_channel!(PPI_CH8, PPI, 8 => configurable); +impl_ppi_channel!(PPI_CH9, PPI, 9 => configurable); +impl_ppi_channel!(PPI_CH10, PPI, 10 => configurable); +impl_ppi_channel!(PPI_CH11, PPI, 11 => configurable); +impl_ppi_channel!(PPI_CH12, PPI, 12 => configurable); +impl_ppi_channel!(PPI_CH13, PPI, 13 => configurable); +impl_ppi_channel!(PPI_CH14, PPI, 14 => configurable); +impl_ppi_channel!(PPI_CH15, PPI, 15 => configurable); +impl_ppi_channel!(PPI_CH16, PPI, 16 => configurable); +impl_ppi_channel!(PPI_CH17, PPI, 17 => configurable); +impl_ppi_channel!(PPI_CH18, PPI, 18 => configurable); +impl_ppi_channel!(PPI_CH19, PPI, 19 => configurable); +impl_ppi_channel!(PPI_CH20, PPI, 20 => static); +impl_ppi_channel!(PPI_CH21, PPI, 21 => static); +impl_ppi_channel!(PPI_CH22, PPI, 22 => static); +impl_ppi_channel!(PPI_CH23, PPI, 23 => static); +impl_ppi_channel!(PPI_CH24, PPI, 24 => static); +impl_ppi_channel!(PPI_CH25, PPI, 25 => static); +impl_ppi_channel!(PPI_CH26, PPI, 26 => static); +impl_ppi_channel!(PPI_CH27, PPI, 27 => static); +impl_ppi_channel!(PPI_CH28, PPI, 28 => static); +impl_ppi_channel!(PPI_CH29, PPI, 29 => static); +impl_ppi_channel!(PPI_CH30, PPI, 30 => static); +impl_ppi_channel!(PPI_CH31, PPI, 31 => static); + +impl_ppi_group!(PPI_GROUP0, PPI, 0); +impl_ppi_group!(PPI_GROUP1, PPI, 1); +impl_ppi_group!(PPI_GROUP2, PPI, 2); +impl_ppi_group!(PPI_GROUP3, PPI, 3); +impl_ppi_group!(PPI_GROUP4, PPI, 4); +impl_ppi_group!(PPI_GROUP5, PPI, 5); impl_saadc_input!(P0_02, ANALOG_INPUT0); impl_saadc_input!(P0_03, ANALOG_INPUT1); diff --git a/embassy-nrf/src/chips/nrf5340_app.rs b/embassy-nrf/src/chips/nrf5340_app.rs index 730c9842d..aa51527fb 100644 --- a/embassy-nrf/src/chips/nrf5340_app.rs +++ b/embassy-nrf/src/chips/nrf5340_app.rs @@ -435,38 +435,45 @@ impl_pin!(P1_13, 1, 13); impl_pin!(P1_14, 1, 14); impl_pin!(P1_15, 1, 15); -impl_ppi_channel!(PPI_CH0, 0 => configurable); -impl_ppi_channel!(PPI_CH1, 1 => configurable); -impl_ppi_channel!(PPI_CH2, 2 => configurable); -impl_ppi_channel!(PPI_CH3, 3 => configurable); -impl_ppi_channel!(PPI_CH4, 4 => configurable); -impl_ppi_channel!(PPI_CH5, 5 => configurable); -impl_ppi_channel!(PPI_CH6, 6 => configurable); -impl_ppi_channel!(PPI_CH7, 7 => configurable); -impl_ppi_channel!(PPI_CH8, 8 => configurable); -impl_ppi_channel!(PPI_CH9, 9 => configurable); -impl_ppi_channel!(PPI_CH10, 10 => configurable); -impl_ppi_channel!(PPI_CH11, 11 => configurable); -impl_ppi_channel!(PPI_CH12, 12 => configurable); -impl_ppi_channel!(PPI_CH13, 13 => configurable); -impl_ppi_channel!(PPI_CH14, 14 => configurable); -impl_ppi_channel!(PPI_CH15, 15 => configurable); -impl_ppi_channel!(PPI_CH16, 16 => configurable); -impl_ppi_channel!(PPI_CH17, 17 => configurable); -impl_ppi_channel!(PPI_CH18, 18 => configurable); -impl_ppi_channel!(PPI_CH19, 19 => configurable); -impl_ppi_channel!(PPI_CH20, 20 => configurable); -impl_ppi_channel!(PPI_CH21, 21 => configurable); -impl_ppi_channel!(PPI_CH22, 22 => configurable); -impl_ppi_channel!(PPI_CH23, 23 => configurable); -impl_ppi_channel!(PPI_CH24, 24 => configurable); -impl_ppi_channel!(PPI_CH25, 25 => configurable); -impl_ppi_channel!(PPI_CH26, 26 => configurable); -impl_ppi_channel!(PPI_CH27, 27 => configurable); -impl_ppi_channel!(PPI_CH28, 28 => configurable); -impl_ppi_channel!(PPI_CH29, 29 => configurable); -impl_ppi_channel!(PPI_CH30, 30 => configurable); -impl_ppi_channel!(PPI_CH31, 31 => configurable); +impl_ppi_channel!(PPI_CH0, DPPIC, 0 => configurable); +impl_ppi_channel!(PPI_CH1, DPPIC, 1 => configurable); +impl_ppi_channel!(PPI_CH2, DPPIC, 2 => configurable); +impl_ppi_channel!(PPI_CH3, DPPIC, 3 => configurable); +impl_ppi_channel!(PPI_CH4, DPPIC, 4 => configurable); +impl_ppi_channel!(PPI_CH5, DPPIC, 5 => configurable); +impl_ppi_channel!(PPI_CH6, DPPIC, 6 => configurable); +impl_ppi_channel!(PPI_CH7, DPPIC, 7 => configurable); +impl_ppi_channel!(PPI_CH8, DPPIC, 8 => configurable); +impl_ppi_channel!(PPI_CH9, DPPIC, 9 => configurable); +impl_ppi_channel!(PPI_CH10, DPPIC, 10 => configurable); +impl_ppi_channel!(PPI_CH11, DPPIC, 11 => configurable); +impl_ppi_channel!(PPI_CH12, DPPIC, 12 => configurable); +impl_ppi_channel!(PPI_CH13, DPPIC, 13 => configurable); +impl_ppi_channel!(PPI_CH14, DPPIC, 14 => configurable); +impl_ppi_channel!(PPI_CH15, DPPIC, 15 => configurable); +impl_ppi_channel!(PPI_CH16, DPPIC, 16 => configurable); +impl_ppi_channel!(PPI_CH17, DPPIC, 17 => configurable); +impl_ppi_channel!(PPI_CH18, DPPIC, 18 => configurable); +impl_ppi_channel!(PPI_CH19, DPPIC, 19 => configurable); +impl_ppi_channel!(PPI_CH20, DPPIC, 20 => configurable); +impl_ppi_channel!(PPI_CH21, DPPIC, 21 => configurable); +impl_ppi_channel!(PPI_CH22, DPPIC, 22 => configurable); +impl_ppi_channel!(PPI_CH23, DPPIC, 23 => configurable); +impl_ppi_channel!(PPI_CH24, DPPIC, 24 => configurable); +impl_ppi_channel!(PPI_CH25, DPPIC, 25 => configurable); +impl_ppi_channel!(PPI_CH26, DPPIC, 26 => configurable); +impl_ppi_channel!(PPI_CH27, DPPIC, 27 => configurable); +impl_ppi_channel!(PPI_CH28, DPPIC, 28 => configurable); +impl_ppi_channel!(PPI_CH29, DPPIC, 29 => configurable); +impl_ppi_channel!(PPI_CH30, DPPIC, 30 => configurable); +impl_ppi_channel!(PPI_CH31, DPPIC, 31 => configurable); + +impl_ppi_group!(PPI_GROUP0, DPPIC, 0); +impl_ppi_group!(PPI_GROUP1, DPPIC, 1); +impl_ppi_group!(PPI_GROUP2, DPPIC, 2); +impl_ppi_group!(PPI_GROUP3, DPPIC, 3); +impl_ppi_group!(PPI_GROUP4, DPPIC, 4); +impl_ppi_group!(PPI_GROUP5, DPPIC, 5); impl_saadc_input!(P0_04, ANALOG_INPUT0); impl_saadc_input!(P0_05, ANALOG_INPUT1); diff --git a/embassy-nrf/src/chips/nrf5340_net.rs b/embassy-nrf/src/chips/nrf5340_net.rs index 413afc5c5..2207e7bda 100644 --- a/embassy-nrf/src/chips/nrf5340_net.rs +++ b/embassy-nrf/src/chips/nrf5340_net.rs @@ -275,38 +275,45 @@ impl_pin!(P1_13, 1, 13); impl_pin!(P1_14, 1, 14); impl_pin!(P1_15, 1, 15); -impl_ppi_channel!(PPI_CH0, 0 => configurable); -impl_ppi_channel!(PPI_CH1, 1 => configurable); -impl_ppi_channel!(PPI_CH2, 2 => configurable); -impl_ppi_channel!(PPI_CH3, 3 => configurable); -impl_ppi_channel!(PPI_CH4, 4 => configurable); -impl_ppi_channel!(PPI_CH5, 5 => configurable); -impl_ppi_channel!(PPI_CH6, 6 => configurable); -impl_ppi_channel!(PPI_CH7, 7 => configurable); -impl_ppi_channel!(PPI_CH8, 8 => configurable); -impl_ppi_channel!(PPI_CH9, 9 => configurable); -impl_ppi_channel!(PPI_CH10, 10 => configurable); -impl_ppi_channel!(PPI_CH11, 11 => configurable); -impl_ppi_channel!(PPI_CH12, 12 => configurable); -impl_ppi_channel!(PPI_CH13, 13 => configurable); -impl_ppi_channel!(PPI_CH14, 14 => configurable); -impl_ppi_channel!(PPI_CH15, 15 => configurable); -impl_ppi_channel!(PPI_CH16, 16 => configurable); -impl_ppi_channel!(PPI_CH17, 17 => configurable); -impl_ppi_channel!(PPI_CH18, 18 => configurable); -impl_ppi_channel!(PPI_CH19, 19 => configurable); -impl_ppi_channel!(PPI_CH20, 20 => configurable); -impl_ppi_channel!(PPI_CH21, 21 => configurable); -impl_ppi_channel!(PPI_CH22, 22 => configurable); -impl_ppi_channel!(PPI_CH23, 23 => configurable); -impl_ppi_channel!(PPI_CH24, 24 => configurable); -impl_ppi_channel!(PPI_CH25, 25 => configurable); -impl_ppi_channel!(PPI_CH26, 26 => configurable); -impl_ppi_channel!(PPI_CH27, 27 => configurable); -impl_ppi_channel!(PPI_CH28, 28 => configurable); -impl_ppi_channel!(PPI_CH29, 29 => configurable); -impl_ppi_channel!(PPI_CH30, 30 => configurable); -impl_ppi_channel!(PPI_CH31, 31 => configurable); +impl_ppi_channel!(PPI_CH0, DPPIC, 0 => configurable); +impl_ppi_channel!(PPI_CH1, DPPIC, 1 => configurable); +impl_ppi_channel!(PPI_CH2, DPPIC, 2 => configurable); +impl_ppi_channel!(PPI_CH3, DPPIC, 3 => configurable); +impl_ppi_channel!(PPI_CH4, DPPIC, 4 => configurable); +impl_ppi_channel!(PPI_CH5, DPPIC, 5 => configurable); +impl_ppi_channel!(PPI_CH6, DPPIC, 6 => configurable); +impl_ppi_channel!(PPI_CH7, DPPIC, 7 => configurable); +impl_ppi_channel!(PPI_CH8, DPPIC, 8 => configurable); +impl_ppi_channel!(PPI_CH9, DPPIC, 9 => configurable); +impl_ppi_channel!(PPI_CH10, DPPIC, 10 => configurable); +impl_ppi_channel!(PPI_CH11, DPPIC, 11 => configurable); +impl_ppi_channel!(PPI_CH12, DPPIC, 12 => configurable); +impl_ppi_channel!(PPI_CH13, DPPIC, 13 => configurable); +impl_ppi_channel!(PPI_CH14, DPPIC, 14 => configurable); +impl_ppi_channel!(PPI_CH15, DPPIC, 15 => configurable); +impl_ppi_channel!(PPI_CH16, DPPIC, 16 => configurable); +impl_ppi_channel!(PPI_CH17, DPPIC, 17 => configurable); +impl_ppi_channel!(PPI_CH18, DPPIC, 18 => configurable); +impl_ppi_channel!(PPI_CH19, DPPIC, 19 => configurable); +impl_ppi_channel!(PPI_CH20, DPPIC, 20 => configurable); +impl_ppi_channel!(PPI_CH21, DPPIC, 21 => configurable); +impl_ppi_channel!(PPI_CH22, DPPIC, 22 => configurable); +impl_ppi_channel!(PPI_CH23, DPPIC, 23 => configurable); +impl_ppi_channel!(PPI_CH24, DPPIC, 24 => configurable); +impl_ppi_channel!(PPI_CH25, DPPIC, 25 => configurable); +impl_ppi_channel!(PPI_CH26, DPPIC, 26 => configurable); +impl_ppi_channel!(PPI_CH27, DPPIC, 27 => configurable); +impl_ppi_channel!(PPI_CH28, DPPIC, 28 => configurable); +impl_ppi_channel!(PPI_CH29, DPPIC, 29 => configurable); +impl_ppi_channel!(PPI_CH30, DPPIC, 30 => configurable); +impl_ppi_channel!(PPI_CH31, DPPIC, 31 => configurable); + +impl_ppi_group!(PPI_GROUP0, DPPIC, 0); +impl_ppi_group!(PPI_GROUP1, DPPIC, 1); +impl_ppi_group!(PPI_GROUP2, DPPIC, 2); +impl_ppi_group!(PPI_GROUP3, DPPIC, 3); +impl_ppi_group!(PPI_GROUP4, DPPIC, 4); +impl_ppi_group!(PPI_GROUP5, DPPIC, 5); impl_radio!(RADIO, RADIO, RADIO); diff --git a/embassy-nrf/src/chips/nrf54l15_app.rs b/embassy-nrf/src/chips/nrf54l15_app.rs index 901c5e7fc..2e1ac9be8 100644 --- a/embassy-nrf/src/chips/nrf54l15_app.rs +++ b/embassy-nrf/src/chips/nrf54l15_app.rs @@ -200,13 +200,67 @@ pub mod pac { /// The maximum buffer size that the EasyDMA can send/recv in one operation. pub const EASY_DMA_SIZE: usize = (1 << 16) - 1; -//pub const FORCE_COPY_BUFFER_SIZE: usize = 1024; +pub const FORCE_COPY_BUFFER_SIZE: usize = 1024; // 1.5 MB NVM #[allow(unused)] pub const FLASH_SIZE: usize = 1536 * 1024; embassy_hal_internal::peripherals! { + // PPI + PPI00_CH0, + PPI00_CH1, + PPI00_CH2, + PPI00_CH3, + PPI00_CH4, + PPI00_CH5, + PPI00_CH6, + PPI00_CH7, + + PPI20_CH0, + PPI20_CH1, + PPI20_CH2, + PPI20_CH3, + PPI20_CH4, + PPI20_CH5, + PPI20_CH6, + PPI20_CH7, + PPI20_CH8, + PPI20_CH9, + PPI20_CH10, + PPI20_CH11, + PPI20_CH12, + PPI20_CH13, + PPI20_CH14, + PPI20_CH15, + + PPI30_CH0, + PPI30_CH1, + PPI30_CH2, + PPI30_CH3, + + PPI00_GROUP0, + PPI00_GROUP1, + + PPI20_GROUP0, + PPI20_GROUP1, + PPI20_GROUP2, + PPI20_GROUP3, + PPI20_GROUP4, + PPI20_GROUP5, + + PPI30_GROUP0, + PPI30_GROUP1, + + // Timers + TIMER00, + TIMER10, + TIMER20, + TIMER21, + TIMER22, + TIMER23, + TIMER24, + // GPIO port 0 P0_00, P0_01, @@ -253,6 +307,11 @@ embassy_hal_internal::peripherals! { RTC10, RTC30, + // PWM + PWM20, + PWM21, + PWM22, + // SERIAL SERIAL00, SERIAL20, @@ -266,11 +325,6 @@ embassy_hal_internal::peripherals! { // RADIO RADIO, - // TIMER - TIMER00, - TIMER10, - TIMER20, - // PPI BRIDGE PPIB00, PPIB01, @@ -281,10 +335,24 @@ embassy_hal_internal::peripherals! { PPIB22, PPIB30, - // GPIOTE + // GPIOTE instances GPIOTE20, GPIOTE30, + // GPIOTE channels + GPIOTE_CH0, + GPIOTE_CH1, + GPIOTE_CH2, + GPIOTE_CH3, + GPIOTE_CH4, + GPIOTE_CH5, + GPIOTE_CH6, + GPIOTE_CH7, + GPIOTE_CH8, + GPIOTE_CH9, + GPIOTE_CH10, + GPIOTE_CH11, + // CRACEN CRACEN, @@ -311,6 +379,13 @@ impl_pin!(P0_03, 0, 3); impl_pin!(P0_04, 0, 4); impl_pin!(P0_05, 0, 5); impl_pin!(P0_06, 0, 6); +impl_gpiote_pin!(P0_00, GPIOTE30); +impl_gpiote_pin!(P0_01, GPIOTE30); +impl_gpiote_pin!(P0_02, GPIOTE30); +impl_gpiote_pin!(P0_03, GPIOTE30); +impl_gpiote_pin!(P0_04, GPIOTE30); +impl_gpiote_pin!(P0_05, GPIOTE30); +impl_gpiote_pin!(P0_06, GPIOTE30); impl_pin!(P1_00, 1, 0); impl_pin!(P1_01, 1, 1); @@ -330,6 +405,24 @@ impl_pin!(P1_14, 1, 14); impl_pin!(P1_15, 1, 15); impl_pin!(P1_16, 1, 16); +impl_gpiote_pin!(P1_00, GPIOTE20); +impl_gpiote_pin!(P1_01, GPIOTE20); +impl_gpiote_pin!(P1_02, GPIOTE20); +impl_gpiote_pin!(P1_03, GPIOTE20); +impl_gpiote_pin!(P1_04, GPIOTE20); +impl_gpiote_pin!(P1_05, GPIOTE20); +impl_gpiote_pin!(P1_06, GPIOTE20); +impl_gpiote_pin!(P1_07, GPIOTE20); +impl_gpiote_pin!(P1_08, GPIOTE20); +impl_gpiote_pin!(P1_09, GPIOTE20); +impl_gpiote_pin!(P1_10, GPIOTE20); +impl_gpiote_pin!(P1_11, GPIOTE20); +impl_gpiote_pin!(P1_12, GPIOTE20); +impl_gpiote_pin!(P1_13, GPIOTE20); +impl_gpiote_pin!(P1_14, GPIOTE20); +impl_gpiote_pin!(P1_15, GPIOTE20); +impl_gpiote_pin!(P1_16, GPIOTE20); + impl_pin!(P2_00, 2, 0); impl_pin!(P2_01, 2, 1); impl_pin!(P2_02, 2, 2); @@ -351,6 +444,107 @@ impl_wdt!(WDT, WDT31, WDT31, 0); impl_wdt!(WDT0, WDT31, WDT31, 0); #[cfg(feature = "_s")] impl_wdt!(WDT1, WDT30, WDT30, 1); +// DPPI00 channels +impl_ppi_channel!(PPI00_CH0, DPPIC00, 0 => configurable); +impl_ppi_channel!(PPI00_CH1, DPPIC00, 1 => configurable); +impl_ppi_channel!(PPI00_CH2, DPPIC00, 2 => configurable); +impl_ppi_channel!(PPI00_CH3, DPPIC00, 3 => configurable); +impl_ppi_channel!(PPI00_CH4, DPPIC00, 4 => configurable); +impl_ppi_channel!(PPI00_CH5, DPPIC00, 5 => configurable); +impl_ppi_channel!(PPI00_CH6, DPPIC00, 6 => configurable); +impl_ppi_channel!(PPI00_CH7, DPPIC00, 7 => configurable); + +// DPPI20 channels +impl_ppi_channel!(PPI20_CH0, DPPIC20, 0 => configurable); +impl_ppi_channel!(PPI20_CH1, DPPIC20, 1 => configurable); +impl_ppi_channel!(PPI20_CH2, DPPIC20, 2 => configurable); +impl_ppi_channel!(PPI20_CH3, DPPIC20, 3 => configurable); +impl_ppi_channel!(PPI20_CH4, DPPIC20, 4 => configurable); +impl_ppi_channel!(PPI20_CH5, DPPIC20, 5 => configurable); +impl_ppi_channel!(PPI20_CH6, DPPIC20, 6 => configurable); +impl_ppi_channel!(PPI20_CH7, DPPIC20, 7 => configurable); +impl_ppi_channel!(PPI20_CH8, DPPIC20, 8 => configurable); +impl_ppi_channel!(PPI20_CH9, DPPIC20, 9 => configurable); +impl_ppi_channel!(PPI20_CH10, DPPIC20, 10 => configurable); +impl_ppi_channel!(PPI20_CH11, DPPIC20, 11 => configurable); +impl_ppi_channel!(PPI20_CH12, DPPIC20, 12 => configurable); +impl_ppi_channel!(PPI20_CH13, DPPIC20, 13 => configurable); +impl_ppi_channel!(PPI20_CH14, DPPIC20, 14 => configurable); +impl_ppi_channel!(PPI20_CH15, DPPIC20, 15 => configurable); + +// DPPI30 channels +impl_ppi_channel!(PPI30_CH0, DPPIC30, 0 => configurable); +impl_ppi_channel!(PPI30_CH1, DPPIC30, 1 => configurable); +impl_ppi_channel!(PPI30_CH2, DPPIC30, 2 => configurable); +impl_ppi_channel!(PPI30_CH3, DPPIC30, 3 => configurable); + +// DPPI00 groups +impl_ppi_group!(PPI00_GROUP0, DPPIC00, 0); +impl_ppi_group!(PPI00_GROUP1, DPPIC00, 1); + +// DPPI20 groups +impl_ppi_group!(PPI20_GROUP0, DPPIC20, 0); +impl_ppi_group!(PPI20_GROUP1, DPPIC20, 1); +impl_ppi_group!(PPI20_GROUP2, DPPIC20, 2); +impl_ppi_group!(PPI20_GROUP3, DPPIC20, 3); +impl_ppi_group!(PPI20_GROUP4, DPPIC20, 4); +impl_ppi_group!(PPI20_GROUP5, DPPIC20, 5); + +// DPPI30 groups +impl_ppi_group!(PPI30_GROUP0, DPPIC30, 0); +impl_ppi_group!(PPI30_GROUP1, DPPIC30, 1); + +// impl_ppi_channel!(PPI10_CH0, pac::DPPIC10, 0 => static); +// impl_ppi_group!(PPI10_GROUP0, pac::DPPIC10, 0); + +impl_timer!(TIMER00, TIMER00, TIMER00); +impl_timer!(TIMER10, TIMER10, TIMER10); +impl_timer!(TIMER20, TIMER20, TIMER20); +impl_timer!(TIMER21, TIMER21, TIMER21); +impl_timer!(TIMER22, TIMER22, TIMER22); +impl_timer!(TIMER23, TIMER23, TIMER23); +impl_timer!(TIMER24, TIMER24, TIMER24); + +impl_twim!(SERIAL20, TWIM20, SERIAL20); +impl_twim!(SERIAL21, TWIM21, SERIAL21); +impl_twim!(SERIAL22, TWIM22, SERIAL22); +impl_twim!(SERIAL30, TWIM30, SERIAL30); + +impl_twis!(SERIAL20, TWIS20, SERIAL20); +impl_twis!(SERIAL21, TWIS21, SERIAL21); +impl_twis!(SERIAL22, TWIS22, SERIAL22); +impl_twis!(SERIAL30, TWIS30, SERIAL30); + +impl_pwm!(PWM20, PWM20, PWM20); +impl_pwm!(PWM21, PWM21, PWM21); +impl_pwm!(PWM22, PWM22, PWM22); + +impl_spim!(SERIAL00, SPIM00, SERIAL00, 128_000_000); +impl_spim!(SERIAL20, SPIM20, SERIAL20, 16_000_000); +impl_spim!(SERIAL21, SPIM21, SERIAL21, 16_000_000); +impl_spim!(SERIAL22, SPIM22, SERIAL22, 16_000_000); +impl_spim!(SERIAL30, SPIM30, SERIAL30, 16_000_000); + +impl_spis!(SERIAL20, SPIS20, SERIAL20); +impl_spis!(SERIAL21, SPIS21, SERIAL21); +impl_spis!(SERIAL22, SPIS22, SERIAL22); +impl_spis!(SERIAL30, SPIS30, SERIAL30); + +impl_uarte!(SERIAL00, UARTE00, SERIAL00); +impl_uarte!(SERIAL20, UARTE20, SERIAL20); +impl_uarte!(SERIAL21, UARTE21, SERIAL21); +impl_uarte!(SERIAL22, UARTE22, SERIAL22); +impl_uarte!(SERIAL30, UARTE30, SERIAL30); + +// NB: SAADC uses "pin" abstraction, not "AIN" +impl_saadc_input!(P1_04, 1, 4); +impl_saadc_input!(P1_05, 1, 5); +impl_saadc_input!(P1_06, 1, 6); +impl_saadc_input!(P1_07, 1, 7); +impl_saadc_input!(P1_11, 1, 11); +impl_saadc_input!(P1_12, 1, 12); +impl_saadc_input!(P1_13, 1, 13); +impl_saadc_input!(P1_14, 1, 14); embassy_hal_internal::interrupt_mod!( SWI00, diff --git a/embassy-nrf/src/chips/nrf9120.rs b/embassy-nrf/src/chips/nrf9120.rs index 5aee19d97..e9f313fef 100644 --- a/embassy-nrf/src/chips/nrf9120.rs +++ b/embassy-nrf/src/chips/nrf9120.rs @@ -314,22 +314,29 @@ impl_pin!(P0_29, 0, 29); impl_pin!(P0_30, 0, 30); impl_pin!(P0_31, 0, 31); -impl_ppi_channel!(PPI_CH0, 0 => configurable); -impl_ppi_channel!(PPI_CH1, 1 => configurable); -impl_ppi_channel!(PPI_CH2, 2 => configurable); -impl_ppi_channel!(PPI_CH3, 3 => configurable); -impl_ppi_channel!(PPI_CH4, 4 => configurable); -impl_ppi_channel!(PPI_CH5, 5 => configurable); -impl_ppi_channel!(PPI_CH6, 6 => configurable); -impl_ppi_channel!(PPI_CH7, 7 => configurable); -impl_ppi_channel!(PPI_CH8, 8 => configurable); -impl_ppi_channel!(PPI_CH9, 9 => configurable); -impl_ppi_channel!(PPI_CH10, 10 => configurable); -impl_ppi_channel!(PPI_CH11, 11 => configurable); -impl_ppi_channel!(PPI_CH12, 12 => configurable); -impl_ppi_channel!(PPI_CH13, 13 => configurable); -impl_ppi_channel!(PPI_CH14, 14 => configurable); -impl_ppi_channel!(PPI_CH15, 15 => configurable); +impl_ppi_channel!(PPI_CH0, DPPIC, 0 => configurable); +impl_ppi_channel!(PPI_CH1, DPPIC, 1 => configurable); +impl_ppi_channel!(PPI_CH2, DPPIC, 2 => configurable); +impl_ppi_channel!(PPI_CH3, DPPIC, 3 => configurable); +impl_ppi_channel!(PPI_CH4, DPPIC, 4 => configurable); +impl_ppi_channel!(PPI_CH5, DPPIC, 5 => configurable); +impl_ppi_channel!(PPI_CH6, DPPIC, 6 => configurable); +impl_ppi_channel!(PPI_CH7, DPPIC, 7 => configurable); +impl_ppi_channel!(PPI_CH8, DPPIC, 8 => configurable); +impl_ppi_channel!(PPI_CH9, DPPIC, 9 => configurable); +impl_ppi_channel!(PPI_CH10, DPPIC, 10 => configurable); +impl_ppi_channel!(PPI_CH11, DPPIC, 11 => configurable); +impl_ppi_channel!(PPI_CH12, DPPIC, 12 => configurable); +impl_ppi_channel!(PPI_CH13, DPPIC, 13 => configurable); +impl_ppi_channel!(PPI_CH14, DPPIC, 14 => configurable); +impl_ppi_channel!(PPI_CH15, DPPIC, 15 => configurable); + +impl_ppi_group!(PPI_GROUP0, DPPIC, 0); +impl_ppi_group!(PPI_GROUP1, DPPIC, 1); +impl_ppi_group!(PPI_GROUP2, DPPIC, 2); +impl_ppi_group!(PPI_GROUP3, DPPIC, 3); +impl_ppi_group!(PPI_GROUP4, DPPIC, 4); +impl_ppi_group!(PPI_GROUP5, DPPIC, 5); impl_saadc_input!(P0_13, ANALOG_INPUT0); impl_saadc_input!(P0_14, ANALOG_INPUT1); diff --git a/embassy-nrf/src/chips/nrf9160.rs b/embassy-nrf/src/chips/nrf9160.rs index 64aec217c..4c6f055dd 100644 --- a/embassy-nrf/src/chips/nrf9160.rs +++ b/embassy-nrf/src/chips/nrf9160.rs @@ -314,22 +314,29 @@ impl_pin!(P0_29, 0, 29); impl_pin!(P0_30, 0, 30); impl_pin!(P0_31, 0, 31); -impl_ppi_channel!(PPI_CH0, 0 => configurable); -impl_ppi_channel!(PPI_CH1, 1 => configurable); -impl_ppi_channel!(PPI_CH2, 2 => configurable); -impl_ppi_channel!(PPI_CH3, 3 => configurable); -impl_ppi_channel!(PPI_CH4, 4 => configurable); -impl_ppi_channel!(PPI_CH5, 5 => configurable); -impl_ppi_channel!(PPI_CH6, 6 => configurable); -impl_ppi_channel!(PPI_CH7, 7 => configurable); -impl_ppi_channel!(PPI_CH8, 8 => configurable); -impl_ppi_channel!(PPI_CH9, 9 => configurable); -impl_ppi_channel!(PPI_CH10, 10 => configurable); -impl_ppi_channel!(PPI_CH11, 11 => configurable); -impl_ppi_channel!(PPI_CH12, 12 => configurable); -impl_ppi_channel!(PPI_CH13, 13 => configurable); -impl_ppi_channel!(PPI_CH14, 14 => configurable); -impl_ppi_channel!(PPI_CH15, 15 => configurable); +impl_ppi_channel!(PPI_CH0, DPPIC, 0 => configurable); +impl_ppi_channel!(PPI_CH1, DPPIC, 1 => configurable); +impl_ppi_channel!(PPI_CH2, DPPIC, 2 => configurable); +impl_ppi_channel!(PPI_CH3, DPPIC, 3 => configurable); +impl_ppi_channel!(PPI_CH4, DPPIC, 4 => configurable); +impl_ppi_channel!(PPI_CH5, DPPIC, 5 => configurable); +impl_ppi_channel!(PPI_CH6, DPPIC, 6 => configurable); +impl_ppi_channel!(PPI_CH7, DPPIC, 7 => configurable); +impl_ppi_channel!(PPI_CH8, DPPIC, 8 => configurable); +impl_ppi_channel!(PPI_CH9, DPPIC, 9 => configurable); +impl_ppi_channel!(PPI_CH10, DPPIC, 10 => configurable); +impl_ppi_channel!(PPI_CH11, DPPIC, 11 => configurable); +impl_ppi_channel!(PPI_CH12, DPPIC, 12 => configurable); +impl_ppi_channel!(PPI_CH13, DPPIC, 13 => configurable); +impl_ppi_channel!(PPI_CH14, DPPIC, 14 => configurable); +impl_ppi_channel!(PPI_CH15, DPPIC, 15 => configurable); + +impl_ppi_group!(PPI_GROUP0, DPPIC, 0); +impl_ppi_group!(PPI_GROUP1, DPPIC, 1); +impl_ppi_group!(PPI_GROUP2, DPPIC, 2); +impl_ppi_group!(PPI_GROUP3, DPPIC, 3); +impl_ppi_group!(PPI_GROUP4, DPPIC, 4); +impl_ppi_group!(PPI_GROUP5, DPPIC, 5); impl_saadc_input!(P0_13, ANALOG_INPUT0); impl_saadc_input!(P0_14, ANALOG_INPUT1); diff --git a/embassy-nrf/src/gpio.rs b/embassy-nrf/src/gpio.rs index 7ed3a7927..43d1b9cb2 100644 --- a/embassy-nrf/src/gpio.rs +++ b/embassy-nrf/src/gpio.rs @@ -585,7 +585,6 @@ impl SealedPin for AnyPin { // ==================== #[cfg(not(feature = "_nrf51"))] -#[cfg_attr(feature = "_nrf54l", allow(unused))] // TODO pub(crate) trait PselBits { fn psel_bits(&self) -> pac::shared::regs::Psel; } @@ -602,7 +601,6 @@ impl<'a, P: Pin> PselBits for Option> { } #[cfg(not(feature = "_nrf51"))] -#[cfg_attr(feature = "_nrf54l", allow(unused))] // TODO pub(crate) const DISCONNECTED: Psel = Psel(1 << 31); #[cfg(not(feature = "_nrf51"))] diff --git a/embassy-nrf/src/gpiote.rs b/embassy-nrf/src/gpiote.rs index 3658657c0..ed95f5d83 100644 --- a/embassy-nrf/src/gpiote.rs +++ b/embassy-nrf/src/gpiote.rs @@ -1,4 +1,5 @@ //! GPIO task/event (GPIOTE) driver. +#![macro_use] use core::convert::Infallible; use core::future::{Future, poll_fn}; @@ -7,7 +8,7 @@ use core::task::{Context, Poll}; use embassy_hal_internal::{Peri, PeripheralType, impl_peripheral}; use embassy_sync::waitqueue::AtomicWaker; -use crate::gpio::{AnyPin, Flex, Input, Output, Pin as GpioPin, SealedPin as _}; +use crate::gpio::{AnyPin, Flex, Input, Level, Output, OutputDrive, Pin as GpioPin, Pull, SealedPin as _}; use crate::interrupt::InterruptExt; #[cfg(not(feature = "_nrf51"))] use crate::pac::gpio::vals::Detectmode; @@ -19,13 +20,28 @@ use crate::{interrupt, pac, peripherals}; #[cfg(feature = "_nrf51")] /// Amount of GPIOTE channels in the chip. const CHANNEL_COUNT: usize = 4; -#[cfg(not(feature = "_nrf51"))] +#[cfg(not(any(feature = "_nrf51", feature = "_nrf54l")))] /// Amount of GPIOTE channels in the chip. const CHANNEL_COUNT: usize = 8; - -#[cfg(any(feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340"))] +#[cfg(any(feature = "_nrf54l"))] +/// Amount of GPIOTE channels in the chip. +const CHANNEL_COUNT: usize = 12; +/// Max channels per port +const CHANNELS_PER_PORT: usize = 8; + +#[cfg(any( + feature = "nrf52833", + feature = "nrf52840", + feature = "_nrf5340", + feature = "_nrf54l" +))] const PIN_COUNT: usize = 48; -#[cfg(not(any(feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340")))] +#[cfg(not(any( + feature = "nrf52833", + feature = "nrf52840", + feature = "_nrf5340", + feature = "_nrf54l" +)))] const PIN_COUNT: usize = 32; #[allow(clippy::declare_interior_mutable_const)] @@ -54,18 +70,6 @@ pub enum OutputChannelPolarity { Toggle, } -fn regs() -> pac::gpiote::Gpiote { - cfg_if::cfg_if! { - if #[cfg(any(feature="nrf5340-app-s", feature="nrf9160-s", feature="nrf9120-s"))] { - pac::GPIOTE0 - } else if #[cfg(any(feature="nrf5340-app-ns", feature="nrf9160-ns", feature="nrf9120-ns"))] { - pac::GPIOTE1 - } else { - pac::GPIOTE - } - } -} - pub(crate) fn init(irq_prio: crate::interrupt::Priority) { // no latched GPIO detect in nrf51. #[cfg(not(feature = "_nrf51"))] @@ -77,9 +81,9 @@ pub(crate) fn init(irq_prio: crate::interrupt::Priority) { for &p in ports { // Enable latched detection - #[cfg(feature = "_s")] + #[cfg(all(feature = "_s", not(feature = "_nrf54l")))] p.detectmode_sec().write(|w| w.set_detectmode(Detectmode::LDETECT)); - #[cfg(not(feature = "_s"))] + #[cfg(any(not(feature = "_s"), feature = "_nrf54l"))] p.detectmode().write(|w| w.set_detectmode(Detectmode::LDETECT)); // Clear latch p.latch().write(|w| w.0 = 0xFFFFFFFF) @@ -88,57 +92,136 @@ pub(crate) fn init(irq_prio: crate::interrupt::Priority) { // Enable interrupts #[cfg(any(feature = "nrf5340-app-s", feature = "nrf9160-s", feature = "nrf9120-s"))] - let irq = interrupt::GPIOTE0; + let irqs = &[(pac::GPIOTE0, interrupt::GPIOTE0)]; #[cfg(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns", feature = "nrf9120-ns"))] - let irq = interrupt::GPIOTE1; + let irqs = &[(pac::GPIOTE1, interrupt::GPIOTE1)]; #[cfg(any(feature = "_nrf51", feature = "_nrf52", feature = "nrf5340-net"))] - let irq = interrupt::GPIOTE; + let irqs = &[(pac::GPIOTE, interrupt::GPIOTE)]; + #[cfg(any(feature = "_nrf54l"))] + let irqs = &[ + #[cfg(feature = "_s")] + (pac::GPIOTE20, interrupt::GPIOTE20_0), + #[cfg(feature = "_s")] + (pac::GPIOTE30, interrupt::GPIOTE30_0), + #[cfg(feature = "_ns")] + (pac::GPIOTE20, interrupt::GPIOTE20_1), + #[cfg(feature = "_ns")] + (pac::GPIOTE30, interrupt::GPIOTE30_1), + ]; + + for (inst, irq) in irqs { + irq.unpend(); + irq.set_priority(irq_prio); + unsafe { irq.enable() }; - irq.unpend(); - irq.set_priority(irq_prio); - unsafe { irq.enable() }; + let g = inst; + #[cfg(not(feature = "_nrf54l"))] + g.intenset(INTNUM).write(|w| w.set_port(true)); - let g = regs(); - g.intenset().write(|w| w.set_port(true)); + #[cfg(all(feature = "_nrf54l", feature = "_ns"))] + g.intenset(INTNUM).write(|w| w.set_port0nonsecure(true)); + + #[cfg(all(feature = "_nrf54l", feature = "_s"))] + g.intenset(INTNUM).write(|w| w.set_port0secure(true)); + } } +#[cfg(all(feature = "_nrf54l", feature = "_ns"))] +const INTNUM: usize = 1; + +#[cfg(any(not(feature = "_nrf54l"), feature = "_s"))] +const INTNUM: usize = 0; + #[cfg(any(feature = "nrf5340-app-s", feature = "nrf9160-s", feature = "nrf9120-s"))] #[cfg(feature = "rt")] #[interrupt] fn GPIOTE0() { - unsafe { handle_gpiote_interrupt() }; + unsafe { handle_gpiote_interrupt(pac::GPIOTE0) }; } #[cfg(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns", feature = "nrf9120-ns"))] #[cfg(feature = "rt")] #[interrupt] fn GPIOTE1() { - unsafe { handle_gpiote_interrupt() }; + unsafe { handle_gpiote_interrupt(pac::GPIOTE1) }; } #[cfg(any(feature = "_nrf51", feature = "_nrf52", feature = "nrf5340-net"))] #[cfg(feature = "rt")] #[interrupt] fn GPIOTE() { - unsafe { handle_gpiote_interrupt() }; + info!("GPIOTE!"); + unsafe { handle_gpiote_interrupt(pac::GPIOTE) }; } -unsafe fn handle_gpiote_interrupt() { - let g = regs(); +#[cfg(all(feature = "_nrf54l", feature = "_s"))] +#[cfg(feature = "rt")] +#[interrupt] +fn GPIOTE20_0() { + info!("GPIOTE20_0!"); + unsafe { handle_gpiote_interrupt(pac::GPIOTE20) }; +} - for i in 0..CHANNEL_COUNT { +#[cfg(all(feature = "_nrf54l", feature = "_s"))] +#[cfg(feature = "rt")] +#[interrupt] +fn GPIOTE30_0() { + info!("GPIOTE30_0!"); + unsafe { handle_gpiote_interrupt(pac::GPIOTE30) }; +} + +#[cfg(all(feature = "_nrf54l", feature = "_ns"))] +#[cfg(feature = "rt")] +#[interrupt] +fn GPIOTE20_1() { + info!("GPIOTE20_1!"); + unsafe { handle_gpiote_interrupt(pac::GPIOTE20) }; +} + +#[cfg(all(feature = "_nrf54l", feature = "_ns"))] +#[cfg(feature = "rt")] +#[interrupt] +fn GPIOTE30_1() { + info!("GPIOTE30_1!"); + unsafe { handle_gpiote_interrupt(pac::GPIOTE30) }; +} + +unsafe fn handle_gpiote_interrupt(g: pac::gpiote::Gpiote) { + for c in 0..CHANNEL_COUNT { + let i = c % CHANNELS_PER_PORT; if g.events_in(i).read() != 0 { - g.intenclr().write(|w| w.0 = 1 << i); - CHANNEL_WAKERS[i].wake(); + info!("Clear IRQ {} waker {}", INTNUM, c); + g.intenclr(INTNUM).write(|w| w.0 = 1 << i); + CHANNEL_WAKERS[c].wake(); } } - if g.events_port().read() != 0 { - g.events_port().write_value(0); + #[cfg(not(feature = "_nrf54l"))] + let eport = g.events_port(0); - #[cfg(any(feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340"))] + #[cfg(all(feature = "_nrf54l", feature = "_ns"))] + let eport = g.events_port(0).nonsecure(); + + #[cfg(all(feature = "_nrf54l", feature = "_s"))] + let eport = g.events_port(0).secure(); + + if eport.read() != 0 { + eport.write_value(0); + + #[cfg(any( + feature = "nrf52833", + feature = "nrf52840", + feature = "_nrf5340", + feature = "_nrf54l" + ))] let ports = &[pac::P0, pac::P1]; - #[cfg(not(any(feature = "_nrf51", feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340")))] + #[cfg(not(any( + feature = "_nrf51", + feature = "nrf52833", + feature = "nrf52840", + feature = "_nrf5340", + feature = "_nrf54l" + )))] let ports = &[pac::P0]; #[cfg(feature = "_nrf51")] let ports = &[pac::GPIO]; @@ -162,9 +245,14 @@ unsafe fn handle_gpiote_interrupt() { #[cfg(not(feature = "_nrf51"))] for (port, &p) in ports.iter().enumerate() { + info!("Interrupt port {}", port); let bits = p.latch().read().0; for pin in BitIter(bits) { p.pin_cnf(pin as usize).modify(|w| w.set_sense(Sense::DISABLED)); + + let w = port * 32 + pin as usize; + + info!("Interrupt pin {}, waker {}", pin as usize, w); PORT_WAKERS[port * 32 + pin as usize].wake(); } p.latch().write(|w| w.0 = bits); @@ -207,19 +295,43 @@ impl InputChannel<'static> { impl<'d> Drop for InputChannel<'d> { fn drop(&mut self) { - let g = regs(); + let g = self.ch.regs(); let num = self.ch.number(); g.config(num).write(|w| w.set_mode(Mode::DISABLED)); - g.intenclr().write(|w| w.0 = 1 << num); + g.intenclr(INTNUM).write(|w| w.0 = 1 << num); } } impl<'d> InputChannel<'d> { /// Create a new GPIOTE input channel driver. - pub fn new(ch: Peri<'d, impl Channel>, pin: Input<'d>, polarity: InputChannelPolarity) -> Self { - let g = regs(); - let num = ch.number(); + #[cfg(feature = "_nrf54l")] + pub fn new>( + ch: Peri<'d, C>, + pin: Peri<'d, T>, + pull: Pull, + polarity: InputChannelPolarity, + ) -> Self { + let pin = Input::new(pin, pull); + let ch = ch.into(); + Self::new_inner(ch, pin, polarity) + } + /// Create a new GPIOTE output channel driver. + #[cfg(not(feature = "_nrf54l"))] + pub fn new( + ch: Peri<'d, C>, + pin: Peri<'d, T>, + pull: Pull, + polarity: InputChannelPolarity, + ) -> Self { + let pin = Input::new(pin, pull); + let ch = ch.into(); + Self::new_inner(ch, pin, polarity) + } + + fn new_inner(ch: Peri<'d, AnyChannel>, pin: Input<'d>, polarity: InputChannelPolarity) -> Self { + let g = ch.regs(); + let num = ch.number(); g.config(num).write(|w| { w.set_mode(Mode::EVENT); match polarity { @@ -228,30 +340,38 @@ impl<'d> InputChannel<'d> { InputChannelPolarity::None => w.set_polarity(Polarity::NONE), InputChannelPolarity::Toggle => w.set_polarity(Polarity::TOGGLE), }; - #[cfg(any(feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340"))] + #[cfg(any(feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340",))] w.set_port(match pin.pin.pin.port() { crate::gpio::Port::Port0 => false, crate::gpio::Port::Port1 => true, }); + #[cfg(any(feature = "_nrf54l"))] + w.set_port(match pin.pin.pin.port() { + crate::gpio::Port::Port0 => 0, + crate::gpio::Port::Port1 => 1, + crate::gpio::Port::Port2 => 2, + }); w.set_psel(pin.pin.pin.pin()); }); g.events_in(num).write_value(0); - InputChannel { ch: ch.into(), pin } + InputChannel { ch, pin } } /// Asynchronously wait for an event in this channel. pub async fn wait(&self) { - let g = regs(); + let g = self.ch.regs(); let num = self.ch.number(); + let waker = self.ch.waker(); // Enable interrupt g.events_in(num).write_value(0); - g.intenset().write(|w| w.0 = 1 << num); + g.intenset(INTNUM).write(|w| w.0 = 1 << num); poll_fn(|cx| { - CHANNEL_WAKERS[num].register(cx.waker()); + info!("Waiting for channel waker {}", num); + CHANNEL_WAKERS[waker].register(cx.waker()); if g.events_in(num).read() != 0 { Poll::Ready(()) @@ -269,7 +389,7 @@ impl<'d> InputChannel<'d> { /// Returns the IN event, for use with PPI. pub fn event_in(&self) -> Event<'d> { - let g = regs(); + let g = self.ch.regs(); Event::from_reg(g.events_in(self.ch.number())) } } @@ -291,17 +411,44 @@ impl OutputChannel<'static> { impl<'d> Drop for OutputChannel<'d> { fn drop(&mut self) { - let g = regs(); + let g = self.ch.regs(); let num = self.ch.number(); g.config(num).write(|w| w.set_mode(Mode::DISABLED)); - g.intenclr().write(|w| w.0 = 1 << num); + g.intenclr(INTNUM).write(|w| w.0 = 1 << num); } } impl<'d> OutputChannel<'d> { /// Create a new GPIOTE output channel driver. - pub fn new(ch: Peri<'d, impl Channel>, pin: Output<'d>, polarity: OutputChannelPolarity) -> Self { - let g = regs(); + #[cfg(feature = "_nrf54l")] + pub fn new>( + ch: Peri<'d, C>, + pin: Peri<'d, T>, + initial_output: Level, + drive: OutputDrive, + polarity: OutputChannelPolarity, + ) -> Self { + let pin = Output::new(pin, initial_output, drive); + let ch = ch.into(); + Self::new_inner(ch, pin, polarity) + } + + /// Create a new GPIOTE output channel driver. + #[cfg(not(feature = "_nrf54l"))] + pub fn new( + ch: Peri<'d, C>, + pin: Peri<'d, T>, + initial_output: Level, + drive: OutputDrive, + polarity: OutputChannelPolarity, + ) -> Self { + let pin = Output::new(pin, initial_output, drive); + let ch = ch.into(); + Self::new_inner(ch, pin, polarity) + } + + fn new_inner(ch: Peri<'d, AnyChannel>, pin: Output<'d>, polarity: OutputChannelPolarity) -> Self { + let g = ch.regs(); let num = ch.number(); g.config(num).write(|w| { @@ -320,52 +467,55 @@ impl<'d> OutputChannel<'d> { crate::gpio::Port::Port0 => false, crate::gpio::Port::Port1 => true, }); + #[cfg(any(feature = "_nrf54l"))] + w.set_port(match pin.pin.pin.port() { + crate::gpio::Port::Port0 => 0, + crate::gpio::Port::Port1 => 1, + crate::gpio::Port::Port2 => 2, + }); w.set_psel(pin.pin.pin.pin()); }); - OutputChannel { - ch: ch.into(), - _pin: pin, - } + OutputChannel { ch, _pin: pin } } /// Triggers the OUT task (does the action as configured with task_out_polarity, defaults to Toggle). pub fn out(&self) { - let g = regs(); + let g = self.ch.regs(); g.tasks_out(self.ch.number()).write_value(1); } /// Triggers the SET task (set associated pin high). #[cfg(not(feature = "_nrf51"))] pub fn set(&self) { - let g = regs(); + let g = self.ch.regs(); g.tasks_set(self.ch.number()).write_value(1); } /// Triggers the CLEAR task (set associated pin low). #[cfg(not(feature = "_nrf51"))] pub fn clear(&self) { - let g = regs(); + let g = self.ch.regs(); g.tasks_clr(self.ch.number()).write_value(1); } /// Returns the OUT task, for use with PPI. pub fn task_out(&self) -> Task<'d> { - let g = regs(); + let g = self.ch.regs(); Task::from_reg(g.tasks_out(self.ch.number())) } /// Returns the CLR task, for use with PPI. #[cfg(not(feature = "_nrf51"))] pub fn task_clr(&self) -> Task<'d> { - let g = regs(); + let g = self.ch.regs(); Task::from_reg(g.tasks_clr(self.ch.number())) } /// Returns the SET task, for use with PPI. #[cfg(not(feature = "_nrf51"))] pub fn task_set(&self) -> Task<'d> { - let g = regs(); + let g = self.ch.regs(); Task::from_reg(g.tasks_set(self.ch.number())) } } @@ -395,6 +545,7 @@ impl<'a> Future for PortInputFuture<'a> { type Output = (); fn poll(self: core::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + info!("register waker on {}", self.pin.port() as usize); PORT_WAKERS[self.pin.pin_port() as usize].register(cx.waker()); if self.pin.conf().read().sense() == Sense::DISABLED { @@ -467,31 +618,52 @@ impl<'d> Flex<'d> { PortInputFuture::new(self.pin.reborrow()).await } } - // ======================= +// -trait SealedChannel {} +trait SealedChannel { + fn waker(&self) -> usize; + fn regs(&self) -> pac::gpiote::Gpiote; +} /// GPIOTE channel trait. /// /// Implemented by all GPIOTE channels. #[allow(private_bounds)] pub trait Channel: PeripheralType + SealedChannel + Into + Sized + 'static { + #[cfg(feature = "_nrf54l")] + /// GPIOTE instance this channel belongs to. + type Instance: GpioteInstance; /// Get the channel number. fn number(&self) -> usize; } -/// Type-erased channel. -/// -/// Obtained by calling `Channel::into()`. -/// -/// This allows using several channels in situations that might require -/// them to be the same type, like putting them in an array. -pub struct AnyChannel { +struct AnyChannel { number: u8, + regs: pac::gpiote::Gpiote, + waker: u8, } + impl_peripheral!(AnyChannel); -impl SealedChannel for AnyChannel {} + +impl SealedChannel for AnyChannel { + fn waker(&self) -> usize { + self.waker as usize + } + + fn regs(&self) -> pac::gpiote::Gpiote { + self.regs + } +} + +#[cfg(feature = "_nrf54l")] +impl AnyChannel { + fn number(&self) -> usize { + self.number as usize + } +} + +#[cfg(not(feature = "_nrf54l"))] impl Channel for AnyChannel { fn number(&self) -> usize { self.number as usize @@ -499,9 +671,19 @@ impl Channel for AnyChannel { } macro_rules! impl_channel { - ($type:ident, $number:expr) => { - impl SealedChannel for peripherals::$type {} + ($type:ident, $inst:ident, $number:expr, $waker:expr) => { + impl SealedChannel for peripherals::$type { + fn waker(&self) -> usize { + $waker as usize + } + + fn regs(&self) -> pac::gpiote::Gpiote { + pac::$inst + } + } impl Channel for peripherals::$type { + #[cfg(feature = "_nrf54l")] + type Instance = peripherals::$inst; fn number(&self) -> usize { $number as usize } @@ -511,24 +693,97 @@ macro_rules! impl_channel { fn from(val: peripherals::$type) -> Self { Self { number: val.number() as u8, + waker: val.waker() as u8, + regs: val.regs(), } } } }; } -impl_channel!(GPIOTE_CH0, 0); -impl_channel!(GPIOTE_CH1, 1); -impl_channel!(GPIOTE_CH2, 2); -impl_channel!(GPIOTE_CH3, 3); -#[cfg(not(feature = "_nrf51"))] -impl_channel!(GPIOTE_CH4, 4); -#[cfg(not(feature = "_nrf51"))] -impl_channel!(GPIOTE_CH5, 5); -#[cfg(not(feature = "_nrf51"))] -impl_channel!(GPIOTE_CH6, 6); -#[cfg(not(feature = "_nrf51"))] -impl_channel!(GPIOTE_CH7, 7); +cfg_if::cfg_if! { + if #[cfg(feature = "_nrf54l")] { + trait SealedGpioteInstance {} + /// Represents a GPIOTE instance. + #[allow(private_bounds)] + pub trait GpioteInstance: PeripheralType + SealedGpioteInstance + Sized + 'static {} + + macro_rules! impl_gpiote { + ($type:ident) => { + impl SealedGpioteInstance for peripherals::$type {} + impl GpioteInstance for peripherals::$type {} + }; + } + + pub(crate) trait SealedGpiotePin {} + + /// Represents a GPIO pin that can be used with GPIOTE. + #[allow(private_bounds)] + pub trait GpiotePin: GpioPin + SealedGpiotePin { + /// The GPIOTE instance this pin belongs to. + type Instance: GpioteInstance; + } + + macro_rules! impl_gpiote_pin { + ($type:ident, $inst:ident) => { + #[cfg(feature = "gpiote")] + impl crate::gpiote::SealedGpiotePin for peripherals::$type {} + #[cfg(feature = "gpiote")] + impl crate::gpiote::GpiotePin for peripherals::$type { + type Instance = peripherals::$inst; + } + }; + } + + impl_gpiote!(GPIOTE20); + impl_gpiote!(GPIOTE30); + impl_channel!(GPIOTE_CH0, GPIOTE20, 0, 0); + impl_channel!(GPIOTE_CH1, GPIOTE20, 1, 1); + impl_channel!(GPIOTE_CH2, GPIOTE20, 2, 2); + impl_channel!(GPIOTE_CH3, GPIOTE20, 3, 3); + impl_channel!(GPIOTE_CH4, GPIOTE20, 4, 4); + impl_channel!(GPIOTE_CH5, GPIOTE20, 5, 5); + impl_channel!(GPIOTE_CH6, GPIOTE20, 6, 6); + impl_channel!(GPIOTE_CH7, GPIOTE20, 7, 7); + + impl_channel!(GPIOTE_CH8, GPIOTE30, 0, 8); + impl_channel!(GPIOTE_CH9, GPIOTE30, 1, 9); + impl_channel!(GPIOTE_CH10, GPIOTE30, 2, 10); + impl_channel!(GPIOTE_CH11, GPIOTE30, 3, 11); + } else if #[cfg(feature = "_nrf51")] { + impl_channel!(GPIOTE_CH0, GPIOTE, 0, 0); + impl_channel!(GPIOTE_CH1, GPIOTE, 1, 1); + impl_channel!(GPIOTE_CH2, GPIOTE, 2, 2); + impl_channel!(GPIOTE_CH3, GPIOTE, 3, 3); + } else if #[cfg(all(feature = "_s", any(feature = "_nrf91", feature = "_nrf5340")))] { + impl_channel!(GPIOTE_CH0, GPIOTE0, 0, 0); + impl_channel!(GPIOTE_CH1, GPIOTE0, 1, 1); + impl_channel!(GPIOTE_CH2, GPIOTE0, 2, 2); + impl_channel!(GPIOTE_CH3, GPIOTE0, 3, 3); + impl_channel!(GPIOTE_CH4, GPIOTE0, 4, 4); + impl_channel!(GPIOTE_CH5, GPIOTE0, 5, 5); + impl_channel!(GPIOTE_CH6, GPIOTE0, 6, 6); + impl_channel!(GPIOTE_CH7, GPIOTE0, 7, 7); + } else if #[cfg(all(feature = "_ns", any(feature = "_nrf91", feature = "_nrf5340")))] { + impl_channel!(GPIOTE_CH0, GPIOTE1, 0, 0); + impl_channel!(GPIOTE_CH1, GPIOTE1, 1, 1); + impl_channel!(GPIOTE_CH2, GPIOTE1, 2, 2); + impl_channel!(GPIOTE_CH3, GPIOTE1, 3, 3); + impl_channel!(GPIOTE_CH4, GPIOTE1, 4, 4); + impl_channel!(GPIOTE_CH5, GPIOTE1, 5, 5); + impl_channel!(GPIOTE_CH6, GPIOTE1, 6, 6); + impl_channel!(GPIOTE_CH7, GPIOTE1, 7, 7); + } else { + impl_channel!(GPIOTE_CH0, GPIOTE, 0, 0); + impl_channel!(GPIOTE_CH1, GPIOTE, 1, 1); + impl_channel!(GPIOTE_CH2, GPIOTE, 2, 2); + impl_channel!(GPIOTE_CH3, GPIOTE, 3, 3); + impl_channel!(GPIOTE_CH4, GPIOTE, 4, 4); + impl_channel!(GPIOTE_CH5, GPIOTE, 5, 5); + impl_channel!(GPIOTE_CH6, GPIOTE, 6, 6); + impl_channel!(GPIOTE_CH7, GPIOTE, 7, 7); + } +} // ==================== diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 705c77453..2f7505746 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -76,14 +76,12 @@ pub(crate) mod util; #[cfg(feature = "_time-driver")] mod time_driver; -#[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(not(feature = "_nrf51"))] pub mod buffered_uarte; #[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(not(feature = "_nrf51"))] pub mod egu; pub mod gpio; -#[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(feature = "gpiote")] pub mod gpiote; #[cfg(not(feature = "_nrf54l"))] // TODO @@ -119,9 +117,7 @@ pub mod pdm; #[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(any(feature = "nrf52840", feature = "nrf9160-s", feature = "nrf9160-ns"))] pub mod power; -#[cfg(not(feature = "_nrf54l"))] // TODO pub mod ppi; -#[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(not(any( feature = "_nrf51", feature = "nrf52805", @@ -156,26 +152,19 @@ pub mod reset; #[cfg(not(any(feature = "_nrf5340-app", feature = "_nrf91")))] pub mod rng; pub mod rtc; -#[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(not(any(feature = "_nrf51", feature = "nrf52820", feature = "_nrf5340-net")))] pub mod saadc; -#[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(not(feature = "_nrf51"))] pub mod spim; -#[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(not(feature = "_nrf51"))] pub mod spis; #[cfg(not(any(feature = "_nrf5340-app", feature = "_nrf91")))] pub mod temp; -#[cfg(not(feature = "_nrf54l"))] // TODO pub mod timer; -#[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(not(feature = "_nrf51"))] pub mod twim; -#[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(not(feature = "_nrf51"))] pub mod twis; -#[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(not(feature = "_nrf51"))] pub mod uarte; #[cfg(not(feature = "_nrf54l"))] // TODO @@ -1153,7 +1142,6 @@ pub fn init(config: config::Config) -> Peripherals { } // Init GPIOTE - #[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(feature = "gpiote")] gpiote::init(config.gpiote_interrupt_priority); diff --git a/embassy-nrf/src/ppi/dppi.rs b/embassy-nrf/src/ppi/dppi.rs index 168647be3..d43a25c4e 100644 --- a/embassy-nrf/src/ppi/dppi.rs +++ b/embassy-nrf/src/ppi/dppi.rs @@ -1,11 +1,12 @@ use super::{Channel, ConfigurableChannel, Event, Ppi, Task}; -use crate::{Peri, pac}; +use crate::Peri; const DPPI_ENABLE_BIT: u32 = 0x8000_0000; const DPPI_CHANNEL_MASK: u32 = 0x0000_00FF; -pub(crate) fn regs() -> pac::dppic::Dppic { - pac::DPPIC +#[cfg(not(feature = "_nrf54l"))] +pub(crate) fn regs() -> crate::pac::dppic::Dppic { + crate::pac::DPPIC } impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> { @@ -49,13 +50,13 @@ impl<'d, C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize> Ppi<'d, /// Enables the channel. pub fn enable(&mut self) { let n = self.ch.number(); - regs().chenset().write(|w| w.0 = 1 << n); + self.ch.regs().chenset().write(|w| w.0 = 1 << n); } /// Disables the channel. pub fn disable(&mut self) { let n = self.ch.number(); - regs().chenclr().write(|w| w.0 = 1 << n); + self.ch.regs().chenclr().write(|w| w.0 = 1 << n); } } diff --git a/embassy-nrf/src/ppi/mod.rs b/embassy-nrf/src/ppi/mod.rs index f30c2218d..a880d3188 100644 --- a/embassy-nrf/src/ppi/mod.rs +++ b/embassy-nrf/src/ppi/mod.rs @@ -21,11 +21,13 @@ use core::ptr::NonNull; use embassy_hal_internal::{Peri, PeripheralType, impl_peripheral}; use crate::pac::common::{RW, Reg, W}; -use crate::peripherals; +use crate::pac::{self}; #[cfg_attr(feature = "_dppi", path = "dppi.rs")] #[cfg_attr(feature = "_ppi", path = "ppi.rs")] mod _version; + +#[allow(unused_imports)] pub(crate) use _version::*; /// PPI channel driver. @@ -47,7 +49,7 @@ impl<'d, G: Group> PpiGroup<'d, G> { /// /// The group is initialized as containing no channels. pub fn new(g: Peri<'d, G>) -> Self { - let r = regs(); + let r = g.regs(); let n = g.number(); r.chg(n).write(|_| ()); @@ -61,7 +63,7 @@ impl<'d, G: Group> PpiGroup<'d, G> { &mut self, ch: &Ppi<'_, C, EVENT_COUNT, TASK_COUNT>, ) { - let r = regs(); + let r = self.g.regs(); let ng = self.g.number(); let nc = ch.ch.number(); r.chg(ng).modify(|w| w.set_ch(nc, true)); @@ -74,7 +76,7 @@ impl<'d, G: Group> PpiGroup<'d, G> { &mut self, ch: &Ppi<'_, C, EVENT_COUNT, TASK_COUNT>, ) { - let r = regs(); + let r = self.g.regs(); let ng = self.g.number(); let nc = ch.ch.number(); r.chg(ng).modify(|w| w.set_ch(nc, false)); @@ -83,13 +85,13 @@ impl<'d, G: Group> PpiGroup<'d, G> { /// Enable all the channels in this group. pub fn enable_all(&mut self) { let n = self.g.number(); - regs().tasks_chg(n).en().write_value(1); + self.g.regs().tasks_chg(n).en().write_value(1); } /// Disable all the channels in this group. pub fn disable_all(&mut self) { let n = self.g.number(); - regs().tasks_chg(n).dis().write_value(1); + self.g.regs().tasks_chg(n).dis().write_value(1); } /// Get a reference to the "enable all" task. @@ -97,7 +99,7 @@ impl<'d, G: Group> PpiGroup<'d, G> { /// When triggered, it will enable all the channels in this group. pub fn task_enable_all(&self) -> Task<'d> { let n = self.g.number(); - Task::from_reg(regs().tasks_chg(n).en()) + Task::from_reg(self.g.regs().tasks_chg(n).en()) } /// Get a reference to the "disable all" task. @@ -105,7 +107,7 @@ impl<'d, G: Group> PpiGroup<'d, G> { /// When triggered, it will disable all the channels in this group. pub fn task_disable_all(&self) -> Task<'d> { let n = self.g.number(); - Task::from_reg(regs().tasks_chg(n).dis()) + Task::from_reg(self.g.regs().tasks_chg(n).dis()) } } impl PpiGroup<'static, G> { @@ -119,7 +121,7 @@ impl PpiGroup<'static, G> { impl<'d, G: Group> Drop for PpiGroup<'d, G> { fn drop(&mut self) { - let r = regs(); + let r = self.g.regs(); let n = self.g.number(); r.chg(n).write(|_| ()); } @@ -211,8 +213,16 @@ unsafe impl Send for Event<'_> {} // ====================== // traits -pub(crate) trait SealedChannel {} -pub(crate) trait SealedGroup {} +pub(crate) trait SealedChannel { + #[cfg(feature = "_dppi")] + fn regs(&self) -> pac::dppic::Dppic; +} +pub(crate) trait SealedGroup { + #[cfg(feature = "_dppi")] + fn regs(&self) -> pac::dppic::Dppic; + #[cfg(not(feature = "_dppi"))] + fn regs(&self) -> pac::ppi::Ppi; +} /// Interface for PPI channels. #[allow(private_bounds)] @@ -241,9 +251,16 @@ pub trait Group: SealedGroup + PeripheralType + Into + Sized + 'static /// This can be used to have fewer generic parameters in some places. pub struct AnyStaticChannel { pub(crate) number: u8, + #[cfg(feature = "_dppi")] + pub(crate) regs: pac::dppic::Dppic, } impl_peripheral!(AnyStaticChannel); -impl SealedChannel for AnyStaticChannel {} +impl SealedChannel for AnyStaticChannel { + #[cfg(feature = "_dppi")] + fn regs(&self) -> pac::dppic::Dppic { + self.regs + } +} impl Channel for AnyStaticChannel { fn number(&self) -> usize { self.number as usize @@ -255,9 +272,16 @@ impl StaticChannel for AnyStaticChannel {} /// This can be used to have fewer generic parameters in some places. pub struct AnyConfigurableChannel { pub(crate) number: u8, + #[cfg(feature = "_dppi")] + pub(crate) regs: pac::dppic::Dppic, } impl_peripheral!(AnyConfigurableChannel); -impl SealedChannel for AnyConfigurableChannel {} +impl SealedChannel for AnyConfigurableChannel { + #[cfg(feature = "_dppi")] + fn regs(&self) -> pac::dppic::Dppic { + self.regs + } +} impl Channel for AnyConfigurableChannel { fn number(&self) -> usize { self.number as usize @@ -267,32 +291,41 @@ impl ConfigurableChannel for AnyConfigurableChannel {} #[cfg(not(feature = "_nrf51"))] macro_rules! impl_ppi_channel { - ($type:ident, $number:expr) => { - impl crate::ppi::SealedChannel for peripherals::$type {} + ($type:ident, $inst:ident, $number:expr) => { + impl crate::ppi::SealedChannel for peripherals::$type { + #[cfg(feature = "_dppi")] + fn regs(&self) -> pac::dppic::Dppic { + pac::$inst + } + } impl crate::ppi::Channel for peripherals::$type { fn number(&self) -> usize { $number } } }; - ($type:ident, $number:expr => static) => { - impl_ppi_channel!($type, $number); + ($type:ident, $inst:ident, $number:expr => static) => { + impl_ppi_channel!($type, $inst, $number); impl crate::ppi::StaticChannel for peripherals::$type {} impl From for crate::ppi::AnyStaticChannel { fn from(val: peripherals::$type) -> Self { Self { number: crate::ppi::Channel::number(&val) as u8, + #[cfg(feature = "_dppi")] + regs: pac::$inst, } } } }; - ($type:ident, $number:expr => configurable) => { - impl_ppi_channel!($type, $number); + ($type:ident, $inst:ident, $number:expr => configurable) => { + impl_ppi_channel!($type, $inst, $number); impl crate::ppi::ConfigurableChannel for peripherals::$type {} impl From for crate::ppi::AnyConfigurableChannel { fn from(val: peripherals::$type) -> Self { Self { number: crate::ppi::Channel::number(&val) as u8, + #[cfg(feature = "_dppi")] + regs: pac::$inst, } } } @@ -304,40 +337,54 @@ macro_rules! impl_ppi_channel { /// A type erased PPI group. pub struct AnyGroup { - number: u8, + pub(crate) number: u8, + #[cfg(feature = "_dppi")] + pub(crate) regs: pac::dppic::Dppic, + #[cfg(not(feature = "_dppi"))] + pub(crate) regs: pac::ppi::Ppi, } impl_peripheral!(AnyGroup); -impl SealedGroup for AnyGroup {} +impl SealedGroup for AnyGroup { + #[cfg(feature = "_dppi")] + fn regs(&self) -> pac::dppic::Dppic { + self.regs + } + #[cfg(not(feature = "_dppi"))] + fn regs(&self) -> pac::ppi::Ppi { + self.regs + } +} impl Group for AnyGroup { fn number(&self) -> usize { self.number as usize } } -macro_rules! impl_group { - ($type:ident, $number:expr) => { - impl SealedGroup for peripherals::$type {} - impl Group for peripherals::$type { +macro_rules! impl_ppi_group { + ($type:ident, $inst:ident, $number:expr) => { + impl crate::ppi::SealedGroup for crate::peripherals::$type { + #[cfg(feature = "_dppi")] + fn regs(&self) -> pac::dppic::Dppic { + pac::$inst + } + #[cfg(not(feature = "_dppi"))] + fn regs(&self) -> pac::ppi::Ppi { + pac::$inst + } + } + impl crate::ppi::Group for crate::peripherals::$type { fn number(&self) -> usize { $number } } - impl From for crate::ppi::AnyGroup { - fn from(val: peripherals::$type) -> Self { + impl From for crate::ppi::AnyGroup { + fn from(val: crate::peripherals::$type) -> Self { Self { number: crate::ppi::Group::number(&val) as u8, + regs: pac::$inst, } } } }; } - -impl_group!(PPI_GROUP0, 0); -impl_group!(PPI_GROUP1, 1); -impl_group!(PPI_GROUP2, 2); -impl_group!(PPI_GROUP3, 3); -#[cfg(not(feature = "_nrf51"))] -impl_group!(PPI_GROUP4, 4); -#[cfg(not(feature = "_nrf51"))] -impl_group!(PPI_GROUP5, 5); diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index 00b3278c7..04eb14a77 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -203,7 +203,7 @@ impl<'d> SequencePwm<'d> { /// Interacting with the sequence while it runs puts it in an unknown state #[inline(always)] pub unsafe fn task_start_seq0(&self) -> Task<'d> { - Task::from_reg(self.r.tasks_seqstart(0)) + Task::from_reg(self.r.tasks_dma().seq(0).start()) } /// Returns reference to `Seq1 Started` task endpoint for PPI. @@ -212,7 +212,7 @@ impl<'d> SequencePwm<'d> { /// Interacting with the sequence while it runs puts it in an unknown state #[inline(always)] pub unsafe fn task_start_seq1(&self) -> Task<'d> { - Task::from_reg(self.r.tasks_seqstart(1)) + Task::from_reg(self.r.tasks_dma().seq(1).start()) } /// Returns reference to `NextStep` task endpoint for PPI. @@ -444,6 +444,21 @@ pub struct Sequencer<'d, 's> { sequence1: Option>, } +#[cfg(feature = "_nrf54l")] +fn pwmseq(r: pac::pwm::Pwm, n: usize) -> pac::pwm::PwmSeq { + r.seq(n) +} + +#[cfg(not(feature = "_nrf54l"))] +fn pwmseq(r: pac::pwm::Pwm, n: usize) -> pac::pwm::DmaSeq { + r.dma().seq(n) +} + +#[cfg(feature = "_nrf54l")] +const CNT_UNIT: u32 = 2; +#[cfg(not(feature = "_nrf54l"))] +const CNT_UNIT: u32 = 1; + impl<'d, 's> Sequencer<'d, 's> { /// Create a new double sequence. In the absence of sequence 1, sequence 0 /// will be used twice in the one loop. @@ -476,15 +491,21 @@ impl<'d, 's> Sequencer<'d, 's> { let r = self._pwm.r; - r.seq(0).refresh().write(|w| w.0 = sequence0.config.refresh); - r.seq(0).enddelay().write(|w| w.0 = sequence0.config.end_delay); - r.seq(0).ptr().write_value(sequence0.words.as_ptr() as u32); - r.seq(0).cnt().write(|w| w.0 = sequence0.words.len() as u32); - - r.seq(1).refresh().write(|w| w.0 = alt_sequence.config.refresh); - r.seq(1).enddelay().write(|w| w.0 = alt_sequence.config.end_delay); - r.seq(1).ptr().write_value(alt_sequence.words.as_ptr() as u32); - r.seq(1).cnt().write(|w| w.0 = alt_sequence.words.len() as u32); + pwmseq(r, 0).refresh().write(|w| w.0 = sequence0.config.refresh); + pwmseq(r, 0).enddelay().write(|w| w.0 = sequence0.config.end_delay); + r.dma().seq(0).ptr().write_value(sequence0.words.as_ptr() as u32); + r.dma() + .seq(0) + .maxcnt() + .write(|w| w.0 = sequence0.words.len() as u32 * CNT_UNIT); + + pwmseq(r, 1).refresh().write(|w| w.0 = alt_sequence.config.refresh); + pwmseq(r, 1).enddelay().write(|w| w.0 = alt_sequence.config.end_delay); + r.dma().seq(1).ptr().write_value(alt_sequence.words.as_ptr() as u32); + r.dma() + .seq(1) + .maxcnt() + .write(|w| w.0 = alt_sequence.words.len() as u32 * CNT_UNIT); r.enable().write(|w| w.set_enable(true)); @@ -500,11 +521,11 @@ impl<'d, 's> Sequencer<'d, 's> { // to play infinitely, repeat the sequence one time, then have loops done self trigger seq0 again SequenceMode::Infinite => { r.loop_().write(|w| w.set_cnt(vals::LoopCnt::from_bits(1))); - r.shorts().write(|w| w.set_loopsdone_seqstart0(true)); + r.shorts().write(|w| w.set_loopsdone_dma_seq0_start(true)); } } - r.tasks_seqstart(seqstart_index).write_value(1); + r.tasks_dma().seq(seqstart_index).start().write_value(1); Ok(()) } @@ -781,10 +802,10 @@ impl<'d> SimplePwm<'d> { // Enable r.enable().write(|w| w.set_enable(true)); - r.seq(0).ptr().write_value((pwm.duty).as_ptr() as u32); - r.seq(0).cnt().write(|w| w.0 = 4); - r.seq(0).refresh().write(|w| w.0 = 0); - r.seq(0).enddelay().write(|w| w.0 = 0); + r.dma().seq(0).ptr().write_value((pwm.duty).as_ptr() as u32); + r.dma().seq(0).maxcnt().write(|w| w.0 = 4 * CNT_UNIT); + pwmseq(r, 0).refresh().write(|w| w.0 = 0); + pwmseq(r, 0).enddelay().write(|w| w.0 = 0); r.decoder().write(|w| { w.set_load(vals::Load::INDIVIDUAL); @@ -846,7 +867,7 @@ impl<'d> SimplePwm<'d> { /// Transfer the duty cycles from `self` to the peripheral. fn sync_duty_cyles_to_peripheral(&self) { // reload ptr in case self was moved - self.r.seq(0).ptr().write_value((self.duty).as_ptr() as u32); + self.r.dma().seq(0).ptr().write_value((self.duty).as_ptr() as u32); // defensive before seqstart compiler_fence(Ordering::SeqCst); @@ -854,7 +875,7 @@ impl<'d> SimplePwm<'d> { self.r.events_seqend(0).write_value(0); // tasks_seqstart() doesn't exist in all svds so write its bit instead - self.r.tasks_seqstart(0).write_value(1); + self.r.tasks_dma().seq(0).start().write_value(1); // defensive wait until waveform is loaded after seqstart so set_duty // can't be called again while dma is still reading diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs index a199c1c4d..ca8cbd73e 100644 --- a/embassy-nrf/src/saadc.rs +++ b/embassy-nrf/src/saadc.rs @@ -10,6 +10,7 @@ use core::task::Poll; use embassy_hal_internal::drop::OnDrop; use embassy_hal_internal::{Peri, impl_peripheral}; use embassy_sync::waitqueue::AtomicWaker; +#[cfg(not(feature = "_nrf54l"))] pub(crate) use vals::Psel as InputChannel; use crate::interrupt::InterruptExt; @@ -84,6 +85,7 @@ pub struct ChannelConfig<'d> { /// Gain used to control the effective input range of the SAADC. pub gain: Gain, /// Positive channel resistor control. + #[cfg(not(feature = "_nrf54l"))] pub resistor: Resistor, /// Acquisition time in microseconds. pub time: Time, @@ -98,7 +100,11 @@ impl<'d> ChannelConfig<'d> { pub fn single_ended(input: impl Input + 'd) -> Self { Self { reference: Reference::INTERNAL, + #[cfg(not(feature = "_nrf54l"))] gain: Gain::GAIN1_6, + #[cfg(feature = "_nrf54l")] + gain: Gain::GAIN2_8, + #[cfg(not(feature = "_nrf54l"))] resistor: Resistor::BYPASS, time: Time::_10US, p_channel: input.degrade_saadc(), @@ -109,7 +115,11 @@ impl<'d> ChannelConfig<'d> { pub fn differential(p_input: impl Input + 'd, n_input: impl Input + 'd) -> Self { Self { reference: Reference::INTERNAL, + #[cfg(not(feature = "_nrf54l"))] gain: Gain::GAIN1_6, + #[cfg(feature = "_nrf54l")] + gain: Gain::GAIN2_8, + #[cfg(not(feature = "_nrf54l"))] resistor: Resistor::BYPASS, time: Time::_10US, p_channel: p_input.degrade_saadc(), @@ -118,6 +128,8 @@ impl<'d> ChannelConfig<'d> { } } +const CNT_UNIT: usize = if cfg!(feature = "_nrf54l") { 2 } else { 1 }; + /// Value returned by the SAADC callback, deciding what happens next. #[derive(PartialEq)] pub enum CallbackResult { @@ -150,19 +162,38 @@ impl<'d, const N: usize> Saadc<'d, N> { r.oversample().write(|w| w.set_oversample(oversample.into())); for (i, cc) in channel_configs.iter().enumerate() { + #[cfg(not(feature = "_nrf54l"))] r.ch(i).pselp().write(|w| w.set_pselp(cc.p_channel.channel())); + #[cfg(feature = "_nrf54l")] + r.ch(i).pselp().write(|w| { + w.set_port(cc.p_channel.port()); + w.set_pin(cc.p_channel.pin()); + w.set_internal(cc.p_channel.internal()); + w.set_connect(cc.p_channel.connect()); + }); if let Some(n_channel) = &cc.n_channel { + #[cfg(not(feature = "_nrf54l"))] r.ch(i).pseln().write(|w| w.set_pseln(n_channel.channel())); + #[cfg(feature = "_nrf54l")] + r.ch(i).pseln().write(|w| { + w.set_port(n_channel.port()); + w.set_pin(n_channel.pin()); + w.set_connect(n_channel.connect().to_bits().into()); + }); } r.ch(i).config().write(|w| { w.set_refsel(cc.reference.into()); w.set_gain(cc.gain.into()); w.set_tacq(cc.time.into()); + #[cfg(feature = "_nrf54l")] + w.set_tconv(7); // 7 is the default from the Nordic C driver w.set_mode(match cc.n_channel { None => vals::ConfigMode::SE, Some(_) => vals::ConfigMode::DIFF, }); + #[cfg(not(feature = "_nrf54l"))] w.set_resp(cc.resistor.into()); + #[cfg(not(feature = "_nrf54l"))] w.set_resn(vals::Resn::BYPASS); w.set_burst(!matches!(oversample, Oversample::BYPASS)); }); @@ -222,7 +253,7 @@ impl<'d, const N: usize> Saadc<'d, N> { // Set up the DMA r.result().ptr().write_value(buf.as_mut_ptr() as u32); - r.result().maxcnt().write(|w| w.set_maxcnt(N as _)); + r.result().maxcnt().write(|w| w.set_maxcnt((N * CNT_UNIT) as _)); // Reset and enable the end event r.events_end().write_value(0); @@ -354,7 +385,7 @@ impl<'d, const N: usize> Saadc<'d, N> { // Set up the initial DMA r.result().ptr().write_value(bufs[0].as_mut_ptr() as u32); - r.result().maxcnt().write(|w| w.set_maxcnt((N0 * N) as _)); + r.result().maxcnt().write(|w| w.set_maxcnt((N0 * N * CNT_UNIT) as _)); // Reset and enable the events r.events_end().write_value(0); @@ -473,12 +504,21 @@ impl<'d, const N: usize> Drop for Saadc<'d, N> { let r = Self::regs(); r.enable().write(|w| w.set_enable(false)); for i in 0..N { - r.ch(i).pselp().write(|w| w.set_pselp(InputChannel::NC)); - r.ch(i).pseln().write(|w| w.set_pseln(InputChannel::NC)); + #[cfg(not(feature = "_nrf54l"))] + { + r.ch(i).pselp().write(|w| w.set_pselp(InputChannel::NC)); + r.ch(i).pseln().write(|w| w.set_pseln(InputChannel::NC)); + } + #[cfg(feature = "_nrf54l")] + { + r.ch(i).pselp().write(|w| w.set_connect(vals::PselpConnect::NC)); + r.ch(i).pseln().write(|w| w.set_connect(vals::PselnConnect::NC)); + } } } } +#[cfg(not(feature = "_nrf54l"))] impl From for vals::Gain { fn from(gain: Gain) -> Self { match gain { @@ -494,7 +534,24 @@ impl From for vals::Gain { } } +#[cfg(feature = "_nrf54l")] +impl From for vals::Gain { + fn from(gain: Gain) -> Self { + match gain { + Gain::GAIN2_8 => vals::Gain::GAIN2_8, + Gain::GAIN2_7 => vals::Gain::GAIN2_7, + Gain::GAIN2_6 => vals::Gain::GAIN2_6, + Gain::GAIN2_5 => vals::Gain::GAIN2_5, + Gain::GAIN2_4 => vals::Gain::GAIN2_4, + Gain::GAIN2_3 => vals::Gain::GAIN2_3, + Gain::GAIN1 => vals::Gain::GAIN1, + Gain::GAIN2 => vals::Gain::GAIN2, + } + } +} + /// Gain control +#[cfg(not(feature = "_nrf54l"))] #[non_exhaustive] #[derive(Clone, Copy)] pub enum Gain { @@ -516,11 +573,37 @@ pub enum Gain { GAIN4 = 7, } +/// Gain control +#[cfg(feature = "_nrf54l")] +#[non_exhaustive] +#[derive(Clone, Copy)] +pub enum Gain { + /// 2/8 + GAIN2_8 = 0, + /// 2/7 + GAIN2_7 = 1, + /// 2/6 + GAIN2_6 = 2, + /// 2/5 + GAIN2_5 = 3, + /// 2/4 + GAIN2_4 = 4, + /// 2/3 + GAIN2_3 = 5, + /// 1 + GAIN1 = 6, + /// 2 + GAIN2 = 7, +} + impl From for vals::Refsel { fn from(reference: Reference) -> Self { match reference { Reference::INTERNAL => vals::Refsel::INTERNAL, + #[cfg(not(feature = "_nrf54l"))] Reference::VDD1_4 => vals::Refsel::VDD1_4, + #[cfg(feature = "_nrf54l")] + Reference::EXTERNAL => vals::Refsel::EXTERNAL, } } } @@ -531,10 +614,15 @@ impl From for vals::Refsel { pub enum Reference { /// Internal reference (0.6 V) INTERNAL = 0, + #[cfg(not(feature = "_nrf54l"))] /// VDD/4 as reference VDD1_4 = 1, + /// PADC_EXT_REF_1V2 as reference + #[cfg(feature = "_nrf54l")] + EXTERNAL = 1, } +#[cfg(not(feature = "_nrf54l"))] impl From for vals::Resp { fn from(resistor: Resistor) -> Self { match resistor { @@ -549,6 +637,7 @@ impl From for vals::Resp { /// Positive channel resistor control #[non_exhaustive] #[derive(Clone, Copy)] +#[cfg(not(feature = "_nrf54l"))] pub enum Resistor { /// Bypass resistor ladder BYPASS = 0, @@ -560,6 +649,7 @@ pub enum Resistor { VDD1_2 = 3, } +#[cfg(not(feature = "_nrf54l"))] impl From