Skip to content

Commit

Permalink
Changes to i2c master and slave to handle large data buffers
Browse files Browse the repository at this point in the history
  • Loading branch information
akshat2112 committed Mar 3, 2025
1 parent 6672f1a commit 62d4d17
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 7 deletions.
124 changes: 124 additions & 0 deletions examples/rt685s-evk/src/bin/i2c-loopback-largebuf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#![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};
use embassy_imxrt::{bind_interrupts, peripherals};
use embedded_hal_async::i2c::I2c;

const ADDR: u8 = 0x20;
const MAX_I2C_CHUNK_SIZE: usize = 512;
const MASTER_BUFLEN: usize = 2000;
// slave buffer has to be 1 bigger than master buffer for each chunk
//because master does not handle end of read properly
const SLAVE_BUFLEN: usize = MASTER_BUFLEN + (MASTER_BUFLEN / MAX_I2C_CHUNK_SIZE) + 1;
const SLAVE_ADDR: Option<Address> = Address::new(ADDR);

bind_interrupts!(struct Irqs {
FLEXCOMM2 => i2c::InterruptHandler<peripherals::FLEXCOMM2>;
FLEXCOMM4 => i2c::InterruptHandler<peripherals::FLEXCOMM4>;
});

#[embassy_executor::task]
async fn slave_service(mut slave: I2cSlave<'static, Async>) {
let mut r_buf = [0xAA; SLAVE_BUFLEN];
let mut t_buf = [0xAA; SLAVE_BUFLEN];

// Initialize write buffer with increment numbers
for (i, e) in t_buf.iter_mut().enumerate() {
*e = ((i / MAX_I2C_CHUNK_SIZE) as u8) + 1;
}
for (i, e) in r_buf.iter_mut().enumerate() {
*e = ((i as u8) % 255) + 1;
}

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 + 1).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;
if n == 0 {
info!("Restart detected");
} else {
info!("Response complete write with {} bytes", 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;

let mut w_buf = [0xAA; MASTER_BUFLEN];
let mut r_buf = [0xAA; MASTER_BUFLEN];

// Initialize write buffer with increment numbers
for (i, e) in w_buf.iter_mut().enumerate() {
*e = ((i / MAX_I2C_CHUNK_SIZE) as u8) + 1;
}

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();
}

#[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));
}
18 changes: 13 additions & 5 deletions src/i2c/master.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -832,19 +832,27 @@ impl<A: embedded_hal_1::i2c::AddressMode + Into<u16>> embedded_hal_1::i2c::I2c<A

impl<A: embedded_hal_1::i2c::AddressMode + Into<u16>> embedded_hal_async::i2c::I2c<A> for I2cMaster<'_, Async> {
async fn read(&mut self, address: A, read: &mut [u8]) -> Result<()> {
self.read_no_stop(address.into(), read).await?;
for chunk in read.chunks_mut(MAX_I2C_CHUNK_SIZE) {
self.read_no_stop(address.into(), chunk).await?;
}
self.stop().await
}

async fn write(&mut self, address: A, write: &[u8]) -> Result<()> {
self.write_no_stop(address.into(), write).await?;
for chunk in write.chunks(MAX_I2C_CHUNK_SIZE) {
self.write_no_stop(address.into(), chunk).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?;
for chunk in write.chunks(MAX_I2C_CHUNK_SIZE) {
self.write_no_stop(address, chunk).await?;
}
for chunk in read.chunks_mut(MAX_I2C_CHUNK_SIZE) {
self.read_no_stop(address, chunk).await?;
}
self.stop().await
}

Expand Down
1 change: 1 addition & 0 deletions src/i2c/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ 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];
const MAX_I2C_CHUNK_SIZE: usize = 512;

/// Ten bit addresses start with first byte 0b11110XXX
pub const TEN_BIT_PREFIX: u8 = 0b11110 << 3;
Expand Down
40 changes: 38 additions & 2 deletions src/i2c/slave.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Response> {
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<Response> {
let i2c = self.info.regs;
let buf_len = buf.len();

Expand Down Expand Up @@ -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<Response> {
let mut xfer_count = 0;
for chunk in buf.chunks(MAX_I2C_CHUNK_SIZE + 1) {
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<Response> {
let i2c = self.info.regs;

// Verify that we are ready for transmit
Expand Down

0 comments on commit 62d4d17

Please sign in to comment.