From 9f37ba7072272e2dade5cedd89c4c22e61b40834 Mon Sep 17 00:00:00 2001 From: Akshat Saxena Date: Wed, 5 Mar 2025 22:34:42 -0800 Subject: [PATCH 1/3] Changes to i2c master to handle large data buffers --- src/i2c/master.rs | 38 +++++++++++++++++++++++++++++++------- src/i2c/mod.rs | 3 +++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/i2c/master.rs b/src/i2c/master.rs index 3c811647..509299b0 100644 --- a/src/i2c/master.rs +++ b/src/i2c/master.rs @@ -9,7 +9,7 @@ use embassy_hal_internal::into_ref; use super::{ Async, Blocking, Error, Info, Instance, InterruptHandler, MasterDma, Mode, Result, SclPin, SdaPin, TransferError, - I2C_WAKERS, TEN_BIT_PREFIX, + I2C_WAKERS, MAX_I2C_CHUNK_SIZE, TEN_BIT_PREFIX, }; use crate::interrupt::typelevel::Interrupt; use crate::{dma, interrupt, Peripheral}; @@ -157,7 +157,7 @@ impl<'a> I2cMaster<'a, Blocking> { i2cregs.mstdat().write(|w| // SAFETY: only unsafe due to .bits usage - unsafe { w.data().bits(address << 1 | u8::from(is_read)) }); + unsafe { w.data().bits((address << 1) | u8::from(is_read)) }); i2cregs.mstctl().write(|w| w.mststart().set_bit()); @@ -393,7 +393,7 @@ impl<'a> I2cMaster<'a, Async> { i2cregs.mstdat().write(|w| // SAFETY: only unsafe due to .bits usage - unsafe { w.data().bits(address << 1 | u8::from(is_read)) }); + unsafe { w.data().bits((address << 1) | u8::from(is_read)) }); i2cregs.mstctl().write(|w| w.mststart().set_bit()); @@ -761,6 +761,30 @@ impl<'a> I2cMaster<'a, Async> { ) .await } + + async fn write_chunks(&mut self, address: u16, write: &[u8]) -> Result<()> { + // write of 0 size is allowed according to i2c spec + if write.is_empty() { + self.write_no_stop(address, write).await?; + } + + for chunk in write.chunks(MAX_I2C_CHUNK_SIZE) { + self.write_no_stop(address, chunk).await?; + } + Ok(()) + } + + async fn read_chunks(&mut self, address: u16, read: &mut [u8]) -> Result<()> { + // read of 0 size is not allowed according to i2c spec + if read.is_empty() { + return Err(TransferError::OtherBusError.into()); + } + + for chunk in read.chunks_mut(MAX_I2C_CHUNK_SIZE) { + self.read_no_stop(address, chunk).await?; + } + Ok(()) + } } /// Error Types for I2C communication @@ -832,19 +856,19 @@ impl> embedded_hal_1::i2c::I2c> embedded_hal_async::i2c::I2c for I2cMaster<'_, Async> { async fn read(&mut self, address: A, read: &mut [u8]) -> Result<()> { - self.read_no_stop(address.into(), read).await?; + self.read_chunks(address.into(), read).await?; self.stop().await } async fn write(&mut self, address: A, write: &[u8]) -> Result<()> { - self.write_no_stop(address.into(), write).await?; + self.write_chunks(address.into(), write).await?; self.stop().await } async fn write_read(&mut self, address: A, write: &[u8], read: &mut [u8]) -> Result<()> { let address = address.into(); - self.write_no_stop(address, write).await?; - self.read_no_stop(address, read).await?; + self.write_chunks(address, write).await?; + self.read_chunks(address, read).await?; self.stop().await } diff --git a/src/i2c/mod.rs b/src/i2c/mod.rs index 6e0e46aa..c3f5228d 100644 --- a/src/i2c/mod.rs +++ b/src/i2c/mod.rs @@ -127,6 +127,9 @@ impl_instance!(0, 1, 2, 3, 4, 5, 6, 7); const I2C_COUNT: usize = 8; static I2C_WAKERS: [AtomicWaker; I2C_COUNT] = [const { AtomicWaker::new() }; I2C_COUNT]; +/// Maximum I2C chunk size for DMA transfers +pub const MAX_I2C_CHUNK_SIZE: usize = 1024; + /// Ten bit addresses start with first byte 0b11110XXX pub const TEN_BIT_PREFIX: u8 = 0b11110 << 3; From 2329ef5eda1bae5ef45464b5ae301e04b4379fc1 Mon Sep 17 00:00:00 2001 From: Akshat Saxena Date: Wed, 5 Mar 2025 22:35:53 -0800 Subject: [PATCH 2/3] Changes to i2c slave to handle large data buffers --- src/i2c/slave.rs | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/i2c/slave.rs b/src/i2c/slave.rs index 6b0bfd07..08693249 100644 --- a/src/i2c/slave.rs +++ b/src/i2c/slave.rs @@ -8,7 +8,7 @@ use embassy_hal_internal::{into_ref, Peripheral}; use super::{ Async, Blocking, Info, Instance, InterruptHandler, Mode, Result, SclPin, SdaPin, SlaveDma, TransferError, - I2C_WAKERS, TEN_BIT_PREFIX, + I2C_WAKERS, MAX_I2C_CHUNK_SIZE, TEN_BIT_PREFIX, }; use crate::interrupt::typelevel::Interrupt; use crate::pac::i2c0::stat::Slvstate; @@ -502,6 +502,24 @@ impl I2cSlave<'_, Async> { /// Respond to write command from master pub async fn respond_to_write(&mut self, buf: &mut [u8]) -> Result { + let mut xfer_count = 0; + for chunk in buf.chunks_mut(MAX_I2C_CHUNK_SIZE) { + let result = self.respond_to_write_inner(chunk).await?; + match result { + Response::Complete(count) => { + xfer_count += count; + return Ok(Response::Complete(xfer_count)); + } + Response::Pending(count) => { + xfer_count += count; + } + } + } + Ok(Response::Complete(xfer_count)) + } + + /// Function to handle the actual write transaction in chunks + pub async fn respond_to_write_inner(&mut self, buf: &mut [u8]) -> Result { let i2c = self.info.regs; let buf_len = buf.len(); @@ -579,8 +597,26 @@ impl I2cSlave<'_, Async> { /// Respond to read command from master /// User must provide enough data to complete the transaction or else - /// we will get stuck in this function + /// we will get stuck in this function pub async fn respond_to_read(&mut self, buf: &[u8]) -> Result { + let mut xfer_count = 0; + for chunk in buf.chunks(MAX_I2C_CHUNK_SIZE) { + let result = self.respond_to_read_inner(chunk).await?; + match result { + Response::Complete(count) => { + xfer_count += count; + return Ok(Response::Complete(xfer_count)); + } + Response::Pending(count) => { + xfer_count += count; + } + } + } + Ok(Response::Complete(xfer_count)) + } + + /// Function to handle the actual read transaction in chunks + pub async fn respond_to_read_inner(&mut self, buf: &[u8]) -> Result { let i2c = self.info.regs; // Verify that we are ready for transmit From 7d8ce43b1f1eff7047787f3b86634ea306e53ca9 Mon Sep 17 00:00:00 2001 From: Akshat Saxena Date: Wed, 5 Mar 2025 22:37:51 -0800 Subject: [PATCH 3/3] Add I2C loopback example with large buffer support --- examples/rt685s-evk/.cargo/config.toml | 13 +- .../src/bin/i2c-loopback-largebuf.rs | 127 ++++++++++++++++++ 2 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 examples/rt685s-evk/src/bin/i2c-loopback-largebuf.rs diff --git a/examples/rt685s-evk/.cargo/config.toml b/examples/rt685s-evk/.cargo/config.toml index db42be81..59c3a138 100644 --- a/examples/rt685s-evk/.cargo/config.toml +++ b/examples/rt685s-evk/.cargo/config.toml @@ -2,12 +2,16 @@ runner = 'probe-rs run --chip MIMXRT685SFVKB' rustflags = [ - "-C", "linker=flip-link", - "-C", "link-arg=-Tlink.x", - "-C", "link-arg=-Tdefmt.x", + "-C", + "linker=flip-link", + "-C", + "link-arg=-Tlink.x", + "-C", + "link-arg=-Tdefmt.x", # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x # See https://github.com/rust-embedded/cortex-m-quickstart/pull/95 - "-C", "link-arg=--nmagic", + "-C", + "link-arg=--nmagic", ] [build] @@ -15,3 +19,4 @@ target = "thumbv8m.main-none-eabihf" # Cortex-M33 [env] DEFMT_LOG = "trace" +EMBASSY_EXECUTOR_TASK_ARENA_SIZE = "16384" diff --git a/examples/rt685s-evk/src/bin/i2c-loopback-largebuf.rs b/examples/rt685s-evk/src/bin/i2c-loopback-largebuf.rs new file mode 100644 index 00000000..7877fde5 --- /dev/null +++ b/examples/rt685s-evk/src/bin/i2c-loopback-largebuf.rs @@ -0,0 +1,127 @@ +#![no_std] +#![no_main] + +extern crate embassy_imxrt_examples; + +use defmt::info; +use embassy_executor::Spawner; +use embassy_imxrt::i2c::master::{I2cMaster, Speed}; +use embassy_imxrt::i2c::slave::{Address, Command, I2cSlave, Response}; +use embassy_imxrt::i2c::{self, Async, MAX_I2C_CHUNK_SIZE}; +use embassy_imxrt::{bind_interrupts, peripherals}; +use embedded_hal_async::i2c::I2c; + +const ADDR: u8 = 0x20; +const BUFLEN: usize = 2500; +const SLAVE_ADDR: Option
= Address::new(ADDR); + +bind_interrupts!(struct Irqs { + FLEXCOMM2 => i2c::InterruptHandler; + FLEXCOMM4 => i2c::InterruptHandler; +}); + +/// Generate a buffer with increment numbers in each segment +fn generate_buffer() -> [u8; SIZE] { + let mut buf = [0xAA; SIZE]; + for (i, e) in buf.iter_mut().enumerate() { + *e = ((i / MAX_I2C_CHUNK_SIZE) as u8) + 1; + } + buf +} + +#[embassy_executor::task] +async fn slave_service(mut slave: I2cSlave<'static, Async>) { + // Buffer containing data read by the master + let t_buf: [u8; BUFLEN] = generate_buffer(); + + // Buffer that the master writes to + let mut r_buf = [0xAA; BUFLEN]; + // Buffer to compare with written data + let expected_buf: [u8; BUFLEN] = generate_buffer(); + + let mut r_offset = 0; + let mut t_offset = 0; + + loop { + match slave.listen().await.unwrap() { + Command::Probe => { + info!("Probe, nothing to do"); + } + Command::Read => { + info!("Read"); + loop { + let end = (t_offset + MAX_I2C_CHUNK_SIZE).min(t_buf.len()); + let t_chunk = &t_buf[t_offset..end]; + match slave.respond_to_read(t_chunk).await.unwrap() { + Response::Complete(n) => { + t_offset += n; + info!("Response complete read with {} bytes", n); + break; + } + Response::Pending(n) => { + t_offset += n; + info!("Response to read got {} bytes, more bytes to fill", n); + } + } + } + } + Command::Write => { + info!("Write"); + loop { + let end = (r_offset + MAX_I2C_CHUNK_SIZE).min(r_buf.len()); + let r_chunk = &mut r_buf[r_offset..end]; + match slave.respond_to_write(r_chunk).await.unwrap() { + Response::Complete(n) => { + r_offset += n; + info!("Response complete write with {} bytes", n); + + // Compare written data with expected data + assert!(r_buf[0..n] == expected_buf[0..n]); + break; + } + Response::Pending(n) => { + r_offset += n; + info!("Response to write got {} bytes, more bytes pending", n); + } + } + } + } + } + } +} + +#[embassy_executor::task] +async fn master_service(mut master: I2cMaster<'static, Async>) { + const ADDR: u8 = 0x20; + + // Buffer containing data to write to slave + let w_buf: [u8; BUFLEN] = generate_buffer(); + + // Buffer to compare with read data + let expected_buf: [u8; BUFLEN] = generate_buffer(); + // Buffer to store data read from slave + let mut r_buf = [0xAA; BUFLEN]; + + let w_end = w_buf.len(); + info!("i2cm write {} bytes", w_end); + master.write(ADDR, &w_buf[0..w_end]).await.unwrap(); + + let r_end = r_buf.len(); + info!("i2cm read {} bytes", r_end); + master.read(ADDR, &mut r_buf[0..r_end]).await.unwrap(); + + assert!(r_buf[0..r_end] == expected_buf[0..r_end]); +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("i2c loopback bigbuffer example"); + let p = embassy_imxrt::init(Default::default()); + + let slave = I2cSlave::new_async(p.FLEXCOMM2, p.PIO0_18, p.PIO0_17, Irqs, SLAVE_ADDR.unwrap(), p.DMA0_CH4).unwrap(); + + let master = I2cMaster::new_async(p.FLEXCOMM4, p.PIO0_29, p.PIO0_30, Irqs, Speed::Standard, p.DMA0_CH9).unwrap(); + + spawner.must_spawn(master_service(master)); + spawner.must_spawn(slave_service(slave)); +}