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 d4e2caab31c1e9802120141cebc0a00b04471c16 Mon Sep 17 00:00:00 2001 From: Valentin Trophime Date: Wed, 12 Nov 2025 13:19:42 +0100 Subject: Add docs for embassy-rp::pio::get_x assumption about autopush. --- embassy-rp/CHANGELOG.md | 2 ++ embassy-rp/src/pio/instr.rs | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/embassy-rp/CHANGELOG.md b/embassy-rp/CHANGELOG.md index 3b3cb5351..4b0d738a7 100644 --- a/embassy-rp/CHANGELOG.md +++ b/embassy-rp/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate +- Add documentation for pio `get_x` about autopush. - Fix several minor typos in documentation - Add PIO SPI - Add PIO I2S input @@ -114,3 +115,4 @@ Small release fixing a few gnarly bugs, upgrading is strongly recommended. - rename the Channel trait to Slice and the PwmPin to PwmChannel - i2c: Fix race condition that appears on fast repeated transfers. - Add a basic "read to break" function + diff --git a/embassy-rp/src/pio/instr.rs b/embassy-rp/src/pio/instr.rs index b15d507de..304ddb20a 100644 --- a/embassy-rp/src/pio/instr.rs +++ b/embassy-rp/src/pio/instr.rs @@ -5,6 +5,10 @@ use crate::pio::{Instance, StateMachine}; impl<'d, PIO: Instance, const SM: usize> StateMachine<'d, PIO, SM> { /// Set value of scratch register X. + /// + /// SAFETY: autopull enabled else it will write undefined data. + /// Make sure to have setup the according configuration see + /// [shift_out](crate::pio::Config::shift_out) and [auto_fill](crate::pio::ShiftConfig::auto_fill). pub unsafe fn set_x(&mut self, value: u32) { const OUT: u16 = InstructionOperands::OUT { destination: OutDestination::X, @@ -16,6 +20,10 @@ impl<'d, PIO: Instance, const SM: usize> StateMachine<'d, PIO, SM> { } /// Get value of scratch register X. + /// + /// SAFETY: autopush enabled else it will read undefined data. + /// Make sure to have setup the according configurations see + /// [shift_in](crate::pio::Config::shift_in) and [auto_fill](crate::pio::ShiftConfig::auto_fill). pub unsafe fn get_x(&mut self) -> u32 { const IN: u16 = InstructionOperands::IN { source: InSource::X, @@ -27,6 +35,10 @@ impl<'d, PIO: Instance, const SM: usize> StateMachine<'d, PIO, SM> { } /// Set value of scratch register Y. + /// + /// SAFETY: autopull enabled else it will write undefined data. + /// Make sure to have setup the according configuration see + /// [shift_out](crate::pio::Config::shift_out) and [auto_fill](crate::pio::ShiftConfig::auto_fill). pub unsafe fn set_y(&mut self, value: u32) { const OUT: u16 = InstructionOperands::OUT { destination: OutDestination::Y, @@ -38,6 +50,10 @@ impl<'d, PIO: Instance, const SM: usize> StateMachine<'d, PIO, SM> { } /// Get value of scratch register Y. + /// + /// SAFETY: autopush enabled else it will read undefined data. + /// Make sure to have setup the according configurations see + /// [shift_in](crate::pio::Config::shift_in) and [auto_fill](crate::pio::ShiftConfig::auto_fill). pub unsafe fn get_y(&mut self) -> u32 { const IN: u16 = InstructionOperands::IN { source: InSource::Y, -- 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 4efd9fccf4259779d96c5d1a4829a90bda1a5def Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Wed, 12 Nov 2025 20:48:03 +0100 Subject: Fix flash erase on dualbank STM32Gxxx --- embassy-stm32/CHANGELOG.md | 1 + embassy-stm32/src/flash/g.rs | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index 3431848d3..b418faee6 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate +- fix: flash erase on dual-bank STM32Gxxx - feat: Add support for STM32N657X0 - feat: timer: Add 32-bit timer support to SimplePwm waveform_up method following waveform pattern ([#4717](https://github.com/embassy-rs/embassy/pull/4717)) - feat: Add support for injected ADC measurements for g4 ([#4840](https://github.com/embassy-rs/embassy/pull/4840)) diff --git a/embassy-stm32/src/flash/g.rs b/embassy-stm32/src/flash/g.rs index d026541a4..d7ba2f571 100644 --- a/embassy-stm32/src/flash/g.rs +++ b/embassy-stm32/src/flash/g.rs @@ -44,7 +44,6 @@ pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) } pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { - let idx = (sector.start - super::FLASH_BASE as u32) / super::BANK1_REGION.erase_size as u32; wait_busy(); clear_all_err(); @@ -54,9 +53,9 @@ pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), E #[cfg(any(flash_g0x0, flash_g0x1, flash_g4c3))] w.set_bker(sector.bank == crate::flash::FlashBank::Bank2); #[cfg(flash_g0x0)] - w.set_pnb(idx as u16); + w.set_pnb(sector.index_in_bank as u16); #[cfg(not(flash_g0x0))] - w.set_pnb(idx as u8); + w.set_pnb(sector.index_in_bank as u8); w.set_strt(true); }); }); -- 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 dccf185e1489c0055fcacdea59ce7837cc4d076d Mon Sep 17 00:00:00 2001 From: "Andreas Lindahl Flåten (ALF)" Date: Wed, 5 Nov 2025 16:47:09 +0100 Subject: Add c.rs flash for the stm32c0 family This is basically a copy of the `g.rs` file, with multi bank support removed (c0 is single bank only). --- embassy-stm32/src/flash/c.rs | 122 ++++++++++++++++++++++++++++++++++++++ embassy-stm32/src/flash/common.rs | 2 +- embassy-stm32/src/flash/mod.rs | 3 +- 3 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 embassy-stm32/src/flash/c.rs diff --git a/embassy-stm32/src/flash/c.rs b/embassy-stm32/src/flash/c.rs new file mode 100644 index 000000000..af3d07ac6 --- /dev/null +++ b/embassy-stm32/src/flash/c.rs @@ -0,0 +1,122 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{Ordering, fence}; + +use cortex_m::interrupt; + +use super::{FlashSector, WRITE_SIZE}; +use crate::flash::Error; +use crate::pac; + +pub(crate) unsafe fn lock() { + pac::FLASH.cr().modify(|w| w.set_lock(true)); +} +pub(crate) unsafe fn unlock() { + // Wait, while the memory interface is busy. + wait_busy(); + + // Unlock flash + if pac::FLASH.cr().read().lock() { + pac::FLASH.keyr().write_value(0x4567_0123); + pac::FLASH.keyr().write_value(0xCDEF_89AB); + } +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); + pac::FLASH.cr().write(|w| w.set_pg(true)); +} + +pub(crate) unsafe fn disable_blocking_write() { + pac::FLASH.cr().write(|w| w.set_pg(false)); +} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + let mut address = start_address; + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(unwrap!(val.try_into()))); + address += val.len() as u32; + + // prevents parallelism errors + fence(Ordering::SeqCst); + } + + wait_ready_blocking() +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + let idx = (sector.start - super::FLASH_BASE as u32) / super::BANK1_REGION.erase_size as u32; + + #[cfg(feature = "defmt")] + defmt::trace!("STM32C0 Erase: addr=0x{:08x}, idx={}, erase_size={}", sector.start, idx, super::BANK1_REGION.erase_size); + + + wait_busy(); + clear_all_err(); + + // Explicitly unlock before erase + unlock(); + + interrupt::free(|_| { + #[cfg(feature = "defmt")] + { + let cr_before = pac::FLASH.cr().read(); + defmt::trace!("FLASH_CR before: 0x{:08x}", cr_before.0); + } + + pac::FLASH.cr().modify(|w| { + w.set_per(true); + w.set_pnb(idx as u8); + w.set_strt(true); + }); + + #[cfg(feature = "defmt")] + { + let cr_after = pac::FLASH.cr().read(); + defmt::trace!("FLASH_CR after: 0x{:08x}, PER={}, PNB={}, STRT={}", + cr_after.0, cr_after.per(), cr_after.pnb(), cr_after.strt()); + } + }); + + let ret: Result<(), Error> = wait_ready_blocking(); + + // Clear erase bit + pac::FLASH.cr().modify(|w| w.set_per(false)); + + // Explicitly lock after erase + lock(); + + // Extra wait to ensure operation completes + wait_busy(); + + ret +} + +pub(crate) unsafe fn wait_ready_blocking() -> Result<(), Error> { + while pac::FLASH.sr().read().bsy() {} + + let sr = pac::FLASH.sr().read(); + + if sr.progerr() { + return Err(Error::Prog); + } + + if sr.wrperr() { + return Err(Error::Protected); + } + + if sr.pgaerr() { + return Err(Error::Unaligned); + } + + Ok(()) +} + +pub(crate) unsafe fn clear_all_err() { + // read and write back the same value. + // This clears all "write 1 to clear" bits. + pac::FLASH.sr().modify(|_| {}); +} + +fn wait_busy() { + while pac::FLASH.sr().read().bsy() {} +} diff --git a/embassy-stm32/src/flash/common.rs b/embassy-stm32/src/flash/common.rs index b595938a6..508bb2548 100644 --- a/embassy-stm32/src/flash/common.rs +++ b/embassy-stm32/src/flash/common.rs @@ -102,7 +102,7 @@ pub(super) unsafe fn blocking_write( } let mut address = base + offset; - trace!("Writing {} bytes at 0x{:x}", bytes.len(), address); + trace!("Writing {} bytes at 0x{:x} (base=0x{:x}, offset=0x{:x})", bytes.len(), address, base, offset); for chunk in bytes.chunks(WRITE_SIZE) { write_chunk(address, chunk)?; diff --git a/embassy-stm32/src/flash/mod.rs b/embassy-stm32/src/flash/mod.rs index 3e74d857a..39cd9b3a9 100644 --- a/embassy-stm32/src/flash/mod.rs +++ b/embassy-stm32/src/flash/mod.rs @@ -99,6 +99,7 @@ compile_error!("The 'eeprom' cfg is enabled for a non-L0/L1 chip family. This is #[cfg_attr(flash_f4, path = "f4.rs")] #[cfg_attr(flash_f7, path = "f7.rs")] #[cfg_attr(any(flash_g0x0, flash_g0x1, flash_g4c2, flash_g4c3, flash_g4c4), path = "g.rs")] +#[cfg_attr(flash_c0, path = "c.rs")] #[cfg_attr(flash_h7, path = "h7.rs")] #[cfg_attr(flash_h7ab, path = "h7.rs")] #[cfg_attr(any(flash_u5, flash_wba), path = "u5.rs")] @@ -108,7 +109,7 @@ compile_error!("The 'eeprom' cfg is enabled for a non-L0/L1 chip family. This is #[cfg_attr( not(any( flash_l0, flash_l1, flash_l4, flash_l5, flash_wl, flash_wb, flash_f0, flash_f1, flash_f2, flash_f3, flash_f4, - flash_f7, flash_g0x0, flash_g0x1, flash_g4c2, flash_g4c3, flash_g4c4, flash_h7, flash_h7ab, flash_u5, + flash_f7, flash_g0x0, flash_g0x1, flash_g4c2, flash_g4c3, flash_g4c4, flash_c0, flash_h7, flash_h7ab, flash_u5, flash_wba, flash_h50, flash_u0, flash_h5, )), path = "other.rs" -- cgit From f72349660eb30f6fc32104db60c33a732a99f6b5 Mon Sep 17 00:00:00 2001 From: "Andreas Lindahl Flåten (ALF)" Date: Thu, 13 Nov 2025 11:24:43 +0100 Subject: add changelog and fix rustfmt errors --- embassy-stm32/CHANGELOG.md | 1 + embassy-stm32/src/flash/c.rs | 17 +++++++++++++---- embassy-stm32/src/flash/common.rs | 8 +++++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index 3431848d3..7586861ef 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -55,6 +55,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - chore: Updated stm32-metapac and stm32-data dependencies - adc: reogranize and cleanup somewhat. require sample_time to be passed on conversion - fix: stm32/i2c v2 slave: prevent misaligned reads, error false positives, and incorrect counts of bytes read/written +- feat: add flash support for c0 family ([#4874](https://github.com/embassy-rs/embassy/pull/4874)) ## 0.4.0 - 2025-08-26 diff --git a/embassy-stm32/src/flash/c.rs b/embassy-stm32/src/flash/c.rs index af3d07ac6..0ad1002b0 100644 --- a/embassy-stm32/src/flash/c.rs +++ b/embassy-stm32/src/flash/c.rs @@ -47,8 +47,12 @@ pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), E let idx = (sector.start - super::FLASH_BASE as u32) / super::BANK1_REGION.erase_size as u32; #[cfg(feature = "defmt")] - defmt::trace!("STM32C0 Erase: addr=0x{:08x}, idx={}, erase_size={}", sector.start, idx, super::BANK1_REGION.erase_size); - + defmt::trace!( + "STM32C0 Erase: addr=0x{:08x}, idx={}, erase_size={}", + sector.start, + idx, + super::BANK1_REGION.erase_size + ); wait_busy(); clear_all_err(); @@ -72,8 +76,13 @@ pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), E #[cfg(feature = "defmt")] { let cr_after = pac::FLASH.cr().read(); - defmt::trace!("FLASH_CR after: 0x{:08x}, PER={}, PNB={}, STRT={}", - cr_after.0, cr_after.per(), cr_after.pnb(), cr_after.strt()); + defmt::trace!( + "FLASH_CR after: 0x{:08x}, PER={}, PNB={}, STRT={}", + cr_after.0, + cr_after.per(), + cr_after.pnb(), + cr_after.strt() + ); } }); diff --git a/embassy-stm32/src/flash/common.rs b/embassy-stm32/src/flash/common.rs index 508bb2548..60d00e766 100644 --- a/embassy-stm32/src/flash/common.rs +++ b/embassy-stm32/src/flash/common.rs @@ -102,7 +102,13 @@ pub(super) unsafe fn blocking_write( } let mut address = base + offset; - trace!("Writing {} bytes at 0x{:x} (base=0x{:x}, offset=0x{:x})", bytes.len(), address, base, offset); + trace!( + "Writing {} bytes at 0x{:x} (base=0x{:x}, offset=0x{:x})", + bytes.len(), + address, + base, + offset + ); for chunk in bytes.chunks(WRITE_SIZE) { write_chunk(address, chunk)?; -- cgit From c17d24d0cc8fedbe69b22032ea7323997ddfe4dc Mon Sep 17 00:00:00 2001 From: xoviat Date: Thu, 13 Nov 2025 07:38:06 -0600 Subject: extract averaging enum --- embassy-stm32/src/adc/c0.rs | 19 ------------------- embassy-stm32/src/adc/mod.rs | 20 ++++++++++++++++++++ embassy-stm32/src/adc/v3.rs | 17 +---------------- embassy-stm32/src/adc/v4.rs | 19 +------------------ 4 files changed, 22 insertions(+), 53 deletions(-) diff --git a/embassy-stm32/src/adc/c0.rs b/embassy-stm32/src/adc/c0.rs index bc97a7c4b..8992d6e6e 100644 --- a/embassy-stm32/src/adc/c0.rs +++ b/embassy-stm32/src/adc/c0.rs @@ -119,25 +119,6 @@ impl<'a> defmt::Format for Prescaler { } } -/// Number of samples used for averaging. -/// TODO: Implement hardware averaging setting. -#[allow(unused)] -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Averaging { - Disabled, - Samples2, - Samples4, - Samples8, - Samples16, - Samples32, - Samples64, - Samples128, - Samples256, - Samples512, - Samples1024, -} - impl<'d, T: Instance> Adc<'d, T> { /// Create a new ADC driver. pub fn new(adc: Peri<'d, T>, resolution: Resolution) -> Self { diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs index 856c2e61e..c91d68e87 100644 --- a/embassy-stm32/src/adc/mod.rs +++ b/embassy-stm32/src/adc/mod.rs @@ -148,6 +148,26 @@ pub(crate) fn blocking_delay_us(us: u32) { } } +#[cfg(any(adc_c0, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5))] +/// Number of samples used for averaging. +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Averaging { + Disabled, + Samples2, + Samples4, + Samples8, + Samples16, + Samples32, + Samples64, + Samples128, + Samples256, + #[cfg(any(adc_c0, adc_v4, adc_u5))] + Samples512, + #[cfg(any(adc_c0, adc_v4, adc_u5))] + Samples1024, +} + #[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5, adc_wba))] #[allow(dead_code)] pub(crate) enum ConversionMode { diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index 4cce1dac3..fa191c663 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -11,7 +11,7 @@ pub use pac::adc::vals::{Ovsr, Ovss, Presc}; #[allow(unused_imports)] use super::SealedAdcChannel; -use super::{Adc, Instance, Resolution, SampleTime, Temperature, Vbat, VrefInt, blocking_delay_us}; +use super::{Adc, Averaging, Instance, Resolution, SampleTime, Temperature, Vbat, VrefInt, blocking_delay_us}; use crate::adc::ConversionMode; use crate::{Peri, pac, rcc}; @@ -100,21 +100,6 @@ cfg_if! { } } -/// Number of samples used for averaging. -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Averaging { - Disabled, - Samples2, - Samples4, - Samples8, - Samples16, - Samples32, - Samples64, - Samples128, - Samples256, -} - cfg_if! { if #[cfg(adc_g0)] { /// Synchronous PCLK prescaler diff --git a/embassy-stm32/src/adc/v4.rs b/embassy-stm32/src/adc/v4.rs index 43eb16fd5..1b17b744f 100644 --- a/embassy-stm32/src/adc/v4.rs +++ b/embassy-stm32/src/adc/v4.rs @@ -4,7 +4,7 @@ use pac::adc::vals::{Adcaldif, Boost}; use pac::adc::vals::{Adstp, Difsel, Dmngt, Exten, Pcsel}; use pac::adccommon::vals::Presc; -use super::{Adc, Instance, Resolution, SampleTime, Temperature, Vbat, VrefInt, blocking_delay_us}; +use super::{Adc, Averaging, Instance, Resolution, SampleTime, Temperature, Vbat, VrefInt, blocking_delay_us}; use crate::adc::ConversionMode; use crate::time::Hertz; use crate::{Peri, pac, rcc}; @@ -127,23 +127,6 @@ impl Prescaler { } } -/// Number of samples used for averaging. -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Averaging { - Disabled, - Samples2, - Samples4, - Samples8, - Samples16, - Samples32, - Samples64, - Samples128, - Samples256, - Samples512, - Samples1024, -} - /// Adc configuration #[derive(Default)] pub struct AdcConfig { -- 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 de582f7d5ed9d4903fb5f07d7816f67a907c1d47 Mon Sep 17 00:00:00 2001 From: xoviat Date: Thu, 13 Nov 2025 08:51:14 -0600 Subject: adc: extract prescalers --- embassy-stm32/build.rs | 124 ++++++++++++++++++++++++------------------ embassy-stm32/src/adc/adc4.rs | 82 +++++----------------------- embassy-stm32/src/adc/g4.rs | 82 +++++----------------------- embassy-stm32/src/adc/v4.rs | 82 +++++----------------------- 4 files changed, 113 insertions(+), 257 deletions(-) diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index ad6743f96..67041a9e0 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -1695,70 +1695,88 @@ fn main() { } // ======== - // Generate Div/Mul impls for RCC prescalers/dividers/multipliers. - for e in rcc_registers.ir.enums { - fn is_rcc_name(e: &str) -> bool { - match e { - "Pllp" | "Pllq" | "Pllr" | "Plldivst" | "Pllm" | "Plln" | "Prediv1" | "Prediv2" | "Hpre5" => true, - "Timpre" | "Pllrclkpre" => false, - e if e.ends_with("pre") || e.ends_with("pres") || e.ends_with("div") || e.ends_with("mul") => true, - _ => false, + // Generate Div/Mul impls for RCC and ADC prescalers/dividers/multipliers. + for (kind, psc_enums) in ["rcc", "adc", "adccommon"].iter().filter_map(|kind| { + METADATA + .peripherals + .iter() + .filter_map(|p| p.registers.as_ref()) + .find(|r| r.kind == *kind) + .map(|r| (*kind, r.ir.enums)) + }) { + for e in psc_enums.iter() { + fn is_adc_name(e: &str) -> bool { + match e { + "Presc" | "Adc4Presc" => true, + _ => false, + } } - } - fn parse_num(n: &str) -> Result { - for prefix in ["DIV", "MUL"] { - if let Some(n) = n.strip_prefix(prefix) { - let exponent = n.find('_').map(|e| n.len() - 1 - e).unwrap_or(0) as u32; - let mantissa = n.replace('_', "").parse().map_err(|_| ())?; - let f = Frac { - num: mantissa, - denom: 10u32.pow(exponent), - }; - return Ok(f.simplify()); + fn is_rcc_name(e: &str) -> bool { + match e { + "Pllp" | "Pllq" | "Pllr" | "Plldivst" | "Pllm" | "Plln" | "Prediv1" | "Prediv2" | "Hpre5" => true, + "Timpre" | "Pllrclkpre" => false, + e if e.ends_with("pre") || e.ends_with("pres") || e.ends_with("div") || e.ends_with("mul") => true, + _ => false, } } - Err(()) - } - if is_rcc_name(e.name) { - let enum_name = format_ident!("{}", e.name); - let mut muls = Vec::new(); - let mut divs = Vec::new(); - for v in e.variants { - let Ok(val) = parse_num(v.name) else { - panic!("could not parse mul/div. enum={} variant={}", e.name, v.name) - }; - let variant_name = format_ident!("{}", v.name); - let variant = quote!(crate::pac::rcc::vals::#enum_name::#variant_name); - let num = val.num; - let denom = val.denom; - muls.push(quote!(#variant => self * #num / #denom,)); - divs.push(quote!(#variant => self * #denom / #num,)); + fn parse_num(n: &str) -> Result { + for prefix in ["DIV", "MUL"] { + if let Some(n) = n.strip_prefix(prefix) { + let exponent = n.find('_').map(|e| n.len() - 1 - e).unwrap_or(0) as u32; + let mantissa = n.replace('_', "").parse().map_err(|_| ())?; + let f = Frac { + num: mantissa, + denom: 10u32.pow(exponent), + }; + return Ok(f.simplify()); + } + } + Err(()) } - g.extend(quote! { - impl core::ops::Div for crate::time::Hertz { - type Output = crate::time::Hertz; - fn div(self, rhs: crate::pac::rcc::vals::#enum_name) -> Self::Output { - match rhs { - #(#divs)* - #[allow(unreachable_patterns)] - _ => unreachable!(), + if (kind == "rcc" && is_rcc_name(e.name)) || ((kind == "adccommon" || kind == "adc") && is_adc_name(e.name)) + { + let kind = format_ident!("{}", kind); + let enum_name = format_ident!("{}", e.name); + let mut muls = Vec::new(); + let mut divs = Vec::new(); + for v in e.variants { + let Ok(val) = parse_num(v.name) else { + panic!("could not parse mul/div. enum={} variant={}", e.name, v.name) + }; + let variant_name = format_ident!("{}", v.name); + let variant = quote!(crate::pac::#kind::vals::#enum_name::#variant_name); + let num = val.num; + let denom = val.denom; + muls.push(quote!(#variant => self * #num / #denom,)); + divs.push(quote!(#variant => self * #denom / #num,)); + } + + g.extend(quote! { + impl core::ops::Div for crate::time::Hertz { + type Output = crate::time::Hertz; + fn div(self, rhs: crate::pac::#kind::vals::#enum_name) -> Self::Output { + match rhs { + #(#divs)* + #[allow(unreachable_patterns)] + _ => unreachable!(), + } } } - } - impl core::ops::Mul for crate::time::Hertz { - type Output = crate::time::Hertz; - fn mul(self, rhs: crate::pac::rcc::vals::#enum_name) -> Self::Output { - match rhs { - #(#muls)* - #[allow(unreachable_patterns)] - _ => unreachable!(), + impl core::ops::Mul for crate::time::Hertz { + type Output = crate::time::Hertz; + fn mul(self, rhs: crate::pac::#kind::vals::#enum_name) -> Self::Output { + match rhs { + #(#muls)* + #[allow(unreachable_patterns)] + _ => unreachable!(), + } } } - } - }); + }); + } } } diff --git a/embassy-stm32/src/adc/adc4.rs b/embassy-stm32/src/adc/adc4.rs index 52678d1b6..d816eea57 100644 --- a/embassy-stm32/src/adc/adc4.rs +++ b/embassy-stm32/src/adc/adc4.rs @@ -76,71 +76,17 @@ pub const fn resolution_to_max_count(res: Resolution) -> u32 { } } -// NOTE (unused): The prescaler enum closely copies the hardware capabilities, -// but high prescaling doesn't make a lot of sense in the current implementation and is ommited. -#[allow(unused)] -enum Prescaler { - NotDivided, - DividedBy2, - DividedBy4, - DividedBy6, - DividedBy8, - DividedBy10, - DividedBy12, - DividedBy16, - DividedBy32, - DividedBy64, - DividedBy128, - DividedBy256, -} - -impl Prescaler { - fn from_ker_ck(frequency: Hertz) -> Self { - let raw_prescaler = frequency.0 / MAX_ADC_CLK_FREQ.0; - match raw_prescaler { - 0 => Self::NotDivided, - 1 => Self::DividedBy2, - 2..=3 => Self::DividedBy4, - 4..=5 => Self::DividedBy6, - 6..=7 => Self::DividedBy8, - 8..=9 => Self::DividedBy10, - 10..=11 => Self::DividedBy12, - _ => unimplemented!(), - } - } - - fn divisor(&self) -> u32 { - match self { - Prescaler::NotDivided => 1, - Prescaler::DividedBy2 => 2, - Prescaler::DividedBy4 => 4, - Prescaler::DividedBy6 => 6, - Prescaler::DividedBy8 => 8, - Prescaler::DividedBy10 => 10, - Prescaler::DividedBy12 => 12, - Prescaler::DividedBy16 => 16, - Prescaler::DividedBy32 => 32, - Prescaler::DividedBy64 => 64, - Prescaler::DividedBy128 => 128, - Prescaler::DividedBy256 => 256, - } - } - - fn presc(&self) -> Presc { - match self { - Prescaler::NotDivided => Presc::DIV1, - Prescaler::DividedBy2 => Presc::DIV2, - Prescaler::DividedBy4 => Presc::DIV4, - Prescaler::DividedBy6 => Presc::DIV6, - Prescaler::DividedBy8 => Presc::DIV8, - Prescaler::DividedBy10 => Presc::DIV10, - Prescaler::DividedBy12 => Presc::DIV12, - Prescaler::DividedBy16 => Presc::DIV16, - Prescaler::DividedBy32 => Presc::DIV32, - Prescaler::DividedBy64 => Presc::DIV64, - Prescaler::DividedBy128 => Presc::DIV128, - Prescaler::DividedBy256 => Presc::DIV256, - } +fn from_ker_ck(frequency: Hertz) -> Presc { + let raw_prescaler = frequency.0 / MAX_ADC_CLK_FREQ.0; + match raw_prescaler { + 0 => Presc::DIV1, + 1 => Presc::DIV2, + 2..=3 => Presc::DIV4, + 4..=5 => Presc::DIV6, + 6..=7 => Presc::DIV8, + 8..=9 => Presc::DIV10, + 10..=11 => Presc::DIV12, + _ => unimplemented!(), } } @@ -283,11 +229,11 @@ impl<'d, T: Instance + super::AnyInstance> super::Adc<'d, T> { /// Create a new ADC driver. pub fn new_adc4(adc: Peri<'d, T>) -> Self { rcc::enable_and_reset::(); - let prescaler = Prescaler::from_ker_ck(T::frequency()); + let prescaler = from_ker_ck(T::frequency()); - T::regs().ccr().modify(|w| w.set_presc(prescaler.presc())); + T::regs().ccr().modify(|w| w.set_presc(prescaler)); - let frequency = Hertz(T::frequency().0 / prescaler.divisor()); + let frequency = T::frequency() / prescaler; info!("ADC4 frequency set to {}", frequency); if frequency > MAX_ADC_CLK_FREQ { diff --git a/embassy-stm32/src/adc/g4.rs b/embassy-stm32/src/adc/g4.rs index 71dc8acc0..514734017 100644 --- a/embassy-stm32/src/adc/g4.rs +++ b/embassy-stm32/src/adc/g4.rs @@ -32,71 +32,17 @@ const MAX_ADC_CLK_FREQ: Hertz = Hertz::mhz(60); #[cfg(stm32h7)] const MAX_ADC_CLK_FREQ: Hertz = Hertz::mhz(50); -// NOTE (unused): The prescaler enum closely copies the hardware capabilities, -// but high prescaling doesn't make a lot of sense in the current implementation and is ommited. -#[allow(unused)] -enum Prescaler { - NotDivided, - DividedBy2, - DividedBy4, - DividedBy6, - DividedBy8, - DividedBy10, - DividedBy12, - DividedBy16, - DividedBy32, - DividedBy64, - DividedBy128, - DividedBy256, -} - -impl Prescaler { - fn from_ker_ck(frequency: Hertz) -> Self { - let raw_prescaler = frequency.0 / MAX_ADC_CLK_FREQ.0; - match raw_prescaler { - 0 => Self::NotDivided, - 1 => Self::DividedBy2, - 2..=3 => Self::DividedBy4, - 4..=5 => Self::DividedBy6, - 6..=7 => Self::DividedBy8, - 8..=9 => Self::DividedBy10, - 10..=11 => Self::DividedBy12, - _ => unimplemented!(), - } - } - - fn divisor(&self) -> u32 { - match self { - Prescaler::NotDivided => 1, - Prescaler::DividedBy2 => 2, - Prescaler::DividedBy4 => 4, - Prescaler::DividedBy6 => 6, - Prescaler::DividedBy8 => 8, - Prescaler::DividedBy10 => 10, - Prescaler::DividedBy12 => 12, - Prescaler::DividedBy16 => 16, - Prescaler::DividedBy32 => 32, - Prescaler::DividedBy64 => 64, - Prescaler::DividedBy128 => 128, - Prescaler::DividedBy256 => 256, - } - } - - fn presc(&self) -> Presc { - match self { - Prescaler::NotDivided => Presc::DIV1, - Prescaler::DividedBy2 => Presc::DIV2, - Prescaler::DividedBy4 => Presc::DIV4, - Prescaler::DividedBy6 => Presc::DIV6, - Prescaler::DividedBy8 => Presc::DIV8, - Prescaler::DividedBy10 => Presc::DIV10, - Prescaler::DividedBy12 => Presc::DIV12, - Prescaler::DividedBy16 => Presc::DIV16, - Prescaler::DividedBy32 => Presc::DIV32, - Prescaler::DividedBy64 => Presc::DIV64, - Prescaler::DividedBy128 => Presc::DIV128, - Prescaler::DividedBy256 => Presc::DIV256, - } +fn from_ker_ck(frequency: Hertz) -> Presc { + let raw_prescaler = frequency.0 / MAX_ADC_CLK_FREQ.0; + match raw_prescaler { + 0 => Presc::DIV1, + 1 => Presc::DIV2, + 2..=3 => Presc::DIV4, + 4..=5 => Presc::DIV6, + 6..=7 => Presc::DIV8, + 8..=9 => Presc::DIV10, + 10..=11 => Presc::DIV12, + _ => unimplemented!(), } } @@ -292,11 +238,11 @@ impl<'d, T: Instance + AnyInstance> Adc<'d, T> { pub fn new(adc: Peri<'d, T>, config: AdcConfig) -> Self { rcc::enable_and_reset::(); - let prescaler = Prescaler::from_ker_ck(T::frequency()); + let prescaler = from_ker_ck(T::frequency()); - T::common_regs().ccr().modify(|w| w.set_presc(prescaler.presc())); + T::common_regs().ccr().modify(|w| w.set_presc(prescaler)); - let frequency = Hertz(T::frequency().0 / prescaler.divisor()); + let frequency = T::frequency() / prescaler; trace!("ADC frequency set to {}", frequency); if frequency > MAX_ADC_CLK_FREQ { diff --git a/embassy-stm32/src/adc/v4.rs b/embassy-stm32/src/adc/v4.rs index 1b17b744f..5c4a1975f 100644 --- a/embassy-stm32/src/adc/v4.rs +++ b/embassy-stm32/src/adc/v4.rs @@ -59,71 +59,17 @@ impl super::SealedSpecialConverter for T { const CHANNEL: u8 = 18; } -// NOTE (unused): The prescaler enum closely copies the hardware capabilities, -// but high prescaling doesn't make a lot of sense in the current implementation and is ommited. -#[allow(unused)] -enum Prescaler { - NotDivided, - DividedBy2, - DividedBy4, - DividedBy6, - DividedBy8, - DividedBy10, - DividedBy12, - DividedBy16, - DividedBy32, - DividedBy64, - DividedBy128, - DividedBy256, -} - -impl Prescaler { - fn from_ker_ck(frequency: Hertz) -> Self { - let raw_prescaler = frequency.0 / MAX_ADC_CLK_FREQ.0; - match raw_prescaler { - 0 => Self::NotDivided, - 1 => Self::DividedBy2, - 2..=3 => Self::DividedBy4, - 4..=5 => Self::DividedBy6, - 6..=7 => Self::DividedBy8, - 8..=9 => Self::DividedBy10, - 10..=11 => Self::DividedBy12, - _ => unimplemented!(), - } - } - - fn divisor(&self) -> u32 { - match self { - Prescaler::NotDivided => 1, - Prescaler::DividedBy2 => 2, - Prescaler::DividedBy4 => 4, - Prescaler::DividedBy6 => 6, - Prescaler::DividedBy8 => 8, - Prescaler::DividedBy10 => 10, - Prescaler::DividedBy12 => 12, - Prescaler::DividedBy16 => 16, - Prescaler::DividedBy32 => 32, - Prescaler::DividedBy64 => 64, - Prescaler::DividedBy128 => 128, - Prescaler::DividedBy256 => 256, - } - } - - fn presc(&self) -> Presc { - match self { - Prescaler::NotDivided => Presc::DIV1, - Prescaler::DividedBy2 => Presc::DIV2, - Prescaler::DividedBy4 => Presc::DIV4, - Prescaler::DividedBy6 => Presc::DIV6, - Prescaler::DividedBy8 => Presc::DIV8, - Prescaler::DividedBy10 => Presc::DIV10, - Prescaler::DividedBy12 => Presc::DIV12, - Prescaler::DividedBy16 => Presc::DIV16, - Prescaler::DividedBy32 => Presc::DIV32, - Prescaler::DividedBy64 => Presc::DIV64, - Prescaler::DividedBy128 => Presc::DIV128, - Prescaler::DividedBy256 => Presc::DIV256, - } +fn from_ker_ck(frequency: Hertz) -> Presc { + let raw_prescaler = frequency.0 / MAX_ADC_CLK_FREQ.0; + match raw_prescaler { + 0 => Presc::DIV1, + 1 => Presc::DIV2, + 2..=3 => Presc::DIV4, + 4..=5 => Presc::DIV6, + 6..=7 => Presc::DIV8, + 8..=9 => Presc::DIV10, + 10..=11 => Presc::DIV12, + _ => unimplemented!(), } } @@ -292,11 +238,11 @@ impl<'d, T: Instance + super::AnyInstance> Adc<'d, T> { pub fn new(adc: Peri<'d, T>) -> Self { rcc::enable_and_reset::(); - let prescaler = Prescaler::from_ker_ck(T::frequency()); + let prescaler = from_ker_ck(T::frequency()); - T::common_regs().ccr().modify(|w| w.set_presc(prescaler.presc())); + T::common_regs().ccr().modify(|w| w.set_presc(prescaler)); - let frequency = Hertz(T::frequency().0 / prescaler.divisor()); + let frequency = T::frequency() / prescaler; info!("ADC frequency set to {}", frequency); if frequency > MAX_ADC_CLK_FREQ { -- cgit From 1a3da1c582b4a9fec1af561e2f4a7ed0352aff33 Mon Sep 17 00:00:00 2001 From: xoviat Date: Thu, 13 Nov 2025 09:33:27 -0600 Subject: adc: extract v2 psc. --- embassy-stm32/build.rs | 2 +- embassy-stm32/src/adc/v2.rs | 51 +++++++++++++++------------------------------ 2 files changed, 18 insertions(+), 35 deletions(-) diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 67041a9e0..1e11eb8dc 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -1707,7 +1707,7 @@ fn main() { for e in psc_enums.iter() { fn is_adc_name(e: &str) -> bool { match e { - "Presc" | "Adc4Presc" => true, + "Presc" | "Adc4Presc" | "Adcpre" => true, _ => false, } } diff --git a/embassy-stm32/src/adc/v2.rs b/embassy-stm32/src/adc/v2.rs index 4065f89a7..07eaebf7c 100644 --- a/embassy-stm32/src/adc/v2.rs +++ b/embassy-stm32/src/adc/v2.rs @@ -3,6 +3,7 @@ use core::sync::atomic::{Ordering, compiler_fence}; use super::{ConversionMode, Temperature, Vbat, VrefInt, blocking_delay_us}; use crate::adc::{Adc, Instance, Resolution, SampleTime}; use crate::pac::adc::vals; +pub use crate::pac::adccommon::vals::Adcpre; use crate::time::Hertz; use crate::{Peri, rcc}; @@ -50,38 +51,20 @@ impl Temperature { } } -enum Prescaler { - Div2, - Div4, - Div6, - Div8, -} - -impl Prescaler { - fn from_pclk2(freq: Hertz) -> Self { - // Datasheet for F2 specifies min frequency 0.6 MHz, and max 30 MHz (with VDDA 2.4-3.6V). - #[cfg(stm32f2)] - const MAX_FREQUENCY: Hertz = Hertz(30_000_000); - // Datasheet for both F4 and F7 specifies min frequency 0.6 MHz, typ freq. 30 MHz and max 36 MHz. - #[cfg(not(stm32f2))] - const MAX_FREQUENCY: Hertz = Hertz(36_000_000); - let raw_div = freq.0 / MAX_FREQUENCY.0; - match raw_div { - 0..=1 => Self::Div2, - 2..=3 => Self::Div4, - 4..=5 => Self::Div6, - 6..=7 => Self::Div8, - _ => panic!("Selected PCLK2 frequency is too high for ADC with largest possible prescaler."), - } - } - - fn adcpre(&self) -> crate::pac::adccommon::vals::Adcpre { - match self { - Prescaler::Div2 => crate::pac::adccommon::vals::Adcpre::DIV2, - Prescaler::Div4 => crate::pac::adccommon::vals::Adcpre::DIV4, - Prescaler::Div6 => crate::pac::adccommon::vals::Adcpre::DIV6, - Prescaler::Div8 => crate::pac::adccommon::vals::Adcpre::DIV8, - } +fn from_pclk2(freq: Hertz) -> Adcpre { + // Datasheet for F2 specifies min frequency 0.6 MHz, and max 30 MHz (with VDDA 2.4-3.6V). + #[cfg(stm32f2)] + const MAX_FREQUENCY: Hertz = Hertz(30_000_000); + // Datasheet for both F4 and F7 specifies min frequency 0.6 MHz, typ freq. 30 MHz and max 36 MHz. + #[cfg(not(stm32f2))] + const MAX_FREQUENCY: Hertz = Hertz(36_000_000); + let raw_div = freq.0 / MAX_FREQUENCY.0; + match raw_div { + 0..=1 => Adcpre::DIV2, + 2..=3 => Adcpre::DIV4, + 4..=5 => Adcpre::DIV6, + 6..=7 => Adcpre::DIV8, + _ => panic!("Selected PCLK2 frequency is too high for ADC with largest possible prescaler."), } } @@ -224,8 +207,8 @@ where pub fn new_with_config(adc: Peri<'d, T>, config: AdcConfig) -> Self { rcc::enable_and_reset::(); - let presc = Prescaler::from_pclk2(T::frequency()); - T::common_regs().ccr().modify(|w| w.set_adcpre(presc.adcpre())); + let presc = from_pclk2(T::frequency()); + T::common_regs().ccr().modify(|w| w.set_adcpre(presc)); T::regs().cr2().modify(|reg| { reg.set_adon(true); }); -- cgit From a8de2ccb8c0721284281715ce6eda28271db3950 Mon Sep 17 00:00:00 2001 From: xoviat Date: Thu, 13 Nov 2025 10:26:56 -0600 Subject: remove allow dead_code --- embassy-stm32/src/adc/adc4.rs | 1 + embassy-stm32/src/adc/mod.rs | 14 +++++++------- embassy-stm32/src/adc/v3.rs | 1 + embassy-stm32/src/adc/v4.rs | 1 + 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/embassy-stm32/src/adc/adc4.rs b/embassy-stm32/src/adc/adc4.rs index d816eea57..babdebfdb 100644 --- a/embassy-stm32/src/adc/adc4.rs +++ b/embassy-stm32/src/adc/adc4.rs @@ -158,6 +158,7 @@ foreach_adc!( reg.set_chselrmod(Chselrmod::ENABLE_INPUT) }); } + #[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_u0))] _ => unreachable!(), } } diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs index c91d68e87..fd5b94224 100644 --- a/embassy-stm32/src/adc/mod.rs +++ b/embassy-stm32/src/adc/mod.rs @@ -88,19 +88,17 @@ pub(crate) trait SealedAdcChannel { } // Temporary patch for ADCs that have not implemented the standard iface yet -#[cfg(not(any(adc_v2, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5, adc_wba, adc_g4)))] +#[cfg(any(adc_v1, adc_l0, adc_f1, adc_f3v1, adc_f3v2, adc_f3v3, adc_v1, adc_c0))] trait_set::trait_set! { pub trait AnyInstance = Instance; } #[cfg(any(adc_v2, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5, adc_wba, adc_g4))] -#[allow(dead_code)] pub trait BasicAnyInstance { type SampleTime; } #[cfg(any(adc_v2, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5, adc_wba, adc_g4))] -#[allow(dead_code)] pub(self) trait SealedAnyInstance: BasicAnyInstance { fn enable(); fn start(); @@ -108,6 +106,7 @@ pub(self) trait SealedAnyInstance: BasicAnyInstance { fn convert() -> u16; fn configure_dma(conversion_mode: ConversionMode); fn configure_sequence(sequence: impl ExactSizeIterator); + #[allow(dead_code)] fn dr() -> *mut u16; } @@ -169,17 +168,18 @@ pub enum Averaging { } #[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5, adc_wba))] -#[allow(dead_code)] pub(crate) enum ConversionMode { + // Should match the cfg on "read" below #[cfg(any(adc_g4, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5, adc_wba))] Singular, - #[allow(dead_code)] + // Should match the cfg on "into_ring_buffered" below + #[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_u0))] Repeated(RegularConversionMode), } -#[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5, adc_wba))] +// Should match the cfg on "into_ring_buffered" below +#[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_u0))] // Conversion mode for regular ADC channels -#[allow(dead_code)] #[derive(Copy, Clone)] pub enum RegularConversionMode { // Samples as fast as possible diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index fa191c663..78b497727 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -259,6 +259,7 @@ impl super::SealedAnyInstance for T { reg.set_cont(true); reg.set_dmacfg(match conversion_mode { ConversionMode::Singular => Dmacfg::ONE_SHOT, + #[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_u0))] ConversionMode::Repeated(_) => Dmacfg::CIRCULAR, }); reg.set_dmaen(true); diff --git a/embassy-stm32/src/adc/v4.rs b/embassy-stm32/src/adc/v4.rs index 5c4a1975f..804e63db6 100644 --- a/embassy-stm32/src/adc/v4.rs +++ b/embassy-stm32/src/adc/v4.rs @@ -143,6 +143,7 @@ impl super::SealedAnyInstance for T { reg.set_dmngt(Dmngt::DMA_ONE_SHOT); }); } + #[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_u0))] _ => unreachable!(), } } -- cgit From 32408f4a031dff11c1c3c8c4aeb2044f1a7e8f42 Mon Sep 17 00:00:00 2001 From: xoviat Date: Thu, 13 Nov 2025 12:02:38 -0600 Subject: adc: extract c0 --- embassy-stm32/src/adc/c0.rs | 488 ++++++++++++++-------------------------- embassy-stm32/src/adc/mod.rs | 53 ++++- examples/stm32c0/src/bin/adc.rs | 18 +- 3 files changed, 214 insertions(+), 345 deletions(-) diff --git a/embassy-stm32/src/adc/c0.rs b/embassy-stm32/src/adc/c0.rs index 8992d6e6e..983e7c10d 100644 --- a/embassy-stm32/src/adc/c0.rs +++ b/embassy-stm32/src/adc/c0.rs @@ -1,12 +1,10 @@ -use pac::adc::vals::Scandir; #[allow(unused)] use pac::adc::vals::{Adstp, Align, Ckmode, Dmacfg, Exten, Ovrmod, Ovsr}; use pac::adccommon::vals::Presc; +use stm32_metapac::adc::vals::Scandir; -use super::{ - Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, blocking_delay_us, -}; -use crate::dma::Transfer; +use super::{Adc, Instance, Resolution, blocking_delay_us}; +use crate::adc::{AnyInstance, ConversionMode}; use crate::time::Hertz; use crate::{Peri, pac, rcc}; @@ -32,104 +30,184 @@ impl super::SealedSpecialConverter for T { const CHANNEL: u8 = 9; } -#[derive(Copy, Clone, Debug)] -pub enum Prescaler { - NotDivided, - DividedBy2, - DividedBy4, - DividedBy6, - DividedBy8, - DividedBy10, - DividedBy12, - DividedBy16, - DividedBy32, - DividedBy64, - DividedBy128, - DividedBy256, +fn from_ker_ck(frequency: Hertz) -> Presc { + let raw_prescaler = frequency.0 / MAX_ADC_CLK_FREQ.0; + match raw_prescaler { + 0 => Presc::DIV1, + 1 => Presc::DIV2, + 2..=3 => Presc::DIV4, + 4..=5 => Presc::DIV6, + 6..=7 => Presc::DIV8, + 8..=9 => Presc::DIV10, + 10..=11 => Presc::DIV12, + _ => unimplemented!(), + } } -impl Prescaler { - fn from_ker_ck(frequency: Hertz) -> Self { - let raw_prescaler = frequency.0 / MAX_ADC_CLK_FREQ.0; - match raw_prescaler { - 0 => Self::NotDivided, - 1 => Self::DividedBy2, - 2..=3 => Self::DividedBy4, - 4..=5 => Self::DividedBy6, - 6..=7 => Self::DividedBy8, - 8..=9 => Self::DividedBy10, - 10..=11 => Self::DividedBy12, - _ => unimplemented!(), - } +impl super::SealedAnyInstance for T { + fn dr() -> *mut u16 { + T::regs().dr().as_ptr() as *mut u16 } - #[allow(unused)] - fn divisor(&self) -> u32 { - match self { - Prescaler::NotDivided => 1, - Prescaler::DividedBy2 => 2, - Prescaler::DividedBy4 => 4, - Prescaler::DividedBy6 => 6, - Prescaler::DividedBy8 => 8, - Prescaler::DividedBy10 => 10, - Prescaler::DividedBy12 => 12, - Prescaler::DividedBy16 => 16, - Prescaler::DividedBy32 => 32, - Prescaler::DividedBy64 => 64, - Prescaler::DividedBy128 => 128, - Prescaler::DividedBy256 => 256, + fn enable() { + T::regs().isr().modify(|w| w.set_adrdy(true)); + T::regs().cr().modify(|w| w.set_aden(true)); + // ADRDY is "ADC ready". Wait until it will be True. + while !T::regs().isr().read().adrdy() {} + } + + fn start() { + // Start conversion + T::regs().cr().modify(|reg| { + reg.set_adstart(true); + }); + } + + fn stop() { + if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() { + T::regs().cr().modify(|reg| { + reg.set_adstp(Adstp::STOP); + }); + while T::regs().cr().read().adstart() {} } + + // Reset configuration. + T::regs().cfgr1().modify(|reg| { + reg.set_cont(false); + reg.set_dmacfg(Dmacfg::from_bits(0)); + reg.set_dmaen(false); + }); } - fn presc(&self) -> Presc { - match self { - Prescaler::NotDivided => Presc::DIV1, - Prescaler::DividedBy2 => Presc::DIV2, - Prescaler::DividedBy4 => Presc::DIV4, - Prescaler::DividedBy6 => Presc::DIV6, - Prescaler::DividedBy8 => Presc::DIV8, - Prescaler::DividedBy10 => Presc::DIV10, - Prescaler::DividedBy12 => Presc::DIV12, - Prescaler::DividedBy16 => Presc::DIV16, - Prescaler::DividedBy32 => Presc::DIV32, - Prescaler::DividedBy64 => Presc::DIV64, - Prescaler::DividedBy128 => Presc::DIV128, - Prescaler::DividedBy256 => Presc::DIV256, + fn configure_dma(conversion_mode: super::ConversionMode) { + match conversion_mode { + ConversionMode::Singular => { + // Enable overrun control, so no new DMA requests will be generated until + // previous DR values is read. + T::regs().isr().modify(|reg| { + reg.set_ovr(true); + }); + + // Set continuous mode with oneshot dma. + T::regs().cfgr1().modify(|reg| { + reg.set_discen(false); + reg.set_cont(true); + reg.set_dmacfg(Dmacfg::DMA_ONE_SHOT); + reg.set_dmaen(true); + reg.set_ovrmod(Ovrmod::PRESERVE); + }); + } } } -} -#[cfg(feature = "defmt")] -impl<'a> defmt::Format for Prescaler { - fn format(&self, fmt: defmt::Formatter) { - match self { - Prescaler::NotDivided => defmt::write!(fmt, "Prescaler::NotDivided"), - Prescaler::DividedBy2 => defmt::write!(fmt, "Prescaler::DividedBy2"), - Prescaler::DividedBy4 => defmt::write!(fmt, "Prescaler::DividedBy4"), - Prescaler::DividedBy6 => defmt::write!(fmt, "Prescaler::DividedBy6"), - Prescaler::DividedBy8 => defmt::write!(fmt, "Prescaler::DividedBy8"), - Prescaler::DividedBy10 => defmt::write!(fmt, "Prescaler::DividedBy10"), - Prescaler::DividedBy12 => defmt::write!(fmt, "Prescaler::DividedBy12"), - Prescaler::DividedBy16 => defmt::write!(fmt, "Prescaler::DividedBy16"), - Prescaler::DividedBy32 => defmt::write!(fmt, "Prescaler::DividedBy32"), - Prescaler::DividedBy64 => defmt::write!(fmt, "Prescaler::DividedBy64"), - Prescaler::DividedBy128 => defmt::write!(fmt, "Prescaler::DividedBy128"), - Prescaler::DividedBy256 => defmt::write!(fmt, "Prescaler::DividedBy256"), + fn configure_sequence(mut sequence: impl ExactSizeIterator, blocking: bool) { + T::regs().cfgr1().modify(|reg| { + reg.set_chselrmod(!blocking); + reg.set_align(Align::RIGHT); + }); + + assert!(!blocking || sequence.len() == 1, "Sequence len must be 1 for blocking."); + if blocking { + let ((ch, _), sample_time) = sequence.next().unwrap(); + // Set all channels to use SMP1 field as source. + T::regs().smpr().modify(|w| { + w.smpsel(0); + w.set_smp1(sample_time); + }); + + // write() because we want all other bits to be set to 0. + T::regs().chselr().write(|w| w.set_chsel(ch.into(), true)); + } else { + let mut hw_channel_selection: u32 = 0; + let mut is_ordered_up = true; + let mut is_ordered_down = true; + let mut needs_hw = false; + + assert!( + sequence.len() <= CHSELR_SQ_SIZE, + "Sequence read set cannot be more than {} in size.", + CHSELR_SQ_SIZE + ); + let mut last_sq_set: usize = 0; + let mut last_channel: u8 = 0; + T::regs().chselr_sq().write(|w| { + for (i, ((channel, _), _sample_time)) in sequence.enumerate() { + needs_hw = needs_hw || channel > CHSELR_SQ_MAX_CHANNEL; + last_sq_set = i; + is_ordered_up = is_ordered_up && channel > last_channel; + is_ordered_down = is_ordered_down && channel < last_channel; + hw_channel_selection += 1 << channel; + last_channel = channel; + + if !needs_hw { + w.set_sq(i, channel); + } + } + + assert!( + !needs_hw || is_ordered_up || is_ordered_down, + "Sequencer is required because of unordered channels, but only support HW channels smaller than {}.", + CHSELR_SQ_MAX_CHANNEL + ); + + if needs_hw { + assert!( + hw_channel_selection != 0, + "Some bits in `hw_channel_selection` shall be set." + ); + assert!( + (hw_channel_selection >> NUM_HW_CHANNELS) == 0, + "STM32C0 only have {} ADC channels. `hw_channel_selection` cannot have bits higher than this number set.", + NUM_HW_CHANNELS + ); + + T::regs().cfgr1().modify(|reg| { + reg.set_chselrmod(false); + reg.set_scandir(if is_ordered_up { Scandir::UP} else { Scandir::BACK }); + }); + + // Set required channels for multi-convert. + unsafe { (T::regs().chselr().as_ptr() as *mut u32).write_volatile(hw_channel_selection) } + } else { + for i in (last_sq_set + 1)..CHSELR_SQ_SIZE { + w.set_sq(i, CHSELR_SQ_SEQUENCE_END_MARKER); + } + } + }); } + + // Trigger and wait for the channel selection procedure to complete. + T::regs().isr().modify(|w| w.set_ccrdy(false)); + while !T::regs().isr().read().ccrdy() {} + } + + fn convert() -> u16 { + // Set single conversion mode. + T::regs().cfgr1().modify(|w| w.set_cont(false)); + + // Start conversion + T::regs().cr().modify(|reg| { + reg.set_adstart(true); + }); + + // Waiting for End Of Conversion (EOC). + while !T::regs().isr().read().eoc() {} + + T::regs().dr().read().data() as u16 } } -impl<'d, T: Instance> Adc<'d, T> { +impl<'d, T: AnyInstance> Adc<'d, T> { /// Create a new ADC driver. pub fn new(adc: Peri<'d, T>, resolution: Resolution) -> Self { rcc::enable_and_reset::(); T::regs().cfgr2().modify(|w| w.set_ckmode(Ckmode::SYSCLK)); - let prescaler = Prescaler::from_ker_ck(T::frequency()); - T::common_regs().ccr().modify(|w| w.set_presc(prescaler.presc())); + let prescaler = from_ker_ck(T::frequency()); + T::common_regs().ccr().modify(|w| w.set_presc(prescaler)); - let frequency = Hertz(T::frequency().0 / prescaler.divisor()); + let frequency = T::frequency() / prescaler; debug!("ADC frequency set to {}", frequency); if frequency > MAX_ADC_CLK_FREQ { @@ -139,22 +217,6 @@ impl<'d, T: Instance> Adc<'d, T> { ); } - let mut s = Self { adc }; - - s.power_up(); - - s.set_resolution(resolution); - - s.calibrate(); - - s.enable(); - - s.configure_default(); - - s - } - - fn power_up(&mut self) { T::regs().cr().modify(|reg| { reg.set_advregen(true); }); @@ -162,9 +224,9 @@ impl<'d, T: Instance> Adc<'d, T> { // "The software must wait for the ADC voltage regulator startup time." // See datasheet for the value. blocking_delay_us(TIME_ADC_VOLTAGE_REGUALTOR_STARTUP_US + 1); - } - fn calibrate(&mut self) { + T::regs().cfgr1().modify(|reg| reg.set_res(resolution)); + // We have to make sure AUTOFF is OFF, but keep its value after calibration. let autoff_value = T::regs().cfgr1().read().autoff(); T::regs().cfgr1().modify(|w| w.set_autoff(false)); @@ -178,22 +240,17 @@ impl<'d, T: Instance> Adc<'d, T> { debug!("ADC calibration value: {}.", T::regs().dr().read().data()); T::regs().cfgr1().modify(|w| w.set_autoff(autoff_value)); - } - fn enable(&mut self) { - T::regs().isr().modify(|w| w.set_adrdy(true)); - T::regs().cr().modify(|w| w.set_aden(true)); - // ADRDY is "ADC ready". Wait until it will be True. - while !T::regs().isr().read().adrdy() {} - } + T::enable(); - fn configure_default(&mut self) { // single conversion mode, software trigger T::regs().cfgr1().modify(|w| { w.set_cont(false); w.set_exten(Exten::DISABLED); w.set_align(Align::RIGHT); }); + + Self { adc } } /// Enable reading the voltage reference internal channel. @@ -214,219 +271,4 @@ impl<'d, T: Instance> Adc<'d, T> { super::Temperature {} } - - /// Set the ADC sample time. - /// Shall only be called when ADC is not converting. - pub fn set_sample_time_all_channels(&mut self, sample_time: SampleTime) { - // Set all channels to use SMP1 field as source. - T::regs().smpr().modify(|w| { - w.smpsel(0); - w.set_smp1(sample_time); - }); - } - - /// Set the ADC resolution. - pub fn set_resolution(&mut self, resolution: Resolution) { - T::regs().cfgr1().modify(|reg| reg.set_res(resolution)); - } - - /// Perform a single conversion. - fn convert(&mut self) -> u16 { - // Set single conversion mode. - T::regs().cfgr1().modify(|w| w.set_cont(false)); - - // Start conversion - T::regs().cr().modify(|reg| { - reg.set_adstart(true); - }); - - // Waiting for End Of Conversion (EOC). - while !T::regs().isr().read().eoc() {} - - T::regs().dr().read().data() as u16 - } - - pub fn blocking_read(&mut self, channel: &mut impl AdcChannel, sample_time: SampleTime) -> u16 { - self.set_sample_time_all_channels(sample_time); - - Self::configure_channel(channel); - T::regs().cfgr1().write(|reg| { - reg.set_chselrmod(false); - reg.set_align(Align::RIGHT); - }); - self.convert() - } - - fn setup_channel_sequencer<'a>(channel_sequence: impl ExactSizeIterator>) { - assert!( - channel_sequence.len() <= CHSELR_SQ_SIZE, - "Seqenced read set cannot be more than {} in size.", - CHSELR_SQ_SIZE - ); - let mut last_sq_set: usize = 0; - T::regs().chselr_sq().write(|w| { - for (i, channel) in channel_sequence.enumerate() { - assert!( - channel.channel() <= CHSELR_SQ_MAX_CHANNEL, - "Sequencer only support HW channels smaller than {}.", - CHSELR_SQ_MAX_CHANNEL - ); - w.set_sq(i, channel.channel()); - last_sq_set = i; - } - - for i in (last_sq_set + 1)..CHSELR_SQ_SIZE { - w.set_sq(i, CHSELR_SQ_SEQUENCE_END_MARKER); - } - }); - - Self::apply_channel_conf() - } - - async fn dma_convert(&mut self, rx_dma: Peri<'_, impl RxDma>, readings: &mut [u16]) { - // Enable overrun control, so no new DMA requests will be generated until - // previous DR values is read. - T::regs().isr().modify(|reg| { - reg.set_ovr(true); - }); - - // Set continuous mode with oneshot dma. - T::regs().cfgr1().modify(|reg| { - reg.set_discen(false); - reg.set_cont(true); - reg.set_dmacfg(Dmacfg::DMA_ONE_SHOT); - reg.set_dmaen(true); - reg.set_ovrmod(Ovrmod::PRESERVE); - }); - - let request = rx_dma.request(); - let transfer = unsafe { - Transfer::new_read( - rx_dma, - request, - T::regs().dr().as_ptr() as *mut u16, - readings, - Default::default(), - ) - }; - - // Start conversion. - T::regs().cr().modify(|reg| { - reg.set_adstart(true); - }); - - // Wait for conversion sequence to finish. - transfer.await; - - // Ensure conversions are finished. - Self::cancel_conversions(); - - // Reset configuration. - T::regs().cfgr1().modify(|reg| { - reg.set_cont(false); - reg.set_dmacfg(Dmacfg::from_bits(0)); - reg.set_dmaen(false); - }); - } - - /// Read one or multiple ADC channels using DMA in hardware order. - /// Readings will be ordered based on **hardware** ADC channel number and `scandir` setting. - /// Readings won't be in the same order as in the `set`! - /// - /// In STM32C0, channels bigger than 14 cannot be read using sequencer, so you have to use - /// either blocking reads or use the mechanism to read in HW order (CHSELRMOD=0). - /// TODO(chudsaviet): externalize generic code and merge with read(). - pub async fn read_in_hw_order( - &mut self, - rx_dma: Peri<'_, impl RxDma>, - hw_channel_selection: u32, - scandir: Scandir, - readings: &mut [u16], - ) { - assert!( - hw_channel_selection != 0, - "Some bits in `hw_channel_selection` shall be set." - ); - assert!( - (hw_channel_selection >> NUM_HW_CHANNELS) == 0, - "STM32C0 only have {} ADC channels. `hw_channel_selection` cannot have bits higher than this number set.", - NUM_HW_CHANNELS - ); - // To check for correct readings slice size, we shall solve Hamming weight problem, - // which is either slow or memory consuming. - // Since we have limited resources, we don't do it here. - // Not doing this have a great potential for a bug through. - - // Ensure no conversions are ongoing. - Self::cancel_conversions(); - - T::regs().cfgr1().modify(|reg| { - reg.set_chselrmod(false); - reg.set_scandir(scandir); - reg.set_align(Align::RIGHT); - }); - - // Set required channels for multi-convert. - unsafe { (T::regs().chselr().as_ptr() as *mut u32).write_volatile(hw_channel_selection) } - - Self::apply_channel_conf(); - - self.dma_convert(rx_dma, readings).await - } - - // Read ADC channels in specified order using DMA (CHSELRMOD = 1). - // In STM32C0, only lower 14 ADC channels can be read this way. - // For other channels, use `read_in_hw_order()` or blocking read. - pub async fn read( - &mut self, - rx_dma: Peri<'_, impl RxDma>, - channel_sequence: impl ExactSizeIterator>, - readings: &mut [u16], - ) { - assert!( - channel_sequence.len() != 0, - "Asynchronous read channel sequence cannot be empty." - ); - assert!( - channel_sequence.len() == readings.len(), - "Channel sequence length must be equal to readings length." - ); - - // Ensure no conversions are ongoing. - Self::cancel_conversions(); - - T::regs().cfgr1().modify(|reg| { - reg.set_chselrmod(true); - reg.set_align(Align::RIGHT); - }); - - Self::setup_channel_sequencer(channel_sequence); - - self.dma_convert(rx_dma, readings).await - } - - fn configure_channel(channel: &mut impl AdcChannel) { - channel.setup(); - // write() because we want all other bits to be set to 0. - T::regs() - .chselr() - .write(|w| w.set_chsel(channel.channel().into(), true)); - - Self::apply_channel_conf(); - } - - fn apply_channel_conf() { - // Trigger and wait for the channel selection procedure to complete. - T::regs().isr().modify(|w| w.set_ccrdy(false)); - while !T::regs().isr().read().ccrdy() {} - } - - fn cancel_conversions() { - if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() { - T::regs().cr().modify(|reg| { - reg.set_adstp(Adstp::STOP); - }); - while T::regs().cr().read().adstart() {} - } - } } diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs index fd5b94224..549f2f5a5 100644 --- a/embassy-stm32/src/adc/mod.rs +++ b/embassy-stm32/src/adc/mod.rs @@ -88,30 +88,37 @@ pub(crate) trait SealedAdcChannel { } // Temporary patch for ADCs that have not implemented the standard iface yet -#[cfg(any(adc_v1, adc_l0, adc_f1, adc_f3v1, adc_f3v2, adc_f3v3, adc_v1, adc_c0))] +#[cfg(any(adc_v1, adc_l0, adc_f1, adc_f3v1, adc_f3v2, adc_f3v3, adc_v1))] trait_set::trait_set! { pub trait AnyInstance = Instance; } -#[cfg(any(adc_v2, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5, adc_wba, adc_g4))] +#[cfg(any( + adc_v2, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5, adc_wba, adc_g4, adc_c0 +))] pub trait BasicAnyInstance { type SampleTime; } -#[cfg(any(adc_v2, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5, adc_wba, adc_g4))] +#[cfg(any( + adc_v2, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5, adc_wba, adc_g4, adc_c0 +))] pub(self) trait SealedAnyInstance: BasicAnyInstance { fn enable(); fn start(); fn stop(); fn convert() -> u16; fn configure_dma(conversion_mode: ConversionMode); + #[cfg(not(adc_c0))] fn configure_sequence(sequence: impl ExactSizeIterator); + #[cfg(adc_c0)] + fn configure_sequence(sequence: impl ExactSizeIterator, blocking: bool); #[allow(dead_code)] fn dr() -> *mut u16; } // On chips without ADC4, AnyInstance is an Instance -#[cfg(any(adc_v2, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_g4))] +#[cfg(any(adc_v2, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_g4, adc_c0))] #[allow(private_bounds)] pub trait AnyInstance: SealedAnyInstance + Instance {} @@ -121,12 +128,16 @@ pub trait AnyInstance: SealedAnyInstance + Instance {} pub trait AnyInstance: SealedAnyInstance + crate::PeripheralType + crate::rcc::RccPeripheral {} // Implement AnyInstance automatically for SealedAnyInstance -#[cfg(any(adc_v2, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5, adc_wba, adc_g4))] +#[cfg(any( + adc_v2, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5, adc_wba, adc_g4, adc_c0 +))] impl BasicAnyInstance for T { type SampleTime = SampleTime; } -#[cfg(any(adc_v2, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5, adc_wba, adc_g4))] +#[cfg(any( + adc_v2, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5, adc_wba, adc_g4, adc_c0 +))] impl AnyInstance for T {} /// Performs a busy-wait delay for a specified number of microseconds. @@ -167,10 +178,12 @@ pub enum Averaging { Samples1024, } -#[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5, adc_wba))] +#[cfg(any( + adc_v2, adc_g4, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5, adc_wba, adc_c0 +))] pub(crate) enum ConversionMode { // Should match the cfg on "read" below - #[cfg(any(adc_g4, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5, adc_wba))] + #[cfg(any(adc_g4, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5, adc_wba, adc_c0))] Singular, // Should match the cfg on "into_ring_buffered" below #[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_u0))] @@ -190,7 +203,9 @@ pub enum RegularConversionMode { } impl<'d, T: AnyInstance> Adc<'d, T> { - #[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_u5, adc_v4, adc_wba))] + #[cfg(any( + adc_v2, adc_g4, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_u5, adc_v4, adc_wba, adc_c0 + ))] /// Read an ADC pin. pub fn blocking_read(&mut self, channel: &mut impl AdcChannel, sample_time: T::SampleTime) -> u16 { #[cfg(any(adc_v1, adc_c0, adc_l0, adc_v2, adc_g4, adc_v4, adc_u5, adc_wba))] @@ -198,12 +213,18 @@ impl<'d, T: AnyInstance> Adc<'d, T> { #[cfg(not(adc_v4))] T::enable(); + #[cfg(not(adc_c0))] T::configure_sequence([((channel.channel(), channel.is_differential()), sample_time)].into_iter()); + #[cfg(adc_c0)] + T::configure_sequence( + [((channel.channel(), channel.is_differential()), sample_time)].into_iter(), + true, + ); T::convert() } - #[cfg(any(adc_g4, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5, adc_wba))] + #[cfg(any(adc_g4, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5, adc_wba, adc_c0))] /// Read one or multiple ADC regular channels using DMA. /// /// `sequence` iterator and `readings` must have the same length. @@ -232,6 +253,11 @@ impl<'d, T: AnyInstance> Adc<'d, T> { /// /// Note: This is not very efficient as the ADC needs to be reconfigured for each read. Use /// `into_ring_buffered`, `into_ring_buffered_and_injected` + /// + /// In STM32C0, channels bigger than 14 cannot be read using sequencer, so you have to use + /// either blocking reads or use the mechanism to read in HW order (CHSELRMOD=0). + /// + /// In addtion, on STM320, this method will panic if the channels are not passed in order pub async fn read( &mut self, rx_dma: embassy_hal_internal::Peri<'_, impl RxDma>, @@ -252,8 +278,15 @@ impl<'d, T: AnyInstance> Adc<'d, T> { T::stop(); T::enable(); + #[cfg(not(adc_c0))] + T::configure_sequence( + sequence.map(|(channel, sample_time)| ((channel.channel, channel.is_differential), sample_time)), + ); + + #[cfg(adc_c0)] T::configure_sequence( sequence.map(|(channel, sample_time)| ((channel.channel, channel.is_differential), sample_time)), + false, ); T::configure_dma(ConversionMode::Singular); diff --git a/examples/stm32c0/src/bin/adc.rs b/examples/stm32c0/src/bin/adc.rs index b52c9e7f8..ad597b63c 100644 --- a/examples/stm32c0/src/bin/adc.rs +++ b/examples/stm32c0/src/bin/adc.rs @@ -3,7 +3,6 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::adc::vals::Scandir; use embassy_stm32::adc::{Adc, AdcChannel, AnyAdcChannel, Resolution, SampleTime}; use embassy_stm32::peripherals::ADC1; use embassy_time::Timer; @@ -35,8 +34,12 @@ async fn main(_spawner: Spawner) { blocking_vref, blocking_temp, blocing_pin0 ); - let channels_seqence: [&mut AnyAdcChannel; 3] = [&mut vref, &mut temp, &mut pin0]; - adc.read(dma.reborrow(), channels_seqence.into_iter(), &mut read_buffer) + let channels_sequence: [(&mut AnyAdcChannel, SampleTime); 3] = [ + (&mut vref, SampleTime::CYCLES12_5), + (&mut temp, SampleTime::CYCLES12_5), + (&mut pin0, SampleTime::CYCLES12_5), + ]; + adc.read(dma.reborrow(), channels_sequence.into_iter(), &mut read_buffer) .await; // Values are ordered according to hardware ADC channel number! info!( @@ -44,15 +47,6 @@ async fn main(_spawner: Spawner) { read_buffer[0], read_buffer[1], read_buffer[2] ); - let hw_channel_selection: u32 = - (1 << temp.get_hw_channel()) + (1 << vref.get_hw_channel()) + (1 << pin0.get_hw_channel()); - adc.read_in_hw_order(dma.reborrow(), hw_channel_selection, Scandir::UP, &mut read_buffer) - .await; - info!( - "DMA ADC read in hardware order: vref = {}, temp = {}, pin0 = {}.", - read_buffer[2], read_buffer[1], read_buffer[0] - ); - Timer::after_millis(2000).await; } } -- 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 From 64b9c28eca4822a3ba1bd07d20964c2291c01cf5 Mon Sep 17 00:00:00 2001 From: xoviat Date: Thu, 13 Nov 2025 14:42:14 -0600 Subject: stm32: extract block_for_us remove from pub api --- embassy-stm32/src/adc/c0.rs | 2 +- embassy-stm32/src/adc/f1.rs | 4 ++-- embassy-stm32/src/adc/f3.rs | 2 +- embassy-stm32/src/adc/mod.rs | 20 ++------------------ embassy-stm32/src/dsihost.rs | 17 +++++------------ embassy-stm32/src/eth/generic_phy.rs | 14 +------------- embassy-stm32/src/lib.rs | 14 ++++++++++++++ embassy-stm32/src/opamp.rs | 13 +++---------- examples/stm32f469/src/bin/dsi_bsp.rs | 14 +++++++------- 9 files changed, 36 insertions(+), 64 deletions(-) diff --git a/embassy-stm32/src/adc/c0.rs b/embassy-stm32/src/adc/c0.rs index 983e7c10d..3bdca7edb 100644 --- a/embassy-stm32/src/adc/c0.rs +++ b/embassy-stm32/src/adc/c0.rs @@ -223,7 +223,7 @@ impl<'d, T: AnyInstance> Adc<'d, T> { // "The software must wait for the ADC voltage regulator startup time." // See datasheet for the value. - blocking_delay_us(TIME_ADC_VOLTAGE_REGUALTOR_STARTUP_US + 1); + blocking_delay_us(TIME_ADC_VOLTAGE_REGUALTOR_STARTUP_US as u64 + 1); T::regs().cfgr1().modify(|reg| reg.set_res(resolution)); diff --git a/embassy-stm32/src/adc/f1.rs b/embassy-stm32/src/adc/f1.rs index f6220de78..d6c6f480b 100644 --- a/embassy-stm32/src/adc/f1.rs +++ b/embassy-stm32/src/adc/f1.rs @@ -43,7 +43,7 @@ impl<'d, T: Instance> Adc<'d, T> { // 11.4: Before starting a calibration, the ADC must have been in power-on state (ADON bit = ‘1’) // for at least two ADC clock cycles. - blocking_delay_us((1_000_000 * 2) / Self::freq().0 + 1); + blocking_delay_us((1_000_000 * 2) / Self::freq().0 as u64 + 1); // Reset calibration T::regs().cr2().modify(|reg| reg.set_rstcal(true)); @@ -58,7 +58,7 @@ impl<'d, T: Instance> Adc<'d, T> { } // One cycle after calibration - blocking_delay_us((1_000_000 * 1) / Self::freq().0 + 1); + blocking_delay_us((1_000_000 * 1) / Self::freq().0 as u64 + 1); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; diff --git a/embassy-stm32/src/adc/f3.rs b/embassy-stm32/src/adc/f3.rs index 4a77f3c5b..29bfdac97 100644 --- a/embassy-stm32/src/adc/f3.rs +++ b/embassy-stm32/src/adc/f3.rs @@ -62,7 +62,7 @@ impl<'d, T: Instance> Adc<'d, T> { while T::regs().cr().read().adcal() {} // Wait more than 4 clock cycles after adcal is cleared (RM0364 p. 223). - blocking_delay_us((1_000_000 * 4) / Self::freq().0 + 1); + blocking_delay_us((1_000_000 * 4) / Self::freq().0 as u64 + 1); // Enable the adc T::regs().cr().modify(|w| w.set_aden(true)); diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs index 549f2f5a5..5ec08a22d 100644 --- a/embassy-stm32/src/adc/mod.rs +++ b/embassy-stm32/src/adc/mod.rs @@ -35,6 +35,8 @@ pub use ringbuffered::RingBufferedAdc; #[path = "adc4.rs"] pub mod adc4; +#[allow(unused)] +pub(self) use crate::block_for_us as blocking_delay_us; pub use crate::pac::adc::vals; #[cfg(not(any(adc_f1, adc_f3v3)))] pub use crate::pac::adc::vals::Res as Resolution; @@ -140,24 +142,6 @@ impl BasicAnyInstance for T { ))] impl AnyInstance for T {} -/// Performs a busy-wait delay for a specified number of microseconds. -#[allow(unused)] -pub(crate) fn blocking_delay_us(us: u32) { - cfg_if::cfg_if! { - // this does strange things on stm32wlx in low power mode depending on exactly when it's called - // as in sometimes 15 us (1 tick) would take > 20 seconds. - if #[cfg(all(feature = "time", all(not(feature = "low-power"), not(stm32wlex))))] { - let duration = embassy_time::Duration::from_micros(us as u64); - embassy_time::block_for(duration); - } else { - let freq = unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 as u64; - let us = us as u64; - let cycles = freq * us / 1_000_000; - cortex_m::asm::delay(cycles as u32); - } - } -} - #[cfg(any(adc_c0, adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0, adc_v4, adc_u5))] /// Number of samples used for averaging. #[derive(Copy, Clone, Debug)] diff --git a/embassy-stm32/src/dsihost.rs b/embassy-stm32/src/dsihost.rs index 59a2cbcdb..b8945820c 100644 --- a/embassy-stm32/src/dsihost.rs +++ b/embassy-stm32/src/dsihost.rs @@ -5,18 +5,11 @@ use core::marker::PhantomData; use embassy_hal_internal::PeripheralType; //use crate::gpio::{AnyPin, SealedPin}; +use crate::block_for_us; use crate::gpio::{AfType, AnyPin, OutputType, Speed}; use crate::rcc::{self, RccPeripheral}; use crate::{Peri, peripherals}; -/// Performs a busy-wait delay for a specified number of microseconds. -pub fn blocking_delay_ms(ms: u32) { - #[cfg(feature = "time")] - embassy_time::block_for(embassy_time::Duration::from_millis(ms as u64)); - #[cfg(not(feature = "time"))] - cortex_m::asm::delay(unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 / 1_000 * ms); -} - /// PacketTypes extracted from CubeMX #[repr(u8)] #[allow(dead_code)] @@ -334,7 +327,7 @@ impl<'d, T: Instance> DsiHost<'d, T> { if T::regs().gpsr().read().cmdfe() { return Ok(()); } - blocking_delay_ms(1); + block_for_us(1_000); } Err(Error::FifoTimeout) } @@ -345,7 +338,7 @@ impl<'d, T: Instance> DsiHost<'d, T> { if !T::regs().gpsr().read().cmdff() { return Ok(()); } - blocking_delay_ms(1); + block_for_us(1_000); } Err(Error::FifoTimeout) } @@ -356,7 +349,7 @@ impl<'d, T: Instance> DsiHost<'d, T> { if !self.read_busy() { return Ok(()); } - blocking_delay_ms(1); + block_for_us(1_000); } Err(Error::ReadTimeout) } @@ -367,7 +360,7 @@ impl<'d, T: Instance> DsiHost<'d, T> { if !T::regs().gpsr().read().prdfe() { return Ok(()); } - blocking_delay_ms(1); + block_for_us(1_000); } Err(Error::FifoTimeout) } diff --git a/embassy-stm32/src/eth/generic_phy.rs b/embassy-stm32/src/eth/generic_phy.rs index 774beef80..947874d7f 100644 --- a/embassy-stm32/src/eth/generic_phy.rs +++ b/embassy-stm32/src/eth/generic_phy.rs @@ -8,6 +8,7 @@ use embassy_time::{Duration, Timer}; use futures_util::FutureExt; use super::{Phy, StationManagement}; +use crate::block_for_us as blocking_delay_us; #[allow(dead_code)] mod phy_consts { @@ -76,19 +77,6 @@ impl GenericPhy { } } -// TODO: Factor out to shared functionality -fn blocking_delay_us(us: u32) { - #[cfg(feature = "time")] - embassy_time::block_for(Duration::from_micros(us as u64)); - #[cfg(not(feature = "time"))] - { - let freq = unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 as u64; - let us = us as u64; - let cycles = freq * us / 1_000_000; - cortex_m::asm::delay(cycles as u32); - } -} - impl Phy for GenericPhy { fn phy_reset(&mut self, sm: &mut S) { // Detect SMI address diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 680edf433..6e492946a 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -658,3 +658,17 @@ fn init_hw(config: Config) -> Peripherals { p }) } + +/// Performs a busy-wait delay for a specified number of microseconds. +#[allow(unused)] +pub(crate) fn block_for_us(us: u64) { + cfg_if::cfg_if! { + // this does strange things on stm32wlx in low power mode depending on exactly when it's called + // as in sometimes 15 us (1 tick) would take > 20 seconds. + if #[cfg(all(feature = "time", all(not(feature = "low-power"), not(stm32wlex))))] { + embassy_time::block_for(embassy_time::Duration::from_micros(us)); + } else { + cortex_m::asm::delay(unsafe { rcc::get_freqs().sys.to_hertz().unwrap().0 as u64 * us / 1_000_000 } as u32); + } + } +} diff --git a/embassy-stm32/src/opamp.rs b/embassy-stm32/src/opamp.rs index ac8d5de21..4a55f5bd3 100644 --- a/embassy-stm32/src/opamp.rs +++ b/embassy-stm32/src/opamp.rs @@ -4,19 +4,12 @@ use embassy_hal_internal::PeripheralType; use crate::Peri; +#[cfg(opamp_v5)] +use crate::block_for_us; use crate::pac::opamp::vals::*; #[cfg(not(any(stm32g4, stm32f3)))] use crate::rcc::RccInfo; -/// Performs a busy-wait delay for a specified number of microseconds. -#[cfg(opamp_v5)] -fn blocking_delay_ms(ms: u32) { - #[cfg(feature = "time")] - embassy_time::block_for(embassy_time::Duration::from_millis(ms as u64)); - #[cfg(not(feature = "time"))] - cortex_m::asm::delay(unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 / 1_000 * ms); -} - /// Gain #[allow(missing_docs)] #[derive(Clone, Copy)] @@ -439,7 +432,7 @@ impl<'d, T: Instance> OpAmp<'d, T> { // The closer the trimming value is to the optimum trimming value, the longer it takes to stabilize // (with a maximum stabilization time remaining below 2 ms in any case) -- RM0440 25.3.7 - blocking_delay_ms(2); + block_for_us(2_000); if !T::regs().csr().read().calout() { if mid == 0 { diff --git a/examples/stm32f469/src/bin/dsi_bsp.rs b/examples/stm32f469/src/bin/dsi_bsp.rs index d659291ff..7ba4da72b 100644 --- a/examples/stm32f469/src/bin/dsi_bsp.rs +++ b/examples/stm32f469/src/bin/dsi_bsp.rs @@ -3,7 +3,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::dsihost::{DsiHost, PacketType, blocking_delay_ms}; +use embassy_stm32::dsihost::{DsiHost, PacketType}; use embassy_stm32::gpio::{Level, Output, Speed}; use embassy_stm32::ltdc::Ltdc; use embassy_stm32::pac::dsihost::regs::{Ier0, Ier1}; @@ -13,7 +13,7 @@ use embassy_stm32::rcc::{ AHBPrescaler, APBPrescaler, Hse, HseMode, Pll, PllMul, PllPDiv, PllPreDiv, PllQDiv, PllRDiv, PllSource, Sysclk, }; use embassy_stm32::time::mhz; -use embassy_time::Timer; +use embassy_time::{Duration, Timer, block_for}; use {defmt_rtt as _, panic_probe as _}; enum _Orientation { @@ -444,7 +444,7 @@ async fn main(_spawner: Spawner) { dsi.enable_wrapper_dsi(); // First, delay 120 ms (reason unknown, STM32 Cube Example does it) - blocking_delay_ms(120); + block_for(Duration::from_millis(120)); // 1 to 26 dsi.write_cmd(0, NT35510_WRITES_0[0], &NT35510_WRITES_0[1..]).unwrap(); @@ -480,7 +480,7 @@ async fn main(_spawner: Spawner) { dsi.write_cmd(0, NT35510_WRITES_37[0], &NT35510_WRITES_37[1..]).unwrap(); // Add a delay, otherwise MADCTL not taken - blocking_delay_ms(200); + block_for(Duration::from_millis(200)); // Configure orientation as landscape dsi.write_cmd(0, NT35510_MADCTL_LANDSCAPE[0], &NT35510_MADCTL_LANDSCAPE[1..]) @@ -494,7 +494,7 @@ async fn main(_spawner: Spawner) { dsi.write_cmd(0, NT35510_WRITES_27[0], &NT35510_WRITES_27[1..]).unwrap(); // Wait for sleep out exit - blocking_delay_ms(120); + block_for(Duration::from_millis(120)); // Configure COLOR_CODING dsi.write_cmd(0, NT35510_WRITES_37[0], &NT35510_WRITES_37[1..]).unwrap(); @@ -590,7 +590,7 @@ async fn main(_spawner: Spawner) { //LTDC->SRCR = LTDC_SRCR_IMR; LTDC.srcr().modify(|w| w.set_imr(Imr::RELOAD)); - blocking_delay_ms(5000); + block_for(Duration::from_millis(5000)); const READ_SIZE: u16 = 1; let mut data = [1u8; READ_SIZE as usize]; @@ -606,7 +606,7 @@ async fn main(_spawner: Spawner) { .unwrap(); info!("Display ID3: {:#04x}", data); - blocking_delay_ms(500); + block_for(Duration::from_millis(500)); info!("Config done, start blinking LED"); loop { -- cgit