From 8798306fb2c124efc06443c2913c3d7a4919dd83 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 3 Dec 2025 09:52:03 -0800 Subject: I2c Async (#50) * I2c Async Signed-off-by: Felipe Balbi * Fix i2c read bug Signed-off-by: Felipe Balbi * Introduce wait_for() Signed-off-by: Felipe Balbi * Review comments Signed-off-by: Felipe Balbi * more review comments Signed-off-by: Felipe Balbi * review comments Signed-off-by: Felipe Balbi * review comments Signed-off-by: Felipe Balbi * convert async fn to impl Future Signed-off-by: Felipe Balbi --------- Signed-off-by: Felipe Balbi Co-authored-by: Felipe Balbi --- examples/src/bin/i2c-async.rs | 39 +++++++ src/i2c/controller.rs | 253 ++++++++++++++++++++++++++++++++++++++++-- src/i2c/mod.rs | 43 +++++-- 3 files changed, 316 insertions(+), 19 deletions(-) create mode 100644 examples/src/bin/i2c-async.rs diff --git a/examples/src/bin/i2c-async.rs b/examples/src/bin/i2c-async.rs new file mode 100644 index 000000000..47b5f3cbe --- /dev/null +++ b/examples/src/bin/i2c-async.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_time::Timer; +use hal::bind_interrupts; +use hal::clocks::config::Div8; +use hal::config::Config; +use hal::i2c::controller::{self, I2c, Speed}; +use hal::i2c::InterruptHandler; +use hal::peripherals::LPI2C3; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +bind_interrupts!( + struct Irqs { + LPI2C3 => InterruptHandler; + } +); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + config.clock_cfg.sirc.fro_lf_div = Div8::from_divisor(1); + + let p = hal::init(config); + + defmt::info!("I2C example"); + + let mut config = controller::Config::default(); + config.speed = Speed::Standard; + let mut i2c = I2c::new_async(p.LPI2C3, p.P3_27, p.P3_28, Irqs, config).unwrap(); + let mut buf = [0u8; 2]; + + loop { + i2c.async_write_read(0x48, &[0x00], &mut buf).await.unwrap(); + defmt::info!("Buffer: {:02x}", buf); + Timer::after_secs(1).await; + } +} diff --git a/src/i2c/controller.rs b/src/i2c/controller.rs index 41bbc821d..818024bc9 100644 --- a/src/i2c/controller.rs +++ b/src/i2c/controller.rs @@ -1,13 +1,15 @@ //! LPI2C controller driver +use core::future::Future; use core::marker::PhantomData; use embassy_hal_internal::Peri; use mcxa_pac::lpi2c0::mtdr::Cmd; -use super::{Blocking, Error, Instance, Mode, Result, SclPin, SdaPin}; +use super::{Async, Blocking, Error, Instance, InterruptHandler, Mode, Result, SclPin, SdaPin}; use crate::clocks::periph_helpers::{Div4, Lpi2cClockSel, Lpi2cConfig}; use crate::clocks::{enable_and_reset, PoweredClock}; +use crate::interrupt::typelevel::Interrupt; use crate::AnyPin; /// Bus speed (nominal SCL, no clock stretching) @@ -199,9 +201,6 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { } fn status(&mut self) -> Result<()> { - // Wait for TxFIFO to be drained - while !self.is_tx_fifo_empty() {} - let msr = T::regs().msr().read(); T::regs().msr().write(|w| { w.epf() @@ -228,6 +227,8 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { Err(Error::AddressNack) } else if msr.alf().bit_is_set() { Err(Error::ArbitrationLoss) + } else if msr.fef().bit_is_set() { + Err(Error::FifoError) } else { Ok(()) } @@ -259,6 +260,9 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { let addr_rw = address << 1 | if read { 1 } else { 0 }; self.send_cmd(if self.is_hs { Cmd::StartHs } else { Cmd::Start }, addr_rw); + // Wait for TxFIFO to be drained + while !self.is_tx_fifo_empty() {} + // Check controller status self.status() } @@ -268,17 +272,21 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { while self.is_tx_fifo_full() {} self.send_cmd(Cmd::Stop, 0); + + // Wait for TxFIFO to be drained + while !self.is_tx_fifo_empty() {} + self.status() } fn blocking_read_internal(&mut self, address: u8, read: &mut [u8], send_stop: SendStop) -> Result<()> { - self.start(address, true)?; - if read.is_empty() { return Err(Error::InvalidReadBufferLength); } for chunk in read.chunks_mut(256) { + self.start(address, true)?; + // Wait until we have space in the TxFIFO while self.is_tx_fifo_full() {} @@ -290,10 +298,10 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { *byte = T::regs().mrdr().read().data().bits(); } + } - if send_stop == SendStop::Yes { - self.stop()?; - } + if send_stop == SendStop::Yes { + self.stop()?; } Ok(()) @@ -351,6 +359,201 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { } } +impl<'d, T: Instance> I2c<'d, T, Async> { + /// Create a new async instance of the I2C Controller bus driver. + pub fn new_async( + peri: Peri<'d, T>, + scl: Peri<'d, impl SclPin>, + sda: Peri<'d, impl SdaPin>, + _irq: impl crate::interrupt::typelevel::Binding> + 'd, + config: Config, + ) -> Result { + T::Interrupt::unpend(); + + // Safety: `_irq` ensures an Interrupt Handler exists. + unsafe { T::Interrupt::enable() }; + + Self::new_inner(peri, scl, sda, config) + } + + fn enable_rx_ints(&mut self) { + T::regs().mier().write(|w| { + w.rdie() + .enabled() + .ndie() + .enabled() + .alie() + .enabled() + .feie() + .enabled() + .pltie() + .enabled() + }); + } + + fn enable_tx_ints(&mut self) { + T::regs().mier().write(|w| { + w.tdie() + .enabled() + .ndie() + .enabled() + .alie() + .enabled() + .feie() + .enabled() + .pltie() + .enabled() + }); + } + + async fn async_start(&mut self, address: u8, read: bool) -> Result<()> { + if address >= 0x80 { + return Err(Error::AddressOutOfRange(address)); + } + + // send the start command + let addr_rw = address << 1 | if read { 1 } else { 0 }; + self.send_cmd(if self.is_hs { Cmd::StartHs } else { Cmd::Start }, addr_rw); + + T::wait_cell() + .wait_for(|| { + // enable interrupts + self.enable_tx_ints(); + // if the command FIFO is empty, we're done sending start + self.is_tx_fifo_empty() + }) + .await + .map_err(|_| Error::Other)?; + + self.status() + } + + async fn async_stop(&mut self) -> Result<()> { + // send the stop command + self.send_cmd(Cmd::Stop, 0); + + T::wait_cell() + .wait_for(|| { + // enable interrupts + self.enable_tx_ints(); + // if the command FIFO is empty, we're done sending stop + self.is_tx_fifo_empty() + }) + .await + .map_err(|_| Error::Other)?; + + self.status() + } + + async fn async_read_internal(&mut self, address: u8, read: &mut [u8], send_stop: SendStop) -> Result<()> { + if read.is_empty() { + return Err(Error::InvalidReadBufferLength); + } + + for chunk in read.chunks_mut(256) { + self.async_start(address, true).await?; + + // send receive command + self.send_cmd(Cmd::Receive, (chunk.len() - 1) as u8); + + T::wait_cell() + .wait_for(|| { + // enable interrupts + self.enable_tx_ints(); + // if the command FIFO is empty, we're done sending start + self.is_tx_fifo_empty() + }) + .await + .map_err(|_| Error::Other)?; + + for byte in chunk.iter_mut() { + T::wait_cell() + .wait_for(|| { + // enable interrupts + self.enable_rx_ints(); + // it the rx FIFO is not empty, we need to read a byte + !self.is_rx_fifo_empty() + }) + .await + .map_err(|_| Error::ReadFail)?; + + *byte = T::regs().mrdr().read().data().bits(); + } + } + + if send_stop == SendStop::Yes { + self.async_stop().await?; + } + + Ok(()) + } + + async fn async_write_internal(&mut self, address: u8, write: &[u8], send_stop: SendStop) -> Result<()> { + self.async_start(address, false).await?; + + // Usually, embassy HALs error out with an empty write, + // however empty writes are useful for writing I2C scanning + // logic through write probing. That is, we send a start with + // R/w bit cleared, but instead of writing any data, just send + // the stop onto the bus. This has the effect of checking if + // the resulting address got an ACK but causing no + // side-effects to the device on the other end. + // + // Because of this, we are not going to error out in case of + // empty writes. + #[cfg(feature = "defmt")] + if write.is_empty() { + defmt::trace!("Empty write, write probing?"); + } + + for byte in write { + T::wait_cell() + .wait_for(|| { + // enable interrupts + self.enable_tx_ints(); + // initiate trasmit + self.send_cmd(Cmd::Transmit, *byte); + // it the rx FIFO is not empty, we're done transmiting + self.is_tx_fifo_empty() + }) + .await + .map_err(|_| Error::WriteFail)?; + } + + if send_stop == SendStop::Yes { + self.async_stop().await?; + } + + Ok(()) + } + + // Public API: Async + + /// Read from address into buffer asynchronously. + pub fn async_read<'a>( + &mut self, + address: u8, + read: &'a mut [u8], + ) -> impl Future> + use<'_, 'a, 'd, T> { + self.async_read_internal(address, read, SendStop::Yes) + } + + /// Write to address from buffer asynchronously. + pub fn async_write<'a>( + &mut self, + address: u8, + write: &'a [u8], + ) -> impl Future> + use<'_, 'a, 'd, T> { + self.async_write_internal(address, write, SendStop::Yes) + } + + /// Write to address from bytes and read from address into buffer asynchronously. + pub async fn async_write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<()> { + self.async_write_internal(address, write, SendStop::No).await?; + self.async_read_internal(address, read, SendStop::Yes).await + } +} + impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Read for I2c<'d, T, M> { type Error = Error; @@ -445,6 +648,38 @@ impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::I2c for I2c<'d, T, M> { } } +impl<'d, T: Instance> embedded_hal_async::i2c::I2c for I2c<'d, T, Async> { + async fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal_async::i2c::Operation<'_>], + ) -> Result<()> { + if let Some((last, rest)) = operations.split_last_mut() { + for op in rest { + match op { + embedded_hal_async::i2c::Operation::Read(buf) => { + self.async_read_internal(address, buf, SendStop::No).await? + } + embedded_hal_async::i2c::Operation::Write(buf) => { + self.async_write_internal(address, buf, SendStop::No).await? + } + } + } + + match last { + embedded_hal_async::i2c::Operation::Read(buf) => { + self.async_read_internal(address, buf, SendStop::Yes).await + } + embedded_hal_async::i2c::Operation::Write(buf) => { + self.async_write_internal(address, buf, SendStop::Yes).await + } + } + } else { + Ok(()) + } + } +} + impl<'d, T: Instance, M: Mode> embassy_embedded_hal::SetConfig for I2c<'d, T, M> { type Config = Config; type ConfigError = Error; diff --git a/src/i2c/mod.rs b/src/i2c/mod.rs index a1f842029..9a014224a 100644 --- a/src/i2c/mod.rs +++ b/src/i2c/mod.rs @@ -3,7 +3,7 @@ use core::marker::PhantomData; use embassy_hal_internal::PeripheralType; -use embassy_sync::waitqueue::AtomicWaker; +use maitake_sync::WaitCell; use paste::paste; use crate::clocks::periph_helpers::Lpi2cConfig; @@ -22,6 +22,8 @@ pub mod controller; pub enum Error { /// Clock configuration error. ClockSetup(ClockError), + /// FIFO Error + FifoError, /// Reading for I2C failed. ReadFail, /// Writing to I2C failed. @@ -47,11 +49,32 @@ pub struct InterruptHandler { impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { - let waker = T::waker(); - - waker.wake(); - - todo!() + if T::regs().mier().read().bits() != 0 { + T::regs().mier().write(|w| { + w.tdie() + .disabled() + .rdie() + .disabled() + .epie() + .disabled() + .sdie() + .disabled() + .ndie() + .disabled() + .alie() + .disabled() + .feie() + .disabled() + .pltie() + .disabled() + .dmie() + .disabled() + .stie() + .disabled() + }); + + T::wait_cell().wake(); + } } } @@ -64,7 +87,7 @@ impl sealed::Sealed for T {} trait SealedInstance { fn regs() -> &'static pac::lpi2c0::RegisterBlock; - fn waker() -> &'static AtomicWaker; + fn wait_cell() -> &'static WaitCell; } /// I2C Instance @@ -85,9 +108,9 @@ macro_rules! impl_instance { unsafe { &*pac::[]::ptr() } } - fn waker() -> &'static AtomicWaker { - static WAKER: AtomicWaker = AtomicWaker::new(); - &WAKER + fn wait_cell() -> &'static WaitCell { + static WAIT_CELL: WaitCell = WaitCell::new(); + &WAIT_CELL } } -- cgit