From 5e76be83cf693d2de4608fec4ef11fbeb32722d4 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Tue, 11 Nov 2025 20:33:00 +0100 Subject: stm32/i2c_v2: Add initial transaction implementation --- embassy-stm32/src/i2c/v2.rs | 502 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 474 insertions(+), 28 deletions(-) diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index 4527e55b9..061f4ff3a 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -172,20 +172,23 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { length: usize, stop: Stop, reload: bool, + restart: bool, timeout: Timeout, ) -> Result<(), Error> { assert!(length < 256); - // Wait for any previous address sequence to end - // automatically. This could be up to 50% of a bus - // cycle (ie. up to 0.5/freq) - while info.regs.cr2().read().start() { - timeout.check()?; - } + if !restart { + // Wait for any previous address sequence to end + // automatically. This could be up to 50% of a bus + // cycle (ie. up to 0.5/freq) + while info.regs.cr2().read().start() { + timeout.check()?; + } - // Wait for the bus to be free - while info.regs.isr().read().busy() { - timeout.check()?; + // Wait for the bus to be free + while info.regs.isr().read().busy() { + timeout.check()?; + } } let reload = if reload { @@ -210,7 +213,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { Ok(()) } - fn reload(info: &'static Info, length: usize, will_reload: bool, timeout: Timeout) -> Result<(), Error> { + fn reload(info: &'static Info, length: usize, will_reload: bool, stop: Stop, timeout: Timeout) -> Result<(), Error> { assert!(length < 256 && length > 0); while !info.regs.isr().read().tcr() { @@ -226,6 +229,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { info.regs.cr2().modify(|w| { w.set_nbytes(length as u8); w.set_reload(will_reload); + w.set_autoend(stop.autoend()); }); Ok(()) @@ -403,7 +407,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { for (number, chunk) in read.chunks_mut(255).enumerate() { if number != 0 { - Self::reload(self.info, chunk.len(), number != last_chunk_idx, timeout)?; + Self::reload(self.info, chunk.len(), number != last_chunk_idx, Stop::Automatic, timeout)?; } for byte in chunk { @@ -441,6 +445,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { write.len().min(255), Stop::Software, last_chunk_idx != 0, + false, // restart timeout, ) { if send_stop { @@ -451,7 +456,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { for (number, chunk) in write.chunks(255).enumerate() { if number != 0 { - Self::reload(self.info, chunk.len(), number != last_chunk_idx, timeout)?; + Self::reload(self.info, chunk.len(), number != last_chunk_idx, Stop::Software, timeout)?; } for byte in chunk { @@ -507,9 +512,219 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { /// /// [transaction contract]: embedded_hal_1::i2c::I2c::transaction pub fn blocking_transaction(&mut self, addr: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> { - let _ = addr; - let _ = operations; - todo!() + if operations.is_empty() { + return Err(Error::ZeroLengthTransfer); + } + + let address = addr.into(); + let timeout = self.timeout(); + + // Group consecutive operations of the same type + let mut op_idx = 0; + let mut is_first_group = true; + + while op_idx < operations.len() { + // Determine the type of current group and find all consecutive operations of same type + let is_read = matches!(operations[op_idx], Operation::Read(_)); + let group_start = op_idx; + + // Find end of this group (consecutive operations of same type) + while op_idx < operations.len() && matches!(operations[op_idx], Operation::Read(_)) == is_read { + op_idx += 1; + } + let group_end = op_idx; + let is_last_group = op_idx >= operations.len(); + + // Execute this group of operations + if is_read { + self.execute_read_group( + address, + &mut operations[group_start..group_end], + is_first_group, + is_last_group, + timeout, + )?; + } else { + self.execute_write_group( + address, + &operations[group_start..group_end], + is_first_group, + is_last_group, + timeout, + )?; + } + + is_first_group = false; + } + + Ok(()) + } + + fn execute_write_group( + &mut self, + address: Address, + operations: &[Operation<'_>], + is_first_group: bool, + is_last_group: bool, + timeout: Timeout, + ) -> Result<(), Error> { + // Calculate total bytes across all operations in this group + let total_bytes: usize = operations + .iter() + .map(|op| match op { + Operation::Write(buf) => buf.len(), + _ => 0, + }) + .sum(); + + if total_bytes == 0 { + // Handle empty write group - just send address + if is_first_group { + Self::master_write(self.info, address, 0, Stop::Software, false, !is_first_group, timeout)?; + } + if is_last_group { + self.master_stop(); + } + return Ok(()); + } + + let mut total_remaining = total_bytes; + let mut first_chunk = true; + + for operation in operations { + if let Operation::Write(buffer) = operation { + for chunk in buffer.chunks(255) { + let chunk_len = chunk.len(); + total_remaining -= chunk_len; + let is_last_chunk = total_remaining == 0; + let will_reload = !is_last_chunk; + + if first_chunk { + // First chunk: initiate transfer + // If not first group, use RESTART instead of START + Self::master_write(self.info, address, chunk_len, Stop::Software, will_reload, !is_first_group, timeout)?; + first_chunk = false; + } else { + // Subsequent chunks: use reload + // Always use Software stop for writes + Self::reload(self.info, chunk_len, will_reload, Stop::Software, timeout)?; + } + + // Send data bytes + for byte in chunk { + self.wait_txis(timeout)?; + self.info.regs.txdr().write(|w| w.set_txdata(*byte)); + } + } + } + } + + // Wait for transfer to complete + if is_last_group { + self.wait_tc(timeout)?; + self.master_stop(); + self.wait_stop(timeout)?; + } else { + // Wait for TC before next group (enables RESTART) + self.wait_tc(timeout)?; + } + + Ok(()) + } + + fn execute_read_group( + &mut self, + address: Address, + operations: &mut [Operation<'_>], + is_first_group: bool, + is_last_group: bool, + timeout: Timeout, + ) -> Result<(), Error> { + // Calculate total bytes across all operations in this group + let total_bytes: usize = operations + .iter() + .map(|op| match op { + Operation::Read(buf) => buf.len(), + _ => 0, + }) + .sum(); + + if total_bytes == 0 { + // Handle empty read group + if is_first_group { + Self::master_read( + self.info, + address, + 0, + if is_last_group { Stop::Automatic } else { Stop::Software }, + false, + !is_first_group, + timeout, + )?; + } + if is_last_group { + self.wait_stop(timeout)?; + } + return Ok(()); + } + + let mut total_remaining = total_bytes; + let mut first_chunk = true; + + for operation in operations { + if let Operation::Read(buffer) = operation { + for chunk in buffer.chunks_mut(255) { + let chunk_len = chunk.len(); + total_remaining -= chunk_len; + let is_last_chunk = total_remaining == 0; + let will_reload = !is_last_chunk; + + if first_chunk { + // First chunk: initiate transfer + let stop = if is_last_group && is_last_chunk { + Stop::Automatic + } else { + Stop::Software + }; + + Self::master_read( + self.info, + address, + chunk_len, + stop, + will_reload, + !is_first_group, // restart if not first group + timeout, + )?; + first_chunk = false; + } else { + // Subsequent chunks: use reload + let stop = if is_last_group && is_last_chunk { + Stop::Automatic + } else { + Stop::Software + }; + Self::reload(self.info, chunk_len, will_reload, stop, timeout)?; + } + + // Receive data bytes + for byte in chunk { + self.wait_rxne(timeout)?; + *byte = self.info.regs.rxdr().read().rxdata(); + } + } + } + } + + // Wait for transfer to complete + if is_last_group { + self.wait_stop(timeout)?; + } + // For non-last read groups, don't wait for TC - the peripheral may hold SCL low + // in Software AUTOEND mode until the next START is issued. Just proceed directly + // to the next group which will issue RESTART and release the clock. + + Ok(()) } /// Blocking write multiple buffers. @@ -531,6 +746,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { first_length.min(255), Stop::Software, (first_length > 255) || (last_slice_index != 0), + false, // restart timeout, ) { self.master_stop(); @@ -552,6 +768,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { self.info, slice_len.min(255), (idx != last_slice_index) || (slice_len > 255), + Stop::Software, timeout, ) { if err != Error::Nack { @@ -567,6 +784,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { self.info, chunk.len(), (number != last_chunk_idx) || (idx != last_slice_index), + Stop::Software, timeout, ) { if err != Error::Nack { @@ -610,6 +828,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { first_slice: bool, last_slice: bool, send_stop: bool, + restart: bool, timeout: Timeout, ) -> Result<(), Error> { let total_len = write.len(); @@ -676,10 +895,11 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { total_len.min(255), Stop::Software, (total_len > 255) || !last_slice, + restart, timeout, )?; } else { - Self::reload(self.info, total_len.min(255), (total_len > 255) || !last_slice, timeout)?; + Self::reload(self.info, total_len.min(255), (total_len > 255) || !last_slice, Stop::Software, timeout)?; self.info.regs.cr1().modify(|w| w.set_tcie(true)); } } else if !(isr.tcr() || isr.tc()) { @@ -690,7 +910,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { } else { let last_piece = (remaining_len <= 255) && last_slice; - if let Err(e) = Self::reload(self.info, remaining_len.min(255), !last_piece, timeout) { + if let Err(e) = Self::reload(self.info, remaining_len.min(255), !last_piece, Stop::Software, timeout) { return Poll::Ready(Err(e)); } self.info.regs.cr1().modify(|w| w.set_tcie(true)); @@ -793,7 +1013,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { } else { let last_piece = remaining_len <= 255; - if let Err(e) = Self::reload(self.info, remaining_len.min(255), !last_piece, timeout) { + if let Err(e) = Self::reload(self.info, remaining_len.min(255), !last_piece, Stop::Automatic, timeout) { return Poll::Ready(Err(e)); } // Return here if we are on last chunk, @@ -826,7 +1046,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { self.write_internal(address.into(), write, true, timeout) } else { timeout - .with(self.write_dma_internal(address.into(), write, true, true, true, timeout)) + .with(self.write_dma_internal(address.into(), write, true, true, true, false, timeout)) .await } } @@ -850,7 +1070,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { let next = iter.next(); let is_last = next.is_none(); - let fut = self.write_dma_internal(address, c, first, is_last, is_last, timeout); + let fut = self.write_dma_internal(address, c, first, is_last, is_last, false, timeout); timeout.with(fut).await?; first = false; current = next; @@ -881,7 +1101,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { if write.is_empty() { self.write_internal(address.into(), write, false, timeout)?; } else { - let fut = self.write_dma_internal(address.into(), write, true, true, false, timeout); + let fut = self.write_dma_internal(address.into(), write, true, true, false, false, timeout); timeout.with(fut).await?; } @@ -903,9 +1123,235 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { pub async fn transaction(&mut self, addr: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> { #[cfg(all(feature = "low-power", stm32wlex))] let _device_busy = crate::low_power::DeviceBusy::new_stop1(); - let _ = addr; - let _ = operations; - todo!() + + if operations.is_empty() { + return Err(Error::ZeroLengthTransfer); + } + + let address = addr.into(); + let timeout = self.timeout(); + + // Group consecutive operations of the same type + let mut op_idx = 0; + let mut is_first_group = true; + + while op_idx < operations.len() { + // Determine the type of current group and find all consecutive operations of same type + let is_read = matches!(operations[op_idx], Operation::Read(_)); + let group_start = op_idx; + + // Find end of this group (consecutive operations of same type) + while op_idx < operations.len() && matches!(operations[op_idx], Operation::Read(_)) == is_read { + op_idx += 1; + } + let group_end = op_idx; + let is_last_group = op_idx >= operations.len(); + + // Execute this group of operations + if is_read { + self.execute_read_group_async( + address, + &mut operations[group_start..group_end], + is_first_group, + is_last_group, + timeout, + ) + .await?; + } else { + self.execute_write_group_async( + address, + &operations[group_start..group_end], + is_first_group, + is_last_group, + timeout, + ) + .await?; + } + + is_first_group = false; + } + + Ok(()) + } + + async fn execute_write_group_async( + &mut self, + address: Address, + operations: &[Operation<'_>], + is_first_group: bool, + is_last_group: bool, + timeout: Timeout, + ) -> Result<(), Error> { + // Calculate total bytes across all operations in this group + let total_bytes: usize = operations + .iter() + .map(|op| match op { + Operation::Write(buf) => buf.len(), + _ => 0, + }) + .sum(); + + if total_bytes == 0 { + // Handle empty write group using blocking call + if is_first_group { + Self::master_write(self.info, address, 0, Stop::Software, false, !is_first_group, timeout)?; + } + if is_last_group { + self.master_stop(); + } + return Ok(()); + } + + let send_stop = is_last_group; + + // Use DMA for each operation in the group + let mut first_in_group = true; + let mut remaining_operations = operations.len(); + + for operation in operations { + if let Operation::Write(buffer) = operation { + remaining_operations -= 1; + let is_last_in_group = remaining_operations == 0; + + if buffer.is_empty() { + // Skip empty buffers + continue; + } + + let fut = self.write_dma_internal( + address, + buffer, + first_in_group && is_first_group, // first_slice + is_last_in_group && is_last_group, // last_slice + send_stop && is_last_in_group, // send_stop + !is_first_group && first_in_group, // restart + timeout, + ); + timeout.with(fut).await?; + first_in_group = false; + } + } + + // If not last group, wait for TC to enable RESTART + if !is_last_group { + self.wait_tc(timeout)?; + } + + Ok(()) + } + + async fn execute_read_group_async( + &mut self, + address: Address, + operations: &mut [Operation<'_>], + is_first_group: bool, + is_last_group: bool, + timeout: Timeout, + ) -> Result<(), Error> { + // Calculate total bytes across all operations in this group + let total_bytes: usize = operations + .iter() + .map(|op| match op { + Operation::Read(buf) => buf.len(), + _ => 0, + }) + .sum(); + + if total_bytes == 0 { + // Handle empty read group using blocking call + if is_first_group { + Self::master_read( + self.info, + address, + 0, + if is_last_group { Stop::Automatic } else { Stop::Software }, + false, + !is_first_group, + timeout, + )?; + } + if is_last_group { + self.wait_stop(timeout)?; + } + return Ok(()); + } + + // For read operations, we need to handle them differently since read_dma_internal + // expects a single buffer. We'll iterate through operations and use DMA for each. + let mut total_remaining = total_bytes; + let restart = !is_first_group; + + for operation in operations { + if let Operation::Read(buffer) = operation { + if buffer.is_empty() { + // Skip empty buffers + continue; + } + + let is_first_read = total_remaining == total_bytes; + let buf_len = buffer.len(); + total_remaining -= buf_len; + let is_last_read = total_remaining == 0; + + if is_first_read { + // First read in the group + let completed_chunks = buf_len / 255; + let total_chunks = if completed_chunks * 255 == buf_len { + completed_chunks + } else { + completed_chunks + 1 + }; + let last_chunk_idx = total_chunks.saturating_sub(1); + + // Use master_read to initiate, then DMA for data + Self::master_read( + self.info, + address, + buf_len.min(255), + if is_last_group && is_last_read { + Stop::Automatic + } else { + Stop::Software + }, + last_chunk_idx != 0 || !is_last_read, + restart, + timeout, + )?; + } + + // Use the existing read_dma_internal, but we need to handle the reload logic ourselves + // For simplicity with consecutive reads, fall back to blocking for now + // This maintains correctness while keeping complexity manageable + for (chunk_idx, chunk) in buffer.chunks_mut(255).enumerate() { + let chunk_len = chunk.len(); + let is_very_last = total_remaining == 0 && chunk_len == chunk.len(); + + if !is_first_read || chunk_idx != 0 { + let stop = if is_last_group && is_very_last { + Stop::Automatic + } else { + Stop::Software + }; + Self::reload(self.info, chunk_len, !(is_last_group && is_very_last), stop, timeout)?; + } + + for byte in chunk { + self.wait_rxne(timeout)?; + *byte = self.info.regs.rxdr().read().rxdata(); + } + } + } + } + + // Wait for transfer to complete + if is_last_group { + self.wait_stop(timeout)?; + } + // For non-last read groups, don't wait for TC - the peripheral may hold SCL low + // in Software AUTOEND mode until the next START is issued. Just proceed directly + // to the next group which will issue RESTART and release the clock. + + Ok(()) } } @@ -1043,7 +1489,7 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { if number == 0 { Self::slave_start(self.info, chunk.len(), number != last_chunk_idx); } else { - Self::reload(self.info, chunk.len(), number != last_chunk_idx, timeout)?; + Self::reload(self.info, chunk.len(), number != last_chunk_idx, Stop::Software, timeout)?; } let mut index = 0; @@ -1092,7 +1538,7 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { if number == 0 { Self::slave_start(self.info, chunk.len(), number != last_chunk_idx); } else { - Self::reload(self.info, chunk.len(), number != last_chunk_idx, timeout)?; + Self::reload(self.info, chunk.len(), number != last_chunk_idx, Stop::Software, timeout)?; } let mut index = 0; @@ -1228,7 +1674,7 @@ impl<'d> I2c<'d, Async, MultiMaster> { Poll::Pending } else if isr.tcr() { let is_last_slice = remaining_len <= 255; - if let Err(e) = Self::reload(self.info, remaining_len.min(255), !is_last_slice, timeout) { + if let Err(e) = Self::reload(self.info, remaining_len.min(255), !is_last_slice, Stop::Software, timeout) { return Poll::Ready(Err(e)); } remaining_len = remaining_len.saturating_sub(255); @@ -1292,7 +1738,7 @@ impl<'d> I2c<'d, Async, MultiMaster> { Poll::Pending } else if isr.tcr() { let is_last_slice = remaining_len <= 255; - if let Err(e) = Self::reload(self.info, remaining_len.min(255), !is_last_slice, timeout) { + if let Err(e) = Self::reload(self.info, remaining_len.min(255), !is_last_slice, Stop::Software, timeout) { return Poll::Ready(Err(e)); } remaining_len = remaining_len.saturating_sub(255); -- cgit From aa5c0c02425104fceea9e5dc773e3f5c346e9656 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Wed, 12 Nov 2025 09:49:25 +0100 Subject: stm32/i2c_v2: Add transaction test --- examples/stm32f0/Cargo.toml | 1 + examples/stm32f0/src/bin/i2c_transaction_test.rs | 219 +++++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 examples/stm32f0/src/bin/i2c_transaction_test.rs diff --git a/examples/stm32f0/Cargo.toml b/examples/stm32f0/Cargo.toml index a78873d21..177dd0ac2 100644 --- a/examples/stm32f0/Cargo.toml +++ b/examples/stm32f0/Cargo.toml @@ -16,6 +16,7 @@ panic-probe = { version = "1.0.0", features = ["print-defmt"] } embassy-sync = { version = "0.7.2", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.9.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } embassy-time = { version = "0.5.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } static_cell = "2" portable-atomic = { version = "1.5", features = ["unsafe-assume-single-core"] } diff --git a/examples/stm32f0/src/bin/i2c_transaction_test.rs b/examples/stm32f0/src/bin/i2c_transaction_test.rs new file mode 100644 index 000000000..0ecc3e8b1 --- /dev/null +++ b/examples/stm32f0/src/bin/i2c_transaction_test.rs @@ -0,0 +1,219 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::i2c::{Config, I2c, Master}; +use embassy_stm32::mode::Blocking; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, i2c, peripherals}; +use embassy_time::Timer; +use embedded_hal_1::i2c::Operation; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + I2C1 => i2c::EventInterruptHandler, i2c::ErrorInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // For STM32F072RB on NUCLEO board + let p = embassy_stm32::init(Default::default()); + + info!("I2C Transaction Test Starting..."); + + // Initialize I2C1: PB6=SCL, PB7=SDA + let mut config = Config::default(); + config.frequency = Hertz(100_000); + let mut i2c = I2c::new_blocking( + p.I2C1, + p.PB8, // SCL + p.PB9, // SDA + config, + ); + + let slave_addr = 0x50u8; + + // Wait for devices to initialize + Timer::after_millis(100).await; + + info!("=== Test 1: Consecutive Writes (Should Merge) ==="); + test_consecutive_writes(&mut i2c, slave_addr); + Timer::after_millis(500).await; + + info!("=== Test 2: Consecutive Reads (Should Merge) ==="); + test_consecutive_reads(&mut i2c, slave_addr); + Timer::after_millis(500).await; + + info!("=== Test 3: Write then Read (RESTART) ==="); + test_write_then_read(&mut i2c, slave_addr); + Timer::after_millis(500).await; + + info!("=== Test 4: Read then Write (RESTART) ==="); + test_read_then_write(&mut i2c, slave_addr); + Timer::after_millis(500).await; + + info!("=== Test 5: Complex Mixed Sequence ==="); + test_mixed_sequence(&mut i2c, slave_addr); + Timer::after_millis(500).await; + + info!("=== Test 6: Single Operations ==="); + test_single_operations(&mut i2c, slave_addr); + + info!("All tests complete!"); + + loop { + Timer::after_secs(1).await; + } +} + +fn test_consecutive_writes(i2c: &mut I2c<'static, Blocking, Master>, addr: u8) { + // Expected on bus: START, ADDR+W, data1, data2, data3, STOP + // NO intermediate RESTART/STOP between writes + let data1 = [0x10, 0x11, 0x12]; + let data2 = [0x20, 0x21]; + let data3 = [0x30, 0x31, 0x32, 0x33]; + + let mut ops = [ + Operation::Write(&data1), + Operation::Write(&data2), + Operation::Write(&data3), + ]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => info!("✓ Consecutive writes succeeded"), + Err(e) => warn!("✗ Consecutive writes failed: {:?}", e), + } + + info!("Expected: START, ADDR+W, [9 bytes], STOP"); + info!("Check Analog Discovery: No RESTART between writes"); +} + +fn test_consecutive_reads(i2c: &mut I2c<'static, Blocking, Master>, addr: u8) { + // Expected on bus: START, ADDR+R, data1, data2, data3, NACK, STOP + // NO intermediate RESTART/STOP between reads + let mut buf1 = [0u8; 4]; + let mut buf2 = [0u8; 3]; + let mut buf3 = [0u8; 2]; + + let mut ops = [ + Operation::Read(&mut buf1), + Operation::Read(&mut buf2), + Operation::Read(&mut buf3), + ]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => { + info!("✓ Consecutive reads succeeded"); + info!(" buf1: {:02x}", buf1); + info!(" buf2: {:02x}", buf2); + info!(" buf3: {:02x}", buf3); + } + Err(e) => warn!("✗ Consecutive reads failed: {:?}", e), + } + + info!("Expected: START, ADDR+R, [9 bytes], NACK on last, STOP"); + info!("Check Analog Discovery: No RESTART between reads"); +} + +fn test_write_then_read(i2c: &mut I2c<'static, Blocking, Master>, addr: u8) { + // Expected: START, ADDR+W, data, RESTART, ADDR+R, data, NACK, STOP + let write_data = [0xAA, 0xBB]; + let mut read_buf = [0u8; 4]; + + let mut ops = [ + Operation::Write(&write_data), + Operation::Read(&mut read_buf), + ]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => { + info!("✓ Write-then-read succeeded"); + info!(" Written: {:02x}", write_data); + info!(" Read: {:02x}", read_buf); + } + Err(e) => warn!("✗ Write-then-read failed: {:?}", e), + } + + info!("Expected: START, ADDR+W, [2 bytes], RESTART, ADDR+R, [4 bytes], NACK, STOP"); + info!("Check Analog Discovery: RESTART between write and read"); +} + +fn test_read_then_write(i2c: &mut I2c<'static, Blocking, Master>, addr: u8) { + // Expected: START, ADDR+R, data, NACK, RESTART, ADDR+W, data, STOP + let mut read_buf = [0u8; 3]; + let write_data = [0xCC, 0xDD, 0xEE]; + + let mut ops = [ + Operation::Read(&mut read_buf), + Operation::Write(&write_data), + ]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => { + info!("✓ Read-then-write succeeded"); + info!(" Read: {:02x}", read_buf); + info!(" Written: {:02x}", write_data); + } + Err(e) => warn!("✗ Read-then-write failed: {:?}", e), + } + + info!("Expected: START, ADDR+R, [3 bytes], NACK, RESTART, ADDR+W, [3 bytes], STOP"); + info!("Check Analog Discovery: RESTART between read and write"); +} + +fn test_mixed_sequence(i2c: &mut I2c<'static, Blocking, Master>, addr: u8) { + // Complex: W, W, R, R, W, R + // Groups: [W,W] RESTART [R,R] RESTART [W] RESTART [R] + let w1 = [0x01, 0x02]; + let w2 = [0x03, 0x04]; + let mut r1 = [0u8; 2]; + let mut r2 = [0u8; 2]; + let w3 = [0x05]; + let mut r3 = [0u8; 1]; + + let mut ops = [ + Operation::Write(&w1), + Operation::Write(&w2), + Operation::Read(&mut r1), + Operation::Read(&mut r2), + Operation::Write(&w3), + Operation::Read(&mut r3), + ]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => { + info!("✓ Mixed sequence succeeded"); + info!(" r1: {:02x}", r1); + info!(" r2: {:02x}", r2); + info!(" r3: {:02x}", r3); + } + Err(e) => warn!("✗ Mixed sequence failed: {:?}", e), + } + + info!("Expected sequence:"); + info!(" START, ADDR+W, [4 bytes merged], RESTART,"); + info!(" ADDR+R, [4 bytes merged], NACK, RESTART,"); + info!(" ADDR+W, [1 byte], RESTART,"); + info!(" ADDR+R, [1 byte], NACK, STOP"); +} + +fn test_single_operations(i2c: &mut I2c<'static, Blocking, Master>, addr: u8) { + // Test single write + let write_data = [0xFF]; + let mut ops = [Operation::Write(&write_data)]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => info!("✓ Single write succeeded"), + Err(e) => warn!("✗ Single write failed: {:?}", e), + } + + // Test single read + let mut read_buf = [0u8; 1]; + let mut ops = [Operation::Read(&mut read_buf)]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => info!("✓ Single read succeeded, data: 0x{:02x}", read_buf[0]), + Err(e) => warn!("✗ Single read failed: {:?}", e), + } +} -- cgit From 74b5f14ede9a6b4349932bff41dac077afc47fa2 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Wed, 12 Nov 2025 19:31:21 +0100 Subject: stm32/i2c_v2: Refactor transaction implementation --- embassy-stm32/src/i2c/v2.rs | 87 +++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 54 deletions(-) diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index 061f4ff3a..3c43887c0 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -98,6 +98,27 @@ pub(crate) unsafe fn on_interrupt() { } impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { + #[inline] + fn to_reload(reload: bool) -> i2c::vals::Reload { + if reload { + i2c::vals::Reload::NOT_COMPLETED + } else { + i2c::vals::Reload::COMPLETED + } + } + + /// Calculate total bytes in a group of operations + #[inline] + fn total_operation_bytes(operations: &[Operation<'_>]) -> usize { + operations + .iter() + .map(|op| match op { + Operation::Write(buf) => buf.len(), + Operation::Read(buf) => buf.len(), + }) + .sum() + } + pub(crate) fn init(&mut self, config: Config) { self.info.regs.cr1().modify(|reg| { reg.set_pe(false); @@ -147,12 +168,6 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { // `buffer`. The START bit can be set even if the bus // is BUSY or I2C is in slave mode. - let reload = if reload { - i2c::vals::Reload::NOT_COMPLETED - } else { - i2c::vals::Reload::COMPLETED - }; - info.regs.cr2().modify(|w| { w.set_sadd(address.addr() << 1); w.set_add10(address.add_mode()); @@ -160,7 +175,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { w.set_nbytes(length as u8); w.set_start(true); w.set_autoend(stop.autoend()); - w.set_reload(reload); + w.set_reload(Self::to_reload(reload)); }); Ok(()) @@ -191,12 +206,6 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { } } - let reload = if reload { - i2c::vals::Reload::NOT_COMPLETED - } else { - i2c::vals::Reload::COMPLETED - }; - // Set START and prepare to send `bytes`. The // START bit can be set even if the bus is BUSY or // I2C is in slave mode. @@ -207,7 +216,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { w.set_nbytes(length as u8); w.set_start(true); w.set_autoend(stop.autoend()); - w.set_reload(reload); + w.set_reload(Self::to_reload(reload)); }); Ok(()) @@ -220,15 +229,9 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { timeout.check()?; } - let will_reload = if will_reload { - i2c::vals::Reload::NOT_COMPLETED - } else { - i2c::vals::Reload::COMPLETED - }; - info.regs.cr2().modify(|w| { w.set_nbytes(length as u8); - w.set_reload(will_reload); + w.set_reload(Self::to_reload(will_reload)); w.set_autoend(stop.autoend()); }); @@ -400,7 +403,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { address, read.len().min(255), Stop::Automatic, - last_chunk_idx != 0, + last_chunk_idx != 0, // reload restart, timeout, )?; @@ -569,13 +572,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { timeout: Timeout, ) -> Result<(), Error> { // Calculate total bytes across all operations in this group - let total_bytes: usize = operations - .iter() - .map(|op| match op { - Operation::Write(buf) => buf.len(), - _ => 0, - }) - .sum(); + let total_bytes = Self::total_operation_bytes(operations); if total_bytes == 0 { // Handle empty write group - just send address @@ -641,13 +638,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { timeout: Timeout, ) -> Result<(), Error> { // Calculate total bytes across all operations in this group - let total_bytes: usize = operations - .iter() - .map(|op| match op { - Operation::Read(buf) => buf.len(), - _ => 0, - }) - .sum(); + let total_bytes = Self::total_operation_bytes(operations); if total_bytes == 0 { // Handle empty read group @@ -657,7 +648,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { address, 0, if is_last_group { Stop::Automatic } else { Stop::Software }, - false, + false, // reload !is_first_group, timeout, )?; @@ -1000,7 +991,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { address, total_len.min(255), Stop::Automatic, - total_len > 255, + total_len > 255, // reload restart, timeout, )?; @@ -1183,13 +1174,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { timeout: Timeout, ) -> Result<(), Error> { // Calculate total bytes across all operations in this group - let total_bytes: usize = operations - .iter() - .map(|op| match op { - Operation::Write(buf) => buf.len(), - _ => 0, - }) - .sum(); + let total_bytes = Self::total_operation_bytes(operations); if total_bytes == 0 { // Handle empty write group using blocking call @@ -1249,13 +1234,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { timeout: Timeout, ) -> Result<(), Error> { // Calculate total bytes across all operations in this group - let total_bytes: usize = operations - .iter() - .map(|op| match op { - Operation::Read(buf) => buf.len(), - _ => 0, - }) - .sum(); + let total_bytes = Self::total_operation_bytes(operations); if total_bytes == 0 { // Handle empty read group using blocking call @@ -1265,7 +1244,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { address, 0, if is_last_group { Stop::Automatic } else { Stop::Software }, - false, + false, // reload !is_first_group, timeout, )?; @@ -1313,7 +1292,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { } else { Stop::Software }, - last_chunk_idx != 0 || !is_last_read, + last_chunk_idx != 0 || !is_last_read, // reload restart, timeout, )?; -- cgit From 1c94d27a147035dfe40d33bae85be0308394dc53 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Wed, 12 Nov 2025 20:13:48 +0100 Subject: stm32: Add entry about i2c v2 transaction implementation --- embassy-stm32/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index 3431848d3..666ee1714 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -43,7 +43,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - fix: sdmmc: don't wait for DBCKEND flag on sdmmc_v2 devices as it never fires (Fixes #4723) - fix: usart: fix race condition in ringbuffered usart - feat: Add backup_sram::init() for H5 devices to access BKPSRAM -- feat: Add I2C MultiMaster (Slave) support for I2C v1 +- feat: stm32/i2c v1: Add I2C MultiMaster (Slave) support +- feat: stm32/i2c v2: Add transaction() and blocking_transaction() methods with contract-compliant operation merging - feat: stm32/fdcan: add ability to control automatic recovery from bus off ([#4821](https://github.com/embassy-rs/embassy/pull/4821)) - low-power: update rtc api to allow reconfig - adc: consolidate ringbuffer -- cgit From 973fdb6b222a24e881c722b33767aab76ab92896 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Wed, 12 Nov 2025 21:02:10 +0100 Subject: stm32: Add i2c v2 transaction test --- examples/stm32f0/Cargo.toml | 1 - examples/stm32f0/src/bin/i2c_transaction_test.rs | 219 ---------------------- tests/stm32/Cargo.toml | 7 + tests/stm32/src/bin/i2c_v2.rs | 220 +++++++++++++++++++++++ tests/stm32/src/common.rs | 25 +++ 5 files changed, 252 insertions(+), 220 deletions(-) delete mode 100644 examples/stm32f0/src/bin/i2c_transaction_test.rs create mode 100644 tests/stm32/src/bin/i2c_v2.rs diff --git a/examples/stm32f0/Cargo.toml b/examples/stm32f0/Cargo.toml index 177dd0ac2..a78873d21 100644 --- a/examples/stm32f0/Cargo.toml +++ b/examples/stm32f0/Cargo.toml @@ -16,7 +16,6 @@ panic-probe = { version = "1.0.0", features = ["print-defmt"] } embassy-sync = { version = "0.7.2", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.9.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } embassy-time = { version = "0.5.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embedded-hal-1 = { package = "embedded-hal", version = "1.0" } static_cell = "2" portable-atomic = { version = "1.5", features = ["unsafe-assume-single-core"] } diff --git a/examples/stm32f0/src/bin/i2c_transaction_test.rs b/examples/stm32f0/src/bin/i2c_transaction_test.rs deleted file mode 100644 index 0ecc3e8b1..000000000 --- a/examples/stm32f0/src/bin/i2c_transaction_test.rs +++ /dev/null @@ -1,219 +0,0 @@ -#![no_std] -#![no_main] - -use defmt::*; -use embassy_executor::Spawner; -use embassy_stm32::i2c::{Config, I2c, Master}; -use embassy_stm32::mode::Blocking; -use embassy_stm32::time::Hertz; -use embassy_stm32::{bind_interrupts, i2c, peripherals}; -use embassy_time::Timer; -use embedded_hal_1::i2c::Operation; -use {defmt_rtt as _, panic_probe as _}; - -bind_interrupts!(struct Irqs { - I2C1 => i2c::EventInterruptHandler, i2c::ErrorInterruptHandler; -}); - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - // For STM32F072RB on NUCLEO board - let p = embassy_stm32::init(Default::default()); - - info!("I2C Transaction Test Starting..."); - - // Initialize I2C1: PB6=SCL, PB7=SDA - let mut config = Config::default(); - config.frequency = Hertz(100_000); - let mut i2c = I2c::new_blocking( - p.I2C1, - p.PB8, // SCL - p.PB9, // SDA - config, - ); - - let slave_addr = 0x50u8; - - // Wait for devices to initialize - Timer::after_millis(100).await; - - info!("=== Test 1: Consecutive Writes (Should Merge) ==="); - test_consecutive_writes(&mut i2c, slave_addr); - Timer::after_millis(500).await; - - info!("=== Test 2: Consecutive Reads (Should Merge) ==="); - test_consecutive_reads(&mut i2c, slave_addr); - Timer::after_millis(500).await; - - info!("=== Test 3: Write then Read (RESTART) ==="); - test_write_then_read(&mut i2c, slave_addr); - Timer::after_millis(500).await; - - info!("=== Test 4: Read then Write (RESTART) ==="); - test_read_then_write(&mut i2c, slave_addr); - Timer::after_millis(500).await; - - info!("=== Test 5: Complex Mixed Sequence ==="); - test_mixed_sequence(&mut i2c, slave_addr); - Timer::after_millis(500).await; - - info!("=== Test 6: Single Operations ==="); - test_single_operations(&mut i2c, slave_addr); - - info!("All tests complete!"); - - loop { - Timer::after_secs(1).await; - } -} - -fn test_consecutive_writes(i2c: &mut I2c<'static, Blocking, Master>, addr: u8) { - // Expected on bus: START, ADDR+W, data1, data2, data3, STOP - // NO intermediate RESTART/STOP between writes - let data1 = [0x10, 0x11, 0x12]; - let data2 = [0x20, 0x21]; - let data3 = [0x30, 0x31, 0x32, 0x33]; - - let mut ops = [ - Operation::Write(&data1), - Operation::Write(&data2), - Operation::Write(&data3), - ]; - - match i2c.blocking_transaction(addr, &mut ops) { - Ok(_) => info!("✓ Consecutive writes succeeded"), - Err(e) => warn!("✗ Consecutive writes failed: {:?}", e), - } - - info!("Expected: START, ADDR+W, [9 bytes], STOP"); - info!("Check Analog Discovery: No RESTART between writes"); -} - -fn test_consecutive_reads(i2c: &mut I2c<'static, Blocking, Master>, addr: u8) { - // Expected on bus: START, ADDR+R, data1, data2, data3, NACK, STOP - // NO intermediate RESTART/STOP between reads - let mut buf1 = [0u8; 4]; - let mut buf2 = [0u8; 3]; - let mut buf3 = [0u8; 2]; - - let mut ops = [ - Operation::Read(&mut buf1), - Operation::Read(&mut buf2), - Operation::Read(&mut buf3), - ]; - - match i2c.blocking_transaction(addr, &mut ops) { - Ok(_) => { - info!("✓ Consecutive reads succeeded"); - info!(" buf1: {:02x}", buf1); - info!(" buf2: {:02x}", buf2); - info!(" buf3: {:02x}", buf3); - } - Err(e) => warn!("✗ Consecutive reads failed: {:?}", e), - } - - info!("Expected: START, ADDR+R, [9 bytes], NACK on last, STOP"); - info!("Check Analog Discovery: No RESTART between reads"); -} - -fn test_write_then_read(i2c: &mut I2c<'static, Blocking, Master>, addr: u8) { - // Expected: START, ADDR+W, data, RESTART, ADDR+R, data, NACK, STOP - let write_data = [0xAA, 0xBB]; - let mut read_buf = [0u8; 4]; - - let mut ops = [ - Operation::Write(&write_data), - Operation::Read(&mut read_buf), - ]; - - match i2c.blocking_transaction(addr, &mut ops) { - Ok(_) => { - info!("✓ Write-then-read succeeded"); - info!(" Written: {:02x}", write_data); - info!(" Read: {:02x}", read_buf); - } - Err(e) => warn!("✗ Write-then-read failed: {:?}", e), - } - - info!("Expected: START, ADDR+W, [2 bytes], RESTART, ADDR+R, [4 bytes], NACK, STOP"); - info!("Check Analog Discovery: RESTART between write and read"); -} - -fn test_read_then_write(i2c: &mut I2c<'static, Blocking, Master>, addr: u8) { - // Expected: START, ADDR+R, data, NACK, RESTART, ADDR+W, data, STOP - let mut read_buf = [0u8; 3]; - let write_data = [0xCC, 0xDD, 0xEE]; - - let mut ops = [ - Operation::Read(&mut read_buf), - Operation::Write(&write_data), - ]; - - match i2c.blocking_transaction(addr, &mut ops) { - Ok(_) => { - info!("✓ Read-then-write succeeded"); - info!(" Read: {:02x}", read_buf); - info!(" Written: {:02x}", write_data); - } - Err(e) => warn!("✗ Read-then-write failed: {:?}", e), - } - - info!("Expected: START, ADDR+R, [3 bytes], NACK, RESTART, ADDR+W, [3 bytes], STOP"); - info!("Check Analog Discovery: RESTART between read and write"); -} - -fn test_mixed_sequence(i2c: &mut I2c<'static, Blocking, Master>, addr: u8) { - // Complex: W, W, R, R, W, R - // Groups: [W,W] RESTART [R,R] RESTART [W] RESTART [R] - let w1 = [0x01, 0x02]; - let w2 = [0x03, 0x04]; - let mut r1 = [0u8; 2]; - let mut r2 = [0u8; 2]; - let w3 = [0x05]; - let mut r3 = [0u8; 1]; - - let mut ops = [ - Operation::Write(&w1), - Operation::Write(&w2), - Operation::Read(&mut r1), - Operation::Read(&mut r2), - Operation::Write(&w3), - Operation::Read(&mut r3), - ]; - - match i2c.blocking_transaction(addr, &mut ops) { - Ok(_) => { - info!("✓ Mixed sequence succeeded"); - info!(" r1: {:02x}", r1); - info!(" r2: {:02x}", r2); - info!(" r3: {:02x}", r3); - } - Err(e) => warn!("✗ Mixed sequence failed: {:?}", e), - } - - info!("Expected sequence:"); - info!(" START, ADDR+W, [4 bytes merged], RESTART,"); - info!(" ADDR+R, [4 bytes merged], NACK, RESTART,"); - info!(" ADDR+W, [1 byte], RESTART,"); - info!(" ADDR+R, [1 byte], NACK, STOP"); -} - -fn test_single_operations(i2c: &mut I2c<'static, Blocking, Master>, addr: u8) { - // Test single write - let write_data = [0xFF]; - let mut ops = [Operation::Write(&write_data)]; - - match i2c.blocking_transaction(addr, &mut ops) { - Ok(_) => info!("✓ Single write succeeded"), - Err(e) => warn!("✗ Single write failed: {:?}", e), - } - - // Test single read - let mut read_buf = [0u8; 1]; - let mut ops = [Operation::Read(&mut read_buf)]; - - match i2c.blocking_transaction(addr, &mut ops) { - Ok(_) => info!("✓ Single read succeeded, data: 0x{:02x}", read_buf[0]), - Err(e) => warn!("✗ Single read failed: {:?}", e), - } -} diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml index b92b47be2..fa757e276 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml @@ -35,6 +35,7 @@ stm32wb55rg = ["embassy-stm32/stm32wb55rg", "chrono", "not-gpdma", "ble", "mac" stm32wba52cg = ["embassy-stm32/stm32wba52cg", "spi-v345", "chrono", "rng", "hash"] stm32wl55jc = ["embassy-stm32/stm32wl55jc-cm4", "not-gpdma", "rng", "chrono"] stm32f091rc = ["embassy-stm32/stm32f091rc", "cm0", "not-gpdma", "chrono"] +stm32f072rb = ["embassy-stm32/stm32f072rb", "cm0", "not-gpdma", "chrono"] stm32h503rb = ["embassy-stm32/stm32h503rb", "spi-v345", "rng", "stop"] stm32h7s3l8 = ["embassy-stm32/stm32h7s3l8", "spi-v345", "rng", "cordic", "hash-v34"] # TODO: fdcan crashes, cryp dma hangs. stm32u083rc = ["embassy-stm32/stm32u083rc", "cm0", "rng", "chrono"] @@ -159,6 +160,11 @@ name = "hash" path = "src/bin/hash.rs" required-features = [ "hash",] +[[bin]] +name = "i2c_v2" +path = "src/bin/i2c_v2.rs" +required-features = [ "stm32f072rb",] + [[bin]] name = "rng" path = "src/bin/rng.rs" @@ -285,6 +291,7 @@ build = [ { target = "thumbv7em-none-eabi", features = ["stm32wl55jc"], artifact-dir = "out/tests/stm32wl55jc" }, { target = "thumbv7em-none-eabi", features = ["stm32h7s3l8"], artifact-dir = "out/tests/stm32h7s3l8" }, { target = "thumbv6m-none-eabi", features = ["stm32f091rc"], artifact-dir = "out/tests/stm32f091rc" }, + { target = "thumbv6m-none-eabi", features = ["stm32f072rb"], artifact-dir = "out/tests/stm32f072rb" }, { target = "thumbv8m.main-none-eabihf", features = ["stm32h503rb"], artifact-dir = "out/tests/stm32h503rb" }, { target = "thumbv6m-none-eabi", features = ["stm32u083rc"], artifact-dir = "out/tests/stm32u083rc" } ] diff --git a/tests/stm32/src/bin/i2c_v2.rs b/tests/stm32/src/bin/i2c_v2.rs new file mode 100644 index 000000000..9a23e28e1 --- /dev/null +++ b/tests/stm32/src/bin/i2c_v2.rs @@ -0,0 +1,220 @@ +#![no_std] +#![no_main] +// required-features: stm32f072rb +// +// Hardware Setup for NUCLEO-F072RB: +// - I2C1 pins: PB8 (SCL), PB9 (SDA) on CN5 connector +// - Connect to I2C slave device (e.g., Digilent Analog Discovery I2C slave, or EEPROM) +// - Default slave address: 0x50 +// - Pull-up resistors: 4.7kΩ on both SCL and SDA +// - CN5 Pin 5 (PB8/SCL) and CN5 Pin 9 (PB9/SDA) +// +// Analog Discovery Setup: +// - Configure as I2C Slave at address 0x50 +// - DIO 0: SCL +// - DIO 1: SDA +// - Enable pull-ups or use external 4.7kΩ pull-up resistors + +#[path = "../common.rs"] +mod common; + +use common::*; +use embassy_executor::Spawner; +use embassy_stm32::i2c::{Config, I2c, Master}; +use embassy_stm32::mode::Blocking; +use embassy_stm32::time::Hertz; +use embassy_time::block_for; +use embedded_hal_1::i2c::Operation; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = init(); + info!("I2C v2 Transaction Test Starting..."); + + let mut i2c_peri = peri!(p, I2C); + let mut scl = peri!(p, I2C_SCL); + let mut sda = peri!(p, I2C_SDA); + + let mut config = Config::default(); + config.frequency = Hertz(100_000); + + let mut i2c = I2c::new_blocking( + i2c_peri.reborrow(), + scl.reborrow(), + sda.reborrow(), + config, + ); + + // I2C slave address for Analog Discovery or test EEPROM + let slave_addr = 0x50u8; + + // Wait for slave device to be ready + block_for(embassy_time::Duration::from_millis(100)); + + info!("=== Test 1: Consecutive Writes (Should Merge) ==="); + test_consecutive_writes(&mut i2c, slave_addr); + + info!("=== Test 2: Consecutive Reads (Should Merge) ==="); + test_consecutive_reads(&mut i2c, slave_addr); + + info!("=== Test 3: Write then Read (RESTART) ==="); + test_write_then_read(&mut i2c, slave_addr); + + info!("=== Test 4: Read then Write (RESTART) ==="); + test_read_then_write(&mut i2c, slave_addr); + + info!("=== Test 5: Complex Mixed Sequence ==="); + test_mixed_sequence(&mut i2c, slave_addr); + + info!("=== Test 6: Single Operations ==="); + test_single_operations(&mut i2c, slave_addr); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +fn test_consecutive_writes(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + // Expected on bus: START, ADDR+W, data1, data2, data3, STOP + // NO intermediate RESTART/STOP between writes - they should be merged + let data1 = [0x10, 0x11, 0x12]; + let data2 = [0x20, 0x21]; + let data3 = [0x30, 0x31, 0x32, 0x33]; + + let mut ops = [ + Operation::Write(&data1), + Operation::Write(&data2), + Operation::Write(&data3), + ]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => info!("✓ Consecutive writes succeeded (merged 9 bytes)"), + Err(e) => { + error!("✗ Consecutive writes failed: {:?}", e); + defmt::panic!("Test failed: consecutive writes"); + } + } +} + +fn test_consecutive_reads(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + // Expected on bus: START, ADDR+R, data1, data2, data3, NACK, STOP + // NO intermediate RESTART/STOP between reads - they should be merged + let mut buf1 = [0u8; 4]; + let mut buf2 = [0u8; 3]; + let mut buf3 = [0u8; 2]; + + let mut ops = [ + Operation::Read(&mut buf1), + Operation::Read(&mut buf2), + Operation::Read(&mut buf3), + ]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => { + info!("✓ Consecutive reads succeeded (merged 9 bytes)"); + info!(" buf1: {:02x}", buf1); + info!(" buf2: {:02x}", buf2); + info!(" buf3: {:02x}", buf3); + } + Err(e) => { + error!("✗ Consecutive reads failed: {:?}", e); + defmt::panic!("Test failed: consecutive reads"); + } + } +} + +fn test_write_then_read(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + // Expected: START, ADDR+W, data, RESTART, ADDR+R, data, NACK, STOP + let write_data = [0xAA, 0xBB]; + let mut read_buf = [0u8; 4]; + + let mut ops = [Operation::Write(&write_data), Operation::Read(&mut read_buf)]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => { + info!("✓ Write-then-read succeeded with RESTART"); + info!(" Written: {:02x}", write_data); + info!(" Read: {:02x}", read_buf); + } + Err(e) => { + error!("✗ Write-then-read failed: {:?}", e); + defmt::panic!("Test failed: write-then-read"); + } + } +} + +fn test_read_then_write(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + // Expected: START, ADDR+R, data, NACK, RESTART, ADDR+W, data, STOP + let mut read_buf = [0u8; 3]; + let write_data = [0xCC, 0xDD, 0xEE]; + + let mut ops = [Operation::Read(&mut read_buf), Operation::Write(&write_data)]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => { + info!("✓ Read-then-write succeeded with RESTART"); + info!(" Read: {:02x}", read_buf); + info!(" Written: {:02x}", write_data); + } + Err(e) => { + error!("✗ Read-then-write failed: {:?}", e); + defmt::panic!("Test failed: read-then-write"); + } + } +} + +fn test_mixed_sequence(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + // Complex: W, W, R, R, W, R + // Groups: [W,W] RESTART [R,R] RESTART [W] RESTART [R] + let w1 = [0x01, 0x02]; + let w2 = [0x03, 0x04]; + let mut r1 = [0u8; 2]; + let mut r2 = [0u8; 2]; + let w3 = [0x05]; + let mut r3 = [0u8; 1]; + + let mut ops = [ + Operation::Write(&w1), + Operation::Write(&w2), + Operation::Read(&mut r1), + Operation::Read(&mut r2), + Operation::Write(&w3), + Operation::Read(&mut r3), + ]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => { + info!("✓ Mixed sequence succeeded"); + info!(" Groups: [W4] RESTART [R4] RESTART [W1] RESTART [R1]"); + } + Err(e) => { + error!("✗ Mixed sequence failed: {:?}", e); + defmt::panic!("Test failed: mixed sequence"); + } + } +} + +fn test_single_operations(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + // Test single write + let write_data = [0xFF]; + let mut ops = [Operation::Write(&write_data)]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => info!("✓ Single write succeeded"), + Err(e) => { + error!("✗ Single write failed: {:?}", e); + defmt::panic!("Test failed: single write"); + } + } + + // Test single read + let mut read_buf = [0u8; 1]; + let mut ops = [Operation::Read(&mut read_buf)]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => info!("✓ Single read succeeded, data: 0x{:02x}", read_buf[0]), + Err(e) => { + error!("✗ Single read failed: {:?}", e); + defmt::panic!("Test failed: single read"); + } + } +} diff --git a/tests/stm32/src/common.rs b/tests/stm32/src/common.rs index 096cce947..07b667ade 100644 --- a/tests/stm32/src/common.rs +++ b/tests/stm32/src/common.rs @@ -60,6 +60,8 @@ teleprobe_meta::target!(b"nucleo-stm32wl55jc"); teleprobe_meta::target!(b"nucleo-stm32wba52cg"); #[cfg(feature = "stm32f091rc")] teleprobe_meta::target!(b"nucleo-stm32f091rc"); +#[cfg(feature = "stm32f072rb")] +teleprobe_meta::target!(b"nucleo-stm32f072rb"); #[cfg(feature = "stm32h503rb")] teleprobe_meta::target!(b"nucleo-stm32h503rb"); #[cfg(feature = "stm32h7s3l8")] @@ -103,6 +105,14 @@ define_peris!( SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH3, SPI_RX_DMA = DMA1_CH2, @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler;}, ); +#[cfg(feature = "stm32f072rb")] +define_peris!( + UART = USART1, UART_TX = PA9, UART_RX = PA10, UART_TX_DMA = DMA1_CH4, UART_RX_DMA = DMA1_CH5, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH3, SPI_RX_DMA = DMA1_CH2, + I2C = I2C1, I2C_SCL = PB8, I2C_SDA = PB9, + @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler;}, + @irq I2C = {I2C1 => embassy_stm32::i2c::EventInterruptHandler, embassy_stm32::i2c::ErrorInterruptHandler;}, +); #[cfg(any(feature = "stm32f100rd", feature = "stm32f103c8", feature = "stm32f107vc"))] define_peris!( UART = USART1, UART_TX = PA9, UART_RX = PA10, UART_TX_DMA = DMA1_CH4, UART_RX_DMA = DMA1_CH5, @@ -325,6 +335,21 @@ pub fn config() -> Config { config.rcc.ahb_pre = AHBPrescaler::DIV1; config.rcc.apb1_pre = APBPrescaler::DIV1; } + #[cfg(feature = "stm32f072rb")] + { + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll = Some(Pll { + src: PllSource::HSE, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL6, + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV1; + } #[cfg(feature = "stm32f103c8")] { config.rcc.hse = Some(Hse { -- cgit From 9e2a4161b32154f19963a222b3eb8f956b60d820 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Wed, 12 Nov 2025 21:55:20 +0100 Subject: stm32/i2c: Fix v2 async transaction implementation --- embassy-stm32/src/i2c/v2.rs | 261 +++++++++++++++++++++++++----------------- tests/stm32/build.rs | 1 + tests/stm32/src/bin/i2c_v2.rs | 238 +++++++++++++++++++++++++++++++++----- tests/stm32/src/common.rs | 2 +- 4 files changed, 363 insertions(+), 139 deletions(-) diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index 3c43887c0..c9656d2c2 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -225,7 +225,14 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { fn reload(info: &'static Info, length: usize, will_reload: bool, stop: Stop, timeout: Timeout) -> Result<(), Error> { assert!(length < 256 && length > 0); - while !info.regs.isr().read().tcr() { + // Wait for either TCR (Transfer Complete Reload) or TC (Transfer Complete) + // TCR occurs when RELOAD=1, TC occurs when RELOAD=0 + // Both indicate the peripheral is ready for the next transfer + loop { + let isr = info.regs.isr().read(); + if isr.tcr() || isr.tc() { + break; + } timeout.check()?; } @@ -885,12 +892,12 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { address, total_len.min(255), Stop::Software, - (total_len > 255) || !last_slice, + total_len > 255, restart, timeout, )?; } else { - Self::reload(self.info, total_len.min(255), (total_len > 255) || !last_slice, Stop::Software, timeout)?; + Self::reload(self.info, total_len.min(255), total_len > 255, Stop::Software, timeout)?; self.info.regs.cr1().modify(|w| w.set_tcie(true)); } } else if !(isr.tcr() || isr.tc()) { @@ -899,9 +906,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { } else if remaining_len == 0 { return Poll::Ready(Ok(())); } else { - let last_piece = (remaining_len <= 255) && last_slice; - - if let Err(e) = Self::reload(self.info, remaining_len.min(255), !last_piece, Stop::Software, timeout) { + if let Err(e) = Self::reload(self.info, remaining_len.min(255), remaining_len > 255, Stop::Software, timeout) { return Poll::Ready(Err(e)); } self.info.regs.cr1().modify(|w| w.set_tcie(true)); @@ -913,10 +918,9 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { .await?; dma_transfer.await; - if last_slice { - // This should be done already - self.wait_tc(timeout)?; - } + + // Always wait for TC after DMA completes - needed for consecutive buffers + self.wait_tc(timeout)?; if last_slice & send_stop { self.master_stop(); @@ -1173,56 +1177,9 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { is_last_group: bool, timeout: Timeout, ) -> Result<(), Error> { - // Calculate total bytes across all operations in this group - let total_bytes = Self::total_operation_bytes(operations); - - if total_bytes == 0 { - // Handle empty write group using blocking call - if is_first_group { - Self::master_write(self.info, address, 0, Stop::Software, false, !is_first_group, timeout)?; - } - if is_last_group { - self.master_stop(); - } - return Ok(()); - } - - let send_stop = is_last_group; - - // Use DMA for each operation in the group - let mut first_in_group = true; - let mut remaining_operations = operations.len(); - - for operation in operations { - if let Operation::Write(buffer) = operation { - remaining_operations -= 1; - let is_last_in_group = remaining_operations == 0; - - if buffer.is_empty() { - // Skip empty buffers - continue; - } - - let fut = self.write_dma_internal( - address, - buffer, - first_in_group && is_first_group, // first_slice - is_last_in_group && is_last_group, // last_slice - send_stop && is_last_in_group, // send_stop - !is_first_group && first_in_group, // restart - timeout, - ); - timeout.with(fut).await?; - first_in_group = false; - } - } - - // If not last group, wait for TC to enable RESTART - if !is_last_group { - self.wait_tc(timeout)?; - } - - Ok(()) + // For now, use blocking implementation for write groups + // This avoids complexity of handling multiple non-contiguous buffers with DMA + self.execute_write_group(address, operations, is_first_group, is_last_group, timeout) } async fn execute_read_group_async( @@ -1255,10 +1212,10 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { return Ok(()); } - // For read operations, we need to handle them differently since read_dma_internal - // expects a single buffer. We'll iterate through operations and use DMA for each. - let mut total_remaining = total_bytes; + // Use DMA for read operations - need to handle multiple buffers let restart = !is_first_group; + let mut total_remaining = total_bytes; + let mut is_first_in_group = true; for operation in operations { if let Operation::Read(buffer) = operation { @@ -1267,57 +1224,46 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { continue; } - let is_first_read = total_remaining == total_bytes; let buf_len = buffer.len(); total_remaining -= buf_len; - let is_last_read = total_remaining == 0; - - if is_first_read { - // First read in the group - let completed_chunks = buf_len / 255; - let total_chunks = if completed_chunks * 255 == buf_len { - completed_chunks + let is_last_in_group = total_remaining == 0; + + // Perform DMA read + if is_first_in_group { + // First buffer: use read_dma_internal which handles restart properly + // Only use Automatic stop if this is the last buffer in the last group + let stop_mode = if is_last_group && is_last_in_group { + Stop::Automatic } else { - completed_chunks + 1 + Stop::Software }; - let last_chunk_idx = total_chunks.saturating_sub(1); - // Use master_read to initiate, then DMA for data - Self::master_read( - self.info, + // We need a custom DMA read that respects our stop mode + self.read_dma_group_internal( address, - buf_len.min(255), - if is_last_group && is_last_read { - Stop::Automatic - } else { - Stop::Software - }, - last_chunk_idx != 0 || !is_last_read, // reload + buffer, restart, + stop_mode, timeout, - )?; - } - - // Use the existing read_dma_internal, but we need to handle the reload logic ourselves - // For simplicity with consecutive reads, fall back to blocking for now - // This maintains correctness while keeping complexity manageable - for (chunk_idx, chunk) in buffer.chunks_mut(255).enumerate() { - let chunk_len = chunk.len(); - let is_very_last = total_remaining == 0 && chunk_len == chunk.len(); - - if !is_first_read || chunk_idx != 0 { - let stop = if is_last_group && is_very_last { - Stop::Automatic - } else { - Stop::Software - }; - Self::reload(self.info, chunk_len, !(is_last_group && is_very_last), stop, timeout)?; - } + ) + .await?; + is_first_in_group = false; + } else { + // Subsequent buffers: need to reload and continue + let stop_mode = if is_last_group && is_last_in_group { + Stop::Automatic + } else { + Stop::Software + }; - for byte in chunk { - self.wait_rxne(timeout)?; - *byte = self.info.regs.rxdr().read().rxdata(); - } + self.read_dma_group_internal( + address, + buffer, + false, // no restart for subsequent buffers in same group + stop_mode, + timeout, + ) + .await?; } } } @@ -1326,9 +1272,108 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { if is_last_group { self.wait_stop(timeout)?; } - // For non-last read groups, don't wait for TC - the peripheral may hold SCL low - // in Software AUTOEND mode until the next START is issued. Just proceed directly - // to the next group which will issue RESTART and release the clock. + + Ok(()) + } + + /// Internal DMA read helper for transaction groups + async fn read_dma_group_internal( + &mut self, + address: Address, + buffer: &mut [u8], + restart: bool, + stop_mode: Stop, + timeout: Timeout, + ) -> Result<(), Error> { + let total_len = buffer.len(); + + let dma_transfer = unsafe { + let regs = self.info.regs; + regs.cr1().modify(|w| { + w.set_rxdmaen(true); + w.set_tcie(true); + w.set_nackie(true); + w.set_errie(true); + }); + let src = regs.rxdr().as_ptr() as *mut u8; + + self.rx_dma.as_mut().unwrap().read(src, buffer, Default::default()) + }; + + let mut remaining_len = total_len; + + let on_drop = OnDrop::new(|| { + let regs = self.info.regs; + regs.cr1().modify(|w| { + w.set_rxdmaen(false); + w.set_tcie(false); + w.set_nackie(false); + w.set_errie(false); + }); + regs.icr().write(|w| { + w.set_nackcf(true); + w.set_berrcf(true); + w.set_arlocf(true); + w.set_ovrcf(true); + }); + }); + + poll_fn(|cx| { + self.state.waker.register(cx.waker()); + + let isr = self.info.regs.isr().read(); + + if isr.nackf() { + return Poll::Ready(Err(Error::Nack)); + } + if isr.arlo() { + return Poll::Ready(Err(Error::Arbitration)); + } + if isr.berr() { + return Poll::Ready(Err(Error::Bus)); + } + if isr.ovr() { + return Poll::Ready(Err(Error::Overrun)); + } + + if remaining_len == total_len { + Self::master_read( + self.info, + address, + total_len.min(255), + stop_mode, + total_len > 255, // reload + restart, + timeout, + )?; + if total_len <= 255 { + return Poll::Ready(Ok(())); + } + } else if isr.tcr() { + // Transfer Complete Reload - need to set up next chunk + let last_piece = remaining_len <= 255; + + if let Err(e) = Self::reload(self.info, remaining_len.min(255), !last_piece, stop_mode, timeout) { + return Poll::Ready(Err(e)); + } + // Return here if we are on last chunk, + // end of transfer will be awaited with the DMA below + if last_piece { + return Poll::Ready(Ok(())); + } + self.info.regs.cr1().modify(|w| w.set_tcie(true)); + } else { + // poll_fn was woken without TCR interrupt + return Poll::Pending; + } + + remaining_len = remaining_len.saturating_sub(255); + Poll::Pending + }) + .await?; + + dma_transfer.await; + drop(on_drop); Ok(()) } diff --git a/tests/stm32/build.rs b/tests/stm32/build.rs index 556d77a20..34030c206 100644 --- a/tests/stm32/build.rs +++ b/tests/stm32/build.rs @@ -15,6 +15,7 @@ fn main() -> Result<(), Box> { feature = "stm32c071rb", // 24 kb feature = "stm32l073rz", // 20 kb feature = "stm32h503rb", // 32 kb + feature = "stm32f072rb", // 16 kb - I2C v2 test with async too large for RAM // no VTOR, so interrupts can't work when running from RAM feature = "stm32f091rc", )) { diff --git a/tests/stm32/src/bin/i2c_v2.rs b/tests/stm32/src/bin/i2c_v2.rs index 9a23e28e1..087b8bbd9 100644 --- a/tests/stm32/src/bin/i2c_v2.rs +++ b/tests/stm32/src/bin/i2c_v2.rs @@ -7,7 +7,7 @@ // - Connect to I2C slave device (e.g., Digilent Analog Discovery I2C slave, or EEPROM) // - Default slave address: 0x50 // - Pull-up resistors: 4.7kΩ on both SCL and SDA -// - CN5 Pin 5 (PB8/SCL) and CN5 Pin 9 (PB9/SDA) +// - CN5 Pin 10 (PB8/SCL) and CN5 Pin 9 (PB9/SDA) // // Analog Discovery Setup: // - Configure as I2C Slave at address 0x50 @@ -21,9 +21,9 @@ mod common; use common::*; use embassy_executor::Spawner; use embassy_stm32::i2c::{Config, I2c, Master}; -use embassy_stm32::mode::Blocking; +use embassy_stm32::mode::{Async, Blocking}; use embassy_stm32::time::Hertz; -use embassy_time::block_for; +use embassy_time::Timer; use embedded_hal_1::i2c::Operation; #[embassy_executor::main] @@ -38,42 +38,80 @@ async fn main(_spawner: Spawner) { let mut config = Config::default(); config.frequency = Hertz(100_000); - let mut i2c = I2c::new_blocking( - i2c_peri.reborrow(), - scl.reborrow(), - sda.reborrow(), - config, - ); - // I2C slave address for Analog Discovery or test EEPROM let slave_addr = 0x50u8; // Wait for slave device to be ready - block_for(embassy_time::Duration::from_millis(100)); + Timer::after_millis(100).await; + + // ========== BLOCKING TESTS ========== + info!("========== BLOCKING TRANSACTION TESTS =========="); + { + let mut i2c = I2c::new_blocking( + i2c_peri.reborrow(), + scl.reborrow(), + sda.reborrow(), + config, + ); + + info!("=== Test 1: Consecutive Writes (Should Merge) ==="); + test_consecutive_writes_blocking(&mut i2c, slave_addr); + + info!("=== Test 2: Consecutive Reads (Should Merge) ==="); + test_consecutive_reads_blocking(&mut i2c, slave_addr); + + info!("=== Test 3: Write then Read (RESTART) ==="); + test_write_then_read_blocking(&mut i2c, slave_addr); + + info!("=== Test 4: Read then Write (RESTART) ==="); + test_read_then_write_blocking(&mut i2c, slave_addr); + + info!("=== Test 5: Complex Mixed Sequence ==="); + test_mixed_sequence_blocking(&mut i2c, slave_addr); + + info!("=== Test 6: Single Operations ==="); + test_single_operations_blocking(&mut i2c, slave_addr); + + info!("Blocking tests OK"); + } + + Timer::after_millis(100).await; + + // ========== ASYNC TESTS ========== + info!("========== ASYNC TRANSACTION TESTS (DMA) =========="); + { + let tx_dma = peri!(p, I2C_TX_DMA); + let rx_dma = peri!(p, I2C_RX_DMA); + let irq = irqs!(I2C); - info!("=== Test 1: Consecutive Writes (Should Merge) ==="); - test_consecutive_writes(&mut i2c, slave_addr); + let mut i2c = I2c::new(i2c_peri, scl, sda, irq, tx_dma, rx_dma, config); - info!("=== Test 2: Consecutive Reads (Should Merge) ==="); - test_consecutive_reads(&mut i2c, slave_addr); + info!("=== Test 1: Consecutive Writes (Should Merge) ==="); + test_consecutive_writes_async(&mut i2c, slave_addr).await; - info!("=== Test 3: Write then Read (RESTART) ==="); - test_write_then_read(&mut i2c, slave_addr); + info!("=== Test 2: Consecutive Reads (Should Merge) ==="); + test_consecutive_reads_async(&mut i2c, slave_addr).await; - info!("=== Test 4: Read then Write (RESTART) ==="); - test_read_then_write(&mut i2c, slave_addr); + info!("=== Test 3: Write then Read (RESTART) ==="); + test_write_then_read_async(&mut i2c, slave_addr).await; - info!("=== Test 5: Complex Mixed Sequence ==="); - test_mixed_sequence(&mut i2c, slave_addr); + info!("=== Test 4: Read then Write (RESTART) ==="); + test_read_then_write_async(&mut i2c, slave_addr).await; - info!("=== Test 6: Single Operations ==="); - test_single_operations(&mut i2c, slave_addr); + info!("=== Test 5: Complex Mixed Sequence ==="); + test_mixed_sequence_async(&mut i2c, slave_addr).await; - info!("Test OK"); + info!("=== Test 6: Single Operations ==="); + test_single_operations_async(&mut i2c, slave_addr).await; + + info!("Async tests OK"); + } + + info!("All tests OK"); cortex_m::asm::bkpt(); } -fn test_consecutive_writes(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { +fn test_consecutive_writes_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { // Expected on bus: START, ADDR+W, data1, data2, data3, STOP // NO intermediate RESTART/STOP between writes - they should be merged let data1 = [0x10, 0x11, 0x12]; @@ -95,7 +133,7 @@ fn test_consecutive_writes(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { } } -fn test_consecutive_reads(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { +fn test_consecutive_reads_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { // Expected on bus: START, ADDR+R, data1, data2, data3, NACK, STOP // NO intermediate RESTART/STOP between reads - they should be merged let mut buf1 = [0u8; 4]; @@ -122,7 +160,7 @@ fn test_consecutive_reads(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { } } -fn test_write_then_read(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { +fn test_write_then_read_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { // Expected: START, ADDR+W, data, RESTART, ADDR+R, data, NACK, STOP let write_data = [0xAA, 0xBB]; let mut read_buf = [0u8; 4]; @@ -142,7 +180,7 @@ fn test_write_then_read(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { } } -fn test_read_then_write(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { +fn test_read_then_write_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { // Expected: START, ADDR+R, data, NACK, RESTART, ADDR+W, data, STOP let mut read_buf = [0u8; 3]; let write_data = [0xCC, 0xDD, 0xEE]; @@ -162,7 +200,7 @@ fn test_read_then_write(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { } } -fn test_mixed_sequence(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { +fn test_mixed_sequence_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { // Complex: W, W, R, R, W, R // Groups: [W,W] RESTART [R,R] RESTART [W] RESTART [R] let w1 = [0x01, 0x02]; @@ -193,7 +231,7 @@ fn test_mixed_sequence(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { } } -fn test_single_operations(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { +fn test_single_operations_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { // Test single write let write_data = [0xFF]; let mut ops = [Operation::Write(&write_data)]; @@ -218,3 +256,143 @@ fn test_single_operations(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { } } } + +// ==================== ASYNC TEST FUNCTIONS ==================== + +async fn test_consecutive_writes_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let data1 = [0x10, 0x11, 0x12]; + let data2 = [0x20, 0x21]; + let data3 = [0x30, 0x31, 0x32, 0x33]; + + let mut ops = [ + Operation::Write(&data1), + Operation::Write(&data2), + Operation::Write(&data3), + ]; + + match i2c.transaction(addr, &mut ops).await { + Ok(_) => info!("✓ Consecutive writes succeeded (merged 9 bytes)"), + Err(e) => { + error!("✗ Consecutive writes failed: {:?}", e); + defmt::panic!("Test failed: consecutive writes"); + } + } +} + +async fn test_consecutive_reads_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let mut buf1 = [0u8; 4]; + let mut buf2 = [0u8; 3]; + let mut buf3 = [0u8; 2]; + + let mut ops = [ + Operation::Read(&mut buf1), + Operation::Read(&mut buf2), + Operation::Read(&mut buf3), + ]; + + match i2c.transaction(addr, &mut ops).await { + Ok(_) => { + info!("✓ Consecutive reads succeeded (merged 9 bytes)"); + info!(" buf1: {:02x}", buf1); + info!(" buf2: {:02x}", buf2); + info!(" buf3: {:02x}", buf3); + } + Err(e) => { + error!("✗ Consecutive reads failed: {:?}", e); + defmt::panic!("Test failed: consecutive reads"); + } + } +} + +async fn test_write_then_read_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let write_data = [0xAA, 0xBB]; + let mut read_buf = [0u8; 4]; + + let mut ops = [Operation::Write(&write_data), Operation::Read(&mut read_buf)]; + + match i2c.transaction(addr, &mut ops).await { + Ok(_) => { + info!("✓ Write-then-read succeeded with RESTART"); + info!(" Written: {:02x}", write_data); + info!(" Read: {:02x}", read_buf); + } + Err(e) => { + error!("✗ Write-then-read failed: {:?}", e); + defmt::panic!("Test failed: write-then-read"); + } + } +} + +async fn test_read_then_write_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let mut read_buf = [0u8; 3]; + let write_data = [0xCC, 0xDD, 0xEE]; + + let mut ops = [Operation::Read(&mut read_buf), Operation::Write(&write_data)]; + + match i2c.transaction(addr, &mut ops).await { + Ok(_) => { + info!("✓ Read-then-write succeeded with RESTART"); + info!(" Read: {:02x}", read_buf); + info!(" Written: {:02x}", write_data); + } + Err(e) => { + error!("✗ Read-then-write failed: {:?}", e); + defmt::panic!("Test failed: read-then-write"); + } + } +} + +async fn test_mixed_sequence_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let w1 = [0x01, 0x02]; + let w2 = [0x03, 0x04]; + let mut r1 = [0u8; 2]; + let mut r2 = [0u8; 2]; + let w3 = [0x05]; + let mut r3 = [0u8; 1]; + + let mut ops = [ + Operation::Write(&w1), + Operation::Write(&w2), + Operation::Read(&mut r1), + Operation::Read(&mut r2), + Operation::Write(&w3), + Operation::Read(&mut r3), + ]; + + match i2c.transaction(addr, &mut ops).await { + Ok(_) => { + info!("✓ Mixed sequence succeeded"); + info!(" Groups: [W4] RESTART [R4] RESTART [W1] RESTART [R1]"); + } + Err(e) => { + error!("✗ Mixed sequence failed: {:?}", e); + defmt::panic!("Test failed: mixed sequence"); + } + } +} + +async fn test_single_operations_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + // Test single write + let write_data = [0xFF]; + let mut ops = [Operation::Write(&write_data)]; + + match i2c.transaction(addr, &mut ops).await { + Ok(_) => info!("✓ Single write succeeded"), + Err(e) => { + error!("✗ Single write failed: {:?}", e); + defmt::panic!("Test failed: single write"); + } + } + + // Test single read + let mut read_buf = [0u8; 1]; + let mut ops = [Operation::Read(&mut read_buf)]; + + match i2c.transaction(addr, &mut ops).await { + Ok(_) => info!("✓ Single read succeeded, data: 0x{:02x}", read_buf[0]), + Err(e) => { + error!("✗ Single read failed: {:?}", e); + defmt::panic!("Test failed: single read"); + } + } +} diff --git a/tests/stm32/src/common.rs b/tests/stm32/src/common.rs index 07b667ade..de06cb267 100644 --- a/tests/stm32/src/common.rs +++ b/tests/stm32/src/common.rs @@ -109,7 +109,7 @@ define_peris!( define_peris!( UART = USART1, UART_TX = PA9, UART_RX = PA10, UART_TX_DMA = DMA1_CH4, UART_RX_DMA = DMA1_CH5, SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH3, SPI_RX_DMA = DMA1_CH2, - I2C = I2C1, I2C_SCL = PB8, I2C_SDA = PB9, + I2C = I2C1, I2C_SCL = PB8, I2C_SDA = PB9, I2C_TX_DMA = DMA1_CH6, I2C_RX_DMA = DMA1_CH7, @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler;}, @irq I2C = {I2C1 => embassy_stm32::i2c::EventInterruptHandler, embassy_stm32::i2c::ErrorInterruptHandler;}, ); -- cgit From 03050a369befb7aeed88079626de21b4055ebccb Mon Sep 17 00:00:00 2001 From: HybridChild Date: Thu, 13 Nov 2025 09:14:03 +0100 Subject: stm32/i2c: Add comprehensive v2 Master API tests and fix async issues --- embassy-stm32/src/i2c/v2.rs | 28 +- tests/stm32/Cargo.toml | 4 +- tests/stm32/src/bin/i2c_v2.rs | 398 ---------------------- tests/stm32/src/bin/i2c_v2_master.rs | 618 +++++++++++++++++++++++++++++++++++ 4 files changed, 627 insertions(+), 421 deletions(-) delete mode 100644 tests/stm32/src/bin/i2c_v2.rs create mode 100644 tests/stm32/src/bin/i2c_v2_master.rs diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index c9656d2c2..9771d7c98 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -1003,9 +1003,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { return Poll::Ready(Ok(())); } } else if isr.tcr() { - // poll_fn was woken without an interrupt present - return Poll::Pending; - } else { + // Transfer Complete Reload - need to set up next chunk let last_piece = remaining_len <= 255; if let Err(e) = Self::reload(self.info, remaining_len.min(255), !last_piece, Stop::Automatic, timeout) { @@ -1017,6 +1015,9 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { return Poll::Ready(Ok(())); } self.info.regs.cr1().modify(|w| w.set_tcie(true)); + } else { + // poll_fn was woken without TCR interrupt + return Poll::Pending; } remaining_len = remaining_len.saturating_sub(255); @@ -1052,25 +1053,10 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { pub async fn write_vectored(&mut self, address: Address, write: &[&[u8]]) -> Result<(), Error> { #[cfg(all(feature = "low-power", stm32wlex))] let _device_busy = crate::low_power::DeviceBusy::new_stop1(); - let timeout = self.timeout(); - if write.is_empty() { - return Err(Error::ZeroLengthTransfer); - } - let mut iter = write.iter(); - - let mut first = true; - let mut current = iter.next(); - while let Some(c) = current { - let next = iter.next(); - let is_last = next.is_none(); - - let fut = self.write_dma_internal(address, c, first, is_last, is_last, false, timeout); - timeout.with(fut).await?; - first = false; - current = next; - } - Ok(()) + // For now, use blocking implementation for write_vectored + // This avoids complexity of handling multiple non-contiguous buffers with DMA + self.blocking_write_vectored((address.addr() & 0xFF) as u8, write) } /// Read. diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml index fa757e276..1912a772c 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml @@ -161,8 +161,8 @@ path = "src/bin/hash.rs" required-features = [ "hash",] [[bin]] -name = "i2c_v2" -path = "src/bin/i2c_v2.rs" +name = "i2c_v2_master" +path = "src/bin/i2c_v2_master.rs" required-features = [ "stm32f072rb",] [[bin]] diff --git a/tests/stm32/src/bin/i2c_v2.rs b/tests/stm32/src/bin/i2c_v2.rs deleted file mode 100644 index 087b8bbd9..000000000 --- a/tests/stm32/src/bin/i2c_v2.rs +++ /dev/null @@ -1,398 +0,0 @@ -#![no_std] -#![no_main] -// required-features: stm32f072rb -// -// Hardware Setup for NUCLEO-F072RB: -// - I2C1 pins: PB8 (SCL), PB9 (SDA) on CN5 connector -// - Connect to I2C slave device (e.g., Digilent Analog Discovery I2C slave, or EEPROM) -// - Default slave address: 0x50 -// - Pull-up resistors: 4.7kΩ on both SCL and SDA -// - CN5 Pin 10 (PB8/SCL) and CN5 Pin 9 (PB9/SDA) -// -// Analog Discovery Setup: -// - Configure as I2C Slave at address 0x50 -// - DIO 0: SCL -// - DIO 1: SDA -// - Enable pull-ups or use external 4.7kΩ pull-up resistors - -#[path = "../common.rs"] -mod common; - -use common::*; -use embassy_executor::Spawner; -use embassy_stm32::i2c::{Config, I2c, Master}; -use embassy_stm32::mode::{Async, Blocking}; -use embassy_stm32::time::Hertz; -use embassy_time::Timer; -use embedded_hal_1::i2c::Operation; - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = init(); - info!("I2C v2 Transaction Test Starting..."); - - let mut i2c_peri = peri!(p, I2C); - let mut scl = peri!(p, I2C_SCL); - let mut sda = peri!(p, I2C_SDA); - - let mut config = Config::default(); - config.frequency = Hertz(100_000); - - // I2C slave address for Analog Discovery or test EEPROM - let slave_addr = 0x50u8; - - // Wait for slave device to be ready - Timer::after_millis(100).await; - - // ========== BLOCKING TESTS ========== - info!("========== BLOCKING TRANSACTION TESTS =========="); - { - let mut i2c = I2c::new_blocking( - i2c_peri.reborrow(), - scl.reborrow(), - sda.reborrow(), - config, - ); - - info!("=== Test 1: Consecutive Writes (Should Merge) ==="); - test_consecutive_writes_blocking(&mut i2c, slave_addr); - - info!("=== Test 2: Consecutive Reads (Should Merge) ==="); - test_consecutive_reads_blocking(&mut i2c, slave_addr); - - info!("=== Test 3: Write then Read (RESTART) ==="); - test_write_then_read_blocking(&mut i2c, slave_addr); - - info!("=== Test 4: Read then Write (RESTART) ==="); - test_read_then_write_blocking(&mut i2c, slave_addr); - - info!("=== Test 5: Complex Mixed Sequence ==="); - test_mixed_sequence_blocking(&mut i2c, slave_addr); - - info!("=== Test 6: Single Operations ==="); - test_single_operations_blocking(&mut i2c, slave_addr); - - info!("Blocking tests OK"); - } - - Timer::after_millis(100).await; - - // ========== ASYNC TESTS ========== - info!("========== ASYNC TRANSACTION TESTS (DMA) =========="); - { - let tx_dma = peri!(p, I2C_TX_DMA); - let rx_dma = peri!(p, I2C_RX_DMA); - let irq = irqs!(I2C); - - let mut i2c = I2c::new(i2c_peri, scl, sda, irq, tx_dma, rx_dma, config); - - info!("=== Test 1: Consecutive Writes (Should Merge) ==="); - test_consecutive_writes_async(&mut i2c, slave_addr).await; - - info!("=== Test 2: Consecutive Reads (Should Merge) ==="); - test_consecutive_reads_async(&mut i2c, slave_addr).await; - - info!("=== Test 3: Write then Read (RESTART) ==="); - test_write_then_read_async(&mut i2c, slave_addr).await; - - info!("=== Test 4: Read then Write (RESTART) ==="); - test_read_then_write_async(&mut i2c, slave_addr).await; - - info!("=== Test 5: Complex Mixed Sequence ==="); - test_mixed_sequence_async(&mut i2c, slave_addr).await; - - info!("=== Test 6: Single Operations ==="); - test_single_operations_async(&mut i2c, slave_addr).await; - - info!("Async tests OK"); - } - - info!("All tests OK"); - cortex_m::asm::bkpt(); -} - -fn test_consecutive_writes_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { - // Expected on bus: START, ADDR+W, data1, data2, data3, STOP - // NO intermediate RESTART/STOP between writes - they should be merged - let data1 = [0x10, 0x11, 0x12]; - let data2 = [0x20, 0x21]; - let data3 = [0x30, 0x31, 0x32, 0x33]; - - let mut ops = [ - Operation::Write(&data1), - Operation::Write(&data2), - Operation::Write(&data3), - ]; - - match i2c.blocking_transaction(addr, &mut ops) { - Ok(_) => info!("✓ Consecutive writes succeeded (merged 9 bytes)"), - Err(e) => { - error!("✗ Consecutive writes failed: {:?}", e); - defmt::panic!("Test failed: consecutive writes"); - } - } -} - -fn test_consecutive_reads_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { - // Expected on bus: START, ADDR+R, data1, data2, data3, NACK, STOP - // NO intermediate RESTART/STOP between reads - they should be merged - let mut buf1 = [0u8; 4]; - let mut buf2 = [0u8; 3]; - let mut buf3 = [0u8; 2]; - - let mut ops = [ - Operation::Read(&mut buf1), - Operation::Read(&mut buf2), - Operation::Read(&mut buf3), - ]; - - match i2c.blocking_transaction(addr, &mut ops) { - Ok(_) => { - info!("✓ Consecutive reads succeeded (merged 9 bytes)"); - info!(" buf1: {:02x}", buf1); - info!(" buf2: {:02x}", buf2); - info!(" buf3: {:02x}", buf3); - } - Err(e) => { - error!("✗ Consecutive reads failed: {:?}", e); - defmt::panic!("Test failed: consecutive reads"); - } - } -} - -fn test_write_then_read_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { - // Expected: START, ADDR+W, data, RESTART, ADDR+R, data, NACK, STOP - let write_data = [0xAA, 0xBB]; - let mut read_buf = [0u8; 4]; - - let mut ops = [Operation::Write(&write_data), Operation::Read(&mut read_buf)]; - - match i2c.blocking_transaction(addr, &mut ops) { - Ok(_) => { - info!("✓ Write-then-read succeeded with RESTART"); - info!(" Written: {:02x}", write_data); - info!(" Read: {:02x}", read_buf); - } - Err(e) => { - error!("✗ Write-then-read failed: {:?}", e); - defmt::panic!("Test failed: write-then-read"); - } - } -} - -fn test_read_then_write_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { - // Expected: START, ADDR+R, data, NACK, RESTART, ADDR+W, data, STOP - let mut read_buf = [0u8; 3]; - let write_data = [0xCC, 0xDD, 0xEE]; - - let mut ops = [Operation::Read(&mut read_buf), Operation::Write(&write_data)]; - - match i2c.blocking_transaction(addr, &mut ops) { - Ok(_) => { - info!("✓ Read-then-write succeeded with RESTART"); - info!(" Read: {:02x}", read_buf); - info!(" Written: {:02x}", write_data); - } - Err(e) => { - error!("✗ Read-then-write failed: {:?}", e); - defmt::panic!("Test failed: read-then-write"); - } - } -} - -fn test_mixed_sequence_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { - // Complex: W, W, R, R, W, R - // Groups: [W,W] RESTART [R,R] RESTART [W] RESTART [R] - let w1 = [0x01, 0x02]; - let w2 = [0x03, 0x04]; - let mut r1 = [0u8; 2]; - let mut r2 = [0u8; 2]; - let w3 = [0x05]; - let mut r3 = [0u8; 1]; - - let mut ops = [ - Operation::Write(&w1), - Operation::Write(&w2), - Operation::Read(&mut r1), - Operation::Read(&mut r2), - Operation::Write(&w3), - Operation::Read(&mut r3), - ]; - - match i2c.blocking_transaction(addr, &mut ops) { - Ok(_) => { - info!("✓ Mixed sequence succeeded"); - info!(" Groups: [W4] RESTART [R4] RESTART [W1] RESTART [R1]"); - } - Err(e) => { - error!("✗ Mixed sequence failed: {:?}", e); - defmt::panic!("Test failed: mixed sequence"); - } - } -} - -fn test_single_operations_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { - // Test single write - let write_data = [0xFF]; - let mut ops = [Operation::Write(&write_data)]; - - match i2c.blocking_transaction(addr, &mut ops) { - Ok(_) => info!("✓ Single write succeeded"), - Err(e) => { - error!("✗ Single write failed: {:?}", e); - defmt::panic!("Test failed: single write"); - } - } - - // Test single read - let mut read_buf = [0u8; 1]; - let mut ops = [Operation::Read(&mut read_buf)]; - - match i2c.blocking_transaction(addr, &mut ops) { - Ok(_) => info!("✓ Single read succeeded, data: 0x{:02x}", read_buf[0]), - Err(e) => { - error!("✗ Single read failed: {:?}", e); - defmt::panic!("Test failed: single read"); - } - } -} - -// ==================== ASYNC TEST FUNCTIONS ==================== - -async fn test_consecutive_writes_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { - let data1 = [0x10, 0x11, 0x12]; - let data2 = [0x20, 0x21]; - let data3 = [0x30, 0x31, 0x32, 0x33]; - - let mut ops = [ - Operation::Write(&data1), - Operation::Write(&data2), - Operation::Write(&data3), - ]; - - match i2c.transaction(addr, &mut ops).await { - Ok(_) => info!("✓ Consecutive writes succeeded (merged 9 bytes)"), - Err(e) => { - error!("✗ Consecutive writes failed: {:?}", e); - defmt::panic!("Test failed: consecutive writes"); - } - } -} - -async fn test_consecutive_reads_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { - let mut buf1 = [0u8; 4]; - let mut buf2 = [0u8; 3]; - let mut buf3 = [0u8; 2]; - - let mut ops = [ - Operation::Read(&mut buf1), - Operation::Read(&mut buf2), - Operation::Read(&mut buf3), - ]; - - match i2c.transaction(addr, &mut ops).await { - Ok(_) => { - info!("✓ Consecutive reads succeeded (merged 9 bytes)"); - info!(" buf1: {:02x}", buf1); - info!(" buf2: {:02x}", buf2); - info!(" buf3: {:02x}", buf3); - } - Err(e) => { - error!("✗ Consecutive reads failed: {:?}", e); - defmt::panic!("Test failed: consecutive reads"); - } - } -} - -async fn test_write_then_read_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { - let write_data = [0xAA, 0xBB]; - let mut read_buf = [0u8; 4]; - - let mut ops = [Operation::Write(&write_data), Operation::Read(&mut read_buf)]; - - match i2c.transaction(addr, &mut ops).await { - Ok(_) => { - info!("✓ Write-then-read succeeded with RESTART"); - info!(" Written: {:02x}", write_data); - info!(" Read: {:02x}", read_buf); - } - Err(e) => { - error!("✗ Write-then-read failed: {:?}", e); - defmt::panic!("Test failed: write-then-read"); - } - } -} - -async fn test_read_then_write_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { - let mut read_buf = [0u8; 3]; - let write_data = [0xCC, 0xDD, 0xEE]; - - let mut ops = [Operation::Read(&mut read_buf), Operation::Write(&write_data)]; - - match i2c.transaction(addr, &mut ops).await { - Ok(_) => { - info!("✓ Read-then-write succeeded with RESTART"); - info!(" Read: {:02x}", read_buf); - info!(" Written: {:02x}", write_data); - } - Err(e) => { - error!("✗ Read-then-write failed: {:?}", e); - defmt::panic!("Test failed: read-then-write"); - } - } -} - -async fn test_mixed_sequence_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { - let w1 = [0x01, 0x02]; - let w2 = [0x03, 0x04]; - let mut r1 = [0u8; 2]; - let mut r2 = [0u8; 2]; - let w3 = [0x05]; - let mut r3 = [0u8; 1]; - - let mut ops = [ - Operation::Write(&w1), - Operation::Write(&w2), - Operation::Read(&mut r1), - Operation::Read(&mut r2), - Operation::Write(&w3), - Operation::Read(&mut r3), - ]; - - match i2c.transaction(addr, &mut ops).await { - Ok(_) => { - info!("✓ Mixed sequence succeeded"); - info!(" Groups: [W4] RESTART [R4] RESTART [W1] RESTART [R1]"); - } - Err(e) => { - error!("✗ Mixed sequence failed: {:?}", e); - defmt::panic!("Test failed: mixed sequence"); - } - } -} - -async fn test_single_operations_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { - // Test single write - let write_data = [0xFF]; - let mut ops = [Operation::Write(&write_data)]; - - match i2c.transaction(addr, &mut ops).await { - Ok(_) => info!("✓ Single write succeeded"), - Err(e) => { - error!("✗ Single write failed: {:?}", e); - defmt::panic!("Test failed: single write"); - } - } - - // Test single read - let mut read_buf = [0u8; 1]; - let mut ops = [Operation::Read(&mut read_buf)]; - - match i2c.transaction(addr, &mut ops).await { - Ok(_) => info!("✓ Single read succeeded, data: 0x{:02x}", read_buf[0]), - Err(e) => { - error!("✗ Single read failed: {:?}", e); - defmt::panic!("Test failed: single read"); - } - } -} diff --git a/tests/stm32/src/bin/i2c_v2_master.rs b/tests/stm32/src/bin/i2c_v2_master.rs new file mode 100644 index 000000000..b841d556a --- /dev/null +++ b/tests/stm32/src/bin/i2c_v2_master.rs @@ -0,0 +1,618 @@ +#![no_std] +#![no_main] +// required-features: stm32f072rb +// +// Hardware Setup for NUCLEO-F072RB: +// - I2C1 pins: PB8 (SCL), PB9 (SDA) on CN5 connector +// - Connect to I2C slave device (e.g., Digilent Analog Discovery I2C slave, or EEPROM) +// - Default slave address: 0x50 +// - Pull-up resistors: 4.7kΩ on both SCL and SDA +// - CN5 Pin 10 (PB8/SCL) and CN5 Pin 9 (PB9/SDA) +// +// Analog Discovery - Waveforms Setup: +// - Increase buffer size: Settings -> Device Manager -> Option 4 +// - Run Protocol Analyzer +// - Configure as I2C Slave at address 0x50 +// - Connect and configure DIO pins for SCL and SDA +// - Frequency: 100kHz - [✓] Clock Stretching + +#[path = "../common.rs"] +mod common; + +use common::*; +use embassy_executor::Spawner; +use embassy_stm32::i2c::{Config, I2c, Master}; +use embassy_stm32::mode::{Async, Blocking}; +use embassy_stm32::time::Hertz; +use embassy_time::Timer; +use embedded_hal_1::i2c::Operation; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = init(); + info!("Run stm32 I2C v2 Master Tests..."); + + let mut i2c_peri = peri!(p, I2C); + let mut scl = peri!(p, I2C_SCL); + let mut sda = peri!(p, I2C_SDA); + + let mut config = Config::default(); + config.frequency = Hertz(100_000); + + // I2C slave address for Analog Discovery or test EEPROM + let slave_addr = 0x50u8; + + // Wait for slave device to be ready + Timer::after_millis(100).await; + + // ========== BLOCKING DIRECT API TESTS ========== + info!("========== BLOCKING DIRECT API TESTS =========="); + { + let mut i2c = I2c::new_blocking( + i2c_peri.reborrow(), + scl.reborrow(), + sda.reborrow(), + config, + ); + + info!("=== Test 1: Direct blocking_write ==="); + test_blocking_write(&mut i2c, slave_addr); + + info!("=== Test 2: Direct blocking_read ==="); + test_blocking_read(&mut i2c, slave_addr); + + info!("=== Test 3: Direct blocking_write_read ==="); + test_blocking_write_read(&mut i2c, slave_addr); + + info!("=== Test 4: Direct blocking_write_vectored ==="); + test_blocking_write_vectored(&mut i2c, slave_addr); + + info!("=== Test 5: Large buffer (>255 bytes) ==="); + test_blocking_large_buffer(&mut i2c, slave_addr); + + info!("Blocking direct API tests OK"); + } + + Timer::after_millis(100).await; + + // ========== BLOCKING TRANSACTION TESTS ========== + info!("========== BLOCKING TRANSACTION TESTS =========="); + { + let mut i2c = I2c::new_blocking( + i2c_peri.reborrow(), + scl.reborrow(), + sda.reborrow(), + config, + ); + + info!("=== Test 6: Consecutive Writes (Should Merge) ==="); + test_consecutive_writes_blocking(&mut i2c, slave_addr); + + info!("=== Test 7: Consecutive Reads (Should Merge) ==="); + test_consecutive_reads_blocking(&mut i2c, slave_addr); + + info!("=== Test 8: Write then Read (RESTART) ==="); + test_write_then_read_blocking(&mut i2c, slave_addr); + + info!("=== Test 9: Read then Write (RESTART) ==="); + test_read_then_write_blocking(&mut i2c, slave_addr); + + info!("=== Test 10: Complex Mixed Sequence ==="); + test_mixed_sequence_blocking(&mut i2c, slave_addr); + + info!("=== Test 11: Single Operations ==="); + test_single_operations_blocking(&mut i2c, slave_addr); + + info!("Blocking transaction tests OK"); + } + + Timer::after_millis(100).await; + + // ========== ASYNC TESTS (DMA) ========== + info!("========== ASYNC TESTS (DMA) =========="); + { + let tx_dma = peri!(p, I2C_TX_DMA); + let rx_dma = peri!(p, I2C_RX_DMA); + let irq = irqs!(I2C); + + let mut i2c = I2c::new(i2c_peri, scl, sda, irq, tx_dma, rx_dma, config); + + // Direct API tests (reusing same I2C instance) + info!("=== Direct API Test 1: write() ==="); + test_async_write(&mut i2c, slave_addr).await; + + info!("=== Direct API Test 2: read() ==="); + test_async_read(&mut i2c, slave_addr).await; + + info!("=== Direct API Test 3: write_read() ==="); + test_async_write_read(&mut i2c, slave_addr).await; + + info!("=== Direct API Test 4: write_vectored() ==="); + test_async_write_vectored(&mut i2c, slave_addr).await; + + info!("=== Direct API Test 5: Large buffer (>255 bytes) ==="); + test_async_large_buffer(&mut i2c, slave_addr).await; + + info!("Async Direct API tests OK"); + + // Transaction tests + info!("=== Transaction Test 6: Consecutive Writes (Should Merge) ==="); + test_consecutive_writes_async(&mut i2c, slave_addr).await; + + info!("=== Transaction Test 7: Consecutive Reads (Should Merge) ==="); + test_consecutive_reads_async(&mut i2c, slave_addr).await; + + info!("=== Transaction Test 8: Write then Read (RESTART) ==="); + test_write_then_read_async(&mut i2c, slave_addr).await; + + info!("=== Transaction Test 9: Read then Write (RESTART) ==="); + test_read_then_write_async(&mut i2c, slave_addr).await; + + info!("=== Transaction Test 10: Complex Mixed Sequence ==="); + test_mixed_sequence_async(&mut i2c, slave_addr).await; + + info!("=== Transaction Test 11: Single Operations ==="); + test_single_operations_async(&mut i2c, slave_addr).await; + + info!("Async transaction tests OK"); + } + + info!("All tests OK"); + cortex_m::asm::bkpt(); +} + +// ==================== BLOCKING DIRECT API TEST FUNCTIONS ==================== + +fn test_blocking_write(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + let write_data = [0x42, 0x43, 0x44, 0x45]; + + match i2c.blocking_write(addr, &write_data) { + Ok(_) => info!("✓ blocking_write succeeded: {:02x}", write_data), + Err(e) => { + error!("✗ blocking_write failed: {:?}", e); + defmt::panic!("Test failed: blocking_write"); + } + } +} + +fn test_blocking_read(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + let mut read_buf = [0u8; 8]; + + match i2c.blocking_read(addr, &mut read_buf) { + Ok(_) => info!("✓ blocking_read succeeded: {:02x}", read_buf), + Err(e) => { + error!("✗ blocking_read failed: {:?}", e); + defmt::panic!("Test failed: blocking_read"); + } + } +} + +fn test_blocking_write_read(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + let write_data = [0x50, 0x51]; + let mut read_buf = [0u8; 6]; + + match i2c.blocking_write_read(addr, &write_data, &mut read_buf) { + Ok(_) => { + info!("✓ blocking_write_read succeeded"); + info!(" Written: {:02x}", write_data); + info!(" Read: {:02x}", read_buf); + } + Err(e) => { + error!("✗ blocking_write_read failed: {:?}", e); + defmt::panic!("Test failed: blocking_write_read"); + } + } +} + +fn test_blocking_write_vectored(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + let buf1 = [0x60, 0x61, 0x62]; + let buf2 = [0x70, 0x71]; + let buf3 = [0x80, 0x81, 0x82, 0x83]; + let bufs = [&buf1[..], &buf2[..], &buf3[..]]; + + match i2c.blocking_write_vectored(addr, &bufs) { + Ok(_) => info!("✓ blocking_write_vectored succeeded (9 bytes total)"), + Err(e) => { + error!("✗ blocking_write_vectored failed: {:?}", e); + defmt::panic!("Test failed: blocking_write_vectored"); + } + } +} + +fn test_blocking_large_buffer(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + // Test with 300 bytes to verify RELOAD mechanism works (needs chunking at 255 bytes) + let mut write_buf = [0u8; 300]; + for (i, byte) in write_buf.iter_mut().enumerate() { + *byte = (i & 0xFF) as u8; + } + + match i2c.blocking_write(addr, &write_buf) { + Ok(_) => info!("✓ Large buffer write succeeded (300 bytes, tests RELOAD)"), + Err(e) => { + error!("✗ Large buffer write failed: {:?}", e); + defmt::panic!("Test failed: large buffer write"); + } + } + + // Test large read + let mut read_buf = [0u8; 300]; + match i2c.blocking_read(addr, &mut read_buf) { + Ok(_) => info!("✓ Large buffer read succeeded (300 bytes, tests RELOAD)"), + Err(e) => { + error!("✗ Large buffer read failed: {:?}", e); + defmt::panic!("Test failed: large buffer read"); + } + } +} + +// ==================== BLOCKING TRANSACTION TEST FUNCTIONS ==================== + +fn test_consecutive_writes_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + // Expected on bus: START, ADDR+W, data1, data2, data3, STOP + // NO intermediate RESTART/STOP between writes - they should be merged + let data1 = [0x10, 0x11, 0x12]; + let data2 = [0x20, 0x21]; + let data3 = [0x30, 0x31, 0x32, 0x33]; + + let mut ops = [ + Operation::Write(&data1), + Operation::Write(&data2), + Operation::Write(&data3), + ]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => info!("✓ Consecutive writes succeeded (merged 9 bytes)"), + Err(e) => { + error!("✗ Consecutive writes failed: {:?}", e); + defmt::panic!("Test failed: consecutive writes"); + } + } +} + +fn test_consecutive_reads_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + // Expected on bus: START, ADDR+R, data1, data2, data3, NACK, STOP + // NO intermediate RESTART/STOP between reads - they should be merged + let mut buf1 = [0u8; 4]; + let mut buf2 = [0u8; 3]; + let mut buf3 = [0u8; 2]; + + let mut ops = [ + Operation::Read(&mut buf1), + Operation::Read(&mut buf2), + Operation::Read(&mut buf3), + ]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => { + info!("✓ Consecutive reads succeeded (merged 9 bytes)"); + info!(" buf1: {:02x}", buf1); + info!(" buf2: {:02x}", buf2); + info!(" buf3: {:02x}", buf3); + } + Err(e) => { + error!("✗ Consecutive reads failed: {:?}", e); + defmt::panic!("Test failed: consecutive reads"); + } + } +} + +fn test_write_then_read_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + // Expected: START, ADDR+W, data, RESTART, ADDR+R, data, NACK, STOP + let write_data = [0xAA, 0xBB]; + let mut read_buf = [0u8; 4]; + + let mut ops = [Operation::Write(&write_data), Operation::Read(&mut read_buf)]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => { + info!("✓ Write-then-read succeeded with RESTART"); + info!(" Written: {:02x}", write_data); + info!(" Read: {:02x}", read_buf); + } + Err(e) => { + error!("✗ Write-then-read failed: {:?}", e); + defmt::panic!("Test failed: write-then-read"); + } + } +} + +fn test_read_then_write_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + // Expected: START, ADDR+R, data, NACK, RESTART, ADDR+W, data, STOP + let mut read_buf = [0u8; 3]; + let write_data = [0xCC, 0xDD, 0xEE]; + + let mut ops = [Operation::Read(&mut read_buf), Operation::Write(&write_data)]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => { + info!("✓ Read-then-write succeeded with RESTART"); + info!(" Read: {:02x}", read_buf); + info!(" Written: {:02x}", write_data); + } + Err(e) => { + error!("✗ Read-then-write failed: {:?}", e); + defmt::panic!("Test failed: read-then-write"); + } + } +} + +fn test_mixed_sequence_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + // Complex: W, W, R, R, W, R + // Groups: [W,W] RESTART [R,R] RESTART [W] RESTART [R] + let w1 = [0x01, 0x02]; + let w2 = [0x03, 0x04]; + let mut r1 = [0u8; 2]; + let mut r2 = [0u8; 2]; + let w3 = [0x05]; + let mut r3 = [0u8; 1]; + + let mut ops = [ + Operation::Write(&w1), + Operation::Write(&w2), + Operation::Read(&mut r1), + Operation::Read(&mut r2), + Operation::Write(&w3), + Operation::Read(&mut r3), + ]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => { + info!("✓ Mixed sequence succeeded"); + info!(" Groups: [W4] RESTART [R4] RESTART [W1] RESTART [R1]"); + } + Err(e) => { + error!("✗ Mixed sequence failed: {:?}", e); + defmt::panic!("Test failed: mixed sequence"); + } + } +} + +fn test_single_operations_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + // Test single write + let write_data = [0xFF]; + let mut ops = [Operation::Write(&write_data)]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => info!("✓ Single write succeeded"), + Err(e) => { + error!("✗ Single write failed: {:?}", e); + defmt::panic!("Test failed: single write"); + } + } + + // Test single read + let mut read_buf = [0u8; 1]; + let mut ops = [Operation::Read(&mut read_buf)]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => info!("✓ Single read succeeded, data: 0x{:02x}", read_buf[0]), + Err(e) => { + error!("✗ Single read failed: {:?}", e); + defmt::panic!("Test failed: single read"); + } + } +} + +// ==================== ASYNC DIRECT API TEST FUNCTIONS ==================== + +async fn test_async_write(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let write_data = [0x42, 0x43, 0x44, 0x45]; + + match i2c.write(addr, &write_data).await { + Ok(_) => info!("✓ async write succeeded: {:02x}", write_data), + Err(e) => { + error!("✗ async write failed: {:?}", e); + defmt::panic!("Test failed: async write"); + } + } +} + +async fn test_async_read(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let mut read_buf = [0u8; 8]; + + match i2c.read(addr, &mut read_buf).await { + Ok(_) => info!("✓ async read succeeded: {:02x}", read_buf), + Err(e) => { + error!("✗ async read failed: {:?}", e); + defmt::panic!("Test failed: async read"); + } + } +} + +async fn test_async_write_read(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let write_data = [0x50, 0x51]; + let mut read_buf = [0u8; 6]; + + match i2c.write_read(addr, &write_data, &mut read_buf).await { + Ok(_) => { + info!("✓ async write_read succeeded"); + info!(" Written: {:02x}", write_data); + info!(" Read: {:02x}", read_buf); + } + Err(e) => { + error!("✗ async write_read failed: {:?}", e); + defmt::panic!("Test failed: async write_read"); + } + } +} + +async fn test_async_write_vectored(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let buf1 = [0x60, 0x61, 0x62]; + let buf2 = [0x70, 0x71]; + let buf3 = [0x80, 0x81, 0x82, 0x83]; + let bufs = [&buf1[..], &buf2[..], &buf3[..]]; + + match i2c.write_vectored(addr.into(), &bufs).await { + Ok(_) => info!("✓ async write_vectored succeeded (9 bytes total)"), + Err(e) => { + error!("✗ async write_vectored failed: {:?}", e); + defmt::panic!("Test failed: async write_vectored"); + } + } +} + +async fn test_async_large_buffer(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + // Test with 300 bytes to verify RELOAD mechanism works with DMA (needs chunking at 255 bytes) + let mut write_buf = [0u8; 300]; + for (i, byte) in write_buf.iter_mut().enumerate() { + *byte = (i & 0xFF) as u8; + } + + match i2c.write(addr, &write_buf).await { + Ok(_) => info!("✓ Large buffer async write succeeded (300 bytes, tests RELOAD with DMA)"), + Err(e) => { + error!("✗ Large buffer async write failed: {:?}", e); + defmt::panic!("Test failed: large buffer async write"); + } + } + + // Test large read + let mut read_buf = [0u8; 300]; + match i2c.read(addr, &mut read_buf).await { + Ok(_) => info!("✓ Large buffer async read succeeded (300 bytes, tests RELOAD with DMA)"), + Err(e) => { + error!("✗ Large buffer async read failed: {:?}", e); + defmt::panic!("Test failed: large buffer async read"); + } + } +} + +// ==================== ASYNC TRANSACTION TEST FUNCTIONS ==================== + +async fn test_consecutive_writes_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let data1 = [0x10, 0x11, 0x12]; + let data2 = [0x20, 0x21]; + let data3 = [0x30, 0x31, 0x32, 0x33]; + + let mut ops = [ + Operation::Write(&data1), + Operation::Write(&data2), + Operation::Write(&data3), + ]; + + match i2c.transaction(addr, &mut ops).await { + Ok(_) => info!("✓ Consecutive writes succeeded (merged 9 bytes)"), + Err(e) => { + error!("✗ Consecutive writes failed: {:?}", e); + defmt::panic!("Test failed: consecutive writes"); + } + } +} + +async fn test_consecutive_reads_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let mut buf1 = [0u8; 4]; + let mut buf2 = [0u8; 3]; + let mut buf3 = [0u8; 2]; + + let mut ops = [ + Operation::Read(&mut buf1), + Operation::Read(&mut buf2), + Operation::Read(&mut buf3), + ]; + + match i2c.transaction(addr, &mut ops).await { + Ok(_) => { + info!("✓ Consecutive reads succeeded (merged 9 bytes)"); + info!(" buf1: {:02x}", buf1); + info!(" buf2: {:02x}", buf2); + info!(" buf3: {:02x}", buf3); + } + Err(e) => { + error!("✗ Consecutive reads failed: {:?}", e); + defmt::panic!("Test failed: consecutive reads"); + } + } +} + +async fn test_write_then_read_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let write_data = [0xAA, 0xBB]; + let mut read_buf = [0u8; 4]; + + let mut ops = [Operation::Write(&write_data), Operation::Read(&mut read_buf)]; + + match i2c.transaction(addr, &mut ops).await { + Ok(_) => { + info!("✓ Write-then-read succeeded with RESTART"); + info!(" Written: {:02x}", write_data); + info!(" Read: {:02x}", read_buf); + } + Err(e) => { + error!("✗ Write-then-read failed: {:?}", e); + defmt::panic!("Test failed: write-then-read"); + } + } +} + +async fn test_read_then_write_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let mut read_buf = [0u8; 3]; + let write_data = [0xCC, 0xDD, 0xEE]; + + let mut ops = [Operation::Read(&mut read_buf), Operation::Write(&write_data)]; + + match i2c.transaction(addr, &mut ops).await { + Ok(_) => { + info!("✓ Read-then-write succeeded with RESTART"); + info!(" Read: {:02x}", read_buf); + info!(" Written: {:02x}", write_data); + } + Err(e) => { + error!("✗ Read-then-write failed: {:?}", e); + defmt::panic!("Test failed: read-then-write"); + } + } +} + +async fn test_mixed_sequence_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let w1 = [0x01, 0x02]; + let w2 = [0x03, 0x04]; + let mut r1 = [0u8; 2]; + let mut r2 = [0u8; 2]; + let w3 = [0x05]; + let mut r3 = [0u8; 1]; + + let mut ops = [ + Operation::Write(&w1), + Operation::Write(&w2), + Operation::Read(&mut r1), + Operation::Read(&mut r2), + Operation::Write(&w3), + Operation::Read(&mut r3), + ]; + + match i2c.transaction(addr, &mut ops).await { + Ok(_) => { + info!("✓ Mixed sequence succeeded"); + info!(" Groups: [W4] RESTART [R4] RESTART [W1] RESTART [R1]"); + } + Err(e) => { + error!("✗ Mixed sequence failed: {:?}", e); + defmt::panic!("Test failed: mixed sequence"); + } + } +} + +async fn test_single_operations_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + // Test single write + let write_data = [0xFF]; + let mut ops = [Operation::Write(&write_data)]; + + match i2c.transaction(addr, &mut ops).await { + Ok(_) => info!("✓ Single write succeeded"), + Err(e) => { + error!("✗ Single write failed: {:?}", e); + defmt::panic!("Test failed: single write"); + } + } + + // Test single read + let mut read_buf = [0u8; 1]; + let mut ops = [Operation::Read(&mut read_buf)]; + + match i2c.transaction(addr, &mut ops).await { + Ok(_) => info!("✓ Single read succeeded, data: 0x{:02x}", read_buf[0]), + Err(e) => { + error!("✗ Single read failed: {:?}", e); + defmt::panic!("Test failed: single read"); + } + } +} -- cgit From dbd4c384f94044505917295145c25777768a3081 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Thu, 13 Nov 2025 10:50:48 +0100 Subject: stm32/i2c: Run cargo fmt --- embassy-stm32/src/i2c/v2.rs | 91 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 69 insertions(+), 22 deletions(-) diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index 9771d7c98..7bcfa00b0 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -222,7 +222,13 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { Ok(()) } - fn reload(info: &'static Info, length: usize, will_reload: bool, stop: Stop, timeout: Timeout) -> Result<(), Error> { + fn reload( + info: &'static Info, + length: usize, + will_reload: bool, + stop: Stop, + timeout: Timeout, + ) -> Result<(), Error> { assert!(length < 256 && length > 0); // Wait for either TCR (Transfer Complete Reload) or TC (Transfer Complete) @@ -417,7 +423,13 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { for (number, chunk) in read.chunks_mut(255).enumerate() { if number != 0 { - Self::reload(self.info, chunk.len(), number != last_chunk_idx, Stop::Automatic, timeout)?; + Self::reload( + self.info, + chunk.len(), + number != last_chunk_idx, + Stop::Automatic, + timeout, + )?; } for byte in chunk { @@ -466,7 +478,13 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { for (number, chunk) in write.chunks(255).enumerate() { if number != 0 { - Self::reload(self.info, chunk.len(), number != last_chunk_idx, Stop::Software, timeout)?; + Self::reload( + self.info, + chunk.len(), + number != last_chunk_idx, + Stop::Software, + timeout, + )?; } for byte in chunk { @@ -606,7 +624,15 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { if first_chunk { // First chunk: initiate transfer // If not first group, use RESTART instead of START - Self::master_write(self.info, address, chunk_len, Stop::Software, will_reload, !is_first_group, timeout)?; + Self::master_write( + self.info, + address, + chunk_len, + Stop::Software, + will_reload, + !is_first_group, + timeout, + )?; first_chunk = false; } else { // Subsequent chunks: use reload @@ -906,7 +932,13 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { } else if remaining_len == 0 { return Poll::Ready(Ok(())); } else { - if let Err(e) = Self::reload(self.info, remaining_len.min(255), remaining_len > 255, Stop::Software, timeout) { + if let Err(e) = Self::reload( + self.info, + remaining_len.min(255), + remaining_len > 255, + Stop::Software, + timeout, + ) { return Poll::Ready(Err(e)); } self.info.regs.cr1().modify(|w| w.set_tcie(true)); @@ -1225,14 +1257,8 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { }; // We need a custom DMA read that respects our stop mode - self.read_dma_group_internal( - address, - buffer, - restart, - stop_mode, - timeout, - ) - .await?; + self.read_dma_group_internal(address, buffer, restart, stop_mode, timeout) + .await?; is_first_in_group = false; } else { // Subsequent buffers: need to reload and continue @@ -1243,11 +1269,8 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { }; self.read_dma_group_internal( - address, - buffer, - false, // no restart for subsequent buffers in same group - stop_mode, - timeout, + address, buffer, false, // no restart for subsequent buffers in same group + stop_mode, timeout, ) .await?; } @@ -1499,7 +1522,13 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { if number == 0 { Self::slave_start(self.info, chunk.len(), number != last_chunk_idx); } else { - Self::reload(self.info, chunk.len(), number != last_chunk_idx, Stop::Software, timeout)?; + Self::reload( + self.info, + chunk.len(), + number != last_chunk_idx, + Stop::Software, + timeout, + )?; } let mut index = 0; @@ -1548,7 +1577,13 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { if number == 0 { Self::slave_start(self.info, chunk.len(), number != last_chunk_idx); } else { - Self::reload(self.info, chunk.len(), number != last_chunk_idx, Stop::Software, timeout)?; + Self::reload( + self.info, + chunk.len(), + number != last_chunk_idx, + Stop::Software, + timeout, + )?; } let mut index = 0; @@ -1684,7 +1719,13 @@ impl<'d> I2c<'d, Async, MultiMaster> { Poll::Pending } else if isr.tcr() { let is_last_slice = remaining_len <= 255; - if let Err(e) = Self::reload(self.info, remaining_len.min(255), !is_last_slice, Stop::Software, timeout) { + if let Err(e) = Self::reload( + self.info, + remaining_len.min(255), + !is_last_slice, + Stop::Software, + timeout, + ) { return Poll::Ready(Err(e)); } remaining_len = remaining_len.saturating_sub(255); @@ -1748,7 +1789,13 @@ impl<'d> I2c<'d, Async, MultiMaster> { Poll::Pending } else if isr.tcr() { let is_last_slice = remaining_len <= 255; - if let Err(e) = Self::reload(self.info, remaining_len.min(255), !is_last_slice, Stop::Software, timeout) { + if let Err(e) = Self::reload( + self.info, + remaining_len.min(255), + !is_last_slice, + Stop::Software, + timeout, + ) { return Poll::Ready(Err(e)); } remaining_len = remaining_len.saturating_sub(255); -- cgit From 260a3fdc530ef430956ed313811efea94e1dff5c Mon Sep 17 00:00:00 2001 From: HybridChild Date: Thu, 13 Nov 2025 10:57:41 +0100 Subject: stm32: Run cargo fmt for tests/ --- tests/stm32/src/bin/i2c_v2_master.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/tests/stm32/src/bin/i2c_v2_master.rs b/tests/stm32/src/bin/i2c_v2_master.rs index b841d556a..34f9b48d3 100644 --- a/tests/stm32/src/bin/i2c_v2_master.rs +++ b/tests/stm32/src/bin/i2c_v2_master.rs @@ -48,12 +48,7 @@ async fn main(_spawner: Spawner) { // ========== BLOCKING DIRECT API TESTS ========== info!("========== BLOCKING DIRECT API TESTS =========="); { - let mut i2c = I2c::new_blocking( - i2c_peri.reborrow(), - scl.reborrow(), - sda.reborrow(), - config, - ); + let mut i2c = I2c::new_blocking(i2c_peri.reborrow(), scl.reborrow(), sda.reborrow(), config); info!("=== Test 1: Direct blocking_write ==="); test_blocking_write(&mut i2c, slave_addr); @@ -78,12 +73,7 @@ async fn main(_spawner: Spawner) { // ========== BLOCKING TRANSACTION TESTS ========== info!("========== BLOCKING TRANSACTION TESTS =========="); { - let mut i2c = I2c::new_blocking( - i2c_peri.reborrow(), - scl.reborrow(), - sda.reborrow(), - config, - ); + let mut i2c = I2c::new_blocking(i2c_peri.reborrow(), scl.reborrow(), sda.reborrow(), config); info!("=== Test 6: Consecutive Writes (Should Merge) ==="); test_consecutive_writes_blocking(&mut i2c, slave_addr); -- cgit From 2553ced205d49d2890e000069f5a170b75d267a9 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Thu, 13 Nov 2025 14:36:31 +0100 Subject: stm32: Move i2c_master test to examples --- examples/stm32f0/Cargo.toml | 1 + examples/stm32f0/src/bin/i2c_master.rs | 609 +++++++++++++++++++++++++++++++++ tests/stm32/Cargo.toml | 7 - tests/stm32/build.rs | 1 - tests/stm32/src/bin/i2c_v2_master.rs | 608 -------------------------------- tests/stm32/src/common.rs | 25 -- 6 files changed, 610 insertions(+), 641 deletions(-) create mode 100644 examples/stm32f0/src/bin/i2c_master.rs delete mode 100644 tests/stm32/src/bin/i2c_v2_master.rs diff --git a/examples/stm32f0/Cargo.toml b/examples/stm32f0/Cargo.toml index a78873d21..177dd0ac2 100644 --- a/examples/stm32f0/Cargo.toml +++ b/examples/stm32f0/Cargo.toml @@ -16,6 +16,7 @@ panic-probe = { version = "1.0.0", features = ["print-defmt"] } embassy-sync = { version = "0.7.2", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.9.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } embassy-time = { version = "0.5.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } static_cell = "2" portable-atomic = { version = "1.5", features = ["unsafe-assume-single-core"] } diff --git a/examples/stm32f0/src/bin/i2c_master.rs b/examples/stm32f0/src/bin/i2c_master.rs new file mode 100644 index 000000000..2e61ecdf7 --- /dev/null +++ b/examples/stm32f0/src/bin/i2c_master.rs @@ -0,0 +1,609 @@ +#![no_std] +#![no_main] + +// Hardware Setup for NUCLEO-F072RB: +// - I2C1 pins: PB8 (SCL), PB9 (SDA) on CN5 connector +// - Connect to I2C slave device (e.g., Digilent Analog Discovery I2C slave, or EEPROM) +// - Default slave address: 0x50 +// - Pull-up resistors: 4.7kΩ on both SCL and SDA +// - CN5 Pin 10 (PB8/SCL) and CN5 Pin 9 (PB9/SDA) +// +// Analog Discovery - Waveforms Setup: +// - Increase buffer size: Settings -> Device Manager -> Option 4 +// - Run Protocol Analyzer +// - Configure as I2C Slave at address 0x50 +// - Connect and configure DIO pins for SCL and SDA +// - Frequency: 100kHz - [✓] Clock Stretching + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::i2c::{Config, I2c, Master}; +use embassy_stm32::mode::{Async, Blocking}; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, i2c, peripherals}; +use embassy_time::Timer; +use embedded_hal_1::i2c::Operation; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + I2C1 => i2c::EventInterruptHandler, i2c::ErrorInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Run stm32 I2C v2 Master Tests..."); + + let mut i2c_peri = p.I2C1; + let mut scl = p.PB8; + let mut sda = p.PB9; + + let mut config = Config::default(); + config.frequency = Hertz(100_000); + + // I2C slave address for Analog Discovery or test EEPROM + let slave_addr = 0x50u8; + + // Wait for slave device to be ready + Timer::after_millis(100).await; + + // ========== BLOCKING DIRECT API TESTS ========== + info!("========== BLOCKING DIRECT API TESTS =========="); + { + let mut i2c = I2c::new_blocking(i2c_peri.reborrow(), scl.reborrow(), sda.reborrow(), config); + + info!("=== Test 1: Direct blocking_write ==="); + test_blocking_write(&mut i2c, slave_addr); + + info!("=== Test 2: Direct blocking_read ==="); + test_blocking_read(&mut i2c, slave_addr); + + info!("=== Test 3: Direct blocking_write_read ==="); + test_blocking_write_read(&mut i2c, slave_addr); + + info!("=== Test 4: Direct blocking_write_vectored ==="); + test_blocking_write_vectored(&mut i2c, slave_addr); + + info!("=== Test 5: Large buffer (>255 bytes) ==="); + test_blocking_large_buffer(&mut i2c, slave_addr); + + info!("Blocking direct API tests OK"); + } + + Timer::after_millis(100).await; + + // ========== BLOCKING TRANSACTION TESTS ========== + info!("========== BLOCKING TRANSACTION TESTS =========="); + { + let mut i2c = I2c::new_blocking(i2c_peri.reborrow(), scl.reborrow(), sda.reborrow(), config); + + info!("=== Test 6: Consecutive Writes (Should Merge) ==="); + test_consecutive_writes_blocking(&mut i2c, slave_addr); + + info!("=== Test 7: Consecutive Reads (Should Merge) ==="); + test_consecutive_reads_blocking(&mut i2c, slave_addr); + + info!("=== Test 8: Write then Read (RESTART) ==="); + test_write_then_read_blocking(&mut i2c, slave_addr); + + info!("=== Test 9: Read then Write (RESTART) ==="); + test_read_then_write_blocking(&mut i2c, slave_addr); + + info!("=== Test 10: Complex Mixed Sequence ==="); + test_mixed_sequence_blocking(&mut i2c, slave_addr); + + info!("=== Test 11: Single Operations ==="); + test_single_operations_blocking(&mut i2c, slave_addr); + + info!("Blocking transaction tests OK"); + } + + Timer::after_millis(100).await; + + // ========== ASYNC TESTS (DMA) ========== + info!("========== ASYNC TESTS (DMA) =========="); + { + let tx_dma = p.DMA1_CH2; + let rx_dma = p.DMA1_CH3; + + let mut i2c = I2c::new(i2c_peri, scl, sda, Irqs, tx_dma, rx_dma, config); + + // Direct API tests (reusing same I2C instance) + info!("=== Direct API Test 1: write() ==="); + test_async_write(&mut i2c, slave_addr).await; + + info!("=== Direct API Test 2: read() ==="); + test_async_read(&mut i2c, slave_addr).await; + + info!("=== Direct API Test 3: write_read() ==="); + test_async_write_read(&mut i2c, slave_addr).await; + + info!("=== Direct API Test 4: write_vectored() ==="); + test_async_write_vectored(&mut i2c, slave_addr).await; + + info!("=== Direct API Test 5: Large buffer (>255 bytes) ==="); + test_async_large_buffer(&mut i2c, slave_addr).await; + + info!("Async Direct API tests OK"); + + // Transaction tests + info!("=== Transaction Test 6: Consecutive Writes (Should Merge) ==="); + test_consecutive_writes_async(&mut i2c, slave_addr).await; + + info!("=== Transaction Test 7: Consecutive Reads (Should Merge) ==="); + test_consecutive_reads_async(&mut i2c, slave_addr).await; + + info!("=== Transaction Test 8: Write then Read (RESTART) ==="); + test_write_then_read_async(&mut i2c, slave_addr).await; + + info!("=== Transaction Test 9: Read then Write (RESTART) ==="); + test_read_then_write_async(&mut i2c, slave_addr).await; + + info!("=== Transaction Test 10: Complex Mixed Sequence ==="); + test_mixed_sequence_async(&mut i2c, slave_addr).await; + + info!("=== Transaction Test 11: Single Operations ==="); + test_single_operations_async(&mut i2c, slave_addr).await; + + info!("Async transaction tests OK"); + } + + info!("All tests OK"); + cortex_m::asm::bkpt(); +} + +// ==================== BLOCKING DIRECT API TEST FUNCTIONS ==================== + +fn test_blocking_write(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + let write_data = [0x42, 0x43, 0x44, 0x45]; + + match i2c.blocking_write(addr, &write_data) { + Ok(_) => info!("✓ blocking_write succeeded: {:02x}", write_data), + Err(e) => { + error!("✗ blocking_write failed: {:?}", e); + defmt::panic!("Test failed: blocking_write"); + } + } +} + +fn test_blocking_read(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + let mut read_buf = [0u8; 8]; + + match i2c.blocking_read(addr, &mut read_buf) { + Ok(_) => info!("✓ blocking_read succeeded: {:02x}", read_buf), + Err(e) => { + error!("✗ blocking_read failed: {:?}", e); + defmt::panic!("Test failed: blocking_read"); + } + } +} + +fn test_blocking_write_read(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + let write_data = [0x50, 0x51]; + let mut read_buf = [0u8; 6]; + + match i2c.blocking_write_read(addr, &write_data, &mut read_buf) { + Ok(_) => { + info!("✓ blocking_write_read succeeded"); + info!(" Written: {:02x}", write_data); + info!(" Read: {:02x}", read_buf); + } + Err(e) => { + error!("✗ blocking_write_read failed: {:?}", e); + defmt::panic!("Test failed: blocking_write_read"); + } + } +} + +fn test_blocking_write_vectored(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + let buf1 = [0x60, 0x61, 0x62]; + let buf2 = [0x70, 0x71]; + let buf3 = [0x80, 0x81, 0x82, 0x83]; + let bufs = [&buf1[..], &buf2[..], &buf3[..]]; + + match i2c.blocking_write_vectored(addr, &bufs) { + Ok(_) => info!("✓ blocking_write_vectored succeeded (9 bytes total)"), + Err(e) => { + error!("✗ blocking_write_vectored failed: {:?}", e); + defmt::panic!("Test failed: blocking_write_vectored"); + } + } +} + +fn test_blocking_large_buffer(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + // Test with 300 bytes to verify RELOAD mechanism works (needs chunking at 255 bytes) + let mut write_buf = [0u8; 300]; + for (i, byte) in write_buf.iter_mut().enumerate() { + *byte = (i & 0xFF) as u8; + } + + match i2c.blocking_write(addr, &write_buf) { + Ok(_) => info!("✓ Large buffer write succeeded (300 bytes, tests RELOAD)"), + Err(e) => { + error!("✗ Large buffer write failed: {:?}", e); + defmt::panic!("Test failed: large buffer write"); + } + } + + // Test large read + let mut read_buf = [0u8; 300]; + match i2c.blocking_read(addr, &mut read_buf) { + Ok(_) => info!("✓ Large buffer read succeeded (300 bytes, tests RELOAD)"), + Err(e) => { + error!("✗ Large buffer read failed: {:?}", e); + defmt::panic!("Test failed: large buffer read"); + } + } +} + +// ==================== BLOCKING TRANSACTION TEST FUNCTIONS ==================== + +fn test_consecutive_writes_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + // Expected on bus: START, ADDR+W, data1, data2, data3, STOP + // NO intermediate RESTART/STOP between writes - they should be merged + let data1 = [0x10, 0x11, 0x12]; + let data2 = [0x20, 0x21]; + let data3 = [0x30, 0x31, 0x32, 0x33]; + + let mut ops = [ + Operation::Write(&data1), + Operation::Write(&data2), + Operation::Write(&data3), + ]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => info!("✓ Consecutive writes succeeded (merged 9 bytes)"), + Err(e) => { + error!("✗ Consecutive writes failed: {:?}", e); + defmt::panic!("Test failed: consecutive writes"); + } + } +} + +fn test_consecutive_reads_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + // Expected on bus: START, ADDR+R, data1, data2, data3, NACK, STOP + // NO intermediate RESTART/STOP between reads - they should be merged + let mut buf1 = [0u8; 4]; + let mut buf2 = [0u8; 3]; + let mut buf3 = [0u8; 2]; + + let mut ops = [ + Operation::Read(&mut buf1), + Operation::Read(&mut buf2), + Operation::Read(&mut buf3), + ]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => { + info!("✓ Consecutive reads succeeded (merged 9 bytes)"); + info!(" buf1: {:02x}", buf1); + info!(" buf2: {:02x}", buf2); + info!(" buf3: {:02x}", buf3); + } + Err(e) => { + error!("✗ Consecutive reads failed: {:?}", e); + defmt::panic!("Test failed: consecutive reads"); + } + } +} + +fn test_write_then_read_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + // Expected: START, ADDR+W, data, RESTART, ADDR+R, data, NACK, STOP + let write_data = [0xAA, 0xBB]; + let mut read_buf = [0u8; 4]; + + let mut ops = [Operation::Write(&write_data), Operation::Read(&mut read_buf)]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => { + info!("✓ Write-then-read succeeded with RESTART"); + info!(" Written: {:02x}", write_data); + info!(" Read: {:02x}", read_buf); + } + Err(e) => { + error!("✗ Write-then-read failed: {:?}", e); + defmt::panic!("Test failed: write-then-read"); + } + } +} + +fn test_read_then_write_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + // Expected: START, ADDR+R, data, NACK, RESTART, ADDR+W, data, STOP + let mut read_buf = [0u8; 3]; + let write_data = [0xCC, 0xDD, 0xEE]; + + let mut ops = [Operation::Read(&mut read_buf), Operation::Write(&write_data)]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => { + info!("✓ Read-then-write succeeded with RESTART"); + info!(" Read: {:02x}", read_buf); + info!(" Written: {:02x}", write_data); + } + Err(e) => { + error!("✗ Read-then-write failed: {:?}", e); + defmt::panic!("Test failed: read-then-write"); + } + } +} + +fn test_mixed_sequence_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + // Complex: W, W, R, R, W, R + // Groups: [W,W] RESTART [R,R] RESTART [W] RESTART [R] + let w1 = [0x01, 0x02]; + let w2 = [0x03, 0x04]; + let mut r1 = [0u8; 2]; + let mut r2 = [0u8; 2]; + let w3 = [0x05]; + let mut r3 = [0u8; 1]; + + let mut ops = [ + Operation::Write(&w1), + Operation::Write(&w2), + Operation::Read(&mut r1), + Operation::Read(&mut r2), + Operation::Write(&w3), + Operation::Read(&mut r3), + ]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => { + info!("✓ Mixed sequence succeeded"); + info!(" Groups: [W4] RESTART [R4] RESTART [W1] RESTART [R1]"); + } + Err(e) => { + error!("✗ Mixed sequence failed: {:?}", e); + defmt::panic!("Test failed: mixed sequence"); + } + } +} + +fn test_single_operations_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { + // Test single write + let write_data = [0xFF]; + let mut ops = [Operation::Write(&write_data)]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => info!("✓ Single write succeeded"), + Err(e) => { + error!("✗ Single write failed: {:?}", e); + defmt::panic!("Test failed: single write"); + } + } + + // Test single read + let mut read_buf = [0u8; 1]; + let mut ops = [Operation::Read(&mut read_buf)]; + + match i2c.blocking_transaction(addr, &mut ops) { + Ok(_) => info!("✓ Single read succeeded, data: 0x{:02x}", read_buf[0]), + Err(e) => { + error!("✗ Single read failed: {:?}", e); + defmt::panic!("Test failed: single read"); + } + } +} + +// ==================== ASYNC DIRECT API TEST FUNCTIONS ==================== + +async fn test_async_write(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let write_data = [0x42, 0x43, 0x44, 0x45]; + + match i2c.write(addr, &write_data).await { + Ok(_) => info!("✓ async write succeeded: {:02x}", write_data), + Err(e) => { + error!("✗ async write failed: {:?}", e); + defmt::panic!("Test failed: async write"); + } + } +} + +async fn test_async_read(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let mut read_buf = [0u8; 8]; + + match i2c.read(addr, &mut read_buf).await { + Ok(_) => info!("✓ async read succeeded: {:02x}", read_buf), + Err(e) => { + error!("✗ async read failed: {:?}", e); + defmt::panic!("Test failed: async read"); + } + } +} + +async fn test_async_write_read(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let write_data = [0x50, 0x51]; + let mut read_buf = [0u8; 6]; + + match i2c.write_read(addr, &write_data, &mut read_buf).await { + Ok(_) => { + info!("✓ async write_read succeeded"); + info!(" Written: {:02x}", write_data); + info!(" Read: {:02x}", read_buf); + } + Err(e) => { + error!("✗ async write_read failed: {:?}", e); + defmt::panic!("Test failed: async write_read"); + } + } +} + +async fn test_async_write_vectored(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let buf1 = [0x60, 0x61, 0x62]; + let buf2 = [0x70, 0x71]; + let buf3 = [0x80, 0x81, 0x82, 0x83]; + let bufs = [&buf1[..], &buf2[..], &buf3[..]]; + + match i2c.write_vectored(addr.into(), &bufs).await { + Ok(_) => info!("✓ async write_vectored succeeded (9 bytes total)"), + Err(e) => { + error!("✗ async write_vectored failed: {:?}", e); + defmt::panic!("Test failed: async write_vectored"); + } + } +} + +async fn test_async_large_buffer(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + // Test with 300 bytes to verify RELOAD mechanism works with DMA (needs chunking at 255 bytes) + let mut write_buf = [0u8; 300]; + for (i, byte) in write_buf.iter_mut().enumerate() { + *byte = (i & 0xFF) as u8; + } + + match i2c.write(addr, &write_buf).await { + Ok(_) => info!("✓ Large buffer async write succeeded (300 bytes, tests RELOAD with DMA)"), + Err(e) => { + error!("✗ Large buffer async write failed: {:?}", e); + defmt::panic!("Test failed: large buffer async write"); + } + } + + // Test large read + let mut read_buf = [0u8; 300]; + match i2c.read(addr, &mut read_buf).await { + Ok(_) => info!("✓ Large buffer async read succeeded (300 bytes, tests RELOAD with DMA)"), + Err(e) => { + error!("✗ Large buffer async read failed: {:?}", e); + defmt::panic!("Test failed: large buffer async read"); + } + } +} + +// ==================== ASYNC TRANSACTION TEST FUNCTIONS ==================== + +async fn test_consecutive_writes_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let data1 = [0x10, 0x11, 0x12]; + let data2 = [0x20, 0x21]; + let data3 = [0x30, 0x31, 0x32, 0x33]; + + let mut ops = [ + Operation::Write(&data1), + Operation::Write(&data2), + Operation::Write(&data3), + ]; + + match i2c.transaction(addr, &mut ops).await { + Ok(_) => info!("✓ Consecutive writes succeeded (merged 9 bytes)"), + Err(e) => { + error!("✗ Consecutive writes failed: {:?}", e); + defmt::panic!("Test failed: consecutive writes"); + } + } +} + +async fn test_consecutive_reads_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let mut buf1 = [0u8; 4]; + let mut buf2 = [0u8; 3]; + let mut buf3 = [0u8; 2]; + + let mut ops = [ + Operation::Read(&mut buf1), + Operation::Read(&mut buf2), + Operation::Read(&mut buf3), + ]; + + match i2c.transaction(addr, &mut ops).await { + Ok(_) => { + info!("✓ Consecutive reads succeeded (merged 9 bytes)"); + info!(" buf1: {:02x}", buf1); + info!(" buf2: {:02x}", buf2); + info!(" buf3: {:02x}", buf3); + } + Err(e) => { + error!("✗ Consecutive reads failed: {:?}", e); + defmt::panic!("Test failed: consecutive reads"); + } + } +} + +async fn test_write_then_read_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let write_data = [0xAA, 0xBB]; + let mut read_buf = [0u8; 4]; + + let mut ops = [Operation::Write(&write_data), Operation::Read(&mut read_buf)]; + + match i2c.transaction(addr, &mut ops).await { + Ok(_) => { + info!("✓ Write-then-read succeeded with RESTART"); + info!(" Written: {:02x}", write_data); + info!(" Read: {:02x}", read_buf); + } + Err(e) => { + error!("✗ Write-then-read failed: {:?}", e); + defmt::panic!("Test failed: write-then-read"); + } + } +} + +async fn test_read_then_write_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let mut read_buf = [0u8; 3]; + let write_data = [0xCC, 0xDD, 0xEE]; + + let mut ops = [Operation::Read(&mut read_buf), Operation::Write(&write_data)]; + + match i2c.transaction(addr, &mut ops).await { + Ok(_) => { + info!("✓ Read-then-write succeeded with RESTART"); + info!(" Read: {:02x}", read_buf); + info!(" Written: {:02x}", write_data); + } + Err(e) => { + error!("✗ Read-then-write failed: {:?}", e); + defmt::panic!("Test failed: read-then-write"); + } + } +} + +async fn test_mixed_sequence_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + let w1 = [0x01, 0x02]; + let w2 = [0x03, 0x04]; + let mut r1 = [0u8; 2]; + let mut r2 = [0u8; 2]; + let w3 = [0x05]; + let mut r3 = [0u8; 1]; + + let mut ops = [ + Operation::Write(&w1), + Operation::Write(&w2), + Operation::Read(&mut r1), + Operation::Read(&mut r2), + Operation::Write(&w3), + Operation::Read(&mut r3), + ]; + + match i2c.transaction(addr, &mut ops).await { + Ok(_) => { + info!("✓ Mixed sequence succeeded"); + info!(" Groups: [W4] RESTART [R4] RESTART [W1] RESTART [R1]"); + } + Err(e) => { + error!("✗ Mixed sequence failed: {:?}", e); + defmt::panic!("Test failed: mixed sequence"); + } + } +} + +async fn test_single_operations_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { + // Test single write + let write_data = [0xFF]; + let mut ops = [Operation::Write(&write_data)]; + + match i2c.transaction(addr, &mut ops).await { + Ok(_) => info!("✓ Single write succeeded"), + Err(e) => { + error!("✗ Single write failed: {:?}", e); + defmt::panic!("Test failed: single write"); + } + } + + // Test single read + let mut read_buf = [0u8; 1]; + let mut ops = [Operation::Read(&mut read_buf)]; + + match i2c.transaction(addr, &mut ops).await { + Ok(_) => info!("✓ Single read succeeded, data: 0x{:02x}", read_buf[0]), + Err(e) => { + error!("✗ Single read failed: {:?}", e); + defmt::panic!("Test failed: single read"); + } + } +} diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml index 1912a772c..b92b47be2 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml @@ -35,7 +35,6 @@ stm32wb55rg = ["embassy-stm32/stm32wb55rg", "chrono", "not-gpdma", "ble", "mac" stm32wba52cg = ["embassy-stm32/stm32wba52cg", "spi-v345", "chrono", "rng", "hash"] stm32wl55jc = ["embassy-stm32/stm32wl55jc-cm4", "not-gpdma", "rng", "chrono"] stm32f091rc = ["embassy-stm32/stm32f091rc", "cm0", "not-gpdma", "chrono"] -stm32f072rb = ["embassy-stm32/stm32f072rb", "cm0", "not-gpdma", "chrono"] stm32h503rb = ["embassy-stm32/stm32h503rb", "spi-v345", "rng", "stop"] stm32h7s3l8 = ["embassy-stm32/stm32h7s3l8", "spi-v345", "rng", "cordic", "hash-v34"] # TODO: fdcan crashes, cryp dma hangs. stm32u083rc = ["embassy-stm32/stm32u083rc", "cm0", "rng", "chrono"] @@ -160,11 +159,6 @@ name = "hash" path = "src/bin/hash.rs" required-features = [ "hash",] -[[bin]] -name = "i2c_v2_master" -path = "src/bin/i2c_v2_master.rs" -required-features = [ "stm32f072rb",] - [[bin]] name = "rng" path = "src/bin/rng.rs" @@ -291,7 +285,6 @@ build = [ { target = "thumbv7em-none-eabi", features = ["stm32wl55jc"], artifact-dir = "out/tests/stm32wl55jc" }, { target = "thumbv7em-none-eabi", features = ["stm32h7s3l8"], artifact-dir = "out/tests/stm32h7s3l8" }, { target = "thumbv6m-none-eabi", features = ["stm32f091rc"], artifact-dir = "out/tests/stm32f091rc" }, - { target = "thumbv6m-none-eabi", features = ["stm32f072rb"], artifact-dir = "out/tests/stm32f072rb" }, { target = "thumbv8m.main-none-eabihf", features = ["stm32h503rb"], artifact-dir = "out/tests/stm32h503rb" }, { target = "thumbv6m-none-eabi", features = ["stm32u083rc"], artifact-dir = "out/tests/stm32u083rc" } ] diff --git a/tests/stm32/build.rs b/tests/stm32/build.rs index 34030c206..556d77a20 100644 --- a/tests/stm32/build.rs +++ b/tests/stm32/build.rs @@ -15,7 +15,6 @@ fn main() -> Result<(), Box> { feature = "stm32c071rb", // 24 kb feature = "stm32l073rz", // 20 kb feature = "stm32h503rb", // 32 kb - feature = "stm32f072rb", // 16 kb - I2C v2 test with async too large for RAM // no VTOR, so interrupts can't work when running from RAM feature = "stm32f091rc", )) { diff --git a/tests/stm32/src/bin/i2c_v2_master.rs b/tests/stm32/src/bin/i2c_v2_master.rs deleted file mode 100644 index 34f9b48d3..000000000 --- a/tests/stm32/src/bin/i2c_v2_master.rs +++ /dev/null @@ -1,608 +0,0 @@ -#![no_std] -#![no_main] -// required-features: stm32f072rb -// -// Hardware Setup for NUCLEO-F072RB: -// - I2C1 pins: PB8 (SCL), PB9 (SDA) on CN5 connector -// - Connect to I2C slave device (e.g., Digilent Analog Discovery I2C slave, or EEPROM) -// - Default slave address: 0x50 -// - Pull-up resistors: 4.7kΩ on both SCL and SDA -// - CN5 Pin 10 (PB8/SCL) and CN5 Pin 9 (PB9/SDA) -// -// Analog Discovery - Waveforms Setup: -// - Increase buffer size: Settings -> Device Manager -> Option 4 -// - Run Protocol Analyzer -// - Configure as I2C Slave at address 0x50 -// - Connect and configure DIO pins for SCL and SDA -// - Frequency: 100kHz - [✓] Clock Stretching - -#[path = "../common.rs"] -mod common; - -use common::*; -use embassy_executor::Spawner; -use embassy_stm32::i2c::{Config, I2c, Master}; -use embassy_stm32::mode::{Async, Blocking}; -use embassy_stm32::time::Hertz; -use embassy_time::Timer; -use embedded_hal_1::i2c::Operation; - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = init(); - info!("Run stm32 I2C v2 Master Tests..."); - - let mut i2c_peri = peri!(p, I2C); - let mut scl = peri!(p, I2C_SCL); - let mut sda = peri!(p, I2C_SDA); - - let mut config = Config::default(); - config.frequency = Hertz(100_000); - - // I2C slave address for Analog Discovery or test EEPROM - let slave_addr = 0x50u8; - - // Wait for slave device to be ready - Timer::after_millis(100).await; - - // ========== BLOCKING DIRECT API TESTS ========== - info!("========== BLOCKING DIRECT API TESTS =========="); - { - let mut i2c = I2c::new_blocking(i2c_peri.reborrow(), scl.reborrow(), sda.reborrow(), config); - - info!("=== Test 1: Direct blocking_write ==="); - test_blocking_write(&mut i2c, slave_addr); - - info!("=== Test 2: Direct blocking_read ==="); - test_blocking_read(&mut i2c, slave_addr); - - info!("=== Test 3: Direct blocking_write_read ==="); - test_blocking_write_read(&mut i2c, slave_addr); - - info!("=== Test 4: Direct blocking_write_vectored ==="); - test_blocking_write_vectored(&mut i2c, slave_addr); - - info!("=== Test 5: Large buffer (>255 bytes) ==="); - test_blocking_large_buffer(&mut i2c, slave_addr); - - info!("Blocking direct API tests OK"); - } - - Timer::after_millis(100).await; - - // ========== BLOCKING TRANSACTION TESTS ========== - info!("========== BLOCKING TRANSACTION TESTS =========="); - { - let mut i2c = I2c::new_blocking(i2c_peri.reborrow(), scl.reborrow(), sda.reborrow(), config); - - info!("=== Test 6: Consecutive Writes (Should Merge) ==="); - test_consecutive_writes_blocking(&mut i2c, slave_addr); - - info!("=== Test 7: Consecutive Reads (Should Merge) ==="); - test_consecutive_reads_blocking(&mut i2c, slave_addr); - - info!("=== Test 8: Write then Read (RESTART) ==="); - test_write_then_read_blocking(&mut i2c, slave_addr); - - info!("=== Test 9: Read then Write (RESTART) ==="); - test_read_then_write_blocking(&mut i2c, slave_addr); - - info!("=== Test 10: Complex Mixed Sequence ==="); - test_mixed_sequence_blocking(&mut i2c, slave_addr); - - info!("=== Test 11: Single Operations ==="); - test_single_operations_blocking(&mut i2c, slave_addr); - - info!("Blocking transaction tests OK"); - } - - Timer::after_millis(100).await; - - // ========== ASYNC TESTS (DMA) ========== - info!("========== ASYNC TESTS (DMA) =========="); - { - let tx_dma = peri!(p, I2C_TX_DMA); - let rx_dma = peri!(p, I2C_RX_DMA); - let irq = irqs!(I2C); - - let mut i2c = I2c::new(i2c_peri, scl, sda, irq, tx_dma, rx_dma, config); - - // Direct API tests (reusing same I2C instance) - info!("=== Direct API Test 1: write() ==="); - test_async_write(&mut i2c, slave_addr).await; - - info!("=== Direct API Test 2: read() ==="); - test_async_read(&mut i2c, slave_addr).await; - - info!("=== Direct API Test 3: write_read() ==="); - test_async_write_read(&mut i2c, slave_addr).await; - - info!("=== Direct API Test 4: write_vectored() ==="); - test_async_write_vectored(&mut i2c, slave_addr).await; - - info!("=== Direct API Test 5: Large buffer (>255 bytes) ==="); - test_async_large_buffer(&mut i2c, slave_addr).await; - - info!("Async Direct API tests OK"); - - // Transaction tests - info!("=== Transaction Test 6: Consecutive Writes (Should Merge) ==="); - test_consecutive_writes_async(&mut i2c, slave_addr).await; - - info!("=== Transaction Test 7: Consecutive Reads (Should Merge) ==="); - test_consecutive_reads_async(&mut i2c, slave_addr).await; - - info!("=== Transaction Test 8: Write then Read (RESTART) ==="); - test_write_then_read_async(&mut i2c, slave_addr).await; - - info!("=== Transaction Test 9: Read then Write (RESTART) ==="); - test_read_then_write_async(&mut i2c, slave_addr).await; - - info!("=== Transaction Test 10: Complex Mixed Sequence ==="); - test_mixed_sequence_async(&mut i2c, slave_addr).await; - - info!("=== Transaction Test 11: Single Operations ==="); - test_single_operations_async(&mut i2c, slave_addr).await; - - info!("Async transaction tests OK"); - } - - info!("All tests OK"); - cortex_m::asm::bkpt(); -} - -// ==================== BLOCKING DIRECT API TEST FUNCTIONS ==================== - -fn test_blocking_write(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { - let write_data = [0x42, 0x43, 0x44, 0x45]; - - match i2c.blocking_write(addr, &write_data) { - Ok(_) => info!("✓ blocking_write succeeded: {:02x}", write_data), - Err(e) => { - error!("✗ blocking_write failed: {:?}", e); - defmt::panic!("Test failed: blocking_write"); - } - } -} - -fn test_blocking_read(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { - let mut read_buf = [0u8; 8]; - - match i2c.blocking_read(addr, &mut read_buf) { - Ok(_) => info!("✓ blocking_read succeeded: {:02x}", read_buf), - Err(e) => { - error!("✗ blocking_read failed: {:?}", e); - defmt::panic!("Test failed: blocking_read"); - } - } -} - -fn test_blocking_write_read(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { - let write_data = [0x50, 0x51]; - let mut read_buf = [0u8; 6]; - - match i2c.blocking_write_read(addr, &write_data, &mut read_buf) { - Ok(_) => { - info!("✓ blocking_write_read succeeded"); - info!(" Written: {:02x}", write_data); - info!(" Read: {:02x}", read_buf); - } - Err(e) => { - error!("✗ blocking_write_read failed: {:?}", e); - defmt::panic!("Test failed: blocking_write_read"); - } - } -} - -fn test_blocking_write_vectored(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { - let buf1 = [0x60, 0x61, 0x62]; - let buf2 = [0x70, 0x71]; - let buf3 = [0x80, 0x81, 0x82, 0x83]; - let bufs = [&buf1[..], &buf2[..], &buf3[..]]; - - match i2c.blocking_write_vectored(addr, &bufs) { - Ok(_) => info!("✓ blocking_write_vectored succeeded (9 bytes total)"), - Err(e) => { - error!("✗ blocking_write_vectored failed: {:?}", e); - defmt::panic!("Test failed: blocking_write_vectored"); - } - } -} - -fn test_blocking_large_buffer(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { - // Test with 300 bytes to verify RELOAD mechanism works (needs chunking at 255 bytes) - let mut write_buf = [0u8; 300]; - for (i, byte) in write_buf.iter_mut().enumerate() { - *byte = (i & 0xFF) as u8; - } - - match i2c.blocking_write(addr, &write_buf) { - Ok(_) => info!("✓ Large buffer write succeeded (300 bytes, tests RELOAD)"), - Err(e) => { - error!("✗ Large buffer write failed: {:?}", e); - defmt::panic!("Test failed: large buffer write"); - } - } - - // Test large read - let mut read_buf = [0u8; 300]; - match i2c.blocking_read(addr, &mut read_buf) { - Ok(_) => info!("✓ Large buffer read succeeded (300 bytes, tests RELOAD)"), - Err(e) => { - error!("✗ Large buffer read failed: {:?}", e); - defmt::panic!("Test failed: large buffer read"); - } - } -} - -// ==================== BLOCKING TRANSACTION TEST FUNCTIONS ==================== - -fn test_consecutive_writes_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { - // Expected on bus: START, ADDR+W, data1, data2, data3, STOP - // NO intermediate RESTART/STOP between writes - they should be merged - let data1 = [0x10, 0x11, 0x12]; - let data2 = [0x20, 0x21]; - let data3 = [0x30, 0x31, 0x32, 0x33]; - - let mut ops = [ - Operation::Write(&data1), - Operation::Write(&data2), - Operation::Write(&data3), - ]; - - match i2c.blocking_transaction(addr, &mut ops) { - Ok(_) => info!("✓ Consecutive writes succeeded (merged 9 bytes)"), - Err(e) => { - error!("✗ Consecutive writes failed: {:?}", e); - defmt::panic!("Test failed: consecutive writes"); - } - } -} - -fn test_consecutive_reads_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { - // Expected on bus: START, ADDR+R, data1, data2, data3, NACK, STOP - // NO intermediate RESTART/STOP between reads - they should be merged - let mut buf1 = [0u8; 4]; - let mut buf2 = [0u8; 3]; - let mut buf3 = [0u8; 2]; - - let mut ops = [ - Operation::Read(&mut buf1), - Operation::Read(&mut buf2), - Operation::Read(&mut buf3), - ]; - - match i2c.blocking_transaction(addr, &mut ops) { - Ok(_) => { - info!("✓ Consecutive reads succeeded (merged 9 bytes)"); - info!(" buf1: {:02x}", buf1); - info!(" buf2: {:02x}", buf2); - info!(" buf3: {:02x}", buf3); - } - Err(e) => { - error!("✗ Consecutive reads failed: {:?}", e); - defmt::panic!("Test failed: consecutive reads"); - } - } -} - -fn test_write_then_read_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { - // Expected: START, ADDR+W, data, RESTART, ADDR+R, data, NACK, STOP - let write_data = [0xAA, 0xBB]; - let mut read_buf = [0u8; 4]; - - let mut ops = [Operation::Write(&write_data), Operation::Read(&mut read_buf)]; - - match i2c.blocking_transaction(addr, &mut ops) { - Ok(_) => { - info!("✓ Write-then-read succeeded with RESTART"); - info!(" Written: {:02x}", write_data); - info!(" Read: {:02x}", read_buf); - } - Err(e) => { - error!("✗ Write-then-read failed: {:?}", e); - defmt::panic!("Test failed: write-then-read"); - } - } -} - -fn test_read_then_write_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { - // Expected: START, ADDR+R, data, NACK, RESTART, ADDR+W, data, STOP - let mut read_buf = [0u8; 3]; - let write_data = [0xCC, 0xDD, 0xEE]; - - let mut ops = [Operation::Read(&mut read_buf), Operation::Write(&write_data)]; - - match i2c.blocking_transaction(addr, &mut ops) { - Ok(_) => { - info!("✓ Read-then-write succeeded with RESTART"); - info!(" Read: {:02x}", read_buf); - info!(" Written: {:02x}", write_data); - } - Err(e) => { - error!("✗ Read-then-write failed: {:?}", e); - defmt::panic!("Test failed: read-then-write"); - } - } -} - -fn test_mixed_sequence_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { - // Complex: W, W, R, R, W, R - // Groups: [W,W] RESTART [R,R] RESTART [W] RESTART [R] - let w1 = [0x01, 0x02]; - let w2 = [0x03, 0x04]; - let mut r1 = [0u8; 2]; - let mut r2 = [0u8; 2]; - let w3 = [0x05]; - let mut r3 = [0u8; 1]; - - let mut ops = [ - Operation::Write(&w1), - Operation::Write(&w2), - Operation::Read(&mut r1), - Operation::Read(&mut r2), - Operation::Write(&w3), - Operation::Read(&mut r3), - ]; - - match i2c.blocking_transaction(addr, &mut ops) { - Ok(_) => { - info!("✓ Mixed sequence succeeded"); - info!(" Groups: [W4] RESTART [R4] RESTART [W1] RESTART [R1]"); - } - Err(e) => { - error!("✗ Mixed sequence failed: {:?}", e); - defmt::panic!("Test failed: mixed sequence"); - } - } -} - -fn test_single_operations_blocking(i2c: &mut I2c<'_, Blocking, Master>, addr: u8) { - // Test single write - let write_data = [0xFF]; - let mut ops = [Operation::Write(&write_data)]; - - match i2c.blocking_transaction(addr, &mut ops) { - Ok(_) => info!("✓ Single write succeeded"), - Err(e) => { - error!("✗ Single write failed: {:?}", e); - defmt::panic!("Test failed: single write"); - } - } - - // Test single read - let mut read_buf = [0u8; 1]; - let mut ops = [Operation::Read(&mut read_buf)]; - - match i2c.blocking_transaction(addr, &mut ops) { - Ok(_) => info!("✓ Single read succeeded, data: 0x{:02x}", read_buf[0]), - Err(e) => { - error!("✗ Single read failed: {:?}", e); - defmt::panic!("Test failed: single read"); - } - } -} - -// ==================== ASYNC DIRECT API TEST FUNCTIONS ==================== - -async fn test_async_write(i2c: &mut I2c<'_, Async, Master>, addr: u8) { - let write_data = [0x42, 0x43, 0x44, 0x45]; - - match i2c.write(addr, &write_data).await { - Ok(_) => info!("✓ async write succeeded: {:02x}", write_data), - Err(e) => { - error!("✗ async write failed: {:?}", e); - defmt::panic!("Test failed: async write"); - } - } -} - -async fn test_async_read(i2c: &mut I2c<'_, Async, Master>, addr: u8) { - let mut read_buf = [0u8; 8]; - - match i2c.read(addr, &mut read_buf).await { - Ok(_) => info!("✓ async read succeeded: {:02x}", read_buf), - Err(e) => { - error!("✗ async read failed: {:?}", e); - defmt::panic!("Test failed: async read"); - } - } -} - -async fn test_async_write_read(i2c: &mut I2c<'_, Async, Master>, addr: u8) { - let write_data = [0x50, 0x51]; - let mut read_buf = [0u8; 6]; - - match i2c.write_read(addr, &write_data, &mut read_buf).await { - Ok(_) => { - info!("✓ async write_read succeeded"); - info!(" Written: {:02x}", write_data); - info!(" Read: {:02x}", read_buf); - } - Err(e) => { - error!("✗ async write_read failed: {:?}", e); - defmt::panic!("Test failed: async write_read"); - } - } -} - -async fn test_async_write_vectored(i2c: &mut I2c<'_, Async, Master>, addr: u8) { - let buf1 = [0x60, 0x61, 0x62]; - let buf2 = [0x70, 0x71]; - let buf3 = [0x80, 0x81, 0x82, 0x83]; - let bufs = [&buf1[..], &buf2[..], &buf3[..]]; - - match i2c.write_vectored(addr.into(), &bufs).await { - Ok(_) => info!("✓ async write_vectored succeeded (9 bytes total)"), - Err(e) => { - error!("✗ async write_vectored failed: {:?}", e); - defmt::panic!("Test failed: async write_vectored"); - } - } -} - -async fn test_async_large_buffer(i2c: &mut I2c<'_, Async, Master>, addr: u8) { - // Test with 300 bytes to verify RELOAD mechanism works with DMA (needs chunking at 255 bytes) - let mut write_buf = [0u8; 300]; - for (i, byte) in write_buf.iter_mut().enumerate() { - *byte = (i & 0xFF) as u8; - } - - match i2c.write(addr, &write_buf).await { - Ok(_) => info!("✓ Large buffer async write succeeded (300 bytes, tests RELOAD with DMA)"), - Err(e) => { - error!("✗ Large buffer async write failed: {:?}", e); - defmt::panic!("Test failed: large buffer async write"); - } - } - - // Test large read - let mut read_buf = [0u8; 300]; - match i2c.read(addr, &mut read_buf).await { - Ok(_) => info!("✓ Large buffer async read succeeded (300 bytes, tests RELOAD with DMA)"), - Err(e) => { - error!("✗ Large buffer async read failed: {:?}", e); - defmt::panic!("Test failed: large buffer async read"); - } - } -} - -// ==================== ASYNC TRANSACTION TEST FUNCTIONS ==================== - -async fn test_consecutive_writes_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { - let data1 = [0x10, 0x11, 0x12]; - let data2 = [0x20, 0x21]; - let data3 = [0x30, 0x31, 0x32, 0x33]; - - let mut ops = [ - Operation::Write(&data1), - Operation::Write(&data2), - Operation::Write(&data3), - ]; - - match i2c.transaction(addr, &mut ops).await { - Ok(_) => info!("✓ Consecutive writes succeeded (merged 9 bytes)"), - Err(e) => { - error!("✗ Consecutive writes failed: {:?}", e); - defmt::panic!("Test failed: consecutive writes"); - } - } -} - -async fn test_consecutive_reads_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { - let mut buf1 = [0u8; 4]; - let mut buf2 = [0u8; 3]; - let mut buf3 = [0u8; 2]; - - let mut ops = [ - Operation::Read(&mut buf1), - Operation::Read(&mut buf2), - Operation::Read(&mut buf3), - ]; - - match i2c.transaction(addr, &mut ops).await { - Ok(_) => { - info!("✓ Consecutive reads succeeded (merged 9 bytes)"); - info!(" buf1: {:02x}", buf1); - info!(" buf2: {:02x}", buf2); - info!(" buf3: {:02x}", buf3); - } - Err(e) => { - error!("✗ Consecutive reads failed: {:?}", e); - defmt::panic!("Test failed: consecutive reads"); - } - } -} - -async fn test_write_then_read_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { - let write_data = [0xAA, 0xBB]; - let mut read_buf = [0u8; 4]; - - let mut ops = [Operation::Write(&write_data), Operation::Read(&mut read_buf)]; - - match i2c.transaction(addr, &mut ops).await { - Ok(_) => { - info!("✓ Write-then-read succeeded with RESTART"); - info!(" Written: {:02x}", write_data); - info!(" Read: {:02x}", read_buf); - } - Err(e) => { - error!("✗ Write-then-read failed: {:?}", e); - defmt::panic!("Test failed: write-then-read"); - } - } -} - -async fn test_read_then_write_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { - let mut read_buf = [0u8; 3]; - let write_data = [0xCC, 0xDD, 0xEE]; - - let mut ops = [Operation::Read(&mut read_buf), Operation::Write(&write_data)]; - - match i2c.transaction(addr, &mut ops).await { - Ok(_) => { - info!("✓ Read-then-write succeeded with RESTART"); - info!(" Read: {:02x}", read_buf); - info!(" Written: {:02x}", write_data); - } - Err(e) => { - error!("✗ Read-then-write failed: {:?}", e); - defmt::panic!("Test failed: read-then-write"); - } - } -} - -async fn test_mixed_sequence_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { - let w1 = [0x01, 0x02]; - let w2 = [0x03, 0x04]; - let mut r1 = [0u8; 2]; - let mut r2 = [0u8; 2]; - let w3 = [0x05]; - let mut r3 = [0u8; 1]; - - let mut ops = [ - Operation::Write(&w1), - Operation::Write(&w2), - Operation::Read(&mut r1), - Operation::Read(&mut r2), - Operation::Write(&w3), - Operation::Read(&mut r3), - ]; - - match i2c.transaction(addr, &mut ops).await { - Ok(_) => { - info!("✓ Mixed sequence succeeded"); - info!(" Groups: [W4] RESTART [R4] RESTART [W1] RESTART [R1]"); - } - Err(e) => { - error!("✗ Mixed sequence failed: {:?}", e); - defmt::panic!("Test failed: mixed sequence"); - } - } -} - -async fn test_single_operations_async(i2c: &mut I2c<'_, Async, Master>, addr: u8) { - // Test single write - let write_data = [0xFF]; - let mut ops = [Operation::Write(&write_data)]; - - match i2c.transaction(addr, &mut ops).await { - Ok(_) => info!("✓ Single write succeeded"), - Err(e) => { - error!("✗ Single write failed: {:?}", e); - defmt::panic!("Test failed: single write"); - } - } - - // Test single read - let mut read_buf = [0u8; 1]; - let mut ops = [Operation::Read(&mut read_buf)]; - - match i2c.transaction(addr, &mut ops).await { - Ok(_) => info!("✓ Single read succeeded, data: 0x{:02x}", read_buf[0]), - Err(e) => { - error!("✗ Single read failed: {:?}", e); - defmt::panic!("Test failed: single read"); - } - } -} diff --git a/tests/stm32/src/common.rs b/tests/stm32/src/common.rs index de06cb267..096cce947 100644 --- a/tests/stm32/src/common.rs +++ b/tests/stm32/src/common.rs @@ -60,8 +60,6 @@ teleprobe_meta::target!(b"nucleo-stm32wl55jc"); teleprobe_meta::target!(b"nucleo-stm32wba52cg"); #[cfg(feature = "stm32f091rc")] teleprobe_meta::target!(b"nucleo-stm32f091rc"); -#[cfg(feature = "stm32f072rb")] -teleprobe_meta::target!(b"nucleo-stm32f072rb"); #[cfg(feature = "stm32h503rb")] teleprobe_meta::target!(b"nucleo-stm32h503rb"); #[cfg(feature = "stm32h7s3l8")] @@ -105,14 +103,6 @@ define_peris!( SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH3, SPI_RX_DMA = DMA1_CH2, @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler;}, ); -#[cfg(feature = "stm32f072rb")] -define_peris!( - UART = USART1, UART_TX = PA9, UART_RX = PA10, UART_TX_DMA = DMA1_CH4, UART_RX_DMA = DMA1_CH5, - SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH3, SPI_RX_DMA = DMA1_CH2, - I2C = I2C1, I2C_SCL = PB8, I2C_SDA = PB9, I2C_TX_DMA = DMA1_CH6, I2C_RX_DMA = DMA1_CH7, - @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler;}, - @irq I2C = {I2C1 => embassy_stm32::i2c::EventInterruptHandler, embassy_stm32::i2c::ErrorInterruptHandler;}, -); #[cfg(any(feature = "stm32f100rd", feature = "stm32f103c8", feature = "stm32f107vc"))] define_peris!( UART = USART1, UART_TX = PA9, UART_RX = PA10, UART_TX_DMA = DMA1_CH4, UART_RX_DMA = DMA1_CH5, @@ -335,21 +325,6 @@ pub fn config() -> Config { config.rcc.ahb_pre = AHBPrescaler::DIV1; config.rcc.apb1_pre = APBPrescaler::DIV1; } - #[cfg(feature = "stm32f072rb")] - { - config.rcc.hse = Some(Hse { - freq: Hertz(8_000_000), - mode: HseMode::Bypass, - }); - config.rcc.pll = Some(Pll { - src: PllSource::HSE, - prediv: PllPreDiv::DIV1, - mul: PllMul::MUL6, - }); - config.rcc.sys = Sysclk::PLL1_P; - config.rcc.ahb_pre = AHBPrescaler::DIV1; - config.rcc.apb1_pre = APBPrescaler::DIV1; - } #[cfg(feature = "stm32f103c8")] { config.rcc.hse = Some(Hse { -- cgit From de16754a2d340eca49885238e265f50bfc3ec2e5 Mon Sep 17 00:00:00 2001 From: HybridChild Date: Thu, 13 Nov 2025 21:18:12 +0100 Subject: stm32/i2c: Fix async write_vectored and restore DMA implementation --- embassy-stm32/src/i2c/v2.rs | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index 7bcfa00b0..c35f3694c 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -389,7 +389,9 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { loop { let isr = self.info.regs.isr().read(); self.error_occurred(&isr, timeout)?; - if isr.tc() { + // Wait for either TC or TCR - both indicate transfer completion + // TCR occurs when RELOAD=1, TC occurs when RELOAD=0 + if isr.tc() || isr.tcr() { return Ok(()); } timeout.check()?; @@ -918,12 +920,12 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { address, total_len.min(255), Stop::Software, - total_len > 255, + (total_len > 255) || !last_slice, restart, timeout, )?; } else { - Self::reload(self.info, total_len.min(255), total_len > 255, Stop::Software, timeout)?; + Self::reload(self.info, total_len.min(255), (total_len > 255) || !last_slice, Stop::Software, timeout)?; self.info.regs.cr1().modify(|w| w.set_tcie(true)); } } else if !(isr.tcr() || isr.tc()) { @@ -935,7 +937,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { if let Err(e) = Self::reload( self.info, remaining_len.min(255), - remaining_len > 255, + (remaining_len > 255) || !last_slice, Stop::Software, timeout, ) { @@ -1085,10 +1087,35 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { pub async fn write_vectored(&mut self, address: Address, write: &[&[u8]]) -> Result<(), Error> { #[cfg(all(feature = "low-power", stm32wlex))] let _device_busy = crate::low_power::DeviceBusy::new_stop1(); + let timeout = self.timeout(); - // For now, use blocking implementation for write_vectored - // This avoids complexity of handling multiple non-contiguous buffers with DMA - self.blocking_write_vectored((address.addr() & 0xFF) as u8, write) + if write.is_empty() { + return Err(Error::ZeroLengthTransfer); + } + + let mut iter = write.iter(); + let mut first = true; + let mut current = iter.next(); + + while let Some(c) = current { + let next = iter.next(); + let is_last = next.is_none(); + + let fut = self.write_dma_internal( + address, + c, + first, // first_slice + is_last, // last_slice + is_last, // send_stop (only on last buffer) + false, // restart (false for all - they're one continuous write) + timeout, + ); + timeout.with(fut).await?; + + first = false; + current = next; + } + Ok(()) } /// Read. -- cgit From 42f38d5b3209134baa8bf424cdb754e1901ac0da Mon Sep 17 00:00:00 2001 From: HybridChild Date: Thu, 13 Nov 2025 21:23:13 +0100 Subject: stm32/i2c: Implement async DMA for transaction write groups --- embassy-stm32/src/i2c/v2.rs | 50 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index c35f3694c..8f51627ff 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -1222,9 +1222,53 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { is_last_group: bool, timeout: Timeout, ) -> Result<(), Error> { - // For now, use blocking implementation for write groups - // This avoids complexity of handling multiple non-contiguous buffers with DMA - self.execute_write_group(address, operations, is_first_group, is_last_group, timeout) + // Calculate total bytes across all operations in this group + let total_bytes = Self::total_operation_bytes(operations); + + if total_bytes == 0 { + // Handle empty write group using blocking call + if is_first_group { + Self::master_write(self.info, address, 0, Stop::Software, false, !is_first_group, timeout)?; + } + if is_last_group { + self.master_stop(); + } + return Ok(()); + } + + // Collect all write buffers + let mut write_buffers: heapless::Vec<&[u8], 16> = heapless::Vec::new(); + for operation in operations { + if let Operation::Write(buffer) = operation { + if !buffer.is_empty() { + let _ = write_buffers.push(buffer); + } + } + } + + if write_buffers.is_empty() { + return Ok(()); + } + + // Send each buffer using DMA + let num_buffers = write_buffers.len(); + for (idx, buffer) in write_buffers.iter().enumerate() { + let is_first_buffer = idx == 0; + let is_last_buffer = idx == num_buffers - 1; + + let fut = self.write_dma_internal( + address, + buffer, + is_first_buffer, // first_slice + is_last_buffer, // last_slice + is_last_buffer && is_last_group, // send_stop + is_first_buffer && !is_first_group, // restart (only for first buffer if not first group) + timeout, + ); + timeout.with(fut).await?; + } + + Ok(()) } async fn execute_read_group_async( -- cgit From 82dcaf118100c883a95397e3f9867a4043a4de7b Mon Sep 17 00:00:00 2001 From: HybridChild Date: Thu, 13 Nov 2025 21:24:09 +0100 Subject: stm32: Run cargo fmt --- embassy-stm32/src/i2c/v2.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index 8f51627ff..61e550ad4 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -925,7 +925,13 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { timeout, )?; } else { - Self::reload(self.info, total_len.min(255), (total_len > 255) || !last_slice, Stop::Software, timeout)?; + Self::reload( + self.info, + total_len.min(255), + (total_len > 255) || !last_slice, + Stop::Software, + timeout, + )?; self.info.regs.cr1().modify(|w| w.set_tcie(true)); } } else if !(isr.tcr() || isr.tc()) { @@ -1102,12 +1108,10 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { let is_last = next.is_none(); let fut = self.write_dma_internal( - address, - c, - first, // first_slice - is_last, // last_slice - is_last, // send_stop (only on last buffer) - false, // restart (false for all - they're one continuous write) + address, c, first, // first_slice + is_last, // last_slice + is_last, // send_stop (only on last buffer) + false, // restart (false for all - they're one continuous write) timeout, ); timeout.with(fut).await?; -- cgit