From bf01151bbb793c36431820bd814db775bf10a870 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sun, 27 Jul 2025 08:27:09 +0200 Subject: [PATCH 01/28] 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 081eb11919..a1ad9caef4 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| { From 399ec8d90f1c4f62fd20d21dabbb9ce64c6986eb Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sun, 27 Jul 2025 10:35:52 +0200 Subject: [PATCH 02/28] 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 5fb49f943f..f51682900b 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 a1ad9caef4..7d2b731d56 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?, From b690a0314f0f2e42137ad4b3e867e056c1d3c14e Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sun, 27 Jul 2025 10:53:48 +0200 Subject: [PATCH 03/28] 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 f51682900b..58225d937c 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 7d2b731d56..78abb85ea9 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 3b09f1b344..72a7d05ab5 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 { From 593fb963b84741bb05d1cae1dbacafb92b4828c6 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sun, 27 Jul 2025 12:25:03 +0200 Subject: [PATCH 04/28] 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 58225d937c..660b8144ae 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 78abb85ea9..eaf7873346 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 72a7d05ab5..f23c58c9e3 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| { From 392548997ab65e5654a32848d93a05d5fb695a79 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Mon, 28 Jul 2025 08:05:59 +0200 Subject: [PATCH 05/28] 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 eaf7873346..ef5624d97e 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?, } } From c531af42c8dd2eaeb56ef1891396ac559918560e Mon Sep 17 00:00:00 2001 From: HybridChild Date: Thu, 7 Aug 2025 10:34:29 +0200 Subject: [PATCH 06/28] 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 660b8144ae..675a392f93 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 ef5624d97e..f2fd0147ef 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 f23c58c9e3..72a7d05ab5 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| { From b88c5195e030b6fac129ea9cb74eb169227f7335 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sun, 10 Aug 2025 08:31:35 +0200 Subject: [PATCH 07/28] 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 f2fd0147ef..7b6ecf8696 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| { From 6570036f141befc76fd5f5237db0045c0b0f9a71 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sun, 10 Aug 2025 10:05:26 +0200 Subject: [PATCH 08/28] 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 7b6ecf8696..3aa003fa5b 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)); } From d6d54392f15cb0430a50ba85e45d746aa09fc6ac Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sun, 10 Aug 2025 10:48:12 +0200 Subject: [PATCH 09/28] 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 3aa003fa5b..2bc3092589 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 { From 46c95921f38d1548c9afc84c1c660f690fc49be4 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Mon, 11 Aug 2025 11:40:56 +0200 Subject: [PATCH 10/28] 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 2bc3092589..83f01f51c8 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> { From 4f7febc34eab0dd5822f313854338997f6dbf617 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Mon, 11 Aug 2025 12:04:38 +0200 Subject: [PATCH 11/28] 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 83f01f51c8..9022c2f5d9 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 { From fd7158063d38ece65f6f3f13422b2d744e8a144f Mon Sep 17 00:00:00 2001 From: HybridChild Date: Mon, 11 Aug 2025 19:56:52 +0200 Subject: [PATCH 12/28] 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 9022c2f5d9..8f4128b456 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 /// From 03496864d765590744582c00a1b8f010d5014a0c Mon Sep 17 00:00:00 2001 From: HybridChild Date: Tue, 12 Aug 2025 06:49:00 +0200 Subject: [PATCH 13/28] 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 8f4128b456..9b5a524df3 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> { From 0ab366e28af266795106cb254b4a0d63f723d476 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Wed, 13 Aug 2025 20:11:55 +0200 Subject: [PATCH 14/28] 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 9b5a524df3..10a3073966 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 From a52497bd39701cd71a877a3d2bd264ae2dea716c Mon Sep 17 00:00:00 2001 From: HybridChild Date: Fri, 22 Aug 2025 12:44:06 +0200 Subject: [PATCH 15/28] 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 10a3073966..c37b48728f 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 } } From 8111bbc54594706d26d02a22ad8f1853317d3495 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Fri, 22 Aug 2025 13:18:52 +0200 Subject: [PATCH 16/28] 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 c37b48728f..0f2953ef61 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 } } From e630e1a6d48ca25a92bc0479608ca75b7179c869 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Fri, 22 Aug 2025 13:45:43 +0200 Subject: [PATCH 17/28] 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 0f2953ef61..b362ab017c 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; From 572a40b4eee99b177733f50b08e29ff9b5ab6fa5 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sat, 23 Aug 2025 08:31:41 +0200 Subject: [PATCH 18/28] stm32/i2c_v1: Add async and blocking example code --- examples/stm32f4/src/bin/i2c_slave_async.rs | 123 ++++++++++++++++++ .../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 0000000000..072c9875ed --- /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 0000000000..c55f2f6c14 --- /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", + } +} From fbefd6c36aa123e479e39d2bd8c37d987e36597c Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sat, 23 Aug 2025 09:17:49 +0200 Subject: [PATCH 19/28] 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 675a392f93..4caa35efb0 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 b362ab017c..879d1686b6 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 72a7d05ab5..3b09f1b344 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 { From 109c2cbdc4711bd929308944c22d079e986ebeb9 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sat, 23 Aug 2025 10:01:53 +0200 Subject: [PATCH 20/28] 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 4e3b736c7e..74fac14b24 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) From 5d3a1dd7e549de578c6a77d3998beddf1b1474d3 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sat, 23 Aug 2025 10:28:50 +0200 Subject: [PATCH 21/28] 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 4caa35efb0..00330c97e7 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>], From a51c0320e907d895bb1f55e8960a69df5f5a276b Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sat, 23 Aug 2025 10:36:56 +0200 Subject: [PATCH 22/28] 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 9cd4d5951d..c93334102a 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 From 97d5de640a90ce31b81411a742b33d9b86d2b441 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sat, 23 Aug 2025 10:46:39 +0200 Subject: [PATCH 23/28] 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 00330c97e7..7f5cde1c58 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 879d1686b6..d1e15d6f2a 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 { From 944ac0bf138ab78f602627fae97891024c8fd082 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sat, 23 Aug 2025 10:50:05 +0200 Subject: [PATCH 24/28] Run cargo fmt for examples --- examples/stm32f4/src/bin/i2c_slave_async.rs | 44 ++++++++++--------- .../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 072c9875ed..c0719af5e8 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 c55f2f6c14..e027cd5112 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; From 524db5a935e506036c282e3c0dfa9abc807ac7ee Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sat, 23 Aug 2025 10:53:22 +0200 Subject: [PATCH 25/28] 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 c0719af5e8..1c48f1ac7f 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 e027cd5112..a6f4da747b 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; From 3c8d078525c69867710bbd291dc135b3a5011702 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Sat, 23 Aug 2025 10:55:00 +0200 Subject: [PATCH 26/28] 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 1c48f1ac7f..db4a805b61 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 a6f4da747b..a62087a29a 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; From 65438769024b657f98da20646beb10e7e5a1b960 Mon Sep 17 00:00:00 2001 From: xoviat Date: Thu, 30 Oct 2025 14:37:10 -0500 Subject: [PATCH 27/28] rustfmt and update --- embassy-stm32/src/i2c/v1.rs | 3 +-- examples/stm32f4/src/bin/i2c_slave_async.rs | 15 ++++++++++++--- examples/stm32f4/src/bin/i2c_slave_blocking.rs | 16 +++++++++++++--- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index 24892796bc..42d39655f1 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -961,8 +961,7 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { let matched_address = Self::decode_matched_address(sr2, self.info)?; trace!( "I2C slave: address matched, direction={:?}, addr={:?}", - direction, - matched_address + direction, matched_address ); return Ok(SlaveCommand { diff --git a/examples/stm32f4/src/bin/i2c_slave_async.rs b/examples/stm32f4/src/bin/i2c_slave_async.rs index db4a805b61..5065bcdd86 100644 --- a/examples/stm32f4/src/bin/i2c_slave_async.rs +++ b/examples/stm32f4/src/bin/i2c_slave_async.rs @@ -49,7 +49,7 @@ async fn main(spawner: Spawner) { 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(); + spawner.spawn(i2c_slave_task(i2c_slave).unwrap()); } #[embassy_executor::task] @@ -73,8 +73,17 @@ pub async fn i2c_slave_task(mut i2c_slave: I2c<'static, embassy_stm32::mode::Asy 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]); + 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)); diff --git a/examples/stm32f4/src/bin/i2c_slave_blocking.rs b/examples/stm32f4/src/bin/i2c_slave_blocking.rs index a62087a29a..ee06d4ac42 100644 --- a/examples/stm32f4/src/bin/i2c_slave_blocking.rs +++ b/examples/stm32f4/src/bin/i2c_slave_blocking.rs @@ -47,7 +47,7 @@ async fn main(spawner: Spawner) { 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(); + spawner.spawn(i2c_slave_task(i2c_slave).unwrap()); } #[embassy_executor::task] @@ -70,8 +70,18 @@ pub async fn i2c_slave_task(mut i2c_slave: I2c<'static, embassy_stm32::mode::Blo 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]); + 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)); From ae726274ddb95c048c386817ef105012dcc06dad Mon Sep 17 00:00:00 2001 From: xoviat Date: Thu, 30 Oct 2025 15:49:07 -0500 Subject: [PATCH 28/28] reduce diff with misc. reversions --- embassy-stm32/src/i2c/mod.rs | 97 +++++++++++------------ embassy-stm32/src/i2c/v1.rs | 144 ++++++++++++++++++++--------------- 2 files changed, 131 insertions(+), 110 deletions(-) diff --git a/embassy-stm32/src/i2c/mod.rs b/embassy-stm32/src/i2c/mod.rs index 785ebd8660..ee60c3f444 100644 --- a/embassy-stm32/src/i2c/mod.rs +++ b/embassy-stm32/src/i2c/mod.rs @@ -436,55 +436,58 @@ impl<'d, IM: MasterMode> embedded_hal_async::i2c::I2c for I2c<'d, Async, IM> { } } -/// Operation framing configuration for I2C transactions. +/// Frame type in I2C transaction. /// -/// 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. +/// This tells each method what kind of frame 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. /// -/// For write operations, some framing configurations are functionally identical -/// because they differ only in ACK/NACK treatment which is relevant only for reads: +/// For write operations, the following options are identical because they differ only in the (N)ACK +/// treatment relevant for read operations: /// -/// - `First` and `FirstAndNext` behave identically for writes -/// - `Next` and `LastNoStop` behave identically for writes +/// - `FirstFrame` and `FirstAndNextFrame` behave identically for writes +/// - `NextFrame` and `LastFrameNoStop` behave identically for writes +/// +/// Abbreviations used below: /// -/// **Framing Legend:** /// - `ST` = start condition -/// - `SR` = repeated start condition +/// - `SR` = repeated start condition /// - `SP` = stop condition -/// - `ACK/NACK` = acknowledgment behavior for the final byte of read operations +/// - `ACK`/`NACK` = last byte in read operation #[derive(Copy, Clone)] #[allow(dead_code)] -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, +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, } #[allow(dead_code)] -impl OperationFraming { +impl FrameOptions { /// Returns true if a start or repeated start condition should be generated before this operation. fn send_start(self) -> bool { match self { - Self::FirstAndLast | Self::First | Self::FirstAndNext => true, - Self::Next | Self::Last | Self::LastNoStop => false, + Self::FirstAndLastFrame | Self::FirstFrame | Self::FirstAndNextFrame => true, + Self::NextFrame | Self::LastFrame | Self::LastFrameNoStop => false, } } /// Returns true if a stop condition should be generated after this operation. fn send_stop(self) -> bool { match self { - Self::FirstAndLast | Self::Last => true, - Self::First | Self::FirstAndNext | Self::Next | Self::LastNoStop => false, + Self::FirstAndLastFrame | Self::LastFrame => true, + Self::FirstFrame | Self::FirstAndNextFrame | Self::NextFrame | Self::LastFrameNoStop => false, } } @@ -494,16 +497,16 @@ impl OperationFraming { /// next transmission (or stop condition). fn send_nack(self) -> bool { match self { - Self::FirstAndLast | Self::First | Self::Last | Self::LastNoStop => true, - Self::FirstAndNext | Self::Next => false, + Self::FirstAndLastFrame | Self::FirstFrame | Self::LastFrame | Self::LastFrameNoStop => true, + Self::FirstAndNextFrame | Self::NextFrame => false, } } } -/// Analyzes I2C transaction operations and assigns appropriate framing to each. +/// Analyzes I2C transaction operations and assigns appropriate frame 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. +/// frame 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) @@ -512,7 +515,7 @@ impl OperationFraming { /// - Ensuring proper bus handoff between different operation types /// /// **Transaction Contract Compliance:** -/// The framing assignments ensure compliance with the embedded-hal I2C transaction contract, +/// The frame 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. /// @@ -524,12 +527,12 @@ impl OperationFraming { /// * `operations` - Mutable slice of I2C operations from embedded-hal /// /// # Returns -/// An iterator over (operation, framing) pairs, or an error if the transaction is invalid +/// An iterator over (operation, frame) pairs, or an error if the transaction is invalid /// #[allow(dead_code)] -fn assign_operation_framing<'a, 'b: 'a>( +fn operation_frames<'a, 'b: 'a>( operations: &'a mut [embedded_hal_1::i2c::Operation<'b>], -) -> Result, OperationFraming)>, Error> { +) -> Result, FrameOptions)>, Error> { use embedded_hal_1::i2c::Operation::{Read, Write}; // Validate that no read operations have empty buffers before starting the transaction. @@ -555,29 +558,29 @@ fn assign_operation_framing<'a, 'b: 'a>( let is_first_of_type = next_first_operation; let next_op = operations.peek(); - // Compute the appropriate framing based on three key properties: + // Compute the appropriate frame 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 // 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) // - // The third property is checked for all operations since the resulting framing + // The third property is checked for all operations since the resulting frame // configurations are identical for write operations regardless of ACK/NACK treatment. - let framing = match (is_first_of_type, next_op) { + let frame = match (is_first_of_type, next_op) { // First operation of type, and it's also the final operation overall - (true, None) => OperationFraming::FirstAndLast, + (true, None) => FrameOptions::FirstAndLastFrame, // First operation of type, next operation is also a read (continue read sequence) - (true, Some(Read(_))) => OperationFraming::FirstAndNext, + (true, Some(Read(_))) => FrameOptions::FirstAndNextFrame, // First operation of type, next operation is write (end current sequence) - (true, Some(Write(_))) => OperationFraming::First, + (true, Some(Write(_))) => FrameOptions::FirstFrame, // Continuation operation, and it's the final operation overall - (false, None) => OperationFraming::Last, + (false, None) => FrameOptions::LastFrame, // Continuation operation, next operation is also a read (continue read sequence) - (false, Some(Read(_))) => OperationFraming::Next, + (false, Some(Read(_))) => FrameOptions::NextFrame, // Continuation operation, next operation is write (end current sequence, no stop) - (false, Some(Write(_))) => OperationFraming::LastNoStop, + (false, Some(Write(_))) => FrameOptions::LastFrameNoStop, }; // Pre-calculate whether the next operation will be the first of its type. @@ -592,6 +595,6 @@ fn assign_operation_framing<'a, 'b: 'a>( (Read(_), Some(Read(_))) | (Write(_), Some(Write(_))) => false, }; - Some((current_op, framing)) + Some((current_op, frame)) })) } diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index 42d39655f1..128a58db78 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; +use crate::mode::Mode as PeriMode; use crate::pac::i2c; // /!\ /!\ @@ -43,7 +43,7 @@ pub unsafe fn on_interrupt() { }); } -impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { +impl<'d, M: PeriMode, IM: MasterMode> I2c<'d, M, IM> { pub(crate) fn init(&mut self, config: Config) { self.info.regs.cr1().modify(|reg| { reg.set_pe(false); @@ -82,8 +82,8 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { reg.set_freq(timings.freq); }); self.info.regs.ccr().modify(|reg| { - reg.set_f_s(timings.f_s); - reg.set_duty(timings.duty); + reg.set_f_s(timings.mode.f_s()); + reg.set_duty(timings.duty.duty()); reg.set_ccr(timings.ccr); }); self.info.regs.trise().modify(|reg| { @@ -158,9 +158,9 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { address: u8, write_buffer: &[u8], timeout: Timeout, - framing: OperationFraming, + frame: FrameOptions, ) -> Result<(), Error> { - if framing.send_start() { + if frame.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> { self.send_byte(*c, timeout)?; } - if framing.send_stop() { + if frame.send_stop() { // Send a STOP condition self.info.regs.cr1().modify(|reg| reg.set_stop(true)); } @@ -247,13 +247,13 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { address: u8, read_buffer: &mut [u8], timeout: Timeout, - framing: OperationFraming, + frame: FrameOptions, ) -> Result<(), Error> { let Some((last_byte, read_buffer)) = read_buffer.split_last_mut() else { return Err(Error::Overrun); }; - if framing.send_start() { + if frame.send_start() { // Send a START condition and set ACK bit self.info.regs.cr1().modify(|reg| { reg.set_start(true); @@ -290,10 +290,10 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { // Prepare to send NACK then STOP after next byte self.info.regs.cr1().modify(|reg| { - if framing.send_nack() { + if frame.send_nack() { reg.set_ack(false); } - if framing.send_stop() { + if frame.send_stop() { reg.set_stop(true); } }); @@ -307,12 +307,12 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { /// Blocking read. 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) + self.blocking_read_timeout(address, read_buffer, self.timeout(), FrameOptions::FirstAndLastFrame) } /// Blocking write. pub fn blocking_write(&mut self, address: u8, write_buffer: &[u8]) -> Result<(), Error> { - self.write_bytes(address, write_buffer, self.timeout(), OperationFraming::FirstAndLast)?; + self.write_bytes(address, write_buffer, self.timeout(), FrameOptions::FirstAndLastFrame)?; // Fallthrough is success Ok(()) @@ -333,8 +333,8 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { let timeout = self.timeout(); - self.write_bytes(address, write_buffer, timeout, OperationFraming::First)?; - self.blocking_read_timeout(address, read_buffer, timeout, OperationFraming::FirstAndLast)?; + self.write_bytes(address, write_buffer, timeout, FrameOptions::FirstFrame)?; + self.blocking_read_timeout(address, read_buffer, timeout, FrameOptions::FirstAndLastFrame)?; Ok(()) } @@ -347,10 +347,10 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { pub fn blocking_transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> { let timeout = self.timeout(); - for (op, framing) in assign_operation_framing(operations)? { + for (op, frame) in operation_frames(operations)? { match op { - 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)?, + Operation::Read(read_buffer) => self.blocking_read_timeout(address, read_buffer, timeout, frame)?, + Operation::Write(write_buffer) => self.write_bytes(address, write_buffer, timeout, frame)?, } } @@ -380,12 +380,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { } 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_frame(&mut self, address: u8, write_buffer: &[u8], frame: FrameOptions) -> 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. @@ -407,7 +402,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { }) }); - if framing.send_start() { + if frame.send_start() { // Send a START condition self.info.regs.cr1().modify(|reg| { reg.set_start(true); @@ -498,7 +493,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { w.set_dmaen(false); }); - if framing.send_stop() { + if frame.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 @@ -534,7 +529,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { /// Write. pub async fn write(&mut self, address: u8, write_buffer: &[u8]) -> Result<(), Error> { - self.write_with_framing(address, write_buffer, OperationFraming::FirstAndLast) + self.write_frame(address, write_buffer, FrameOptions::FirstAndLastFrame) .await?; Ok(()) @@ -542,18 +537,13 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { /// Read. pub async fn read(&mut self, address: u8, read_buffer: &mut [u8]) -> Result<(), Error> { - self.read_with_framing(address, read_buffer, OperationFraming::FirstAndLast) + self.read_frame(address, read_buffer, FrameOptions::FirstAndLastFrame) .await?; Ok(()) } - async fn read_with_framing( - &mut self, - address: u8, - read_buffer: &mut [u8], - framing: OperationFraming, - ) -> Result<(), Error> { + async fn read_frame(&mut self, address: u8, read_buffer: &mut [u8], frame: FrameOptions) -> Result<(), Error> { if read_buffer.is_empty() { return Err(Error::Overrun); } @@ -571,7 +561,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(framing.send_nack() && !single_byte); + w.set_last(frame.send_nack() && !single_byte); }); // Sentinel to disable transfer when an error occurs or future is canceled. @@ -584,7 +574,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { }) }); - if framing.send_start() { + if frame.send_start() { // Send a START condition and set ACK bit self.info.regs.cr1().modify(|reg| { reg.set_start(true); @@ -639,7 +629,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 framing.send_nack() && single_byte { + if frame.send_nack() && single_byte { self.info.regs.cr1().modify(|w| { w.set_ack(false); }); @@ -650,7 +640,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { } 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 { + if frame.send_nack() && single_byte { self.info.regs.cr1().modify(|w| { w.set_ack(false); }); @@ -660,7 +650,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 framing.send_stop() && single_byte { + if frame.send_stop() && single_byte { self.info.regs.cr1().modify(|w| { w.set_stop(true); }); @@ -697,7 +687,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { w.set_dmaen(false); }); - if framing.send_stop() && !single_byte { + if frame.send_stop() && !single_byte { self.info.regs.cr1().modify(|w| { w.set_stop(true); }); @@ -717,9 +707,9 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { return Err(Error::Overrun); } - self.write_with_framing(address, write_buffer, OperationFraming::First) + self.write_frame(address, write_buffer, FrameOptions::FirstFrame) .await?; - self.read_with_framing(address, read_buffer, OperationFraming::FirstAndLast) + self.read_frame(address, read_buffer, FrameOptions::FirstAndLastFrame) .await } @@ -729,10 +719,10 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { /// /// [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)? { + for (op, frame) in operation_frames(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?, + Operation::Read(read_buffer) => self.read_frame(address, read_buffer, frame).await?, + Operation::Write(write_buffer) => self.write_frame(address, write_buffer, frame).await?, } } @@ -740,6 +730,34 @@ 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, + } + } +} + /// Result of attempting to send a byte in slave transmitter mode #[derive(Debug, PartialEq)] enum TransmitResult { @@ -776,7 +794,7 @@ enum SlaveTermination { Nack, } -impl<'d, M: Mode> I2c<'d, M, Master> { +impl<'d, M: PeriMode> 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 { @@ -797,7 +815,7 @@ impl<'d, M: Mode> I2c<'d, M, Master> { } // Address configuration methods -impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { +impl<'d, M: PeriMode, 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); @@ -888,7 +906,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { } } -impl<'d, M: Mode> I2c<'d, M, MultiMaster> { +impl<'d, M: PeriMode> 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. @@ -1685,11 +1703,11 @@ impl<'d> I2c<'d, Async, MultiMaster> { /// 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 + mode: Mode, // Standard or Fast mode selection + trise: u8, // Rise time compensation value + ccr: u16, // Clock control register value + duty: Duty, // Fast mode duty cycle selection } impl Timings { @@ -1709,25 +1727,25 @@ impl Timings { let mut ccr; let duty; - let f_s; + let mode; // I2C clock control calculation if frequency <= 100_000 { - duty = i2c::vals::Duty::DUTY2_1; - f_s = i2c::vals::FS::STANDARD; + duty = Duty::Duty2_1; + mode = Mode::Standard; ccr = { let ccr = clock / (frequency * 2); if ccr < 4 { 4 } else { ccr } }; } else { const DUTYCYCLE: u8 = 0; - f_s = i2c::vals::FS::FAST; + mode = Mode::Fast; if DUTYCYCLE == 0 { - duty = i2c::vals::Duty::DUTY2_1; + duty = Duty::Duty2_1; ccr = clock / (frequency * 3); ccr = if ccr < 1 { 1 } else { ccr }; } else { - duty = i2c::vals::Duty::DUTY16_9; + duty = Duty::Duty16_9; ccr = clock / (frequency * 25); ccr = if ccr < 1 { 1 } else { ccr }; } @@ -1735,15 +1753,15 @@ impl Timings { Self { freq: freq as u8, - f_s, trise: trise as u8, ccr: ccr as u16, duty, + mode, } } } -impl<'d, M: Mode> SetConfig for I2c<'d, M, Master> { +impl<'d, M: PeriMode> SetConfig for I2c<'d, M, Master> { type Config = Hertz; type ConfigError = (); fn set_config(&mut self, config: &Self::Config) -> Result<(), ()> { @@ -1752,8 +1770,8 @@ impl<'d, M: Mode> SetConfig for I2c<'d, M, Master> { reg.set_freq(timings.freq); }); self.info.regs.ccr().modify(|reg| { - reg.set_f_s(timings.f_s); - reg.set_duty(timings.duty); + reg.set_f_s(timings.mode.f_s()); + reg.set_duty(timings.duty.duty()); reg.set_ccr(timings.ccr); }); self.info.regs.trise().modify(|reg| {