From bf01151bbb793c36431820bd814db775bf10a870 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sun, 27 Jul 2025 08:27:09 +0200 Subject: stm32/i2c_v1: Remove redundant timing config abstractions for DutyCycle and I2C mode --- embassy-stm32/src/i2c/v1.rs | 79 ++++++++++++++------------------------------- 1 file changed, 24 insertions(+), 55 deletions(-) diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index 081eb1191..a1ad9caef 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -14,7 +14,7 @@ use embedded_hal_1::i2c::Operation; use mode::Master; use super::*; -use crate::mode::Mode as PeriMode; +use crate::mode::Mode; use crate::pac::i2c; // /!\ /!\ @@ -42,7 +42,7 @@ pub unsafe fn on_interrupt() { }); } -impl<'d, M: PeriMode, IM: MasterMode> I2c<'d, M, IM> { +impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { pub(crate) fn init(&mut self, config: Config) { self.info.regs.cr1().modify(|reg| { reg.set_pe(false); @@ -81,8 +81,8 @@ impl<'d, M: PeriMode, IM: MasterMode> I2c<'d, M, IM> { reg.set_freq(timings.freq); }); self.info.regs.ccr().modify(|reg| { - reg.set_f_s(timings.mode.f_s()); - reg.set_duty(timings.duty.duty()); + reg.set_f_s(timings.f_s); + reg.set_duty(timings.duty); reg.set_ccr(timings.ccr); }); self.info.regs.trise().modify(|reg| { @@ -701,40 +701,18 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { } } -enum Mode { - Fast, - Standard, -} - -impl Mode { - fn f_s(&self) -> i2c::vals::FS { - match self { - Mode::Fast => i2c::vals::FS::FAST, - Mode::Standard => i2c::vals::FS::STANDARD, - } - } -} - -enum Duty { - Duty2_1, - Duty16_9, -} - -impl Duty { - fn duty(&self) -> i2c::vals::Duty { - match self { - Duty::Duty2_1 => i2c::vals::Duty::DUTY2_1, - Duty::Duty16_9 => i2c::vals::Duty::DUTY16_9, - } - } -} +/// Timing configuration for I2C v1 hardware +/// +/// This struct encapsulates the complex timing calculations required for STM32 I2C v1 +/// peripherals, which use three separate registers (CR2.FREQ, CCR, TRISE) instead of +/// the unified TIMINGR register found in v2 hardware. struct Timings { - freq: u8, - mode: Mode, - trise: u8, - ccr: u16, - duty: Duty, + freq: u8, // APB frequency in MHz for CR2.FREQ register + f_s: i2c::vals::FS, // Standard or Fast mode selection + trise: u8, // Rise time compensation value + ccr: u16, // Clock control register value + duty: i2c::vals::Duty, // Fast mode duty cycle selection } impl Timings { @@ -754,12 +732,12 @@ impl Timings { let mut ccr; let duty; - let mode; + let f_s; // I2C clock control calculation if frequency <= 100_000 { - duty = Duty::Duty2_1; - mode = Mode::Standard; + duty = i2c::vals::Duty::DUTY2_1; + f_s = i2c::vals::FS::STANDARD; ccr = { let ccr = clock / (frequency * 2); if ccr < 4 { @@ -770,38 +748,29 @@ impl Timings { }; } else { const DUTYCYCLE: u8 = 0; - mode = Mode::Fast; + f_s = i2c::vals::FS::FAST; if DUTYCYCLE == 0 { - duty = Duty::Duty2_1; + duty = i2c::vals::Duty::DUTY2_1; ccr = clock / (frequency * 3); ccr = if ccr < 1 { 1 } else { ccr }; - - // Set clock to fast mode with appropriate parameters for selected speed (2:1 duty cycle) } else { - duty = Duty::Duty16_9; + duty = i2c::vals::Duty::DUTY16_9; ccr = clock / (frequency * 25); ccr = if ccr < 1 { 1 } else { ccr }; - - // Set clock to fast mode with appropriate parameters for selected speed (16:9 duty cycle) } } Self { freq: freq as u8, + f_s, trise: trise as u8, ccr: ccr as u16, duty, - mode, - //prescale: presc_reg, - //scll, - //sclh, - //sdadel, - //scldel, } } } -impl<'d, M: PeriMode> SetConfig for I2c<'d, M, Master> { +impl<'d, M: Mode> SetConfig for I2c<'d, M, Master> { type Config = Hertz; type ConfigError = (); fn set_config(&mut self, config: &Self::Config) -> Result<(), ()> { @@ -810,8 +779,8 @@ impl<'d, M: PeriMode> SetConfig for I2c<'d, M, Master> { reg.set_freq(timings.freq); }); self.info.regs.ccr().modify(|reg| { - reg.set_f_s(timings.mode.f_s()); - reg.set_duty(timings.duty.duty()); + reg.set_f_s(timings.f_s); + reg.set_duty(timings.duty); reg.set_ccr(timings.ccr); }); self.info.regs.trise().modify(|reg| { -- cgit From 399ec8d90f1c4f62fd20d21dabbb9ce64c6986eb Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sun, 27 Jul 2025 10:35:52 +0200 Subject: stm32/i2c: Rename FrameOptions enum to OperationFraming --- embassy-stm32/src/i2c/mod.rs | 185 +++++++++++++++++++++++++------------------ embassy-stm32/src/i2c/v1.rs | 28 +++---- 2 files changed, 124 insertions(+), 89 deletions(-) diff --git a/embassy-stm32/src/i2c/mod.rs b/embassy-stm32/src/i2c/mod.rs index 5fb49f943..f51682900 100644 --- a/embassy-stm32/src/i2c/mod.rs +++ b/embassy-stm32/src/i2c/mod.rs @@ -435,88 +435,117 @@ impl<'d, IM: MasterMode> embedded_hal_async::i2c::I2c for I2c<'d, Async, IM> { } } -/// Frame type in I2C transaction. +/// Operation framing configuration for I2C transactions. /// -/// This tells each method what kind of framing to use, to generate a (repeated) start condition (ST -/// or SR), and/or a stop condition (SP). For read operations, this also controls whether to send an -/// ACK or NACK after the last byte received. +/// This determines the I2C frame boundaries for each operation within a transaction, +/// controlling the generation of start conditions (ST or SR), stop conditions (SP), +/// and ACK/NACK behavior for read operations. /// -/// For write operations, the following options are identical because they differ only in the (N)ACK -/// treatment relevant for read operations: +/// For write operations, some framing configurations are functionally identical +/// because they differ only in ACK/NACK treatment which is relevant only for reads: /// -/// - `FirstFrame` and `FirstAndNextFrame` -/// - `NextFrame` and `LastFrameNoStop` -/// -/// Abbreviations used below: +/// - `First` and `FirstAndNext` behave identically for writes +/// - `Next` and `LastNoStop` behave identically for writes /// +/// **Framing Legend:** /// - `ST` = start condition -/// - `SR` = repeated start condition +/// - `SR` = repeated start condition /// - `SP` = stop condition -/// - `ACK`/`NACK` = last byte in read operation +/// - `ACK/NACK` = acknowledgment behavior for the final byte of read operations #[derive(Copy, Clone)] #[allow(dead_code)] -enum FrameOptions { - /// `[ST/SR]+[NACK]+[SP]` First frame (of this type) in transaction and also last frame overall. - FirstAndLastFrame, - /// `[ST/SR]+[NACK]` First frame of this type in transaction, last frame in a read operation but - /// not the last frame overall. - FirstFrame, - /// `[ST/SR]+[ACK]` First frame of this type in transaction, neither last frame overall nor last - /// frame in a read operation. - FirstAndNextFrame, - /// `[ACK]` Middle frame in a read operation (neither first nor last). - NextFrame, - /// `[NACK]+[SP]` Last frame overall in this transaction but not the first frame. - LastFrame, - /// `[NACK]` Last frame in a read operation but not last frame overall in this transaction. - LastFrameNoStop, +enum OperationFraming { + /// `[ST/SR]+[NACK]+[SP]` - First operation of its type in the transaction and also the final operation overall. + FirstAndLast, + /// `[ST/SR]+[NACK]` - First operation of its type in the transaction, final operation in a read sequence, but not the final operation overall. + First, + /// `[ST/SR]+[ACK]` - First operation of its type in the transaction, but neither the final operation overall nor the final operation in a read sequence. + FirstAndNext, + /// `[ACK]` - Continuation operation in a read sequence (neither first nor last). + Next, + /// `[NACK]+[SP]` - Final operation overall in the transaction, but not the first operation of its type. + Last, + /// `[NACK]` - Final operation in a read sequence, but not the final operation overall in the transaction. + LastNoStop, } #[allow(dead_code)] -impl FrameOptions { - /// Sends start or repeated start condition before transfer. +impl OperationFraming { + /// Returns true if a start or repeated start condition should be generated before this operation. fn send_start(self) -> bool { match self { - Self::FirstAndLastFrame | Self::FirstFrame | Self::FirstAndNextFrame => true, - Self::NextFrame | Self::LastFrame | Self::LastFrameNoStop => false, + Self::FirstAndLast | Self::First | Self::FirstAndNext => true, + Self::Next | Self::Last | Self::LastNoStop => false, } } - /// Sends stop condition after transfer. + /// Returns true if a stop condition should be generated after this operation. fn send_stop(self) -> bool { match self { - Self::FirstAndLastFrame | Self::LastFrame => true, - Self::FirstFrame | Self::FirstAndNextFrame | Self::NextFrame | Self::LastFrameNoStop => false, + Self::FirstAndLast | Self::Last => true, + Self::First | Self::FirstAndNext | Self::Next | Self::LastNoStop => false, } } - /// Sends NACK after last byte received, indicating end of read operation. + /// Returns true if NACK should be sent after the last byte received in a read operation. + /// + /// This signals the end of a read sequence and releases the bus for the master's + /// next transmission (or stop condition). fn send_nack(self) -> bool { match self { - Self::FirstAndLastFrame | Self::FirstFrame | Self::LastFrame | Self::LastFrameNoStop => true, - Self::FirstAndNextFrame | Self::NextFrame => false, + Self::FirstAndLast | Self::First | Self::Last | Self::LastNoStop => true, + Self::FirstAndNext | Self::Next => false, } } } -/// Iterates over operations in transaction. +/// Analyzes I2C transaction operations and assigns appropriate framing to each. +/// +/// This function processes a sequence of I2C operations and determines the correct +/// framing configuration for each operation to ensure proper I2C protocol compliance. +/// It handles the complex logic of: +/// +/// - Generating start conditions for the first operation of each type (read/write) +/// - Generating stop conditions for the final operation in the entire transaction +/// - Managing ACK/NACK behavior for read operations, including merging consecutive reads +/// - Ensuring proper bus handoff between different operation types +/// +/// **Transaction Contract Compliance:** +/// The framing assignments ensure compliance with the embedded-hal I2C transaction contract, +/// where consecutive operations of the same type are logically merged while maintaining +/// proper protocol boundaries. /// -/// Returns necessary frame options for each operation to uphold the [transaction contract] and have -/// the right start/stop/(N)ACK conditions on the wire. +/// **Error Handling:** +/// Returns an error if any read operation has an empty buffer, as this would create +/// an invalid I2C transaction that could halt mid-execution. /// -/// [transaction contract]: embedded_hal_1::i2c::I2c::transaction +/// # Arguments +/// * `operations` - Mutable slice of I2C operations from embedded-hal +/// +/// # Returns +/// An iterator over (operation, framing) pairs, or an error if the transaction is invalid +/// +/// # Example +/// ```rust +/// for (op, framing) in assign_operation_framing(operations)? { +/// match op { +/// Operation::Read(buffer) => self.read_with_framing(addr, buffer, framing).await?, +/// Operation::Write(data) => self.write_with_framing(addr, data, framing).await?, +/// } +/// } +/// ``` #[allow(dead_code)] -fn operation_frames<'a, 'b: 'a>( +fn assign_operation_framing<'a, 'b: 'a>( operations: &'a mut [embedded_hal_1::i2c::Operation<'b>], -) -> Result, FrameOptions)>, Error> { +) -> Result, OperationFraming)>, Error> { use embedded_hal_1::i2c::Operation::{Read, Write}; - // Check empty read buffer before starting transaction. Otherwise, we would risk halting with an - // error in the middle of the transaction. + // Validate that no read operations have empty buffers before starting the transaction. + // Empty read operations would risk halting with an error mid-transaction. // - // In principle, we could allow empty read frames within consecutive read operations, as long as - // at least one byte remains in the final (merged) read operation, but that makes the logic more - // complicated and error-prone. + // Note: We could theoretically allow empty read operations within consecutive read + // sequences as long as the final merged read has at least one byte, but this would + // complicate the logic significantly and create error-prone edge cases. if operations.iter().any(|op| match op { Read(read) => read.is_empty(), Write(_) => false, @@ -525,46 +554,52 @@ fn operation_frames<'a, 'b: 'a>( } let mut operations = operations.iter_mut().peekable(); - - let mut next_first_frame = true; + let mut next_first_operation = true; Ok(iter::from_fn(move || { - let op = operations.next()?; + let current_op = operations.next()?; - // Is `op` first frame of its type? - let first_frame = next_first_frame; + // Determine if this is the first operation of its type (read or write) + let is_first_of_type = next_first_operation; let next_op = operations.peek(); - // Get appropriate frame options as combination of the following properties: + // Compute the appropriate framing based on three key properties: // - // - For each first operation of its type, generate a (repeated) start condition. - // - For the last operation overall in the entire transaction, generate a stop condition. - // - For read operations, check the next operation: if it is also a read operation, we merge - // these and send ACK for all bytes in the current operation; send NACK only for the final - // read operation's last byte (before write or end of entire transaction) to indicate last - // byte read and release the bus for transmission of the bus master's next byte (or stop). + // 1. **Start Condition**: Generate (repeated) start for first operation of each type + // 2. **Stop Condition**: Generate stop for the final operation in the entire transaction + // 3. **ACK/NACK for Reads**: For read operations, send ACK if more reads follow in the + // sequence, or NACK for the final read in a sequence (before write or transaction end) // - // We check the third property unconditionally, i.e. even for write opeartions. This is okay - // because the resulting frame options are identical for write operations. - let frame = match (first_frame, next_op) { - (true, None) => FrameOptions::FirstAndLastFrame, - (true, Some(Read(_))) => FrameOptions::FirstAndNextFrame, - (true, Some(Write(_))) => FrameOptions::FirstFrame, - // - (false, None) => FrameOptions::LastFrame, - (false, Some(Read(_))) => FrameOptions::NextFrame, - (false, Some(Write(_))) => FrameOptions::LastFrameNoStop, + // The third property is checked for all operations since the resulting framing + // configurations are identical for write operations regardless of ACK/NACK treatment. + let framing = match (is_first_of_type, next_op) { + // First operation of type, and it's also the final operation overall + (true, None) => OperationFraming::FirstAndLast, + // First operation of type, next operation is also a read (continue read sequence) + (true, Some(Read(_))) => OperationFraming::FirstAndNext, + // First operation of type, next operation is write (end current sequence) + (true, Some(Write(_))) => OperationFraming::First, + + // Continuation operation, and it's the final operation overall + (false, None) => OperationFraming::Last, + // Continuation operation, next operation is also a read (continue read sequence) + (false, Some(Read(_))) => OperationFraming::Next, + // Continuation operation, next operation is write (end current sequence, no stop) + (false, Some(Write(_))) => OperationFraming::LastNoStop, }; - // Pre-calculate if `next_op` is the first operation of its type. We do this here and not at - // the beginning of the loop because we hand out `op` as iterator value and cannot access it - // anymore in the next iteration. - next_first_frame = match (&op, next_op) { + // Pre-calculate whether the next operation will be the first of its type. + // This is done here because we consume `current_op` as the iterator value + // and cannot access it in the next iteration. + next_first_operation = match (¤t_op, next_op) { + // No next operation (_, None) => false, + // Operation type changes: next will be first of its type (Read(_), Some(Write(_))) | (Write(_), Some(Read(_))) => true, + // Operation type continues: next will not be first of its type (Read(_), Some(Read(_))) | (Write(_), Some(Write(_))) => false, }; - Some((op, frame)) + Some((current_op, framing)) })) } diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index a1ad9caef..7d2b731d5 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -151,7 +151,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { Ok(sr1) } - fn write_bytes(&mut self, addr: u8, bytes: &[u8], timeout: Timeout, frame: FrameOptions) -> Result<(), Error> { + fn write_bytes(&mut self, addr: u8, bytes: &[u8], timeout: Timeout, frame: OperationFraming) -> Result<(), Error> { if frame.send_start() { // Send a START condition @@ -239,7 +239,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { addr: u8, buffer: &mut [u8], timeout: Timeout, - frame: FrameOptions, + frame: OperationFraming, ) -> Result<(), Error> { let Some((last, buffer)) = buffer.split_last_mut() else { return Err(Error::Overrun); @@ -299,12 +299,12 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { /// Blocking read. pub fn blocking_read(&mut self, addr: u8, read: &mut [u8]) -> Result<(), Error> { - self.blocking_read_timeout(addr, read, self.timeout(), FrameOptions::FirstAndLastFrame) + self.blocking_read_timeout(addr, read, self.timeout(), OperationFraming::FirstAndLast) } /// Blocking write. pub fn blocking_write(&mut self, addr: u8, write: &[u8]) -> Result<(), Error> { - self.write_bytes(addr, write, self.timeout(), FrameOptions::FirstAndLastFrame)?; + self.write_bytes(addr, write, self.timeout(), OperationFraming::FirstAndLast)?; // Fallthrough is success Ok(()) @@ -320,8 +320,8 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { let timeout = self.timeout(); - self.write_bytes(addr, write, timeout, FrameOptions::FirstFrame)?; - self.blocking_read_timeout(addr, read, timeout, FrameOptions::FirstAndLastFrame)?; + self.write_bytes(addr, write, timeout, OperationFraming::First)?; + self.blocking_read_timeout(addr, read, timeout, OperationFraming::FirstAndLast)?; Ok(()) } @@ -334,7 +334,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { pub fn blocking_transaction(&mut self, addr: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> { let timeout = self.timeout(); - for (op, frame) in operation_frames(operations)? { + for (op, frame) in assign_operation_framing(operations)? { match op { Operation::Read(read) => self.blocking_read_timeout(addr, read, timeout, frame)?, Operation::Write(write) => self.write_bytes(addr, write, timeout, frame)?, @@ -356,7 +356,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { } impl<'d, IM: MasterMode> I2c<'d, Async, IM> { - async fn write_frame(&mut self, address: u8, write: &[u8], frame: FrameOptions) -> Result<(), Error> { + async fn write_frame(&mut self, address: u8, write: &[u8], frame: OperationFraming) -> Result<(), Error> { self.info.regs.cr2().modify(|w| { // Note: Do not enable the ITBUFEN bit in the I2C_CR2 register if DMA is used for // reception. @@ -502,7 +502,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { /// Write. pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> { - self.write_frame(address, write, FrameOptions::FirstAndLastFrame) + self.write_frame(address, write, OperationFraming::FirstAndLast) .await?; Ok(()) @@ -510,13 +510,13 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { /// Read. pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { - self.read_frame(address, buffer, FrameOptions::FirstAndLastFrame) + self.read_frame(address, buffer, OperationFraming::FirstAndLast) .await?; Ok(()) } - async fn read_frame(&mut self, address: u8, buffer: &mut [u8], frame: FrameOptions) -> Result<(), Error> { + async fn read_frame(&mut self, address: u8, buffer: &mut [u8], frame: OperationFraming) -> Result<(), Error> { if buffer.is_empty() { return Err(Error::Overrun); } @@ -680,8 +680,8 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { return Err(Error::Overrun); } - self.write_frame(address, write, FrameOptions::FirstFrame).await?; - self.read_frame(address, read, FrameOptions::FirstAndLastFrame).await + self.write_frame(address, write, OperationFraming::First).await?; + self.read_frame(address, read, OperationFraming::FirstAndLast).await } /// Transaction with operations. @@ -690,7 +690,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { /// /// [transaction contract]: embedded_hal_1::i2c::I2c::transaction pub async fn transaction(&mut self, addr: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> { - for (op, frame) in operation_frames(operations)? { + for (op, frame) in assign_operation_framing(operations)? { match op { Operation::Read(read) => self.read_frame(addr, read, frame).await?, Operation::Write(write) => self.write_frame(addr, write, frame).await?, -- cgit From b690a0314f0f2e42137ad4b3e867e056c1d3c14e Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sun, 27 Jul 2025 10:53:48 +0200 Subject: stm32/i2c: v1 and v2 now has separate definitions for the State struct --- embassy-stm32/src/i2c/mod.rs | 17 +++-------------- embassy-stm32/src/i2c/v1.rs | 15 +++++++++++++++ embassy-stm32/src/i2c/v2.rs | 15 +++++++++++++++ 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/embassy-stm32/src/i2c/mod.rs b/embassy-stm32/src/i2c/mod.rs index f51682900..58225d937 100644 --- a/embassy-stm32/src/i2c/mod.rs +++ b/embassy-stm32/src/i2c/mod.rs @@ -5,6 +5,9 @@ #[cfg_attr(any(i2c_v2, i2c_v3), path = "v2.rs")] mod _version; +// Type alias for the peri_trait! macro +type State = _version::State; + mod config; use core::future::Future; @@ -13,7 +16,6 @@ use core::marker::PhantomData; pub use config::*; use embassy_hal_internal::Peri; -use embassy_sync::waitqueue::AtomicWaker; #[cfg(feature = "time")] use embassy_time::{Duration, Instant}; use mode::MasterMode; @@ -274,19 +276,6 @@ impl Timeout { } } -struct State { - #[allow(unused)] - waker: AtomicWaker, -} - -impl State { - const fn new() -> Self { - Self { - waker: AtomicWaker::new(), - } - } -} - struct Info { regs: crate::pac::i2c::I2c, rcc: RccInfo, diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index 7d2b731d5..78abb85ea 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -17,6 +17,21 @@ use super::*; use crate::mode::Mode; use crate::pac::i2c; +use embassy_sync::waitqueue::AtomicWaker; + +/// I2C v2 peripheral state +pub(crate) struct State { + pub(crate) waker: AtomicWaker, +} + +impl State { + pub(crate) const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + } + } +} + // /!\ /!\ // /!\ Implementation note! /!\ // /!\ /!\ diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index 3b09f1b34..72a7d05ab 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -12,6 +12,21 @@ use stm32_metapac::i2c::vals::{Addmode, Oamsk}; use super::*; use crate::pac::i2c; +use embassy_sync::waitqueue::AtomicWaker; + +/// I2C v2 peripheral state +pub(crate) struct State { + pub(crate) waker: AtomicWaker, +} + +impl State { + pub(crate) const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + } + } +} + impl From for Oamsk { fn from(value: AddrMask) -> Self { match value { -- cgit From 593fb963b84741bb05d1cae1dbacafb92b4828c6 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sun, 27 Jul 2025 12:25:03 +0200 Subject: stm32/i2c: Add temporary trait for version-specific initialization during v1 rework --- embassy-stm32/src/i2c/mod.rs | 22 +++++++++++++++++++++- embassy-stm32/src/i2c/v1.rs | 38 ++++++++++++++++++++++++++++++-------- embassy-stm32/src/i2c/v2.rs | 4 ++++ 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/embassy-stm32/src/i2c/mod.rs b/embassy-stm32/src/i2c/mod.rs index 58225d937..660b8144a 100644 --- a/embassy-stm32/src/i2c/mod.rs +++ b/embassy-stm32/src/i2c/mod.rs @@ -29,6 +29,21 @@ use crate::rcc::{RccInfo, SealedRccPeripheral}; use crate::time::Hertz; use crate::{interrupt, peripherals}; +/// Temporary trait for version-specific initialization during I2C v1 async rework. +/// +/// This trait allows the shared constructor in mod.rs to call version-specific +/// initialization while we incrementally migrate v1 async operations to use +/// the new event-driven interrupt pattern. Will be removed once the rework +/// is complete and both blocking/async modes use unified initialization. +pub trait VersionSpecificInit { + /// Performs version and mode-specific initialization. + /// + /// For v1: Sets interrupt pattern flag based on blocking vs async mode. + /// For v2: No-op, does nothing. + fn version_specific_init(&mut self); +} + + /// I2C error. #[derive(Debug, PartialEq, Eq, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -192,7 +207,10 @@ impl<'d> I2c<'d, Blocking, Master> { } } -impl<'d, M: Mode> I2c<'d, M, Master> { +impl<'d, M: Mode> I2c<'d, M, Master> +where + Self: VersionSpecificInit +{ /// Create a new I2C driver. fn new_inner( _peri: Peri<'d, T>, @@ -221,7 +239,9 @@ impl<'d, M: Mode> I2c<'d, M, Master> { sda, }, }; + this.enable_and_init(config); + this.version_specific_init(); this } diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index 78abb85ea..eaf787334 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -6,6 +6,7 @@ use core::future::poll_fn; use core::task::Poll; +use core::sync::atomic::{AtomicBool, Ordering}; use embassy_embedded_hal::SetConfig; use embassy_futures::select::{select, Either}; @@ -22,12 +23,14 @@ use embassy_sync::waitqueue::AtomicWaker; /// I2C v2 peripheral state pub(crate) struct State { pub(crate) waker: AtomicWaker, + pub use_new_interrupt_pattern: AtomicBool, } impl State { pub(crate) const fn new() -> Self { Self { waker: AtomicWaker::new(), + use_new_interrupt_pattern: AtomicBool::new(false), } } } @@ -44,17 +47,25 @@ impl State { // There's some more details there, and we might have a fix for you. But please let us know if you // hit a case like this! pub unsafe fn on_interrupt() { - let regs = T::info().regs; // i2c v2 only woke the task on transfer complete interrupts. v1 uses interrupts for a bunch of // other stuff, so we wake the task on every interrupt. - T::state().waker.wake(); - critical_section::with(|_| { - // Clear event interrupt flag. - regs.cr2().modify(|w| { - w.set_itevten(false); - w.set_iterren(false); + + let regs = T::info().regs; + let state = T::state(); + + if state.use_new_interrupt_pattern.load(Ordering::Relaxed) { + + } else { + critical_section::with(|_| { + // Clear event interrupt flag. + regs.cr2().modify(|w| { + w.set_itevten(false); + w.set_iterren(false); + }); }); - }); + } + + state.waker.wake(); } impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { @@ -716,6 +727,17 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { } } +impl<'d> VersionSpecificInit for I2c<'d, Blocking, Master> { + fn version_specific_init(&mut self) { + self.state.use_new_interrupt_pattern.store(false, Ordering::Relaxed); + } +} + +impl<'d> VersionSpecificInit for I2c<'d, Async, Master> { + fn version_specific_init(&mut self) { + self.state.use_new_interrupt_pattern.store(true, Ordering::Relaxed); + } +} /// Timing configuration for I2C v1 hardware /// diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index 72a7d05ab..f23c58c9e 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -107,6 +107,10 @@ pub(crate) unsafe fn on_interrupt() { }); } +impl<'d, M: Mode> VersionSpecificInit for I2c<'d, M, Master> { + fn version_specific_init(&mut self) {} +} + impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { pub(crate) fn init(&mut self, config: Config) { self.info.regs.cr1().modify(|reg| { -- cgit From 392548997ab65e5654a32848d93a05d5fb695a79 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Mon, 28 Jul 2025 08:05:59 +0200 Subject: stm32/i2c_v1: Rename function parameters for consistent naming --- embassy-stm32/src/i2c/v1.rs | 108 ++++++++++++++++++++++---------------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index eaf787334..ef5624d97 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -177,8 +177,8 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { Ok(sr1) } - fn write_bytes(&mut self, addr: u8, bytes: &[u8], timeout: Timeout, frame: OperationFraming) -> Result<(), Error> { - if frame.send_start() { + fn write_bytes(&mut self, address: u8, write_buffer: &[u8], timeout: Timeout, framing: OperationFraming) -> Result<(), Error> { + if framing.send_start() { // Send a START condition self.info.regs.cr1().modify(|reg| { @@ -196,7 +196,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { } // Set up current address we're trying to talk to - self.info.regs.dr().write(|reg| reg.set_dr(addr << 1)); + self.info.regs.dr().write(|reg| reg.set_dr(address << 1)); // Wait until address was sent // Wait for the address to be acknowledged @@ -210,11 +210,11 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { } // Send bytes - for c in bytes { + for c in write_buffer { self.send_byte(*c, timeout)?; } - if frame.send_stop() { + if framing.send_stop() { // Send a STOP condition self.info.regs.cr1().modify(|reg| reg.set_stop(true)); } @@ -262,16 +262,16 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { fn blocking_read_timeout( &mut self, - addr: u8, - buffer: &mut [u8], + address: u8, + read_buffer: &mut [u8], timeout: Timeout, - frame: OperationFraming, + framing: OperationFraming, ) -> Result<(), Error> { - let Some((last, buffer)) = buffer.split_last_mut() else { + let Some((last_byte, read_buffer)) = read_buffer.split_last_mut() else { return Err(Error::Overrun); }; - if frame.send_start() { + if framing.send_start() { // Send a START condition and set ACK bit self.info.regs.cr1().modify(|reg| { reg.set_start(true); @@ -289,7 +289,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { } // Set up current address we're trying to talk to - self.info.regs.dr().write(|reg| reg.set_dr((addr << 1) + 1)); + self.info.regs.dr().write(|reg| reg.set_dr((address << 1) + 1)); // Wait until address was sent // Wait for the address to be acknowledged @@ -302,52 +302,52 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { } // Receive bytes into buffer - for c in buffer { + for c in read_buffer { *c = self.recv_byte(timeout)?; } // Prepare to send NACK then STOP after next byte self.info.regs.cr1().modify(|reg| { - if frame.send_nack() { + if framing.send_nack() { reg.set_ack(false); } - if frame.send_stop() { + if framing.send_stop() { reg.set_stop(true); } }); // Receive last byte - *last = self.recv_byte(timeout)?; + *last_byte = self.recv_byte(timeout)?; // Fallthrough is success Ok(()) } /// Blocking read. - pub fn blocking_read(&mut self, addr: u8, read: &mut [u8]) -> Result<(), Error> { - self.blocking_read_timeout(addr, read, self.timeout(), OperationFraming::FirstAndLast) + pub fn blocking_read(&mut self, address: u8, read_buffer: &mut [u8]) -> Result<(), Error> { + self.blocking_read_timeout(address, read_buffer, self.timeout(), OperationFraming::FirstAndLast) } /// Blocking write. - pub fn blocking_write(&mut self, addr: u8, write: &[u8]) -> Result<(), Error> { - self.write_bytes(addr, write, self.timeout(), OperationFraming::FirstAndLast)?; + pub fn blocking_write(&mut self, address: u8, write_buffer: &[u8]) -> Result<(), Error> { + self.write_bytes(address, write_buffer, self.timeout(), OperationFraming::FirstAndLast)?; // Fallthrough is success Ok(()) } /// Blocking write, restart, read. - pub fn blocking_write_read(&mut self, addr: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> { + pub fn blocking_write_read(&mut self, address: u8, write_buffer: &[u8], read_buffer: &mut [u8]) -> Result<(), Error> { // Check empty read buffer before starting transaction. Otherwise, we would not generate the // stop condition below. - if read.is_empty() { + if read_buffer.is_empty() { return Err(Error::Overrun); } let timeout = self.timeout(); - self.write_bytes(addr, write, timeout, OperationFraming::First)?; - self.blocking_read_timeout(addr, read, timeout, OperationFraming::FirstAndLast)?; + self.write_bytes(address, write_buffer, timeout, OperationFraming::First)?; + self.blocking_read_timeout(address, read_buffer, timeout, OperationFraming::FirstAndLast)?; Ok(()) } @@ -357,13 +357,13 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { /// Consecutive operations of same type are merged. See [transaction contract] for details. /// /// [transaction contract]: embedded_hal_1::i2c::I2c::transaction - pub fn blocking_transaction(&mut self, addr: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> { + pub fn blocking_transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> { let timeout = self.timeout(); - for (op, frame) in assign_operation_framing(operations)? { + for (op, framing) in assign_operation_framing(operations)? { match op { - Operation::Read(read) => self.blocking_read_timeout(addr, read, timeout, frame)?, - Operation::Write(write) => self.write_bytes(addr, write, timeout, frame)?, + Operation::Read(read_buffer) => self.blocking_read_timeout(address, read_buffer, timeout, framing)?, + Operation::Write(write_buffer) => self.write_bytes(address, write_buffer, timeout, framing)?, } } @@ -382,7 +382,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { } impl<'d, IM: MasterMode> I2c<'d, Async, IM> { - async fn write_frame(&mut self, address: u8, write: &[u8], frame: OperationFraming) -> Result<(), Error> { + async fn write_with_framing(&mut self, address: u8, write_buffer: &[u8], framing: OperationFraming) -> Result<(), Error> { self.info.regs.cr2().modify(|w| { // Note: Do not enable the ITBUFEN bit in the I2C_CR2 register if DMA is used for // reception. @@ -404,7 +404,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { }) }); - if frame.send_start() { + if framing.send_start() { // Send a START condition self.info.regs.cr1().modify(|reg| { reg.set_start(true); @@ -465,7 +465,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { // this address from the memory after each TxE event. let dst = self.info.regs.dr().as_ptr() as *mut u8; - self.tx_dma.as_mut().unwrap().write(write, dst, Default::default()) + self.tx_dma.as_mut().unwrap().write(write_buffer, dst, Default::default()) }; // Wait for bytes to be sent, or an error to occur. @@ -492,7 +492,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { w.set_dmaen(false); }); - if frame.send_stop() { + if framing.send_stop() { // The I2C transfer itself will take longer than the DMA transfer, so wait for that to finish too. // 18.3.8 “Master transmitter: In the interrupt routine after the EOT interrupt, disable DMA @@ -527,28 +527,28 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { } /// Write. - pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> { - self.write_frame(address, write, OperationFraming::FirstAndLast) + pub async fn write(&mut self, address: u8, write_buffer: &[u8]) -> Result<(), Error> { + self.write_with_framing(address, write_buffer, OperationFraming::FirstAndLast) .await?; Ok(()) } /// Read. - pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { - self.read_frame(address, buffer, OperationFraming::FirstAndLast) + pub async fn read(&mut self, address: u8, read_buffer: &mut [u8]) -> Result<(), Error> { + self.read_with_framing(address, read_buffer, OperationFraming::FirstAndLast) .await?; Ok(()) } - async fn read_frame(&mut self, address: u8, buffer: &mut [u8], frame: OperationFraming) -> Result<(), Error> { - if buffer.is_empty() { + async fn read_with_framing(&mut self, address: u8, read_buffer: &mut [u8], framing: OperationFraming) -> Result<(), Error> { + if read_buffer.is_empty() { return Err(Error::Overrun); } // Some branches below depend on whether the buffer contains only a single byte. - let single_byte = buffer.len() == 1; + let single_byte = read_buffer.len() == 1; self.info.regs.cr2().modify(|w| { // Note: Do not enable the ITBUFEN bit in the I2C_CR2 register if DMA is used for @@ -560,7 +560,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { // If, in the I2C_CR2 register, the LAST bit is set, I2C automatically sends a NACK // after the next byte following EOT_1. The user can generate a Stop condition in // the DMA Transfer Complete interrupt routine if enabled. - w.set_last(frame.send_nack() && !single_byte); + w.set_last(framing.send_nack() && !single_byte); }); // Sentinel to disable transfer when an error occurs or future is canceled. @@ -573,7 +573,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { }) }); - if frame.send_start() { + if framing.send_start() { // Send a START condition and set ACK bit self.info.regs.cr1().modify(|reg| { reg.set_start(true); @@ -628,7 +628,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { // 18.3.8: When a single byte must be received: the NACK must be programmed during EV6 // event, i.e. program ACK=0 when ADDR=1, before clearing ADDR flag. - if frame.send_nack() && single_byte { + if framing.send_nack() && single_byte { self.info.regs.cr1().modify(|w| { w.set_ack(false); }); @@ -638,8 +638,8 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { self.info.regs.sr2().read(); } else { // Before starting reception of single byte (but without START condition, i.e. in case - // of continued frame), program NACK to emit at end of this byte. - if frame.send_nack() && single_byte { + // of merged operations), program NACK to emit at end of this byte. + if framing.send_nack() && single_byte { self.info.regs.cr1().modify(|w| { w.set_ack(false); }); @@ -649,7 +649,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { // 18.3.8: When a single byte must be received: [snip] Then the user can program the STOP // condition either after clearing ADDR flag, or in the DMA Transfer Complete interrupt // routine. - if frame.send_stop() && single_byte { + if framing.send_stop() && single_byte { self.info.regs.cr1().modify(|w| { w.set_stop(true); }); @@ -660,7 +660,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { // from this address from the memory after each RxE event. let src = self.info.regs.dr().as_ptr() as *mut u8; - self.rx_dma.as_mut().unwrap().read(src, buffer, Default::default()) + self.rx_dma.as_mut().unwrap().read(src, read_buffer, Default::default()) }; // Wait for bytes to be received, or an error to occur. @@ -686,7 +686,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { w.set_dmaen(false); }); - if frame.send_stop() && !single_byte { + if framing.send_stop() && !single_byte { self.info.regs.cr1().modify(|w| { w.set_stop(true); }); @@ -699,15 +699,15 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { } /// Write, restart, read. - pub async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> { + pub async fn write_read(&mut self, address: u8, write_buffer: &[u8], read_buffer: &mut [u8]) -> Result<(), Error> { // Check empty read buffer before starting transaction. Otherwise, we would not generate the // stop condition below. - if read.is_empty() { + if read_buffer.is_empty() { return Err(Error::Overrun); } - self.write_frame(address, write, OperationFraming::First).await?; - self.read_frame(address, read, OperationFraming::FirstAndLast).await + self.write_with_framing(address, write_buffer, OperationFraming::First).await?; + self.read_with_framing(address, read_buffer, OperationFraming::FirstAndLast).await } /// Transaction with operations. @@ -715,11 +715,11 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { /// Consecutive operations of same type are merged. See [transaction contract] for details. /// /// [transaction contract]: embedded_hal_1::i2c::I2c::transaction - pub async fn transaction(&mut self, addr: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> { - for (op, frame) in assign_operation_framing(operations)? { + pub async fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> { + for (op, framing) in assign_operation_framing(operations)? { match op { - Operation::Read(read) => self.read_frame(addr, read, frame).await?, - Operation::Write(write) => self.write_frame(addr, write, frame).await?, + Operation::Read(read_buffer) => self.read_with_framing(address, read_buffer, framing).await?, + Operation::Write(write_buffer) => self.write_with_framing(address, write_buffer, framing).await?, } } -- cgit From c531af42c8dd2eaeb56ef1891396ac559918560e Mon Sep 17 00:00:00 2001 From: HybridChild Date: Thu, 7 Aug 2025 10:34:29 +0200 Subject: Revert "stm32/i2c: Add temporary trait for version-specific initialization during v1 rework" This reverts commit d38e1de962b92d1d48f1991ce09e494ea46d3f7f. --- embassy-stm32/src/i2c/mod.rs | 21 +-------------------- embassy-stm32/src/i2c/v1.rs | 38 ++++++++------------------------------ embassy-stm32/src/i2c/v2.rs | 4 ---- 3 files changed, 9 insertions(+), 54 deletions(-) diff --git a/embassy-stm32/src/i2c/mod.rs b/embassy-stm32/src/i2c/mod.rs index 660b8144a..675a392f9 100644 --- a/embassy-stm32/src/i2c/mod.rs +++ b/embassy-stm32/src/i2c/mod.rs @@ -29,21 +29,6 @@ use crate::rcc::{RccInfo, SealedRccPeripheral}; use crate::time::Hertz; use crate::{interrupt, peripherals}; -/// Temporary trait for version-specific initialization during I2C v1 async rework. -/// -/// This trait allows the shared constructor in mod.rs to call version-specific -/// initialization while we incrementally migrate v1 async operations to use -/// the new event-driven interrupt pattern. Will be removed once the rework -/// is complete and both blocking/async modes use unified initialization. -pub trait VersionSpecificInit { - /// Performs version and mode-specific initialization. - /// - /// For v1: Sets interrupt pattern flag based on blocking vs async mode. - /// For v2: No-op, does nothing. - fn version_specific_init(&mut self); -} - - /// I2C error. #[derive(Debug, PartialEq, Eq, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -207,10 +192,7 @@ impl<'d> I2c<'d, Blocking, Master> { } } -impl<'d, M: Mode> I2c<'d, M, Master> -where - Self: VersionSpecificInit -{ +impl<'d, M: Mode> I2c<'d, M, Master> { /// Create a new I2C driver. fn new_inner( _peri: Peri<'d, T>, @@ -241,7 +223,6 @@ where }; this.enable_and_init(config); - this.version_specific_init(); this } diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index ef5624d97..f2fd0147e 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -6,7 +6,6 @@ use core::future::poll_fn; use core::task::Poll; -use core::sync::atomic::{AtomicBool, Ordering}; use embassy_embedded_hal::SetConfig; use embassy_futures::select::{select, Either}; @@ -23,14 +22,12 @@ use embassy_sync::waitqueue::AtomicWaker; /// I2C v2 peripheral state pub(crate) struct State { pub(crate) waker: AtomicWaker, - pub use_new_interrupt_pattern: AtomicBool, } impl State { pub(crate) const fn new() -> Self { Self { waker: AtomicWaker::new(), - use_new_interrupt_pattern: AtomicBool::new(false), } } } @@ -47,25 +44,17 @@ impl State { // There's some more details there, and we might have a fix for you. But please let us know if you // hit a case like this! pub unsafe fn on_interrupt() { + let regs = T::info().regs; // i2c v2 only woke the task on transfer complete interrupts. v1 uses interrupts for a bunch of // other stuff, so we wake the task on every interrupt. - - let regs = T::info().regs; - let state = T::state(); - - if state.use_new_interrupt_pattern.load(Ordering::Relaxed) { - - } else { - critical_section::with(|_| { - // Clear event interrupt flag. - regs.cr2().modify(|w| { - w.set_itevten(false); - w.set_iterren(false); - }); + T::state().waker.wake(); + critical_section::with(|_| { + // Clear event interrupt flag. + regs.cr2().modify(|w| { + w.set_itevten(false); + w.set_iterren(false); }); - } - - state.waker.wake(); + }); } impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { @@ -727,17 +716,6 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { } } -impl<'d> VersionSpecificInit for I2c<'d, Blocking, Master> { - fn version_specific_init(&mut self) { - self.state.use_new_interrupt_pattern.store(false, Ordering::Relaxed); - } -} - -impl<'d> VersionSpecificInit for I2c<'d, Async, Master> { - fn version_specific_init(&mut self) { - self.state.use_new_interrupt_pattern.store(true, Ordering::Relaxed); - } -} /// Timing configuration for I2C v1 hardware /// diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index f23c58c9e..72a7d05ab 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -107,10 +107,6 @@ pub(crate) unsafe fn on_interrupt() { }); } -impl<'d, M: Mode> VersionSpecificInit for I2c<'d, M, Master> { - fn version_specific_init(&mut self) {} -} - impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { pub(crate) fn init(&mut self, config: Config) { self.info.regs.cr1().modify(|reg| { -- cgit From b88c5195e030b6fac129ea9cb74eb169227f7335 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sun, 10 Aug 2025 08:31:35 +0200 Subject: stm32/i2c_v1: Add MultiMaster (Slave) mode implementation --- embassy-stm32/src/i2c/v1.rs | 341 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 341 insertions(+) diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index f2fd0147e..7b6ecf869 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -32,6 +32,21 @@ impl State { } } +#[derive(Debug, PartialEq)] +enum SlaveSendResult { + Acked, // Byte sent and ACK received from master + Nacked, // Byte sent but NACK received (normal end of transmission) + Stopped, // STOP condition detected + Restart, // RESTART condition detected +} + +#[derive(Debug, PartialEq)] +enum SlaveReceiveResult { + Byte(u8), // Data byte received + Stop, // STOP condition detected + Restart, // RESTART condition (new ADDR) detected +} + // /!\ /!\ // /!\ Implementation note! /!\ // /!\ /!\ @@ -370,6 +385,332 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { } } +impl<'d, M: Mode> I2c<'d, M, Master> { + /// Configure the I2C driver for slave operations, allowing for the driver to be used as a slave and a master (multimaster) + pub fn into_slave_multimaster(mut self, slave_addr_config: SlaveAddrConfig) -> I2c<'d, M, MultiMaster> { + let mut slave = I2c { + info: self.info, + state: self.state, + kernel_clock: self.kernel_clock, + tx_dma: self.tx_dma.take(), // Use take() to move ownership + rx_dma: self.rx_dma.take(), // Use take() to move ownership + #[cfg(feature = "time")] + timeout: self.timeout, + _phantom: PhantomData, + _phantom2: PhantomData, + _drop_guard: self._drop_guard, // Move the drop guard + }; + slave.init_slave(slave_addr_config); + slave + } +} + +impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { + pub(crate) fn init_slave(&mut self, config: SlaveAddrConfig) { + // Disable peripheral for configuration + self.info.regs.cr1().modify(|reg| { + reg.set_pe(false); + }); + + // Configure v1-specific slave settings + self.configure_addresses(config); + + // Enable slave mode interrupts and settings + self.info.regs.cr2().modify(|w| { + w.set_itevten(true); // Event interrupts + w.set_iterren(true); // Error interrupts + }); + + // Re-enable peripheral + self.info.regs.cr1().modify(|reg| { + reg.set_pe(true); + }); + } + + fn configure_oa1(&mut self, addr: Address) { + match addr { + Address::SevenBit(addr) => { + self.info.regs.oar1().write(|reg| { + // v1 uses left-shifted 7-bit address in bits [7:1] + // STM32 reference manual says bits 7:1 for address, bit 0 don't care for 7-bit + reg.set_add((addr as u16) << 1); + reg.set_addmode(i2c::vals::Addmode::BIT7); + }); + }, + Address::TenBit(addr) => { + self.info.regs.oar1().modify(|reg| { + reg.set_add(addr); // Set address bits [9:0] + reg.set_addmode(i2c::vals::Addmode::BIT10); + // Manually set bit 14 as required by reference manual + reg.0 |= 1 << 14; + }); + } + } + } + + fn configure_oa2_simple(&mut self, addr: u8) { + self.info.regs.oar2().write(|reg| { + // v1 OA2: 7-bit address only, no masking support + // Address goes in bits [7:1], enable dual addressing + reg.set_add2(addr); + reg.set_endual(i2c::vals::Endual::DUAL); + }); + } + + fn configure_addresses(&mut self, config: SlaveAddrConfig) { + match config.addr { + OwnAddresses::OA1(addr) => { + self.configure_oa1(addr); + // Disable OA2 if not needed + self.info.regs.oar2().write(|reg| { + reg.set_endual(i2c::vals::Endual::SINGLE); + }); + }, + OwnAddresses::OA2(oa2) => { + // v1 limitation: ignore mask, only support simple OA2 + if !matches!(oa2.mask, AddrMask::NOMASK) { + // Could log a warning here that masking is ignored in v1 + #[cfg(feature = "defmt")] + defmt::warn!("I2C v1 does not support OA2 address masking, ignoring mask setting"); + } + + // Must have a default OA1 when using OA2-only mode + // Set OA1 to a reserved address that won't conflict + self.info.regs.oar1().write(|reg| { + reg.set_add(0); // Address 0x00 is reserved, safe to use + reg.set_addmode(i2c::vals::Addmode::BIT7); + }); + + self.configure_oa2_simple(oa2.addr); + }, + OwnAddresses::Both { oa1, oa2 } => { + self.configure_oa1(oa1); + + // Same masking limitation applies + if !matches!(oa2.mask, AddrMask::NOMASK) { + #[cfg(feature = "defmt")] + defmt::warn!("I2C v1 does not support OA2 address masking, ignoring mask setting"); + } + + self.configure_oa2_simple(oa2.addr); + } + } + + // Configure general call if requested + if config.general_call { + self.info.regs.cr1().modify(|w| w.set_engc(true)); + } + } +} + +impl<'d, M: Mode> I2c<'d, M, MultiMaster> { + /// Listen for incoming I2C address match and return the command type + pub fn blocking_listen(&mut self) -> Result { + let timeout = self.timeout(); // Get timeout internally + self.blocking_listen_timeout(timeout) + } + + /// Respond to master read request (master wants to read from us) + pub fn blocking_respond_to_read(&mut self, data: &[u8]) -> Result { + let timeout = self.timeout(); // Get timeout internally + self.blocking_respond_to_read_timeout(data, timeout) + } + + /// Respond to master write request (master wants to write to us) + pub fn blocking_respond_to_write(&mut self, buffer: &mut [u8]) -> Result { + let timeout = self.timeout(); // Get timeout internally + self.blocking_respond_to_write_timeout(buffer, timeout) + } + + // Private implementation methods with Timeout parameter + fn blocking_listen_timeout(&mut self, timeout: Timeout) -> Result { + // Enable address match interrupt for slave mode + self.info.regs.cr2().modify(|w| { + w.set_itevten(true); // Enable event interrupts + }); + + // Wait for address match (ADDR flag) + loop { + let sr1 = Self::check_and_clear_error_flags(self.info)?; + + if sr1.addr() { + // Address matched! Read SR2 to get direction and clear ADDR + let sr2 = self.info.regs.sr2().read(); + let direction = if sr2.tra() { + SlaveCommandKind::Read // Master wants to read from us (we transmit) + } else { + SlaveCommandKind::Write // Master wants to write to us (we receive) + }; + + // Determine which address was matched + let matched_address = self.determine_matched_address(sr2)?; + + // ADDR is automatically cleared by reading SR1 then SR2 + return Ok(SlaveCommand { + kind: direction, + address: matched_address, + }); + } + + timeout.check()?; + } + } + + fn blocking_respond_to_read_timeout(&mut self, data: &[u8], timeout: Timeout) -> Result { + let mut bytes_sent = 0; + + for &byte in data { + match self.send_byte_or_nack(byte, timeout)? { + SlaveSendResult::Acked => { + bytes_sent += 1; + // Continue sending + }, + SlaveSendResult::Nacked | SlaveSendResult::Stopped => { + // Master finished reading or sent STOP + break; + } + SlaveSendResult::Restart => { + // Master wants to change direction (rare but possible) + break; + } + } + } + + Ok(bytes_sent) + } + + fn blocking_respond_to_write_timeout(&mut self, buffer: &mut [u8], timeout: Timeout) -> Result { + let mut bytes_received = 0; + while bytes_received < buffer.len() { + match self.recv_byte_or_stop(timeout)? { + SlaveReceiveResult::Byte(b) => { + buffer[bytes_received] = b; + bytes_received += 1; + }, + SlaveReceiveResult::Stop => break, + SlaveReceiveResult::Restart => break, + } + } + Ok(bytes_received) + } + + fn determine_matched_address(&self, sr2: stm32_metapac::i2c::regs::Sr2) -> Result { + // Check for general call first + if sr2.gencall() { + Ok(Address::SevenBit(0x00)) + } else if sr2.dualf() { + // OA2 was matched - verify it's actually enabled + let oar2 = self.info.regs.oar2().read(); + if oar2.endual() != i2c::vals::Endual::DUAL { + return Err(Error::Bus); // Hardware inconsistency + } + Ok(Address::SevenBit(oar2.add2())) + } else { + // OA1 was matched + let oar1 = self.info.regs.oar1().read(); + match oar1.addmode() { + i2c::vals::Addmode::BIT7 => { + Ok(Address::SevenBit((oar1.add() >> 1) as u8)) + }, + i2c::vals::Addmode::BIT10 => { + Ok(Address::TenBit(oar1.add())) + }, + } + } + } + + /// Send a byte in slave transmitter mode and check for ACK/NACK/STOP + fn send_byte_or_nack(&mut self, byte: u8, timeout: Timeout) -> Result { + // Wait until we're ready for sending (TXE flag set) + loop { + let sr1 = Self::check_and_clear_error_flags(self.info)?; + + // Check for STOP condition first + if sr1.stopf() { + self.info.regs.cr1().modify(|_w| {}); + return Ok(SlaveSendResult::Stopped); + } + + // Check for RESTART (new ADDR) + if sr1.addr() { + // Don't clear ADDR here - let next blocking_listen() handle it + return Ok(SlaveSendResult::Restart); + } + + // Check for NACK (AF flag) + if sr1.af() { + self.info.regs.sr1().modify(|w| w.set_af(false)); + return Ok(SlaveSendResult::Nacked); + } + + // Check if we can send data + if sr1.txe() { + break; // Ready to send + } + + timeout.check()?; + } + + // Send the byte + self.info.regs.dr().write(|w| w.set_dr(byte)); + + // Wait for byte transfer to complete (BTF flag or error) + loop { + let sr1 = Self::check_and_clear_error_flags(self.info)?; + + // Check for STOP condition + if sr1.stopf() { + self.info.regs.cr1().modify(|_w| {}); + return Ok(SlaveSendResult::Stopped); + } + + // Check for RESTART (new ADDR) + if sr1.addr() { + return Ok(SlaveSendResult::Restart); + } + + // Check for NACK (AF flag) + if sr1.af() { + self.info.regs.sr1().modify(|w| w.set_af(false)); + return Ok(SlaveSendResult::Nacked); + } + + // Check for byte transfer finished + if sr1.btf() { + return Ok(SlaveSendResult::Acked); + } + + timeout.check()?; + } + } + + /// Receive a byte in slave receiver mode or detect STOP condition + fn recv_byte_or_stop(&mut self, timeout: Timeout) -> Result { + loop { + let sr1 = Self::check_and_clear_error_flags(self.info)?; + + // Check for STOP condition first + if sr1.stopf() { + self.info.regs.cr1().modify(|_w| {}); + return Ok(SlaveReceiveResult::Stop); + } + + // Check for RESTART (new ADDR) + if sr1.addr() { + // Don't clear ADDR here - let next blocking_listen() handle it + return Ok(SlaveReceiveResult::Restart); + } + + if sr1.rxne() { + let byte = self.info.regs.dr().read().dr(); + return Ok(SlaveReceiveResult::Byte(byte)); + } + + timeout.check()?; + } + } +} + impl<'d, IM: MasterMode> I2c<'d, Async, IM> { async fn write_with_framing(&mut self, address: u8, write_buffer: &[u8], framing: OperationFraming) -> Result<(), Error> { self.info.regs.cr2().modify(|w| { -- cgit From 6570036f141befc76fd5f5237db0045c0b0f9a71 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sun, 10 Aug 2025 10:05:26 +0200 Subject: stm32/i2c_v1: Add defmt trace messages --- embassy-stm32/src/i2c/v1.rs | 75 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 10 deletions(-) diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index 7b6ecf869..3aa003fa5 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -60,6 +60,7 @@ enum SlaveReceiveResult { // hit a case like this! pub unsafe fn on_interrupt() { let regs = T::info().regs; + trace!("i2c v1 interrupt triggered"); // i2c v2 only woke the task on transfer complete interrupts. v1 uses interrupts for a bunch of // other stuff, so we wake the task on every interrupt. T::state().waker.wake(); @@ -122,6 +123,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { self.info.regs.cr1().modify(|reg| { reg.set_pe(true); }); + trace!("i2c v1 init complete"); } fn check_and_clear_error_flags(info: &'static Info) -> Result { @@ -407,6 +409,7 @@ impl<'d, M: Mode> I2c<'d, M, Master> { impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { pub(crate) fn init_slave(&mut self, config: SlaveAddrConfig) { + trace!("i2c v1 slave init: config={:?}", config); // Disable peripheral for configuration self.info.regs.cr1().modify(|reg| { reg.set_pe(false); @@ -425,6 +428,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { self.info.regs.cr1().modify(|reg| { reg.set_pe(true); }); + trace!("i2c v1 slave init complete"); } fn configure_oa1(&mut self, addr: Address) { @@ -471,7 +475,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { if !matches!(oa2.mask, AddrMask::NOMASK) { // Could log a warning here that masking is ignored in v1 #[cfg(feature = "defmt")] - defmt::warn!("I2C v1 does not support OA2 address masking, ignoring mask setting"); + warn!("I2C v1 does not support OA2 address masking, ignoring mask setting"); } // Must have a default OA1 when using OA2-only mode @@ -506,24 +510,34 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { impl<'d, M: Mode> I2c<'d, M, MultiMaster> { /// Listen for incoming I2C address match and return the command type pub fn blocking_listen(&mut self) -> Result { + trace!("i2c v1 slave: blocking_listen start"); let timeout = self.timeout(); // Get timeout internally - self.blocking_listen_timeout(timeout) + let result = self.blocking_listen_timeout(timeout); + trace!("i2c v1 slave: blocking_listen result={:?}", result); + result } /// Respond to master read request (master wants to read from us) pub fn blocking_respond_to_read(&mut self, data: &[u8]) -> Result { + trace!("i2c v1 slave: blocking_respond_to_read start, data_len={}", data.len()); let timeout = self.timeout(); // Get timeout internally - self.blocking_respond_to_read_timeout(data, timeout) + let result = self.blocking_respond_to_read_timeout(data, timeout); + trace!("i2c v1 slave: blocking_respond_to_read result={:?}", result); + result } /// Respond to master write request (master wants to write to us) pub fn blocking_respond_to_write(&mut self, buffer: &mut [u8]) -> Result { + trace!("i2c v1 slave: blocking_respond_to_write start, buffer_len={}", buffer.len()); let timeout = self.timeout(); // Get timeout internally - self.blocking_respond_to_write_timeout(buffer, timeout) + let result = self.blocking_respond_to_write_timeout(buffer, timeout); + trace!("i2c v1 slave: blocking_respond_to_write result={:?}", result); + result } // Private implementation methods with Timeout parameter fn blocking_listen_timeout(&mut self, timeout: Timeout) -> Result { + trace!("i2c v1 slave: listen_timeout start"); // Enable address match interrupt for slave mode self.info.regs.cr2().modify(|w| { w.set_itevten(true); // Enable event interrupts @@ -537,13 +551,16 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { // Address matched! Read SR2 to get direction and clear ADDR let sr2 = self.info.regs.sr2().read(); let direction = if sr2.tra() { + trace!("i2c v1 slave: address match - READ direction (master wants to read from us)"); SlaveCommandKind::Read // Master wants to read from us (we transmit) } else { + trace!("i2c v1 slave: address match - WRITE direction (master wants to write to us)"); SlaveCommandKind::Write // Master wants to write to us (we receive) }; // Determine which address was matched let matched_address = self.determine_matched_address(sr2)?; + trace!("i2c v1 slave: matched address={:?}", matched_address); // ADDR is automatically cleared by reading SR1 then SR2 return Ok(SlaveCommand { @@ -557,62 +574,86 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { } fn blocking_respond_to_read_timeout(&mut self, data: &[u8], timeout: Timeout) -> Result { + trace!("i2c v1 slave: respond_to_read_timeout start, data_len={}", data.len()); let mut bytes_sent = 0; for &byte in data { + trace!("i2c v1 slave: sending byte={:#x} ({})", byte, bytes_sent); match self.send_byte_or_nack(byte, timeout)? { SlaveSendResult::Acked => { bytes_sent += 1; + trace!("i2c v1 slave: byte acked, total_sent={}", bytes_sent); // Continue sending }, - SlaveSendResult::Nacked | SlaveSendResult::Stopped => { - // Master finished reading or sent STOP + SlaveSendResult::Nacked => { + trace!("i2c v1 slave: byte nacked, stopping transmission"); + break; + }, + SlaveSendResult::Stopped => { + trace!("i2c v1 slave: stop condition detected, stopping transmission"); break; } SlaveSendResult::Restart => { - // Master wants to change direction (rare but possible) + trace!("i2c v1 slave: restart detected, stopping transmission"); break; } } } + trace!("i2c v1 slave: respond_to_read_timeout complete, bytes_sent={}", bytes_sent); Ok(bytes_sent) } fn blocking_respond_to_write_timeout(&mut self, buffer: &mut [u8], timeout: Timeout) -> Result { + trace!("i2c v1 slave: respond_to_write_timeout start, buffer_len={}", buffer.len()); let mut bytes_received = 0; while bytes_received < buffer.len() { match self.recv_byte_or_stop(timeout)? { SlaveReceiveResult::Byte(b) => { + trace!("i2c v1 slave: received byte={:#x} ({})", b, bytes_received); buffer[bytes_received] = b; bytes_received += 1; }, - SlaveReceiveResult::Stop => break, - SlaveReceiveResult::Restart => break, + SlaveReceiveResult::Stop => { + trace!("i2c v1 slave: stop condition detected, stopping reception"); + break; + }, + SlaveReceiveResult::Restart => { + trace!("i2c v1 slave: restart detected, stopping reception"); + break; + }, } } + trace!("i2c v1 slave: respond_to_write_timeout complete, bytes_received={}", bytes_received); Ok(bytes_received) } fn determine_matched_address(&self, sr2: stm32_metapac::i2c::regs::Sr2) -> Result { + trace!("i2c v1 slave: determine_matched_address, sr2={:#x}", sr2.0); // Check for general call first if sr2.gencall() { + trace!("i2c v1 slave: general call address matched"); Ok(Address::SevenBit(0x00)) } else if sr2.dualf() { // OA2 was matched - verify it's actually enabled let oar2 = self.info.regs.oar2().read(); if oar2.endual() != i2c::vals::Endual::DUAL { + error!("i2c v1 slave: OA2 matched but not enabled - hardware inconsistency"); return Err(Error::Bus); // Hardware inconsistency } + trace!("i2c v1 slave: OA2 address matched: {:#x}", oar2.add2()); Ok(Address::SevenBit(oar2.add2())) } else { // OA1 was matched let oar1 = self.info.regs.oar1().read(); match oar1.addmode() { i2c::vals::Addmode::BIT7 => { - Ok(Address::SevenBit((oar1.add() >> 1) as u8)) + let addr = (oar1.add() >> 1) as u8; + trace!("i2c v1 slave: OA1 7-bit address matched: {:#x}", addr); + Ok(Address::SevenBit(addr)) }, i2c::vals::Addmode::BIT10 => { + trace!("i2c v1 slave: OA1 10-bit address matched: {:#x}", oar1.add()); Ok(Address::TenBit(oar1.add())) }, } @@ -621,30 +662,35 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { /// Send a byte in slave transmitter mode and check for ACK/NACK/STOP fn send_byte_or_nack(&mut self, byte: u8, timeout: Timeout) -> Result { + trace!("i2c v1 slave: send_byte_or_nack start, byte={:#x}", byte); // Wait until we're ready for sending (TXE flag set) loop { let sr1 = Self::check_and_clear_error_flags(self.info)?; // Check for STOP condition first if sr1.stopf() { + trace!("i2c v1 slave: STOP detected before send"); self.info.regs.cr1().modify(|_w| {}); return Ok(SlaveSendResult::Stopped); } // Check for RESTART (new ADDR) if sr1.addr() { + trace!("i2c v1 slave: RESTART detected before send"); // Don't clear ADDR here - let next blocking_listen() handle it return Ok(SlaveSendResult::Restart); } // Check for NACK (AF flag) if sr1.af() { + trace!("i2c v1 slave: NACK detected before send"); self.info.regs.sr1().modify(|w| w.set_af(false)); return Ok(SlaveSendResult::Nacked); } // Check if we can send data if sr1.txe() { + trace!("i2c v1 slave: TXE ready, sending byte"); break; // Ready to send } @@ -653,6 +699,7 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { // Send the byte self.info.regs.dr().write(|w| w.set_dr(byte)); + trace!("i2c v1 slave: byte written to DR, waiting for BTF"); // Wait for byte transfer to complete (BTF flag or error) loop { @@ -660,23 +707,27 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { // Check for STOP condition if sr1.stopf() { + trace!("i2c v1 slave: STOP detected after send"); self.info.regs.cr1().modify(|_w| {}); return Ok(SlaveSendResult::Stopped); } // Check for RESTART (new ADDR) if sr1.addr() { + trace!("i2c v1 slave: RESTART detected after send"); return Ok(SlaveSendResult::Restart); } // Check for NACK (AF flag) if sr1.af() { + trace!("i2c v1 slave: NACK detected after send"); self.info.regs.sr1().modify(|w| w.set_af(false)); return Ok(SlaveSendResult::Nacked); } // Check for byte transfer finished if sr1.btf() { + trace!("i2c v1 slave: BTF set, byte transfer complete"); return Ok(SlaveSendResult::Acked); } @@ -686,23 +737,27 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { /// Receive a byte in slave receiver mode or detect STOP condition fn recv_byte_or_stop(&mut self, timeout: Timeout) -> Result { + trace!("i2c v1 slave: recv_byte_or_stop start"); loop { let sr1 = Self::check_and_clear_error_flags(self.info)?; // Check for STOP condition first if sr1.stopf() { + trace!("i2c v1 slave: STOP detected during receive"); self.info.regs.cr1().modify(|_w| {}); return Ok(SlaveReceiveResult::Stop); } // Check for RESTART (new ADDR) if sr1.addr() { + trace!("i2c v1 slave: RESTART detected during receive"); // Don't clear ADDR here - let next blocking_listen() handle it return Ok(SlaveReceiveResult::Restart); } if sr1.rxne() { let byte = self.info.regs.dr().read().dr(); + trace!("i2c v1 slave: received byte={:#x}", byte); return Ok(SlaveReceiveResult::Byte(byte)); } -- cgit From d6d54392f15cb0430a50ba85e45d746aa09fc6ac Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sun, 10 Aug 2025 10:48:12 +0200 Subject: stm32/i2c_v1: Fix bugs with slave address initialization and missing ACK bit --- embassy-stm32/src/i2c/v1.rs | 128 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 107 insertions(+), 21 deletions(-) diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index 3aa003fa5..2bc309258 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -408,57 +408,97 @@ impl<'d, M: Mode> I2c<'d, M, Master> { } impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { + /// Enhanced slave configuration with proper v1 address setup pub(crate) fn init_slave(&mut self, config: SlaveAddrConfig) { trace!("i2c v1 slave init: config={:?}", config); + // Disable peripheral for configuration self.info.regs.cr1().modify(|reg| { reg.set_pe(false); }); - - // Configure v1-specific slave settings + + // Configure addresses with proper v1 format self.configure_addresses(config); - // Enable slave mode interrupts and settings - self.info.regs.cr2().modify(|w| { - w.set_itevten(true); // Event interrupts - w.set_iterren(true); // Error interrupts - }); + // Configure general call if requested + if config.general_call { + self.info.regs.cr1().modify(|w| w.set_engc(true)); + trace!("i2c v1 slave: General call enabled"); + } + + // Log final configuration before enabling + let cr1 = self.info.regs.cr1().read(); + let oar1 = self.info.regs.oar1().read(); + let oar2 = self.info.regs.oar2().read(); + trace!("i2c v1 slave: Pre-enable state - CR1={:#x}, OAR1={:#x}, OAR2={:#x}", + cr1.0, oar1.0, oar2.0); + trace!("i2c v1 slave: Address details - OAR1.ADD={:#x}, OAR1.ADDMODE={}, bit14={}", + oar1.add(), oar1.addmode() as u8, (oar1.0 >> 14) & 1); - // Re-enable peripheral self.info.regs.cr1().modify(|reg| { - reg.set_pe(true); + reg.set_pe(true); // Re-enable peripheral + reg.set_ack(true); // Critical for slave to ACK its address }); + + // Verify peripheral is enabled and ready + let cr1_final = self.info.regs.cr1().read(); + trace!("i2c v1 slave: Final state - CR1={:#x}, PE={}", cr1_final.0, cr1_final.pe()); + trace!("i2c v1 slave init complete"); } fn configure_oa1(&mut self, addr: Address) { match addr { Address::SevenBit(addr) => { + trace!("i2c v1 slave: Setting OA1 7-bit address: input={:#x}", addr); self.info.regs.oar1().write(|reg| { - // v1 uses left-shifted 7-bit address in bits [7:1] - // STM32 reference manual says bits 7:1 for address, bit 0 don't care for 7-bit - reg.set_add((addr as u16) << 1); + // For I2C v1, the 7-bit address goes in bits [7:1] of the ADD field + // The ADD field spans bits [9:0], so we put the address in the correct position + let hw_addr = (addr as u16) << 1; // This puts address in bits [7:1], bit [0] = 0 + reg.set_add(hw_addr); reg.set_addmode(i2c::vals::Addmode::BIT7); }); + + // CRITICAL: Set bit 14 as required by the reference manual + // "Bit 14: Should always be kept at 1 by software" + self.info.regs.oar1().modify(|reg| { + reg.0 |= 1 << 14; // Set bit 14 + }); + + let oar1_verify = self.info.regs.oar1().read(); + trace!("i2c v1 slave: OA1 configured - OAR1={:#x}, stored_addr={:#x}, bit14={}", + oar1_verify.0, oar1_verify.add(), (oar1_verify.0 >> 14) & 1); }, Address::TenBit(addr) => { - self.info.regs.oar1().modify(|reg| { - reg.set_add(addr); // Set address bits [9:0] + trace!("i2c v1 slave: Setting OA1 10-bit address: {:#x}", addr); + self.info.regs.oar1().write(|reg| { + reg.set_add(addr); // For 10-bit, full address goes in ADD field reg.set_addmode(i2c::vals::Addmode::BIT10); - // Manually set bit 14 as required by reference manual - reg.0 |= 1 << 14; }); + + // Set required bit 14 for 10-bit mode too + self.info.regs.oar1().modify(|reg| { + reg.0 |= 1 << 14; // Set bit 14 + }); + + let oar1_verify = self.info.regs.oar1().read(); + trace!("i2c v1 slave: OA1 10-bit configured - OAR1={:#x}, bit14={}", + oar1_verify.0, (oar1_verify.0 >> 14) & 1); } } } - + fn configure_oa2_simple(&mut self, addr: u8) { + trace!("i2c v1 slave: Setting OA2 address: {:#x}", addr); self.info.regs.oar2().write(|reg| { - // v1 OA2: 7-bit address only, no masking support - // Address goes in bits [7:1], enable dual addressing - reg.set_add2(addr); - reg.set_endual(i2c::vals::Endual::DUAL); + // For OA2, the address goes in bits [7:1] of the ADD2 field + reg.set_add2(addr); // ADD2 field automatically handles bits [7:1] placement + reg.set_endual(i2c::vals::Endual::DUAL); // Enable dual addressing }); + + let oar2_verify = self.info.regs.oar2().read(); + trace!("i2c v1 slave: OA2 configured - OAR2={:#x}, ADD2={:#x}, ENDUAL={}", + oar2_verify.0, oar2_verify.add2(), oar2_verify.endual() as u8); } fn configure_addresses(&mut self, config: SlaveAddrConfig) { @@ -507,6 +547,52 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { } } +// Also add a verification function to check address configuration +impl<'d, M: Mode> I2c<'d, M, MultiMaster> { + /// Verify the slave address configuration is correct + pub fn verify_slave_config(&self) -> Result<(), Error> { + let oar1 = self.info.regs.oar1().read(); + let oar2 = self.info.regs.oar2().read(); + let cr1 = self.info.regs.cr1().read(); + + info!("I2C v1 Slave Configuration Verification:"); + info!(" CR1: {:#x} (PE={})", cr1.0, cr1.pe()); + info!(" OAR1: {:#x}", oar1.0); + info!(" ADD: {:#x}", oar1.add()); + info!(" ADDMODE: {} ({})", oar1.addmode() as u8, + if oar1.addmode() as u8 == 0 { "7-bit" } else { "10-bit" }); + info!(" Bit 14: {}", (oar1.0 >> 14) & 1); + info!(" OAR2: {:#x}", oar2.0); + info!(" ADD2: {:#x}", oar2.add2()); + info!(" ENDUAL: {} ({})", oar2.endual() as u8, + if oar2.endual() as u8 == 0 { "Single" } else { "Dual" }); + + // Check critical requirements + if !cr1.pe() { + error!("ERROR: I2C peripheral not enabled (PE=0)"); + return Err(Error::Bus); + } + + if (oar1.0 >> 14) & 1 == 0 { + error!("ERROR: OAR1 bit 14 not set (required by reference manual)"); + return Err(Error::Bus); + } + + // For 7-bit mode, verify address is in correct position + if oar1.addmode() as u8 == 0 { // 7-bit mode + let expected_addr = 0x42u16 << 1; // 0x84 + if oar1.add() != expected_addr { + error!("ERROR: OAR1 address mismatch - expected {:#x}, got {:#x}", + expected_addr, oar1.add()); + return Err(Error::Bus); + } + } + + info!("✓ Slave configuration appears correct"); + Ok(()) + } +} + impl<'d, M: Mode> I2c<'d, M, MultiMaster> { /// Listen for incoming I2C address match and return the command type pub fn blocking_listen(&mut self) -> Result { -- cgit From 46c95921f38d1548c9afc84c1c660f690fc49be4 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Mon, 11 Aug 2025 11:40:56 +0200 Subject: stm32/i2c_v1: Fix bugs in slave read and write logic --- embassy-stm32/src/i2c/v1.rs | 170 +++++++++++++++++++++----------------------- 1 file changed, 82 insertions(+), 88 deletions(-) diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index 2bc309258..83f01f51c 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -408,7 +408,7 @@ impl<'d, M: Mode> I2c<'d, M, Master> { } impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { - /// Enhanced slave configuration with proper v1 address setup + /// Slave configuration with v1 address setup pub(crate) fn init_slave(&mut self, config: SlaveAddrConfig) { trace!("i2c v1 slave init: config={:?}", config); @@ -547,52 +547,6 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { } } -// Also add a verification function to check address configuration -impl<'d, M: Mode> I2c<'d, M, MultiMaster> { - /// Verify the slave address configuration is correct - pub fn verify_slave_config(&self) -> Result<(), Error> { - let oar1 = self.info.regs.oar1().read(); - let oar2 = self.info.regs.oar2().read(); - let cr1 = self.info.regs.cr1().read(); - - info!("I2C v1 Slave Configuration Verification:"); - info!(" CR1: {:#x} (PE={})", cr1.0, cr1.pe()); - info!(" OAR1: {:#x}", oar1.0); - info!(" ADD: {:#x}", oar1.add()); - info!(" ADDMODE: {} ({})", oar1.addmode() as u8, - if oar1.addmode() as u8 == 0 { "7-bit" } else { "10-bit" }); - info!(" Bit 14: {}", (oar1.0 >> 14) & 1); - info!(" OAR2: {:#x}", oar2.0); - info!(" ADD2: {:#x}", oar2.add2()); - info!(" ENDUAL: {} ({})", oar2.endual() as u8, - if oar2.endual() as u8 == 0 { "Single" } else { "Dual" }); - - // Check critical requirements - if !cr1.pe() { - error!("ERROR: I2C peripheral not enabled (PE=0)"); - return Err(Error::Bus); - } - - if (oar1.0 >> 14) & 1 == 0 { - error!("ERROR: OAR1 bit 14 not set (required by reference manual)"); - return Err(Error::Bus); - } - - // For 7-bit mode, verify address is in correct position - if oar1.addmode() as u8 == 0 { // 7-bit mode - let expected_addr = 0x42u16 << 1; // 0x84 - if oar1.add() != expected_addr { - error!("ERROR: OAR1 address mismatch - expected {:#x}, got {:#x}", - expected_addr, oar1.add()); - return Err(Error::Bus); - } - } - - info!("✓ Slave configuration appears correct"); - Ok(()) - } -} - impl<'d, M: Mode> I2c<'d, M, MultiMaster> { /// Listen for incoming I2C address match and return the command type pub fn blocking_listen(&mut self) -> Result { @@ -624,24 +578,26 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { // Private implementation methods with Timeout parameter fn blocking_listen_timeout(&mut self, timeout: Timeout) -> Result { trace!("i2c v1 slave: listen_timeout start"); - // Enable address match interrupt for slave mode + + // Disable interrupts for blocking operation self.info.regs.cr2().modify(|w| { - w.set_itevten(true); // Enable event interrupts + w.set_itevten(false); + w.set_iterren(false); }); // Wait for address match (ADDR flag) loop { - let sr1 = Self::check_and_clear_error_flags(self.info)?; + let sr1 = Self::check_slave_error_flags_and_get_sr1(self.info)?; if sr1.addr() { // Address matched! Read SR2 to get direction and clear ADDR let sr2 = self.info.regs.sr2().read(); let direction = if sr2.tra() { - trace!("i2c v1 slave: address match - READ direction (master wants to read from us)"); - SlaveCommandKind::Read // Master wants to read from us (we transmit) + trace!("i2c v1 slave: address match - READ direction"); + SlaveCommandKind::Read } else { - trace!("i2c v1 slave: address match - WRITE direction (master wants to write to us)"); - SlaveCommandKind::Write // Master wants to write to us (we receive) + trace!("i2c v1 slave: address match - WRITE direction"); + SlaveCommandKind::Write }; // Determine which address was matched @@ -672,22 +628,23 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { // Continue sending }, SlaveSendResult::Nacked => { - trace!("i2c v1 slave: byte nacked, stopping transmission"); - break; + bytes_sent += 1; // Count the NACKed byte as sent + trace!("i2c v1 slave: byte nacked by master (normal completion), total_sent={}", bytes_sent); + break; // Normal end of transmission }, SlaveSendResult::Stopped => { trace!("i2c v1 slave: stop condition detected, stopping transmission"); - break; + break; // Master sent STOP } SlaveSendResult::Restart => { trace!("i2c v1 slave: restart detected, stopping transmission"); - break; + break; // Master sent RESTART } } } trace!("i2c v1 slave: respond_to_read_timeout complete, bytes_sent={}", bytes_sent); - Ok(bytes_sent) + Ok(bytes_sent) // Always return success with byte count } fn blocking_respond_to_write_timeout(&mut self, buffer: &mut [u8], timeout: Timeout) -> Result { @@ -749,9 +706,10 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { /// Send a byte in slave transmitter mode and check for ACK/NACK/STOP fn send_byte_or_nack(&mut self, byte: u8, timeout: Timeout) -> Result { trace!("i2c v1 slave: send_byte_or_nack start, byte={:#x}", byte); + // Wait until we're ready for sending (TXE flag set) loop { - let sr1 = Self::check_and_clear_error_flags(self.info)?; + let sr1 = Self::check_slave_error_flags_and_get_sr1(self.info)?; // Check for STOP condition first if sr1.stopf() { @@ -763,14 +721,17 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { // Check for RESTART (new ADDR) if sr1.addr() { trace!("i2c v1 slave: RESTART detected before send"); - // Don't clear ADDR here - let next blocking_listen() handle it return Ok(SlaveSendResult::Restart); } - // Check for NACK (AF flag) - if sr1.af() { + // Check for NACK (AF flag) before writing + let sr1_current = self.info.regs.sr1().read(); + if sr1_current.af() { trace!("i2c v1 slave: NACK detected before send"); - self.info.regs.sr1().modify(|w| w.set_af(false)); + self.info.regs.sr1().write(|reg| { + reg.0 = !0; + reg.set_af(false); + }); return Ok(SlaveSendResult::Nacked); } @@ -785,11 +746,28 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { // Send the byte self.info.regs.dr().write(|w| w.set_dr(byte)); - trace!("i2c v1 slave: byte written to DR, waiting for BTF"); + trace!("i2c v1 slave: byte written to DR, waiting for completion"); - // Wait for byte transfer to complete (BTF flag or error) + // Wait for completion - but be more flexible about what constitutes "completion" + // In slave transmitter mode, we need to detect: + // 1. BTF - byte transfer finished (normal case) + // 2. AF (NACK) - master signals end of transaction + // 3. STOP - master terminates transaction + // 4. ADDR - master starts new transaction (restart) loop { - let sr1 = Self::check_and_clear_error_flags(self.info)?; + // Get current flags without error handling that clears AF + let sr1 = self.info.regs.sr1().read(); + + // Check for NACK FIRST - this is the most likely end condition + if sr1.af() { + trace!("i2c v1 slave: NACK detected after send"); + // Clear the AF flag + self.info.regs.sr1().write(|reg| { + reg.0 = !0; + reg.set_af(false); + }); + return Ok(SlaveSendResult::Nacked); + } // Check for STOP condition if sr1.stopf() { @@ -804,19 +782,21 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { return Ok(SlaveSendResult::Restart); } - // Check for NACK (AF flag) - if sr1.af() { - trace!("i2c v1 slave: NACK detected after send"); - self.info.regs.sr1().modify(|w| w.set_af(false)); - return Ok(SlaveSendResult::Nacked); - } - - // Check for byte transfer finished + // Check for byte transfer finished (normal ACK case) if sr1.btf() { - trace!("i2c v1 slave: BTF set, byte transfer complete"); + trace!("i2c v1 slave: BTF set, byte transfer complete (ACK)"); return Ok(SlaveSendResult::Acked); } + // Check for other error conditions that should be propagated + if sr1.timeout() || sr1.ovr() || sr1.arlo() || sr1.berr() { + // Use the error handling function for these + match Self::check_and_clear_error_flags(self.info) { + Ok(_) => {}, // Shouldn't happen given the flags we checked + Err(e) => return Err(e), + } + } + timeout.check()?; } } @@ -825,31 +805,45 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { fn recv_byte_or_stop(&mut self, timeout: Timeout) -> Result { trace!("i2c v1 slave: recv_byte_or_stop start"); loop { - let sr1 = Self::check_and_clear_error_flags(self.info)?; + let sr1 = Self::check_slave_error_flags_and_get_sr1(self.info)?; - // Check for STOP condition first - if sr1.stopf() { - trace!("i2c v1 slave: STOP detected during receive"); - self.info.regs.cr1().modify(|_w| {}); - return Ok(SlaveReceiveResult::Stop); + // Check for received data FIRST (handles race condition) + if sr1.rxne() { + let byte = self.info.regs.dr().read().dr(); + trace!("i2c v1 slave: received byte={:#x}", byte); + return Ok(SlaveReceiveResult::Byte(byte)); } - // Check for RESTART (new ADDR) + // Check for RESTART (new ADDR) before STOP if sr1.addr() { trace!("i2c v1 slave: RESTART detected during receive"); - // Don't clear ADDR here - let next blocking_listen() handle it return Ok(SlaveReceiveResult::Restart); } - if sr1.rxne() { - let byte = self.info.regs.dr().read().dr(); - trace!("i2c v1 slave: received byte={:#x}", byte); - return Ok(SlaveReceiveResult::Byte(byte)); + // Check for STOP condition LAST + if sr1.stopf() { + trace!("i2c v1 slave: STOP detected during receive"); + self.info.regs.cr1().modify(|_w| {}); + return Ok(SlaveReceiveResult::Stop); } timeout.check()?; } } + + /// Wrapper that treats AF (NACK) as normal protocol behavior in slave mode + fn check_slave_error_flags_and_get_sr1(info: &'static Info) -> Result { + match Self::check_and_clear_error_flags(info) { + Ok(sr1) => Ok(sr1), + Err(Error::Nack) => { + // AF flag was set and cleared by check_and_clear_error_flags + // In slave mode, this is normal protocol behavior, not an error + // Read SR1 again to get current state (AF should now be cleared) + Ok(info.regs.sr1().read()) + }, + Err(other_error) => Err(other_error), // Propagate real errors + } + } } impl<'d, IM: MasterMode> I2c<'d, Async, IM> { -- cgit From 4f7febc34eab0dd5822f313854338997f6dbf617 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Mon, 11 Aug 2025 12:04:38 +0200 Subject: stm32/i2c_v1: Better handling of slave read and write overflow --- embassy-stm32/src/i2c/v1.rs | 64 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index 83f01f51c..9022c2f5d 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -618,14 +618,28 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { fn blocking_respond_to_read_timeout(&mut self, data: &[u8], timeout: Timeout) -> Result { trace!("i2c v1 slave: respond_to_read_timeout start, data_len={}", data.len()); let mut bytes_sent = 0; + let mut data_exhausted = false; - for &byte in data { - trace!("i2c v1 slave: sending byte={:#x} ({})", byte, bytes_sent); - match self.send_byte_or_nack(byte, timeout)? { + loop { + // Determine what byte to send + let byte_to_send = if bytes_sent < data.len() { + // Send real data + data[bytes_sent] + } else { + // Data exhausted - send padding bytes + if !data_exhausted { + trace!("i2c v1 slave: real data exhausted, sending padding bytes"); + data_exhausted = true; + } + 0x00 // Send zeros as padding (or 0xFF, or last byte repeated) + }; + + trace!("i2c v1 slave: sending byte={:#x} ({})", byte_to_send, bytes_sent); + match self.send_byte_or_nack(byte_to_send, timeout)? { SlaveSendResult::Acked => { bytes_sent += 1; trace!("i2c v1 slave: byte acked, total_sent={}", bytes_sent); - // Continue sending + // Continue sending more bytes }, SlaveSendResult::Nacked => { bytes_sent += 1; // Count the NACKed byte as sent @@ -644,13 +658,16 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { } trace!("i2c v1 slave: respond_to_read_timeout complete, bytes_sent={}", bytes_sent); - Ok(bytes_sent) // Always return success with byte count + Ok(bytes_sent) // Return total bytes sent (including padding) } fn blocking_respond_to_write_timeout(&mut self, buffer: &mut [u8], timeout: Timeout) -> Result { trace!("i2c v1 slave: respond_to_write_timeout start, buffer_len={}", buffer.len()); let mut bytes_received = 0; - while bytes_received < buffer.len() { + let buffer_capacity = buffer.len(); + let mut overflow_detected = false; + + while bytes_received < buffer_capacity { match self.recv_byte_or_stop(timeout)? { SlaveReceiveResult::Byte(b) => { trace!("i2c v1 slave: received byte={:#x} ({})", b, bytes_received); @@ -667,8 +684,39 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { }, } } - trace!("i2c v1 slave: respond_to_write_timeout complete, bytes_received={}", bytes_received); - Ok(bytes_received) + + // Handle buffer overflow - continue receiving but discard bytes + if bytes_received >= buffer_capacity { + loop { + match self.recv_byte_or_stop(timeout)? { + SlaveReceiveResult::Byte(b) => { + if !overflow_detected { + trace!("i2c v1 slave: buffer full, discarding excess bytes"); + overflow_detected = true; + } + trace!("i2c v1 slave: discarding overflow byte={:#x}", b); + // Byte is discarded but we still ACK it + }, + SlaveReceiveResult::Stop => { + trace!("i2c v1 slave: stop condition detected after overflow"); + break; + }, + SlaveReceiveResult::Restart => { + trace!("i2c v1 slave: restart detected after overflow"); + break; + }, + } + } + } + + if overflow_detected { + trace!("i2c v1 slave: transaction complete with overflow - received {} bytes, buffer held {}", + bytes_received, buffer_capacity); + } else { + trace!("i2c v1 slave: respond_to_write_timeout complete, bytes_received={}", bytes_received); + } + + Ok(bytes_received.min(buffer_capacity)) // Return stored bytes, not total received } fn determine_matched_address(&self, sr2: stm32_metapac::i2c::regs::Sr2) -> Result { -- cgit From fd7158063d38ece65f6f3f13422b2d744e8a144f Mon Sep 17 00:00:00 2001 From: HybridChild Date: Mon, 11 Aug 2025 19:56:52 +0200 Subject: stm32/i2c_v1: Clean up slave implementation --- embassy-stm32/src/i2c/v1.rs | 1382 +++++++++++++++++++++---------------------- 1 file changed, 665 insertions(+), 717 deletions(-) diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index 9022c2f5d..8f4128b45 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -32,21 +32,6 @@ impl State { } } -#[derive(Debug, PartialEq)] -enum SlaveSendResult { - Acked, // Byte sent and ACK received from master - Nacked, // Byte sent but NACK received (normal end of transmission) - Stopped, // STOP condition detected - Restart, // RESTART condition detected -} - -#[derive(Debug, PartialEq)] -enum SlaveReceiveResult { - Byte(u8), // Data byte received - Stop, // STOP condition detected - Restart, // RESTART condition (new ADDR) detected -} - // /!\ /!\ // /!\ Implementation note! /!\ // /!\ /!\ @@ -387,515 +372,175 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { } } -impl<'d, M: Mode> I2c<'d, M, Master> { - /// Configure the I2C driver for slave operations, allowing for the driver to be used as a slave and a master (multimaster) - pub fn into_slave_multimaster(mut self, slave_addr_config: SlaveAddrConfig) -> I2c<'d, M, MultiMaster> { - let mut slave = I2c { - info: self.info, - state: self.state, - kernel_clock: self.kernel_clock, - tx_dma: self.tx_dma.take(), // Use take() to move ownership - rx_dma: self.rx_dma.take(), // Use take() to move ownership - #[cfg(feature = "time")] - timeout: self.timeout, - _phantom: PhantomData, - _phantom2: PhantomData, - _drop_guard: self._drop_guard, // Move the drop guard - }; - slave.init_slave(slave_addr_config); - slave - } -} - -impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { - /// Slave configuration with v1 address setup - pub(crate) fn init_slave(&mut self, config: SlaveAddrConfig) { - trace!("i2c v1 slave init: config={:?}", config); - - // Disable peripheral for configuration - self.info.regs.cr1().modify(|reg| { - reg.set_pe(false); +impl<'d, IM: MasterMode> I2c<'d, Async, IM> { + async fn write_with_framing(&mut self, address: u8, write_buffer: &[u8], framing: OperationFraming) -> Result<(), Error> { + self.info.regs.cr2().modify(|w| { + // Note: Do not enable the ITBUFEN bit in the I2C_CR2 register if DMA is used for + // reception. + w.set_itbufen(false); + // DMA mode can be enabled for transmission by setting the DMAEN bit in the I2C_CR2 + // register. + w.set_dmaen(true); + // Sending NACK is not necessary (nor possible) for write transfer. + w.set_last(false); }); - - // Configure addresses with proper v1 format - self.configure_addresses(config); - - // Configure general call if requested - if config.general_call { - self.info.regs.cr1().modify(|w| w.set_engc(true)); - trace!("i2c v1 slave: General call enabled"); - } - - // Log final configuration before enabling - let cr1 = self.info.regs.cr1().read(); - let oar1 = self.info.regs.oar1().read(); - let oar2 = self.info.regs.oar2().read(); - trace!("i2c v1 slave: Pre-enable state - CR1={:#x}, OAR1={:#x}, OAR2={:#x}", - cr1.0, oar1.0, oar2.0); - trace!("i2c v1 slave: Address details - OAR1.ADD={:#x}, OAR1.ADDMODE={}, bit14={}", - oar1.add(), oar1.addmode() as u8, (oar1.0 >> 14) & 1); - self.info.regs.cr1().modify(|reg| { - reg.set_pe(true); // Re-enable peripheral - reg.set_ack(true); // Critical for slave to ACK its address + // Sentinel to disable transfer when an error occurs or future is canceled. + // TODO: Generate STOP condition on cancel? + let on_drop = OnDrop::new(|| { + self.info.regs.cr2().modify(|w| { + w.set_dmaen(false); + w.set_iterren(false); + w.set_itevten(false); + }) }); - - // Verify peripheral is enabled and ready - let cr1_final = self.info.regs.cr1().read(); - trace!("i2c v1 slave: Final state - CR1={:#x}, PE={}", cr1_final.0, cr1_final.pe()); - - trace!("i2c v1 slave init complete"); - } - fn configure_oa1(&mut self, addr: Address) { - match addr { - Address::SevenBit(addr) => { - trace!("i2c v1 slave: Setting OA1 7-bit address: input={:#x}", addr); - self.info.regs.oar1().write(|reg| { - // For I2C v1, the 7-bit address goes in bits [7:1] of the ADD field - // The ADD field spans bits [9:0], so we put the address in the correct position - let hw_addr = (addr as u16) << 1; // This puts address in bits [7:1], bit [0] = 0 - reg.set_add(hw_addr); - reg.set_addmode(i2c::vals::Addmode::BIT7); - }); - - // CRITICAL: Set bit 14 as required by the reference manual - // "Bit 14: Should always be kept at 1 by software" - self.info.regs.oar1().modify(|reg| { - reg.0 |= 1 << 14; // Set bit 14 - }); - - let oar1_verify = self.info.regs.oar1().read(); - trace!("i2c v1 slave: OA1 configured - OAR1={:#x}, stored_addr={:#x}, bit14={}", - oar1_verify.0, oar1_verify.add(), (oar1_verify.0 >> 14) & 1); - }, - Address::TenBit(addr) => { - trace!("i2c v1 slave: Setting OA1 10-bit address: {:#x}", addr); - self.info.regs.oar1().write(|reg| { - reg.set_add(addr); // For 10-bit, full address goes in ADD field - reg.set_addmode(i2c::vals::Addmode::BIT10); - }); - - // Set required bit 14 for 10-bit mode too - self.info.regs.oar1().modify(|reg| { - reg.0 |= 1 << 14; // Set bit 14 - }); - - let oar1_verify = self.info.regs.oar1().read(); - trace!("i2c v1 slave: OA1 10-bit configured - OAR1={:#x}, bit14={}", - oar1_verify.0, (oar1_verify.0 >> 14) & 1); + if framing.send_start() { + // Send a START condition + self.info.regs.cr1().modify(|reg| { + reg.set_start(true); + }); + + // Wait until START condition was generated + poll_fn(|cx| { + self.state.waker.register(cx.waker()); + + match Self::check_and_clear_error_flags(self.info) { + Err(e) => Poll::Ready(Err(e)), + Ok(sr1) => { + if sr1.start() { + Poll::Ready(Ok(())) + } else { + // When pending, (re-)enable interrupts to wake us up. + Self::enable_interrupts(self.info); + Poll::Pending + } + } + } + }) + .await?; + + // Check if we were the ones to generate START + if self.info.regs.cr1().read().start() || !self.info.regs.sr2().read().msl() { + return Err(Error::Arbitration); } - } - } - - fn configure_oa2_simple(&mut self, addr: u8) { - trace!("i2c v1 slave: Setting OA2 address: {:#x}", addr); - self.info.regs.oar2().write(|reg| { - // For OA2, the address goes in bits [7:1] of the ADD2 field - reg.set_add2(addr); // ADD2 field automatically handles bits [7:1] placement - reg.set_endual(i2c::vals::Endual::DUAL); // Enable dual addressing - }); - - let oar2_verify = self.info.regs.oar2().read(); - trace!("i2c v1 slave: OA2 configured - OAR2={:#x}, ADD2={:#x}, ENDUAL={}", - oar2_verify.0, oar2_verify.add2(), oar2_verify.endual() as u8); - } - fn configure_addresses(&mut self, config: SlaveAddrConfig) { - match config.addr { - OwnAddresses::OA1(addr) => { - self.configure_oa1(addr); - // Disable OA2 if not needed - self.info.regs.oar2().write(|reg| { - reg.set_endual(i2c::vals::Endual::SINGLE); - }); - }, - OwnAddresses::OA2(oa2) => { - // v1 limitation: ignore mask, only support simple OA2 - if !matches!(oa2.mask, AddrMask::NOMASK) { - // Could log a warning here that masking is ignored in v1 - #[cfg(feature = "defmt")] - warn!("I2C v1 does not support OA2 address masking, ignoring mask setting"); + // Set up current address we're trying to talk to + self.info.regs.dr().write(|reg| reg.set_dr(address << 1)); + + // Wait for the address to be acknowledged + poll_fn(|cx| { + self.state.waker.register(cx.waker()); + + match Self::check_and_clear_error_flags(self.info) { + Err(e) => Poll::Ready(Err(e)), + Ok(sr1) => { + if sr1.addr() { + Poll::Ready(Ok(())) + } else { + // When pending, (re-)enable interrupts to wake us up. + Self::enable_interrupts(self.info); + Poll::Pending + } + } } - - // Must have a default OA1 when using OA2-only mode - // Set OA1 to a reserved address that won't conflict - self.info.regs.oar1().write(|reg| { - reg.set_add(0); // Address 0x00 is reserved, safe to use - reg.set_addmode(i2c::vals::Addmode::BIT7); - }); - - self.configure_oa2_simple(oa2.addr); - }, - OwnAddresses::Both { oa1, oa2 } => { - self.configure_oa1(oa1); - - // Same masking limitation applies - if !matches!(oa2.mask, AddrMask::NOMASK) { - #[cfg(feature = "defmt")] - defmt::warn!("I2C v1 does not support OA2 address masking, ignoring mask setting"); + }) + .await?; + + // Clear condition by reading SR2 + self.info.regs.sr2().read(); + } + + let dma_transfer = unsafe { + // Set the I2C_DR register address in the DMA_SxPAR register. The data will be moved to + // this address from the memory after each TxE event. + let dst = self.info.regs.dr().as_ptr() as *mut u8; + + self.tx_dma.as_mut().unwrap().write(write_buffer, dst, Default::default()) + }; + + // Wait for bytes to be sent, or an error to occur. + let poll_error = poll_fn(|cx| { + self.state.waker.register(cx.waker()); + + match Self::check_and_clear_error_flags(self.info) { + Err(e) => Poll::Ready(Err::<(), Error>(e)), + Ok(_) => { + // When pending, (re-)enable interrupts to wake us up. + Self::enable_interrupts(self.info); + Poll::Pending } - - self.configure_oa2_simple(oa2.addr); } + }); + + // Wait for either the DMA transfer to successfully finish, or an I2C error to occur. + match select(dma_transfer, poll_error).await { + Either::Second(Err(e)) => Err(e), + _ => Ok(()), + }?; + + self.info.regs.cr2().modify(|w| { + w.set_dmaen(false); + }); + + if framing.send_stop() { + // The I2C transfer itself will take longer than the DMA transfer, so wait for that to finish too. + + // 18.3.8 “Master transmitter: In the interrupt routine after the EOT interrupt, disable DMA + // requests then wait for a BTF event before programming the Stop condition.” + poll_fn(|cx| { + self.state.waker.register(cx.waker()); + + match Self::check_and_clear_error_flags(self.info) { + Err(e) => Poll::Ready(Err(e)), + Ok(sr1) => { + if sr1.btf() { + Poll::Ready(Ok(())) + } else { + // When pending, (re-)enable interrupts to wake us up. + Self::enable_interrupts(self.info); + Poll::Pending + } + } + } + }) + .await?; + + self.info.regs.cr1().modify(|w| { + w.set_stop(true); + }); } - - // Configure general call if requested - if config.general_call { - self.info.regs.cr1().modify(|w| w.set_engc(true)); - } - } -} -impl<'d, M: Mode> I2c<'d, M, MultiMaster> { - /// Listen for incoming I2C address match and return the command type - pub fn blocking_listen(&mut self) -> Result { - trace!("i2c v1 slave: blocking_listen start"); - let timeout = self.timeout(); // Get timeout internally - let result = self.blocking_listen_timeout(timeout); - trace!("i2c v1 slave: blocking_listen result={:?}", result); - result + drop(on_drop); + + // Fallthrough is success + Ok(()) } - - /// Respond to master read request (master wants to read from us) - pub fn blocking_respond_to_read(&mut self, data: &[u8]) -> Result { - trace!("i2c v1 slave: blocking_respond_to_read start, data_len={}", data.len()); - let timeout = self.timeout(); // Get timeout internally - let result = self.blocking_respond_to_read_timeout(data, timeout); - trace!("i2c v1 slave: blocking_respond_to_read result={:?}", result); - result + + /// Write. + pub async fn write(&mut self, address: u8, write_buffer: &[u8]) -> Result<(), Error> { + self.write_with_framing(address, write_buffer, OperationFraming::FirstAndLast) + .await?; + + Ok(()) } - - /// Respond to master write request (master wants to write to us) - pub fn blocking_respond_to_write(&mut self, buffer: &mut [u8]) -> Result { - trace!("i2c v1 slave: blocking_respond_to_write start, buffer_len={}", buffer.len()); - let timeout = self.timeout(); // Get timeout internally - let result = self.blocking_respond_to_write_timeout(buffer, timeout); - trace!("i2c v1 slave: blocking_respond_to_write result={:?}", result); - result + + /// Read. + pub async fn read(&mut self, address: u8, read_buffer: &mut [u8]) -> Result<(), Error> { + self.read_with_framing(address, read_buffer, OperationFraming::FirstAndLast) + .await?; + + Ok(()) } - - // Private implementation methods with Timeout parameter - fn blocking_listen_timeout(&mut self, timeout: Timeout) -> Result { - trace!("i2c v1 slave: listen_timeout start"); - - // Disable interrupts for blocking operation - self.info.regs.cr2().modify(|w| { - w.set_itevten(false); - w.set_iterren(false); - }); - - // Wait for address match (ADDR flag) - loop { - let sr1 = Self::check_slave_error_flags_and_get_sr1(self.info)?; - - if sr1.addr() { - // Address matched! Read SR2 to get direction and clear ADDR - let sr2 = self.info.regs.sr2().read(); - let direction = if sr2.tra() { - trace!("i2c v1 slave: address match - READ direction"); - SlaveCommandKind::Read - } else { - trace!("i2c v1 slave: address match - WRITE direction"); - SlaveCommandKind::Write - }; - - // Determine which address was matched - let matched_address = self.determine_matched_address(sr2)?; - trace!("i2c v1 slave: matched address={:?}", matched_address); - - // ADDR is automatically cleared by reading SR1 then SR2 - return Ok(SlaveCommand { - kind: direction, - address: matched_address, - }); - } - - timeout.check()?; - } - } - - fn blocking_respond_to_read_timeout(&mut self, data: &[u8], timeout: Timeout) -> Result { - trace!("i2c v1 slave: respond_to_read_timeout start, data_len={}", data.len()); - let mut bytes_sent = 0; - let mut data_exhausted = false; - - loop { - // Determine what byte to send - let byte_to_send = if bytes_sent < data.len() { - // Send real data - data[bytes_sent] - } else { - // Data exhausted - send padding bytes - if !data_exhausted { - trace!("i2c v1 slave: real data exhausted, sending padding bytes"); - data_exhausted = true; - } - 0x00 // Send zeros as padding (or 0xFF, or last byte repeated) - }; - - trace!("i2c v1 slave: sending byte={:#x} ({})", byte_to_send, bytes_sent); - match self.send_byte_or_nack(byte_to_send, timeout)? { - SlaveSendResult::Acked => { - bytes_sent += 1; - trace!("i2c v1 slave: byte acked, total_sent={}", bytes_sent); - // Continue sending more bytes - }, - SlaveSendResult::Nacked => { - bytes_sent += 1; // Count the NACKed byte as sent - trace!("i2c v1 slave: byte nacked by master (normal completion), total_sent={}", bytes_sent); - break; // Normal end of transmission - }, - SlaveSendResult::Stopped => { - trace!("i2c v1 slave: stop condition detected, stopping transmission"); - break; // Master sent STOP - } - SlaveSendResult::Restart => { - trace!("i2c v1 slave: restart detected, stopping transmission"); - break; // Master sent RESTART - } - } - } - - trace!("i2c v1 slave: respond_to_read_timeout complete, bytes_sent={}", bytes_sent); - Ok(bytes_sent) // Return total bytes sent (including padding) - } - - fn blocking_respond_to_write_timeout(&mut self, buffer: &mut [u8], timeout: Timeout) -> Result { - trace!("i2c v1 slave: respond_to_write_timeout start, buffer_len={}", buffer.len()); - let mut bytes_received = 0; - let buffer_capacity = buffer.len(); - let mut overflow_detected = false; - - while bytes_received < buffer_capacity { - match self.recv_byte_or_stop(timeout)? { - SlaveReceiveResult::Byte(b) => { - trace!("i2c v1 slave: received byte={:#x} ({})", b, bytes_received); - buffer[bytes_received] = b; - bytes_received += 1; - }, - SlaveReceiveResult::Stop => { - trace!("i2c v1 slave: stop condition detected, stopping reception"); - break; - }, - SlaveReceiveResult::Restart => { - trace!("i2c v1 slave: restart detected, stopping reception"); - break; - }, - } - } - - // Handle buffer overflow - continue receiving but discard bytes - if bytes_received >= buffer_capacity { - loop { - match self.recv_byte_or_stop(timeout)? { - SlaveReceiveResult::Byte(b) => { - if !overflow_detected { - trace!("i2c v1 slave: buffer full, discarding excess bytes"); - overflow_detected = true; - } - trace!("i2c v1 slave: discarding overflow byte={:#x}", b); - // Byte is discarded but we still ACK it - }, - SlaveReceiveResult::Stop => { - trace!("i2c v1 slave: stop condition detected after overflow"); - break; - }, - SlaveReceiveResult::Restart => { - trace!("i2c v1 slave: restart detected after overflow"); - break; - }, - } - } - } - - if overflow_detected { - trace!("i2c v1 slave: transaction complete with overflow - received {} bytes, buffer held {}", - bytes_received, buffer_capacity); - } else { - trace!("i2c v1 slave: respond_to_write_timeout complete, bytes_received={}", bytes_received); - } - - Ok(bytes_received.min(buffer_capacity)) // Return stored bytes, not total received - } - - fn determine_matched_address(&self, sr2: stm32_metapac::i2c::regs::Sr2) -> Result { - trace!("i2c v1 slave: determine_matched_address, sr2={:#x}", sr2.0); - // Check for general call first - if sr2.gencall() { - trace!("i2c v1 slave: general call address matched"); - Ok(Address::SevenBit(0x00)) - } else if sr2.dualf() { - // OA2 was matched - verify it's actually enabled - let oar2 = self.info.regs.oar2().read(); - if oar2.endual() != i2c::vals::Endual::DUAL { - error!("i2c v1 slave: OA2 matched but not enabled - hardware inconsistency"); - return Err(Error::Bus); // Hardware inconsistency - } - trace!("i2c v1 slave: OA2 address matched: {:#x}", oar2.add2()); - Ok(Address::SevenBit(oar2.add2())) - } else { - // OA1 was matched - let oar1 = self.info.regs.oar1().read(); - match oar1.addmode() { - i2c::vals::Addmode::BIT7 => { - let addr = (oar1.add() >> 1) as u8; - trace!("i2c v1 slave: OA1 7-bit address matched: {:#x}", addr); - Ok(Address::SevenBit(addr)) - }, - i2c::vals::Addmode::BIT10 => { - trace!("i2c v1 slave: OA1 10-bit address matched: {:#x}", oar1.add()); - Ok(Address::TenBit(oar1.add())) - }, - } - } - } - - /// Send a byte in slave transmitter mode and check for ACK/NACK/STOP - fn send_byte_or_nack(&mut self, byte: u8, timeout: Timeout) -> Result { - trace!("i2c v1 slave: send_byte_or_nack start, byte={:#x}", byte); - - // Wait until we're ready for sending (TXE flag set) - loop { - let sr1 = Self::check_slave_error_flags_and_get_sr1(self.info)?; - - // Check for STOP condition first - if sr1.stopf() { - trace!("i2c v1 slave: STOP detected before send"); - self.info.regs.cr1().modify(|_w| {}); - return Ok(SlaveSendResult::Stopped); - } - - // Check for RESTART (new ADDR) - if sr1.addr() { - trace!("i2c v1 slave: RESTART detected before send"); - return Ok(SlaveSendResult::Restart); - } - - // Check for NACK (AF flag) before writing - let sr1_current = self.info.regs.sr1().read(); - if sr1_current.af() { - trace!("i2c v1 slave: NACK detected before send"); - self.info.regs.sr1().write(|reg| { - reg.0 = !0; - reg.set_af(false); - }); - return Ok(SlaveSendResult::Nacked); - } - - // Check if we can send data - if sr1.txe() { - trace!("i2c v1 slave: TXE ready, sending byte"); - break; // Ready to send - } - - timeout.check()?; - } - - // Send the byte - self.info.regs.dr().write(|w| w.set_dr(byte)); - trace!("i2c v1 slave: byte written to DR, waiting for completion"); - - // Wait for completion - but be more flexible about what constitutes "completion" - // In slave transmitter mode, we need to detect: - // 1. BTF - byte transfer finished (normal case) - // 2. AF (NACK) - master signals end of transaction - // 3. STOP - master terminates transaction - // 4. ADDR - master starts new transaction (restart) - loop { - // Get current flags without error handling that clears AF - let sr1 = self.info.regs.sr1().read(); - - // Check for NACK FIRST - this is the most likely end condition - if sr1.af() { - trace!("i2c v1 slave: NACK detected after send"); - // Clear the AF flag - self.info.regs.sr1().write(|reg| { - reg.0 = !0; - reg.set_af(false); - }); - return Ok(SlaveSendResult::Nacked); - } - - // Check for STOP condition - if sr1.stopf() { - trace!("i2c v1 slave: STOP detected after send"); - self.info.regs.cr1().modify(|_w| {}); - return Ok(SlaveSendResult::Stopped); - } - - // Check for RESTART (new ADDR) - if sr1.addr() { - trace!("i2c v1 slave: RESTART detected after send"); - return Ok(SlaveSendResult::Restart); - } - - // Check for byte transfer finished (normal ACK case) - if sr1.btf() { - trace!("i2c v1 slave: BTF set, byte transfer complete (ACK)"); - return Ok(SlaveSendResult::Acked); - } - - // Check for other error conditions that should be propagated - if sr1.timeout() || sr1.ovr() || sr1.arlo() || sr1.berr() { - // Use the error handling function for these - match Self::check_and_clear_error_flags(self.info) { - Ok(_) => {}, // Shouldn't happen given the flags we checked - Err(e) => return Err(e), - } - } - - timeout.check()?; - } - } - - /// Receive a byte in slave receiver mode or detect STOP condition - fn recv_byte_or_stop(&mut self, timeout: Timeout) -> Result { - trace!("i2c v1 slave: recv_byte_or_stop start"); - loop { - let sr1 = Self::check_slave_error_flags_and_get_sr1(self.info)?; - - // Check for received data FIRST (handles race condition) - if sr1.rxne() { - let byte = self.info.regs.dr().read().dr(); - trace!("i2c v1 slave: received byte={:#x}", byte); - return Ok(SlaveReceiveResult::Byte(byte)); - } - - // Check for RESTART (new ADDR) before STOP - if sr1.addr() { - trace!("i2c v1 slave: RESTART detected during receive"); - return Ok(SlaveReceiveResult::Restart); - } - - // Check for STOP condition LAST - if sr1.stopf() { - trace!("i2c v1 slave: STOP detected during receive"); - self.info.regs.cr1().modify(|_w| {}); - return Ok(SlaveReceiveResult::Stop); - } - - timeout.check()?; - } - } - - /// Wrapper that treats AF (NACK) as normal protocol behavior in slave mode - fn check_slave_error_flags_and_get_sr1(info: &'static Info) -> Result { - match Self::check_and_clear_error_flags(info) { - Ok(sr1) => Ok(sr1), - Err(Error::Nack) => { - // AF flag was set and cleared by check_and_clear_error_flags - // In slave mode, this is normal protocol behavior, not an error - // Read SR1 again to get current state (AF should now be cleared) - Ok(info.regs.sr1().read()) - }, - Err(other_error) => Err(other_error), // Propagate real errors - } - } -} - -impl<'d, IM: MasterMode> I2c<'d, Async, IM> { - async fn write_with_framing(&mut self, address: u8, write_buffer: &[u8], framing: OperationFraming) -> Result<(), Error> { + + async fn read_with_framing(&mut self, address: u8, read_buffer: &mut [u8], framing: OperationFraming) -> Result<(), Error> { + if read_buffer.is_empty() { + return Err(Error::Overrun); + } + + // Some branches below depend on whether the buffer contains only a single byte. + let single_byte = read_buffer.len() == 1; + self.info.regs.cr2().modify(|w| { // Note: Do not enable the ITBUFEN bit in the I2C_CR2 register if DMA is used for // reception. @@ -903,8 +548,10 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { // DMA mode can be enabled for transmission by setting the DMAEN bit in the I2C_CR2 // register. w.set_dmaen(true); - // Sending NACK is not necessary (nor possible) for write transfer. - w.set_last(false); + // If, in the I2C_CR2 register, the LAST bit is set, I2C automatically sends a NACK + // after the next byte following EOT_1. The user can generate a Stop condition in + // the DMA Transfer Complete interrupt routine if enabled. + w.set_last(framing.send_nack() && !single_byte); }); // Sentinel to disable transfer when an error occurs or future is canceled. @@ -918,9 +565,10 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { }); if framing.send_start() { - // Send a START condition + // Send a START condition and set ACK bit self.info.regs.cr1().modify(|reg| { reg.set_start(true); + reg.set_ack(true); }); // Wait until START condition was generated @@ -948,7 +596,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { } // Set up current address we're trying to talk to - self.info.regs.dr().write(|reg| reg.set_dr(address << 1)); + self.info.regs.dr().write(|reg| reg.set_dr((address << 1) + 1)); // Wait for the address to be acknowledged poll_fn(|cx| { @@ -969,25 +617,50 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { }) .await?; + // 18.3.8: When a single byte must be received: the NACK must be programmed during EV6 + // event, i.e. program ACK=0 when ADDR=1, before clearing ADDR flag. + if framing.send_nack() && single_byte { + self.info.regs.cr1().modify(|w| { + w.set_ack(false); + }); + } + // Clear condition by reading SR2 self.info.regs.sr2().read(); + } else { + // Before starting reception of single byte (but without START condition, i.e. in case + // of merged operations), program NACK to emit at end of this byte. + if framing.send_nack() && single_byte { + self.info.regs.cr1().modify(|w| { + w.set_ack(false); + }); + } + } + + // 18.3.8: When a single byte must be received: [snip] Then the user can program the STOP + // condition either after clearing ADDR flag, or in the DMA Transfer Complete interrupt + // routine. + if framing.send_stop() && single_byte { + self.info.regs.cr1().modify(|w| { + w.set_stop(true); + }); } let dma_transfer = unsafe { - // Set the I2C_DR register address in the DMA_SxPAR register. The data will be moved to - // this address from the memory after each TxE event. - let dst = self.info.regs.dr().as_ptr() as *mut u8; + // Set the I2C_DR register address in the DMA_SxPAR register. The data will be moved + // from this address from the memory after each RxE event. + let src = self.info.regs.dr().as_ptr() as *mut u8; - self.tx_dma.as_mut().unwrap().write(write_buffer, dst, Default::default()) + self.rx_dma.as_mut().unwrap().read(src, read_buffer, Default::default()) }; - // Wait for bytes to be sent, or an error to occur. + // Wait for bytes to be received, or an error to occur. let poll_error = poll_fn(|cx| { self.state.waker.register(cx.waker()); match Self::check_and_clear_error_flags(self.info) { Err(e) => Poll::Ready(Err::<(), Error>(e)), - Ok(_) => { + _ => { // When pending, (re-)enable interrupts to wake us up. Self::enable_interrupts(self.info); Poll::Pending @@ -995,7 +668,6 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { } }); - // Wait for either the DMA transfer to successfully finish, or an I2C error to occur. match select(dma_transfer, poll_error).await { Either::Second(Err(e)) => Err(e), _ => Ok(()), @@ -1005,29 +677,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { w.set_dmaen(false); }); - if framing.send_stop() { - // The I2C transfer itself will take longer than the DMA transfer, so wait for that to finish too. - - // 18.3.8 “Master transmitter: In the interrupt routine after the EOT interrupt, disable DMA - // requests then wait for a BTF event before programming the Stop condition.” - poll_fn(|cx| { - self.state.waker.register(cx.waker()); - - match Self::check_and_clear_error_flags(self.info) { - Err(e) => Poll::Ready(Err(e)), - Ok(sr1) => { - if sr1.btf() { - Poll::Ready(Ok(())) - } else { - // When pending, (re-)enable interrupts to wake us up. - Self::enable_interrupts(self.info); - Poll::Pending - } - } - } - }) - .await?; - + if framing.send_stop() && !single_byte { self.info.regs.cr1().modify(|w| { w.set_stop(true); }); @@ -1039,207 +689,505 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { Ok(()) } - /// Write. - pub async fn write(&mut self, address: u8, write_buffer: &[u8]) -> Result<(), Error> { - self.write_with_framing(address, write_buffer, OperationFraming::FirstAndLast) - .await?; + /// Write, restart, read. + pub async fn write_read(&mut self, address: u8, write_buffer: &[u8], read_buffer: &mut [u8]) -> Result<(), Error> { + // Check empty read buffer before starting transaction. Otherwise, we would not generate the + // stop condition below. + if read_buffer.is_empty() { + return Err(Error::Overrun); + } - Ok(()) + self.write_with_framing(address, write_buffer, OperationFraming::First).await?; + self.read_with_framing(address, read_buffer, OperationFraming::FirstAndLast).await } - /// Read. - pub async fn read(&mut self, address: u8, read_buffer: &mut [u8]) -> Result<(), Error> { - self.read_with_framing(address, read_buffer, OperationFraming::FirstAndLast) - .await?; + /// Transaction with operations. + /// + /// Consecutive operations of same type are merged. See [transaction contract] for details. + /// + /// [transaction contract]: embedded_hal_1::i2c::I2c::transaction + pub async fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> { + for (op, framing) in assign_operation_framing(operations)? { + match op { + Operation::Read(read_buffer) => self.read_with_framing(address, read_buffer, framing).await?, + Operation::Write(write_buffer) => self.write_with_framing(address, write_buffer, framing).await?, + } + } Ok(()) } +} - async fn read_with_framing(&mut self, address: u8, read_buffer: &mut [u8], framing: OperationFraming) -> Result<(), Error> { - if read_buffer.is_empty() { - return Err(Error::Overrun); - } - - // Some branches below depend on whether the buffer contains only a single byte. - let single_byte = read_buffer.len() == 1; - - self.info.regs.cr2().modify(|w| { - // Note: Do not enable the ITBUFEN bit in the I2C_CR2 register if DMA is used for - // reception. - w.set_itbufen(false); - // DMA mode can be enabled for transmission by setting the DMAEN bit in the I2C_CR2 - // register. - w.set_dmaen(true); - // If, in the I2C_CR2 register, the LAST bit is set, I2C automatically sends a NACK - // after the next byte following EOT_1. The user can generate a Stop condition in - // the DMA Transfer Complete interrupt routine if enabled. - w.set_last(framing.send_nack() && !single_byte); - }); - - // Sentinel to disable transfer when an error occurs or future is canceled. - // TODO: Generate STOP condition on cancel? - let on_drop = OnDrop::new(|| { - self.info.regs.cr2().modify(|w| { - w.set_dmaen(false); - w.set_iterren(false); - w.set_itevten(false); - }) - }); - - if framing.send_start() { - // Send a START condition and set ACK bit - self.info.regs.cr1().modify(|reg| { - reg.set_start(true); - reg.set_ack(true); - }); +/// Result of attempting to send a byte in slave transmitter mode +#[derive(Debug, PartialEq)] +enum TransmitResult { + /// Byte sent and ACKed by master - continue transmission + Acknowledged, + /// Byte sent but NACKed by master - normal end of read transaction + NotAcknowledged, + /// STOP condition detected - master terminated transaction + Stopped, + /// RESTART condition detected - master starting new transaction + Restarted, +} - // Wait until START condition was generated - poll_fn(|cx| { - self.state.waker.register(cx.waker()); +/// Result of attempting to receive a byte in slave receiver mode +#[derive(Debug, PartialEq)] +enum ReceiveResult { + /// Data byte successfully received + Data(u8), + /// STOP condition detected - end of write transaction + Stopped, + /// RESTART condition detected - master starting new transaction + Restarted, +} - match Self::check_and_clear_error_flags(self.info) { - Err(e) => Poll::Ready(Err(e)), - Ok(sr1) => { - if sr1.start() { - Poll::Ready(Ok(())) - } else { - // When pending, (re-)enable interrupts to wake us up. - Self::enable_interrupts(self.info); - Poll::Pending - } - } - } - }) - .await?; +impl<'d, M: Mode> I2c<'d, M, Master> { + /// Configure the I2C driver for slave operations, allowing for the driver to be used as a slave and a master (multimaster) + pub fn into_slave_multimaster(mut self, slave_addr_config: SlaveAddrConfig) -> I2c<'d, M, MultiMaster> { + let mut slave = I2c { + info: self.info, + state: self.state, + kernel_clock: self.kernel_clock, + tx_dma: self.tx_dma.take(), // Use take() to move ownership + rx_dma: self.rx_dma.take(), // Use take() to move ownership + #[cfg(feature = "time")] + timeout: self.timeout, + _phantom: PhantomData, + _phantom2: PhantomData, + _drop_guard: self._drop_guard, // Move the drop guard + }; + slave.init_slave(slave_addr_config); + slave + } +} - // Check if we were the ones to generate START - if self.info.regs.cr1().read().start() || !self.info.regs.sr2().read().msl() { - return Err(Error::Arbitration); +impl<'d, M: Mode> I2c<'d, M, MultiMaster> { + /// Listen for incoming I2C address match and return the command type + /// + /// This method blocks until the slave address is matched by a master. + /// Returns the command type (Read/Write) and the matched address. + pub fn blocking_listen(&mut self) -> Result { + trace!("I2C slave: listening for address match"); + let result = self.blocking_listen_with_timeout(self.timeout()); + trace!("I2C slave: listen result={:?}", result); + result + } + + /// Respond to a master read request by transmitting data + /// + /// Sends the provided data to the master. If the master requests more bytes + /// than available, padding bytes (0x00) are sent until the master terminates + /// the transaction with NACK. + /// + /// Returns the total number of bytes transmitted (including padding). + pub fn blocking_respond_to_read(&mut self, data: &[u8]) -> Result { + trace!("I2C slave: responding to read, data_len={}", data.len()); + let result = self.transmit_to_master(data, self.timeout()); + trace!("I2C slave: read response complete, result={:?}", result); + result + } + + /// Respond to a master write request by receiving data + /// + /// Receives data from the master into the provided buffer. If the master + /// sends more bytes than the buffer can hold, excess bytes are acknowledged + /// but discarded. + /// + /// Returns the number of bytes stored in the buffer (not total received). + pub fn blocking_respond_to_write(&mut self, buffer: &mut [u8]) -> Result { + trace!("I2C slave: responding to write, buffer_len={}", buffer.len()); + let result = self.receive_from_master(buffer, self.timeout()); + trace!("I2C slave: write response complete, result={:?}", result); + result + } + + // Private implementation methods + + /// Wait for address match and determine transaction type + fn blocking_listen_with_timeout(&mut self, timeout: Timeout) -> Result { + // Ensure interrupts are disabled for blocking operation + self.disable_i2c_interrupts(); + + // Wait for address match (ADDR flag) + loop { + let sr1 = Self::read_status_and_handle_errors(self.info)?; + + if sr1.addr() { + // Address matched - read SR2 to get direction and clear ADDR flag + let sr2 = self.info.regs.sr2().read(); + let direction = if sr2.tra() { + SlaveCommandKind::Read + } else { + SlaveCommandKind::Write + }; + + let matched_address = self.decode_matched_address(sr2)?; + trace!("I2C slave: address matched, direction={:?}, addr={:?}", direction, matched_address); + + return Ok(SlaveCommand { + kind: direction, + address: matched_address, + }); } - - // Set up current address we're trying to talk to - self.info.regs.dr().write(|reg| reg.set_dr((address << 1) + 1)); - - // Wait for the address to be acknowledged - poll_fn(|cx| { - self.state.waker.register(cx.waker()); - - match Self::check_and_clear_error_flags(self.info) { - Err(e) => Poll::Ready(Err(e)), - Ok(sr1) => { - if sr1.addr() { - Poll::Ready(Ok(())) - } else { - // When pending, (re-)enable interrupts to wake us up. - Self::enable_interrupts(self.info); - Poll::Pending - } - } + + timeout.check()?; + } + } + + /// Transmit data to master in response to read request + fn transmit_to_master(&mut self, data: &[u8], timeout: Timeout) -> Result { + let mut bytes_transmitted = 0; + + loop { + // Determine next byte to send + let byte_to_send = if bytes_transmitted < data.len() { + data[bytes_transmitted] + } else { + 0x00 // Send padding bytes when data is exhausted + }; + + // Attempt to send the byte + match self.transmit_byte(byte_to_send, timeout)? { + TransmitResult::Acknowledged => { + bytes_transmitted += 1; + // Continue transmission + }, + TransmitResult::NotAcknowledged => { + bytes_transmitted += 1; // Count the NACKed byte + break; // Normal end of read transaction + }, + TransmitResult::Stopped | TransmitResult::Restarted => { + break; // Transaction terminated by master } - }) - .await?; - - // 18.3.8: When a single byte must be received: the NACK must be programmed during EV6 - // event, i.e. program ACK=0 when ADDR=1, before clearing ADDR flag. - if framing.send_nack() && single_byte { - self.info.regs.cr1().modify(|w| { - w.set_ack(false); - }); } - - // Clear condition by reading SR2 - self.info.regs.sr2().read(); - } else { - // Before starting reception of single byte (but without START condition, i.e. in case - // of merged operations), program NACK to emit at end of this byte. - if framing.send_nack() && single_byte { - self.info.regs.cr1().modify(|w| { - w.set_ack(false); - }); + } + + Ok(bytes_transmitted) + } + + /// Receive data from master during write request + fn receive_from_master(&mut self, buffer: &mut [u8], timeout: Timeout) -> Result { + let mut bytes_stored = 0; + + // Receive bytes that fit in buffer + while bytes_stored < buffer.len() { + match self.receive_byte(timeout)? { + ReceiveResult::Data(byte) => { + buffer[bytes_stored] = byte; + bytes_stored += 1; + }, + ReceiveResult::Stopped | ReceiveResult::Restarted => { + return Ok(bytes_stored); + }, } } - - // 18.3.8: When a single byte must be received: [snip] Then the user can program the STOP - // condition either after clearing ADDR flag, or in the DMA Transfer Complete interrupt - // routine. - if framing.send_stop() && single_byte { - self.info.regs.cr1().modify(|w| { - w.set_stop(true); - }); + + // Handle buffer overflow by discarding excess bytes + if bytes_stored == buffer.len() { + trace!("I2C slave: buffer full, discarding excess bytes"); + self.discard_excess_bytes(timeout)?; } - - let dma_transfer = unsafe { - // Set the I2C_DR register address in the DMA_SxPAR register. The data will be moved - // from this address from the memory after each RxE event. - let src = self.info.regs.dr().as_ptr() as *mut u8; - - self.rx_dma.as_mut().unwrap().read(src, read_buffer, Default::default()) - }; - - // Wait for bytes to be received, or an error to occur. - let poll_error = poll_fn(|cx| { - self.state.waker.register(cx.waker()); - - match Self::check_and_clear_error_flags(self.info) { - Err(e) => Poll::Ready(Err::<(), Error>(e)), - _ => { - // When pending, (re-)enable interrupts to wake us up. - Self::enable_interrupts(self.info); - Poll::Pending - } + + Ok(bytes_stored) + } + + /// Discard excess bytes when buffer is full + fn discard_excess_bytes(&mut self, timeout: Timeout) -> Result<(), Error> { + loop { + match self.receive_byte(timeout)? { + ReceiveResult::Data(_) => { + // Byte received and ACKed, but discarded + continue; + }, + ReceiveResult::Stopped | ReceiveResult::Restarted => { + break; // Transaction completed + }, } - }); - - match select(dma_transfer, poll_error).await { - Either::Second(Err(e)) => Err(e), - _ => Ok(()), - }?; - - self.info.regs.cr2().modify(|w| { - w.set_dmaen(false); - }); - - if framing.send_stop() && !single_byte { - self.info.regs.cr1().modify(|w| { - w.set_stop(true); - }); } - - drop(on_drop); - - // Fallthrough is success Ok(()) } - - /// Write, restart, read. - pub async fn write_read(&mut self, address: u8, write_buffer: &[u8], read_buffer: &mut [u8]) -> Result<(), Error> { - // Check empty read buffer before starting transaction. Otherwise, we would not generate the - // stop condition below. - if read_buffer.is_empty() { - return Err(Error::Overrun); + + /// Send a single byte and wait for master's response + fn transmit_byte(&mut self, byte: u8, timeout: Timeout) -> Result { + // Wait for transmit buffer ready + self.wait_for_transmit_ready(timeout)?; + + // Send the byte + self.info.regs.dr().write(|w| w.set_dr(byte)); + + // Wait for transmission completion or master response + self.wait_for_transmit_completion(timeout) + } + + /// Wait until transmit buffer is ready (TXE flag set) + fn wait_for_transmit_ready(&mut self, timeout: Timeout) -> Result<(), Error> { + loop { + let sr1 = Self::read_status_and_handle_errors(self.info)?; + + // Check for early termination conditions + if let Some(result) = Self::check_early_termination(sr1) { + return Err(self.handle_early_termination(result)); + } + + if sr1.txe() { + return Ok(()); // Ready to transmit + } + + timeout.check()?; } - - self.write_with_framing(address, write_buffer, OperationFraming::First).await?; - self.read_with_framing(address, read_buffer, OperationFraming::FirstAndLast).await } - - /// Transaction with operations. - /// - /// Consecutive operations of same type are merged. See [transaction contract] for details. - /// - /// [transaction contract]: embedded_hal_1::i2c::I2c::transaction - pub async fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> { - for (op, framing) in assign_operation_framing(operations)? { - match op { - Operation::Read(read_buffer) => self.read_with_framing(address, read_buffer, framing).await?, - Operation::Write(write_buffer) => self.write_with_framing(address, write_buffer, framing).await?, + + /// Wait for byte transmission completion or master response + fn wait_for_transmit_completion(&mut self, timeout: Timeout) -> Result { + loop { + let sr1 = self.info.regs.sr1().read(); + + // Check flags in priority order + if sr1.af() { + self.clear_acknowledge_failure(); + return Ok(TransmitResult::NotAcknowledged); } + + if sr1.btf() { + return Ok(TransmitResult::Acknowledged); + } + + if sr1.stopf() { + self.clear_stop_flag(); + return Ok(TransmitResult::Stopped); + } + + if sr1.addr() { + return Ok(TransmitResult::Restarted); + } + + // Check for other error conditions + self.check_for_hardware_errors(sr1)?; + + timeout.check()?; + } + } + + /// Receive a single byte or detect transaction termination + fn receive_byte(&mut self, timeout: Timeout) -> Result { + loop { + let sr1 = Self::read_status_and_handle_errors(self.info)?; + + // Check for received data first (prioritize data over control signals) + if sr1.rxne() { + let byte = self.info.regs.dr().read().dr(); + return Ok(ReceiveResult::Data(byte)); + } + + // Check for transaction termination + if sr1.addr() { + return Ok(ReceiveResult::Restarted); + } + + if sr1.stopf() { + self.clear_stop_flag(); + return Ok(ReceiveResult::Stopped); + } + + timeout.check()?; + } + } + + /// Determine which slave address was matched based on SR2 flags + fn decode_matched_address(&self, sr2: i2c::regs::Sr2) -> Result { + if sr2.gencall() { + Ok(Address::SevenBit(0x00)) // General call address + } else if sr2.dualf() { + // OA2 (secondary address) was matched + let oar2 = self.info.regs.oar2().read(); + if oar2.endual() != i2c::vals::Endual::DUAL { + return Err(Error::Bus); // Hardware inconsistency + } + Ok(Address::SevenBit(oar2.add2())) + } else { + // OA1 (primary address) was matched + let oar1 = self.info.regs.oar1().read(); + match oar1.addmode() { + i2c::vals::Addmode::BIT7 => { + let addr = (oar1.add() >> 1) as u8; + Ok(Address::SevenBit(addr)) + }, + i2c::vals::Addmode::BIT10 => { + Ok(Address::TenBit(oar1.add())) + }, + } + } + } + + // Helper methods for hardware interaction + + /// Read status register and handle I2C errors (except NACK in slave mode) + fn read_status_and_handle_errors(info: &'static Info) -> Result { + match Self::check_and_clear_error_flags(info) { + Ok(sr1) => Ok(sr1), + Err(Error::Nack) => { + // In slave mode, NACK is normal protocol behavior, not an error + Ok(info.regs.sr1().read()) + }, + Err(other_error) => Err(other_error), + } + } + + /// Check for conditions that cause early termination of operations + fn check_early_termination(sr1: i2c::regs::Sr1) -> Option { + if sr1.stopf() { + Some(TransmitResult::Stopped) + } else if sr1.addr() { + Some(TransmitResult::Restarted) + } else if sr1.af() { + Some(TransmitResult::NotAcknowledged) + } else { + None + } + } + + /// Convert early termination to appropriate error + fn handle_early_termination(&mut self, result: TransmitResult) -> Error { + match result { + TransmitResult::Stopped => { + self.clear_stop_flag(); + Error::Bus // Unexpected STOP during setup + }, + TransmitResult::Restarted => { + Error::Bus // Unexpected RESTART during setup + }, + TransmitResult::NotAcknowledged => { + self.clear_acknowledge_failure(); + Error::Bus // Unexpected NACK during setup + }, + TransmitResult::Acknowledged => { + unreachable!() // This should never be passed to this function + } + } + } + + /// Check for hardware-level I2C errors during transmission + fn check_for_hardware_errors(&self, sr1: i2c::regs::Sr1) -> Result<(), Error> { + if sr1.timeout() || sr1.ovr() || sr1.arlo() || sr1.berr() { + // Delegate to existing error handling + Self::check_and_clear_error_flags(self.info)?; } - Ok(()) } + + /// Disable I2C event and error interrupts for blocking operations + fn disable_i2c_interrupts(&mut self) { + self.info.regs.cr2().modify(|w| { + w.set_itevten(false); + w.set_iterren(false); + }); + } + + /// Clear the acknowledge failure flag + fn clear_acknowledge_failure(&mut self) { + self.info.regs.sr1().write(|reg| { + reg.0 = !0; + reg.set_af(false); + }); + } + + /// Clear the stop condition flag + fn clear_stop_flag(&mut self) { + self.info.regs.cr1().modify(|_w| {}); + } } +// Address configuration methods +impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { + /// Initialize slave mode with address configuration + pub(crate) fn init_slave(&mut self, config: SlaveAddrConfig) { + trace!("I2C slave: initializing with config={:?}", config); + + // Disable peripheral for configuration + self.info.regs.cr1().modify(|reg| reg.set_pe(false)); + + // Configure slave addresses + self.apply_address_configuration(config); + + // Enable peripheral with slave settings + self.info.regs.cr1().modify(|reg| { + reg.set_pe(true); + reg.set_ack(true); // Enable acknowledgment for slave mode + }); + + trace!("I2C slave: initialization complete"); + } + + /// Apply the complete address configuration for slave mode + fn apply_address_configuration(&mut self, config: SlaveAddrConfig) { + match config.addr { + OwnAddresses::OA1(addr) => { + self.configure_primary_address(addr); + self.disable_secondary_address(); + }, + OwnAddresses::OA2(oa2) => { + self.configure_default_primary_address(); + self.configure_secondary_address(oa2.addr); // v1 ignores mask + }, + OwnAddresses::Both { oa1, oa2 } => { + self.configure_primary_address(oa1); + self.configure_secondary_address(oa2.addr); // v1 ignores mask + } + } + + // Configure general call detection + if config.general_call { + self.info.regs.cr1().modify(|w| w.set_engc(true)); + } + } + + /// Configure the primary address (OA1) register + fn configure_primary_address(&mut self, addr: Address) { + match addr { + Address::SevenBit(addr) => { + self.info.regs.oar1().write(|reg| { + let hw_addr = (addr as u16) << 1; // Address in bits [7:1] + reg.set_add(hw_addr); + reg.set_addmode(i2c::vals::Addmode::BIT7); + }); + }, + Address::TenBit(addr) => { + self.info.regs.oar1().write(|reg| { + reg.set_add(addr); + reg.set_addmode(i2c::vals::Addmode::BIT10); + }); + } + } + + // Set required bit 14 as per reference manual + self.info.regs.oar1().modify(|reg| reg.0 |= 1 << 14); + } + + /// Configure the secondary address (OA2) register + fn configure_secondary_address(&mut self, addr: u8) { + self.info.regs.oar2().write(|reg| { + reg.set_add2(addr); + reg.set_endual(i2c::vals::Endual::DUAL); + }); + } + + /// Set a default primary address when using OA2-only mode + fn configure_default_primary_address(&mut self) { + self.info.regs.oar1().write(|reg| { + reg.set_add(0); // Reserved address, safe to use + reg.set_addmode(i2c::vals::Addmode::BIT7); + }); + self.info.regs.oar1().modify(|reg| reg.0 |= 1 << 14); + } + + /// Disable secondary address when not needed + fn disable_secondary_address(&mut self) { + self.info.regs.oar2().write(|reg| { + reg.set_endual(i2c::vals::Endual::SINGLE); + }); + } +} /// Timing configuration for I2C v1 hardware /// -- cgit From 03496864d765590744582c00a1b8f010d5014a0c Mon Sep 17 00:00:00 2001 From: HybridChild Date: Tue, 12 Aug 2025 06:49:00 +0200 Subject: stm32/i2c_v1: Add handling of zero-length read --- embassy-stm32/src/i2c/v1.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index 8f4128b45..9b5a524df 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -783,6 +783,13 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { /// Returns the total number of bytes transmitted (including padding). pub fn blocking_respond_to_read(&mut self, data: &[u8]) -> Result { trace!("I2C slave: responding to read, data_len={}", data.len()); + + // Check for zero-length read BEFORE any transmission setup + if let Some(zero_length_result) = self.detect_zero_length_read(self.timeout())? { + trace!("I2C slave: zero-length read detected"); + return Ok(zero_length_result); + } + let result = self.transmit_to_master(data, self.timeout()); trace!("I2C slave: read response complete, result={:?}", result); result @@ -891,6 +898,61 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { Ok(bytes_stored) } + + /// Detect zero-length read pattern early + /// + /// Zero-length reads occur when a master sends START+ADDR+R followed immediately + /// by NACK+STOP without wanting any data. This must be detected before attempting + /// to transmit any bytes to avoid SDA line issues. + fn detect_zero_length_read(&mut self, _timeout: Timeout) -> Result, Error> { + // Quick check for immediate termination signals + let sr1 = self.info.regs.sr1().read(); + + // Check for immediate NACK (fastest zero-length pattern) + if sr1.af() { + self.clear_acknowledge_failure(); + return Ok(Some(0)); + } + + // Check for immediate STOP (alternative zero-length pattern) + if sr1.stopf() { + self.clear_stop_flag(); + return Ok(Some(0)); + } + + // Give a brief window for master to send termination signals + // This handles masters that have slight delays between address ACK and NACK + const ZERO_LENGTH_DETECTION_CYCLES: u32 = 100; // ~5-10µs window + + for _ in 0..ZERO_LENGTH_DETECTION_CYCLES { + let sr1 = self.info.regs.sr1().read(); + + // Immediate NACK indicates zero-length read + if sr1.af() { + self.clear_acknowledge_failure(); + return Ok(Some(0)); + } + + // Immediate STOP indicates zero-length read + if sr1.stopf() { + self.clear_stop_flag(); + return Ok(Some(0)); + } + + // If TXE becomes ready, master is waiting for data - not zero-length + if sr1.txe() { + return Ok(None); // Proceed with normal transmission + } + + // If RESTART detected, handle as zero-length + if sr1.addr() { + return Ok(Some(0)); + } + } + + // No zero-length pattern detected within the window + Ok(None) + } /// Discard excess bytes when buffer is full fn discard_excess_bytes(&mut self, timeout: Timeout) -> Result<(), Error> { -- cgit From 0ab366e28af266795106cb254b4a0d63f723d476 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Wed, 13 Aug 2025 20:11:55 +0200 Subject: stm32/i2c_v1: Add async slave implementation --- embassy-stm32/src/i2c/v1.rs | 359 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 339 insertions(+), 20 deletions(-) diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index 9b5a524df..10a307396 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -362,14 +362,30 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { } // Async - + + /// Can be used by both blocking and async implementations #[inline] // pretty sure this should always be inlined - fn enable_interrupts(info: &'static Info) -> () { - info.regs.cr2().modify(|w| { - w.set_iterren(true); - w.set_itevten(true); + fn enable_interrupts(info: &'static Info) { + // The interrupt handler disables interrupts globally, so we need to re-enable them + // This must be done in a critical section to avoid races + critical_section::with(|_| { + info.regs.cr2().modify(|w| { + w.set_iterren(true); + w.set_itevten(true); + }); }); + trace!("I2C slave: safely enabled interrupts"); } + + /// Can be used by both blocking and async implementations + fn clear_stop_flag(info: &'static Info) { + trace!("I2C slave: clearing STOPF flag (v1 sequence)"); + // v1 requires: READ SR1 then WRITE CR1 to clear STOPF + let _ = info.regs.sr1().read(); + info.regs.cr1().modify(|_| {}); // Dummy write to clear STOPF + trace!("I2C slave: STOPF flag cleared"); + } + } impl<'d, IM: MasterMode> I2c<'d, Async, IM> { @@ -829,7 +845,8 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { SlaveCommandKind::Write }; - let matched_address = self.decode_matched_address(sr2)?; + // Use the static method instead of the instance method + let matched_address = Self::decode_matched_address(sr2, self.info)?; trace!("I2C slave: address matched, direction={:?}, addr={:?}", direction, matched_address); return Ok(SlaveCommand { @@ -916,13 +933,13 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { // Check for immediate STOP (alternative zero-length pattern) if sr1.stopf() { - self.clear_stop_flag(); + Self::clear_stop_flag(self.info); return Ok(Some(0)); } // Give a brief window for master to send termination signals // This handles masters that have slight delays between address ACK and NACK - const ZERO_LENGTH_DETECTION_CYCLES: u32 = 100; // ~5-10µs window + const ZERO_LENGTH_DETECTION_CYCLES: u32 = 20; // ~5-10µs window for _ in 0..ZERO_LENGTH_DETECTION_CYCLES { let sr1 = self.info.regs.sr1().read(); @@ -935,7 +952,7 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { // Immediate STOP indicates zero-length read if sr1.stopf() { - self.clear_stop_flag(); + Self::clear_stop_flag(self.info); return Ok(Some(0)); } @@ -1016,7 +1033,7 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { } if sr1.stopf() { - self.clear_stop_flag(); + Self::clear_stop_flag(self.info); return Ok(TransmitResult::Stopped); } @@ -1048,7 +1065,7 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { } if sr1.stopf() { - self.clear_stop_flag(); + Self::clear_stop_flag(self.info); return Ok(ReceiveResult::Stopped); } @@ -1057,19 +1074,19 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { } /// Determine which slave address was matched based on SR2 flags - fn decode_matched_address(&self, sr2: i2c::regs::Sr2) -> Result { + fn decode_matched_address(sr2: i2c::regs::Sr2, info: &'static Info) -> Result { if sr2.gencall() { Ok(Address::SevenBit(0x00)) // General call address } else if sr2.dualf() { // OA2 (secondary address) was matched - let oar2 = self.info.regs.oar2().read(); + let oar2 = info.regs.oar2().read(); if oar2.endual() != i2c::vals::Endual::DUAL { return Err(Error::Bus); // Hardware inconsistency } Ok(Address::SevenBit(oar2.add2())) } else { // OA1 (primary address) was matched - let oar1 = self.info.regs.oar1().read(); + let oar1 = info.regs.oar1().read(); match oar1.addmode() { i2c::vals::Addmode::BIT7 => { let addr = (oar1.add() >> 1) as u8; @@ -1113,7 +1130,7 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { fn handle_early_termination(&mut self, result: TransmitResult) -> Error { match result { TransmitResult::Stopped => { - self.clear_stop_flag(); + Self::clear_stop_flag(self.info); Error::Bus // Unexpected STOP during setup }, TransmitResult::Restarted => { @@ -1153,11 +1170,6 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { reg.set_af(false); }); } - - /// Clear the stop condition flag - fn clear_stop_flag(&mut self) { - self.info.regs.cr1().modify(|_w| {}); - } } // Address configuration methods @@ -1176,6 +1188,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { self.info.regs.cr1().modify(|reg| { reg.set_pe(true); reg.set_ack(true); // Enable acknowledgment for slave mode + reg.set_nostretch(false); // Allow clock stretching for processing time }); trace!("I2C slave: initialization complete"); @@ -1251,6 +1264,312 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { } } +impl<'d> I2c<'d, Async, MultiMaster> { + /// Async listen for incoming I2C messages using interrupts + pub async fn listen(&mut self) -> Result { + trace!("I2C slave: starting listen for address match"); + + // Extract references needed by the closure + let state = self.state; + let info = self.info; + + Self::enable_interrupts(info); + trace!("I2C slave: enabled event and error interrupts"); + + // Sentinel to clean up interrupts on drop + let on_drop = OnDrop::new(|| { + critical_section::with(|_| { + info.regs.cr2().modify(|w| { + w.set_iterren(false); + w.set_itevten(false); + }); + }); + trace!("I2C slave: disabled interrupts on drop"); + }); + + let result = poll_fn(|cx| { + state.waker.register(cx.waker()); + + match Self::check_and_clear_error_flags(info) { + Err(e) => { + error!("I2C slave: error during listen: {:?}", e); + Poll::Ready(Err(e)) + }, + Ok(sr1) => { + if sr1.addr() { + trace!("I2C slave: ADDR flag detected - address matched"); + + // Address matched - determine direction + let sr2 = info.regs.sr2().read(); + let direction = if sr2.tra() { + trace!("I2C slave: direction = READ (master wants to read from us)"); + SlaveCommandKind::Read + } else { + trace!("I2C slave: direction = WRITE (master wants to write to us)"); + SlaveCommandKind::Write + }; + + let matched_address = match Self::decode_matched_address(sr2, info) { + Ok(addr) => { + trace!("I2C slave: matched address decoded: {:?}", addr); + addr + }, + Err(e) => { + error!("I2C slave: failed to decode matched address: {:?}", e); + return Poll::Ready(Err(e)); + } + }; + + trace!("I2C slave: listen complete - returning command"); + // Don't clear ADDR here - leave it for DMA setup + Poll::Ready(Ok(SlaveCommand { + kind: direction, + address: matched_address, + })) + } else { + // Re-enable interrupts and continue waiting + // Use safe method since handler disables them globally + Self::enable_interrupts(info); + Poll::Pending + } + } + } + }).await; + + drop(on_drop); + trace!("I2C slave: listen result: {:?}", result); + result + } + + /// Async respond to write command using RX DMA + pub async fn respond_to_write(&mut self, buffer: &mut [u8]) -> Result { + trace!("I2C slave: starting respond_to_write, buffer_len={}", buffer.len()); + + if buffer.is_empty() { + warn!("I2C slave: respond_to_write called with empty buffer"); + return Err(Error::Overrun); + } + + // Extract references needed by closures + let state = self.state; + let info = self.info; + + trace!("I2C slave: configuring registers for RX DMA"); + info.regs.cr2().modify(|w| { + w.set_itbufen(false); // Disable buffer interrupts for DMA + w.set_dmaen(true); // Enable DMA + w.set_last(false); // Not needed for slave writes + }); + + // Sentinel to clean up DMA on drop + let on_drop = OnDrop::new(|| { + critical_section::with(|_| { + info.regs.cr2().modify(|w| { + w.set_dmaen(false); + w.set_iterren(false); + w.set_itevten(false); + }); + }); + trace!("I2C slave: DMA and interrupts disabled on drop (respond_to_write)"); + }); + + // Clear ADDR flag to release clock stretching - DMA is now ready + trace!("I2C slave: clearing ADDR flag to release clock stretching"); + info.regs.sr2().read(); // Clear ADDR by reading SR2 + + // Set up RX DMA transfer (I2C DR -> buffer) + trace!("I2C slave: setting up RX DMA transfer"); + let dma_transfer = unsafe { + let src = info.regs.dr().as_ptr() as *mut u8; + self.rx_dma.as_mut().unwrap().read(src, buffer, Default::default()) + }; + + // Poll for I2C errors while DMA runs + let poll_error = poll_fn(|cx| { + state.waker.register(cx.waker()); + + match Self::check_and_clear_error_flags(info) { + Err(e) => { + error!("I2C slave: error during write operation: {:?}", e); + Poll::Ready(Err::<(), Error>(e)) + }, + Ok(sr1) => { + // Check for STOP condition (normal end of write) + if sr1.stopf() { + trace!("I2C slave: STOP condition detected - write transaction complete"); + Self::clear_stop_flag(info); + Poll::Ready(Ok(())) + } else if sr1.addr() { + trace!("I2C slave: RESTART condition detected - transitioning to read phase"); + Poll::Ready(Ok(())) + } else { + // Re-enable interrupts and continue monitoring + // Use safe method since handler disables them globally + Self::enable_interrupts(info); + Poll::Pending + } + } + } + }); + + trace!("I2C slave: starting select between DMA completion and I2C events"); + // Wait for either DMA completion or I2C termination condition (using master pattern) + match select(dma_transfer, poll_error).await { + Either::Second(Err(e)) => { + error!("I2C slave: I2C error occurred during write: {:?}", e); + critical_section::with(|_| { + info.regs.cr2().modify(|w| w.set_dmaen(false)); + }); + drop(on_drop); + Err(e) + } + Either::First(_) => { + trace!("I2C slave: DMA transfer completed first"); + critical_section::with(|_| { + info.regs.cr2().modify(|w| w.set_dmaen(false)); + }); + drop(on_drop); + + // For v1 hardware, determining exact bytes received is complex + // Return the buffer length as an approximation + let bytes_received = buffer.len(); + trace!("I2C slave: write complete, returning {} bytes (buffer length)", bytes_received); + Ok(bytes_received) + } + Either::Second(Ok(())) => { + trace!("I2C slave: I2C event (STOP/RESTART) occurred first"); + critical_section::with(|_| { + info.regs.cr2().modify(|w| w.set_dmaen(false)); + }); + drop(on_drop); + + // Transaction ended normally, return buffer length + let bytes_received = buffer.len(); + trace!("I2C slave: write complete via I2C event, returning {} bytes", bytes_received); + Ok(bytes_received) + } + } + } + + /// Async respond to read command using TX DMA + pub async fn respond_to_read(&mut self, data: &[u8]) -> Result { + trace!("I2C slave: starting respond_to_read, data_len={}", data.len()); + + if data.is_empty() { + warn!("I2C slave: respond_to_read called with empty data"); + return Err(Error::Overrun); + } + + // Extract references needed by closures + let state = self.state; + let info = self.info; + + trace!("I2C slave: configuring registers for TX DMA"); + info.regs.cr2().modify(|w| { + w.set_itbufen(false); // Disable buffer interrupts for DMA + w.set_dmaen(true); // Enable DMA + w.set_last(false); // Not applicable for slave transmit + }); + + // Sentinel to clean up DMA on drop + let on_drop = OnDrop::new(|| { + critical_section::with(|_| { + info.regs.cr2().modify(|w| { + w.set_dmaen(false); + w.set_iterren(false); + w.set_itevten(false); + }); + }); + trace!("I2C slave: DMA and interrupts disabled on drop (respond_to_read)"); + }); + + // Clear ADDR flag to release clock stretching + trace!("I2C slave: clearing ADDR flag to release clock stretching"); + info.regs.sr2().read(); + + // Set up TX DMA transfer (data -> I2C DR) + trace!("I2C slave: setting up TX DMA transfer"); + let dma_transfer = unsafe { + let dst = info.regs.dr().as_ptr() as *mut u8; + self.tx_dma.as_mut().unwrap().write(data, dst, Default::default()) + }; + + // Monitor for I2C events while DMA runs + let poll_completion = poll_fn(|cx| { + state.waker.register(cx.waker()); + + match Self::check_and_clear_error_flags(info) { + Err(Error::Nack) => { + trace!("I2C slave: NACK received - master stopped reading (normal)"); + Poll::Ready(Ok(())) + } + Err(e) => { + error!("I2C slave: error during read operation: {:?}", e); + Poll::Ready(Err(e)) + }, + Ok(sr1) => { + if sr1.stopf() { + trace!("I2C slave: STOP condition detected - read transaction complete"); + Self::clear_stop_flag(info); + Poll::Ready(Ok(())) + } else if sr1.addr() { + trace!("I2C slave: RESTART condition detected during read"); + Poll::Ready(Ok(())) + } else if sr1.af() { + trace!("I2C slave: acknowledge failure (NACK) - normal end of read"); + // Acknowledge failure (NACK) - normal end of read + info.regs.sr1().write(|reg| { + reg.0 = !0; + reg.set_af(false); + }); + Poll::Ready(Ok(())) + } else { + Self::enable_interrupts(info); + Poll::Pending + } + } + } + }); + + trace!("I2C slave: starting select between DMA completion and I2C events"); + // Wait for either DMA completion or I2C termination (using master pattern) + match select(dma_transfer, poll_completion).await { + Either::Second(Err(e)) => { + error!("I2C slave: I2C error occurred during read: {:?}", e); + critical_section::with(|_| { + info.regs.cr2().modify(|w| w.set_dmaen(false)); + }); + drop(on_drop); + Err(e) + } + Either::First(_) => { + trace!("I2C slave: DMA transfer completed first - all data sent"); + critical_section::with(|_| { + info.regs.cr2().modify(|w| w.set_dmaen(false)); + }); + drop(on_drop); + + let bytes_sent = data.len(); + trace!("I2C slave: read complete via DMA, sent {} bytes", bytes_sent); + Ok(bytes_sent) + } + Either::Second(Ok(())) => { + trace!("I2C slave: I2C event (STOP/NACK/RESTART) occurred first"); + critical_section::with(|_| { + info.regs.cr2().modify(|w| w.set_dmaen(false)); + }); + drop(on_drop); + + // For slave read, we can't easily determine exact bytes sent in v1 + // Return the full data length if no error occurred + let bytes_sent = data.len(); + trace!("I2C slave: read complete via I2C event, reporting {} bytes sent", bytes_sent); + Ok(bytes_sent) + } + } + } +} + /// Timing configuration for I2C v1 hardware /// /// This struct encapsulates the complex timing calculations required for STM32 I2C v1 -- cgit From a52497bd39701cd71a877a3d2bd264ae2dea716c Mon Sep 17 00:00:00 2001 From: HybridChild Date: Fri, 22 Aug 2025 12:44:06 +0200 Subject: stm32/i2c_v1: Add handling of excess Write and Read from Master --- embassy-stm32/src/i2c/v1.rs | 153 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 129 insertions(+), 24 deletions(-) diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index 10a307396..c37b48728 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -1414,41 +1414,85 @@ impl<'d> I2c<'d, Async, MultiMaster> { trace!("I2C slave: starting select between DMA completion and I2C events"); // Wait for either DMA completion or I2C termination condition (using master pattern) - match select(dma_transfer, poll_error).await { + let dma_result = match select(dma_transfer, poll_error).await { Either::Second(Err(e)) => { error!("I2C slave: I2C error occurred during write: {:?}", e); critical_section::with(|_| { info.regs.cr2().modify(|w| w.set_dmaen(false)); }); drop(on_drop); - Err(e) + return Err(e); } Either::First(_) => { - trace!("I2C slave: DMA transfer completed first"); + trace!("I2C slave: DMA transfer completed first - buffer full"); critical_section::with(|_| { info.regs.cr2().modify(|w| w.set_dmaen(false)); }); - drop(on_drop); - // For v1 hardware, determining exact bytes received is complex - // Return the buffer length as an approximation + // DMA completed but master might still be sending more bytes + // We need to discard any excess bytes until STOP/RESTART + trace!("I2C slave: DMA complete, checking for excess bytes to discard"); + + let excess_discard = poll_fn(|cx| { + state.waker.register(cx.waker()); + + match Self::check_and_clear_error_flags(info) { + Err(e) => { + error!("I2C slave: error while discarding excess bytes: {:?}", e); + Poll::Ready(Err::<(), Error>(e)) + }, + Ok(sr1) => { + // Check for transaction termination first + if sr1.stopf() { + trace!("I2C slave: STOP condition detected while discarding excess"); + Self::clear_stop_flag(info); + return Poll::Ready(Ok(())); + } + + if sr1.addr() { + trace!("I2C slave: RESTART condition detected while discarding excess"); + return Poll::Ready(Ok(())); + } + + // If there's data to read, read and discard it + if sr1.rxne() { + let discarded_byte = info.regs.dr().read().dr(); + trace!("I2C slave: discarded excess byte: 0x{:02X}", discarded_byte); + // Continue polling for more bytes or termination + Self::enable_interrupts(info); + return Poll::Pending; + } + + // No immediate termination or data, keep waiting + Self::enable_interrupts(info); + Poll::Pending + } + } + }); + + // Wait for transaction to actually end + excess_discard.await?; + let bytes_received = buffer.len(); - trace!("I2C slave: write complete, returning {} bytes (buffer length)", bytes_received); + trace!("I2C slave: write complete after discarding excess, returning {} bytes", bytes_received); + drop(on_drop); Ok(bytes_received) } Either::Second(Ok(())) => { - trace!("I2C slave: I2C event (STOP/RESTART) occurred first"); + trace!("I2C slave: I2C event (STOP/RESTART) occurred before DMA completion"); critical_section::with(|_| { info.regs.cr2().modify(|w| w.set_dmaen(false)); }); - drop(on_drop); - // Transaction ended normally, return buffer length + // Transaction ended normally before buffer was full let bytes_received = buffer.len(); - trace!("I2C slave: write complete via I2C event, returning {} bytes", bytes_received); + trace!("I2C slave: write complete via early I2C event, returning {} bytes", bytes_received); + drop(on_drop); Ok(bytes_received) } - } + }; + + dma_result } /// Async respond to read command using TX DMA @@ -1533,40 +1577,101 @@ impl<'d> I2c<'d, Async, MultiMaster> { trace!("I2C slave: starting select between DMA completion and I2C events"); // Wait for either DMA completion or I2C termination (using master pattern) - match select(dma_transfer, poll_completion).await { + let dma_result = match select(dma_transfer, poll_completion).await { Either::Second(Err(e)) => { error!("I2C slave: I2C error occurred during read: {:?}", e); critical_section::with(|_| { info.regs.cr2().modify(|w| w.set_dmaen(false)); }); drop(on_drop); - Err(e) + return Err(e); } Either::First(_) => { trace!("I2C slave: DMA transfer completed first - all data sent"); critical_section::with(|_| { info.regs.cr2().modify(|w| w.set_dmaen(false)); }); - drop(on_drop); - let bytes_sent = data.len(); - trace!("I2C slave: read complete via DMA, sent {} bytes", bytes_sent); - Ok(bytes_sent) + // DMA completed but master might still be requesting more bytes + // We need to send padding bytes (0x00) until NACK/STOP/RESTART + trace!("I2C slave: DMA complete, entering padding phase"); + let mut padding_bytes_sent = 0; + + let padding_phase = poll_fn(|cx| { + state.waker.register(cx.waker()); + + match Self::check_and_clear_error_flags(info) { + Err(Error::Nack) => { + trace!("I2C slave: NACK received during padding - normal end"); + return Poll::Ready(Ok(())); + }, + Err(e) => { + error!("I2C slave: error while sending padding bytes: {:?}", e); + return Poll::Ready(Err::<(), Error>(e)); + }, + Ok(sr1) => { + // Check for transaction termination first + if sr1.af() { + trace!("I2C slave: acknowledge failure during padding - normal end"); + info.regs.sr1().write(|reg| { + reg.0 = !0; + reg.set_af(false); + }); + return Poll::Ready(Ok(())); + } + + if sr1.stopf() { + trace!("I2C slave: STOP condition detected during padding"); + Self::clear_stop_flag(info); + return Poll::Ready(Ok(())); + } + + if sr1.addr() { + trace!("I2C slave: RESTART condition detected during padding"); + return Poll::Ready(Ok(())); + } + + // If transmit buffer is empty, send a padding byte + if sr1.txe() { + info.regs.dr().write(|w| w.set_dr(0x00)); + padding_bytes_sent += 1; + trace!("I2C slave: sent padding byte #{}", padding_bytes_sent); + // Continue padding until transaction ends + Self::enable_interrupts(info); + return Poll::Pending; + } + + // No immediate termination or transmit request, keep waiting + Self::enable_interrupts(info); + Poll::Pending + } + } + }); + + // Wait for transaction to actually end + padding_phase.await?; + + let total_bytes_sent = data.len() + padding_bytes_sent; + trace!("I2C slave: read complete after sending {} padding bytes, total {} bytes sent", + padding_bytes_sent, total_bytes_sent); + drop(on_drop); + Ok(total_bytes_sent) } Either::Second(Ok(())) => { - trace!("I2C slave: I2C event (STOP/NACK/RESTART) occurred first"); + trace!("I2C slave: I2C event (STOP/NACK/RESTART) occurred before DMA completion"); critical_section::with(|_| { info.regs.cr2().modify(|w| w.set_dmaen(false)); }); - drop(on_drop); - // For slave read, we can't easily determine exact bytes sent in v1 - // Return the full data length if no error occurred + // Transaction ended normally before all data was sent let bytes_sent = data.len(); - trace!("I2C slave: read complete via I2C event, reporting {} bytes sent", bytes_sent); + trace!("I2C slave: read complete via early I2C event, sent {} bytes", bytes_sent); + drop(on_drop); Ok(bytes_sent) } - } + }; + + dma_result } } -- cgit From 8111bbc54594706d26d02a22ad8f1853317d3495 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Fri, 22 Aug 2025 13:18:52 +0200 Subject: stm32/i2c_v1: Clean up Async MultiMaster implementation --- embassy-stm32/src/i2c/v1.rs | 732 ++++++++++++++++++++------------------------ 1 file changed, 340 insertions(+), 392 deletions(-) diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index c37b48728..0f2953ef6 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -758,6 +758,17 @@ enum ReceiveResult { Restarted, } +/// Enumeration of slave transaction termination conditions +#[derive(Debug, Clone, Copy, PartialEq)] +enum SlaveTermination { + /// STOP condition received - normal end of transaction + Stop, + /// RESTART condition received - master starting new transaction + Restart, + /// NACK received - normal end of read transaction + Nack, +} + impl<'d, M: Mode> I2c<'d, M, Master> { /// Configure the I2C driver for slave operations, allowing for the driver to be used as a slave and a master (multimaster) pub fn into_slave_multimaster(mut self, slave_addr_config: SlaveAddrConfig) -> I2c<'d, M, MultiMaster> { @@ -778,6 +789,98 @@ impl<'d, M: Mode> I2c<'d, M, Master> { } } +// Address configuration methods +impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { + /// Initialize slave mode with address configuration + pub(crate) fn init_slave(&mut self, config: SlaveAddrConfig) { + trace!("I2C slave: initializing with config={:?}", config); + + // Disable peripheral for configuration + self.info.regs.cr1().modify(|reg| reg.set_pe(false)); + + // Configure slave addresses + self.apply_address_configuration(config); + + // Enable peripheral with slave settings + self.info.regs.cr1().modify(|reg| { + reg.set_pe(true); + reg.set_ack(true); // Enable acknowledgment for slave mode + reg.set_nostretch(false); // Allow clock stretching for processing time + }); + + trace!("I2C slave: initialization complete"); + } + + /// Apply the complete address configuration for slave mode + fn apply_address_configuration(&mut self, config: SlaveAddrConfig) { + match config.addr { + OwnAddresses::OA1(addr) => { + self.configure_primary_address(addr); + self.disable_secondary_address(); + }, + OwnAddresses::OA2(oa2) => { + self.configure_default_primary_address(); + self.configure_secondary_address(oa2.addr); // v1 ignores mask + }, + OwnAddresses::Both { oa1, oa2 } => { + self.configure_primary_address(oa1); + self.configure_secondary_address(oa2.addr); // v1 ignores mask + } + } + + // Configure general call detection + if config.general_call { + self.info.regs.cr1().modify(|w| w.set_engc(true)); + } + } + + /// Configure the primary address (OA1) register + fn configure_primary_address(&mut self, addr: Address) { + match addr { + Address::SevenBit(addr) => { + self.info.regs.oar1().write(|reg| { + let hw_addr = (addr as u16) << 1; // Address in bits [7:1] + reg.set_add(hw_addr); + reg.set_addmode(i2c::vals::Addmode::BIT7); + }); + }, + Address::TenBit(addr) => { + self.info.regs.oar1().write(|reg| { + reg.set_add(addr); + reg.set_addmode(i2c::vals::Addmode::BIT10); + }); + } + } + + // Set required bit 14 as per reference manual + self.info.regs.oar1().modify(|reg| reg.0 |= 1 << 14); + } + + /// Configure the secondary address (OA2) register + fn configure_secondary_address(&mut self, addr: u8) { + self.info.regs.oar2().write(|reg| { + reg.set_add2(addr); + reg.set_endual(i2c::vals::Endual::DUAL); + }); + } + + /// Set a default primary address when using OA2-only mode + fn configure_default_primary_address(&mut self) { + self.info.regs.oar1().write(|reg| { + reg.set_add(0); // Reserved address, safe to use + reg.set_addmode(i2c::vals::Addmode::BIT7); + }); + self.info.regs.oar1().modify(|reg| reg.0 |= 1 << 14); + } + + /// Disable secondary address when not needed + fn disable_secondary_address(&mut self) { + self.info.regs.oar2().write(|reg| { + reg.set_endual(i2c::vals::Endual::SINGLE); + }); + } +} + impl<'d, M: Mode> I2c<'d, M, MultiMaster> { /// Listen for incoming I2C address match and return the command type /// @@ -1170,165 +1273,85 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { reg.set_af(false); }); } -} -// Address configuration methods -impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { - /// Initialize slave mode with address configuration - pub(crate) fn init_slave(&mut self, config: SlaveAddrConfig) { - trace!("I2C slave: initializing with config={:?}", config); - - // Disable peripheral for configuration - self.info.regs.cr1().modify(|reg| reg.set_pe(false)); - - // Configure slave addresses - self.apply_address_configuration(config); - - // Enable peripheral with slave settings - self.info.regs.cr1().modify(|reg| { - reg.set_pe(true); - reg.set_ack(true); // Enable acknowledgment for slave mode - reg.set_nostretch(false); // Allow clock stretching for processing time - }); - - trace!("I2C slave: initialization complete"); - } - - /// Apply the complete address configuration for slave mode - fn apply_address_configuration(&mut self, config: SlaveAddrConfig) { - match config.addr { - OwnAddresses::OA1(addr) => { - self.configure_primary_address(addr); - self.disable_secondary_address(); - }, - OwnAddresses::OA2(oa2) => { - self.configure_default_primary_address(); - self.configure_secondary_address(oa2.addr); // v1 ignores mask - }, - OwnAddresses::Both { oa1, oa2 } => { - self.configure_primary_address(oa1); - self.configure_secondary_address(oa2.addr); // v1 ignores mask - } - } - - // Configure general call detection - if config.general_call { - self.info.regs.cr1().modify(|w| w.set_engc(true)); - } - } - - /// Configure the primary address (OA1) register - fn configure_primary_address(&mut self, addr: Address) { - match addr { - Address::SevenBit(addr) => { - self.info.regs.oar1().write(|reg| { - let hw_addr = (addr as u16) << 1; // Address in bits [7:1] - reg.set_add(hw_addr); - reg.set_addmode(i2c::vals::Addmode::BIT7); - }); - }, - Address::TenBit(addr) => { - self.info.regs.oar1().write(|reg| { - reg.set_add(addr); - reg.set_addmode(i2c::vals::Addmode::BIT10); - }); - } - } - - // Set required bit 14 as per reference manual - self.info.regs.oar1().modify(|reg| reg.0 |= 1 << 14); - } - - /// Configure the secondary address (OA2) register - fn configure_secondary_address(&mut self, addr: u8) { - self.info.regs.oar2().write(|reg| { - reg.set_add2(addr); - reg.set_endual(i2c::vals::Endual::DUAL); + /// Configure DMA settings for slave operations (shared between read/write) + fn setup_slave_dma_base(&mut self) { + self.info.regs.cr2().modify(|w| { + w.set_itbufen(false); // Always disable buffer interrupts when using DMA + w.set_dmaen(true); // Enable DMA requests + w.set_last(false); // LAST bit not used in slave mode for v1 hardware }); } - /// Set a default primary address when using OA2-only mode - fn configure_default_primary_address(&mut self) { - self.info.regs.oar1().write(|reg| { - reg.set_add(0); // Reserved address, safe to use - reg.set_addmode(i2c::vals::Addmode::BIT7); + /// Disable DMA and interrupts in a critical section + fn disable_dma_and_interrupts(info: &'static Info) { + critical_section::with(|_| { + info.regs.cr2().modify(|w| { + w.set_dmaen(false); + w.set_iterren(false); + w.set_itevten(false); + }); }); - self.info.regs.oar1().modify(|reg| reg.0 |= 1 << 14); } - - /// Disable secondary address when not needed - fn disable_secondary_address(&mut self) { - self.info.regs.oar2().write(|reg| { - reg.set_endual(i2c::vals::Endual::SINGLE); - }); + + /// Check for early termination conditions during slave operations + /// Returns Some(result) if termination detected, None to continue + fn check_slave_termination_conditions(sr1: i2c::regs::Sr1) -> Option { + if sr1.stopf() { + Some(SlaveTermination::Stop) + } else if sr1.addr() { + Some(SlaveTermination::Restart) + } else if sr1.af() { + Some(SlaveTermination::Nack) + } else { + None + } } } impl<'d> I2c<'d, Async, MultiMaster> { /// Async listen for incoming I2C messages using interrupts + /// + /// Waits for a master to address this slave and returns the command type + /// (Read/Write) and the matched address. This method will suspend until + /// an address match occurs. pub async fn listen(&mut self) -> Result { - trace!("I2C slave: starting listen for address match"); - - // Extract references needed by the closure let state = self.state; let info = self.info; Self::enable_interrupts(info); - trace!("I2C slave: enabled event and error interrupts"); - // Sentinel to clean up interrupts on drop + // Ensure interrupts are cleaned up on early exit let on_drop = OnDrop::new(|| { - critical_section::with(|_| { - info.regs.cr2().modify(|w| { - w.set_iterren(false); - w.set_itevten(false); - }); - }); - trace!("I2C slave: disabled interrupts on drop"); + Self::disable_dma_and_interrupts(info); }); let result = poll_fn(|cx| { state.waker.register(cx.waker()); match Self::check_and_clear_error_flags(info) { - Err(e) => { - error!("I2C slave: error during listen: {:?}", e); - Poll::Ready(Err(e)) - }, + Err(e) => Poll::Ready(Err(e)), Ok(sr1) => { if sr1.addr() { - trace!("I2C slave: ADDR flag detected - address matched"); - - // Address matched - determine direction + // Address matched - determine direction and decode address let sr2 = info.regs.sr2().read(); let direction = if sr2.tra() { - trace!("I2C slave: direction = READ (master wants to read from us)"); SlaveCommandKind::Read } else { - trace!("I2C slave: direction = WRITE (master wants to write to us)"); SlaveCommandKind::Write }; let matched_address = match Self::decode_matched_address(sr2, info) { - Ok(addr) => { - trace!("I2C slave: matched address decoded: {:?}", addr); - addr - }, - Err(e) => { - error!("I2C slave: failed to decode matched address: {:?}", e); - return Poll::Ready(Err(e)); - } + Ok(addr) => addr, + Err(e) => return Poll::Ready(Err(e)), }; - trace!("I2C slave: listen complete - returning command"); - // Don't clear ADDR here - leave it for DMA setup + // Don't clear ADDR here - leave it for DMA setup in respond methods Poll::Ready(Ok(SlaveCommand { kind: direction, address: matched_address, })) } else { - // Re-enable interrupts and continue waiting - // Use safe method since handler disables them globally Self::enable_interrupts(info); Poll::Pending } @@ -1337,341 +1360,266 @@ impl<'d> I2c<'d, Async, MultiMaster> { }).await; drop(on_drop); - trace!("I2C slave: listen result: {:?}", result); result } /// Async respond to write command using RX DMA + /// + /// Receives data from the master into the provided buffer using DMA. + /// If the master sends more bytes than the buffer can hold, excess bytes + /// are acknowledged but discarded to prevent interrupt flooding. + /// + /// Returns the number of bytes stored in the buffer (not total received). pub async fn respond_to_write(&mut self, buffer: &mut [u8]) -> Result { - trace!("I2C slave: starting respond_to_write, buffer_len={}", buffer.len()); - if buffer.is_empty() { - warn!("I2C slave: respond_to_write called with empty buffer"); return Err(Error::Overrun); } - // Extract references needed by closures let state = self.state; let info = self.info; - trace!("I2C slave: configuring registers for RX DMA"); - info.regs.cr2().modify(|w| { - w.set_itbufen(false); // Disable buffer interrupts for DMA - w.set_dmaen(true); // Enable DMA - w.set_last(false); // Not needed for slave writes - }); + // Use shared DMA setup + self.setup_slave_dma_base(); - // Sentinel to clean up DMA on drop + // Ensure proper cleanup on exit let on_drop = OnDrop::new(|| { - critical_section::with(|_| { - info.regs.cr2().modify(|w| { - w.set_dmaen(false); - w.set_iterren(false); - w.set_itevten(false); - }); - }); - trace!("I2C slave: DMA and interrupts disabled on drop (respond_to_write)"); + Self::disable_dma_and_interrupts(info); }); - // Clear ADDR flag to release clock stretching - DMA is now ready - trace!("I2C slave: clearing ADDR flag to release clock stretching"); - info.regs.sr2().read(); // Clear ADDR by reading SR2 - - // Set up RX DMA transfer (I2C DR -> buffer) - trace!("I2C slave: setting up RX DMA transfer"); - let dma_transfer = unsafe { - let src = info.regs.dr().as_ptr() as *mut u8; - self.rx_dma.as_mut().unwrap().read(src, buffer, Default::default()) - }; - - // Poll for I2C errors while DMA runs - let poll_error = poll_fn(|cx| { - state.waker.register(cx.waker()); + // Clear ADDR flag to release clock stretching + info.regs.sr2().read(); - match Self::check_and_clear_error_flags(info) { - Err(e) => { - error!("I2C slave: error during write operation: {:?}", e); - Poll::Ready(Err::<(), Error>(e)) - }, - Ok(sr1) => { - // Check for STOP condition (normal end of write) - if sr1.stopf() { - trace!("I2C slave: STOP condition detected - write transaction complete"); - Self::clear_stop_flag(info); - Poll::Ready(Ok(())) - } else if sr1.addr() { - trace!("I2C slave: RESTART condition detected - transitioning to read phase"); - Poll::Ready(Ok(())) - } else { - // Re-enable interrupts and continue monitoring - // Use safe method since handler disables them globally - Self::enable_interrupts(info); - Poll::Pending - } - } - } - }); + // Start DMA transfer and monitor completion + let result = self.execute_slave_receive_transfer(buffer, state, info).await; - trace!("I2C slave: starting select between DMA completion and I2C events"); - // Wait for either DMA completion or I2C termination condition (using master pattern) - let dma_result = match select(dma_transfer, poll_error).await { - Either::Second(Err(e)) => { - error!("I2C slave: I2C error occurred during write: {:?}", e); - critical_section::with(|_| { - info.regs.cr2().modify(|w| w.set_dmaen(false)); - }); - drop(on_drop); - return Err(e); - } - Either::First(_) => { - trace!("I2C slave: DMA transfer completed first - buffer full"); - critical_section::with(|_| { - info.regs.cr2().modify(|w| w.set_dmaen(false)); - }); - - // DMA completed but master might still be sending more bytes - // We need to discard any excess bytes until STOP/RESTART - trace!("I2C slave: DMA complete, checking for excess bytes to discard"); - - let excess_discard = poll_fn(|cx| { - state.waker.register(cx.waker()); - - match Self::check_and_clear_error_flags(info) { - Err(e) => { - error!("I2C slave: error while discarding excess bytes: {:?}", e); - Poll::Ready(Err::<(), Error>(e)) - }, - Ok(sr1) => { - // Check for transaction termination first - if sr1.stopf() { - trace!("I2C slave: STOP condition detected while discarding excess"); - Self::clear_stop_flag(info); - return Poll::Ready(Ok(())); - } - - if sr1.addr() { - trace!("I2C slave: RESTART condition detected while discarding excess"); - return Poll::Ready(Ok(())); - } - - // If there's data to read, read and discard it - if sr1.rxne() { - let discarded_byte = info.regs.dr().read().dr(); - trace!("I2C slave: discarded excess byte: 0x{:02X}", discarded_byte); - // Continue polling for more bytes or termination - Self::enable_interrupts(info); - return Poll::Pending; - } - - // No immediate termination or data, keep waiting - Self::enable_interrupts(info); - Poll::Pending - } - } - }); - - // Wait for transaction to actually end - excess_discard.await?; - - let bytes_received = buffer.len(); - trace!("I2C slave: write complete after discarding excess, returning {} bytes", bytes_received); - drop(on_drop); - Ok(bytes_received) - } - Either::Second(Ok(())) => { - trace!("I2C slave: I2C event (STOP/RESTART) occurred before DMA completion"); - critical_section::with(|_| { - info.regs.cr2().modify(|w| w.set_dmaen(false)); - }); - - // Transaction ended normally before buffer was full - let bytes_received = buffer.len(); - trace!("I2C slave: write complete via early I2C event, returning {} bytes", bytes_received); - drop(on_drop); - Ok(bytes_received) - } - }; - - dma_result + drop(on_drop); + result } /// Async respond to read command using TX DMA + /// + /// Transmits data to the master using DMA. If the master requests more bytes + /// than available in the data buffer, padding bytes (0x00) are sent until + /// the master terminates the transaction with NACK, STOP, or RESTART. + /// + /// Returns the total number of bytes transmitted (data + padding). pub async fn respond_to_read(&mut self, data: &[u8]) -> Result { - trace!("I2C slave: starting respond_to_read, data_len={}", data.len()); - if data.is_empty() { - warn!("I2C slave: respond_to_read called with empty data"); return Err(Error::Overrun); } - // Extract references needed by closures let state = self.state; let info = self.info; - trace!("I2C slave: configuring registers for TX DMA"); - info.regs.cr2().modify(|w| { - w.set_itbufen(false); // Disable buffer interrupts for DMA - w.set_dmaen(true); // Enable DMA - w.set_last(false); // Not applicable for slave transmit - }); + // Use shared DMA setup + self.setup_slave_dma_base(); - // Sentinel to clean up DMA on drop + // Ensure proper cleanup on exit let on_drop = OnDrop::new(|| { - critical_section::with(|_| { - info.regs.cr2().modify(|w| { - w.set_dmaen(false); - w.set_iterren(false); - w.set_itevten(false); - }); - }); - trace!("I2C slave: DMA and interrupts disabled on drop (respond_to_read)"); + Self::disable_dma_and_interrupts(info); }); // Clear ADDR flag to release clock stretching - trace!("I2C slave: clearing ADDR flag to release clock stretching"); info.regs.sr2().read(); - // Set up TX DMA transfer (data -> I2C DR) - trace!("I2C slave: setting up TX DMA transfer"); + // Start DMA transfer and monitor completion + let result = self.execute_slave_transmit_transfer(data, state, info).await; + + drop(on_drop); + result + } + + // === Private Transfer Execution Methods === + + /// Execute complete slave receive transfer with excess byte handling + async fn execute_slave_receive_transfer( + &mut self, + buffer: &mut [u8], + state: &'static State, + info: &'static Info + ) -> Result { + // Start DMA transfer + let dma_transfer = unsafe { + let src = info.regs.dr().as_ptr() as *mut u8; + self.rx_dma.as_mut().unwrap().read(src, buffer, Default::default()) + }; + + // Monitor for I2C events during transfer + let i2c_monitor = Self::create_termination_monitor(state, info, &[SlaveTermination::Stop, SlaveTermination::Restart]); + + match select(dma_transfer, i2c_monitor).await { + Either::Second(Err(e)) => { + Self::disable_dma_and_interrupts(info); + Err(e) + } + Either::First(_) => { + // DMA completed first - handle potential excess bytes + Self::disable_dma_and_interrupts(info); + self.handle_excess_bytes(state, info).await?; + Ok(buffer.len()) + } + Either::Second(Ok(_)) => { + // I2C event occurred first - normal transaction end + Self::disable_dma_and_interrupts(info); + Ok(buffer.len()) + } + } + } + + /// Execute complete slave transmit transfer with padding byte handling + async fn execute_slave_transmit_transfer( + &mut self, + data: &[u8], + state: &'static State, + info: &'static Info + ) -> Result { + // Start DMA transfer let dma_transfer = unsafe { let dst = info.regs.dr().as_ptr() as *mut u8; self.tx_dma.as_mut().unwrap().write(data, dst, Default::default()) }; - // Monitor for I2C events while DMA runs - let poll_completion = poll_fn(|cx| { - state.waker.register(cx.waker()); + // Monitor for I2C events during transfer (NACK is normal for slave transmit) + let i2c_monitor = Self::create_termination_monitor(state, info, &[SlaveTermination::Stop, SlaveTermination::Restart, SlaveTermination::Nack]); + match select(dma_transfer, i2c_monitor).await { + Either::Second(Err(e)) => { + Self::disable_dma_and_interrupts(info); + Err(e) + } + Either::First(_) => { + // DMA completed first - handle potential padding bytes + Self::disable_dma_and_interrupts(info); + let padding_count = self.handle_padding_bytes(state, info).await?; + Ok(data.len() + padding_count) + } + Either::Second(Ok(_)) => { + // I2C event occurred first - normal transaction end + Self::disable_dma_and_interrupts(info); + Ok(data.len()) + } + } + } + + /// Create a future that monitors for specific slave termination conditions + fn create_termination_monitor( + state: &'static State, + info: &'static Info, + allowed_terminations: &'static [SlaveTermination], + ) -> impl Future> { + poll_fn(move |cx| { + state.waker.register(cx.waker()); + match Self::check_and_clear_error_flags(info) { - Err(Error::Nack) => { - trace!("I2C slave: NACK received - master stopped reading (normal)"); - Poll::Ready(Ok(())) + Err(Error::Nack) if allowed_terminations.contains(&SlaveTermination::Nack) => { + Poll::Ready(Ok(SlaveTermination::Nack)) } - Err(e) => { - error!("I2C slave: error during read operation: {:?}", e); - Poll::Ready(Err(e)) - }, + Err(e) => Poll::Ready(Err(e)), Ok(sr1) => { - if sr1.stopf() { - trace!("I2C slave: STOP condition detected - read transaction complete"); - Self::clear_stop_flag(info); - Poll::Ready(Ok(())) - } else if sr1.addr() { - trace!("I2C slave: RESTART condition detected during read"); - Poll::Ready(Ok(())) - } else if sr1.af() { - trace!("I2C slave: acknowledge failure (NACK) - normal end of read"); - // Acknowledge failure (NACK) - normal end of read - info.regs.sr1().write(|reg| { - reg.0 = !0; - reg.set_af(false); - }); - Poll::Ready(Ok(())) + if let Some(termination) = Self::check_slave_termination_conditions(sr1) { + if allowed_terminations.contains(&termination) { + // Handle the specific termination condition + match termination { + SlaveTermination::Stop => Self::clear_stop_flag(info), + SlaveTermination::Nack => { + info.regs.sr1().write(|reg| { + reg.0 = !0; + reg.set_af(false); + }); + } + SlaveTermination::Restart => { + // ADDR flag will be handled by next listen() call + } + } + Poll::Ready(Ok(termination)) + } else { + // Unexpected termination condition + Poll::Ready(Err(Error::Bus)) + } } else { Self::enable_interrupts(info); Poll::Pending } } } - }); + }) + } - trace!("I2C slave: starting select between DMA completion and I2C events"); - // Wait for either DMA completion or I2C termination (using master pattern) - let dma_result = match select(dma_transfer, poll_completion).await { - Either::Second(Err(e)) => { - error!("I2C slave: I2C error occurred during read: {:?}", e); - critical_section::with(|_| { - info.regs.cr2().modify(|w| w.set_dmaen(false)); - }); - drop(on_drop); - return Err(e); - } - Either::First(_) => { - trace!("I2C slave: DMA transfer completed first - all data sent"); - critical_section::with(|_| { - info.regs.cr2().modify(|w| w.set_dmaen(false)); - }); - - // DMA completed but master might still be requesting more bytes - // We need to send padding bytes (0x00) until NACK/STOP/RESTART - trace!("I2C slave: DMA complete, entering padding phase"); - let mut padding_bytes_sent = 0; - - let padding_phase = poll_fn(|cx| { - state.waker.register(cx.waker()); + /// Handle excess bytes after DMA buffer is full + /// + /// Reads and discards bytes until transaction termination to prevent interrupt flooding + async fn handle_excess_bytes(&mut self, state: &'static State, info: &'static Info) -> Result<(), Error> { + poll_fn(|cx| { + state.waker.register(cx.waker()); + + match Self::check_and_clear_error_flags(info) { + Err(e) => Poll::Ready(Err(e)), + Ok(sr1) => { + // Check for transaction termination first + if let Some(termination) = Self::check_slave_termination_conditions(sr1) { + match termination { + SlaveTermination::Stop => Self::clear_stop_flag(info), + SlaveTermination::Restart => {}, // Leave ADDR for next operation + SlaveTermination::Nack => unreachable!("NACK not expected during receive"), + } + return Poll::Ready(Ok(())); + } - match Self::check_and_clear_error_flags(info) { - Err(Error::Nack) => { - trace!("I2C slave: NACK received during padding - normal end"); - return Poll::Ready(Ok(())); - }, - Err(e) => { - error!("I2C slave: error while sending padding bytes: {:?}", e); - return Poll::Ready(Err::<(), Error>(e)); - }, - Ok(sr1) => { - // Check for transaction termination first - if sr1.af() { - trace!("I2C slave: acknowledge failure during padding - normal end"); + // If there's data to read, discard it + if sr1.rxne() { + let _discarded_byte = info.regs.dr().read().dr(); + Self::enable_interrupts(info); + return Poll::Pending; + } + + Self::enable_interrupts(info); + Poll::Pending + } + } + }).await + } + + /// Handle padding bytes after DMA data is exhausted + /// + /// Sends 0x00 bytes until transaction termination to prevent interrupt flooding + async fn handle_padding_bytes(&mut self, state: &'static State, info: &'static Info) -> Result { + let mut padding_count = 0; + + poll_fn(|cx| { + state.waker.register(cx.waker()); + + match Self::check_and_clear_error_flags(info) { + Err(Error::Nack) => Poll::Ready(Ok(padding_count)), // Normal termination + Err(e) => Poll::Ready(Err(e)), + Ok(sr1) => { + // Check for transaction termination first + if let Some(termination) = Self::check_slave_termination_conditions(sr1) { + match termination { + SlaveTermination::Stop => Self::clear_stop_flag(info), + SlaveTermination::Restart => {}, // Leave ADDR for next operation + SlaveTermination::Nack => { info.regs.sr1().write(|reg| { reg.0 = !0; reg.set_af(false); }); - return Poll::Ready(Ok(())); - } - - if sr1.stopf() { - trace!("I2C slave: STOP condition detected during padding"); - Self::clear_stop_flag(info); - return Poll::Ready(Ok(())); - } - - if sr1.addr() { - trace!("I2C slave: RESTART condition detected during padding"); - return Poll::Ready(Ok(())); - } - - // If transmit buffer is empty, send a padding byte - if sr1.txe() { - info.regs.dr().write(|w| w.set_dr(0x00)); - padding_bytes_sent += 1; - trace!("I2C slave: sent padding byte #{}", padding_bytes_sent); - // Continue padding until transaction ends - Self::enable_interrupts(info); - return Poll::Pending; } - - // No immediate termination or transmit request, keep waiting - Self::enable_interrupts(info); - Poll::Pending } + return Poll::Ready(Ok(padding_count)); } - }); - - // Wait for transaction to actually end - padding_phase.await?; - - let total_bytes_sent = data.len() + padding_bytes_sent; - trace!("I2C slave: read complete after sending {} padding bytes, total {} bytes sent", - padding_bytes_sent, total_bytes_sent); - drop(on_drop); - Ok(total_bytes_sent) - } - Either::Second(Ok(())) => { - trace!("I2C slave: I2C event (STOP/NACK/RESTART) occurred before DMA completion"); - critical_section::with(|_| { - info.regs.cr2().modify(|w| w.set_dmaen(false)); - }); - - // Transaction ended normally before all data was sent - let bytes_sent = data.len(); - trace!("I2C slave: read complete via early I2C event, sent {} bytes", bytes_sent); - drop(on_drop); - Ok(bytes_sent) + + // If transmit buffer is empty, send padding byte + if sr1.txe() { + info.regs.dr().write(|w| w.set_dr(0x00)); + padding_count += 1; + Self::enable_interrupts(info); + return Poll::Pending; + } + + Self::enable_interrupts(info); + Poll::Pending + } } - }; - - dma_result + }).await } } -- cgit From e630e1a6d48ca25a92bc0479608ca75b7179c869 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Fri, 22 Aug 2025 13:45:43 +0200 Subject: stm32/i2c_v1: Update defmt logging for MultiMaster --- embassy-stm32/src/i2c/v1.rs | 128 ++++++++++++++++++++++++++------------------ 1 file changed, 75 insertions(+), 53 deletions(-) diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index 0f2953ef6..b362ab017 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -45,7 +45,7 @@ impl State { // hit a case like this! pub unsafe fn on_interrupt() { let regs = T::info().regs; - trace!("i2c v1 interrupt triggered"); + trace!("I2C interrupt triggered"); // i2c v2 only woke the task on transfer complete interrupts. v1 uses interrupts for a bunch of // other stuff, so we wake the task on every interrupt. T::state().waker.wake(); @@ -360,8 +360,6 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { Ok(()) } - - // Async /// Can be used by both blocking and async implementations #[inline] // pretty sure this should always be inlined @@ -374,7 +372,6 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { w.set_itevten(true); }); }); - trace!("I2C slave: safely enabled interrupts"); } /// Can be used by both blocking and async implementations @@ -383,7 +380,6 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { // v1 requires: READ SR1 then WRITE CR1 to clear STOPF let _ = info.regs.sr1().read(); info.regs.cr1().modify(|_| {}); // Dummy write to clear STOPF - trace!("I2C slave: STOPF flag cleared"); } } @@ -760,6 +756,7 @@ enum ReceiveResult { /// Enumeration of slave transaction termination conditions #[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] enum SlaveTermination { /// STOP condition received - normal end of transaction Stop, @@ -887,9 +884,9 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { /// This method blocks until the slave address is matched by a master. /// Returns the command type (Read/Write) and the matched address. pub fn blocking_listen(&mut self) -> Result { - trace!("I2C slave: listening for address match"); + trace!("I2C slave: starting blocking listen for address match"); let result = self.blocking_listen_with_timeout(self.timeout()); - trace!("I2C slave: listen result={:?}", result); + trace!("I2C slave: blocking listen complete, result={:?}", result); result } @@ -901,16 +898,15 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { /// /// Returns the total number of bytes transmitted (including padding). pub fn blocking_respond_to_read(&mut self, data: &[u8]) -> Result { - trace!("I2C slave: responding to read, data_len={}", data.len()); + trace!("I2C slave: starting blocking respond_to_read, data_len={}", data.len()); - // Check for zero-length read BEFORE any transmission setup if let Some(zero_length_result) = self.detect_zero_length_read(self.timeout())? { trace!("I2C slave: zero-length read detected"); return Ok(zero_length_result); } let result = self.transmit_to_master(data, self.timeout()); - trace!("I2C slave: read response complete, result={:?}", result); + trace!("I2C slave: blocking respond_to_read complete, result={:?}", result); result } @@ -922,9 +918,9 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { /// /// Returns the number of bytes stored in the buffer (not total received). pub fn blocking_respond_to_write(&mut self, buffer: &mut [u8]) -> Result { - trace!("I2C slave: responding to write, buffer_len={}", buffer.len()); + trace!("I2C slave: starting blocking respond_to_write, buffer_len={}", buffer.len()); let result = self.receive_from_master(buffer, self.timeout()); - trace!("I2C slave: write response complete, result={:?}", result); + trace!("I2C slave: blocking respond_to_write complete, result={:?}", result); result } @@ -965,31 +961,35 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { /// Transmit data to master in response to read request fn transmit_to_master(&mut self, data: &[u8], timeout: Timeout) -> Result { let mut bytes_transmitted = 0; + let mut padding_count = 0; loop { - // Determine next byte to send let byte_to_send = if bytes_transmitted < data.len() { data[bytes_transmitted] } else { + padding_count += 1; 0x00 // Send padding bytes when data is exhausted }; - // Attempt to send the byte match self.transmit_byte(byte_to_send, timeout)? { TransmitResult::Acknowledged => { bytes_transmitted += 1; - // Continue transmission }, TransmitResult::NotAcknowledged => { bytes_transmitted += 1; // Count the NACKed byte - break; // Normal end of read transaction + break; }, TransmitResult::Stopped | TransmitResult::Restarted => { - break; // Transaction terminated by master + break; } } } + if padding_count > 0 { + trace!("I2C slave: sent {} data bytes + {} padding bytes = {} total", + data.len(), padding_count, bytes_transmitted); + } + Ok(bytes_transmitted) } @@ -1076,14 +1076,19 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { /// Discard excess bytes when buffer is full fn discard_excess_bytes(&mut self, timeout: Timeout) -> Result<(), Error> { + let mut discarded_count = 0; + loop { match self.receive_byte(timeout)? { ReceiveResult::Data(_) => { - // Byte received and ACKed, but discarded + discarded_count += 1; continue; }, ReceiveResult::Stopped | ReceiveResult::Restarted => { - break; // Transaction completed + if discarded_count > 0 { + trace!("I2C slave: discarded {} excess bytes", discarded_count); + } + break; }, } } @@ -1316,12 +1321,12 @@ impl<'d> I2c<'d, Async, MultiMaster> { /// (Read/Write) and the matched address. This method will suspend until /// an address match occurs. pub async fn listen(&mut self) -> Result { + trace!("I2C slave: starting async listen for address match"); let state = self.state; let info = self.info; Self::enable_interrupts(info); - // Ensure interrupts are cleaned up on early exit let on_drop = OnDrop::new(|| { Self::disable_dma_and_interrupts(info); }); @@ -1330,10 +1335,12 @@ impl<'d> I2c<'d, Async, MultiMaster> { state.waker.register(cx.waker()); match Self::check_and_clear_error_flags(info) { - Err(e) => Poll::Ready(Err(e)), + Err(e) => { + error!("I2C slave: error during listen: {:?}", e); + Poll::Ready(Err(e)) + }, Ok(sr1) => { if sr1.addr() { - // Address matched - determine direction and decode address let sr2 = info.regs.sr2().read(); let direction = if sr2.tra() { SlaveCommandKind::Read @@ -1342,11 +1349,16 @@ impl<'d> I2c<'d, Async, MultiMaster> { }; let matched_address = match Self::decode_matched_address(sr2, info) { - Ok(addr) => addr, - Err(e) => return Poll::Ready(Err(e)), + Ok(addr) => { + trace!("I2C slave: address matched, direction={:?}, addr={:?}", direction, addr); + addr + }, + Err(e) => { + error!("I2C slave: failed to decode matched address: {:?}", e); + return Poll::Ready(Err(e)); + } }; - // Don't clear ADDR here - leave it for DMA setup in respond methods Poll::Ready(Ok(SlaveCommand { kind: direction, address: matched_address, @@ -1360,6 +1372,7 @@ impl<'d> I2c<'d, Async, MultiMaster> { }).await; drop(on_drop); + trace!("I2C slave: listen complete, result={:?}", result); result } @@ -1371,28 +1384,28 @@ impl<'d> I2c<'d, Async, MultiMaster> { /// /// Returns the number of bytes stored in the buffer (not total received). pub async fn respond_to_write(&mut self, buffer: &mut [u8]) -> Result { + trace!("I2C slave: starting respond_to_write, buffer_len={}", buffer.len()); + if buffer.is_empty() { + warn!("I2C slave: respond_to_write called with empty buffer"); return Err(Error::Overrun); } let state = self.state; let info = self.info; - // Use shared DMA setup self.setup_slave_dma_base(); - // Ensure proper cleanup on exit let on_drop = OnDrop::new(|| { Self::disable_dma_and_interrupts(info); }); - // Clear ADDR flag to release clock stretching info.regs.sr2().read(); - // Start DMA transfer and monitor completion let result = self.execute_slave_receive_transfer(buffer, state, info).await; drop(on_drop); + trace!("I2C slave: respond_to_write complete, result={:?}", result); result } @@ -1404,28 +1417,28 @@ impl<'d> I2c<'d, Async, MultiMaster> { /// /// Returns the total number of bytes transmitted (data + padding). pub async fn respond_to_read(&mut self, data: &[u8]) -> Result { + trace!("I2C slave: starting respond_to_read, data_len={}", data.len()); + if data.is_empty() { + warn!("I2C slave: respond_to_read called with empty data"); return Err(Error::Overrun); } let state = self.state; let info = self.info; - // Use shared DMA setup self.setup_slave_dma_base(); - // Ensure proper cleanup on exit let on_drop = OnDrop::new(|| { Self::disable_dma_and_interrupts(info); }); - // Clear ADDR flag to release clock stretching info.regs.sr2().read(); - // Start DMA transfer and monitor completion let result = self.execute_slave_transmit_transfer(data, state, info).await; drop(on_drop); + trace!("I2C slave: respond_to_read complete, result={:?}", result); result } @@ -1438,28 +1451,27 @@ impl<'d> I2c<'d, Async, MultiMaster> { state: &'static State, info: &'static Info ) -> Result { - // Start DMA transfer let dma_transfer = unsafe { let src = info.regs.dr().as_ptr() as *mut u8; self.rx_dma.as_mut().unwrap().read(src, buffer, Default::default()) }; - // Monitor for I2C events during transfer let i2c_monitor = Self::create_termination_monitor(state, info, &[SlaveTermination::Stop, SlaveTermination::Restart]); match select(dma_transfer, i2c_monitor).await { Either::Second(Err(e)) => { + error!("I2C slave: error during receive transfer: {:?}", e); Self::disable_dma_and_interrupts(info); Err(e) } Either::First(_) => { - // DMA completed first - handle potential excess bytes + trace!("I2C slave: DMA receive completed, handling excess bytes"); Self::disable_dma_and_interrupts(info); self.handle_excess_bytes(state, info).await?; Ok(buffer.len()) } - Either::Second(Ok(_)) => { - // I2C event occurred first - normal transaction end + Either::Second(Ok(termination)) => { + trace!("I2C slave: receive terminated by I2C event: {:?}", termination); Self::disable_dma_and_interrupts(info); Ok(buffer.len()) } @@ -1473,28 +1485,30 @@ impl<'d> I2c<'d, Async, MultiMaster> { state: &'static State, info: &'static Info ) -> Result { - // Start DMA transfer let dma_transfer = unsafe { let dst = info.regs.dr().as_ptr() as *mut u8; self.tx_dma.as_mut().unwrap().write(data, dst, Default::default()) }; - // Monitor for I2C events during transfer (NACK is normal for slave transmit) let i2c_monitor = Self::create_termination_monitor(state, info, &[SlaveTermination::Stop, SlaveTermination::Restart, SlaveTermination::Nack]); match select(dma_transfer, i2c_monitor).await { Either::Second(Err(e)) => { + error!("I2C slave: error during transmit transfer: {:?}", e); Self::disable_dma_and_interrupts(info); Err(e) } Either::First(_) => { - // DMA completed first - handle potential padding bytes + trace!("I2C slave: DMA transmit completed, handling padding bytes"); Self::disable_dma_and_interrupts(info); let padding_count = self.handle_padding_bytes(state, info).await?; - Ok(data.len() + padding_count) + let total_bytes = data.len() + padding_count; + trace!("I2C slave: sent {} data bytes + {} padding bytes = {} total", + data.len(), padding_count, total_bytes); + Ok(total_bytes) } - Either::Second(Ok(_)) => { - // I2C event occurred first - normal transaction end + Either::Second(Ok(termination)) => { + trace!("I2C slave: transmit terminated by I2C event: {:?}", termination); Self::disable_dma_and_interrupts(info); Ok(data.len()) } @@ -1549,25 +1563,32 @@ impl<'d> I2c<'d, Async, MultiMaster> { /// /// Reads and discards bytes until transaction termination to prevent interrupt flooding async fn handle_excess_bytes(&mut self, state: &'static State, info: &'static Info) -> Result<(), Error> { + let mut discarded_count = 0; + poll_fn(|cx| { state.waker.register(cx.waker()); match Self::check_and_clear_error_flags(info) { - Err(e) => Poll::Ready(Err(e)), + Err(e) => { + error!("I2C slave: error while discarding excess bytes: {:?}", e); + Poll::Ready(Err(e)) + }, Ok(sr1) => { - // Check for transaction termination first if let Some(termination) = Self::check_slave_termination_conditions(sr1) { match termination { SlaveTermination::Stop => Self::clear_stop_flag(info), - SlaveTermination::Restart => {}, // Leave ADDR for next operation + SlaveTermination::Restart => {}, SlaveTermination::Nack => unreachable!("NACK not expected during receive"), } + if discarded_count > 0 { + trace!("I2C slave: discarded {} excess bytes", discarded_count); + } return Poll::Ready(Ok(())); } - // If there's data to read, discard it if sr1.rxne() { let _discarded_byte = info.regs.dr().read().dr(); + discarded_count += 1; Self::enable_interrupts(info); return Poll::Pending; } @@ -1589,14 +1610,16 @@ impl<'d> I2c<'d, Async, MultiMaster> { state.waker.register(cx.waker()); match Self::check_and_clear_error_flags(info) { - Err(Error::Nack) => Poll::Ready(Ok(padding_count)), // Normal termination - Err(e) => Poll::Ready(Err(e)), + Err(Error::Nack) => Poll::Ready(Ok(padding_count)), + Err(e) => { + error!("I2C slave: error while sending padding bytes: {:?}", e); + Poll::Ready(Err(e)) + }, Ok(sr1) => { - // Check for transaction termination first if let Some(termination) = Self::check_slave_termination_conditions(sr1) { match termination { SlaveTermination::Stop => Self::clear_stop_flag(info), - SlaveTermination::Restart => {}, // Leave ADDR for next operation + SlaveTermination::Restart => {}, SlaveTermination::Nack => { info.regs.sr1().write(|reg| { reg.0 = !0; @@ -1607,7 +1630,6 @@ impl<'d> I2c<'d, Async, MultiMaster> { return Poll::Ready(Ok(padding_count)); } - // If transmit buffer is empty, send padding byte if sr1.txe() { info.regs.dr().write(|w| w.set_dr(0x00)); padding_count += 1; -- cgit From 572a40b4eee99b177733f50b08e29ff9b5ab6fa5 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sat, 23 Aug 2025 08:31:41 +0200 Subject: stm32/i2c_v1: Add async and blocking example code --- examples/stm32f4/src/bin/i2c_slave_async.rs | 123 +++++++++++++++++++++++++ examples/stm32f4/src/bin/i2c_slave_blocking.rs | 118 ++++++++++++++++++++++++ 2 files changed, 241 insertions(+) create mode 100644 examples/stm32f4/src/bin/i2c_slave_async.rs create mode 100644 examples/stm32f4/src/bin/i2c_slave_blocking.rs diff --git a/examples/stm32f4/src/bin/i2c_slave_async.rs b/examples/stm32f4/src/bin/i2c_slave_async.rs new file mode 100644 index 000000000..072c9875e --- /dev/null +++ b/examples/stm32f4/src/bin/i2c_slave_async.rs @@ -0,0 +1,123 @@ +//! I2C slave example using async operations with DMA +//! +//! This example demonstrates DMA-accelerated I2C slave operations, +//! which provide better performance and lower CPU overhead for +//! high-frequency I2C transactions. + +#![no_std] +#![no_main] + +use defmt_rtt as _; +use defmt::{error, info}; +use embassy_executor::Spawner; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_stm32::i2c::{self, I2c, SlaveAddrConfig, SlaveCommand, SlaveCommandKind, Address}; +use embassy_stm32::time::Hertz; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::mutex::Mutex; +use embassy_time::{Duration, Timer}; +use panic_probe as _; + +pub const I2C_SLAVE_ADDR: u8 = 0x42; +pub const BUFFER_SIZE: usize = 8; +static I2C_BUFFER: Mutex = Mutex::new([0; BUFFER_SIZE]); + +bind_interrupts!(struct Irqs { + I2C1_EV => i2c::EventInterruptHandler; + I2C1_ER => i2c::ErrorInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + + // Configure I2C + let mut i2c_config = i2c::Config::default(); + i2c_config.sda_pullup = false; + i2c_config.scl_pullup = false; + i2c_config.frequency = Hertz(100_000); // 100kHz I2C speed + + // Initialize I2C as master first + let i2c_master = I2c::new( + p.I2C1, + p.PB8, // SCL + p.PB9, // SDA + Irqs, + p.DMA1_CH6, // TX DMA + p.DMA1_CH0, // RX DMA + i2c_config, + ); + + // Convert to MultiMaster mode + let slave_config = SlaveAddrConfig::basic(I2C_SLAVE_ADDR); + let i2c_slave = i2c_master.into_slave_multimaster(slave_config); + + spawner.spawn(i2c_slave_task(i2c_slave)).unwrap(); +} + +#[embassy_executor::task] +pub async fn i2c_slave_task(mut i2c_slave: I2c<'static, embassy_stm32::mode::Async, i2c::mode::MultiMaster>) { + info!("Async I2C slave ready at address 0x{:02X}", I2C_SLAVE_ADDR); + + loop { + match i2c_slave.listen().await { + Ok(SlaveCommand { kind: SlaveCommandKind::Write, address }) => { + let addr_val = match address { + Address::SevenBit(addr) => addr, + Address::TenBit(addr) => (addr & 0xFF) as u8, + }; + + info!("I2C: Received write command - Address 0x{:02X}", addr_val); + + let mut data_buffer = I2C_BUFFER.lock().await; + + match i2c_slave.respond_to_write(&mut *data_buffer).await { + Ok(_) => { + info!("I2C: Data received - Buffer now contains: 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}", + data_buffer[0], data_buffer[1], data_buffer[2], data_buffer[3], data_buffer[4], data_buffer[5], data_buffer[6], data_buffer[7]); + } + Err(e) => { + error!("I2C: Write error: {}", format_i2c_error(&e)); + } + } + } + + Ok(SlaveCommand { kind: SlaveCommandKind::Read, address }) => { + let addr_val = match address { + Address::SevenBit(addr) => addr, + Address::TenBit(addr) => (addr & 0xFF) as u8, // Show low byte for 10-bit + }; + + info!("I2C: Received read command - Address 0x{:02X}", addr_val); + + let data_buffer = I2C_BUFFER.lock().await; + + match i2c_slave.respond_to_read(&data_buffer[..BUFFER_SIZE]).await { + Ok(_) => { + info!("I2C: Responded to read command"); + } + Err(e) => { + error!("I2C: Read error: {}", format_i2c_error(&e)); + } + } + } + + Err(e) => { + error!("I2C: Listen error: {}", format_i2c_error(&e)); + Timer::after(Duration::from_millis(100)).await; + } + } + } +} + +fn format_i2c_error(e: &embassy_stm32::i2c::Error) -> &'static str { + match e { + embassy_stm32::i2c::Error::Bus => "Bus", + embassy_stm32::i2c::Error::Arbitration => "Arbitration", + embassy_stm32::i2c::Error::Nack => "Nack", + embassy_stm32::i2c::Error::Timeout => "Timeout", + embassy_stm32::i2c::Error::Crc => "Crc", + embassy_stm32::i2c::Error::Overrun => "Overrun", + embassy_stm32::i2c::Error::ZeroLengthTransfer => "ZeroLengthTransfer", + } +} diff --git a/examples/stm32f4/src/bin/i2c_slave_blocking.rs b/examples/stm32f4/src/bin/i2c_slave_blocking.rs new file mode 100644 index 000000000..c55f2f6c1 --- /dev/null +++ b/examples/stm32f4/src/bin/i2c_slave_blocking.rs @@ -0,0 +1,118 @@ +//! Complete I2C slave example using blocking operations +//! +//! This example shows how to set up an STM32F4 as an I2C slave device +//! that can handle both read and write transactions from master devices. + +#![no_std] +#![no_main] + +use defmt_rtt as _; +use defmt::{error, info}; +use embassy_executor::Spawner; +use embassy_stm32::i2c::{self, I2c, SlaveAddrConfig, SlaveCommand, SlaveCommandKind, Address}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_stm32::time::Hertz; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::mutex::Mutex; +use embassy_time::{Duration, Timer}; +use panic_probe as _; + +pub const I2C_SLAVE_ADDR: u8 = 0x42; +pub const BUFFER_SIZE: usize = 8; +static I2C_BUFFER: Mutex = Mutex::new([0; BUFFER_SIZE]); + +bind_interrupts!(struct Irqs { + I2C1_EV => i2c::EventInterruptHandler; + I2C1_ER => i2c::ErrorInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + + // Configure I2C + let mut i2c_config = i2c::Config::default(); + i2c_config.sda_pullup = false; + i2c_config.scl_pullup = false; + i2c_config.frequency = Hertz(100_000); + i2c_config.timeout = embassy_time::Duration::from_millis(30000); + + // Initialize I2C as master first + let i2c_master = I2c::new_blocking( + p.I2C1, + p.PB8, // SCL + p.PB9, // SDA + i2c_config, + ); + + // Convert to slave+master mode + let slave_config = SlaveAddrConfig::basic(I2C_SLAVE_ADDR); + let i2c_slave = i2c_master.into_slave_multimaster(slave_config); + + spawner.spawn(i2c_slave_task(i2c_slave)).unwrap(); +} + +#[embassy_executor::task] +pub async fn i2c_slave_task(mut i2c_slave: I2c<'static, embassy_stm32::mode::Blocking, i2c::mode::MultiMaster>) { + info!("Blocking I2C slave ready at address 0x{:02X}", I2C_SLAVE_ADDR); + + loop { + match i2c_slave.blocking_listen() { + Ok(SlaveCommand { kind: SlaveCommandKind::Write, address }) => { + let addr_val = match address { + Address::SevenBit(addr) => addr, + Address::TenBit(addr) => (addr & 0xFF) as u8, + }; + + info!("I2C: Received write command - Address 0x{:02X}", addr_val); + let mut data_buffer = I2C_BUFFER.lock().await; + + match i2c_slave.blocking_respond_to_write(&mut *data_buffer) { + Ok(bytes_received) => { + info!("I2C: Received {} bytes - Buffer now contains: 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}", + bytes_received, data_buffer[0], data_buffer[1], data_buffer[2], data_buffer[3], data_buffer[4], data_buffer[5], data_buffer[6], data_buffer[7]); + } + Err(e) => { + error!("I2C: Write error: {}", format_i2c_error(&e)); + } + } + } + + Ok(SlaveCommand { kind: SlaveCommandKind::Read, address }) => { + let addr_val = match address { + Address::SevenBit(addr) => addr, + Address::TenBit(addr) => (addr & 0xFF) as u8, // Show low byte for 10-bit + }; + + info!("I2C: Received read command - Address 0x{:02X}", addr_val); + let data_buffer = I2C_BUFFER.lock().await; + + match i2c_slave.blocking_respond_to_read(&data_buffer[..BUFFER_SIZE]) { + Ok(bytes_sent) => { + info!("I2C: Responded to read - {} bytes sent", bytes_sent); + } + Err(e) => { + error!("I2C: Read error: {}", format_i2c_error(&e)); + } + } + } + + Err(e) => { + error!("I2C: Listen error: {}", format_i2c_error(&e)); + Timer::after(Duration::from_millis(100)).await; + } + } + } +} + +fn format_i2c_error(e: &embassy_stm32::i2c::Error) -> &'static str { + match e { + embassy_stm32::i2c::Error::Bus => "Bus", + embassy_stm32::i2c::Error::Arbitration => "Arbitration", + embassy_stm32::i2c::Error::Nack => "Nack", + embassy_stm32::i2c::Error::Timeout => "Timeout", + embassy_stm32::i2c::Error::Crc => "Crc", + embassy_stm32::i2c::Error::Overrun => "Overrun", + embassy_stm32::i2c::Error::ZeroLengthTransfer => "ZeroLengthTransfer", + } +} -- cgit From fbefd6c36aa123e479e39d2bd8c37d987e36597c Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sat, 23 Aug 2025 09:17:49 +0200 Subject: Revert "stm32/i2c: v1 and v2 now has separate definitions for the State struct" This reverts commit b690a0314f0f2e42137ad4b3e867e056c1d3c14e. --- embassy-stm32/src/i2c/mod.rs | 17 ++++++++++++++--- embassy-stm32/src/i2c/v1.rs | 15 --------------- embassy-stm32/src/i2c/v2.rs | 15 --------------- 3 files changed, 14 insertions(+), 33 deletions(-) diff --git a/embassy-stm32/src/i2c/mod.rs b/embassy-stm32/src/i2c/mod.rs index 675a392f9..4caa35efb 100644 --- a/embassy-stm32/src/i2c/mod.rs +++ b/embassy-stm32/src/i2c/mod.rs @@ -5,9 +5,6 @@ #[cfg_attr(any(i2c_v2, i2c_v3), path = "v2.rs")] mod _version; -// Type alias for the peri_trait! macro -type State = _version::State; - mod config; use core::future::Future; @@ -16,6 +13,7 @@ use core::marker::PhantomData; pub use config::*; use embassy_hal_internal::Peri; +use embassy_sync::waitqueue::AtomicWaker; #[cfg(feature = "time")] use embassy_time::{Duration, Instant}; use mode::MasterMode; @@ -277,6 +275,19 @@ impl Timeout { } } +struct State { + #[allow(unused)] + waker: AtomicWaker, +} + +impl State { + const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + } + } +} + struct Info { regs: crate::pac::i2c::I2c, rcc: RccInfo, diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index b362ab017..879d1686b 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -17,21 +17,6 @@ use super::*; use crate::mode::Mode; use crate::pac::i2c; -use embassy_sync::waitqueue::AtomicWaker; - -/// I2C v2 peripheral state -pub(crate) struct State { - pub(crate) waker: AtomicWaker, -} - -impl State { - pub(crate) const fn new() -> Self { - Self { - waker: AtomicWaker::new(), - } - } -} - // /!\ /!\ // /!\ Implementation note! /!\ // /!\ /!\ diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index 72a7d05ab..3b09f1b34 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -12,21 +12,6 @@ use stm32_metapac::i2c::vals::{Addmode, Oamsk}; use super::*; use crate::pac::i2c; -use embassy_sync::waitqueue::AtomicWaker; - -/// I2C v2 peripheral state -pub(crate) struct State { - pub(crate) waker: AtomicWaker, -} - -impl State { - pub(crate) const fn new() -> Self { - Self { - waker: AtomicWaker::new(), - } - } -} - impl From for Oamsk { fn from(value: AddrMask) -> Self { match value { -- cgit From 109c2cbdc4711bd929308944c22d079e986ebeb9 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sat, 23 Aug 2025 10:01:53 +0200 Subject: stm32/i2c: Add core::fmt::Debug for enums --- embassy-stm32/src/i2c/config.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/embassy-stm32/src/i2c/config.rs b/embassy-stm32/src/i2c/config.rs index 4e3b736c7..74fac14b2 100644 --- a/embassy-stm32/src/i2c/config.rs +++ b/embassy-stm32/src/i2c/config.rs @@ -4,7 +4,7 @@ use crate::gpio::{AfType, OutputType, Speed}; use crate::time::Hertz; #[repr(u8)] -#[derive(Copy, Clone)] +#[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] /// Bits of the I2C OA2 register to mask out. pub enum AddrMask { @@ -60,7 +60,7 @@ impl Address { } } -#[derive(Copy, Clone)] +#[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] /// The second Own Address register. pub struct OA2 { @@ -70,7 +70,7 @@ pub struct OA2 { pub mask: AddrMask, } -#[derive(Copy, Clone)] +#[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] /// The Own Address(es) of the I2C peripheral. pub enum OwnAddresses { @@ -88,7 +88,7 @@ pub enum OwnAddresses { } /// Slave Configuration -#[derive(Copy, Clone)] +#[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct SlaveAddrConfig { /// Target Address(es) -- cgit From 5d3a1dd7e549de578c6a77d3998beddf1b1474d3 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sat, 23 Aug 2025 10:28:50 +0200 Subject: stm32/i2c: Remove rustdoc example for assign_operation_framing function --- embassy-stm32/src/i2c/mod.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/embassy-stm32/src/i2c/mod.rs b/embassy-stm32/src/i2c/mod.rs index 4caa35efb..00330c97e 100644 --- a/embassy-stm32/src/i2c/mod.rs +++ b/embassy-stm32/src/i2c/mod.rs @@ -525,16 +525,7 @@ impl OperationFraming { /// /// # Returns /// An iterator over (operation, framing) pairs, or an error if the transaction is invalid -/// -/// # Example -/// ```rust -/// for (op, framing) in assign_operation_framing(operations)? { -/// match op { -/// Operation::Read(buffer) => self.read_with_framing(addr, buffer, framing).await?, -/// Operation::Write(data) => self.write_with_framing(addr, data, framing).await?, -/// } -/// } -/// ``` +/// #[allow(dead_code)] fn assign_operation_framing<'a, 'b: 'a>( operations: &'a mut [embedded_hal_1::i2c::Operation<'b>], -- cgit From a51c0320e907d895bb1f55e8960a69df5f5a276b Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sat, 23 Aug 2025 10:36:56 +0200 Subject: stm32: Add entry in CHANGELOG.md file --- embassy-stm32/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index 9cd4d5951..c93334102 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - fix: Fix performing a hash after performing a hmac - chore: Updated stm32-metapac and stm32-data dependencies - fix: Fix XSPI not disabling alternate bytes when they were previously enabled +- feat: Add I2C MultiMaster (Slave) support for I2C v1 ## 0.3.0 - 2025-08-12 -- cgit From 97d5de640a90ce31b81411a742b33d9b86d2b441 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sat, 23 Aug 2025 10:46:39 +0200 Subject: stm32: Run cargo fmt --- embassy-stm32/src/i2c/mod.rs | 12 +- embassy-stm32/src/i2c/v1.rs | 362 ++++++++++++++++++++++++------------------- 2 files changed, 212 insertions(+), 162 deletions(-) diff --git a/embassy-stm32/src/i2c/mod.rs b/embassy-stm32/src/i2c/mod.rs index 00330c97e..7f5cde1c5 100644 --- a/embassy-stm32/src/i2c/mod.rs +++ b/embassy-stm32/src/i2c/mod.rs @@ -442,7 +442,7 @@ impl<'d, IM: MasterMode> embedded_hal_async::i2c::I2c for I2c<'d, Async, IM> { /// controlling the generation of start conditions (ST or SR), stop conditions (SP), /// and ACK/NACK behavior for read operations. /// -/// For write operations, some framing configurations are functionally identical +/// For write operations, some framing configurations are functionally identical /// because they differ only in ACK/NACK treatment which is relevant only for reads: /// /// - `First` and `FirstAndNext` behave identically for writes @@ -489,7 +489,7 @@ impl OperationFraming { } /// Returns true if NACK should be sent after the last byte received in a read operation. - /// + /// /// This signals the end of a read sequence and releases the bus for the master's /// next transmission (or stop condition). fn send_nack(self) -> bool { @@ -525,7 +525,7 @@ impl OperationFraming { /// /// # Returns /// An iterator over (operation, framing) pairs, or an error if the transaction is invalid -/// +/// #[allow(dead_code)] fn assign_operation_framing<'a, 'b: 'a>( operations: &'a mut [embedded_hal_1::i2c::Operation<'b>], @@ -535,7 +535,7 @@ fn assign_operation_framing<'a, 'b: 'a>( // Validate that no read operations have empty buffers before starting the transaction. // Empty read operations would risk halting with an error mid-transaction. // - // Note: We could theoretically allow empty read operations within consecutive read + // Note: We could theoretically allow empty read operations within consecutive read // sequences as long as the final merged read has at least one byte, but this would // complicate the logic significantly and create error-prone edge cases. if operations.iter().any(|op| match op { @@ -558,7 +558,7 @@ fn assign_operation_framing<'a, 'b: 'a>( // Compute the appropriate framing based on three key properties: // // 1. **Start Condition**: Generate (repeated) start for first operation of each type - // 2. **Stop Condition**: Generate stop for the final operation in the entire transaction + // 2. **Stop Condition**: Generate stop for the final operation in the entire transaction // 3. **ACK/NACK for Reads**: For read operations, send ACK if more reads follow in the // sequence, or NACK for the final read in a sequence (before write or transaction end) // @@ -571,7 +571,7 @@ fn assign_operation_framing<'a, 'b: 'a>( (true, Some(Read(_))) => OperationFraming::FirstAndNext, // First operation of type, next operation is write (end current sequence) (true, Some(Write(_))) => OperationFraming::First, - + // Continuation operation, and it's the final operation overall (false, None) => OperationFraming::Last, // Continuation operation, next operation is also a read (continue read sequence) diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index 879d1686b..d1e15d6f2 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -153,7 +153,13 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { Ok(sr1) } - fn write_bytes(&mut self, address: u8, write_buffer: &[u8], timeout: Timeout, framing: OperationFraming) -> Result<(), Error> { + fn write_bytes( + &mut self, + address: u8, + write_buffer: &[u8], + timeout: Timeout, + framing: OperationFraming, + ) -> Result<(), Error> { if framing.send_start() { // Send a START condition @@ -313,7 +319,12 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { } /// Blocking write, restart, read. - pub fn blocking_write_read(&mut self, address: u8, write_buffer: &[u8], read_buffer: &mut [u8]) -> Result<(), Error> { + pub fn blocking_write_read( + &mut self, + address: u8, + write_buffer: &[u8], + read_buffer: &mut [u8], + ) -> Result<(), Error> { // Check empty read buffer before starting transaction. Otherwise, we would not generate the // stop condition below. if read_buffer.is_empty() { @@ -345,7 +356,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { Ok(()) } - + /// Can be used by both blocking and async implementations #[inline] // pretty sure this should always be inlined fn enable_interrupts(info: &'static Info) { @@ -366,11 +377,15 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { let _ = info.regs.sr1().read(); info.regs.cr1().modify(|_| {}); // Dummy write to clear STOPF } - } impl<'d, IM: MasterMode> I2c<'d, Async, IM> { - async fn write_with_framing(&mut self, address: u8, write_buffer: &[u8], framing: OperationFraming) -> Result<(), Error> { + async fn write_with_framing( + &mut self, + address: u8, + write_buffer: &[u8], + framing: OperationFraming, + ) -> Result<(), Error> { self.info.regs.cr2().modify(|w| { // Note: Do not enable the ITBUFEN bit in the I2C_CR2 register if DMA is used for // reception. @@ -453,7 +468,10 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { // this address from the memory after each TxE event. let dst = self.info.regs.dr().as_ptr() as *mut u8; - self.tx_dma.as_mut().unwrap().write(write_buffer, dst, Default::default()) + self.tx_dma + .as_mut() + .unwrap() + .write(write_buffer, dst, Default::default()) }; // Wait for bytes to be sent, or an error to occur. @@ -530,7 +548,12 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { Ok(()) } - async fn read_with_framing(&mut self, address: u8, read_buffer: &mut [u8], framing: OperationFraming) -> Result<(), Error> { + async fn read_with_framing( + &mut self, + address: u8, + read_buffer: &mut [u8], + framing: OperationFraming, + ) -> Result<(), Error> { if read_buffer.is_empty() { return Err(Error::Overrun); } @@ -694,8 +717,10 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { return Err(Error::Overrun); } - self.write_with_framing(address, write_buffer, OperationFraming::First).await?; - self.read_with_framing(address, read_buffer, OperationFraming::FirstAndLast).await + self.write_with_framing(address, write_buffer, OperationFraming::First) + .await?; + self.read_with_framing(address, read_buffer, OperationFraming::FirstAndLast) + .await } /// Transaction with operations. @@ -758,13 +783,13 @@ impl<'d, M: Mode> I2c<'d, M, Master> { info: self.info, state: self.state, kernel_clock: self.kernel_clock, - tx_dma: self.tx_dma.take(), // Use take() to move ownership - rx_dma: self.rx_dma.take(), // Use take() to move ownership + tx_dma: self.tx_dma.take(), // Use take() to move ownership + rx_dma: self.rx_dma.take(), // Use take() to move ownership #[cfg(feature = "time")] timeout: self.timeout, _phantom: PhantomData, _phantom2: PhantomData, - _drop_guard: self._drop_guard, // Move the drop guard + _drop_guard: self._drop_guard, // Move the drop guard }; slave.init_slave(slave_addr_config); slave @@ -776,46 +801,46 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { /// Initialize slave mode with address configuration pub(crate) fn init_slave(&mut self, config: SlaveAddrConfig) { trace!("I2C slave: initializing with config={:?}", config); - + // Disable peripheral for configuration self.info.regs.cr1().modify(|reg| reg.set_pe(false)); - + // Configure slave addresses self.apply_address_configuration(config); - + // Enable peripheral with slave settings self.info.regs.cr1().modify(|reg| { reg.set_pe(true); reg.set_ack(true); // Enable acknowledgment for slave mode reg.set_nostretch(false); // Allow clock stretching for processing time }); - + trace!("I2C slave: initialization complete"); } - + /// Apply the complete address configuration for slave mode fn apply_address_configuration(&mut self, config: SlaveAddrConfig) { match config.addr { OwnAddresses::OA1(addr) => { self.configure_primary_address(addr); self.disable_secondary_address(); - }, + } OwnAddresses::OA2(oa2) => { self.configure_default_primary_address(); self.configure_secondary_address(oa2.addr); // v1 ignores mask - }, + } OwnAddresses::Both { oa1, oa2 } => { self.configure_primary_address(oa1); self.configure_secondary_address(oa2.addr); // v1 ignores mask } } - + // Configure general call detection if config.general_call { self.info.regs.cr1().modify(|w| w.set_engc(true)); } } - + /// Configure the primary address (OA1) register fn configure_primary_address(&mut self, addr: Address) { match addr { @@ -825,7 +850,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { reg.set_add(hw_addr); reg.set_addmode(i2c::vals::Addmode::BIT7); }); - }, + } Address::TenBit(addr) => { self.info.regs.oar1().write(|reg| { reg.set_add(addr); @@ -833,11 +858,11 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { }); } } - + // Set required bit 14 as per reference manual self.info.regs.oar1().modify(|reg| reg.0 |= 1 << 14); } - + /// Configure the secondary address (OA2) register fn configure_secondary_address(&mut self, addr: u8) { self.info.regs.oar2().write(|reg| { @@ -845,7 +870,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { reg.set_endual(i2c::vals::Endual::DUAL); }); } - + /// Set a default primary address when using OA2-only mode fn configure_default_primary_address(&mut self) { self.info.regs.oar1().write(|reg| { @@ -854,7 +879,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { }); self.info.regs.oar1().modify(|reg| reg.0 |= 1 << 14); } - + /// Disable secondary address when not needed fn disable_secondary_address(&mut self) { self.info.regs.oar2().write(|reg| { @@ -865,7 +890,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { impl<'d, M: Mode> I2c<'d, M, MultiMaster> { /// Listen for incoming I2C address match and return the command type - /// + /// /// This method blocks until the slave address is matched by a master. /// Returns the command type (Read/Write) and the matched address. pub fn blocking_listen(&mut self) -> Result { @@ -874,7 +899,7 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { trace!("I2C slave: blocking listen complete, result={:?}", result); result } - + /// Respond to a master read request by transmitting data /// /// Sends the provided data to the master. If the master requests more bytes @@ -884,17 +909,17 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { /// Returns the total number of bytes transmitted (including padding). pub fn blocking_respond_to_read(&mut self, data: &[u8]) -> Result { trace!("I2C slave: starting blocking respond_to_read, data_len={}", data.len()); - + if let Some(zero_length_result) = self.detect_zero_length_read(self.timeout())? { trace!("I2C slave: zero-length read detected"); return Ok(zero_length_result); } - + let result = self.transmit_to_master(data, self.timeout()); trace!("I2C slave: blocking respond_to_read complete, result={:?}", result); result } - + /// Respond to a master write request by receiving data /// /// Receives data from the master into the provided buffer. If the master @@ -903,23 +928,26 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { /// /// Returns the number of bytes stored in the buffer (not total received). pub fn blocking_respond_to_write(&mut self, buffer: &mut [u8]) -> Result { - trace!("I2C slave: starting blocking respond_to_write, buffer_len={}", buffer.len()); + trace!( + "I2C slave: starting blocking respond_to_write, buffer_len={}", + buffer.len() + ); let result = self.receive_from_master(buffer, self.timeout()); trace!("I2C slave: blocking respond_to_write complete, result={:?}", result); result } - + // Private implementation methods - + /// Wait for address match and determine transaction type fn blocking_listen_with_timeout(&mut self, timeout: Timeout) -> Result { // Ensure interrupts are disabled for blocking operation self.disable_i2c_interrupts(); - + // Wait for address match (ADDR flag) loop { let sr1 = Self::read_status_and_handle_errors(self.info)?; - + if sr1.addr() { // Address matched - read SR2 to get direction and clear ADDR flag let sr2 = self.info.regs.sr2().read(); @@ -928,26 +956,30 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { } else { SlaveCommandKind::Write }; - + // Use the static method instead of the instance method let matched_address = Self::decode_matched_address(sr2, self.info)?; - trace!("I2C slave: address matched, direction={:?}, addr={:?}", direction, matched_address); - + trace!( + "I2C slave: address matched, direction={:?}, addr={:?}", + direction, + matched_address + ); + return Ok(SlaveCommand { kind: direction, address: matched_address, }); } - + timeout.check()?; } } - + /// Transmit data to master in response to read request fn transmit_to_master(&mut self, data: &[u8], timeout: Timeout) -> Result { let mut bytes_transmitted = 0; let mut padding_count = 0; - + loop { let byte_to_send = if bytes_transmitted < data.len() { data[bytes_transmitted] @@ -955,217 +987,221 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { padding_count += 1; 0x00 // Send padding bytes when data is exhausted }; - + match self.transmit_byte(byte_to_send, timeout)? { TransmitResult::Acknowledged => { bytes_transmitted += 1; - }, + } TransmitResult::NotAcknowledged => { bytes_transmitted += 1; // Count the NACKed byte break; - }, + } TransmitResult::Stopped | TransmitResult::Restarted => { break; } } } - + if padding_count > 0 { - trace!("I2C slave: sent {} data bytes + {} padding bytes = {} total", - data.len(), padding_count, bytes_transmitted); + trace!( + "I2C slave: sent {} data bytes + {} padding bytes = {} total", + data.len(), + padding_count, + bytes_transmitted + ); } - + Ok(bytes_transmitted) } - + /// Receive data from master during write request fn receive_from_master(&mut self, buffer: &mut [u8], timeout: Timeout) -> Result { let mut bytes_stored = 0; - + // Receive bytes that fit in buffer while bytes_stored < buffer.len() { match self.receive_byte(timeout)? { ReceiveResult::Data(byte) => { buffer[bytes_stored] = byte; bytes_stored += 1; - }, + } ReceiveResult::Stopped | ReceiveResult::Restarted => { return Ok(bytes_stored); - }, + } } } - + // Handle buffer overflow by discarding excess bytes if bytes_stored == buffer.len() { trace!("I2C slave: buffer full, discarding excess bytes"); self.discard_excess_bytes(timeout)?; } - + Ok(bytes_stored) } /// Detect zero-length read pattern early - /// + /// /// Zero-length reads occur when a master sends START+ADDR+R followed immediately /// by NACK+STOP without wanting any data. This must be detected before attempting /// to transmit any bytes to avoid SDA line issues. fn detect_zero_length_read(&mut self, _timeout: Timeout) -> Result, Error> { // Quick check for immediate termination signals let sr1 = self.info.regs.sr1().read(); - + // Check for immediate NACK (fastest zero-length pattern) if sr1.af() { self.clear_acknowledge_failure(); return Ok(Some(0)); } - + // Check for immediate STOP (alternative zero-length pattern) if sr1.stopf() { Self::clear_stop_flag(self.info); return Ok(Some(0)); } - + // Give a brief window for master to send termination signals // This handles masters that have slight delays between address ACK and NACK const ZERO_LENGTH_DETECTION_CYCLES: u32 = 20; // ~5-10µs window - + for _ in 0..ZERO_LENGTH_DETECTION_CYCLES { let sr1 = self.info.regs.sr1().read(); - + // Immediate NACK indicates zero-length read if sr1.af() { self.clear_acknowledge_failure(); return Ok(Some(0)); } - + // Immediate STOP indicates zero-length read if sr1.stopf() { Self::clear_stop_flag(self.info); return Ok(Some(0)); } - + // If TXE becomes ready, master is waiting for data - not zero-length if sr1.txe() { return Ok(None); // Proceed with normal transmission } - + // If RESTART detected, handle as zero-length if sr1.addr() { return Ok(Some(0)); } } - + // No zero-length pattern detected within the window Ok(None) } - + /// Discard excess bytes when buffer is full fn discard_excess_bytes(&mut self, timeout: Timeout) -> Result<(), Error> { let mut discarded_count = 0; - + loop { match self.receive_byte(timeout)? { ReceiveResult::Data(_) => { discarded_count += 1; continue; - }, + } ReceiveResult::Stopped | ReceiveResult::Restarted => { if discarded_count > 0 { trace!("I2C slave: discarded {} excess bytes", discarded_count); } break; - }, + } } } Ok(()) } - + /// Send a single byte and wait for master's response fn transmit_byte(&mut self, byte: u8, timeout: Timeout) -> Result { // Wait for transmit buffer ready self.wait_for_transmit_ready(timeout)?; - + // Send the byte self.info.regs.dr().write(|w| w.set_dr(byte)); - + // Wait for transmission completion or master response self.wait_for_transmit_completion(timeout) } - + /// Wait until transmit buffer is ready (TXE flag set) fn wait_for_transmit_ready(&mut self, timeout: Timeout) -> Result<(), Error> { loop { let sr1 = Self::read_status_and_handle_errors(self.info)?; - + // Check for early termination conditions if let Some(result) = Self::check_early_termination(sr1) { return Err(self.handle_early_termination(result)); } - + if sr1.txe() { return Ok(()); // Ready to transmit } - + timeout.check()?; } } - + /// Wait for byte transmission completion or master response fn wait_for_transmit_completion(&mut self, timeout: Timeout) -> Result { loop { let sr1 = self.info.regs.sr1().read(); - + // Check flags in priority order if sr1.af() { self.clear_acknowledge_failure(); return Ok(TransmitResult::NotAcknowledged); } - + if sr1.btf() { return Ok(TransmitResult::Acknowledged); } - + if sr1.stopf() { Self::clear_stop_flag(self.info); return Ok(TransmitResult::Stopped); } - + if sr1.addr() { return Ok(TransmitResult::Restarted); } - + // Check for other error conditions self.check_for_hardware_errors(sr1)?; - + timeout.check()?; } } - + /// Receive a single byte or detect transaction termination fn receive_byte(&mut self, timeout: Timeout) -> Result { loop { let sr1 = Self::read_status_and_handle_errors(self.info)?; - + // Check for received data first (prioritize data over control signals) if sr1.rxne() { let byte = self.info.regs.dr().read().dr(); return Ok(ReceiveResult::Data(byte)); } - + // Check for transaction termination if sr1.addr() { return Ok(ReceiveResult::Restarted); } - + if sr1.stopf() { Self::clear_stop_flag(self.info); return Ok(ReceiveResult::Stopped); } - + timeout.check()?; } } - + /// Determine which slave address was matched based on SR2 flags fn decode_matched_address(sr2: i2c::regs::Sr2, info: &'static Info) -> Result { if sr2.gencall() { @@ -1184,16 +1220,14 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { i2c::vals::Addmode::BIT7 => { let addr = (oar1.add() >> 1) as u8; Ok(Address::SevenBit(addr)) - }, - i2c::vals::Addmode::BIT10 => { - Ok(Address::TenBit(oar1.add())) - }, + } + i2c::vals::Addmode::BIT10 => Ok(Address::TenBit(oar1.add())), } } } - + // Helper methods for hardware interaction - + /// Read status register and handle I2C errors (except NACK in slave mode) fn read_status_and_handle_errors(info: &'static Info) -> Result { match Self::check_and_clear_error_flags(info) { @@ -1201,11 +1235,11 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { Err(Error::Nack) => { // In slave mode, NACK is normal protocol behavior, not an error Ok(info.regs.sr1().read()) - }, + } Err(other_error) => Err(other_error), } } - + /// Check for conditions that cause early termination of operations fn check_early_termination(sr1: i2c::regs::Sr1) -> Option { if sr1.stopf() { @@ -1218,27 +1252,27 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { None } } - + /// Convert early termination to appropriate error fn handle_early_termination(&mut self, result: TransmitResult) -> Error { match result { TransmitResult::Stopped => { Self::clear_stop_flag(self.info); Error::Bus // Unexpected STOP during setup - }, + } TransmitResult::Restarted => { Error::Bus // Unexpected RESTART during setup - }, + } TransmitResult::NotAcknowledged => { self.clear_acknowledge_failure(); Error::Bus // Unexpected NACK during setup - }, + } TransmitResult::Acknowledged => { unreachable!() // This should never be passed to this function } } } - + /// Check for hardware-level I2C errors during transmission fn check_for_hardware_errors(&self, sr1: i2c::regs::Sr1) -> Result<(), Error> { if sr1.timeout() || sr1.ovr() || sr1.arlo() || sr1.berr() { @@ -1247,7 +1281,7 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { } Ok(()) } - + /// Disable I2C event and error interrupts for blocking operations fn disable_i2c_interrupts(&mut self) { self.info.regs.cr2().modify(|w| { @@ -1255,7 +1289,7 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { w.set_iterren(false); }); } - + /// Clear the acknowledge failure flag fn clear_acknowledge_failure(&mut self) { self.info.regs.sr1().write(|reg| { @@ -1268,11 +1302,11 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { fn setup_slave_dma_base(&mut self) { self.info.regs.cr2().modify(|w| { w.set_itbufen(false); // Always disable buffer interrupts when using DMA - w.set_dmaen(true); // Enable DMA requests - w.set_last(false); // LAST bit not used in slave mode for v1 hardware + w.set_dmaen(true); // Enable DMA requests + w.set_last(false); // LAST bit not used in slave mode for v1 hardware }); } - + /// Disable DMA and interrupts in a critical section fn disable_dma_and_interrupts(info: &'static Info) { critical_section::with(|_| { @@ -1301,7 +1335,7 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { impl<'d> I2c<'d, Async, MultiMaster> { /// Async listen for incoming I2C messages using interrupts - /// + /// /// Waits for a master to address this slave and returns the command type /// (Read/Write) and the matched address. This method will suspend until /// an address match occurs. @@ -1309,7 +1343,7 @@ impl<'d> I2c<'d, Async, MultiMaster> { trace!("I2C slave: starting async listen for address match"); let state = self.state; let info = self.info; - + Self::enable_interrupts(info); let on_drop = OnDrop::new(|| { @@ -1323,7 +1357,7 @@ impl<'d> I2c<'d, Async, MultiMaster> { Err(e) => { error!("I2C slave: error during listen: {:?}", e); Poll::Ready(Err(e)) - }, + } Ok(sr1) => { if sr1.addr() { let sr2 = info.regs.sr2().read(); @@ -1332,18 +1366,18 @@ impl<'d> I2c<'d, Async, MultiMaster> { } else { SlaveCommandKind::Write }; - + let matched_address = match Self::decode_matched_address(sr2, info) { Ok(addr) => { trace!("I2C slave: address matched, direction={:?}, addr={:?}", direction, addr); addr - }, + } Err(e) => { error!("I2C slave: failed to decode matched address: {:?}", e); return Poll::Ready(Err(e)); } }; - + Poll::Ready(Ok(SlaveCommand { kind: direction, address: matched_address, @@ -1354,7 +1388,8 @@ impl<'d> I2c<'d, Async, MultiMaster> { } } } - }).await; + }) + .await; drop(on_drop); trace!("I2C slave: listen complete, result={:?}", result); @@ -1362,15 +1397,15 @@ impl<'d> I2c<'d, Async, MultiMaster> { } /// Async respond to write command using RX DMA - /// + /// /// Receives data from the master into the provided buffer using DMA. /// If the master sends more bytes than the buffer can hold, excess bytes /// are acknowledged but discarded to prevent interrupt flooding. - /// + /// /// Returns the number of bytes stored in the buffer (not total received). pub async fn respond_to_write(&mut self, buffer: &mut [u8]) -> Result { trace!("I2C slave: starting respond_to_write, buffer_len={}", buffer.len()); - + if buffer.is_empty() { warn!("I2C slave: respond_to_write called with empty buffer"); return Err(Error::Overrun); @@ -1395,15 +1430,15 @@ impl<'d> I2c<'d, Async, MultiMaster> { } /// Async respond to read command using TX DMA - /// + /// /// Transmits data to the master using DMA. If the master requests more bytes /// than available in the data buffer, padding bytes (0x00) are sent until /// the master terminates the transaction with NACK, STOP, or RESTART. - /// + /// /// Returns the total number of bytes transmitted (data + padding). pub async fn respond_to_read(&mut self, data: &[u8]) -> Result { trace!("I2C slave: starting respond_to_read, data_len={}", data.len()); - + if data.is_empty() { warn!("I2C slave: respond_to_read called with empty data"); return Err(Error::Overrun); @@ -1431,17 +1466,18 @@ impl<'d> I2c<'d, Async, MultiMaster> { /// Execute complete slave receive transfer with excess byte handling async fn execute_slave_receive_transfer( - &mut self, - buffer: &mut [u8], - state: &'static State, - info: &'static Info + &mut self, + buffer: &mut [u8], + state: &'static State, + info: &'static Info, ) -> Result { let dma_transfer = unsafe { let src = info.regs.dr().as_ptr() as *mut u8; self.rx_dma.as_mut().unwrap().read(src, buffer, Default::default()) }; - let i2c_monitor = Self::create_termination_monitor(state, info, &[SlaveTermination::Stop, SlaveTermination::Restart]); + let i2c_monitor = + Self::create_termination_monitor(state, info, &[SlaveTermination::Stop, SlaveTermination::Restart]); match select(dma_transfer, i2c_monitor).await { Either::Second(Err(e)) => { @@ -1465,17 +1501,25 @@ impl<'d> I2c<'d, Async, MultiMaster> { /// Execute complete slave transmit transfer with padding byte handling async fn execute_slave_transmit_transfer( - &mut self, - data: &[u8], - state: &'static State, - info: &'static Info + &mut self, + data: &[u8], + state: &'static State, + info: &'static Info, ) -> Result { let dma_transfer = unsafe { let dst = info.regs.dr().as_ptr() as *mut u8; self.tx_dma.as_mut().unwrap().write(data, dst, Default::default()) }; - let i2c_monitor = Self::create_termination_monitor(state, info, &[SlaveTermination::Stop, SlaveTermination::Restart, SlaveTermination::Nack]); + let i2c_monitor = Self::create_termination_monitor( + state, + info, + &[ + SlaveTermination::Stop, + SlaveTermination::Restart, + SlaveTermination::Nack, + ], + ); match select(dma_transfer, i2c_monitor).await { Either::Second(Err(e)) => { @@ -1488,8 +1532,12 @@ impl<'d> I2c<'d, Async, MultiMaster> { Self::disable_dma_and_interrupts(info); let padding_count = self.handle_padding_bytes(state, info).await?; let total_bytes = data.len() + padding_count; - trace!("I2C slave: sent {} data bytes + {} padding bytes = {} total", - data.len(), padding_count, total_bytes); + trace!( + "I2C slave: sent {} data bytes + {} padding bytes = {} total", + data.len(), + padding_count, + total_bytes + ); Ok(total_bytes) } Either::Second(Ok(termination)) => { @@ -1508,7 +1556,7 @@ impl<'d> I2c<'d, Async, MultiMaster> { ) -> impl Future> { poll_fn(move |cx| { state.waker.register(cx.waker()); - + match Self::check_and_clear_error_flags(info) { Err(Error::Nack) if allowed_terminations.contains(&SlaveTermination::Nack) => { Poll::Ready(Ok(SlaveTermination::Nack)) @@ -1545,24 +1593,24 @@ impl<'d> I2c<'d, Async, MultiMaster> { } /// Handle excess bytes after DMA buffer is full - /// + /// /// Reads and discards bytes until transaction termination to prevent interrupt flooding async fn handle_excess_bytes(&mut self, state: &'static State, info: &'static Info) -> Result<(), Error> { let mut discarded_count = 0; - + poll_fn(|cx| { state.waker.register(cx.waker()); - + match Self::check_and_clear_error_flags(info) { Err(e) => { error!("I2C slave: error while discarding excess bytes: {:?}", e); Poll::Ready(Err(e)) - }, + } Ok(sr1) => { if let Some(termination) = Self::check_slave_termination_conditions(sr1) { match termination { SlaveTermination::Stop => Self::clear_stop_flag(info), - SlaveTermination::Restart => {}, + SlaveTermination::Restart => {} SlaveTermination::Nack => unreachable!("NACK not expected during receive"), } if discarded_count > 0 { @@ -1570,41 +1618,42 @@ impl<'d> I2c<'d, Async, MultiMaster> { } return Poll::Ready(Ok(())); } - + if sr1.rxne() { let _discarded_byte = info.regs.dr().read().dr(); discarded_count += 1; Self::enable_interrupts(info); return Poll::Pending; } - + Self::enable_interrupts(info); Poll::Pending } } - }).await + }) + .await } /// Handle padding bytes after DMA data is exhausted - /// + /// /// Sends 0x00 bytes until transaction termination to prevent interrupt flooding async fn handle_padding_bytes(&mut self, state: &'static State, info: &'static Info) -> Result { let mut padding_count = 0; poll_fn(|cx| { state.waker.register(cx.waker()); - + match Self::check_and_clear_error_flags(info) { Err(Error::Nack) => Poll::Ready(Ok(padding_count)), Err(e) => { error!("I2C slave: error while sending padding bytes: {:?}", e); Poll::Ready(Err(e)) - }, + } Ok(sr1) => { if let Some(termination) = Self::check_slave_termination_conditions(sr1) { match termination { SlaveTermination::Stop => Self::clear_stop_flag(info), - SlaveTermination::Restart => {}, + SlaveTermination::Restart => {} SlaveTermination::Nack => { info.regs.sr1().write(|reg| { reg.0 = !0; @@ -1614,33 +1663,34 @@ impl<'d> I2c<'d, Async, MultiMaster> { } return Poll::Ready(Ok(padding_count)); } - + if sr1.txe() { info.regs.dr().write(|w| w.set_dr(0x00)); padding_count += 1; Self::enable_interrupts(info); return Poll::Pending; } - + Self::enable_interrupts(info); Poll::Pending } } - }).await + }) + .await } } /// Timing configuration for I2C v1 hardware -/// -/// This struct encapsulates the complex timing calculations required for STM32 I2C v1 -/// peripherals, which use three separate registers (CR2.FREQ, CCR, TRISE) instead of +/// +/// This struct encapsulates the complex timing calculations required for STM32 I2C v1 +/// peripherals, which use three separate registers (CR2.FREQ, CCR, TRISE) instead of /// the unified TIMINGR register found in v2 hardware. struct Timings { - freq: u8, // APB frequency in MHz for CR2.FREQ register - f_s: i2c::vals::FS, // Standard or Fast mode selection - trise: u8, // Rise time compensation value - ccr: u16, // Clock control register value - duty: i2c::vals::Duty, // Fast mode duty cycle selection + freq: u8, // APB frequency in MHz for CR2.FREQ register + f_s: i2c::vals::FS, // Standard or Fast mode selection + trise: u8, // Rise time compensation value + ccr: u16, // Clock control register value + duty: i2c::vals::Duty, // Fast mode duty cycle selection } impl Timings { -- cgit From 944ac0bf138ab78f602627fae97891024c8fd082 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sat, 23 Aug 2025 10:50:05 +0200 Subject: Run cargo fmt for examples --- examples/stm32f4/src/bin/i2c_slave_async.rs | 44 ++++++++++++++------------ examples/stm32f4/src/bin/i2c_slave_blocking.rs | 41 +++++++++++++----------- 2 files changed, 47 insertions(+), 38 deletions(-) diff --git a/examples/stm32f4/src/bin/i2c_slave_async.rs b/examples/stm32f4/src/bin/i2c_slave_async.rs index 072c9875e..c0719af5e 100644 --- a/examples/stm32f4/src/bin/i2c_slave_async.rs +++ b/examples/stm32f4/src/bin/i2c_slave_async.rs @@ -1,5 +1,5 @@ //! I2C slave example using async operations with DMA -//! +//! //! This example demonstrates DMA-accelerated I2C slave operations, //! which provide better performance and lower CPU overhead for //! high-frequency I2C transactions. @@ -7,12 +7,12 @@ #![no_std] #![no_main] -use defmt_rtt as _; use defmt::{error, info}; +use defmt_rtt as _; use embassy_executor::Spawner; -use embassy_stm32::{bind_interrupts, peripherals}; -use embassy_stm32::i2c::{self, I2c, SlaveAddrConfig, SlaveCommand, SlaveCommandKind, Address}; +use embassy_stm32::i2c::{self, Address, I2c, SlaveAddrConfig, SlaveCommand, SlaveCommandKind}; use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, peripherals}; use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; use embassy_sync::mutex::Mutex; use embassy_time::{Duration, Timer}; @@ -30,38 +30,39 @@ bind_interrupts!(struct Irqs { #[embassy_executor::main] async fn main(spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - + // Configure I2C let mut i2c_config = i2c::Config::default(); i2c_config.sda_pullup = false; i2c_config.scl_pullup = false; i2c_config.frequency = Hertz(100_000); // 100kHz I2C speed - + // Initialize I2C as master first let i2c_master = I2c::new( - p.I2C1, - p.PB8, // SCL - p.PB9, // SDA - Irqs, - p.DMA1_CH6, // TX DMA + p.I2C1, p.PB8, // SCL + p.PB9, // SDA + Irqs, p.DMA1_CH6, // TX DMA p.DMA1_CH0, // RX DMA i2c_config, ); - + // Convert to MultiMaster mode let slave_config = SlaveAddrConfig::basic(I2C_SLAVE_ADDR); let i2c_slave = i2c_master.into_slave_multimaster(slave_config); - + spawner.spawn(i2c_slave_task(i2c_slave)).unwrap(); } #[embassy_executor::task] pub async fn i2c_slave_task(mut i2c_slave: I2c<'static, embassy_stm32::mode::Async, i2c::mode::MultiMaster>) { info!("Async I2C slave ready at address 0x{:02X}", I2C_SLAVE_ADDR); - + loop { match i2c_slave.listen().await { - Ok(SlaveCommand { kind: SlaveCommandKind::Write, address }) => { + Ok(SlaveCommand { + kind: SlaveCommandKind::Write, + address, + }) => { let addr_val = match address { Address::SevenBit(addr) => addr, Address::TenBit(addr) => (addr & 0xFF) as u8, @@ -70,7 +71,7 @@ pub async fn i2c_slave_task(mut i2c_slave: I2c<'static, embassy_stm32::mode::Asy info!("I2C: Received write command - Address 0x{:02X}", addr_val); let mut data_buffer = I2C_BUFFER.lock().await; - + match i2c_slave.respond_to_write(&mut *data_buffer).await { Ok(_) => { info!("I2C: Data received - Buffer now contains: 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}", @@ -81,8 +82,11 @@ pub async fn i2c_slave_task(mut i2c_slave: I2c<'static, embassy_stm32::mode::Asy } } } - - Ok(SlaveCommand { kind: SlaveCommandKind::Read, address }) => { + + Ok(SlaveCommand { + kind: SlaveCommandKind::Read, + address, + }) => { let addr_val = match address { Address::SevenBit(addr) => addr, Address::TenBit(addr) => (addr & 0xFF) as u8, // Show low byte for 10-bit @@ -91,7 +95,7 @@ pub async fn i2c_slave_task(mut i2c_slave: I2c<'static, embassy_stm32::mode::Asy info!("I2C: Received read command - Address 0x{:02X}", addr_val); let data_buffer = I2C_BUFFER.lock().await; - + match i2c_slave.respond_to_read(&data_buffer[..BUFFER_SIZE]).await { Ok(_) => { info!("I2C: Responded to read command"); @@ -101,7 +105,7 @@ pub async fn i2c_slave_task(mut i2c_slave: I2c<'static, embassy_stm32::mode::Asy } } } - + Err(e) => { error!("I2C: Listen error: {}", format_i2c_error(&e)); Timer::after(Duration::from_millis(100)).await; diff --git a/examples/stm32f4/src/bin/i2c_slave_blocking.rs b/examples/stm32f4/src/bin/i2c_slave_blocking.rs index c55f2f6c1..e027cd511 100644 --- a/examples/stm32f4/src/bin/i2c_slave_blocking.rs +++ b/examples/stm32f4/src/bin/i2c_slave_blocking.rs @@ -1,17 +1,17 @@ //! Complete I2C slave example using blocking operations -//! +//! //! This example shows how to set up an STM32F4 as an I2C slave device //! that can handle both read and write transactions from master devices. #![no_std] #![no_main] -use defmt_rtt as _; use defmt::{error, info}; +use defmt_rtt as _; use embassy_executor::Spawner; -use embassy_stm32::i2c::{self, I2c, SlaveAddrConfig, SlaveCommand, SlaveCommandKind, Address}; -use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_stm32::i2c::{self, Address, I2c, SlaveAddrConfig, SlaveCommand, SlaveCommandKind}; use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, peripherals}; use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; use embassy_sync::mutex::Mutex; use embassy_time::{Duration, Timer}; @@ -29,36 +29,38 @@ bind_interrupts!(struct Irqs { #[embassy_executor::main] async fn main(spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - + // Configure I2C let mut i2c_config = i2c::Config::default(); i2c_config.sda_pullup = false; i2c_config.scl_pullup = false; i2c_config.frequency = Hertz(100_000); i2c_config.timeout = embassy_time::Duration::from_millis(30000); - + // Initialize I2C as master first let i2c_master = I2c::new_blocking( - p.I2C1, - p.PB8, // SCL - p.PB9, // SDA + p.I2C1, p.PB8, // SCL + p.PB9, // SDA i2c_config, ); - + // Convert to slave+master mode let slave_config = SlaveAddrConfig::basic(I2C_SLAVE_ADDR); let i2c_slave = i2c_master.into_slave_multimaster(slave_config); - + spawner.spawn(i2c_slave_task(i2c_slave)).unwrap(); } #[embassy_executor::task] pub async fn i2c_slave_task(mut i2c_slave: I2c<'static, embassy_stm32::mode::Blocking, i2c::mode::MultiMaster>) { info!("Blocking I2C slave ready at address 0x{:02X}", I2C_SLAVE_ADDR); - + loop { match i2c_slave.blocking_listen() { - Ok(SlaveCommand { kind: SlaveCommandKind::Write, address }) => { + Ok(SlaveCommand { + kind: SlaveCommandKind::Write, + address, + }) => { let addr_val = match address { Address::SevenBit(addr) => addr, Address::TenBit(addr) => (addr & 0xFF) as u8, @@ -66,7 +68,7 @@ pub async fn i2c_slave_task(mut i2c_slave: I2c<'static, embassy_stm32::mode::Blo info!("I2C: Received write command - Address 0x{:02X}", addr_val); let mut data_buffer = I2C_BUFFER.lock().await; - + match i2c_slave.blocking_respond_to_write(&mut *data_buffer) { Ok(bytes_received) => { info!("I2C: Received {} bytes - Buffer now contains: 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}, 0x{:02X}", @@ -77,8 +79,11 @@ pub async fn i2c_slave_task(mut i2c_slave: I2c<'static, embassy_stm32::mode::Blo } } } - - Ok(SlaveCommand { kind: SlaveCommandKind::Read, address }) => { + + Ok(SlaveCommand { + kind: SlaveCommandKind::Read, + address, + }) => { let addr_val = match address { Address::SevenBit(addr) => addr, Address::TenBit(addr) => (addr & 0xFF) as u8, // Show low byte for 10-bit @@ -86,7 +91,7 @@ pub async fn i2c_slave_task(mut i2c_slave: I2c<'static, embassy_stm32::mode::Blo info!("I2C: Received read command - Address 0x{:02X}", addr_val); let data_buffer = I2C_BUFFER.lock().await; - + match i2c_slave.blocking_respond_to_read(&data_buffer[..BUFFER_SIZE]) { Ok(bytes_sent) => { info!("I2C: Responded to read - {} bytes sent", bytes_sent); @@ -96,7 +101,7 @@ pub async fn i2c_slave_task(mut i2c_slave: I2c<'static, embassy_stm32::mode::Blo } } } - + Err(e) => { error!("I2C: Listen error: {}", format_i2c_error(&e)); Timer::after(Duration::from_millis(100)).await; -- cgit From 524db5a935e506036c282e3c0dfa9abc807ac7ee Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sat, 23 Aug 2025 10:53:22 +0200 Subject: Fix formatting in examples --- examples/stm32f4/src/bin/i2c_slave_async.rs | 3 +-- examples/stm32f4/src/bin/i2c_slave_blocking.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/stm32f4/src/bin/i2c_slave_async.rs b/examples/stm32f4/src/bin/i2c_slave_async.rs index c0719af5e..1c48f1ac7 100644 --- a/examples/stm32f4/src/bin/i2c_slave_async.rs +++ b/examples/stm32f4/src/bin/i2c_slave_async.rs @@ -8,7 +8,7 @@ #![no_main] use defmt::{error, info}; -use defmt_rtt as _; +use {defmt_rtt as _, panic_probe as _}; use embassy_executor::Spawner; use embassy_stm32::i2c::{self, Address, I2c, SlaveAddrConfig, SlaveCommand, SlaveCommandKind}; use embassy_stm32::time::Hertz; @@ -16,7 +16,6 @@ use embassy_stm32::{bind_interrupts, peripherals}; use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; use embassy_sync::mutex::Mutex; use embassy_time::{Duration, Timer}; -use panic_probe as _; pub const I2C_SLAVE_ADDR: u8 = 0x42; pub const BUFFER_SIZE: usize = 8; diff --git a/examples/stm32f4/src/bin/i2c_slave_blocking.rs b/examples/stm32f4/src/bin/i2c_slave_blocking.rs index e027cd511..a6f4da747 100644 --- a/examples/stm32f4/src/bin/i2c_slave_blocking.rs +++ b/examples/stm32f4/src/bin/i2c_slave_blocking.rs @@ -7,7 +7,7 @@ #![no_main] use defmt::{error, info}; -use defmt_rtt as _; +use {defmt_rtt as _, panic_probe as _}; use embassy_executor::Spawner; use embassy_stm32::i2c::{self, Address, I2c, SlaveAddrConfig, SlaveCommand, SlaveCommandKind}; use embassy_stm32::time::Hertz; @@ -15,7 +15,6 @@ use embassy_stm32::{bind_interrupts, peripherals}; use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; use embassy_sync::mutex::Mutex; use embassy_time::{Duration, Timer}; -use panic_probe as _; pub const I2C_SLAVE_ADDR: u8 = 0x42; pub const BUFFER_SIZE: usize = 8; -- cgit From 3c8d078525c69867710bbd291dc135b3a5011702 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sat, 23 Aug 2025 10:55:00 +0200 Subject: Fix formatting in examples --- examples/stm32f4/src/bin/i2c_slave_async.rs | 2 +- examples/stm32f4/src/bin/i2c_slave_blocking.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/stm32f4/src/bin/i2c_slave_async.rs b/examples/stm32f4/src/bin/i2c_slave_async.rs index 1c48f1ac7..db4a805b6 100644 --- a/examples/stm32f4/src/bin/i2c_slave_async.rs +++ b/examples/stm32f4/src/bin/i2c_slave_async.rs @@ -8,7 +8,6 @@ #![no_main] use defmt::{error, info}; -use {defmt_rtt as _, panic_probe as _}; use embassy_executor::Spawner; use embassy_stm32::i2c::{self, Address, I2c, SlaveAddrConfig, SlaveCommand, SlaveCommandKind}; use embassy_stm32::time::Hertz; @@ -16,6 +15,7 @@ use embassy_stm32::{bind_interrupts, peripherals}; use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; use embassy_sync::mutex::Mutex; use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; pub const I2C_SLAVE_ADDR: u8 = 0x42; pub const BUFFER_SIZE: usize = 8; diff --git a/examples/stm32f4/src/bin/i2c_slave_blocking.rs b/examples/stm32f4/src/bin/i2c_slave_blocking.rs index a6f4da747..a62087a29 100644 --- a/examples/stm32f4/src/bin/i2c_slave_blocking.rs +++ b/examples/stm32f4/src/bin/i2c_slave_blocking.rs @@ -7,7 +7,6 @@ #![no_main] use defmt::{error, info}; -use {defmt_rtt as _, panic_probe as _}; use embassy_executor::Spawner; use embassy_stm32::i2c::{self, Address, I2c, SlaveAddrConfig, SlaveCommand, SlaveCommandKind}; use embassy_stm32::time::Hertz; @@ -15,6 +14,7 @@ use embassy_stm32::{bind_interrupts, peripherals}; use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; use embassy_sync::mutex::Mutex; use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; pub const I2C_SLAVE_ADDR: u8 = 0x42; pub const BUFFER_SIZE: usize = 8; -- cgit From 14a047a9ad75709e0bde8b0fa71f3b4bddedc576 Mon Sep 17 00:00:00 2001 From: maor malka Date: Sun, 24 Aug 2025 21:36:33 -0400 Subject: stm32/adc/v3: added support for DMA based adc sampling --- embassy-stm32/src/adc/v3.rs | 274 +++++++++++++++++++++++++++++++++++- examples/stm32l4/src/bin/adc_dma.rs | 49 +++++++ 2 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 examples/stm32l4/src/bin/adc_dma.rs diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index a2e42fe52..6d874dbba 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -3,10 +3,13 @@ use pac::adc::vals::Dmacfg; #[cfg(adc_v3)] use pac::adc::vals::{OversamplingRatio, OversamplingShift, Rovsm, Trovs}; +use core::marker::PhantomData; +use core::sync::atomic::{compiler_fence, Ordering}; + use super::{ blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, }; -use crate::dma::Transfer; +use crate::dma::{ReadableRingBuffer, Transfer, TransferOptions}; use crate::{pac, rcc, Peri}; /// Default VREF voltage used for sample conversion to millivolts. @@ -107,6 +110,15 @@ pub enum Averaging { Samples128, Samples256, } + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OverrunError; + +pub struct RingBufferedAdc<'d, T: Instance> { + _phantom: PhantomData, + ring_buf: ReadableRingBuffer<'d, u16>, +} + impl<'d, T: Instance> Adc<'d, T> { pub fn new(adc: Peri<'d, T>) -> Self { rcc::enable_and_reset::(); @@ -449,6 +461,122 @@ impl<'d, T: Instance> Adc<'d, T> { }); } + /// Configures the ADC to use a DMA ring buffer for continuous data acquisition. + /// + /// The `dma_buf` should be large enough to prevent DMA buffer overrun. + /// The length of the `dma_buf` should be a multiple of the ADC channel count. + /// For example, if 3 channels are measured, its length can be 3 * 40 = 120 measurements. + /// + /// `read` method is used to read out measurements from the DMA ring buffer, and its buffer should be exactly half of the `dma_buf` length. + /// It is critical to call `read` frequently to prevent DMA buffer overrun. + /// + /// [`read`]: #method.read + pub fn into_ring_buffered<'a>( + &mut self, + dma: Peri<'a, impl RxDma>, + dma_buf: &'a mut [u16], + sequence: impl ExactSizeIterator, SampleTime)>, + ) -> RingBufferedAdc<'a, T> { + assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF); + assert!(sequence.len() != 0, "Asynchronous read sequence cannot be empty"); + assert!( + sequence.len() <= 16, + "Asynchronous read sequence cannot be more than 16 in length" + ); + // reset conversions and enable the adc + Self::cancel_conversions(); + self.enable(); + + //adc side setup + + // Set sequence length + #[cfg(not(any(adc_g0, adc_u0)))] + T::regs().sqr1().modify(|w| { + w.set_l(sequence.len() as u8 - 1); + }); + + #[cfg(any(adc_g0, adc_u0))] + let mut channel_mask = 0; + + // Configure channels and ranks + for (_i, (channel, sample_time)) in sequence.enumerate() { + Self::configure_channel(channel, sample_time); + + // Each channel is sampled according to sequence + #[cfg(not(any(adc_g0, adc_u0)))] + match _i { + 0..=3 => { + T::regs().sqr1().modify(|w| { + w.set_sq(_i, channel.channel()); + }); + } + 4..=8 => { + T::regs().sqr2().modify(|w| { + w.set_sq(_i - 4, channel.channel()); + }); + } + 9..=13 => { + T::regs().sqr3().modify(|w| { + w.set_sq(_i - 9, channel.channel()); + }); + } + 14..=15 => { + T::regs().sqr4().modify(|w| { + w.set_sq(_i - 14, channel.channel()); + }); + } + _ => unreachable!(), + } + } + + //dma side setup + let opts = TransferOptions { + half_transfer_ir: true, + circular: true, + ..Default::default() + }; + + // Safety: we forget the struct before this function returns. + let request = dma.request(); + + let ring_buf = + unsafe { ReadableRingBuffer::new(dma, request, T::regs().dr().as_ptr() as *mut u16, dma_buf, opts) }; + + // On G0 and U0 enabled channels are sampled from 0 to last channel. + // It is possible to add up to 8 sequences if CHSELRMOD = 1. + // However for supporting more than 8 channels alternative CHSELRMOD = 0 approach is used. + #[cfg(any(adc_g0, adc_u0))] + T::regs().chselr().modify(|reg| { + reg.set_chsel(channel_mask); + }); + + // Set continuous mode with Circular dma. + // Clear overrun flag before starting transfer. + T::regs().isr().modify(|reg| { + reg.set_ovr(true); + }); + + #[cfg(not(any(adc_g0, adc_u0)))] + T::regs().cfgr().modify(|reg| { + reg.set_discen(false); + reg.set_cont(true); + reg.set_dmacfg(Dmacfg::CIRCULAR); + reg.set_dmaen(true); + }); + #[cfg(any(adc_g0, adc_u0))] + T::regs().cfgr1().modify(|reg| { + reg.set_discen(false); + reg.set_cont(true); + reg.set_dmacfg(Dmacfg::CIRCULAR); + reg.set_dmaen(true); + }); + + RingBufferedAdc { + _phantom: PhantomData, + ring_buf, + } + } + fn configure_channel(channel: &mut impl AdcChannel, sample_time: SampleTime) { // RM0492, RM0481, etc. // "This option bit must be set to 1 when ADCx_INP0 or ADCx_INN1 channel is selected." @@ -553,3 +681,147 @@ impl<'d, T: Instance> Adc<'d, T> { } } } + +impl<'d, T: Instance> RingBufferedAdc<'d, T> { + #[inline] + fn start_continous_sampling(&mut self) { + // Start adc conversion + T::regs().cr().modify(|reg| { + reg.set_adstart(true); + }); + self.ring_buf.start(); + } + + #[inline] + pub fn stop_continous_sampling(&mut self) { + // Stop adc conversion + if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() { + T::regs().cr().modify(|reg| { + reg.set_adstp(true); + }); + while T::regs().cr().read().adstart() {} + } + } + pub fn disable_adc(&mut self) { + self.stop_continous_sampling(); + self.ring_buf.clear(); + self.ring_buf.request_pause(); + } + + pub fn teardown_adc(&mut self) { + self.disable_adc(); + + //disable dma control + #[cfg(not(any(adc_g0, adc_u0)))] + T::regs().cfgr().modify(|reg| { + reg.set_dmaen(false); + }); + #[cfg(any(adc_g0, adc_u0))] + T::regs().cfgr1().modify(|reg| { + reg.set_dmaen(false); + }); + + //TODO: do we need to cleanup the DMA request here? + + compiler_fence(Ordering::SeqCst); + } + + /// Reads measurements from the DMA ring buffer. + /// + /// This method fills the provided `measurements` array with ADC readings from the DMA buffer. + /// The length of the `measurements` array should be exactly half of the DMA buffer length. Because interrupts are only generated if half or full DMA transfer completes. + /// + /// Each call to `read` will populate the `measurements` array in the same order as the channels defined with `sequence`. + /// There will be many sequences worth of measurements in this array because it only returns if at least half of the DMA buffer is filled. + /// For example if 2 channels are sampled `measurements` contain: `[sq0 sq1 sq0 sq1 sq0 sq1 ..]`. + /// + /// Note that the ADC Datarate can be very fast, it is suggested to use DMA mode inside tightly running tasks + /// Otherwise, you'll see constant Overrun errors occuring, this means that you're sampling too quickly for the task to handle, and you may need to increase the buffer size. + /// Example: + /// ```rust,ignore + /// const DMA_BUF_LEN: usize = 120; + /// use embassy_stm32::adc::{Adc, AdcChannel} + /// + /// let mut adc = Adc::new(p.ADC1); + /// let mut adc_pin0 = p.PA0.degrade_adc(); + /// let mut adc_pin1 = p.PA1.degrade_adc(); + /// let adc_dma_buf = [0u16; DMA_BUF_LEN]; + /// + /// let mut ring_buffered_adc: RingBufferedAdc = adc.into_ring_buffered( + /// p.DMA2_CH0, + /// adc_dma_buf, [ + /// (&mut *adc_pin0, SampleTime::CYCLES160_5), + /// (&mut *adc_pin1, SampleTime::CYCLES160_5), + /// ].into_iter()); + /// + /// + /// let mut measurements = [0u16; DMA_BUF_LEN / 2]; + /// loop { + /// match ring_buffered_adc.read(&mut measurements).await { + /// Ok(_) => { + /// defmt::info!("adc1: {}", measurements); + /// } + /// Err(e) => { + /// defmt::warn!("Error: {:?}", e); + /// } + /// } + /// } + /// ``` + /// + /// + /// [`teardown_adc`]: #method.teardown_adc + /// [`start_continous_sampling`]: #method.start_continous_sampling + pub async fn read(&mut self, measurements: &mut [u16]) -> Result { + assert_eq!( + self.ring_buf.capacity() / 2, + measurements.len(), + "Buffer size must be half the size of the ring buffer" + ); + + let r = T::regs(); + + // Start background receive if it was not already started + if !r.cr().read().adstart() { + self.start_continous_sampling(); + } + + self.ring_buf.read_exact(measurements).await.map_err(|_| OverrunError) + } + + /// Read bytes that are readily available in the ring buffer. + /// If no bytes are currently available in the buffer the call waits until the some + /// bytes are available (at least one byte and at most half the buffer size) + /// + /// Background receive is started if `start_continous_sampling()` has not been previously called. + /// + /// Receive in the background is terminated if an error is returned. + /// It must then manually be started again by calling `start_continous_sampling()` or by re-calling `blocking_read()`. + pub fn blocking_read(&mut self, buf: &mut [u16]) -> Result { + let r = T::regs(); + + // Start background receive if it was not already started + if !r.cr().read().adstart() { + self.start_continous_sampling(); + } + + loop { + match self.ring_buf.read(buf) { + Ok((0, _)) => {} + Ok((len, _)) => { + return Ok(len); + } + Err(_) => { + self.stop_continous_sampling(); + return Err(OverrunError); + } + } + } + } +} + +impl Drop for RingBufferedAdc<'_, T> { + fn drop(&mut self) { + self.teardown_adc(); + rcc::disable::(); + } +} diff --git a/examples/stm32l4/src/bin/adc_dma.rs b/examples/stm32l4/src/bin/adc_dma.rs new file mode 100644 index 000000000..1769c735a --- /dev/null +++ b/examples/stm32l4/src/bin/adc_dma.rs @@ -0,0 +1,49 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, AdcChannel, SampleTime}; +use embassy_stm32::Config; +use {defmt_rtt as _, panic_probe as _}; + +const DMA_BUF_LEN: usize = 512; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let config = Config::default(); + + let p = embassy_stm32::init(config); + + let mut adc = Adc::new(p.ADC1); + let mut adc_pin0 = p.PA0.degrade_adc(); + let mut adc_pin1 = p.PA1.degrade_adc(); + let mut adc_dma_buf = [0u16; DMA_BUF_LEN]; + let mut measurements = [0u16; DMA_BUF_LEN / 2]; + let mut ring_buffered_adc = adc.into_ring_buffered( + p.DMA1_CH1, + &mut adc_dma_buf, + [ + (&mut adc_pin0, SampleTime::CYCLES640_5), + (&mut adc_pin1, SampleTime::CYCLES640_5), + ] + .into_iter(), + ); + + info!("starting measurement loop"); + loop { + match ring_buffered_adc.read(&mut measurements).await { + Ok(_) => { + //note: originally there was a print here showing all the samples, + //but even that takes too much time and would cause adc overruns + info!("adc1 first 10 samples: {}",measurements[0..10]); + } + Err(e) => { + warn!("Error: {:?}", e); + } + } + } + +} -- cgit From 0b8da5ab8f6469bdf2adf7462e7ebedee93dde3f Mon Sep 17 00:00:00 2001 From: maor malka Date: Sun, 24 Aug 2025 21:42:30 -0400 Subject: stm32l4/example/adc_dma: missing clock configuration --- examples/stm32l4/src/bin/adc_dma.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/stm32l4/src/bin/adc_dma.rs b/examples/stm32l4/src/bin/adc_dma.rs index 1769c735a..a5b7b0c5e 100644 --- a/examples/stm32l4/src/bin/adc_dma.rs +++ b/examples/stm32l4/src/bin/adc_dma.rs @@ -13,8 +13,11 @@ const DMA_BUF_LEN: usize = 512; async fn main(_spawner: Spawner) { info!("Hello World!"); - let config = Config::default(); - + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.mux.adcsel = mux::Adcsel::SYS; + } let p = embassy_stm32::init(config); let mut adc = Adc::new(p.ADC1); @@ -36,14 +39,13 @@ async fn main(_spawner: Spawner) { loop { match ring_buffered_adc.read(&mut measurements).await { Ok(_) => { - //note: originally there was a print here showing all the samples, + //note: originally there was a print here showing all the samples, //but even that takes too much time and would cause adc overruns - info!("adc1 first 10 samples: {}",measurements[0..10]); + info!("adc1 first 10 samples: {}", measurements[0..10]); } Err(e) => { warn!("Error: {:?}", e); } } } - } -- cgit From 135070040688a4e5b50b84718526b630714d1a13 Mon Sep 17 00:00:00 2001 From: maor malka Date: Sun, 24 Aug 2025 21:53:19 -0400 Subject: stm32/adc/v3: build fix attempt --- embassy-stm32/src/adc/v3.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index 6d874dbba..bc5b80ccd 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -9,6 +9,7 @@ use core::sync::atomic::{compiler_fence, Ordering}; use super::{ blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, }; +#[cfg(adc_v3)] use crate::dma::{ReadableRingBuffer, Transfer, TransferOptions}; use crate::{pac, rcc, Peri}; -- cgit From e5d4ef42699859fbcbcee083842195c8d4f7b8b0 Mon Sep 17 00:00:00 2001 From: maor malka Date: Sun, 24 Aug 2025 22:00:08 -0400 Subject: stm32/adc/v3: build formatting fixes --- embassy-stm32/src/adc/v3.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index bc5b80ccd..03864b171 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -1,11 +1,11 @@ +use core::marker::PhantomData; +use core::sync::atomic::{compiler_fence, Ordering}; + use cfg_if::cfg_if; use pac::adc::vals::Dmacfg; #[cfg(adc_v3)] use pac::adc::vals::{OversamplingRatio, OversamplingShift, Rovsm, Trovs}; -use core::marker::PhantomData; -use core::sync::atomic::{compiler_fence, Ordering}; - use super::{ blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, }; -- cgit From 756ec7f1837a714f9c8304e2b2b21b542e0260d6 Mon Sep 17 00:00:00 2001 From: maor malka Date: Sun, 24 Aug 2025 22:04:15 -0400 Subject: stm32/adc/v3: updated changelog and cfg fence for DMA methods --- embassy-stm32/CHANGELOG.md | 1 + embassy-stm32/src/adc/v3.rs | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index 9cd4d5951..90d38a425 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - fix: Fix performing a hash after performing a hmac - chore: Updated stm32-metapac and stm32-data dependencies - fix: Fix XSPI not disabling alternate bytes when they were previously enabled +- feat: stm32/adc/v3: added support for Continous DMA configuration ## 0.3.0 - 2025-08-12 diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index 03864b171..8062fa169 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -471,7 +471,8 @@ impl<'d, T: Instance> Adc<'d, T> { /// `read` method is used to read out measurements from the DMA ring buffer, and its buffer should be exactly half of the `dma_buf` length. /// It is critical to call `read` frequently to prevent DMA buffer overrun. /// - /// [`read`]: #method.read + /// [`read`]: #method.read + #[cfg(adc_v3)] pub fn into_ring_buffered<'a>( &mut self, dma: Peri<'a, impl RxDma>, @@ -683,6 +684,7 @@ impl<'d, T: Instance> Adc<'d, T> { } } +#[cfg(adc_v3)] impl<'d, T: Instance> RingBufferedAdc<'d, T> { #[inline] fn start_continous_sampling(&mut self) { -- cgit From 75484f4f51847a92e2df1e8319debec61cd7aca2 Mon Sep 17 00:00:00 2001 From: maor malka Date: Sun, 24 Aug 2025 22:05:25 -0400 Subject: stm32/adc/v3: build formatting fixes --- embassy-stm32/src/adc/v3.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index 8062fa169..a52141a34 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -471,7 +471,7 @@ impl<'d, T: Instance> Adc<'d, T> { /// `read` method is used to read out measurements from the DMA ring buffer, and its buffer should be exactly half of the `dma_buf` length. /// It is critical to call `read` frequently to prevent DMA buffer overrun. /// - /// [`read`]: #method.read + /// [`read`]: #method.read #[cfg(adc_v3)] pub fn into_ring_buffered<'a>( &mut self, -- cgit From 6b8d375813116fba0e04aa28e23ded8ab077729a Mon Sep 17 00:00:00 2001 From: maor malka Date: Tue, 26 Aug 2025 22:15:34 -0400 Subject: stm32/adc/v3: moved ringbuffered to seperate file --- embassy-stm32/src/adc/ringbuffered_v3.rs | 181 ++++++++++++++++++++++++++++++ embassy-stm32/src/adc/v3.rs | 182 ++----------------------------- 2 files changed, 189 insertions(+), 174 deletions(-) create mode 100644 embassy-stm32/src/adc/ringbuffered_v3.rs diff --git a/embassy-stm32/src/adc/ringbuffered_v3.rs b/embassy-stm32/src/adc/ringbuffered_v3.rs new file mode 100644 index 000000000..655ae712f --- /dev/null +++ b/embassy-stm32/src/adc/ringbuffered_v3.rs @@ -0,0 +1,181 @@ +use core::marker::PhantomData; +use core::sync::atomic::{compiler_fence, Ordering}; +use embassy_hal_internal::Peri; + +use crate::dma::{ReadableRingBuffer, TransferOptions}; + +use crate::adc::Instance; +use crate::adc::RxDma; +use crate::rcc; + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OverrunError; + +pub struct RingBufferedAdc<'d, T: Instance> { + pub _phantom: PhantomData, + pub ring_buf: ReadableRingBuffer<'d, u16>, +} + +impl<'d, T: Instance> RingBufferedAdc<'d, T> { + pub fn new(dma: Peri<'d, impl RxDma>, dma_buf: &'d mut [u16]) -> Self { + //dma side setup + let opts = TransferOptions { + half_transfer_ir: true, + circular: true, + ..Default::default() + }; + + // Safety: we forget the struct before this function returns. + let request = dma.request(); + + let ring_buf = + unsafe { ReadableRingBuffer::new(dma, request, T::regs().dr().as_ptr() as *mut u16, dma_buf, opts) }; + + Self { + _phantom: PhantomData, + ring_buf, + } + } + + #[inline] + fn start_continous_sampling(&mut self) { + // Start adc conversion + T::regs().cr().modify(|reg| { + reg.set_adstart(true); + }); + self.ring_buf.start(); + } + + #[inline] + pub fn stop_continous_sampling(&mut self) { + // Stop adc conversion + if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() { + T::regs().cr().modify(|reg| { + reg.set_adstp(true); + }); + while T::regs().cr().read().adstart() {} + } + } + pub fn disable_adc(&mut self) { + self.stop_continous_sampling(); + self.ring_buf.clear(); + self.ring_buf.request_pause(); + } + + pub fn teardown_adc(&mut self) { + self.disable_adc(); + + //disable dma control + #[cfg(not(any(adc_g0, adc_u0)))] + T::regs().cfgr().modify(|reg| { + reg.set_dmaen(false); + }); + #[cfg(any(adc_g0, adc_u0))] + T::regs().cfgr1().modify(|reg| { + reg.set_dmaen(false); + }); + + //TODO: do we need to cleanup the DMA request here? + + compiler_fence(Ordering::SeqCst); + } + + /// Reads measurements from the DMA ring buffer. + /// + /// This method fills the provided `measurements` array with ADC readings from the DMA buffer. + /// The length of the `measurements` array should be exactly half of the DMA buffer length. Because interrupts are only generated if half or full DMA transfer completes. + /// + /// Each call to `read` will populate the `measurements` array in the same order as the channels defined with `sequence`. + /// There will be many sequences worth of measurements in this array because it only returns if at least half of the DMA buffer is filled. + /// For example if 2 channels are sampled `measurements` contain: `[sq0 sq1 sq0 sq1 sq0 sq1 ..]`. + /// + /// Note that the ADC Datarate can be very fast, it is suggested to use DMA mode inside tightly running tasks + /// Otherwise, you'll see constant Overrun errors occuring, this means that you're sampling too quickly for the task to handle, and you may need to increase the buffer size. + /// Example: + /// ```rust,ignore + /// const DMA_BUF_LEN: usize = 120; + /// use embassy_stm32::adc::{Adc, AdcChannel} + /// + /// let mut adc = Adc::new(p.ADC1); + /// let mut adc_pin0 = p.PA0.degrade_adc(); + /// let mut adc_pin1 = p.PA1.degrade_adc(); + /// let adc_dma_buf = [0u16; DMA_BUF_LEN]; + /// + /// let mut ring_buffered_adc: RingBufferedAdc = adc.into_ring_buffered( + /// p.DMA2_CH0, + /// adc_dma_buf, [ + /// (&mut *adc_pin0, SampleTime::CYCLES160_5), + /// (&mut *adc_pin1, SampleTime::CYCLES160_5), + /// ].into_iter()); + /// + /// + /// let mut measurements = [0u16; DMA_BUF_LEN / 2]; + /// loop { + /// match ring_buffered_adc.read(&mut measurements).await { + /// Ok(_) => { + /// defmt::info!("adc1: {}", measurements); + /// } + /// Err(e) => { + /// defmt::warn!("Error: {:?}", e); + /// } + /// } + /// } + /// ``` + /// + /// + /// [`teardown_adc`]: #method.teardown_adc + /// [`start_continous_sampling`]: #method.start_continous_sampling + pub async fn read(&mut self, measurements: &mut [u16]) -> Result { + assert_eq!( + self.ring_buf.capacity() / 2, + measurements.len(), + "Buffer size must be half the size of the ring buffer" + ); + + let r = T::regs(); + + // Start background receive if it was not already started + if !r.cr().read().adstart() { + self.start_continous_sampling(); + } + + self.ring_buf.read_exact(measurements).await.map_err(|_| OverrunError) + } + + /// Read bytes that are readily available in the ring buffer. + /// If no bytes are currently available in the buffer the call waits until the some + /// bytes are available (at least one byte and at most half the buffer size) + /// + /// Background receive is started if `start_continous_sampling()` has not been previously called. + /// + /// Receive in the background is terminated if an error is returned. + /// It must then manually be started again by calling `start_continous_sampling()` or by re-calling `blocking_read()`. + pub fn blocking_read(&mut self, buf: &mut [u16]) -> Result { + let r = T::regs(); + + // Start background receive if it was not already started + if !r.cr().read().adstart() { + self.start_continous_sampling(); + } + + loop { + match self.ring_buf.read(buf) { + Ok((0, _)) => {} + Ok((len, _)) => { + return Ok(len); + } + Err(_) => { + self.stop_continous_sampling(); + return Err(OverrunError); + } + } + } + } +} + +impl Drop for RingBufferedAdc<'_, T> { + fn drop(&mut self) { + self.teardown_adc(); + rcc::disable::(); + } +} diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index a52141a34..30b04fc81 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -1,6 +1,3 @@ -use core::marker::PhantomData; -use core::sync::atomic::{compiler_fence, Ordering}; - use cfg_if::cfg_if; use pac::adc::vals::Dmacfg; #[cfg(adc_v3)] @@ -9,8 +6,14 @@ use pac::adc::vals::{OversamplingRatio, OversamplingShift, Rovsm, Trovs}; use super::{ blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, }; + #[cfg(adc_v3)] -use crate::dma::{ReadableRingBuffer, Transfer, TransferOptions}; +mod ringbuffered_v3; + +#[cfg(adc_v3)] +use ringbuffered_v3::RingBufferedAdc; + +use crate::dma::Transfer; use crate::{pac, rcc, Peri}; /// Default VREF voltage used for sample conversion to millivolts. @@ -112,14 +115,6 @@ pub enum Averaging { Samples256, } -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct OverrunError; - -pub struct RingBufferedAdc<'d, T: Instance> { - _phantom: PhantomData, - ring_buf: ReadableRingBuffer<'d, u16>, -} - impl<'d, T: Instance> Adc<'d, T> { pub fn new(adc: Peri<'d, T>) -> Self { rcc::enable_and_reset::(); @@ -531,19 +526,6 @@ impl<'d, T: Instance> Adc<'d, T> { } } - //dma side setup - let opts = TransferOptions { - half_transfer_ir: true, - circular: true, - ..Default::default() - }; - - // Safety: we forget the struct before this function returns. - let request = dma.request(); - - let ring_buf = - unsafe { ReadableRingBuffer::new(dma, request, T::regs().dr().as_ptr() as *mut u16, dma_buf, opts) }; - // On G0 and U0 enabled channels are sampled from 0 to last channel. // It is possible to add up to 8 sequences if CHSELRMOD = 1. // However for supporting more than 8 channels alternative CHSELRMOD = 0 approach is used. @@ -573,10 +555,7 @@ impl<'d, T: Instance> Adc<'d, T> { reg.set_dmaen(true); }); - RingBufferedAdc { - _phantom: PhantomData, - ring_buf, - } + RingBufferedAdc::new(dma, dma_buf) } fn configure_channel(channel: &mut impl AdcChannel, sample_time: SampleTime) { @@ -683,148 +662,3 @@ impl<'d, T: Instance> Adc<'d, T> { } } } - -#[cfg(adc_v3)] -impl<'d, T: Instance> RingBufferedAdc<'d, T> { - #[inline] - fn start_continous_sampling(&mut self) { - // Start adc conversion - T::regs().cr().modify(|reg| { - reg.set_adstart(true); - }); - self.ring_buf.start(); - } - - #[inline] - pub fn stop_continous_sampling(&mut self) { - // Stop adc conversion - if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() { - T::regs().cr().modify(|reg| { - reg.set_adstp(true); - }); - while T::regs().cr().read().adstart() {} - } - } - pub fn disable_adc(&mut self) { - self.stop_continous_sampling(); - self.ring_buf.clear(); - self.ring_buf.request_pause(); - } - - pub fn teardown_adc(&mut self) { - self.disable_adc(); - - //disable dma control - #[cfg(not(any(adc_g0, adc_u0)))] - T::regs().cfgr().modify(|reg| { - reg.set_dmaen(false); - }); - #[cfg(any(adc_g0, adc_u0))] - T::regs().cfgr1().modify(|reg| { - reg.set_dmaen(false); - }); - - //TODO: do we need to cleanup the DMA request here? - - compiler_fence(Ordering::SeqCst); - } - - /// Reads measurements from the DMA ring buffer. - /// - /// This method fills the provided `measurements` array with ADC readings from the DMA buffer. - /// The length of the `measurements` array should be exactly half of the DMA buffer length. Because interrupts are only generated if half or full DMA transfer completes. - /// - /// Each call to `read` will populate the `measurements` array in the same order as the channels defined with `sequence`. - /// There will be many sequences worth of measurements in this array because it only returns if at least half of the DMA buffer is filled. - /// For example if 2 channels are sampled `measurements` contain: `[sq0 sq1 sq0 sq1 sq0 sq1 ..]`. - /// - /// Note that the ADC Datarate can be very fast, it is suggested to use DMA mode inside tightly running tasks - /// Otherwise, you'll see constant Overrun errors occuring, this means that you're sampling too quickly for the task to handle, and you may need to increase the buffer size. - /// Example: - /// ```rust,ignore - /// const DMA_BUF_LEN: usize = 120; - /// use embassy_stm32::adc::{Adc, AdcChannel} - /// - /// let mut adc = Adc::new(p.ADC1); - /// let mut adc_pin0 = p.PA0.degrade_adc(); - /// let mut adc_pin1 = p.PA1.degrade_adc(); - /// let adc_dma_buf = [0u16; DMA_BUF_LEN]; - /// - /// let mut ring_buffered_adc: RingBufferedAdc = adc.into_ring_buffered( - /// p.DMA2_CH0, - /// adc_dma_buf, [ - /// (&mut *adc_pin0, SampleTime::CYCLES160_5), - /// (&mut *adc_pin1, SampleTime::CYCLES160_5), - /// ].into_iter()); - /// - /// - /// let mut measurements = [0u16; DMA_BUF_LEN / 2]; - /// loop { - /// match ring_buffered_adc.read(&mut measurements).await { - /// Ok(_) => { - /// defmt::info!("adc1: {}", measurements); - /// } - /// Err(e) => { - /// defmt::warn!("Error: {:?}", e); - /// } - /// } - /// } - /// ``` - /// - /// - /// [`teardown_adc`]: #method.teardown_adc - /// [`start_continous_sampling`]: #method.start_continous_sampling - pub async fn read(&mut self, measurements: &mut [u16]) -> Result { - assert_eq!( - self.ring_buf.capacity() / 2, - measurements.len(), - "Buffer size must be half the size of the ring buffer" - ); - - let r = T::regs(); - - // Start background receive if it was not already started - if !r.cr().read().adstart() { - self.start_continous_sampling(); - } - - self.ring_buf.read_exact(measurements).await.map_err(|_| OverrunError) - } - - /// Read bytes that are readily available in the ring buffer. - /// If no bytes are currently available in the buffer the call waits until the some - /// bytes are available (at least one byte and at most half the buffer size) - /// - /// Background receive is started if `start_continous_sampling()` has not been previously called. - /// - /// Receive in the background is terminated if an error is returned. - /// It must then manually be started again by calling `start_continous_sampling()` or by re-calling `blocking_read()`. - pub fn blocking_read(&mut self, buf: &mut [u16]) -> Result { - let r = T::regs(); - - // Start background receive if it was not already started - if !r.cr().read().adstart() { - self.start_continous_sampling(); - } - - loop { - match self.ring_buf.read(buf) { - Ok((0, _)) => {} - Ok((len, _)) => { - return Ok(len); - } - Err(_) => { - self.stop_continous_sampling(); - return Err(OverrunError); - } - } - } - } -} - -impl Drop for RingBufferedAdc<'_, T> { - fn drop(&mut self) { - self.teardown_adc(); - rcc::disable::(); - } -} -- cgit From e48dc90edc3277d1b406900d16b35653aafdbe3a Mon Sep 17 00:00:00 2001 From: maor malka Date: Thu, 28 Aug 2025 18:15:43 -0400 Subject: stm32/adc/v3: rustfmt shananigans --- embassy-stm32/src/adc/ringbuffered_v3.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/adc/ringbuffered_v3.rs b/embassy-stm32/src/adc/ringbuffered_v3.rs index 655ae712f..7ff37f68d 100644 --- a/embassy-stm32/src/adc/ringbuffered_v3.rs +++ b/embassy-stm32/src/adc/ringbuffered_v3.rs @@ -1,11 +1,11 @@ use core::marker::PhantomData; use core::sync::atomic::{compiler_fence, Ordering}; + use embassy_hal_internal::Peri; +use crate::adc::{Instance, RxDma}; use crate::dma::{ReadableRingBuffer, TransferOptions}; -use crate::adc::Instance; -use crate::adc::RxDma; use crate::rcc; #[cfg_attr(feature = "defmt", derive(defmt::Format))] -- cgit From 1db247f5a493f178751ba083483009bfa31c7899 Mon Sep 17 00:00:00 2001 From: maor malka Date: Thu, 28 Aug 2025 18:16:56 -0400 Subject: rustfmt --- embassy-stm32/src/adc/ringbuffered_v3.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/embassy-stm32/src/adc/ringbuffered_v3.rs b/embassy-stm32/src/adc/ringbuffered_v3.rs index 7ff37f68d..d7af2322d 100644 --- a/embassy-stm32/src/adc/ringbuffered_v3.rs +++ b/embassy-stm32/src/adc/ringbuffered_v3.rs @@ -5,7 +5,6 @@ use embassy_hal_internal::Peri; use crate::adc::{Instance, RxDma}; use crate::dma::{ReadableRingBuffer, TransferOptions}; - use crate::rcc; #[cfg_attr(feature = "defmt", derive(defmt::Format))] -- cgit From 5046a8a4a117f68c2792fac8dbbdb50fb0b1e3d8 Mon Sep 17 00:00:00 2001 From: maor malka Date: Mon, 6 Oct 2025 07:50:48 -0400 Subject: stm32/adc/v3: * spelling mistakes fixed * added required changes to ringbufferedadc to support G0 --- embassy-stm32/CHANGELOG.md | 2 +- embassy-stm32/src/adc/ringbuffered_v3.rs | 18 +++++++++--------- embassy-stm32/src/adc/v3.rs | 11 ++++++++--- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index b661e0bae..6e069b22d 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -43,7 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - chore: Updated stm32-metapac and stm32-data dependencies - feat: stm32/adc/v3: allow DMA reads to loop through enable channels - fix: Fix XSPI not disabling alternate bytes when they were previously enabled -- feat: stm32/adc/v3: added support for Continous DMA configuration +- feat: stm32/adc/v3: added support for Continuous DMA configuration - fix: Fix stm32h7rs init when using external flash via XSPI - feat: Add Adc::new_with_clock() to configure analog clock - feat: Add GPDMA linked-list + ringbuffer support ([#3923](https://github.com/embassy-rs/embassy/pull/3923)) diff --git a/embassy-stm32/src/adc/ringbuffered_v3.rs b/embassy-stm32/src/adc/ringbuffered_v3.rs index d7af2322d..a2c9f2bca 100644 --- a/embassy-stm32/src/adc/ringbuffered_v3.rs +++ b/embassy-stm32/src/adc/ringbuffered_v3.rs @@ -37,7 +37,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { } #[inline] - fn start_continous_sampling(&mut self) { + fn start_continuous_sampling(&mut self) { // Start adc conversion T::regs().cr().modify(|reg| { reg.set_adstart(true); @@ -46,7 +46,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { } #[inline] - pub fn stop_continous_sampling(&mut self) { + pub fn stop_continuous_sampling(&mut self) { // Stop adc conversion if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() { T::regs().cr().modify(|reg| { @@ -56,7 +56,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { } } pub fn disable_adc(&mut self) { - self.stop_continous_sampling(); + self.stop_continuous_sampling(); self.ring_buf.clear(); self.ring_buf.request_pause(); } @@ -123,7 +123,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { /// /// /// [`teardown_adc`]: #method.teardown_adc - /// [`start_continous_sampling`]: #method.start_continous_sampling + /// [`start_continuous_sampling`]: #method.start_continuous_sampling pub async fn read(&mut self, measurements: &mut [u16]) -> Result { assert_eq!( self.ring_buf.capacity() / 2, @@ -135,7 +135,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { // Start background receive if it was not already started if !r.cr().read().adstart() { - self.start_continous_sampling(); + self.start_continuous_sampling(); } self.ring_buf.read_exact(measurements).await.map_err(|_| OverrunError) @@ -145,16 +145,16 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { /// If no bytes are currently available in the buffer the call waits until the some /// bytes are available (at least one byte and at most half the buffer size) /// - /// Background receive is started if `start_continous_sampling()` has not been previously called. + /// Background receive is started if `start_continuous_sampling()` has not been previously called. /// /// Receive in the background is terminated if an error is returned. - /// It must then manually be started again by calling `start_continous_sampling()` or by re-calling `blocking_read()`. + /// It must then manually be started again by calling `start_continuous_sampling()` or by re-calling `blocking_read()`. pub fn blocking_read(&mut self, buf: &mut [u16]) -> Result { let r = T::regs(); // Start background receive if it was not already started if !r.cr().read().adstart() { - self.start_continous_sampling(); + self.start_continuous_sampling(); } loop { @@ -164,7 +164,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { return Ok(len); } Err(_) => { - self.stop_continous_sampling(); + self.stop_continuous_sampling(); return Err(OverrunError); } } diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index f714e030f..ef68fe223 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -13,10 +13,10 @@ use super::{ blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, }; -#[cfg(adc_v3)] +#[cfg(adc_v3, adc_g0)] mod ringbuffered_v3; -#[cfg(adc_v3)] +#[cfg(adc_v3, adc_g0)] use ringbuffered_v3::RingBufferedAdc; use crate::dma::Transfer; @@ -576,7 +576,7 @@ impl<'d, T: Instance> Adc<'d, T> { /// It is critical to call `read` frequently to prevent DMA buffer overrun. /// /// [`read`]: #method.read - #[cfg(adc_v3)] + #[cfg(adc_v3, adc_g0)] pub fn into_ring_buffered<'a>( &mut self, dma: Peri<'a, impl RxDma>, @@ -633,6 +633,11 @@ impl<'d, T: Instance> Adc<'d, T> { } _ => unreachable!(), } + + #[cfg(any(adc_g0, adc_u0))] + { + channel_mask |= 1 << channel.channel(); + } } // On G0 and U0 enabled channels are sampled from 0 to last channel. -- cgit From 0a97a1d6536be20e8aea1da2d7a6dcd16ca679d4 Mon Sep 17 00:00:00 2001 From: maor malka Date: Mon, 6 Oct 2025 07:56:20 -0400 Subject: stm32/adc/v3: syntax errors :( --- embassy-stm32/src/adc/v3.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index ef68fe223..39f9ee463 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -13,10 +13,10 @@ use super::{ blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, }; -#[cfg(adc_v3, adc_g0)] +#[cfg(any(adc_v3, adc_g0))] mod ringbuffered_v3; -#[cfg(adc_v3, adc_g0)] +#[cfg(any(adc_v3, adc_g0))] use ringbuffered_v3::RingBufferedAdc; use crate::dma::Transfer; @@ -576,7 +576,7 @@ impl<'d, T: Instance> Adc<'d, T> { /// It is critical to call `read` frequently to prevent DMA buffer overrun. /// /// [`read`]: #method.read - #[cfg(adc_v3, adc_g0)] + #[cfg(any(adc_v3, adc_g0))] pub fn into_ring_buffered<'a>( &mut self, dma: Peri<'a, impl RxDma>, -- cgit From cd91fe3b30dbb3d5b3c9c7d9e7cb151d721fb8d5 Mon Sep 17 00:00:00 2001 From: maor malka Date: Tue, 7 Oct 2025 07:57:29 -0400 Subject: stm32/adc/v3: merged newer adc_g0 configuration method to intoRingBuffered --- embassy-stm32/src/adc/v3.rs | 109 +++++++++++++++++++++++++++----------------- 1 file changed, 67 insertions(+), 42 deletions(-) diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index 39f9ee463..0b9b80db2 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -13,10 +13,10 @@ use super::{ blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, }; -#[cfg(any(adc_v3, adc_g0))] +#[cfg(any(adc_v3, adc_g0, adc_u0))] mod ringbuffered_v3; -#[cfg(any(adc_v3, adc_g0))] +#[cfg(any(adc_v3, adc_g0, adc_u0))] use ringbuffered_v3::RingBufferedAdc; use crate::dma::Transfer; @@ -601,53 +601,78 @@ impl<'d, T: Instance> Adc<'d, T> { w.set_l(sequence.len() as u8 - 1); }); - #[cfg(any(adc_g0, adc_u0))] - let mut channel_mask = 0; + #[cfg(adc_g0)] + { + let mut sample_times = Vec::::new(); + + T::regs().chselr().write(|chselr| { + T::regs().smpr().write(|smpr| { + for (channel, sample_time) in sequence { + chselr.set_chsel(channel.channel.into(), true); + if let Some(i) = sample_times.iter().position(|&t| t == sample_time) { + smpr.set_smpsel(channel.channel.into(), (i as u8).into()); + } else { + smpr.set_sample_time(sample_times.len(), sample_time); + if let Err(_) = sample_times.push(sample_time) { + panic!( + "Implementation is limited to {} unique sample times among all channels.", + SAMPLE_TIMES_CAPACITY + ); + } + } + } + }) + }); + } + #[cfg(not(adc_g0))] + { + #[cfg(adc_u0)] + let mut channel_mask = 0; - // Configure channels and ranks - for (_i, (channel, sample_time)) in sequence.enumerate() { - Self::configure_channel(channel, sample_time); + // Configure channels and ranks + for (_i, (channel, sample_time)) in sequence.enumerate() { + Self::configure_channel(channel, sample_time); - // Each channel is sampled according to sequence - #[cfg(not(any(adc_g0, adc_u0)))] - match _i { - 0..=3 => { - T::regs().sqr1().modify(|w| { - w.set_sq(_i, channel.channel()); - }); - } - 4..=8 => { - T::regs().sqr2().modify(|w| { - w.set_sq(_i - 4, channel.channel()); - }); - } - 9..=13 => { - T::regs().sqr3().modify(|w| { - w.set_sq(_i - 9, channel.channel()); - }); + // Each channel is sampled according to sequence + #[cfg(not(any(adc_g0, adc_u0)))] + match _i { + 0..=3 => { + T::regs().sqr1().modify(|w| { + w.set_sq(_i, channel.channel()); + }); + } + 4..=8 => { + T::regs().sqr2().modify(|w| { + w.set_sq(_i - 4, channel.channel()); + }); + } + 9..=13 => { + T::regs().sqr3().modify(|w| { + w.set_sq(_i - 9, channel.channel()); + }); + } + 14..=15 => { + T::regs().sqr4().modify(|w| { + w.set_sq(_i - 14, channel.channel()); + }); + } + _ => unreachable!(), } - 14..=15 => { - T::regs().sqr4().modify(|w| { - w.set_sq(_i - 14, channel.channel()); - }); + + #[cfg(adc_u0)] + { + channel_mask |= 1 << channel.channel(); } - _ => unreachable!(), } - #[cfg(any(adc_g0, adc_u0))] - { - channel_mask |= 1 << channel.channel(); - } + // On G0 and U0 enabled channels are sampled from 0 to last channel. + // It is possible to add up to 8 sequences if CHSELRMOD = 1. + // However for supporting more than 8 channels alternative CHSELRMOD = 0 approach is used. + #[cfg(adc_u0)] + T::regs().chselr().modify(|reg| { + reg.set_chsel(channel_mask); + }); } - - // On G0 and U0 enabled channels are sampled from 0 to last channel. - // It is possible to add up to 8 sequences if CHSELRMOD = 1. - // However for supporting more than 8 channels alternative CHSELRMOD = 0 approach is used. - #[cfg(any(adc_g0, adc_u0))] - T::regs().chselr().modify(|reg| { - reg.set_chsel(channel_mask); - }); - // Set continuous mode with Circular dma. // Clear overrun flag before starting transfer. T::regs().isr().modify(|reg| { -- cgit From cb0175a89f072d38393368ef380d9db8e3994740 Mon Sep 17 00:00:00 2001 From: maor malka Date: Tue, 7 Oct 2025 15:03:31 -0400 Subject: stm32/adc/v3: missing cfg option for adc_u0 fro into_ring_buffered --- embassy-stm32/src/adc/ringbuffered_v3.rs | 2 +- embassy-stm32/src/adc/v3.rs | 2 +- examples/stm32l4/src/bin/adc_dma.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/embassy-stm32/src/adc/ringbuffered_v3.rs b/embassy-stm32/src/adc/ringbuffered_v3.rs index a2c9f2bca..b58630585 100644 --- a/embassy-stm32/src/adc/ringbuffered_v3.rs +++ b/embassy-stm32/src/adc/ringbuffered_v3.rs @@ -1,5 +1,5 @@ use core::marker::PhantomData; -use core::sync::atomic::{compiler_fence, Ordering}; +use core::sync::atomic::{Ordering, compiler_fence}; use embassy_hal_internal::Peri; diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index 8dcb5d04e..d9a3ce21d 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -576,7 +576,7 @@ impl<'d, T: Instance> Adc<'d, T> { /// It is critical to call `read` frequently to prevent DMA buffer overrun. /// /// [`read`]: #method.read - #[cfg(any(adc_v3, adc_g0))] + #[cfg(any(adc_v3, adc_g0, adc_u0))] pub fn into_ring_buffered<'a>( &mut self, dma: Peri<'a, impl RxDma>, diff --git a/examples/stm32l4/src/bin/adc_dma.rs b/examples/stm32l4/src/bin/adc_dma.rs index a5b7b0c5e..7a9200edd 100644 --- a/examples/stm32l4/src/bin/adc_dma.rs +++ b/examples/stm32l4/src/bin/adc_dma.rs @@ -3,8 +3,8 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::adc::{Adc, AdcChannel, SampleTime}; use embassy_stm32::Config; +use embassy_stm32::adc::{Adc, AdcChannel, SampleTime}; use {defmt_rtt as _, panic_probe as _}; const DMA_BUF_LEN: usize = 512; -- 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 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 1f1e3cd0e3490f23bedb125514550b3a2e0177c1 Mon Sep 17 00:00:00 2001 From: Kat <187942808+northernpaws@users.noreply.github.com> Date: Tue, 21 Oct 2025 22:03:43 -0600 Subject: add 16 bit SDRAM configuration for Bank 1 in fmc.rs --- embassy-stm32/src/fmc.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/embassy-stm32/src/fmc.rs b/embassy-stm32/src/fmc.rs index 8ecfbc522..4fd7dac60 100644 --- a/embassy-stm32/src/fmc.rs +++ b/embassy-stm32/src/fmc.rs @@ -236,6 +236,24 @@ impl<'d, T: Instance> Fmc<'d, T> { (sdcke: SDCKE1Pin), (sdclk: SDCLKPin), (sdncas: SDNCASPin), (sdne: SDNE1Pin), (sdnras: SDNRASPin), (sdnwe: SDNWEPin) ] )); + + fmc_sdram_constructor!(sdram_a13bits_d16bits_16banks_bank1: ( + bank: stm32_fmc::SdramTargetBank::Bank1, + addr: [ + (a0: A0Pin), (a1: A1Pin), (a2: A2Pin), (a3: A3Pin), (a4: A4Pin), (a5: A5Pin), (a6: A6Pin), (a7: A7Pin), (a8: A8Pin), (a9: A9Pin), (a10: A10Pin), (a11: A11Pin), (a12: A12Pin) + ], + ba: [(ba0: BA0Pin), (ba1: BA1Pin)], + d: [ + (d0: D0Pin), (d1: D1Pin), (d2: D2Pin), (d3: D3Pin), (d4: D4Pin), (d5: D5Pin), (d6: D6Pin), (d7: D7Pin), + (d8: D8Pin), (d9: D9Pin), (d10: D10Pin), (d11: D11Pin), (d12: D12Pin), (d13: D13Pin), (d14: D14Pin), (d15: D15Pin) + ], + nbl: [ + (nbl0: NBL0Pin), (nbl1: NBL1Pin) + ], + ctrl: [ + (sdcke: SDCKE0Pin), (sdclk: SDCLKPin), (sdncas: SDNCASPin), (sdne: SDNE0Pin), (sdnras: SDNRASPin), (sdnwe: SDNWEPin) + ] + )); } trait SealedInstance: crate::rcc::RccPeripheral { -- cgit From 51b72a3f5a187b4102ef89f225ff9101742eab40 Mon Sep 17 00:00:00 2001 From: Kat <187942808+northernpaws@users.noreply.github.com> Date: Tue, 21 Oct 2025 22:17:09 -0600 Subject: correct 16 bit SDRAM configuration label from 16 banks to 4 banks --- embassy-stm32/src/fmc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-stm32/src/fmc.rs b/embassy-stm32/src/fmc.rs index 4fd7dac60..b21506c38 100644 --- a/embassy-stm32/src/fmc.rs +++ b/embassy-stm32/src/fmc.rs @@ -237,7 +237,7 @@ impl<'d, T: Instance> Fmc<'d, T> { ] )); - fmc_sdram_constructor!(sdram_a13bits_d16bits_16banks_bank1: ( + fmc_sdram_constructor!(sdram_a13bits_d16bits_4banks_bank1: ( bank: stm32_fmc::SdramTargetBank::Bank1, addr: [ (a0: A0Pin), (a1: A1Pin), (a2: A2Pin), (a3: A3Pin), (a4: A4Pin), (a5: A5Pin), (a6: A6Pin), (a7: A7Pin), (a8: A8Pin), (a9: A9Pin), (a10: A10Pin), (a11: A11Pin), (a12: A12Pin) -- 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 5dac73f15bac10b097f51d941847a28bec36e97f Mon Sep 17 00:00:00 2001 From: Kat <187942808+northernpaws@users.noreply.github.com> Date: Wed, 22 Oct 2025 19:26:12 -0600 Subject: update stm32-fmc dependency version to 0.4.0 Fixes incompatibility issues using other libraries that rely on stm32-fmc 0.4.0 --- embassy-stm32/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 7c243b350..23e8a49d9 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -182,7 +182,7 @@ stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", ta vcell = "0.1.3" nb = "1.0.0" -stm32-fmc = "0.3.0" +stm32-fmc = "0.4.0" cfg-if = "1.0.0" embedded-io = { version = "0.6.0" } embedded-io-async = { version = "0.6.1" } -- 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