Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes to i2c master and slave to handle large data buffers #322

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions examples/rt685s-evk/.cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@
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]
target = "thumbv8m.main-none-eabihf" # Cortex-M33

[env]
DEFMT_LOG = "trace"
EMBASSY_EXECUTOR_TASK_ARENA_SIZE = "16384"
127 changes: 127 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,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> = Address::new(ADDR);

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

/// Generate a buffer with increment numbers in each segment
fn generate_buffer<const SIZE: usize>() -> [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));
}
38 changes: 31 additions & 7 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 @@ -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());

Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -761,6 +761,30 @@ impl<'a> I2cMaster<'a, Async> {
)
.await
}

async fn write_chunks(&mut self, address: u16, write: &[u8]) -> Result<()> {
Copy link
Preview

Copilot AI Mar 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] In write_chunks, when the write buffer is empty the function calls write_no_stop but does not return immediately. To improve clarity and control flow, consider returning immediately after handling the empty buffer.

Copilot is powered by AI, so mistakes are possible. Review output carefully before use.

Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
// 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
Expand Down Expand Up @@ -832,19 +856,19 @@ 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?;
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
}

Expand Down
3 changes: 3 additions & 0 deletions src/i2c/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

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;
Copy link
Preview

Copilot AI Mar 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] In respond_to_write, the for loop returns as soon as a chunk yields a 'Complete' response, which may unintentionally skip processing remaining chunks. Review this behavior to ensure it meets the intended protocol for multi-chunk transactions.

Copilot is powered by AI, so mistakes are possible. Review output carefully before use.

Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
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) {
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