From d874a858c6213ae0b592fea3d23bb2ff2cacfd34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Thu, 31 Oct 2024 09:32:28 +0100 Subject: [PATCH 01/18] Rework I2C --- esp-hal/src/i2c.rs | 722 ++++++++++++------------- esp-hal/src/soc/esp32/peripherals.rs | 4 +- esp-hal/src/soc/esp32c2/peripherals.rs | 2 +- esp-hal/src/soc/esp32c3/peripherals.rs | 2 +- esp-hal/src/soc/esp32c6/peripherals.rs | 2 +- esp-hal/src/soc/esp32h2/peripherals.rs | 4 +- esp-hal/src/soc/esp32s2/peripherals.rs | 4 +- esp-hal/src/soc/esp32s3/peripherals.rs | 4 +- 8 files changed, 363 insertions(+), 381 deletions(-) diff --git a/esp-hal/src/i2c.rs b/esp-hal/src/i2c.rs index 0e636efe22d..93502b43e53 100644 --- a/esp-hal/src/i2c.rs +++ b/esp-hal/src/i2c.rs @@ -62,7 +62,6 @@ use core::{ use embassy_sync::waitqueue::AtomicWaker; use embedded_hal::i2c::Operation as EhalOperation; use fugit::HertzU32; -use procmacros::handler; use crate::{ clock::Clocks, @@ -70,7 +69,11 @@ use crate::{ gpio::{interconnect::PeripheralOutput, InputSignal, OutputSignal, Pull}, interrupt::InterruptHandler, peripheral::{Peripheral, PeripheralRef}, - peripherals::i2c0::{RegisterBlock, COMD}, + peripherals::{ + i2c0::{RegisterBlock, COMD}, + Interrupt, + }, + private, system::PeripheralClockControl, Async, Blocking, @@ -308,10 +311,10 @@ impl<'d, T, DM: Mode> I2c<'d, DM, T> where T: Instance, { - fn new_internal( + fn new_internal( i2c: impl Peripheral

+ 'd, - sda: impl Peripheral

+ 'd, - scl: impl Peripheral

+ 'd, + sda: impl Peripheral

+ 'd, + scl: impl Peripheral

+ 'd, frequency: HertzU32, ) -> Self { crate::into_ref!(i2c); @@ -327,34 +330,21 @@ where PeripheralClockControl::reset(i2c.i2c.peripheral()); PeripheralClockControl::enable(i2c.i2c.peripheral()); - // TODO: implement with_pins et. al. - // avoid SCL/SDA going low during configuration - scl.set_output_high(true, crate::private::Internal); - sda.set_output_high(true, crate::private::Internal); - - scl.set_to_open_drain_output(crate::private::Internal); - scl.enable_input(true, crate::private::Internal); - scl.pull_direction(Pull::Up, crate::private::Internal); - - i2c.i2c.scl_input_signal().connect_to(&mut scl); - i2c.i2c.scl_output_signal().connect_to(&mut scl); + let i2c = i2c.with_sda(sda).with_scl(scl); - sda.set_to_open_drain_output(crate::private::Internal); - sda.enable_input(true, crate::private::Internal); - sda.pull_direction(Pull::Up, crate::private::Internal); - - i2c.i2c.sda_input_signal().connect_to(&mut sda); - i2c.i2c.sda_output_signal().connect_to(&mut sda); - - i2c.i2c.setup(frequency, None); + i2c.info().setup(frequency, None); i2c } + fn info(&self) -> &Info { + self.i2c.info() + } + fn internal_recover(&self) { PeripheralClockControl::reset(self.i2c.peripheral()); PeripheralClockControl::enable(self.i2c.peripheral()); - self.i2c.setup(self.frequency, self.timeout); + self.info().setup(self.frequency, self.timeout); } /// Set the I2C timeout. @@ -362,7 +352,7 @@ where // timeout, and just what exactly is a timeout in this context? pub fn with_timeout(mut self, timeout: Option) -> Self { self.timeout = timeout; - self.i2c.setup(self.frequency, self.timeout); + self.info().setup(self.frequency, self.timeout); self } @@ -381,15 +371,15 @@ where let next_op = op_iter.peek().map(|v| v.kind()); let kind = op.kind(); // Clear all I2C interrupts - self.i2c.clear_all_interrupts(); + self.info().clear_all_interrupts(); - let cmd_iterator = &mut self.i2c.register_block().comd_iter(); + let cmd_iterator = &mut self.info().register_block().comd_iter(); match op { Operation::Write(buffer) => { // execute a write operation: // - issue START/RSTART if op is different from previous // - issue STOP if op is the last one - self.i2c + self.info() .write_operation( address, buffer, @@ -404,7 +394,7 @@ where // - issue START/RSTART if op is different from previous // - issue STOP if op is the last one // - will_continue is true if there is another read operation next - self.i2c + self.info() .read_operation( address, buffer, @@ -422,16 +412,50 @@ where Ok(()) } + + fn with_sda(self, sda: impl Peripheral

+ 'd) -> Self { + let info = self.info(); + let input = info.sda_input; + let output = info.sda_output; + self.with_pin(sda, input, output) + } + + fn with_scl(self, scl: impl Peripheral

+ 'd) -> Self { + let info = self.info(); + let input = info.scl_input; + let output = info.scl_output; + self.with_pin(scl, input, output) + } + + fn with_pin( + self, + pin: impl Peripheral

+ 'd, + input: InputSignal, + output: OutputSignal, + ) -> Self { + crate::into_mapped_ref!(pin); + // avoid the pin going low during configuration + pin.set_output_high(true, private::Internal); + + pin.set_to_open_drain_output(private::Internal); + pin.enable_input(true, private::Internal); + pin.pull_direction(Pull::Up, private::Internal); + + input.connect_to(&mut pin); + output.connect_to(&mut pin); + + self + } } impl<'d> I2c<'d, Blocking> { /// Create a new I2C instance /// This will enable the peripheral but the peripheral won't get /// automatically disabled when this gets dropped. - pub fn new( + pub fn new( i2c: impl Peripheral

+ 'd, - sda: impl Peripheral

+ 'd, - scl: impl Peripheral

+ 'd, + sda: impl Peripheral

+ 'd, + scl: impl Peripheral

+ 'd, frequency: HertzU32, ) -> Self { Self::new_typed(i2c.map_into(), sda, scl, frequency) @@ -445,10 +469,10 @@ where /// Create a new I2C instance /// This will enable the peripheral but the peripheral won't get /// automatically disabled when this gets dropped. - pub fn new_typed( + pub fn new_typed( i2c: impl Peripheral

+ 'd, - sda: impl Peripheral

+ 'd, - scl: impl Peripheral

+ 'd, + sda: impl Peripheral

+ 'd, + scl: impl Peripheral

+ 'd, frequency: HertzU32, ) -> Self { Self::new_internal(i2c, sda, scl, frequency) @@ -458,7 +482,7 @@ where /// Configures the I2C peripheral to operate in asynchronous mode. pub fn into_async(mut self) -> I2c<'d, Async, T> { - self.set_interrupt_handler(self.i2c.async_handler()); + self.set_interrupt_handler(self.info().async_handler); I2c { i2c: self.i2c, @@ -473,11 +497,11 @@ where let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); for (idx, chunk) in buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { // Clear all I2C interrupts - self.i2c.clear_all_interrupts(); + self.info().clear_all_interrupts(); - let cmd_iterator = &mut self.i2c.register_block().comd_iter(); + let cmd_iterator = &mut self.info().register_block().comd_iter(); - self.i2c + self.info() .read_operation( address, chunk, @@ -497,11 +521,11 @@ where let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); for (idx, chunk) in buffer.chunks(I2C_CHUNK_SIZE).enumerate() { // Clear all I2C interrupts - self.i2c.clear_all_interrupts(); + self.info().clear_all_interrupts(); - let cmd_iterator = &mut self.i2c.register_block().comd_iter(); + let cmd_iterator = &mut self.info().register_block().comd_iter(); - self.i2c + self.info() .write_operation( address, chunk, @@ -528,11 +552,11 @@ where for (idx, chunk) in write_buffer.chunks(I2C_CHUNK_SIZE).enumerate() { // Clear all I2C interrupts - self.i2c.clear_all_interrupts(); + self.info().clear_all_interrupts(); - let cmd_iterator = &mut self.i2c.register_block().comd_iter(); + let cmd_iterator = &mut self.info().register_block().comd_iter(); - self.i2c + self.info() .write_operation( address, chunk, @@ -545,11 +569,11 @@ where for (idx, chunk) in read_buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { // Clear all I2C interrupts - self.i2c.clear_all_interrupts(); + self.info().clear_all_interrupts(); - let cmd_iterator = &mut self.i2c.register_block().comd_iter(); + let cmd_iterator = &mut self.info().register_block().comd_iter(); - self.i2c + self.info() .read_operation( address, chunk, @@ -591,27 +615,22 @@ where } } -impl crate::private::Sealed for I2c<'_, Blocking, T> where T: Instance {} +impl private::Sealed for I2c<'_, Blocking, T> where T: Instance {} impl InterruptConfigurable for I2c<'_, Blocking, T> where T: Instance, { fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) { + let interrupt = self.info().interrupt; for core in Cpu::other() { - crate::interrupt::disable(core, self.i2c.interrupt()); + crate::interrupt::disable(core, interrupt); } - unsafe { crate::interrupt::bind_interrupt(self.i2c.interrupt(), handler.handler()) }; - unwrap!(crate::interrupt::enable( - self.i2c.interrupt(), - handler.priority() - )); + unsafe { crate::interrupt::bind_interrupt(interrupt, handler.handler()) }; + unwrap!(crate::interrupt::enable(interrupt, handler.priority())); } } -const NUM_I2C: usize = 1 + cfg!(i2c1) as usize; -static WAKERS: [AtomicWaker; NUM_I2C] = [const { AtomicWaker::new() }; NUM_I2C]; - #[cfg_attr(esp32, allow(dead_code))] pub(crate) enum Event { EndDetect, @@ -622,21 +641,16 @@ pub(crate) enum Event { #[cfg(not(esp32))] #[must_use = "futures do nothing unless you `.await` or poll them"] -struct I2cFuture<'a, T> -where - T: Instance, -{ +struct I2cFuture<'a> { event: Event, - instance: &'a T, + info: &'a Info, + state: &'a State, } #[cfg(not(esp32))] -impl<'a, T> I2cFuture<'a, T> -where - T: Instance, -{ - pub fn new(event: Event, instance: &'a T) -> Self { - instance.register_block().int_ena().modify(|_, w| { +impl<'a> I2cFuture<'a> { + pub fn new(event: Event, instance: &'a impl Instance) -> Self { + instance.info().register_block().int_ena().modify(|_, w| { let w = match event { Event::EndDetect => w.end_detect().set_bit(), Event::TxComplete => w.trans_complete().set_bit(), @@ -655,11 +669,15 @@ where w }); - Self { event, instance } + Self { + event, + state: instance.state(), + info: instance.info(), + } } fn event_bit_is_clear(&self) -> bool { - let r = self.instance.register_block().int_ena().read(); + let r = self.info.register_block().int_ena().read(); match self.event { Event::EndDetect => r.end_detect().bit_is_clear(), @@ -670,7 +688,7 @@ where } fn check_error(&self) -> Result<(), Error> { - let r = self.instance.register_block().int_raw().read(); + let r = self.info.register_block().int_raw().read(); if r.arbitration_lost().bit_is_set() { return Err(Error::ArbitrationLost); @@ -693,7 +711,7 @@ where #[cfg(not(esp32))] if r.trans_complete().bit_is_set() && self - .instance + .info .register_block() .sr() .read() @@ -708,14 +726,11 @@ where } #[cfg(not(esp32))] -impl<'a, T> core::future::Future for I2cFuture<'a, T> -where - T: Instance, -{ +impl core::future::Future for I2cFuture<'_> { type Output = Result<(), Error>; fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { - WAKERS[self.instance.i2c_number()].register(ctx.waker()); + self.state.waker.register(ctx.waker()); let error = self.check_error(); @@ -737,7 +752,7 @@ where { /// Configure the I2C peripheral to operate in blocking mode. pub fn into_blocking(self) -> I2c<'d, Blocking, T> { - crate::interrupt::disable(Cpu::current(), self.i2c.interrupt()); + crate::interrupt::disable(Cpu::current(), self.info().interrupt); I2c { i2c: self.i2c, @@ -756,7 +771,7 @@ where self.wait_for_completion(false).await?; for byte in buffer.iter_mut() { - *byte = read_fifo(self.i2c.register_block()); + *byte = read_fifo(self.info().register_block()); } Ok(()) @@ -764,7 +779,7 @@ where #[cfg(not(any(esp32, esp32s2)))] async fn read_all_from_fifo(&self, buffer: &mut [u8]) -> Result<(), Error> { - self.i2c.read_all_from_fifo(buffer) + self.info().read_all_from_fifo(buffer) } #[cfg(any(esp32, esp32s2))] @@ -774,8 +789,8 @@ where } for b in bytes { - write_fifo(self.i2c.register_block(), *b); - self.i2c.check_errors()?; + write_fifo(self.info().register_block(), *b); + self.info().check_errors()?; } Ok(()) @@ -785,11 +800,11 @@ where async fn write_remaining_tx_fifo(&self, start_index: usize, bytes: &[u8]) -> Result<(), Error> { let mut index = start_index; loop { - self.i2c.check_errors()?; + self.info().check_errors()?; I2cFuture::new(Event::TxFifoWatermark, &*self.i2c).await?; - self.i2c + self.info() .register_block() .int_clr() .write(|w| w.txfifo_wm().clear_bit_by_one()); @@ -800,14 +815,14 @@ where break Ok(()); } - write_fifo(self.i2c.register_block(), bytes[index]); + write_fifo(self.info().register_block(), bytes[index]); index += 1; } } #[cfg(not(esp32))] async fn wait_for_completion(&self, end_only: bool) -> Result<(), Error> { - self.i2c.check_errors()?; + self.info().check_errors()?; if end_only { I2cFuture::new(Event::EndDetect, &*self.i2c).await?; @@ -823,7 +838,7 @@ where embassy_futures::select::Either::Second(res) => res?, } } - self.i2c.check_all_commands_done()?; + self.info().check_all_commands_done()?; Ok(()) } @@ -835,9 +850,9 @@ where let mut tout = MAX_ITERATIONS / 10; // adjust the timeout because we are yielding in the loop loop { - let interrupts = self.i2c.register_block().int_raw().read(); + let interrupts = self.info().register_block().int_raw().read(); - self.i2c.check_errors()?; + self.info().check_errors()?; // Handle completion cases // A full transmission was completed (either a STOP condition or END was @@ -855,7 +870,7 @@ where embassy_futures::yield_now().await; } - self.i2c.check_all_commands_done()?; + self.info().check_all_commands_done()?; Ok(()) } @@ -885,18 +900,20 @@ where } // Reset FIFO and command list - self.i2c.reset_fifo(); - self.i2c.reset_command_list(); + self.info().reset_fifo(); + self.info().reset_command_list(); if start { add_cmd(cmd_iterator, Command::Start)?; } - self.i2c.setup_write(address, bytes, start, cmd_iterator)?; + self.i2c + .info() + .setup_write(address, bytes, start, cmd_iterator)?; add_cmd( cmd_iterator, if stop { Command::Stop } else { Command::End }, )?; - let index = self.i2c.fill_tx_fifo(bytes); - self.i2c.start_transmission(); + let index = self.info().fill_tx_fifo(bytes); + self.info().start_transmission(); // Fill the FIFO with the remaining bytes: self.write_remaining_tx_fifo(index, bytes).await?; @@ -933,18 +950,19 @@ where } // Reset FIFO and command list - self.i2c.reset_fifo(); - self.i2c.reset_command_list(); + self.info().reset_fifo(); + self.info().reset_command_list(); if start { add_cmd(cmd_iterator, Command::Start)?; } self.i2c + .info() .setup_read(address, buffer, start, will_continue, cmd_iterator)?; add_cmd( cmd_iterator, if stop { Command::Stop } else { Command::End }, )?; - self.i2c.start_transmission(); + self.info().start_transmission(); self.read_all_from_fifo(buffer).await?; self.wait_for_completion(!stop).await?; Ok(()) @@ -955,9 +973,9 @@ where let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); for (idx, chunk) in buffer.chunks(I2C_CHUNK_SIZE).enumerate() { // Clear all I2C interrupts - self.i2c.clear_all_interrupts(); + self.info().clear_all_interrupts(); - let cmd_iterator = &mut self.i2c.register_block().comd_iter(); + let cmd_iterator = &mut self.info().register_block().comd_iter(); self.write_operation( address, @@ -977,9 +995,9 @@ where let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); for (idx, chunk) in buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { // Clear all I2C interrupts - self.i2c.clear_all_interrupts(); + self.info().clear_all_interrupts(); - let cmd_iterator = &mut self.i2c.register_block().comd_iter(); + let cmd_iterator = &mut self.info().register_block().comd_iter(); self.read_operation( address, @@ -1007,9 +1025,9 @@ where let read_count = read_buffer.len().div_ceil(I2C_CHUNK_SIZE); for (idx, chunk) in write_buffer.chunks(I2C_CHUNK_SIZE).enumerate() { // Clear all I2C interrupts - self.i2c.clear_all_interrupts(); + self.info().clear_all_interrupts(); - let cmd_iterator = &mut self.i2c.register_block().comd_iter(); + let cmd_iterator = &mut self.info().register_block().comd_iter(); self.write_operation( address, @@ -1023,9 +1041,9 @@ where for (idx, chunk) in read_buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { // Clear all I2C interrupts - self.i2c.clear_all_interrupts(); + self.info().clear_all_interrupts(); - let cmd_iterator = &mut self.i2c.register_block().comd_iter(); + let cmd_iterator = &mut self.info().register_block().comd_iter(); self.read_operation( address, @@ -1084,9 +1102,9 @@ where let next_op = op_iter.peek().map(|v| v.kind()); let kind = op.kind(); // Clear all I2C interrupts - self.i2c.clear_all_interrupts(); + self.info().clear_all_interrupts(); - let cmd_iterator = &mut self.i2c.register_block().comd_iter(); + let cmd_iterator = &mut self.info().register_block().comd_iter(); match op { Operation::Write(buffer) => { // execute a write operation: @@ -1139,63 +1157,180 @@ where } } -fn handler(regs: &RegisterBlock) { +fn async_handler(info: &Info, state: &State) { + let regs = info.register_block(); regs.int_ena().modify(|_, w| { w.end_detect().clear_bit(); w.trans_complete().clear_bit(); w.arbitration_lost().clear_bit(); - w.time_out().clear_bit() - }); + w.time_out().clear_bit(); - #[cfg(not(any(esp32, esp32s2)))] - regs.int_ena().modify(|_, w| w.txfifo_wm().clear_bit()); + #[cfg(not(any(esp32, esp32s2)))] + w.txfifo_wm().clear_bit(); - #[cfg(not(esp32))] - regs.int_ena().modify(|_, w| w.nack().clear_bit()); + cfg_if::cfg_if! { + if #[cfg(esp32)] { + w.ack_err().clear_bit() + } else { + w.nack().clear_bit() + } + } + }); - #[cfg(esp32)] - regs.int_ena().modify(|_, w| w.ack_err().clear_bit()); + state.waker.wake(); } -#[handler] -pub(super) fn i2c0_handler() { - let regs = unsafe { crate::peripherals::I2C0::steal() }; - handler(regs.register_block()); - - WAKERS[0].wake(); +/// Sets the filter with a supplied threshold in clock cycles for which a +/// pulse must be present to pass the filter +fn set_filter( + register_block: &RegisterBlock, + sda_threshold: Option, + scl_threshold: Option, +) { + cfg_if::cfg_if! { + if #[cfg(any(esp32, esp32s2))] { + register_block.sda_filter_cfg().modify(|_, w| { + if let Some(threshold) = sda_threshold { + unsafe { w.sda_filter_thres().bits(threshold) }; + } + w.sda_filter_en().bit(sda_threshold.is_some()) + }); + register_block.scl_filter_cfg().modify(|_, w| { + if let Some(threshold) = scl_threshold { + unsafe { w.scl_filter_thres().bits(threshold) }; + } + w.scl_filter_en().bit(scl_threshold.is_some()) + }); + } else { + register_block.filter_cfg().modify(|_, w| { + if let Some(threshold) = sda_threshold { + unsafe { w.sda_filter_thres().bits(threshold) }; + } + if let Some(threshold) = scl_threshold { + unsafe { w.scl_filter_thres().bits(threshold) }; + } + w.sda_filter_en().bit(sda_threshold.is_some()); + w.scl_filter_en().bit(scl_threshold.is_some()) + }); + } + } } -#[cfg(i2c1)] -#[handler] -pub(super) fn i2c1_handler() { - let regs = unsafe { crate::peripherals::I2C1::steal() }; - handler(regs.register_block()); +#[allow(clippy::too_many_arguments, unused)] +/// Configures the clock and timing parameters for the I2C peripheral. +fn configure_clock( + register_block: &RegisterBlock, + sclk_div: u32, + scl_low_period: u32, + scl_high_period: u32, + scl_wait_high_period: u32, + sda_hold_time: u32, + sda_sample_time: u32, + scl_rstart_setup_time: u32, + scl_stop_setup_time: u32, + scl_start_hold_time: u32, + scl_stop_hold_time: u32, + time_out_value: u32, + time_out_en: bool, +) { + unsafe { + // divider + #[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2, esp32s3))] + register_block.clk_conf().modify(|_, w| { + w.sclk_sel().clear_bit(); + w.sclk_div_num().bits((sclk_div - 1) as u8) + }); - WAKERS[1].wake(); + // scl period + register_block + .scl_low_period() + .write(|w| w.scl_low_period().bits(scl_low_period as u16)); + + register_block.scl_high_period().write(|w| { + #[cfg(not(esp32))] // ESP32 does not have a wait_high field + w.scl_wait_high_period() + .bits(scl_wait_high_period.try_into().unwrap()); + w.scl_high_period().bits(scl_high_period as u16) + }); + + // sda sample + register_block + .sda_hold() + .write(|w| w.time().bits(sda_hold_time as u16)); + register_block + .sda_sample() + .write(|w| w.time().bits(sda_sample_time as u16)); + + // setup + register_block + .scl_rstart_setup() + .write(|w| w.time().bits(scl_rstart_setup_time as u16)); + register_block + .scl_stop_setup() + .write(|w| w.time().bits(scl_stop_setup_time as u16)); + + // hold + register_block + .scl_start_hold() + .write(|w| w.time().bits(scl_start_hold_time as u16)); + register_block + .scl_stop_hold() + .write(|w| w.time().bits(scl_stop_hold_time as u16)); + + // The ESP32 variant does not have an enable flag for the + // timeout mechanism + cfg_if::cfg_if! { + if #[cfg(esp32)] { + // timeout + register_block + .to() + .write(|w| w.time_out().bits(time_out_value)); + } else { + // timeout + // FIXME: Enable timout for other chips! + #[allow(clippy::useless_conversion)] + register_block + .to() + .write(|w| w.time_out_en().bit(time_out_en) + .time_out_value() + .bits(time_out_value.try_into().unwrap()) + ); + } + } + } } -/// I2C Peripheral Instance -#[doc(hidden)] -pub trait Instance: Peripheral

+ PeripheralMarker + Into + 'static { - /// Returns the interrupt associated with this I2C peripheral. - fn interrupt(&self) -> crate::peripherals::Interrupt; +/// Peripheral data describing a particular I2C instance. +pub struct Info { + /// Pointer to the register block for this I2C instance. + /// + /// Use [Self::register_block] to access the register block. + pub register_block: *const RegisterBlock, - fn async_handler(&self) -> InterruptHandler; + /// Interrupt handler for the asynchronous operations of this I2C instance. + pub async_handler: InterruptHandler, - /// Returns the SCL output signal for this I2C peripheral. - fn scl_output_signal(&self) -> OutputSignal; - /// Returns the SCL input signal for this I2C peripheral. - fn scl_input_signal(&self) -> InputSignal; - /// Returns the SDA output signal for this I2C peripheral. - fn sda_output_signal(&self) -> OutputSignal; - /// Returns the SDA input signal for this I2C peripheral. - fn sda_input_signal(&self) -> InputSignal; + /// Interrupt for this I2C instance. + pub interrupt: Interrupt, - /// Returns a reference to the register block of the I2C peripheral. - fn register_block(&self) -> &RegisterBlock; + /// SCL output signal. + pub scl_output: OutputSignal, - /// Returns the I2C peripheral's index number. - fn i2c_number(&self) -> usize; + /// SCL input signal. + pub scl_input: InputSignal, + + /// SDA output signal. + pub sda_output: OutputSignal, + + /// SDA input signal. + pub sda_input: InputSignal, +} + +impl Info { + /// Returns the register block for this I2C instance. + pub fn register_block(&self) -> &RegisterBlock { + unsafe { &*self.register_block } + } /// Configures the I2C peripheral with the specified frequency, clocks, and /// optional timeout. @@ -1220,7 +1355,7 @@ pub trait Instance: Peripheral

+ PeripheralMarker + Into + 'st // Configure filter // FIXME if we ever change this we need to adapt `set_frequency` for ESP32 - self.set_filter(Some(7), Some(7)); + set_filter(self.register_block(), Some(7), Some(7)); // Configure frequency let clocks = Clocks::get(); @@ -1271,36 +1406,6 @@ pub trait Instance: Peripheral

+ PeripheralMarker + Into + 'st } } - /// Sets the filter with a supplied threshold in clock cycles for which a - /// pulse must be present to pass the filter - fn set_filter(&self, sda_threshold: Option, scl_threshold: Option) { - cfg_if::cfg_if! { - if #[cfg(any(esp32, esp32s2))] { - let sda_register = &self.register_block().sda_filter_cfg(); - let scl_register = &self.register_block().scl_filter_cfg(); - } else { - let sda_register = &self.register_block().filter_cfg(); - let scl_register = &self.register_block().filter_cfg(); - } - } - - match sda_threshold { - Some(threshold) => { - sda_register.modify(|_, w| unsafe { w.sda_filter_thres().bits(threshold) }); - sda_register.modify(|_, w| w.sda_filter_en().set_bit()); - } - None => sda_register.modify(|_, w| w.sda_filter_en().clear_bit()), - } - - match scl_threshold { - Some(threshold) => { - scl_register.modify(|_, w| unsafe { w.scl_filter_thres().bits(threshold) }); - scl_register.modify(|_, w| w.scl_filter_en().set_bit()); - } - None => scl_register.modify(|_, w| w.scl_filter_en().clear_bit()), - } - } - #[cfg(esp32)] /// Sets the frequency of the I2C interface by calculating and applying the /// associated timings - corresponds to i2c_ll_cal_bus_clk and @@ -1358,7 +1463,8 @@ pub trait Instance: Peripheral

+ PeripheralMarker + Into + 'st let scl_start_hold_time = hold; let scl_stop_hold_time = hold; - self.configure_clock( + configure_clock( + self.register_block(), 0, scl_low_period, scl_high_period, @@ -1411,7 +1517,8 @@ pub trait Instance: Peripheral

+ PeripheralMarker + Into + 'st let scl_stop_hold_time = hold; let time_out_en = true; - self.configure_clock( + configure_clock( + self.register_block(), 0, scl_low_period, scl_high_period, @@ -1484,7 +1591,8 @@ pub trait Instance: Peripheral

+ PeripheralMarker + Into + 'st let scl_stop_hold_time = hold - 1; let time_out_en = true; - self.configure_clock( + configure_clock( + self.register_block(), clkm_div, scl_low_period, scl_high_period, @@ -1500,91 +1608,6 @@ pub trait Instance: Peripheral

+ PeripheralMarker + Into + 'st ); } - #[allow(clippy::too_many_arguments, unused)] - /// Configures the clock and timing parameters for the I2C peripheral. - fn configure_clock( - &self, - sclk_div: u32, - scl_low_period: u32, - scl_high_period: u32, - scl_wait_high_period: u32, - sda_hold_time: u32, - sda_sample_time: u32, - scl_rstart_setup_time: u32, - scl_stop_setup_time: u32, - scl_start_hold_time: u32, - scl_stop_hold_time: u32, - time_out_value: u32, - time_out_en: bool, - ) { - let register_block = self.register_block(); - unsafe { - // divider - #[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2, esp32s3))] - register_block.clk_conf().modify(|_, w| { - w.sclk_sel().clear_bit(); - w.sclk_div_num().bits((sclk_div - 1) as u8) - }); - - // scl period - register_block - .scl_low_period() - .write(|w| w.scl_low_period().bits(scl_low_period as u16)); - - register_block.scl_high_period().write(|w| { - #[cfg(not(esp32))] // ESP32 does not have a wait_high field - w.scl_wait_high_period() - .bits(scl_wait_high_period.try_into().unwrap()); - w.scl_high_period().bits(scl_high_period as u16) - }); - - // sda sample - register_block - .sda_hold() - .write(|w| w.time().bits(sda_hold_time as u16)); - register_block - .sda_sample() - .write(|w| w.time().bits(sda_sample_time as u16)); - - // setup - register_block - .scl_rstart_setup() - .write(|w| w.time().bits(scl_rstart_setup_time as u16)); - register_block - .scl_stop_setup() - .write(|w| w.time().bits(scl_stop_setup_time as u16)); - - // hold - register_block - .scl_start_hold() - .write(|w| w.time().bits(scl_start_hold_time as u16)); - register_block - .scl_stop_hold() - .write(|w| w.time().bits(scl_stop_hold_time as u16)); - - // The ESP32 variant does not have an enable flag for the - // timeout mechanism - cfg_if::cfg_if! { - if #[cfg(esp32)] { - // timeout - register_block - .to() - .write(|w| w.time_out().bits(time_out_value)); - } else { - // timeout - // FIXME: Enable timout for other chips! - #[allow(clippy::useless_conversion)] - register_block - .to() - .write(|w| w.time_out_en().bit(time_out_en) - .time_out_value() - .bits(time_out_value.try_into().unwrap()) - ); - } - } - } - } - /// Configures the I2C peripheral for a write operation. /// - `addr` is the address of the slave device. /// - `bytes` is the data two be sent. @@ -2135,6 +2158,39 @@ pub trait Instance: Peripheral

+ PeripheralMarker + Into + 'st } } +impl PartialEq for Info { + fn eq(&self, other: &Self) -> bool { + self.register_block == other.register_block + } +} + +unsafe impl Sync for Info {} + +/// Peripheral state for an I2C instance. +pub struct State { + /// Waker for the asynchronous operations. + pub waker: AtomicWaker, +} + +/// I2C Peripheral Instance +#[doc(hidden)] +pub trait Instance: Peripheral

+ PeripheralMarker + Into + 'static { + /// Returns the peripheral data and state describing this instance. + fn parts(&self) -> (&Info, &State); + + /// Returns the peripheral data describing this instance. + #[inline(always)] + fn info(&self) -> &Info { + self.parts().0 + } + + /// Returns the peripheral state for this instance. + #[inline(always)] + fn state(&self) -> &State { + self.parts().1 + } +} + /// Adds a command to the I2C command sequence. fn add_cmd<'a, I>(cmd_iterator: &mut I, command: Command) -> Result<(), Error> where @@ -2204,105 +2260,38 @@ fn write_fifo(register_block: &RegisterBlock, data: u8) { } } -impl PeripheralMarker for crate::peripherals::I2C0 { - #[inline(always)] - fn peripheral(&self) -> crate::system::Peripheral { - crate::system::Peripheral::I2cExt0 - } -} - -impl Instance for crate::peripherals::I2C0 { - #[inline(always)] - fn async_handler(&self) -> InterruptHandler { - i2c0_handler - } - - #[inline(always)] - fn scl_output_signal(&self) -> OutputSignal { - OutputSignal::I2CEXT0_SCL - } - - #[inline(always)] - fn scl_input_signal(&self) -> InputSignal { - InputSignal::I2CEXT0_SCL - } - - #[inline(always)] - fn sda_output_signal(&self) -> OutputSignal { - OutputSignal::I2CEXT0_SDA - } - - #[inline(always)] - fn sda_input_signal(&self) -> InputSignal { - InputSignal::I2CEXT0_SDA - } - - #[inline(always)] - fn register_block(&self) -> &RegisterBlock { - self - } - - #[inline(always)] - fn i2c_number(&self) -> usize { - 0 - } +macro_rules! instance { + ($inst:ident, $scl:ident, $sda:ident, $interrupt:ident) => { + impl Instance for crate::peripherals::$inst { + fn parts(&self) -> (&Info, &State) { + #[crate::macros::handler] + pub(super) fn irq_handler() { + async_handler(&PERIPHERAL, &STATE); + } - #[inline(always)] - fn interrupt(&self) -> crate::peripherals::Interrupt { - crate::peripherals::Interrupt::I2C_EXT0 - } -} + static STATE: State = State { + waker: AtomicWaker::new(), + }; -#[cfg(i2c1)] -impl PeripheralMarker for crate::peripherals::I2C1 { - #[inline(always)] - fn peripheral(&self) -> crate::system::Peripheral { - crate::system::Peripheral::I2cExt1 - } + static PERIPHERAL: Info = Info { + register_block: crate::peripherals::$inst::ptr(), + async_handler: irq_handler, + interrupt: Interrupt::$interrupt, + scl_output: OutputSignal::$scl, + scl_input: InputSignal::$scl, + sda_output: OutputSignal::$sda, + sda_input: InputSignal::$sda, + }; + (&PERIPHERAL, &STATE) + } + } + }; } +#[cfg(i2c0)] +instance!(I2C0, I2CEXT0_SCL, I2CEXT0_SDA, I2C_EXT0); #[cfg(i2c1)] -impl Instance for crate::peripherals::I2C1 { - #[inline(always)] - fn async_handler(&self) -> InterruptHandler { - i2c1_handler - } - - #[inline(always)] - fn scl_output_signal(&self) -> OutputSignal { - OutputSignal::I2CEXT1_SCL - } - - #[inline(always)] - fn scl_input_signal(&self) -> InputSignal { - InputSignal::I2CEXT1_SCL - } - - #[inline(always)] - fn sda_output_signal(&self) -> OutputSignal { - OutputSignal::I2CEXT1_SDA - } - - #[inline(always)] - fn sda_input_signal(&self) -> InputSignal { - InputSignal::I2CEXT1_SDA - } - - #[inline(always)] - fn register_block(&self) -> &RegisterBlock { - self - } - - #[inline(always)] - fn i2c_number(&self) -> usize { - 1 - } - - #[inline(always)] - fn interrupt(&self) -> crate::peripherals::Interrupt { - crate::peripherals::Interrupt::I2C_EXT1 - } -} +instance!(I2C1, I2CEXT1_SCL, I2CEXT1_SDA, I2C_EXT1); crate::any_peripheral! { /// Represents any I2C peripheral. @@ -2321,14 +2310,7 @@ impl Instance for AnyI2c { #[cfg(i2c1)] AnyI2cInner::I2c1(i2c) => i2c, } { - fn interrupt(&self) -> crate::peripherals::Interrupt; - fn async_handler(&self) -> InterruptHandler; - fn scl_output_signal(&self) -> OutputSignal; - fn scl_input_signal(&self) -> InputSignal; - fn sda_output_signal(&self) -> OutputSignal; - fn sda_input_signal(&self) -> InputSignal; - fn register_block(&self) -> &RegisterBlock; - fn i2c_number(&self) -> usize; + fn parts(&self) -> (&Info, &State); } } } diff --git a/esp-hal/src/soc/esp32/peripherals.rs b/esp-hal/src/soc/esp32/peripherals.rs index c0bd6f2732b..3d1fbadfd78 100644 --- a/esp-hal/src/soc/esp32/peripherals.rs +++ b/esp-hal/src/soc/esp32/peripherals.rs @@ -36,8 +36,8 @@ crate::peripherals! { GPIO <= GPIO (GPIO,GPIO_NMI), GPIO_SD <= GPIO_SD, HINF <= HINF, - I2C0 <= I2C0, - I2C1 <= I2C1, + [I2cExt0] I2C0 <= I2C0, + [I2cExt1] I2C1 <= I2C1, [I2s0] I2S0 <= I2S0 (I2S0), [I2s1] I2S1 <= I2S1 (I2S1), IO_MUX <= IO_MUX, diff --git a/esp-hal/src/soc/esp32c2/peripherals.rs b/esp-hal/src/soc/esp32c2/peripherals.rs index 310c62416dc..68c00598bb9 100644 --- a/esp-hal/src/soc/esp32c2/peripherals.rs +++ b/esp-hal/src/soc/esp32c2/peripherals.rs @@ -29,7 +29,7 @@ crate::peripherals! { EFUSE <= EFUSE, EXTMEM <= EXTMEM, GPIO <= GPIO (GPIO,GPIO_NMI), - I2C0 <= I2C0, + [I2cExt0] I2C0 <= I2C0, INTERRUPT_CORE0 <= INTERRUPT_CORE0, IO_MUX <= IO_MUX, LEDC <= LEDC, diff --git a/esp-hal/src/soc/esp32c3/peripherals.rs b/esp-hal/src/soc/esp32c3/peripherals.rs index 12296386d6a..5b762d4a2d5 100644 --- a/esp-hal/src/soc/esp32c3/peripherals.rs +++ b/esp-hal/src/soc/esp32c3/peripherals.rs @@ -33,7 +33,7 @@ crate::peripherals! { GPIO <= GPIO (GPIO,GPIO_NMI), GPIO_SD <= GPIO_SD, HMAC <= HMAC, - I2C0 <= I2C0, + [I2cExt0] I2C0 <= I2C0, [I2s0] I2S0 <= I2S0 (I2S0), INTERRUPT_CORE0 <= INTERRUPT_CORE0, IO_MUX <= IO_MUX, diff --git a/esp-hal/src/soc/esp32c6/peripherals.rs b/esp-hal/src/soc/esp32c6/peripherals.rs index 8c5e2de92c7..784f957e796 100644 --- a/esp-hal/src/soc/esp32c6/peripherals.rs +++ b/esp-hal/src/soc/esp32c6/peripherals.rs @@ -36,7 +36,7 @@ crate::peripherals! { HMAC <= HMAC, HP_APM <= HP_APM, HP_SYS <= HP_SYS, - I2C0 <= I2C0, + [I2cExt0] I2C0 <= I2C0, [I2s0] I2S0 <= I2S0 (I2S0), IEEE802154 <= IEEE802154, INTERRUPT_CORE0 <= INTERRUPT_CORE0, diff --git a/esp-hal/src/soc/esp32h2/peripherals.rs b/esp-hal/src/soc/esp32h2/peripherals.rs index 37eb1285f68..ccc359decbf 100644 --- a/esp-hal/src/soc/esp32h2/peripherals.rs +++ b/esp-hal/src/soc/esp32h2/peripherals.rs @@ -33,8 +33,8 @@ crate::peripherals! { HMAC <= HMAC, HP_APM <= HP_APM, HP_SYS <= HP_SYS, - I2C0 <= I2C0, - I2C1 <= I2C1, + [I2cExt0] I2C0 <= I2C0, + [I2cExt1] I2C1 <= I2C1, [I2s0] I2S0 <= I2S0 (I2S0), IEEE802154 <= IEEE802154, INTERRUPT_CORE0 <= INTERRUPT_CORE0, diff --git a/esp-hal/src/soc/esp32s2/peripherals.rs b/esp-hal/src/soc/esp32s2/peripherals.rs index d6bfb5b831f..28e776daa62 100644 --- a/esp-hal/src/soc/esp32s2/peripherals.rs +++ b/esp-hal/src/soc/esp32s2/peripherals.rs @@ -33,8 +33,8 @@ crate::peripherals! { GPIO <= GPIO (GPIO,GPIO_NMI), GPIO_SD <= GPIO_SD, HMAC <= HMAC, - I2C0 <= I2C0, - I2C1 <= I2C1, + [I2cExt0] I2C0 <= I2C0, + [I2cExt1] I2C1 <= I2C1, [I2s0] I2S0 <= I2S0 (I2S0), INTERRUPT_CORE0 <= INTERRUPT_CORE0, IO_MUX <= IO_MUX, diff --git a/esp-hal/src/soc/esp32s3/peripherals.rs b/esp-hal/src/soc/esp32s3/peripherals.rs index 30159a65367..dc8bfe2b48d 100644 --- a/esp-hal/src/soc/esp32s3/peripherals.rs +++ b/esp-hal/src/soc/esp32s3/peripherals.rs @@ -34,8 +34,8 @@ crate::peripherals! { GPIO <= GPIO (GPIO,GPIO_NMI), GPIO_SD <= GPIO_SD, HMAC <= HMAC, - I2C0 <= I2C0, - I2C1 <= I2C1, + [I2cExt0] I2C0 <= I2C0, + [I2cExt1] I2C1 <= I2C1, [I2s0] I2S0 <= I2S0 (I2S0), [I2s1] I2S1 <= I2S1 (I2S1), INTERRUPT_CORE0 <= INTERRUPT_CORE0, From bd7c60a9bf5b6651ea29213525775ea358bec1ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Thu, 31 Oct 2024 10:45:53 +0100 Subject: [PATCH 02/18] Move driver --- esp-hal/src/{i2c.rs => i2c/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename esp-hal/src/{i2c.rs => i2c/mod.rs} (100%) diff --git a/esp-hal/src/i2c.rs b/esp-hal/src/i2c/mod.rs similarity index 100% rename from esp-hal/src/i2c.rs rename to esp-hal/src/i2c/mod.rs From 673633b3389fa12f3cf4f40a80f81d854322f65e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 4 Nov 2024 11:43:42 +0100 Subject: [PATCH 03/18] Extract modules --- esp-hal/src/i2c/lp_i2c.rs | 341 +++++++++++++++++++++++++ esp-hal/src/i2c/mod.rs | 438 +------------------------------- esp-hal/src/i2c/support/eh.rs | 43 ++++ esp-hal/src/i2c/support/eh02.rs | 42 +++ esp-hal/src/i2c/support/eh_a.rs | 20 ++ esp-hal/src/i2c/support/mod.rs | 3 + 6 files changed, 452 insertions(+), 435 deletions(-) create mode 100644 esp-hal/src/i2c/lp_i2c.rs create mode 100644 esp-hal/src/i2c/support/eh.rs create mode 100644 esp-hal/src/i2c/support/eh02.rs create mode 100644 esp-hal/src/i2c/support/eh_a.rs create mode 100644 esp-hal/src/i2c/support/mod.rs diff --git a/esp-hal/src/i2c/lp_i2c.rs b/esp-hal/src/i2c/lp_i2c.rs new file mode 100644 index 00000000000..2a16c94809e --- /dev/null +++ b/esp-hal/src/i2c/lp_i2c.rs @@ -0,0 +1,341 @@ +//! Low-power I2C driver + +use fugit::HertzU32; + +use crate::{ + gpio::lp_io::LowPowerOutputOpenDrain, + peripherals::{LP_CLKRST, LP_I2C0}, +}; + +const LP_I2C_FILTER_CYC_NUM_DEF: u8 = 7; + +/// I2C-specific transmission errors +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The transmission exceeded the FIFO size. + ExceedingFifo, + /// The acknowledgment check failed. + AckCheckFailed, + /// A timeout occurred during transmission. + TimeOut, + /// The arbitration for the bus was lost. + ArbitrationLost, + /// The execution of the I2C command was incomplete. + ExecIncomplete, + /// The number of commands issued exceeded the limit. + CommandNrExceeded, + /// The response received from the I2C device was invalid. + InvalidResponse, +} + +#[allow(unused)] +enum OperationType { + Write = 0, + Read = 1, +} + +#[allow(unused)] +#[derive(Eq, PartialEq, Copy, Clone)] +enum Ack { + Ack, + Nack, +} + +#[derive(PartialEq)] +#[allow(unused)] +enum Command { + Start, + Stop, + End, + Write { + /// This bit is to set an expected ACK value for the transmitter. + ack_exp: Ack, + /// Enables checking the ACK value received against the ack_exp + /// value. + ack_check_en: bool, + /// Length of data (in bytes) to be written. The maximum length is + /// 255, while the minimum is 1. + length: u8, + }, + Read { + /// Indicates whether the receiver will send an ACK after this byte + /// has been received. + ack_value: Ack, + /// Length of data (in bytes) to be read. The maximum length is 255, + /// while the minimum is 1. + length: u8, + }, +} + +// https://github.com/espressif/esp-idf/blob/master/components/ulp/lp_core/lp_core_i2c.c#L122 +// TX/RX RAM size is 16*8 bit +// TX RX FIFO has 16 bit depth +// The clock source of APB_CLK in LP_I2C is CLK_AON_FAST. +// Configure LP_I2C_SCLK_SEL to select the clock source for I2C_SCLK. +// When LP_I2C_SCLK_SEL is 0, select CLK_ROOT_FAST as clock source, +// and when LP_I2C_SCLK_SEL is 1, select CLK _XTALD2 as the clock source. +// Configure LP_EXT_I2C_CK_EN high to enable the clock source of I2C_SCLK. +// Adjust the timing registers accordingly when the clock frequency changes. + +/// Represents a Low-Power I2C peripheral. +pub struct LpI2c { + i2c: LP_I2C0, +} + +impl LpI2c { + /// Creates a new instance of the `LpI2c` peripheral. + pub fn new( + i2c: LP_I2C0, + _sda: LowPowerOutputOpenDrain<'_, 6>, + _scl: LowPowerOutputOpenDrain<'_, 7>, + frequency: HertzU32, + ) -> Self { + let me = Self { i2c }; + + // Configure LP I2C GPIOs + // Initialize IO Pins + let lp_io = unsafe { &*crate::peripherals::LP_IO::PTR }; + let lp_aon = unsafe { &*crate::peripherals::LP_AON::PTR }; + let lp_peri = unsafe { &*crate::peripherals::LP_PERI::PTR }; + + unsafe { + // FIXME: use GPIO APIs to configure pins + lp_aon + .gpio_mux() + .modify(|r, w| w.sel().bits(r.sel().bits() | (1 << 6))); + lp_aon + .gpio_mux() + .modify(|r, w| w.sel().bits(r.sel().bits() | (1 << 7))); + lp_io.gpio(6).modify(|_, w| w.mcu_sel().bits(1)); // TODO + + lp_io.gpio(7).modify(|_, w| w.mcu_sel().bits(1)); + + // Set output mode to Normal + lp_io.pin(6).modify(|_, w| w.pad_driver().set_bit()); + // Enable output (writing to write-1-to-set register, then internally the + // `GPIO_OUT_REG` will be set) + lp_io + .out_enable_w1ts() + .write(|w| w.enable_w1ts().bits(1 << 6)); + // Enable input + lp_io.gpio(6).modify(|_, w| w.fun_ie().set_bit()); + + // Disable pulldown (enable internal weak pull-down) + lp_io.gpio(6).modify(|_, w| w.fun_wpd().clear_bit()); + // Enable pullup + lp_io.gpio(6).modify(|_, w| w.fun_wpu().set_bit()); + + // Same process for SCL pin + lp_io.pin(7).modify(|_, w| w.pad_driver().set_bit()); + // Enable output (writing to write-1-to-set register, then internally the + // `GPIO_OUT_REG` will be set) + lp_io + .out_enable_w1ts() + .write(|w| w.enable_w1ts().bits(1 << 7)); + // Enable input + lp_io.gpio(7).modify(|_, w| w.fun_ie().set_bit()); + // Disable pulldown (enable internal weak pull-down) + lp_io.gpio(7).modify(|_, w| w.fun_wpd().clear_bit()); + // Enable pullup + lp_io.gpio(7).modify(|_, w| w.fun_wpu().set_bit()); + + // Select LP I2C function for the SDA and SCL pins + lp_io.gpio(6).modify(|_, w| w.mcu_sel().bits(1)); + lp_io.gpio(7).modify(|_, w| w.mcu_sel().bits(1)); + } + + // Initialize LP I2C HAL */ + me.i2c.clk_conf().modify(|_, w| w.sclk_active().set_bit()); + + // Enable LP I2C controller clock + lp_peri + .clk_en() + .modify(|_, w| w.lp_ext_i2c_ck_en().set_bit()); + + lp_peri + .reset_en() + .modify(|_, w| w.lp_ext_i2c_reset_en().set_bit()); + lp_peri + .reset_en() + .modify(|_, w| w.lp_ext_i2c_reset_en().clear_bit()); + + // Set LP I2C source clock + unsafe { &*LP_CLKRST::PTR } + .lpperi() + .modify(|_, w| w.lp_i2c_clk_sel().clear_bit()); + + // Initialize LP I2C Master mode + me.i2c.ctr().modify(|_, w| unsafe { + // Clear register + w.bits(0); + // Use open drain output for SDA and SCL + w.sda_force_out().set_bit(); + w.scl_force_out().set_bit(); + // Ensure that clock is enabled + w.clk_en().set_bit() + }); + + // First, reset the fifo buffers + me.i2c.fifo_conf().modify(|_, w| w.nonfifo_en().clear_bit()); + + me.i2c.ctr().modify(|_, w| { + w.tx_lsb_first().clear_bit(); + w.rx_lsb_first().clear_bit() + }); + + me.reset_fifo(); + + // Set LP I2C source clock + unsafe { &*LP_CLKRST::PTR } + .lpperi() + .modify(|_, w| w.lp_i2c_clk_sel().clear_bit()); + + // Configure LP I2C timing paramters. source_clk is ignored for LP_I2C in this + // call + + let source_clk = 16_000_000; + let bus_freq = frequency.raw(); + + let clkm_div: u32 = source_clk / (bus_freq * 1024) + 1; + let sclk_freq: u32 = source_clk / clkm_div; + let half_cycle: u32 = sclk_freq / bus_freq / 2; + + // SCL + let scl_low = half_cycle; + // default, scl_wait_high < scl_high + // Make 80KHz as a boundary here, because when working at lower frequency, too + // much scl_wait_high will faster the frequency according to some + // hardware behaviors. + let scl_wait_high = if bus_freq >= 80 * 1000 { + half_cycle / 2 - 2 + } else { + half_cycle / 4 + }; + let scl_high = half_cycle - scl_wait_high; + let sda_hold = half_cycle / 4; + let sda_sample = half_cycle / 2; // TODO + scl_wait_high; + let setup = half_cycle; + let hold = half_cycle; + // default we set the timeout value to about 10 bus cycles + // log(20*half_cycle)/log(2) = log(half_cycle)/log(2) + log(20)/log(2) + let tout = (4 * 8 - (5 * half_cycle).leading_zeros()) + 2; + + // According to the Technical Reference Manual, the following timings must be + // subtracted by 1. However, according to the practical measurement and + // some hardware behaviour, if wait_high_period and scl_high minus one. + // The SCL frequency would be a little higher than expected. Therefore, the + // solution here is not to minus scl_high as well as scl_wait high, and + // the frequency will be absolutely accurate to all frequency + // to some extent. + let scl_low_period = scl_low - 1; + let scl_high_period = scl_high; + let scl_wait_high_period = scl_wait_high; + // sda sample + let sda_hold_time = sda_hold - 1; + let sda_sample_time = sda_sample - 1; + // setup + let scl_rstart_setup_time = setup - 1; + let scl_stop_setup_time = setup - 1; + // hold + let scl_start_hold_time = hold - 1; + let scl_stop_hold_time = hold - 1; + let time_out_value = tout; + let time_out_en = true; + + // Write data to registers + unsafe { + me.i2c.clk_conf().modify(|_, w| { + w.sclk_sel().clear_bit(); + w.sclk_div_num().bits((clkm_div - 1) as u8) + }); + + // scl period + me.i2c + .scl_low_period() + .write(|w| w.scl_low_period().bits(scl_low_period as u16)); + + me.i2c.scl_high_period().write(|w| { + w.scl_high_period().bits(scl_high_period as u16); + w.scl_wait_high_period().bits(scl_wait_high_period as u8) + }); + // sda sample + me.i2c + .sda_hold() + .write(|w| w.time().bits(sda_hold_time as u16)); + me.i2c + .sda_sample() + .write(|w| w.time().bits(sda_sample_time as u16)); + + // setup + me.i2c + .scl_rstart_setup() + .write(|w| w.time().bits(scl_rstart_setup_time as u16)); + me.i2c + .scl_stop_setup() + .write(|w| w.time().bits(scl_stop_setup_time as u16)); + + // hold + me.i2c + .scl_start_hold() + .write(|w| w.time().bits(scl_start_hold_time as u16)); + me.i2c + .scl_stop_hold() + .write(|w| w.time().bits(scl_stop_hold_time as u16)); + + me.i2c.to().write(|w| { + w.time_out_en().bit(time_out_en); + w.time_out_value().bits(time_out_value.try_into().unwrap()) + }); + } + + // Enable SDA and SCL filtering. This configuration matches the HP I2C filter + // config + + me.i2c + .filter_cfg() + .modify(|_, w| unsafe { w.sda_filter_thres().bits(LP_I2C_FILTER_CYC_NUM_DEF) }); + me.i2c + .filter_cfg() + .modify(|_, w| unsafe { w.scl_filter_thres().bits(LP_I2C_FILTER_CYC_NUM_DEF) }); + + me.i2c + .filter_cfg() + .modify(|_, w| w.sda_filter_en().set_bit()); + me.i2c + .filter_cfg() + .modify(|_, w| w.scl_filter_en().set_bit()); + + // Configure the I2C master to send a NACK when the Rx FIFO count is full + me.i2c.ctr().modify(|_, w| w.rx_full_ack_level().set_bit()); + + // Synchronize the config register values to the LP I2C peripheral clock + me.lp_i2c_update(); + + me + } + + /// Update I2C configuration + fn lp_i2c_update(&self) { + self.i2c.ctr().modify(|_, w| w.conf_upgate().set_bit()); + } + + /// Resets the transmit and receive FIFO buffers. + fn reset_fifo(&self) { + self.i2c + .fifo_conf() + .modify(|_, w| w.tx_fifo_rst().set_bit()); + + self.i2c + .fifo_conf() + .modify(|_, w| w.tx_fifo_rst().clear_bit()); + + self.i2c + .fifo_conf() + .modify(|_, w| w.rx_fifo_rst().set_bit()); + + self.i2c + .fifo_conf() + .modify(|_, w| w.rx_fifo_rst().clear_bit()); + } +} diff --git a/esp-hal/src/i2c/mod.rs b/esp-hal/src/i2c/mod.rs index 93502b43e53..b4885d0fafe 100644 --- a/esp-hal/src/i2c/mod.rs +++ b/esp-hal/src/i2c/mod.rs @@ -52,6 +52,8 @@ //! ``` //! [`embedded-hal`]: https://crates.io/crates/embedded-hal +mod support; + use core::marker::PhantomData; #[cfg(not(esp32))] use core::{ @@ -60,7 +62,6 @@ use core::{ }; use embassy_sync::waitqueue::AtomicWaker; -use embedded_hal::i2c::Operation as EhalOperation; use fugit::HertzU32; use crate::{ @@ -142,15 +143,6 @@ pub enum Operation<'a> { Read(&'a mut [u8]), } -impl<'a, 'b> From<&'a mut embedded_hal::i2c::Operation<'b>> for Operation<'a> { - fn from(value: &'a mut embedded_hal::i2c::Operation<'b>) -> Self { - match value { - embedded_hal::i2c::Operation::Write(buffer) => Operation::Write(buffer), - embedded_hal::i2c::Operation::Read(buffer) => Operation::Read(buffer), - } - } -} - impl<'a, 'b> From<&'a mut Operation<'b>> for Operation<'a> { fn from(value: &'a mut Operation<'b>) -> Self { match value { @@ -180,19 +172,6 @@ impl Operation<'_> { } } -impl embedded_hal::i2c::Error for Error { - fn kind(&self) -> embedded_hal::i2c::ErrorKind { - use embedded_hal::i2c::{ErrorKind, NoAcknowledgeSource}; - - match self { - Self::ExceedingFifo => ErrorKind::Overrun, - Self::ArbitrationLost => ErrorKind::ArbitrationLoss, - Self::AckCheckFailed => ErrorKind::NoAcknowledge(NoAcknowledgeSource::Unknown), - _ => ErrorKind::Other, - } - } -} - /// A generic I2C Command #[cfg_attr(feature = "debug", derive(Debug))] enum Command { @@ -252,61 +231,6 @@ pub struct I2c<'d, DM: Mode, T = AnyI2c> { timeout: Option, } -impl embedded_hal_02::blocking::i2c::Read for I2c<'_, Blocking, T> -where - T: Instance, -{ - type Error = Error; - - fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { - self.read(address, buffer) - } -} - -impl embedded_hal_02::blocking::i2c::Write for I2c<'_, Blocking, T> -where - T: Instance, -{ - type Error = Error; - - fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> { - self.write(addr, bytes) - } -} - -impl embedded_hal_02::blocking::i2c::WriteRead for I2c<'_, Blocking, T> -where - T: Instance, -{ - type Error = Error; - - fn write_read( - &mut self, - address: u8, - bytes: &[u8], - buffer: &mut [u8], - ) -> Result<(), Self::Error> { - self.write_read(address, bytes, buffer) - } -} - -impl embedded_hal::i2c::ErrorType for I2c<'_, DM, T> { - type Error = Error; -} - -impl embedded_hal::i2c::I2c for I2c<'_, DM, T> -where - T: Instance, -{ - fn transaction( - &mut self, - address: u8, - operations: &mut [embedded_hal::i2c::Operation<'_>], - ) -> Result<(), Self::Error> { - self.transaction_impl(address, operations.iter_mut().map(Operation::from)) - } -} - impl<'d, T, DM: Mode> I2c<'d, DM, T> where T: Instance, @@ -1143,20 +1067,6 @@ where } } -impl<'d, T> embedded_hal_async::i2c::I2c for I2c<'d, Async, T> -where - T: Instance, -{ - async fn transaction( - &mut self, - address: u8, - operations: &mut [EhalOperation<'_>], - ) -> Result<(), Self::Error> { - self.transaction_impl_async(address, operations.iter_mut().map(Operation::from)) - .await - } -} - fn async_handler(info: &Info, state: &State) { let regs = info.register_block(); regs.int_ena().modify(|_, w| { @@ -2316,346 +2226,4 @@ impl Instance for AnyI2c { } #[cfg(lp_i2c0)] -pub mod lp_i2c { - //! Low-power I2C driver - - use fugit::HertzU32; - - use crate::{ - gpio::lp_io::LowPowerOutputOpenDrain, - peripherals::{LP_CLKRST, LP_I2C0}, - }; - - const LP_I2C_FILTER_CYC_NUM_DEF: u8 = 7; - - /// I2C-specific transmission errors - #[derive(Debug, Clone, Copy, PartialEq)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - pub enum Error { - /// The transmission exceeded the FIFO size. - ExceedingFifo, - /// The acknowledgment check failed. - AckCheckFailed, - /// A timeout occurred during transmission. - TimeOut, - /// The arbitration for the bus was lost. - ArbitrationLost, - /// The execution of the I2C command was incomplete. - ExecIncomplete, - /// The number of commands issued exceeded the limit. - CommandNrExceeded, - /// The response received from the I2C device was invalid. - InvalidResponse, - } - - #[allow(unused)] - enum OperationType { - Write = 0, - Read = 1, - } - - #[allow(unused)] - #[derive(Eq, PartialEq, Copy, Clone)] - enum Ack { - Ack, - Nack, - } - - #[derive(PartialEq)] - #[allow(unused)] - enum Command { - Start, - Stop, - End, - Write { - /// This bit is to set an expected ACK value for the transmitter. - ack_exp: Ack, - /// Enables checking the ACK value received against the ack_exp - /// value. - ack_check_en: bool, - /// Length of data (in bytes) to be written. The maximum length is - /// 255, while the minimum is 1. - length: u8, - }, - Read { - /// Indicates whether the receiver will send an ACK after this byte - /// has been received. - ack_value: Ack, - /// Length of data (in bytes) to be read. The maximum length is 255, - /// while the minimum is 1. - length: u8, - }, - } - - // https://github.com/espressif/esp-idf/blob/master/components/ulp/lp_core/lp_core_i2c.c#L122 - // TX/RX RAM size is 16*8 bit - // TX RX FIFO has 16 bit depth - // The clock source of APB_CLK in LP_I2C is CLK_AON_FAST. - // Configure LP_I2C_SCLK_SEL to select the clock source for I2C_SCLK. - // When LP_I2C_SCLK_SEL is 0, select CLK_ROOT_FAST as clock source, - // and when LP_I2C_SCLK_SEL is 1, select CLK _XTALD2 as the clock source. - // Configure LP_EXT_I2C_CK_EN high to enable the clock source of I2C_SCLK. - // Adjust the timing registers accordingly when the clock frequency changes. - - /// Represents a Low-Power I2C peripheral. - pub struct LpI2c { - i2c: LP_I2C0, - } - - impl LpI2c { - /// Creates a new instance of the `LpI2c` peripheral. - pub fn new( - i2c: LP_I2C0, - _sda: LowPowerOutputOpenDrain<'_, 6>, - _scl: LowPowerOutputOpenDrain<'_, 7>, - frequency: HertzU32, - ) -> Self { - let me = Self { i2c }; - - // Configure LP I2C GPIOs - // Initialize IO Pins - let lp_io = unsafe { &*crate::peripherals::LP_IO::PTR }; - let lp_aon = unsafe { &*crate::peripherals::LP_AON::PTR }; - let lp_peri = unsafe { &*crate::peripherals::LP_PERI::PTR }; - - unsafe { - // FIXME: use GPIO APIs to configure pins - lp_aon - .gpio_mux() - .modify(|r, w| w.sel().bits(r.sel().bits() | (1 << 6))); - lp_aon - .gpio_mux() - .modify(|r, w| w.sel().bits(r.sel().bits() | (1 << 7))); - lp_io.gpio(6).modify(|_, w| w.mcu_sel().bits(1)); // TODO - - lp_io.gpio(7).modify(|_, w| w.mcu_sel().bits(1)); - - // Set output mode to Normal - lp_io.pin(6).modify(|_, w| w.pad_driver().set_bit()); - // Enable output (writing to write-1-to-set register, then internally the - // `GPIO_OUT_REG` will be set) - lp_io - .out_enable_w1ts() - .write(|w| w.enable_w1ts().bits(1 << 6)); - // Enable input - lp_io.gpio(6).modify(|_, w| w.fun_ie().set_bit()); - - // Disable pulldown (enable internal weak pull-down) - lp_io.gpio(6).modify(|_, w| w.fun_wpd().clear_bit()); - // Enable pullup - lp_io.gpio(6).modify(|_, w| w.fun_wpu().set_bit()); - - // Same process for SCL pin - lp_io.pin(7).modify(|_, w| w.pad_driver().set_bit()); - // Enable output (writing to write-1-to-set register, then internally the - // `GPIO_OUT_REG` will be set) - lp_io - .out_enable_w1ts() - .write(|w| w.enable_w1ts().bits(1 << 7)); - // Enable input - lp_io.gpio(7).modify(|_, w| w.fun_ie().set_bit()); - // Disable pulldown (enable internal weak pull-down) - lp_io.gpio(7).modify(|_, w| w.fun_wpd().clear_bit()); - // Enable pullup - lp_io.gpio(7).modify(|_, w| w.fun_wpu().set_bit()); - - // Select LP I2C function for the SDA and SCL pins - lp_io.gpio(6).modify(|_, w| w.mcu_sel().bits(1)); - lp_io.gpio(7).modify(|_, w| w.mcu_sel().bits(1)); - } - - // Initialize LP I2C HAL */ - me.i2c.clk_conf().modify(|_, w| w.sclk_active().set_bit()); - - // Enable LP I2C controller clock - lp_peri - .clk_en() - .modify(|_, w| w.lp_ext_i2c_ck_en().set_bit()); - - lp_peri - .reset_en() - .modify(|_, w| w.lp_ext_i2c_reset_en().set_bit()); - lp_peri - .reset_en() - .modify(|_, w| w.lp_ext_i2c_reset_en().clear_bit()); - - // Set LP I2C source clock - unsafe { &*LP_CLKRST::PTR } - .lpperi() - .modify(|_, w| w.lp_i2c_clk_sel().clear_bit()); - - // Initialize LP I2C Master mode - me.i2c.ctr().modify(|_, w| unsafe { - // Clear register - w.bits(0); - // Use open drain output for SDA and SCL - w.sda_force_out().set_bit(); - w.scl_force_out().set_bit(); - // Ensure that clock is enabled - w.clk_en().set_bit() - }); - - // First, reset the fifo buffers - me.i2c.fifo_conf().modify(|_, w| w.nonfifo_en().clear_bit()); - - me.i2c.ctr().modify(|_, w| { - w.tx_lsb_first().clear_bit(); - w.rx_lsb_first().clear_bit() - }); - - me.reset_fifo(); - - // Set LP I2C source clock - unsafe { &*LP_CLKRST::PTR } - .lpperi() - .modify(|_, w| w.lp_i2c_clk_sel().clear_bit()); - - // Configure LP I2C timing paramters. source_clk is ignored for LP_I2C in this - // call - - let source_clk = 16_000_000; - let bus_freq = frequency.raw(); - - let clkm_div: u32 = source_clk / (bus_freq * 1024) + 1; - let sclk_freq: u32 = source_clk / clkm_div; - let half_cycle: u32 = sclk_freq / bus_freq / 2; - - // SCL - let scl_low = half_cycle; - // default, scl_wait_high < scl_high - // Make 80KHz as a boundary here, because when working at lower frequency, too - // much scl_wait_high will faster the frequency according to some - // hardware behaviors. - let scl_wait_high = if bus_freq >= 80 * 1000 { - half_cycle / 2 - 2 - } else { - half_cycle / 4 - }; - let scl_high = half_cycle - scl_wait_high; - let sda_hold = half_cycle / 4; - let sda_sample = half_cycle / 2; // TODO + scl_wait_high; - let setup = half_cycle; - let hold = half_cycle; - // default we set the timeout value to about 10 bus cycles - // log(20*half_cycle)/log(2) = log(half_cycle)/log(2) + log(20)/log(2) - let tout = (4 * 8 - (5 * half_cycle).leading_zeros()) + 2; - - // According to the Technical Reference Manual, the following timings must be - // subtracted by 1. However, according to the practical measurement and - // some hardware behaviour, if wait_high_period and scl_high minus one. - // The SCL frequency would be a little higher than expected. Therefore, the - // solution here is not to minus scl_high as well as scl_wait high, and - // the frequency will be absolutely accurate to all frequency - // to some extent. - let scl_low_period = scl_low - 1; - let scl_high_period = scl_high; - let scl_wait_high_period = scl_wait_high; - // sda sample - let sda_hold_time = sda_hold - 1; - let sda_sample_time = sda_sample - 1; - // setup - let scl_rstart_setup_time = setup - 1; - let scl_stop_setup_time = setup - 1; - // hold - let scl_start_hold_time = hold - 1; - let scl_stop_hold_time = hold - 1; - let time_out_value = tout; - let time_out_en = true; - - // Write data to registers - unsafe { - me.i2c.clk_conf().modify(|_, w| { - w.sclk_sel().clear_bit(); - w.sclk_div_num().bits((clkm_div - 1) as u8) - }); - - // scl period - me.i2c - .scl_low_period() - .write(|w| w.scl_low_period().bits(scl_low_period as u16)); - - me.i2c.scl_high_period().write(|w| { - w.scl_high_period().bits(scl_high_period as u16); - w.scl_wait_high_period().bits(scl_wait_high_period as u8) - }); - // sda sample - me.i2c - .sda_hold() - .write(|w| w.time().bits(sda_hold_time as u16)); - me.i2c - .sda_sample() - .write(|w| w.time().bits(sda_sample_time as u16)); - - // setup - me.i2c - .scl_rstart_setup() - .write(|w| w.time().bits(scl_rstart_setup_time as u16)); - me.i2c - .scl_stop_setup() - .write(|w| w.time().bits(scl_stop_setup_time as u16)); - - // hold - me.i2c - .scl_start_hold() - .write(|w| w.time().bits(scl_start_hold_time as u16)); - me.i2c - .scl_stop_hold() - .write(|w| w.time().bits(scl_stop_hold_time as u16)); - - me.i2c.to().write(|w| { - w.time_out_en().bit(time_out_en); - w.time_out_value().bits(time_out_value.try_into().unwrap()) - }); - } - - // Enable SDA and SCL filtering. This configuration matches the HP I2C filter - // config - - me.i2c - .filter_cfg() - .modify(|_, w| unsafe { w.sda_filter_thres().bits(LP_I2C_FILTER_CYC_NUM_DEF) }); - me.i2c - .filter_cfg() - .modify(|_, w| unsafe { w.scl_filter_thres().bits(LP_I2C_FILTER_CYC_NUM_DEF) }); - - me.i2c - .filter_cfg() - .modify(|_, w| w.sda_filter_en().set_bit()); - me.i2c - .filter_cfg() - .modify(|_, w| w.scl_filter_en().set_bit()); - - // Configure the I2C master to send a NACK when the Rx FIFO count is full - me.i2c.ctr().modify(|_, w| w.rx_full_ack_level().set_bit()); - - // Synchronize the config register values to the LP I2C peripheral clock - me.lp_i2c_update(); - - me - } - - /// Update I2C configuration - fn lp_i2c_update(&self) { - self.i2c.ctr().modify(|_, w| w.conf_upgate().set_bit()); - } - - /// Resets the transmit and receive FIFO buffers. - fn reset_fifo(&self) { - self.i2c - .fifo_conf() - .modify(|_, w| w.tx_fifo_rst().set_bit()); - - self.i2c - .fifo_conf() - .modify(|_, w| w.tx_fifo_rst().clear_bit()); - - self.i2c - .fifo_conf() - .modify(|_, w| w.rx_fifo_rst().set_bit()); - - self.i2c - .fifo_conf() - .modify(|_, w| w.rx_fifo_rst().clear_bit()); - } - } -} +pub mod lp_i2c; diff --git a/esp-hal/src/i2c/support/eh.rs b/esp-hal/src/i2c/support/eh.rs new file mode 100644 index 00000000000..39a53879b13 --- /dev/null +++ b/esp-hal/src/i2c/support/eh.rs @@ -0,0 +1,43 @@ +use crate::{ + i2c::{Error, I2c, Instance, Operation}, + Mode, +}; + +impl<'a, 'b> From<&'a mut embedded_hal::i2c::Operation<'b>> for Operation<'a> { + fn from(value: &'a mut embedded_hal::i2c::Operation<'b>) -> Self { + match value { + embedded_hal::i2c::Operation::Write(buffer) => Operation::Write(buffer), + embedded_hal::i2c::Operation::Read(buffer) => Operation::Read(buffer), + } + } +} + +impl embedded_hal::i2c::Error for Error { + fn kind(&self) -> embedded_hal::i2c::ErrorKind { + use embedded_hal::i2c::{ErrorKind, NoAcknowledgeSource}; + + match self { + Self::ExceedingFifo => ErrorKind::Overrun, + Self::ArbitrationLost => ErrorKind::ArbitrationLoss, + Self::AckCheckFailed => ErrorKind::NoAcknowledge(NoAcknowledgeSource::Unknown), + _ => ErrorKind::Other, + } + } +} + +impl embedded_hal::i2c::ErrorType for I2c<'_, DM, T> { + type Error = Error; +} + +impl embedded_hal::i2c::I2c for I2c<'_, DM, T> +where + T: Instance, +{ + fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + self.transaction_impl(address, operations.iter_mut().map(Into::into)) + } +} diff --git a/esp-hal/src/i2c/support/eh02.rs b/esp-hal/src/i2c/support/eh02.rs new file mode 100644 index 00000000000..4f572d9aadb --- /dev/null +++ b/esp-hal/src/i2c/support/eh02.rs @@ -0,0 +1,42 @@ +use crate::{ + i2c::{Error, I2c, Instance}, + Blocking, +}; + +impl embedded_hal_02::blocking::i2c::Read for I2c<'_, Blocking, T> +where + T: Instance, +{ + type Error = Error; + + fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { + self.read(address, buffer) + } +} + +impl embedded_hal_02::blocking::i2c::Write for I2c<'_, Blocking, T> +where + T: Instance, +{ + type Error = Error; + + fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(addr, bytes) + } +} + +impl embedded_hal_02::blocking::i2c::WriteRead for I2c<'_, Blocking, T> +where + T: Instance, +{ + type Error = Error; + + fn write_read( + &mut self, + address: u8, + bytes: &[u8], + buffer: &mut [u8], + ) -> Result<(), Self::Error> { + self.write_read(address, bytes, buffer) + } +} diff --git a/esp-hal/src/i2c/support/eh_a.rs b/esp-hal/src/i2c/support/eh_a.rs new file mode 100644 index 00000000000..f12053c9fa9 --- /dev/null +++ b/esp-hal/src/i2c/support/eh_a.rs @@ -0,0 +1,20 @@ +use embedded_hal::i2c::Operation; + +use crate::{ + i2c::{I2c, Instance}, + Async, +}; + +impl embedded_hal_async::i2c::I2c for I2c<'_, Async, T> +where + T: Instance, +{ + async fn transaction( + &mut self, + address: u8, + operations: &mut [Operation<'_>], + ) -> Result<(), Self::Error> { + self.transaction_impl_async(address, operations.iter_mut().map(Into::into)) + .await + } +} diff --git a/esp-hal/src/i2c/support/mod.rs b/esp-hal/src/i2c/support/mod.rs new file mode 100644 index 00000000000..6920dd9b064 --- /dev/null +++ b/esp-hal/src/i2c/support/mod.rs @@ -0,0 +1,3 @@ +pub mod eh; +pub mod eh02; +pub mod eh_a; From 0e11310aeb6786ba11df4678bdcce30189f71de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Thu, 31 Oct 2024 12:13:39 +0100 Subject: [PATCH 04/18] Move some code out into versioned modules --- esp-hal/src/i2c/mod.rs | 1364 +++++------------------ esp-hal/src/i2c/version/mod.rs | 16 + esp-hal/src/i2c/version/v1.rs | 206 ++++ esp-hal/src/i2c/version/v1_v2_common.rs | 120 ++ esp-hal/src/i2c/version/v2.rs | 87 ++ esp-hal/src/i2c/version/v2_v3_common.rs | 218 ++++ esp-hal/src/i2c/version/v3.rs | 247 ++++ 7 files changed, 1165 insertions(+), 1093 deletions(-) create mode 100644 esp-hal/src/i2c/version/mod.rs create mode 100644 esp-hal/src/i2c/version/v1.rs create mode 100644 esp-hal/src/i2c/version/v1_v2_common.rs create mode 100644 esp-hal/src/i2c/version/v2.rs create mode 100644 esp-hal/src/i2c/version/v2_v3_common.rs create mode 100644 esp-hal/src/i2c/version/v3.rs diff --git a/esp-hal/src/i2c/mod.rs b/esp-hal/src/i2c/mod.rs index b4885d0fafe..a3bb9476425 100644 --- a/esp-hal/src/i2c/mod.rs +++ b/esp-hal/src/i2c/mod.rs @@ -53,16 +53,13 @@ //! [`embedded-hal`]: https://crates.io/crates/embedded-hal mod support; +mod version; use core::marker::PhantomData; -#[cfg(not(esp32))] -use core::{ - pin::Pin, - task::{Context, Poll}, -}; use embassy_sync::waitqueue::AtomicWaker; use fugit::HertzU32; +use version::{I2C_CHUNK_SIZE, I2C_LL_INTR_MASK}; use crate::{ clock::Clocks, @@ -83,21 +80,6 @@ use crate::{ Mode, }; -cfg_if::cfg_if! { - if #[cfg(esp32s2)] { - const I2C_LL_INTR_MASK: u32 = 0x1ffff; - } else { - const I2C_LL_INTR_MASK: u32 = 0x3ffff; - } -} - -// Chunk writes/reads by this size -#[cfg(any(esp32, esp32s2))] -const I2C_CHUNK_SIZE: usize = 32; - -#[cfg(not(any(esp32, esp32s2)))] -const I2C_CHUNK_SIZE: usize = 254; - // on ESP32 there is a chance to get trapped in `wait_for_completion` forever const MAX_ITERATIONS: u32 = 1_000_000; @@ -256,19 +238,22 @@ where let i2c = i2c.with_sda(sda).with_scl(scl); - i2c.info().setup(frequency, None); + i2c.driver().setup(frequency, None); i2c } - fn info(&self) -> &Info { - self.i2c.info() + fn driver(&self) -> Driver<'_> { + Driver { + info: self.i2c.info(), + state: self.i2c.state(), + } } fn internal_recover(&self) { PeripheralClockControl::reset(self.i2c.peripheral()); PeripheralClockControl::enable(self.i2c.peripheral()); - self.info().setup(self.frequency, self.timeout); + self.driver().setup(self.frequency, self.timeout); } /// Set the I2C timeout. @@ -276,7 +261,7 @@ where // timeout, and just what exactly is a timeout in this context? pub fn with_timeout(mut self, timeout: Option) -> Self { self.timeout = timeout; - self.info().setup(self.frequency, self.timeout); + self.driver().setup(self.frequency, self.timeout); self } @@ -295,16 +280,16 @@ where let next_op = op_iter.peek().map(|v| v.kind()); let kind = op.kind(); // Clear all I2C interrupts - self.info().clear_all_interrupts(); + self.driver().clear_all_interrupts(); - let cmd_iterator = &mut self.info().register_block().comd_iter(); + let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); match op { Operation::Write(buffer) => { // execute a write operation: // - issue START/RSTART if op is different from previous // - issue STOP if op is the last one - self.info() - .write_operation( + self.driver() + .write_operation_blocking( address, buffer, !matches!(last_op, Some(OpKind::Write)), @@ -318,8 +303,8 @@ where // - issue START/RSTART if op is different from previous // - issue STOP if op is the last one // - will_continue is true if there is another read operation next - self.info() - .read_operation( + self.driver() + .read_operation_blocking( address, buffer, !matches!(last_op, Some(OpKind::Read)), @@ -338,14 +323,14 @@ where } fn with_sda(self, sda: impl Peripheral

+ 'd) -> Self { - let info = self.info(); + let info = self.driver().info; let input = info.sda_input; let output = info.sda_output; self.with_pin(sda, input, output) } fn with_scl(self, scl: impl Peripheral

+ 'd) -> Self { - let info = self.info(); + let info = self.driver().info; let input = info.scl_input; let output = info.scl_output; self.with_pin(scl, input, output) @@ -406,7 +391,7 @@ where /// Configures the I2C peripheral to operate in asynchronous mode. pub fn into_async(mut self) -> I2c<'d, Async, T> { - self.set_interrupt_handler(self.info().async_handler); + self.set_interrupt_handler(self.driver().info.async_handler); I2c { i2c: self.i2c, @@ -421,12 +406,12 @@ where let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); for (idx, chunk) in buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { // Clear all I2C interrupts - self.info().clear_all_interrupts(); + self.driver().clear_all_interrupts(); - let cmd_iterator = &mut self.info().register_block().comd_iter(); + let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - self.info() - .read_operation( + self.driver() + .read_operation_blocking( address, chunk, idx == 0, @@ -445,12 +430,12 @@ where let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); for (idx, chunk) in buffer.chunks(I2C_CHUNK_SIZE).enumerate() { // Clear all I2C interrupts - self.info().clear_all_interrupts(); + self.driver().clear_all_interrupts(); - let cmd_iterator = &mut self.info().register_block().comd_iter(); + let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - self.info() - .write_operation( + self.driver() + .write_operation_blocking( address, chunk, idx == 0, @@ -476,12 +461,12 @@ where for (idx, chunk) in write_buffer.chunks(I2C_CHUNK_SIZE).enumerate() { // Clear all I2C interrupts - self.info().clear_all_interrupts(); + self.driver().clear_all_interrupts(); - let cmd_iterator = &mut self.info().register_block().comd_iter(); + let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - self.info() - .write_operation( + self.driver() + .write_operation_blocking( address, chunk, idx == 0, @@ -493,12 +478,12 @@ where for (idx, chunk) in read_buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { // Clear all I2C interrupts - self.info().clear_all_interrupts(); + self.driver().clear_all_interrupts(); - let cmd_iterator = &mut self.info().register_block().comd_iter(); + let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - self.info() - .read_operation( + self.driver() + .read_operation_blocking( address, chunk, idx == 0, @@ -546,7 +531,7 @@ where T: Instance, { fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) { - let interrupt = self.info().interrupt; + let interrupt = self.driver().info.interrupt; for core in Cpu::other() { crate::interrupt::disable(core, interrupt); } @@ -555,128 +540,13 @@ where } } -#[cfg_attr(esp32, allow(dead_code))] -pub(crate) enum Event { - EndDetect, - TxComplete, - #[cfg(not(any(esp32, esp32s2)))] - TxFifoWatermark, -} - -#[cfg(not(esp32))] -#[must_use = "futures do nothing unless you `.await` or poll them"] -struct I2cFuture<'a> { - event: Event, - info: &'a Info, - state: &'a State, -} - -#[cfg(not(esp32))] -impl<'a> I2cFuture<'a> { - pub fn new(event: Event, instance: &'a impl Instance) -> Self { - instance.info().register_block().int_ena().modify(|_, w| { - let w = match event { - Event::EndDetect => w.end_detect().set_bit(), - Event::TxComplete => w.trans_complete().set_bit(), - #[cfg(not(any(esp32, esp32s2)))] - Event::TxFifoWatermark => w.txfifo_wm().set_bit(), - }; - - w.arbitration_lost().set_bit(); - w.time_out().set_bit(); - - #[cfg(esp32)] - w.ack_err().set_bit(); - #[cfg(not(esp32))] - w.nack().set_bit(); - - w - }); - - Self { - event, - state: instance.state(), - info: instance.info(), - } - } - - fn event_bit_is_clear(&self) -> bool { - let r = self.info.register_block().int_ena().read(); - - match self.event { - Event::EndDetect => r.end_detect().bit_is_clear(), - Event::TxComplete => r.trans_complete().bit_is_clear(), - #[cfg(not(any(esp32, esp32s2)))] - Event::TxFifoWatermark => r.txfifo_wm().bit_is_clear(), - } - } - - fn check_error(&self) -> Result<(), Error> { - let r = self.info.register_block().int_raw().read(); - - if r.arbitration_lost().bit_is_set() { - return Err(Error::ArbitrationLost); - } - - if r.time_out().bit_is_set() { - return Err(Error::TimeOut); - } - - #[cfg(not(esp32))] - if r.nack().bit_is_set() { - return Err(Error::AckCheckFailed); - } - - #[cfg(esp32)] - if r.ack_err().bit_is_set() { - return Err(Error::AckCheckFailed); - } - - #[cfg(not(esp32))] - if r.trans_complete().bit_is_set() - && self - .info - .register_block() - .sr() - .read() - .resp_rec() - .bit_is_clear() - { - return Err(Error::AckCheckFailed); - } - - Ok(()) - } -} - -#[cfg(not(esp32))] -impl core::future::Future for I2cFuture<'_> { - type Output = Result<(), Error>; - - fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { - self.state.waker.register(ctx.waker()); - - let error = self.check_error(); - - if error.is_err() { - return Poll::Ready(error); - } - - if self.event_bit_is_clear() { - Poll::Ready(Ok(())) - } else { - Poll::Pending - } - } -} - impl<'d, T> I2c<'d, Async, T> where T: Instance, { /// Configure the I2C peripheral to operate in blocking mode. pub fn into_blocking(self) -> I2c<'d, Blocking, T> { - crate::interrupt::disable(Cpu::current(), self.info().interrupt); + crate::interrupt::disable(Cpu::current(), self.driver().info.interrupt); I2c { i2c: self.i2c, @@ -686,229 +556,24 @@ where } } - #[cfg(any(esp32, esp32s2))] - async fn read_all_from_fifo(&self, buffer: &mut [u8]) -> Result<(), Error> { - if buffer.len() > 32 { - panic!("On ESP32 and ESP32-S2 the max I2C read is limited to 32 bytes"); - } - - self.wait_for_completion(false).await?; - - for byte in buffer.iter_mut() { - *byte = read_fifo(self.info().register_block()); - } - - Ok(()) - } - - #[cfg(not(any(esp32, esp32s2)))] - async fn read_all_from_fifo(&self, buffer: &mut [u8]) -> Result<(), Error> { - self.info().read_all_from_fifo(buffer) - } - - #[cfg(any(esp32, esp32s2))] - async fn write_remaining_tx_fifo(&self, start_index: usize, bytes: &[u8]) -> Result<(), Error> { - if start_index >= bytes.len() { - return Ok(()); - } - - for b in bytes { - write_fifo(self.info().register_block(), *b); - self.info().check_errors()?; - } - - Ok(()) - } - - #[cfg(not(any(esp32, esp32s2)))] - async fn write_remaining_tx_fifo(&self, start_index: usize, bytes: &[u8]) -> Result<(), Error> { - let mut index = start_index; - loop { - self.info().check_errors()?; - - I2cFuture::new(Event::TxFifoWatermark, &*self.i2c).await?; - - self.info() - .register_block() - .int_clr() - .write(|w| w.txfifo_wm().clear_bit_by_one()); - - I2cFuture::new(Event::TxFifoWatermark, &*self.i2c).await?; - - if index >= bytes.len() { - break Ok(()); - } - - write_fifo(self.info().register_block(), bytes[index]); - index += 1; - } - } - - #[cfg(not(esp32))] - async fn wait_for_completion(&self, end_only: bool) -> Result<(), Error> { - self.info().check_errors()?; - - if end_only { - I2cFuture::new(Event::EndDetect, &*self.i2c).await?; - } else { - let res = embassy_futures::select::select( - I2cFuture::new(Event::TxComplete, &*self.i2c), - I2cFuture::new(Event::EndDetect, &*self.i2c), - ) - .await; - - match res { - embassy_futures::select::Either::First(res) => res?, - embassy_futures::select::Either::Second(res) => res?, - } - } - self.info().check_all_commands_done()?; - - Ok(()) - } - - #[cfg(esp32)] - async fn wait_for_completion(&self, end_only: bool) -> Result<(), Error> { - // for ESP32 we need a timeout here but wasting a timer seems unnecessary - // given the short time we spend here - - let mut tout = MAX_ITERATIONS / 10; // adjust the timeout because we are yielding in the loop - loop { - let interrupts = self.info().register_block().int_raw().read(); - - self.info().check_errors()?; - - // Handle completion cases - // A full transmission was completed (either a STOP condition or END was - // processed) - if (!end_only && interrupts.trans_complete().bit_is_set()) - || interrupts.end_detect().bit_is_set() - { - break; - } - - tout -= 1; - if tout == 0 { - return Err(Error::TimeOut); - } - - embassy_futures::yield_now().await; - } - self.info().check_all_commands_done()?; - Ok(()) - } - - /// Executes an async I2C write operation. - /// - `addr` is the address of the slave device. - /// - `bytes` is the data two be sent. - /// - `start` indicates whether the operation should start by a START - /// condition and sending the address. - /// - `stop` indicates whether the operation should end with a STOP - /// condition. - /// - `cmd_iterator` is an iterator over the command registers. - async fn write_operation<'a, I>( - &self, - address: u8, - bytes: &[u8], - start: bool, - stop: bool, - cmd_iterator: &mut I, - ) -> Result<(), Error> - where - I: Iterator, - { - // Short circuit for zero length writes without start or end as that would be an - // invalid operation write lengths in the TRM (at least for ESP32-S3) are 1-255 - if bytes.is_empty() && !start && !stop { - return Ok(()); - } - - // Reset FIFO and command list - self.info().reset_fifo(); - self.info().reset_command_list(); - if start { - add_cmd(cmd_iterator, Command::Start)?; - } - self.i2c - .info() - .setup_write(address, bytes, start, cmd_iterator)?; - add_cmd( - cmd_iterator, - if stop { Command::Stop } else { Command::End }, - )?; - let index = self.info().fill_tx_fifo(bytes); - self.info().start_transmission(); - - // Fill the FIFO with the remaining bytes: - self.write_remaining_tx_fifo(index, bytes).await?; - self.wait_for_completion(!stop).await?; - Ok(()) - } - - /// Executes an async I2C read operation. - /// - `addr` is the address of the slave device. - /// - `buffer` is the buffer to store the read data. - /// - `start` indicates whether the operation should start by a START - /// condition and sending the address. - /// - `stop` indicates whether the operation should end with a STOP - /// condition. - /// - `will_continue` indicates whether there is another read operation - /// following this one and we should not nack the last byte. - /// - `cmd_iterator` is an iterator over the command registers. - async fn read_operation<'a, I>( - &self, - address: u8, - buffer: &mut [u8], - start: bool, - stop: bool, - will_continue: bool, - cmd_iterator: &mut I, - ) -> Result<(), Error> - where - I: Iterator, - { - // Short circuit for zero length reads as that would be an invalid operation - // read lengths in the TRM (at least for ESP32-S3) are 1-255 - if buffer.is_empty() { - return Ok(()); - } - - // Reset FIFO and command list - self.info().reset_fifo(); - self.info().reset_command_list(); - if start { - add_cmd(cmd_iterator, Command::Start)?; - } - self.i2c - .info() - .setup_read(address, buffer, start, will_continue, cmd_iterator)?; - add_cmd( - cmd_iterator, - if stop { Command::Stop } else { Command::End }, - )?; - self.info().start_transmission(); - self.read_all_from_fifo(buffer).await?; - self.wait_for_completion(!stop).await?; - Ok(()) - } - /// Writes bytes to slave with address `address` pub async fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); for (idx, chunk) in buffer.chunks(I2C_CHUNK_SIZE).enumerate() { // Clear all I2C interrupts - self.info().clear_all_interrupts(); + self.driver().clear_all_interrupts(); - let cmd_iterator = &mut self.info().register_block().comd_iter(); + let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - self.write_operation( - address, - chunk, - idx == 0, - idx == chunk_count - 1, - cmd_iterator, - ) - .await?; + self.driver() + .write_operation( + address, + chunk, + idx == 0, + idx == chunk_count - 1, + cmd_iterator, + ) + .await?; } Ok(()) @@ -919,19 +584,20 @@ where let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); for (idx, chunk) in buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { // Clear all I2C interrupts - self.info().clear_all_interrupts(); + self.driver().clear_all_interrupts(); - let cmd_iterator = &mut self.info().register_block().comd_iter(); + let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - self.read_operation( - address, - chunk, - idx == 0, - idx == chunk_count - 1, - idx < chunk_count - 1, - cmd_iterator, - ) - .await?; + self.driver() + .read_operation( + address, + chunk, + idx == 0, + idx == chunk_count - 1, + idx < chunk_count - 1, + cmd_iterator, + ) + .await?; } Ok(()) @@ -949,35 +615,37 @@ where let read_count = read_buffer.len().div_ceil(I2C_CHUNK_SIZE); for (idx, chunk) in write_buffer.chunks(I2C_CHUNK_SIZE).enumerate() { // Clear all I2C interrupts - self.info().clear_all_interrupts(); + self.driver().clear_all_interrupts(); - let cmd_iterator = &mut self.info().register_block().comd_iter(); + let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - self.write_operation( - address, - chunk, - idx == 0, - idx == write_count - 1 && read_count == 0, - cmd_iterator, - ) - .await?; + self.driver() + .write_operation( + address, + chunk, + idx == 0, + idx == write_count - 1 && read_count == 0, + cmd_iterator, + ) + .await?; } for (idx, chunk) in read_buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { // Clear all I2C interrupts - self.info().clear_all_interrupts(); + self.driver().clear_all_interrupts(); - let cmd_iterator = &mut self.info().register_block().comd_iter(); + let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - self.read_operation( - address, - chunk, - idx == 0, - idx == read_count - 1, - idx < read_count - 1, - cmd_iterator, - ) - .await?; + self.driver() + .read_operation( + address, + chunk, + idx == 0, + idx == read_count - 1, + idx < read_count - 1, + cmd_iterator, + ) + .await?; } Ok(()) @@ -1026,37 +694,39 @@ where let next_op = op_iter.peek().map(|v| v.kind()); let kind = op.kind(); // Clear all I2C interrupts - self.info().clear_all_interrupts(); + self.driver().clear_all_interrupts(); - let cmd_iterator = &mut self.info().register_block().comd_iter(); + let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); match op { Operation::Write(buffer) => { // execute a write operation: // - issue START/RSTART if op is different from previous // - issue STOP if op is the last one - self.write_operation( - address, - buffer, - !matches!(last_op, Some(OpKind::Write)), - next_op.is_none(), - cmd_iterator, - ) - .await?; + self.driver() + .write_operation( + address, + buffer, + !matches!(last_op, Some(OpKind::Write)), + next_op.is_none(), + cmd_iterator, + ) + .await?; } Operation::Read(buffer) => { // execute a read operation: // - issue START/RSTART if op is different from previous // - issue STOP if op is the last one // - will_continue is true if there is another read operation next - self.read_operation( - address, - buffer, - !matches!(last_op, Some(OpKind::Read)), - next_op.is_none(), - matches!(next_op, Some(OpKind::Read)), - cmd_iterator, - ) - .await?; + self.driver() + .read_operation( + address, + buffer, + !matches!(last_op, Some(OpKind::Read)), + next_op.is_none(), + matches!(next_op, Some(OpKind::Read)), + cmd_iterator, + ) + .await?; } } @@ -1090,42 +760,6 @@ fn async_handler(info: &Info, state: &State) { state.waker.wake(); } -/// Sets the filter with a supplied threshold in clock cycles for which a -/// pulse must be present to pass the filter -fn set_filter( - register_block: &RegisterBlock, - sda_threshold: Option, - scl_threshold: Option, -) { - cfg_if::cfg_if! { - if #[cfg(any(esp32, esp32s2))] { - register_block.sda_filter_cfg().modify(|_, w| { - if let Some(threshold) = sda_threshold { - unsafe { w.sda_filter_thres().bits(threshold) }; - } - w.sda_filter_en().bit(sda_threshold.is_some()) - }); - register_block.scl_filter_cfg().modify(|_, w| { - if let Some(threshold) = scl_threshold { - unsafe { w.scl_filter_thres().bits(threshold) }; - } - w.scl_filter_en().bit(scl_threshold.is_some()) - }); - } else { - register_block.filter_cfg().modify(|_, w| { - if let Some(threshold) = sda_threshold { - unsafe { w.sda_filter_thres().bits(threshold) }; - } - if let Some(threshold) = scl_threshold { - unsafe { w.scl_filter_thres().bits(threshold) }; - } - w.sda_filter_en().bit(sda_threshold.is_some()); - w.scl_filter_en().bit(scl_threshold.is_some()) - }); - } - } -} - #[allow(clippy::too_many_arguments, unused)] /// Configures the clock and timing parameters for the I2C peripheral. fn configure_clock( @@ -1241,11 +875,19 @@ impl Info { pub fn register_block(&self) -> &RegisterBlock { unsafe { &*self.register_block } } +} + +#[allow(dead_code)] // Some versions don't need `state` +struct Driver<'a> { + info: &'a Info, + state: &'a State, +} +impl Driver<'_> { /// Configures the I2C peripheral with the specified frequency, clocks, and /// optional timeout. fn setup(&self, frequency: HertzU32, timeout: Option) { - self.register_block().ctr().write(|w| { + self.info.register_block().ctr().write(|w| { // Set I2C controller to master mode w.ms_mode().set_bit(); // Use open drain output for SDA and SCL @@ -1255,30 +897,17 @@ impl Info { w.tx_lsb_first().clear_bit(); w.rx_lsb_first().clear_bit(); // Ensure that clock is enabled + #[cfg(esp32s2)] + w.ref_always_on().set_bit(); w.clk_en().set_bit() }); - #[cfg(esp32s2)] - self.register_block() - .ctr() - .modify(|_, w| w.ref_always_on().set_bit()); - // Configure filter // FIXME if we ever change this we need to adapt `set_frequency` for ESP32 - set_filter(self.register_block(), Some(7), Some(7)); + self.set_filter(Some(7), Some(7)); // Configure frequency - let clocks = Clocks::get(); - cfg_if::cfg_if! { - if #[cfg(esp32)] { - let clock = clocks.i2c_clock.convert(); - } else if #[cfg(esp32s2)] { - let clock = clocks.apb_clock.convert(); - } else { - let clock = clocks.xtal_clock.convert(); - } - } - self.set_frequency(clock, frequency, timeout); + self.set_frequency(frequency, timeout); self.update_config(); @@ -1286,238 +915,14 @@ impl Info { self.reset(); } - /// Resets the I2C controller (FIFO + FSM + command list) - fn reset(&self) { - // Reset the FSM - // (the option to reset the FSM is not available - // for the ESP32) - #[cfg(not(esp32))] - self.register_block() - .ctr() - .modify(|_, w| w.fsm_rst().set_bit()); - - // Clear all I2C interrupts - self.register_block() - .int_clr() - .write(|w| unsafe { w.bits(I2C_LL_INTR_MASK) }); - - // Reset fifo - self.reset_fifo(); - - // Reset the command list - self.reset_command_list(); - } - /// Resets the I2C peripheral's command registers fn reset_command_list(&self) { // Confirm that all commands that were configured were actually executed - for cmd in self.register_block().comd_iter() { + for cmd in self.info.register_block().comd_iter() { cmd.reset(); } } - #[cfg(esp32)] - /// Sets the frequency of the I2C interface by calculating and applying the - /// associated timings - corresponds to i2c_ll_cal_bus_clk and - /// i2c_ll_set_bus_timing in ESP-IDF - fn set_frequency(&self, source_clk: HertzU32, bus_freq: HertzU32, timeout: Option) { - let source_clk = source_clk.raw(); - let bus_freq = bus_freq.raw(); - - let half_cycle: u32 = source_clk / bus_freq / 2; - let scl_low = half_cycle; - let scl_high = half_cycle; - let sda_hold = half_cycle / 2; - let sda_sample = scl_high / 2; - let setup = half_cycle; - let hold = half_cycle; - // default we set the timeout value to 10 bus cycles - let time_out_value = timeout.unwrap_or(half_cycle * 20); - - // SCL period. According to the TRM, we should always subtract 1 to SCL low - // period - let scl_low = scl_low - 1; - // Still according to the TRM, if filter is not enbled, we have to subtract 7, - // if SCL filter is enabled, we have to subtract: - // 8 if SCL filter is between 0 and 2 (included) - // 6 + SCL threshold if SCL filter is between 3 and 7 (included) - // to SCL high period - let mut scl_high = scl_high; - // In the "worst" case, we will subtract 13, make sure the result will still be - // correct - - // FIXME since we always set the filter threshold to 7 we don't need conditional - // code here once that changes we need the conditional code here - scl_high -= 7 + 6; - - // if (filter_cfg_en) { - // if (thres <= 2) { - // scl_high -= 8; - // } else { - // assert(hw->scl_filter_cfg.thres <= 7); - // scl_high -= thres + 6; - // } - // } else { - // scl_high -= 7; - //} - - let scl_high_period = scl_high; - let scl_low_period = scl_low; - // sda sample - let sda_hold_time = sda_hold; - let sda_sample_time = sda_sample; - // setup - let scl_rstart_setup_time = setup; - let scl_stop_setup_time = setup; - // hold - let scl_start_hold_time = hold; - let scl_stop_hold_time = hold; - - configure_clock( - self.register_block(), - 0, - scl_low_period, - scl_high_period, - 0, - sda_hold_time, - sda_sample_time, - scl_rstart_setup_time, - scl_stop_setup_time, - scl_start_hold_time, - scl_stop_hold_time, - time_out_value, - true, - ); - } - - #[cfg(esp32s2)] - /// Sets the frequency of the I2C interface by calculating and applying the - /// associated timings - corresponds to i2c_ll_cal_bus_clk and - /// i2c_ll_set_bus_timing in ESP-IDF - fn set_frequency(&self, source_clk: HertzU32, bus_freq: HertzU32, timeout: Option) { - let source_clk = source_clk.raw(); - let bus_freq = bus_freq.raw(); - - let half_cycle: u32 = source_clk / bus_freq / 2; - // SCL - let scl_low = half_cycle; - // default, scl_wait_high < scl_high - let scl_high = half_cycle / 2 + 2; - let scl_wait_high = half_cycle - scl_high; - let sda_hold = half_cycle / 2; - // scl_wait_high < sda_sample <= scl_high - let sda_sample = half_cycle / 2 - 1; - let setup = half_cycle; - let hold = half_cycle; - // default we set the timeout value to 10 bus cycles - let time_out_value = timeout.unwrap_or(half_cycle * 20); - - // scl period - let scl_low_period = scl_low - 1; - let scl_high_period = scl_high; - let scl_wait_high_period = scl_wait_high; - // sda sample - let sda_hold_time = sda_hold; - let sda_sample_time = sda_sample; - // setup - let scl_rstart_setup_time = setup; - let scl_stop_setup_time = setup; - // hold - let scl_start_hold_time = hold - 1; - let scl_stop_hold_time = hold; - let time_out_en = true; - - configure_clock( - self.register_block(), - 0, - scl_low_period, - scl_high_period, - scl_wait_high_period, - sda_hold_time, - sda_sample_time, - scl_rstart_setup_time, - scl_stop_setup_time, - scl_start_hold_time, - scl_stop_hold_time, - time_out_value, - time_out_en, - ); - } - - #[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2, esp32s3))] - /// Sets the frequency of the I2C interface by calculating and applying the - /// associated timings - corresponds to i2c_ll_cal_bus_clk and - /// i2c_ll_set_bus_timing in ESP-IDF - fn set_frequency(&self, source_clk: HertzU32, bus_freq: HertzU32, timeout: Option) { - let source_clk = source_clk.raw(); - let bus_freq = bus_freq.raw(); - - let clkm_div: u32 = source_clk / (bus_freq * 1024) + 1; - let sclk_freq: u32 = source_clk / clkm_div; - let half_cycle: u32 = sclk_freq / bus_freq / 2; - // SCL - let scl_low = half_cycle; - // default, scl_wait_high < scl_high - // Make 80KHz as a boundary here, because when working at lower frequency, too - // much scl_wait_high will faster the frequency according to some - // hardware behaviors. - let scl_wait_high = if bus_freq >= 80 * 1000 { - half_cycle / 2 - 2 - } else { - half_cycle / 4 - }; - let scl_high = half_cycle - scl_wait_high; - let sda_hold = half_cycle / 4; - let sda_sample = half_cycle / 2 + scl_wait_high; - let setup = half_cycle; - let hold = half_cycle; - - let time_out_value = if let Some(timeout) = timeout { - timeout - } else { - // default we set the timeout value to about 10 bus cycles - // log(20*half_cycle)/log(2) = log(half_cycle)/log(2) + log(20)/log(2) - (4 * 8 - (5 * half_cycle).leading_zeros()) + 2 - }; - - // According to the Technical Reference Manual, the following timings must be - // subtracted by 1. However, according to the practical measurement and - // some hardware behaviour, if wait_high_period and scl_high minus one. - // The SCL frequency would be a little higher than expected. Therefore, the - // solution here is not to minus scl_high as well as scl_wait high, and - // the frequency will be absolutely accurate to all frequency - // to some extent. - let scl_low_period = scl_low - 1; - let scl_high_period = scl_high; - let scl_wait_high_period = scl_wait_high; - // sda sample - let sda_hold_time = sda_hold - 1; - let sda_sample_time = sda_sample - 1; - // setup - let scl_rstart_setup_time = setup - 1; - let scl_stop_setup_time = setup - 1; - // hold - let scl_start_hold_time = hold - 1; - let scl_stop_hold_time = hold - 1; - let time_out_en = true; - - configure_clock( - self.register_block(), - clkm_div, - scl_low_period, - scl_high_period, - scl_wait_high_period, - sda_hold_time, - sda_sample_time, - scl_rstart_setup_time, - scl_stop_setup_time, - scl_start_hold_time, - scl_stop_hold_time, - time_out_value, - time_out_en, - ); - } - /// Configures the I2C peripheral for a write operation. /// - `addr` is the address of the slave device. /// - `bytes` is the data two be sent. @@ -1546,7 +951,7 @@ impl Info { // don't issue write if there is no data to write if write_len > 0 { // WRITE command - add_cmd( + self.add_cmd( cmd_iterator, Command::Write { ack_exp: Ack::Ack, @@ -1560,10 +965,7 @@ impl Info { if start { // Load address and R/W bit into FIFO - write_fifo( - self.register_block(), - addr << 1 | OperationType::Write as u8, - ); + self.write_fifo(addr << 1 | OperationType::Write as u8); } Ok(()) } @@ -1602,7 +1004,7 @@ impl Info { if start { // WRITE command - add_cmd( + self.add_cmd( cmd_iterator, Command::Write { ack_exp: Ack::Ack, @@ -1614,7 +1016,7 @@ impl Info { if initial_len > 0 { // READ command - add_cmd( + self.add_cmd( cmd_iterator, Command::Read { ack_value: Ack::Ack, @@ -1626,7 +1028,7 @@ impl Info { if !will_continue { // this is the last read so we need to nack the last byte // READ w/o ACK - add_cmd( + self.add_cmd( cmd_iterator, Command::Read { ack_value: Ack::Nack, @@ -1639,71 +1041,24 @@ impl Info { if start { // Load address and R/W bit into FIFO - write_fifo(self.register_block(), addr << 1 | OperationType::Read as u8); + self.write_fifo(addr << 1 | OperationType::Read as u8); } Ok(()) } - #[cfg(not(any(esp32, esp32s2)))] - /// Reads all bytes from the RX FIFO. - fn read_all_from_fifo(&self, buffer: &mut [u8]) -> Result<(), Error> { - // Read bytes from FIFO - // FIXME: Handle case where less data has been provided by the slave than - // requested? Or is this prevented from a protocol perspective? - for byte in buffer.iter_mut() { - loop { - self.check_errors()?; - - let reg = self.register_block().fifo_st().read(); - if reg.rxfifo_raddr().bits() != reg.rxfifo_waddr().bits() { - break; - } - } - - *byte = read_fifo(self.register_block()); - } - - Ok(()) - } - - #[cfg(any(esp32, esp32s2))] - /// Reads all bytes from the RX FIFO. - fn read_all_from_fifo(&self, buffer: &mut [u8]) -> Result<(), Error> { - // on ESP32/ESP32-S2 we currently don't support I2C transactions larger than the - // FIFO apparently it would be possible by using non-fifo mode - // see https://github.com/espressif/arduino-esp32/blob/7e9afe8c5ed7b5bf29624a5cd6e07d431c027b97/cores/esp32/esp32-hal-i2c.c#L615 - - if buffer.len() > 32 { - panic!("On ESP32 and ESP32-S2 the max I2C read is limited to 32 bytes"); - } - - // wait for completion - then we can just read the data from FIFO - // once we change to non-fifo mode to support larger transfers that - // won't work anymore - self.wait_for_completion(false)?; - - // Read bytes from FIFO - // FIXME: Handle case where less data has been provided by the slave than - // requested? Or is this prevented from a protocol perspective? - for byte in buffer.iter_mut() { - *byte = read_fifo(self.register_block()); - } - - Ok(()) - } - /// Clears all pending interrupts for the I2C peripheral. fn clear_all_interrupts(&self) { - self.register_block() + self.info + .register_block() .int_clr() .write(|w| unsafe { w.bits(I2C_LL_INTR_MASK) }); } /// Waits for the completion of an I2C transaction. - fn wait_for_completion(&self, end_only: bool) -> Result<(), Error> { + fn wait_for_completion_blocking(&self, end_only: bool) -> Result<(), Error> { let mut tout = MAX_ITERATIONS; loop { - let interrupts = self.register_block().int_raw().read(); + let interrupts = self.info.register_block().int_raw().read(); self.check_errors()?; @@ -1730,7 +1085,7 @@ impl Info { // NOTE: on esp32 executing the end command generates the end_detect interrupt // but does not seem to clear the done bit! So we don't check the done // status of an end command - for cmd_reg in self.register_block().comd_iter() { + for cmd_reg in self.info.register_block().comd_iter() { let cmd = cmd_reg.read(); if cmd.bits() != 0x0 && !cmd.opcode().is_end() && !cmd.command_done().bit_is_set() { @@ -1741,238 +1096,45 @@ impl Info { Ok(()) } - /// Checks for I2C transmission errors and handles them. - /// - /// This function inspects specific I2C-related interrupts to detect errors - /// during communication, such as timeouts, failed acknowledgments, or - /// arbitration loss. If an error is detected, the function handles it - /// by resetting the I2C peripheral to clear the error condition and then - /// returns an appropriate error. - fn check_errors(&self) -> Result<(), Error> { - let interrupts = self.register_block().int_raw().read(); - - // The ESP32 variant has a slightly different interrupt naming - // scheme! - cfg_if::cfg_if! { - if #[cfg(esp32)] { - // Handle error cases - let retval = if interrupts.time_out().bit_is_set() { - Err(Error::TimeOut) - } else if interrupts.ack_err().bit_is_set() { - Err(Error::AckCheckFailed) - } else if interrupts.arbitration_lost().bit_is_set() { - Err(Error::ArbitrationLost) - } else { - Ok(()) - }; - } else { - // Handle error cases - let retval = if interrupts.time_out().bit_is_set() { - Err(Error::TimeOut) - } else if interrupts.nack().bit_is_set() { - Err(Error::AckCheckFailed) - } else if interrupts.arbitration_lost().bit_is_set() { - Err(Error::ArbitrationLost) - } else if interrupts.trans_complete().bit_is_set() && self.register_block().sr().read().resp_rec().bit_is_clear() { - Err(Error::AckCheckFailed) - } else { - Ok(()) - }; - } - } - - if retval.is_err() { - self.reset(); - } - - retval - } - - /// Updates the configuration of the I2C peripheral. - /// - /// This function ensures that the configuration values, such as clock - /// settings, SDA/SCL filtering, timeouts, and other operational - /// parameters, which are configured in other functions, are properly - /// propagated to the I2C hardware. This step is necessary to synchronize - /// the software-configured settings with the peripheral's internal - /// registers, ensuring that the hardware behaves according to the - /// current configuration. - fn update_config(&self) { - // Ensure that the configuration of the peripheral is correctly propagated - // (only necessary for C2, C3, C6, H2 and S3 variant) - #[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2, esp32s3))] - self.register_block() - .ctr() - .modify(|_, w| w.conf_upgate().set_bit()); - } - /// Starts an I2C transmission. fn start_transmission(&self) { // Start transmission - self.register_block() + self.info + .register_block() .ctr() .modify(|_, w| w.trans_start().set_bit()); } - #[cfg(not(any(esp32, esp32s2)))] - /// Fills the TX FIFO with data from the provided slice. - fn fill_tx_fifo(&self, bytes: &[u8]) -> usize { - let mut index = 0; - while index < bytes.len() - && !self - .register_block() - .int_raw() - .read() - .txfifo_ovf() - .bit_is_set() - { - write_fifo(self.register_block(), bytes[index]); - index += 1; - } - if self - .register_block() - .int_raw() - .read() - .txfifo_ovf() - .bit_is_set() - { - index -= 1; - self.register_block() - .int_clr() - .write(|w| w.txfifo_ovf().clear_bit_by_one()); - } - index - } - - #[cfg(not(any(esp32, esp32s2)))] - /// Writes remaining data from byte slice to the TX FIFO from the specified - /// index. - fn write_remaining_tx_fifo(&self, start_index: usize, bytes: &[u8]) -> Result<(), Error> { - let mut index = start_index; - loop { - self.check_errors()?; - - while !self - .register_block() - .int_raw() - .read() - .txfifo_wm() - .bit_is_set() - { - self.check_errors()?; - } - - self.register_block() - .int_clr() - .write(|w| w.txfifo_wm().clear_bit_by_one()); - - while !self - .register_block() - .int_raw() - .read() - .txfifo_wm() - .bit_is_set() - { - self.check_errors()?; - } - - if index >= bytes.len() { - break Ok(()); - } - - write_fifo(self.register_block(), bytes[index]); - index += 1; - } - } - - #[cfg(any(esp32, esp32s2))] - /// Fills the TX FIFO with data from the provided slice. - fn fill_tx_fifo(&self, bytes: &[u8]) -> usize { - // on ESP32/ESP32-S2 we currently don't support I2C transactions larger than the - // FIFO apparently it would be possible by using non-fifo mode - // see https://github.com/espressif/arduino-esp32/blob/7e9afe8c5ed7b5bf29624a5cd6e07d431c027b97/cores/esp32/esp32-hal-i2c.c#L615 - - if bytes.len() > 31 { - panic!("On ESP32 and ESP32-S2 the max I2C transfer is limited to 31 bytes"); - } - - for b in bytes { - write_fifo(self.register_block(), *b); - } - - bytes.len() - } - - #[cfg(any(esp32, esp32s2))] - /// Writes remaining data from byte slice to the TX FIFO from the specified - /// index. - fn write_remaining_tx_fifo(&self, start_index: usize, bytes: &[u8]) -> Result<(), Error> { - // on ESP32/ESP32-S2 we currently don't support I2C transactions larger than the - // FIFO apparently it would be possible by using non-fifo mode - // see https://github.com/espressif/arduino-esp32/blob/7e9afe8c5ed7b5bf29624a5cd6e07d431c027b97/cores/esp32/esp32-hal-i2c.c#L615 - - if start_index >= bytes.len() { + /// Executes an I2C write operation. + /// - `addr` is the address of the slave device. + /// - `bytes` is the data two be sent. + /// - `start` indicates whether the operation should start by a START + /// condition and sending the address. + /// - `stop` indicates whether the operation should end with a STOP + /// condition. + /// - `cmd_iterator` is an iterator over the command registers. + fn write_operation_blocking<'a>( + &self, + address: u8, + bytes: &[u8], + start: bool, + stop: bool, + cmd_iterator: &mut impl Iterator, + ) -> Result<(), Error> { + // Short circuit for zero length writes without start or end as that would be an + // invalid operation write lengths in the TRM (at least for ESP32-S3) are 1-255 + if bytes.is_empty() && !start && !stop { return Ok(()); } - // this is only possible when writing the I2C address in release mode - // from [perform_write_read] - for b in bytes { - write_fifo(self.register_block(), *b); - self.check_errors()?; - } + let index = self.start_write_operation(address, bytes, start, stop, cmd_iterator)?; + // Fill the FIFO with the remaining bytes: + self.write_remaining_tx_fifo_blocking(index, bytes)?; + self.wait_for_completion_blocking(!stop)?; Ok(()) } - /// Resets the transmit and receive FIFO buffers - #[cfg(not(esp32))] - fn reset_fifo(&self) { - // First, reset the fifo buffers - self.register_block().fifo_conf().modify(|_, w| unsafe { - w.tx_fifo_rst().set_bit(); - w.rx_fifo_rst().set_bit(); - w.nonfifo_en().clear_bit(); - w.fifo_prt_en().set_bit(); - w.rxfifo_wm_thrhd().bits(1); - w.txfifo_wm_thrhd().bits(8) - }); - - self.register_block().fifo_conf().modify(|_, w| { - w.tx_fifo_rst().clear_bit(); - w.rx_fifo_rst().clear_bit() - }); - - self.register_block().int_clr().write(|w| { - w.rxfifo_wm().clear_bit_by_one(); - w.txfifo_wm().clear_bit_by_one() - }); - - self.update_config(); - } - - /// Resets the transmit and receive FIFO buffers - #[cfg(esp32)] - fn reset_fifo(&self) { - // First, reset the fifo buffers - self.register_block().fifo_conf().modify(|_, w| unsafe { - w.tx_fifo_rst().set_bit(); - w.rx_fifo_rst().set_bit(); - w.nonfifo_en().clear_bit(); - w.nonfifo_rx_thres().bits(1); - w.nonfifo_tx_thres().bits(32) - }); - - self.register_block().fifo_conf().modify(|_, w| { - w.tx_fifo_rst().clear_bit(); - w.rx_fifo_rst().clear_bit() - }); - - self.register_block() - .int_clr() - .write(|w| w.rxfifo_full().clear_bit_by_one()); - } - /// Executes an I2C write operation. /// - `addr` is the address of the slave device. /// - `bytes` is the data two be sent. @@ -1981,41 +1143,25 @@ impl Info { /// - `stop` indicates whether the operation should end with a STOP /// condition. /// - `cmd_iterator` is an iterator over the command registers. - fn write_operation<'a, I>( + async fn write_operation<'a>( &self, address: u8, bytes: &[u8], start: bool, stop: bool, - cmd_iterator: &mut I, - ) -> Result<(), Error> - where - I: Iterator, - { + cmd_iterator: &mut impl Iterator, + ) -> Result<(), Error> { // Short circuit for zero length writes without start or end as that would be an // invalid operation write lengths in the TRM (at least for ESP32-S3) are 1-255 if bytes.is_empty() && !start && !stop { return Ok(()); } - // Reset FIFO and command list - self.reset_fifo(); - self.reset_command_list(); - - if start { - add_cmd(cmd_iterator, Command::Start)?; - } - self.setup_write(address, bytes, start, cmd_iterator)?; - add_cmd( - cmd_iterator, - if stop { Command::Stop } else { Command::End }, - )?; - let index = self.fill_tx_fifo(bytes); - self.start_transmission(); + let index = self.start_write_operation(address, bytes, start, stop, cmd_iterator)?; // Fill the FIFO with the remaining bytes: - self.write_remaining_tx_fifo(index, bytes)?; - self.wait_for_completion(!stop)?; + self.write_remaining_tx_fifo(index, bytes).await?; + self.wait_for_completion(!stop).await?; Ok(()) } @@ -2029,41 +1175,142 @@ impl Info { /// - `will_continue` indicates whether there is another read operation /// following this one and we should not nack the last byte. /// - `cmd_iterator` is an iterator over the command registers. - fn read_operation<'a, I>( + fn read_operation_blocking<'a>( &self, address: u8, buffer: &mut [u8], start: bool, stop: bool, will_continue: bool, - cmd_iterator: &mut I, - ) -> Result<(), Error> - where - I: Iterator, - { + cmd_iterator: &mut impl Iterator, + ) -> Result<(), Error> { // Short circuit for zero length reads as that would be an invalid operation // read lengths in the TRM (at least for ESP32-S3) are 1-255 if buffer.is_empty() { return Ok(()); } + self.start_read_operation(address, buffer, start, stop, will_continue, cmd_iterator)?; + self.read_all_from_fifo_blocking(buffer)?; + self.wait_for_completion_blocking(!stop)?; + Ok(()) + } + + /// Executes an async I2C read operation. + /// - `addr` is the address of the slave device. + /// - `buffer` is the buffer to store the read data. + /// - `start` indicates whether the operation should start by a START + /// condition and sending the address. + /// - `stop` indicates whether the operation should end with a STOP + /// condition. + /// - `will_continue` indicates whether there is another read operation + /// following this one and we should not nack the last byte. + /// - `cmd_iterator` is an iterator over the command registers. + async fn read_operation<'a>( + &self, + address: u8, + buffer: &mut [u8], + start: bool, + stop: bool, + will_continue: bool, + cmd_iterator: &mut impl Iterator, + ) -> Result<(), Error> { + // Short circuit for zero length reads as that would be an invalid operation + // read lengths in the TRM (at least for ESP32-S3) are 1-255 + if buffer.is_empty() { + return Ok(()); + } + + self.start_read_operation(address, buffer, start, stop, will_continue, cmd_iterator)?; + self.read_all_from_fifo(buffer).await?; + self.wait_for_completion(!stop).await?; + Ok(()) + } + + fn start_read_operation<'a>( + &self, + address: u8, + buffer: &mut [u8], + start: bool, + stop: bool, + will_continue: bool, + cmd_iterator: &mut impl Iterator, + ) -> Result<(), Error> { // Reset FIFO and command list self.reset_fifo(); self.reset_command_list(); if start { - add_cmd(cmd_iterator, Command::Start)?; + self.add_cmd(cmd_iterator, Command::Start)?; } self.setup_read(address, buffer, start, will_continue, cmd_iterator)?; - add_cmd( + self.add_cmd( + cmd_iterator, + if stop { Command::Stop } else { Command::End }, + )?; + self.start_transmission(); + + Ok(()) + } + + fn start_write_operation<'a>( + &self, + address: u8, + bytes: &[u8], + start: bool, + stop: bool, + cmd_iterator: &mut impl Iterator, + ) -> Result { + // Reset FIFO and command list + self.reset_fifo(); + self.reset_command_list(); + + if start { + self.add_cmd(cmd_iterator, Command::Start)?; + } + self.setup_write(address, bytes, start, cmd_iterator)?; + self.add_cmd( cmd_iterator, if stop { Command::Stop } else { Command::End }, )?; + let index = self.fill_tx_fifo(bytes); self.start_transmission(); - self.read_all_from_fifo(buffer)?; - self.wait_for_completion(!stop)?; + + Ok(index) + } + + /// Adds a command to the I2C command sequence. + fn add_cmd<'a, I>(&self, cmd_iterator: &mut I, command: Command) -> Result<(), Error> + where + I: Iterator, + { + let cmd = cmd_iterator.next().ok_or(Error::CommandNrExceeded)?; + + cmd.write(|w| match command { + Command::Start => w.opcode().rstart(), + Command::Stop => w.opcode().stop(), + Command::End => w.opcode().end(), + Command::Write { + ack_exp, + ack_check_en, + length, + } => unsafe { + w.opcode().write(); + w.ack_exp().bit(ack_exp == Ack::Nack); + w.ack_check_en().bit(ack_check_en); + w.byte_num().bits(length); + w + }, + Command::Read { ack_value, length } => unsafe { + w.opcode().read(); + w.ack_value().bit(ack_value == Ack::Nack); + w.byte_num().bits(length); + w + }, + }); + Ok(()) } } @@ -2101,75 +1348,6 @@ pub trait Instance: Peripheral

+ PeripheralMarker + Into + 'st } } -/// Adds a command to the I2C command sequence. -fn add_cmd<'a, I>(cmd_iterator: &mut I, command: Command) -> Result<(), Error> -where - I: Iterator, -{ - let cmd = cmd_iterator.next().ok_or(Error::CommandNrExceeded)?; - - cmd.write(|w| match command { - Command::Start => w.opcode().rstart(), - Command::Stop => w.opcode().stop(), - Command::End => w.opcode().end(), - Command::Write { - ack_exp, - ack_check_en, - length, - } => unsafe { - w.opcode().write(); - w.ack_exp().bit(ack_exp == Ack::Nack); - w.ack_check_en().bit(ack_check_en); - w.byte_num().bits(length); - w - }, - Command::Read { ack_value, length } => unsafe { - w.opcode().read(); - w.ack_value().bit(ack_value == Ack::Nack); - w.byte_num().bits(length); - w - }, - }); - - Ok(()) -} - -#[cfg(not(esp32s2))] -fn read_fifo(register_block: &RegisterBlock) -> u8 { - register_block.data().read().fifo_rdata().bits() -} - -#[cfg(not(esp32))] -fn write_fifo(register_block: &RegisterBlock, data: u8) { - register_block - .data() - .write(|w| unsafe { w.fifo_rdata().bits(data) }); -} - -#[cfg(esp32s2)] -fn read_fifo(register_block: &RegisterBlock) -> u8 { - let base_addr = register_block.scl_low_period().as_ptr(); - let fifo_ptr = (if base_addr as u32 == 0x3f413000 { - 0x6001301c - } else { - 0x6002701c - }) as *mut u32; - unsafe { (fifo_ptr.read_volatile() & 0xff) as u8 } -} - -#[cfg(esp32)] -fn write_fifo(register_block: &RegisterBlock, data: u8) { - let base_addr = register_block.scl_low_period().as_ptr(); - let fifo_ptr = (if base_addr as u32 == 0x3FF53000 { - 0x6001301c - } else { - 0x6002701c - }) as *mut u32; - unsafe { - fifo_ptr.write_volatile(data as u32); - } -} - macro_rules! instance { ($inst:ident, $scl:ident, $sda:ident, $interrupt:ident) => { impl Instance for crate::peripherals::$inst { diff --git a/esp-hal/src/i2c/version/mod.rs b/esp-hal/src/i2c/version/mod.rs new file mode 100644 index 00000000000..0d33622dc07 --- /dev/null +++ b/esp-hal/src/i2c/version/mod.rs @@ -0,0 +1,16 @@ +cfg_if::cfg_if! { + if #[cfg(esp32)] { + mod v1; + mod v1_v2_common; + pub(crate) use v1::*; + } else if #[cfg(esp32s2)] { + mod v2; + mod v1_v2_common; + mod v2_v3_common; + pub(crate) use v2::*; + } else { + mod v3; + mod v2_v3_common; + pub(crate) use v3::*; + } +} diff --git a/esp-hal/src/i2c/version/v1.rs b/esp-hal/src/i2c/version/v1.rs new file mode 100644 index 00000000000..1dd0b32622c --- /dev/null +++ b/esp-hal/src/i2c/version/v1.rs @@ -0,0 +1,206 @@ +//! ESP32-specific implementation +use crate::i2c::*; + +pub(crate) const I2C_CHUNK_SIZE: usize = 32; +pub(crate) const I2C_LL_INTR_MASK: u32 = 0x3ffff; + +impl Driver<'_> { + pub(crate) async fn wait_for_completion(&self, end_only: bool) -> Result<(), Error> { + // for ESP32 we need a timeout here but wasting a timer seems unnecessary + // given the short time we spend here + + let mut tout = MAX_ITERATIONS / 10; // adjust the timeout because we are yielding in the loop + loop { + let interrupts = self.info.register_block().int_raw().read(); + + self.check_errors()?; + + // Handle completion cases + // A full transmission was completed (either a STOP condition or END was + // processed) + if (!end_only && interrupts.trans_complete().bit_is_set()) + || interrupts.end_detect().bit_is_set() + { + break; + } + + tout -= 1; + if tout == 0 { + return Err(Error::TimeOut); + } + + embassy_futures::yield_now().await; + } + self.check_all_commands_done()?; + Ok(()) + } + + pub(crate) async fn write_remaining_tx_fifo( + &self, + start_index: usize, + bytes: &[u8], + ) -> Result<(), Error> { + if start_index >= bytes.len() { + return Ok(()); + } + + for b in bytes { + self.write_fifo(*b); + self.check_errors()?; + } + + Ok(()) + } + + /// Checks for I2C transmission errors and handles them. + /// + /// This function inspects specific I2C-related interrupts to detect errors + /// during communication, such as timeouts, failed acknowledgments, or + /// arbitration loss. If an error is detected, the function handles it + /// by resetting the I2C peripheral to clear the error condition and then + /// returns an appropriate error. + pub(crate) fn check_errors(&self) -> Result<(), Error> { + let interrupts = self.info.register_block().int_raw().read(); + + let retval = if interrupts.time_out().bit_is_set() { + Error::TimeOut + } else if interrupts.ack_err().bit_is_set() { + Error::AckCheckFailed + } else if interrupts.arbitration_lost().bit_is_set() { + Error::ArbitrationLost + } else { + return Ok(()); + }; + + self.reset(); + + Err(retval) + } + + /// Resets the I2C controller (FIFO + FSM + command list) + pub(crate) fn reset(&self) { + // Clear all I2C interrupts + self.info + .register_block() + .int_clr() + .write(|w| unsafe { w.bits(I2C_LL_INTR_MASK) }); + + // Reset fifo + self.reset_fifo(); + + // Reset the command list + self.reset_command_list(); + } + + /// Resets the transmit and receive FIFO buffers + pub(crate) fn reset_fifo(&self) { + // First, reset the fifo buffers + self.info + .register_block() + .fifo_conf() + .modify(|_, w| unsafe { + w.tx_fifo_rst().set_bit(); + w.rx_fifo_rst().set_bit(); + w.nonfifo_en().clear_bit(); + w.nonfifo_rx_thres().bits(1); + w.nonfifo_tx_thres().bits(32) + }); + + self.info.register_block().fifo_conf().modify(|_, w| { + w.tx_fifo_rst().clear_bit(); + w.rx_fifo_rst().clear_bit() + }); + + self.info + .register_block() + .int_clr() + .write(|w| w.rxfifo_full().clear_bit_by_one()); + } + + /// Sets the frequency of the I2C interface by calculating and applying the + /// associated timings - corresponds to i2c_ll_cal_bus_clk and + /// i2c_ll_set_bus_timing in ESP-IDF + pub(crate) fn set_frequency(&self, bus_freq: HertzU32, timeout: Option) { + let source_clk = Clocks::get().i2c_clock.raw(); + let bus_freq = bus_freq.raw(); + + let half_cycle: u32 = source_clk / bus_freq / 2; + let scl_low = half_cycle; + let scl_high = half_cycle; + let sda_hold = half_cycle / 2; + let sda_sample = scl_high / 2; + let setup = half_cycle; + let hold = half_cycle; + // default we set the timeout value to 10 bus cycles + let time_out_value = timeout.unwrap_or(half_cycle * 20); + + // SCL period. According to the TRM, we should always subtract 1 to SCL low + // period + let scl_low = scl_low - 1; + // Still according to the TRM, if filter is not enbled, we have to subtract 7, + // if SCL filter is enabled, we have to subtract: + // 8 if SCL filter is between 0 and 2 (included) + // 6 + SCL threshold if SCL filter is between 3 and 7 (included) + // to SCL high period + let mut scl_high = scl_high; + // In the "worst" case, we will subtract 13, make sure the result will still be + // correct + + // FIXME since we always set the filter threshold to 7 we don't need conditional + // code here once that changes we need the conditional code here + scl_high -= 7 + 6; + + // if (filter_cfg_en) { + // if (thres <= 2) { + // scl_high -= 8; + // } else { + // assert(hw->scl_filter_cfg.thres <= 7); + // scl_high -= thres + 6; + // } + // } else { + // scl_high -= 7; + //} + + let scl_high_period = scl_high; + let scl_low_period = scl_low; + // sda sample + let sda_hold_time = sda_hold; + let sda_sample_time = sda_sample; + // setup + let scl_rstart_setup_time = setup; + let scl_stop_setup_time = setup; + // hold + let scl_start_hold_time = hold; + let scl_stop_hold_time = hold; + + configure_clock( + self.info.register_block(), + 0, + scl_low_period, + scl_high_period, + 0, + sda_hold_time, + sda_sample_time, + scl_rstart_setup_time, + scl_stop_setup_time, + scl_start_hold_time, + scl_stop_hold_time, + time_out_value, + true, + ); + } + + pub(crate) fn read_fifo(&self) -> u8 { + self.info.register_block().data().read().fifo_rdata().bits() + } + + pub(crate) fn write_fifo(&self, data: u8) { + let base_addr = self.info.register_block().scl_low_period().as_ptr(); + let fifo_ptr = (if base_addr as u32 == 0x3FF53000 { + 0x6001301c + } else { + 0x6002701c + }) as *mut u32; + unsafe { fifo_ptr.write_volatile(data as u32) }; + } +} diff --git a/esp-hal/src/i2c/version/v1_v2_common.rs b/esp-hal/src/i2c/version/v1_v2_common.rs new file mode 100644 index 00000000000..a07bb199b3f --- /dev/null +++ b/esp-hal/src/i2c/version/v1_v2_common.rs @@ -0,0 +1,120 @@ +use crate::i2c::*; + +fn assert_read_length(len: usize) { + if len <= 32 { + return; + } + + cfg_if::cfg_if! { + if #[cfg(esp32)] { + panic!("On ESP32 the max I2C read is limited to 32 bytes"); + } else { + panic!("On ESP32-S2 the max I2C read is limited to 32 bytes"); + } + } +} + +impl Driver<'_> { + /// Writes remaining data from byte slice to the TX FIFO from the specified + /// index. + pub(crate) fn write_remaining_tx_fifo_blocking( + &self, + start_index: usize, + bytes: &[u8], + ) -> Result<(), Error> { + // on ESP32/ESP32-S2 we currently don't support I2C transactions larger than the + // FIFO apparently it would be possible by using non-fifo mode + // see https://github.com/espressif/arduino-esp32/blob/7e9afe8c5ed7b5bf29624a5cd6e07d431c027b97/cores/esp32/esp32-hal-i2c.c#L615 + + if start_index >= bytes.len() { + return Ok(()); + } + + // this is only possible when writing the I2C address in release mode + // from [perform_write_read] + for b in bytes { + self.write_fifo(*b); + self.check_errors()?; + } + + Ok(()) + } + + pub(crate) fn read_all_from_fifo_blocking(&self, buffer: &mut [u8]) -> Result<(), Error> { + // on ESP32/ESP32-S2 we currently don't support I2C transactions larger than the + // FIFO apparently it would be possible by using non-fifo mode + // see https://github.com/espressif/arduino-esp32/blob/7e9afe8c5ed7b5bf29624a5cd6e07d431c027b97/cores/esp32/esp32-hal-i2c.c#L615 + + assert_read_length(buffer.len()); + + // wait for completion - then we can just read the data from FIFO + // once we change to non-fifo mode to support larger transfers that + // won't work anymore + self.wait_for_completion_blocking(false)?; + + // Read bytes from FIFO + // FIXME: Handle case where less data has been provided by the slave than + // requested? Or is this prevented from a protocol perspective? + for byte in buffer.iter_mut() { + *byte = self.read_fifo(); + } + + Ok(()) + } + + pub(crate) async fn read_all_from_fifo(&self, buffer: &mut [u8]) -> Result<(), Error> { + assert_read_length(buffer.len()); + + self.wait_for_completion(false).await?; + + for byte in buffer.iter_mut() { + *byte = self.read_fifo(); + } + + Ok(()) + } + + /// Updates the configuration of the I2C peripheral. + pub(crate) fn update_config(&self) {} + + /// Sets the filter with a supplied threshold in clock cycles for which a + /// pulse must be present to pass the filter + pub(crate) fn set_filter(&self, sda_threshold: Option, scl_threshold: Option) { + let register_block = self.info.register_block(); + register_block.sda_filter_cfg().modify(|_, w| { + if let Some(threshold) = sda_threshold { + unsafe { w.sda_filter_thres().bits(threshold) }; + } + w.sda_filter_en().bit(sda_threshold.is_some()) + }); + register_block.scl_filter_cfg().modify(|_, w| { + if let Some(threshold) = scl_threshold { + unsafe { w.scl_filter_thres().bits(threshold) }; + } + w.scl_filter_en().bit(scl_threshold.is_some()) + }); + } + + /// Fills the TX FIFO with data from the provided slice. + pub(crate) fn fill_tx_fifo(&self, bytes: &[u8]) -> usize { + // on ESP32/ESP32-S2 we currently don't support I2C transactions larger than the + // FIFO apparently it would be possible by using non-fifo mode + // see https://github.com/espressif/arduino-esp32/blob/7e9afe8c5ed7b5bf29624a5cd6e07d431c027b97/cores/esp32/esp32-hal-i2c.c#L615 + + if bytes.len() > 31 { + cfg_if::cfg_if! { + if #[cfg(esp32)] { + panic!("On ESP32 the max I2C transfer is limited to 31 bytes"); + } else { + panic!("On ESP32-S2 the max I2C transfer is limited to 31 bytes"); + } + } + } + + for b in bytes { + self.write_fifo(*b); + } + + bytes.len() + } +} diff --git a/esp-hal/src/i2c/version/v2.rs b/esp-hal/src/i2c/version/v2.rs new file mode 100644 index 00000000000..afb51e40509 --- /dev/null +++ b/esp-hal/src/i2c/version/v2.rs @@ -0,0 +1,87 @@ +//! ESP32-S2-specific implementation +use crate::i2c::*; + +pub(crate) const I2C_CHUNK_SIZE: usize = 32; +pub(crate) const I2C_LL_INTR_MASK: u32 = 0x1ffff; + +impl Driver<'_> { + pub(crate) async fn write_remaining_tx_fifo( + &self, + start_index: usize, + bytes: &[u8], + ) -> Result<(), Error> { + if start_index >= bytes.len() { + return Ok(()); + } + + for b in bytes { + self.write_fifo(*b); + self.check_errors()?; + } + + Ok(()) + } + + /// Sets the frequency of the I2C interface by calculating and applying the + /// associated timings - corresponds to i2c_ll_cal_bus_clk and + /// i2c_ll_set_bus_timing in ESP-IDF + pub(crate) fn set_frequency(&self, bus_freq: HertzU32, timeout: Option) { + let source_clk = Clocks::get().apb_clock.raw(); + let bus_freq = bus_freq.raw(); + + let half_cycle: u32 = source_clk / bus_freq / 2; + // SCL + let scl_low = half_cycle; + // default, scl_wait_high < scl_high + let scl_high = half_cycle / 2 + 2; + let scl_wait_high = half_cycle - scl_high; + let sda_hold = half_cycle / 2; + // scl_wait_high < sda_sample <= scl_high + let sda_sample = half_cycle / 2 - 1; + let setup = half_cycle; + let hold = half_cycle; + // default we set the timeout value to 10 bus cycles + let time_out_value = timeout.unwrap_or(half_cycle * 20); + + // scl period + let scl_low_period = scl_low - 1; + let scl_high_period = scl_high; + let scl_wait_high_period = scl_wait_high; + // sda sample + let sda_hold_time = sda_hold; + let sda_sample_time = sda_sample; + // setup + let scl_rstart_setup_time = setup; + let scl_stop_setup_time = setup; + // hold + let scl_start_hold_time = hold - 1; + let scl_stop_hold_time = hold; + let time_out_en = true; + + configure_clock( + self.info.register_block(), + 0, + scl_low_period, + scl_high_period, + scl_wait_high_period, + sda_hold_time, + sda_sample_time, + scl_rstart_setup_time, + scl_stop_setup_time, + scl_start_hold_time, + scl_stop_hold_time, + time_out_value, + time_out_en, + ); + } + + pub(crate) fn read_fifo(&self) -> u8 { + let base_addr = self.info.register_block().scl_low_period().as_ptr(); + let fifo_ptr = (if base_addr as u32 == 0x3f413000 { + 0x6001301c + } else { + 0x6002701c + }) as *mut u32; + unsafe { (fifo_ptr.read_volatile() & 0xff) as u8 } + } +} diff --git a/esp-hal/src/i2c/version/v2_v3_common.rs b/esp-hal/src/i2c/version/v2_v3_common.rs new file mode 100644 index 00000000000..e5a2c816717 --- /dev/null +++ b/esp-hal/src/i2c/version/v2_v3_common.rs @@ -0,0 +1,218 @@ +use core::{ + pin::Pin, + task::{Context, Poll}, +}; + +use crate::i2c::*; + +pub enum Event { + EndDetect, + TxComplete, + #[cfg(not(esp32s2))] + TxFifoWatermark, +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct I2cFuture<'a> { + event: Event, + info: &'a Info, + state: &'a State, +} + +impl<'a> I2cFuture<'a> { + pub fn new(event: Event, info: &'a Info, state: &'a State) -> Self { + info.register_block().int_ena().modify(|_, w| { + let w = match event { + Event::EndDetect => w.end_detect().set_bit(), + Event::TxComplete => w.trans_complete().set_bit(), + #[cfg(not(esp32s2))] + Event::TxFifoWatermark => w.txfifo_wm().set_bit(), + }; + + w.arbitration_lost().set_bit(); + w.time_out().set_bit(); + w.nack().set_bit() + }); + + Self { event, state, info } + } + + fn event_bit_is_clear(&self) -> bool { + let r = self.info.register_block().int_ena().read(); + + match self.event { + Event::EndDetect => r.end_detect().bit_is_clear(), + Event::TxComplete => r.trans_complete().bit_is_clear(), + #[cfg(not(esp32s2))] + Event::TxFifoWatermark => r.txfifo_wm().bit_is_clear(), + } + } + + fn check_error(&self) -> Result<(), Error> { + let r = self.info.register_block().int_raw().read(); + + if r.arbitration_lost().bit_is_set() { + return Err(Error::ArbitrationLost); + } + + if r.time_out().bit_is_set() { + return Err(Error::TimeOut); + } + + if r.nack().bit_is_set() { + return Err(Error::AckCheckFailed); + } + + if r.trans_complete().bit_is_set() + && self + .info + .register_block() + .sr() + .read() + .resp_rec() + .bit_is_clear() + { + return Err(Error::AckCheckFailed); + } + + Ok(()) + } +} + +impl core::future::Future for I2cFuture<'_> { + type Output = Result<(), Error>; + + fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { + self.state.waker.register(ctx.waker()); + + let error = self.check_error(); + + if error.is_err() { + return Poll::Ready(error); + } + + if self.event_bit_is_clear() { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + } +} + +impl Driver<'_> { + pub(crate) async fn wait_for_completion(&self, end_only: bool) -> Result<(), Error> { + self.check_errors()?; + + if end_only { + I2cFuture::new(Event::EndDetect, self.info, self.state).await?; + } else { + let res = embassy_futures::select::select( + I2cFuture::new(Event::TxComplete, self.info, self.state), + I2cFuture::new(Event::EndDetect, self.info, self.state), + ) + .await; + + match res { + embassy_futures::select::Either::First(res) => res?, + embassy_futures::select::Either::Second(res) => res?, + } + } + self.check_all_commands_done()?; + + Ok(()) + } +} + +impl Driver<'_> { + /// Checks for I2C transmission errors and handles them. + /// + /// This function inspects specific I2C-related interrupts to detect errors + /// during communication, such as timeouts, failed acknowledgments, or + /// arbitration loss. If an error is detected, the function handles it + /// by resetting the I2C peripheral to clear the error condition and then + /// returns an appropriate error. + pub(crate) fn check_errors(&self) -> Result<(), Error> { + let interrupts = self.info.register_block().int_raw().read(); + + // Handle error cases + let retval = if interrupts.time_out().bit_is_set() { + Error::TimeOut + } else if interrupts.nack().bit_is_set() { + Error::AckCheckFailed + } else if interrupts.arbitration_lost().bit_is_set() { + Error::ArbitrationLost + } else if interrupts.trans_complete().bit_is_set() + && self + .info + .register_block() + .sr() + .read() + .resp_rec() + .bit_is_clear() + { + Error::AckCheckFailed + } else { + return Ok(()); + }; + + self.reset(); + + Err(retval) + } + + /// Resets the I2C controller (FIFO + FSM + command list) + pub(crate) fn reset(&self) { + // Reset the FSM + self.info + .register_block() + .ctr() + .modify(|_, w| w.fsm_rst().set_bit()); + + // Clear all I2C interrupts + self.info + .register_block() + .int_clr() + .write(|w| unsafe { w.bits(I2C_LL_INTR_MASK) }); + + // Reset fifo + self.reset_fifo(); + + // Reset the command list + self.reset_command_list(); + } + + /// Resets the transmit and receive FIFO buffers + pub(crate) fn reset_fifo(&self) { + // First, reset the fifo buffers + self.info + .register_block() + .fifo_conf() + .modify(|_, w| unsafe { + w.tx_fifo_rst().set_bit(); + w.rx_fifo_rst().set_bit(); + w.nonfifo_en().clear_bit(); + w.fifo_prt_en().set_bit(); + w.rxfifo_wm_thrhd().bits(1); + w.txfifo_wm_thrhd().bits(8) + }); + + self.info.register_block().fifo_conf().modify(|_, w| { + w.tx_fifo_rst().clear_bit(); + w.rx_fifo_rst().clear_bit() + }); + + self.info.register_block().int_clr().write(|w| { + w.rxfifo_wm().clear_bit_by_one(); + w.txfifo_wm().clear_bit_by_one() + }); + + self.update_config(); + } + + pub(crate) fn write_fifo(&self, data: u8) { + self.info + .register_block() + .data() + .write(|w| unsafe { w.fifo_rdata().bits(data) }); + } +} diff --git a/esp-hal/src/i2c/version/v3.rs b/esp-hal/src/i2c/version/v3.rs new file mode 100644 index 00000000000..79bdc3902a3 --- /dev/null +++ b/esp-hal/src/i2c/version/v3.rs @@ -0,0 +1,247 @@ +use crate::i2c::{version::v2_v3_common::*, *}; + +pub(crate) const I2C_CHUNK_SIZE: usize = 254; +pub(crate) const I2C_LL_INTR_MASK: u32 = 0x3ffff; + +impl Driver<'_> { + /// Writes remaining data from byte slice to the TX FIFO from the specified + /// index. + pub(crate) fn write_remaining_tx_fifo_blocking( + &self, + start_index: usize, + bytes: &[u8], + ) -> Result<(), Error> { + let mut index = start_index; + loop { + self.check_errors()?; + + while !self + .info + .register_block() + .int_raw() + .read() + .txfifo_wm() + .bit_is_set() + { + self.check_errors()?; + } + + self.info + .register_block() + .int_clr() + .write(|w| w.txfifo_wm().clear_bit_by_one()); + + while !self + .info + .register_block() + .int_raw() + .read() + .txfifo_wm() + .bit_is_set() + { + self.check_errors()?; + } + + if index >= bytes.len() { + break Ok(()); + } + + self.write_fifo(bytes[index]); + index += 1; + } + } + + pub(crate) async fn write_remaining_tx_fifo( + &self, + start_index: usize, + bytes: &[u8], + ) -> Result<(), Error> { + let mut index = start_index; + loop { + self.check_errors()?; + + I2cFuture::new(Event::TxFifoWatermark, self.info, self.state).await?; + + self.info + .register_block() + .int_clr() + .write(|w| w.txfifo_wm().clear_bit_by_one()); + + I2cFuture::new(Event::TxFifoWatermark, self.info, self.state).await?; + + if index >= bytes.len() { + break Ok(()); + } + + self.write_fifo(bytes[index]); + index += 1; + } + } + + /// Reads all bytes from the RX FIFO. + pub(crate) fn read_all_from_fifo_blocking(&self, buffer: &mut [u8]) -> Result<(), Error> { + // Read bytes from FIFO + // FIXME: Handle case where less data has been provided by the slave than + // requested? Or is this prevented from a protocol perspective? + for byte in buffer.iter_mut() { + loop { + self.check_errors()?; + + let reg = self.info.register_block().fifo_st().read(); + if reg.rxfifo_raddr().bits() != reg.rxfifo_waddr().bits() { + break; + } + } + + *byte = self.read_fifo(); + } + + Ok(()) + } + + pub(crate) async fn read_all_from_fifo(&self, buffer: &mut [u8]) -> Result<(), Error> { + self.read_all_from_fifo_blocking(buffer) + } + + /// Updates the configuration of the I2C peripheral. + /// + /// This function ensures that the configuration values, such as clock + /// settings, SDA/SCL filtering, timeouts, and other operational + /// parameters, which are configured in other functions, are properly + /// propagated to the I2C hardware. This step is necessary to synchronize + /// the software-configured settings with the peripheral's internal + /// registers, ensuring that the hardware behaves according to the + /// current configuration. + pub(crate) fn update_config(&self) { + // Ensure that the configuration of the peripheral is correctly propagated + self.info + .register_block() + .ctr() + .modify(|_, w| w.conf_upgate().set_bit()); + } + + /// Sets the filter with a supplied threshold in clock cycles for which a + /// pulse must be present to pass the filter + pub(crate) fn set_filter(&self, sda_threshold: Option, scl_threshold: Option) { + let register_block = self.info.register_block(); + register_block.filter_cfg().modify(|_, w| { + if let Some(threshold) = sda_threshold { + unsafe { w.sda_filter_thres().bits(threshold) }; + } + if let Some(threshold) = scl_threshold { + unsafe { w.scl_filter_thres().bits(threshold) }; + } + w.sda_filter_en().bit(sda_threshold.is_some()); + w.scl_filter_en().bit(scl_threshold.is_some()) + }); + } + + /// Sets the frequency of the I2C interface by calculating and applying the + /// associated timings - corresponds to i2c_ll_cal_bus_clk and + /// i2c_ll_set_bus_timing in ESP-IDF + pub(crate) fn set_frequency(&self, bus_freq: HertzU32, timeout: Option) { + let source_clk = Clocks::get().xtal_clock.raw(); + let bus_freq = bus_freq.raw(); + + let clkm_div: u32 = source_clk / (bus_freq * 1024) + 1; + let sclk_freq: u32 = source_clk / clkm_div; + let half_cycle: u32 = sclk_freq / bus_freq / 2; + // SCL + let scl_low = half_cycle; + // default, scl_wait_high < scl_high + // Make 80KHz as a boundary here, because when working at lower frequency, too + // much scl_wait_high will faster the frequency according to some + // hardware behaviors. + let scl_wait_high = if bus_freq >= 80 * 1000 { + half_cycle / 2 - 2 + } else { + half_cycle / 4 + }; + let scl_high = half_cycle - scl_wait_high; + let sda_hold = half_cycle / 4; + let sda_sample = half_cycle / 2 + scl_wait_high; + let setup = half_cycle; + let hold = half_cycle; + + let time_out_value = if let Some(timeout) = timeout { + timeout + } else { + // default we set the timeout value to about 10 bus cycles + // log(20*half_cycle)/log(2) = log(half_cycle)/log(2) + log(20)/log(2) + (4 * 8 - (5 * half_cycle).leading_zeros()) + 2 + }; + + // According to the Technical Reference Manual, the following timings must be + // subtracted by 1. However, according to the practical measurement and + // some hardware behaviour, if wait_high_period and scl_high minus one. + // The SCL frequency would be a little higher than expected. Therefore, the + // solution here is not to minus scl_high as well as scl_wait high, and + // the frequency will be absolutely accurate to all frequency + // to some extent. + let scl_low_period = scl_low - 1; + let scl_high_period = scl_high; + let scl_wait_high_period = scl_wait_high; + // sda sample + let sda_hold_time = sda_hold - 1; + let sda_sample_time = sda_sample - 1; + // setup + let scl_rstart_setup_time = setup - 1; + let scl_stop_setup_time = setup - 1; + // hold + let scl_start_hold_time = hold - 1; + let scl_stop_hold_time = hold - 1; + let time_out_en = true; + + configure_clock( + self.info.register_block(), + clkm_div, + scl_low_period, + scl_high_period, + scl_wait_high_period, + sda_hold_time, + sda_sample_time, + scl_rstart_setup_time, + scl_stop_setup_time, + scl_start_hold_time, + scl_stop_hold_time, + time_out_value, + time_out_en, + ); + } + + pub(crate) fn read_fifo(&self) -> u8 { + self.info.register_block().data().read().fifo_rdata().bits() + } + + /// Fills the TX FIFO with data from the provided slice. + pub(crate) fn fill_tx_fifo(&self, bytes: &[u8]) -> usize { + let mut index = 0; + while index < bytes.len() + && !self + .info + .register_block() + .int_raw() + .read() + .txfifo_ovf() + .bit_is_set() + { + self.write_fifo(bytes[index]); + index += 1; + } + if self + .info + .register_block() + .int_raw() + .read() + .txfifo_ovf() + .bit_is_set() + { + index -= 1; + self.info + .register_block() + .int_clr() + .write(|w| w.txfifo_ovf().clear_bit_by_one()); + } + index + } +} From 76a1bb550cd27c526434c9fbc3f708c735b257c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 4 Nov 2024 15:14:02 +0100 Subject: [PATCH 05/18] Remove pins from constructors --- esp-hal/src/i2c/mod.rs | 68 +++++++------------ examples/src/bin/embassy_i2c.rs | 5 +- .../embassy_i2c_bmp180_calibration_data.rs | 5 +- .../src/bin/i2c_bmp180_calibration_data.rs | 4 +- examples/src/bin/i2c_display.rs | 4 +- examples/src/bin/lcd_cam_ov2640.rs | 4 +- hil-test/tests/i2c.rs | 4 +- 7 files changed, 44 insertions(+), 50 deletions(-) diff --git a/esp-hal/src/i2c/mod.rs b/esp-hal/src/i2c/mod.rs index a3bb9476425..eb21133699e 100644 --- a/esp-hal/src/i2c/mod.rs +++ b/esp-hal/src/i2c/mod.rs @@ -39,10 +39,10 @@ //! // and standard I2C clock speed. //! let mut i2c = I2c::new( //! peripherals.I2C0, -//! io.pins.gpio1, -//! io.pins.gpio2, //! 100.kHz(), -//! ); +//! ) +//! .with_sda(io.pins.gpio1) +//! .with_scl(io.pins.gpio2); //! //! loop { //! let mut data = [0u8; 22]; @@ -217,31 +217,6 @@ impl<'d, T, DM: Mode> I2c<'d, DM, T> where T: Instance, { - fn new_internal( - i2c: impl Peripheral

+ 'd, - sda: impl Peripheral

+ 'd, - scl: impl Peripheral

+ 'd, - frequency: HertzU32, - ) -> Self { - crate::into_ref!(i2c); - crate::into_mapped_ref!(sda, scl); - - let i2c = I2c { - i2c, - phantom: PhantomData, - frequency, - timeout: None, - }; - - PeripheralClockControl::reset(i2c.i2c.peripheral()); - PeripheralClockControl::enable(i2c.i2c.peripheral()); - - let i2c = i2c.with_sda(sda).with_scl(scl); - - i2c.driver().setup(frequency, None); - i2c - } - fn driver(&self) -> Driver<'_> { Driver { info: self.i2c.info(), @@ -322,14 +297,16 @@ where Ok(()) } - fn with_sda(self, sda: impl Peripheral

+ 'd) -> Self { + /// Connect a pin to the I2C SDA signal. + pub fn with_sda(self, sda: impl Peripheral

+ 'd) -> Self { let info = self.driver().info; let input = info.sda_input; let output = info.sda_output; self.with_pin(sda, input, output) } - fn with_scl(self, scl: impl Peripheral

+ 'd) -> Self { + /// Connect a pin to the I2C SCL signal. + pub fn with_scl(self, scl: impl Peripheral

+ 'd) -> Self { let info = self.driver().info; let input = info.scl_input; let output = info.scl_output; @@ -361,13 +338,8 @@ impl<'d> I2c<'d, Blocking> { /// Create a new I2C instance /// This will enable the peripheral but the peripheral won't get /// automatically disabled when this gets dropped. - pub fn new( - i2c: impl Peripheral

+ 'd, - sda: impl Peripheral

+ 'd, - scl: impl Peripheral

+ 'd, - frequency: HertzU32, - ) -> Self { - Self::new_typed(i2c.map_into(), sda, scl, frequency) + pub fn new(i2c: impl Peripheral

+ 'd, frequency: HertzU32) -> Self { + Self::new_typed(i2c.map_into(), frequency) } } @@ -378,13 +350,21 @@ where /// Create a new I2C instance /// This will enable the peripheral but the peripheral won't get /// automatically disabled when this gets dropped. - pub fn new_typed( - i2c: impl Peripheral

+ 'd, - sda: impl Peripheral

+ 'd, - scl: impl Peripheral

+ 'd, - frequency: HertzU32, - ) -> Self { - Self::new_internal(i2c, sda, scl, frequency) + pub fn new_typed(i2c: impl Peripheral

+ 'd, frequency: HertzU32) -> Self { + crate::into_ref!(i2c); + + let i2c = I2c { + i2c, + phantom: PhantomData, + frequency, + timeout: None, + }; + + PeripheralClockControl::reset(i2c.i2c.peripheral()); + PeripheralClockControl::enable(i2c.i2c.peripheral()); + + i2c.driver().setup(frequency, None); + i2c } // TODO: missing interrupt APIs diff --git a/examples/src/bin/embassy_i2c.rs b/examples/src/bin/embassy_i2c.rs index 152369854a4..8b6fcb343c1 100644 --- a/examples/src/bin/embassy_i2c.rs +++ b/examples/src/bin/embassy_i2c.rs @@ -31,7 +31,10 @@ async fn main(_spawner: Spawner) { let io = Io::new(peripherals.GPIO, peripherals.IO_MUX); - let i2c0 = I2c::new(peripherals.I2C0, io.pins.gpio4, io.pins.gpio5, 400.kHz()).into_async(); + let i2c0 = I2c::new(peripherals.I2C0, 400.kHz()) + .with_sda(io.pins.gpio4) + .with_scl(io.pins.gpio5) + .into_async(); let mut lis3dh = Lis3dh::new_i2c(i2c0, SlaveAddr::Alternate).await.unwrap(); lis3dh.set_range(Range::G8).await.unwrap(); diff --git a/examples/src/bin/embassy_i2c_bmp180_calibration_data.rs b/examples/src/bin/embassy_i2c_bmp180_calibration_data.rs index 24fb3b7798c..7ba6bc9179d 100644 --- a/examples/src/bin/embassy_i2c_bmp180_calibration_data.rs +++ b/examples/src/bin/embassy_i2c_bmp180_calibration_data.rs @@ -31,7 +31,10 @@ async fn main(_spawner: Spawner) { let io = Io::new(peripherals.GPIO, peripherals.IO_MUX); - let mut i2c = I2c::new(peripherals.I2C0, io.pins.gpio4, io.pins.gpio5, 400.kHz()).into_async(); + let mut i2c = I2c::new(peripherals.I2C0, 400.kHz()) + .with_sda(io.pins.gpio4) + .with_scl(io.pins.gpio5) + .into_async(); loop { let mut data = [0u8; 22]; diff --git a/examples/src/bin/i2c_bmp180_calibration_data.rs b/examples/src/bin/i2c_bmp180_calibration_data.rs index 582a9454ae8..161a30a692e 100644 --- a/examples/src/bin/i2c_bmp180_calibration_data.rs +++ b/examples/src/bin/i2c_bmp180_calibration_data.rs @@ -23,7 +23,9 @@ fn main() -> ! { // Create a new peripheral object with the described wiring and standard // I2C clock speed: - let mut i2c = I2c::new(peripherals.I2C0, io.pins.gpio4, io.pins.gpio5, 100.kHz()); + let mut i2c = I2c::new(peripherals.I2C0, 100.kHz()) + .with_sda(io.pins.gpio4) + .with_scl(io.pins.gpio5); loop { let mut data = [0u8; 22]; diff --git a/examples/src/bin/i2c_display.rs b/examples/src/bin/i2c_display.rs index f31a1a84c7e..60848f36669 100644 --- a/examples/src/bin/i2c_display.rs +++ b/examples/src/bin/i2c_display.rs @@ -34,7 +34,9 @@ fn main() -> ! { // Create a new peripheral object with the described wiring // and standard I2C clock speed - let i2c = I2c::new(peripherals.I2C0, io.pins.gpio4, io.pins.gpio5, 100.kHz()); + let i2c = I2c::new(peripherals.I2C0, 100.kHz()) + .with_sda(io.pins.gpio4) + .with_scl(io.pins.gpio5); // Initialize display let interface = I2CDisplayInterface::new(i2c); diff --git a/examples/src/bin/lcd_cam_ov2640.rs b/examples/src/bin/lcd_cam_ov2640.rs index cdc6d64c1b2..6220401f3e1 100644 --- a/examples/src/bin/lcd_cam_ov2640.rs +++ b/examples/src/bin/lcd_cam_ov2640.rs @@ -80,7 +80,9 @@ fn main() -> ! { delay.delay_millis(500u32); - let i2c = I2c::new(peripherals.I2C0, cam_siod, cam_sioc, 100u32.kHz()); + let i2c = I2c::new(peripherals.I2C0, 100u32.kHz()) + .with_sda(cam_siod) + .with_scl(cam_sioc); let mut sccb = Sccb::new(i2c); diff --git a/hil-test/tests/i2c.rs b/hil-test/tests/i2c.rs index b0d37c72470..c8949c0886c 100644 --- a/hil-test/tests/i2c.rs +++ b/hil-test/tests/i2c.rs @@ -40,7 +40,9 @@ mod tests { // Create a new peripheral object with the described wiring and standard // I2C clock speed: - let i2c = I2c::new(peripherals.I2C0, sda, scl, 100.kHz()); + let i2c = I2c::new(peripherals.I2C0, 100.kHz()) + .with_sda(sda) + .with_scl(scl); Context { i2c } } From 2cb0f6bb9d282bd3279de00a2ab68337fe0decca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 4 Nov 2024 15:30:56 +0100 Subject: [PATCH 06/18] apply_config --- esp-hal/CHANGELOG.md | 3 + esp-hal/MIGRATING-0.21.md | 27 ++++- esp-hal/src/i2c/mod.rs | 103 +++++++++++++----- examples/src/bin/embassy_i2c.rs | 2 +- .../embassy_i2c_bmp180_calibration_data.rs | 2 +- .../src/bin/i2c_bmp180_calibration_data.rs | 2 +- examples/src/bin/i2c_display.rs | 2 +- examples/src/bin/lcd_cam_ov2640.rs | 2 +- hil-test/tests/i2c.rs | 9 +- 9 files changed, 111 insertions(+), 41 deletions(-) diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index d112b9a9f65..df03068dfb2 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -34,6 +34,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - GPIO ETM tasks and events now accept `InputSignal` and `OutputSignal` (#2427) - `spi::master::Config` and `{Spi, SpiDma, SpiDmaBus}::apply_config` (#2448) - `embassy_embedded_hal::SetConfig` is now implemented for `{Spi, SpiDma, SpiDmaBus}` (#2448) +- `I2c::{apply_config(), with_sda(), with_scl()}` (#2437) +- `I2c` now implements `embassy_embedded_hal::SetConfig` (#2437) ### Changed @@ -90,6 +92,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The `GpioEtmEventRising`, `GpioEtmEventFalling`, `GpioEtmEventAny` types have been replaced with `Event` (#2427) - The `TaskSet`, `TaskClear`, `TaskToggle` types have been replaced with `Task` (#2427) - `{Spi, SpiDma, SpiDmaBus}` configuration methods (#2448) +- `I2c::new()` no longer takes `frequency` and pins as parameters. (#2437) ## [0.21.1] diff --git a/esp-hal/MIGRATING-0.21.md b/esp-hal/MIGRATING-0.21.md index d490ce16778..b2df2743f02 100644 --- a/esp-hal/MIGRATING-0.21.md +++ b/esp-hal/MIGRATING-0.21.md @@ -275,4 +275,29 @@ refer to the `Config` struct as `uart::Config`. + ..Config::default() + }, +) -``` \ No newline at end of file +``` + +### I2C drivers can now be configured using `i2c::Config` + +- The old methods to change configuration have been removed. +- The `new` and `new_typed` constructor no longer takes `frequency` and pins. +- The default configuration is now: + - bus frequency: 100 kHz + - timeout: `None` +- There are new constructors (`new_with_config`, `new_typed_with_config`) and a new `apply_config` method to apply custom configuration. +- Pins can now be configured using `with_sda` and `with_scl` + +```diff +-use esp_hal::i2c::I2c; ++use esp_hal::i2c::{Config, I2c}; +-I2c::new(I2C0, sda, scl, 100.kHz()); ++I2c::new_with_config( ++ I2C0, ++ Config { ++ frequency: 400.kHz(), ++ ..Config::default() ++ }, ++) ++.with_sda(sda) ++.with_scl(scl); +``` diff --git a/esp-hal/src/i2c/mod.rs b/esp-hal/src/i2c/mod.rs index eb21133699e..9904d6081da 100644 --- a/esp-hal/src/i2c/mod.rs +++ b/esp-hal/src/i2c/mod.rs @@ -37,10 +37,10 @@ //! //! // Create a new peripheral object with the described wiring //! // and standard I2C clock speed. -//! let mut i2c = I2c::new( -//! peripherals.I2C0, -//! 100.kHz(), -//! ) +//! let mut i2c = I2c::new_with_config(peripherals.I2C0, Config { +//! frequency: 100.kHz(), +//! timeout: None, +//! }) //! .with_sda(io.pins.gpio1) //! .with_scl(io.pins.gpio2); //! @@ -55,8 +55,9 @@ mod support; mod version; -use core::marker::PhantomData; +use core::{convert::Infallible, marker::PhantomData}; +use embassy_embedded_hal::SetConfig; use embassy_sync::waitqueue::AtomicWaker; use fugit::HertzU32; use version::{I2C_CHUNK_SIZE, I2C_LL_INTR_MASK}; @@ -205,12 +206,44 @@ impl From for u32 { } } +/// I2C driver configuration +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Config { + /// The I2C clock frequency. + pub frequency: HertzU32, + + /// The I2C timeout. + // TODO: explain this function better - what's the unit, what happens on + // timeout, and just what exactly is a timeout in this context? + pub timeout: Option, +} + +impl Default for Config { + fn default() -> Self { + use fugit::RateExtU32; + Config { + frequency: 100.kHz(), + timeout: None, + } + } +} + /// I2C driver pub struct I2c<'d, DM: Mode, T = AnyI2c> { i2c: PeripheralRef<'d, T>, phantom: PhantomData, - frequency: HertzU32, - timeout: Option, + config: Config, +} + +impl SetConfig for I2c<'_, DM, T> { + type Config = Config; + type ConfigError = Infallible; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.apply_config(config); + Ok(()) + } } impl<'d, T, DM: Mode> I2c<'d, DM, T> @@ -228,16 +261,13 @@ where PeripheralClockControl::reset(self.i2c.peripheral()); PeripheralClockControl::enable(self.i2c.peripheral()); - self.driver().setup(self.frequency, self.timeout); + self.driver().setup(&self.config); } - /// Set the I2C timeout. - // TODO: explain this function better - what's the unit, what happens on - // timeout, and just what exactly is a timeout in this context? - pub fn with_timeout(mut self, timeout: Option) -> Self { - self.timeout = timeout; - self.driver().setup(self.frequency, self.timeout); - self + /// Applies a new configuration. + pub fn apply_config(&mut self, config: &Config) { + self.config = *config; + self.driver().setup(&self.config); } fn transaction_impl<'a>( @@ -335,11 +365,20 @@ where } impl<'d> I2c<'d, Blocking> { - /// Create a new I2C instance + /// Creates a new I2C instance. + /// + /// This will enable the peripheral but the peripheral won't get + /// automatically disabled when this gets dropped. + pub fn new(i2c: impl Peripheral

+ 'd) -> Self { + Self::new_with_config(i2c.map_into(), Config::default()) + } + + /// Creates a new I2C instance with a given configuration. + /// /// This will enable the peripheral but the peripheral won't get /// automatically disabled when this gets dropped. - pub fn new(i2c: impl Peripheral

+ 'd, frequency: HertzU32) -> Self { - Self::new_typed(i2c.map_into(), frequency) + pub fn new_with_config(i2c: impl Peripheral

+ 'd, config: Config) -> Self { + Self::new_typed_with_config(i2c.map_into(), config) } } @@ -347,23 +386,31 @@ impl<'d, T> I2c<'d, Blocking, T> where T: Instance, { - /// Create a new I2C instance + /// Creates a new I2C instance with a given configuration. + /// + /// This will enable the peripheral but the peripheral won't get + /// automatically disabled when this gets dropped. + pub fn new_typed(i2c: impl Peripheral

+ 'd) -> Self { + Self::new_typed_with_config(i2c, Config::default()) + } + + /// Creates a new I2C instance with a given configuration. + /// /// This will enable the peripheral but the peripheral won't get /// automatically disabled when this gets dropped. - pub fn new_typed(i2c: impl Peripheral

+ 'd, frequency: HertzU32) -> Self { + pub fn new_typed_with_config(i2c: impl Peripheral

+ 'd, config: Config) -> Self { crate::into_ref!(i2c); let i2c = I2c { i2c, phantom: PhantomData, - frequency, - timeout: None, + config, }; PeripheralClockControl::reset(i2c.i2c.peripheral()); PeripheralClockControl::enable(i2c.i2c.peripheral()); - i2c.driver().setup(frequency, None); + i2c.driver().setup(&i2c.config); i2c } @@ -376,8 +423,7 @@ where I2c { i2c: self.i2c, phantom: PhantomData, - frequency: self.frequency, - timeout: self.timeout, + config: self.config, } } @@ -531,8 +577,7 @@ where I2c { i2c: self.i2c, phantom: PhantomData, - frequency: self.frequency, - timeout: self.timeout, + config: self.config, } } @@ -866,7 +911,7 @@ struct Driver<'a> { impl Driver<'_> { /// Configures the I2C peripheral with the specified frequency, clocks, and /// optional timeout. - fn setup(&self, frequency: HertzU32, timeout: Option) { + fn setup(&self, config: &Config) { self.info.register_block().ctr().write(|w| { // Set I2C controller to master mode w.ms_mode().set_bit(); @@ -887,7 +932,7 @@ impl Driver<'_> { self.set_filter(Some(7), Some(7)); // Configure frequency - self.set_frequency(frequency, timeout); + self.set_frequency(config.frequency, config.timeout); self.update_config(); diff --git a/examples/src/bin/embassy_i2c.rs b/examples/src/bin/embassy_i2c.rs index 8b6fcb343c1..f0fe41b8673 100644 --- a/examples/src/bin/embassy_i2c.rs +++ b/examples/src/bin/embassy_i2c.rs @@ -31,7 +31,7 @@ async fn main(_spawner: Spawner) { let io = Io::new(peripherals.GPIO, peripherals.IO_MUX); - let i2c0 = I2c::new(peripherals.I2C0, 400.kHz()) + let i2c0 = I2c::new(peripherals.I2C0) .with_sda(io.pins.gpio4) .with_scl(io.pins.gpio5) .into_async(); diff --git a/examples/src/bin/embassy_i2c_bmp180_calibration_data.rs b/examples/src/bin/embassy_i2c_bmp180_calibration_data.rs index 7ba6bc9179d..fbc5de7a950 100644 --- a/examples/src/bin/embassy_i2c_bmp180_calibration_data.rs +++ b/examples/src/bin/embassy_i2c_bmp180_calibration_data.rs @@ -31,7 +31,7 @@ async fn main(_spawner: Spawner) { let io = Io::new(peripherals.GPIO, peripherals.IO_MUX); - let mut i2c = I2c::new(peripherals.I2C0, 400.kHz()) + let mut i2c = I2c::new(peripherals.I2C0) .with_sda(io.pins.gpio4) .with_scl(io.pins.gpio5) .into_async(); diff --git a/examples/src/bin/i2c_bmp180_calibration_data.rs b/examples/src/bin/i2c_bmp180_calibration_data.rs index 161a30a692e..86f4ea7b94d 100644 --- a/examples/src/bin/i2c_bmp180_calibration_data.rs +++ b/examples/src/bin/i2c_bmp180_calibration_data.rs @@ -23,7 +23,7 @@ fn main() -> ! { // Create a new peripheral object with the described wiring and standard // I2C clock speed: - let mut i2c = I2c::new(peripherals.I2C0, 100.kHz()) + let mut i2c = I2c::new(peripherals.I2C0) .with_sda(io.pins.gpio4) .with_scl(io.pins.gpio5); diff --git a/examples/src/bin/i2c_display.rs b/examples/src/bin/i2c_display.rs index 60848f36669..72821a4808d 100644 --- a/examples/src/bin/i2c_display.rs +++ b/examples/src/bin/i2c_display.rs @@ -34,7 +34,7 @@ fn main() -> ! { // Create a new peripheral object with the described wiring // and standard I2C clock speed - let i2c = I2c::new(peripherals.I2C0, 100.kHz()) + let i2c = I2c::new(peripherals.I2C0) .with_sda(io.pins.gpio4) .with_scl(io.pins.gpio5); diff --git a/examples/src/bin/lcd_cam_ov2640.rs b/examples/src/bin/lcd_cam_ov2640.rs index 6220401f3e1..f3dd74cc518 100644 --- a/examples/src/bin/lcd_cam_ov2640.rs +++ b/examples/src/bin/lcd_cam_ov2640.rs @@ -80,7 +80,7 @@ fn main() -> ! { delay.delay_millis(500u32); - let i2c = I2c::new(peripherals.I2C0, 100u32.kHz()) + let i2c = I2c::new(peripherals.I2C0) .with_sda(cam_siod) .with_scl(cam_sioc); diff --git a/hil-test/tests/i2c.rs b/hil-test/tests/i2c.rs index c8949c0886c..9cf9991764d 100644 --- a/hil-test/tests/i2c.rs +++ b/hil-test/tests/i2c.rs @@ -8,7 +8,6 @@ use esp_hal::{ gpio::Io, i2c::{I2c, Operation}, - prelude::*, Async, Blocking, }; @@ -40,11 +39,9 @@ mod tests { // Create a new peripheral object with the described wiring and standard // I2C clock speed: - let i2c = I2c::new(peripherals.I2C0, 100.kHz()) - .with_sda(sda) - .with_scl(scl); - - Context { i2c } + Context { + i2c: I2c::new(peripherals.I2C0).with_sda(sda).with_scl(scl), + } } #[test] From a67cb4de585ffa0571f2d49aece735d98c3faaad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 4 Nov 2024 16:50:00 +0100 Subject: [PATCH 07/18] Redefine timeout --- esp-hal/CHANGELOG.md | 1 + esp-hal/MIGRATING-0.21.md | 2 +- esp-hal/src/i2c/mod.rs | 32 +++++++++++++++++++------------- esp-hal/src/i2c/version/v1.rs | 8 ++++---- esp-hal/src/i2c/version/v2.rs | 6 +----- esp-hal/src/i2c/version/v3.rs | 18 +++++++----------- 6 files changed, 33 insertions(+), 34 deletions(-) diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index df03068dfb2..a28bf39b455 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -56,6 +56,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - UART configuration types have been moved to `esp_hal::uart` (#2449) - `spi::master::Spi::new()` no longer takes `frequency` and `mode` as a parameter. (#2448) - Peripheral interconnections via GPIO pins now use the GPIO matrix. (#2419) +- `I2c` SCL timeout is now defined in bus clock cycles. (#2437) ### Fixed diff --git a/esp-hal/MIGRATING-0.21.md b/esp-hal/MIGRATING-0.21.md index b2df2743f02..4d92542b6a8 100644 --- a/esp-hal/MIGRATING-0.21.md +++ b/esp-hal/MIGRATING-0.21.md @@ -283,7 +283,7 @@ refer to the `Config` struct as `uart::Config`. - The `new` and `new_typed` constructor no longer takes `frequency` and pins. - The default configuration is now: - bus frequency: 100 kHz - - timeout: `None` + - timeout: about 10 bus clock cycles - There are new constructors (`new_with_config`, `new_typed_with_config`) and a new `apply_config` method to apply custom configuration. - Pins can now be configured using `with_sda` and `with_scl` diff --git a/esp-hal/src/i2c/mod.rs b/esp-hal/src/i2c/mod.rs index 9904d6081da..66d6896e886 100644 --- a/esp-hal/src/i2c/mod.rs +++ b/esp-hal/src/i2c/mod.rs @@ -213,9 +213,20 @@ pub struct Config { /// The I2C clock frequency. pub frequency: HertzU32, - /// The I2C timeout. - // TODO: explain this function better - what's the unit, what happens on - // timeout, and just what exactly is a timeout in this context? + /// I2C SCL timeout period. + /// + /// When the level of SCL remains unchanged for more than `timeout` bus + /// clock cycles, the bus goes to idle state. + /// + /// The default value is about 10 bus clock cycles. + #[doc = ""] + #[cfg_attr( + not(esp32), + doc = "Note that the effective timeout may be longer than the value configured here." + )] + #[cfg_attr(not(esp32), doc = "Configuring `None` disables timeout control.")] + #[cfg_attr(esp32, doc = "Configuring `None` equals to the maximum timeout value.")] + // TODO: when supporting interrupts, document that SCL = high also triggers an interrupt. pub timeout: Option, } @@ -224,7 +235,7 @@ impl Default for Config { use fugit::RateExtU32; Config { frequency: 100.kHz(), - timeout: None, + timeout: Some(10), } } } @@ -799,8 +810,7 @@ fn configure_clock( scl_stop_setup_time: u32, scl_start_hold_time: u32, scl_stop_hold_time: u32, - time_out_value: u32, - time_out_en: bool, + timeout: Option, ) { unsafe { // divider @@ -850,19 +860,15 @@ fn configure_clock( // timeout mechanism cfg_if::cfg_if! { if #[cfg(esp32)] { - // timeout register_block .to() - .write(|w| w.time_out().bits(time_out_value)); + .write(|w| w.time_out().bits(unwrap!(timeout))); } else { - // timeout - // FIXME: Enable timout for other chips! - #[allow(clippy::useless_conversion)] register_block .to() - .write(|w| w.time_out_en().bit(time_out_en) + .write(|w| w.time_out_en().bit(timeout.is_some()) .time_out_value() - .bits(time_out_value.try_into().unwrap()) + .bits(timeout.unwrap_or(1) as _) ); } } diff --git a/esp-hal/src/i2c/version/v1.rs b/esp-hal/src/i2c/version/v1.rs index 1dd0b32622c..1f83af03f0d 100644 --- a/esp-hal/src/i2c/version/v1.rs +++ b/esp-hal/src/i2c/version/v1.rs @@ -131,8 +131,9 @@ impl Driver<'_> { let sda_sample = scl_high / 2; let setup = half_cycle; let hold = half_cycle; - // default we set the timeout value to 10 bus cycles - let time_out_value = timeout.unwrap_or(half_cycle * 20); + let timeout = timeout.map_or(Some(0xF_FFFF), |to_bus| { + Some((to_bus * 2 * half_cycle).min(0xF_FFFF)) + }); // SCL period. According to the TRM, we should always subtract 1 to SCL low // period @@ -185,8 +186,7 @@ impl Driver<'_> { scl_stop_setup_time, scl_start_hold_time, scl_stop_hold_time, - time_out_value, - true, + timeout, ); } diff --git a/esp-hal/src/i2c/version/v2.rs b/esp-hal/src/i2c/version/v2.rs index afb51e40509..87156741a78 100644 --- a/esp-hal/src/i2c/version/v2.rs +++ b/esp-hal/src/i2c/version/v2.rs @@ -40,8 +40,6 @@ impl Driver<'_> { let sda_sample = half_cycle / 2 - 1; let setup = half_cycle; let hold = half_cycle; - // default we set the timeout value to 10 bus cycles - let time_out_value = timeout.unwrap_or(half_cycle * 20); // scl period let scl_low_period = scl_low - 1; @@ -56,7 +54,6 @@ impl Driver<'_> { // hold let scl_start_hold_time = hold - 1; let scl_stop_hold_time = hold; - let time_out_en = true; configure_clock( self.info.register_block(), @@ -70,8 +67,7 @@ impl Driver<'_> { scl_stop_setup_time, scl_start_hold_time, scl_stop_hold_time, - time_out_value, - time_out_en, + timeout.map(|to_bus| (to_bus * 2 * half_cycle).min(0xFF_FFFF)), ); } diff --git a/esp-hal/src/i2c/version/v3.rs b/esp-hal/src/i2c/version/v3.rs index 79bdc3902a3..086afcc3247 100644 --- a/esp-hal/src/i2c/version/v3.rs +++ b/esp-hal/src/i2c/version/v3.rs @@ -163,14 +163,6 @@ impl Driver<'_> { let setup = half_cycle; let hold = half_cycle; - let time_out_value = if let Some(timeout) = timeout { - timeout - } else { - // default we set the timeout value to about 10 bus cycles - // log(20*half_cycle)/log(2) = log(half_cycle)/log(2) + log(20)/log(2) - (4 * 8 - (5 * half_cycle).leading_zeros()) + 2 - }; - // According to the Technical Reference Manual, the following timings must be // subtracted by 1. However, according to the practical measurement and // some hardware behaviour, if wait_high_period and scl_high minus one. @@ -190,7 +182,6 @@ impl Driver<'_> { // hold let scl_start_hold_time = hold - 1; let scl_stop_hold_time = hold - 1; - let time_out_en = true; configure_clock( self.info.register_block(), @@ -204,8 +195,13 @@ impl Driver<'_> { scl_stop_setup_time, scl_start_hold_time, scl_stop_hold_time, - time_out_value, - time_out_en, + timeout.map(|to_bus| { + let to_peri = (to_bus * 2 * half_cycle).max(1); + let log2 = to_peri.ilog2(); + // Round up so that we don't shorten timeouts. + let raw = if to_peri != 1 << log2 { log2 + 1 } else { log2 }; + raw.min(0x1F) + }), ); } From cae0bedafbb640d00df85724b2d46e771ec48e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 4 Nov 2024 17:11:28 +0100 Subject: [PATCH 08/18] Explain change in MG --- esp-hal/MIGRATING-0.21.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/esp-hal/MIGRATING-0.21.md b/esp-hal/MIGRATING-0.21.md index 4d92542b6a8..10b7f008f7f 100644 --- a/esp-hal/MIGRATING-0.21.md +++ b/esp-hal/MIGRATING-0.21.md @@ -301,3 +301,13 @@ refer to the `Config` struct as `uart::Config`. +.with_sda(sda) +.with_scl(scl); ``` + +### The calculation of I2C timeout has changed + +Previously, I2C timeouts were counted in increments of I2C peripheral clock cycles. This meant that +the configure value meant different lengths of time depending on the device. With this update, the +I2C configuration now expects the timeout value in number of bus clock cycles, which is consistent +between devices. + +ESP32 and ESP32-S2 use an exact number of clock cycles for its timeout. Other MCUs, however, use +the `2^timeout` value internally, and the HAL rounds up the timeout to the next appropriate value. From e61af4c3753a9834c2dee5350b2e43223c2b8d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 4 Nov 2024 17:11:56 +0100 Subject: [PATCH 09/18] Fix doc example --- esp-hal/src/i2c/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esp-hal/src/i2c/mod.rs b/esp-hal/src/i2c/mod.rs index 66d6896e886..5d9f1571fb3 100644 --- a/esp-hal/src/i2c/mod.rs +++ b/esp-hal/src/i2c/mod.rs @@ -31,7 +31,7 @@ //! //! ```rust, no_run #![doc = crate::before_snippet!()] -//! # use esp_hal::i2c::I2c; +//! # use esp_hal::i2c::{Config, I2c}; //! # use esp_hal::gpio::Io; //! let io = Io::new(peripherals.GPIO, peripherals.IO_MUX); //! From f03c68fb88898a33ce7cc078d4b5845626041bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 4 Nov 2024 17:35:39 +0100 Subject: [PATCH 10/18] Move driver to i2c::master --- esp-hal/CHANGELOG.md | 1 + esp-hal/MIGRATING-0.21.md | 242 +-- esp-hal/src/i2c/{ => master}/lp_i2c.rs | 0 esp-hal/src/i2c/master/mod.rs | 1439 +++++++++++++++++ esp-hal/src/i2c/{ => master}/support/eh.rs | 2 +- esp-hal/src/i2c/{ => master}/support/eh02.rs | 2 +- esp-hal/src/i2c/{ => master}/support/eh_a.rs | 2 +- esp-hal/src/i2c/{ => master}/support/mod.rs | 0 esp-hal/src/i2c/{ => master}/version/mod.rs | 0 esp-hal/src/i2c/{ => master}/version/v1.rs | 2 +- .../i2c/{ => master}/version/v1_v2_common.rs | 2 +- esp-hal/src/i2c/{ => master}/version/v2.rs | 2 +- .../i2c/{ => master}/version/v2_v3_common.rs | 2 +- esp-hal/src/i2c/{ => master}/version/v3.rs | 2 +- esp-hal/src/i2c/mod.rs | 1432 +--------------- esp-hal/src/prelude.rs | 2 +- examples/src/bin/embassy_i2c.rs | 2 +- .../embassy_i2c_bmp180_calibration_data.rs | 2 +- .../src/bin/i2c_bmp180_calibration_data.rs | 2 +- examples/src/bin/i2c_display.rs | 2 +- examples/src/bin/lcd_cam_ov2640.rs | 11 +- hil-test/tests/i2c.rs | 2 +- 22 files changed, 1583 insertions(+), 1570 deletions(-) rename esp-hal/src/i2c/{ => master}/lp_i2c.rs (100%) create mode 100644 esp-hal/src/i2c/master/mod.rs rename esp-hal/src/i2c/{ => master}/support/eh.rs (96%) rename esp-hal/src/i2c/{ => master}/support/eh02.rs (95%) rename esp-hal/src/i2c/{ => master}/support/eh_a.rs (92%) rename esp-hal/src/i2c/{ => master}/support/mod.rs (100%) rename esp-hal/src/i2c/{ => master}/version/mod.rs (100%) rename esp-hal/src/i2c/{ => master}/version/v1.rs (99%) rename esp-hal/src/i2c/{ => master}/version/v1_v2_common.rs (99%) rename esp-hal/src/i2c/{ => master}/version/v2.rs (98%) rename esp-hal/src/i2c/{ => master}/version/v2_v3_common.rs (99%) rename esp-hal/src/i2c/{ => master}/version/v3.rs (99%) diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index a28bf39b455..b1088d065ca 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -56,6 +56,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - UART configuration types have been moved to `esp_hal::uart` (#2449) - `spi::master::Spi::new()` no longer takes `frequency` and `mode` as a parameter. (#2448) - Peripheral interconnections via GPIO pins now use the GPIO matrix. (#2419) +- The `I2c` master driver has been moved to `esp_hal::i2c::master`. (#2437) - `I2c` SCL timeout is now defined in bus clock cycles. (#2437) ### Fixed diff --git a/esp-hal/MIGRATING-0.21.md b/esp-hal/MIGRATING-0.21.md index 10b7f008f7f..fc335815632 100644 --- a/esp-hal/MIGRATING-0.21.md +++ b/esp-hal/MIGRATING-0.21.md @@ -88,7 +88,9 @@ the peripheral instance has been moved to the last generic parameter position. let spi: Spi<'static, FullDuplexMode, SPI2> = Spi::new_typed(peripherals.SPI2, 1.MHz(), SpiMode::Mode0); ``` -## I2C constructor changes +## I2C changes + +The I2C master driver and related types have been moved to `esp_hal::i2c::master`. The `with_timeout` constructors have been removed in favour of `set_timeout` or `with_timeout`. @@ -97,12 +99,129 @@ The `with_timeout` constructors have been removed in favour of `set_timeout` or +let i2c = I2c::new(peripherals.I2C0, io.pins.gpio4, io.pins.gpio5, 100.kHz()).with_timeout(timeout); ``` -## Changes to half-duplex SPI +### I2C drivers can now be configured using `i2c::master::Config` + +- The old methods to change configuration have been removed. +- The `new` and `new_typed` constructor no longer takes `frequency` and pins. +- The default configuration is now: + - bus frequency: 100 kHz + - timeout: about 10 bus clock cycles +- There are new constructors (`new_with_config`, `new_typed_with_config`) and a new `apply_config` method to apply custom configuration. +- Pins can now be configured using `with_sda` and `with_scl` + +```diff +-use esp_hal::i2c::I2c; ++use esp_hal::i2c::master::{Config, I2c}; +-I2c::new(I2C0, sda, scl, 100.kHz()); ++I2c::new_with_config( ++ I2C0, ++ Config { ++ frequency: 400.kHz(), ++ ..Config::default() ++ }, ++) ++.with_sda(sda) ++.with_scl(scl); +``` + +### The calculation of I2C timeout has changed + +Previously, I2C timeouts were counted in increments of I2C peripheral clock cycles. This meant that +the configure value meant different lengths of time depending on the device. With this update, the +I2C configuration now expects the timeout value in number of bus clock cycles, which is consistent +between devices. + +ESP32 and ESP32-S2 use an exact number of clock cycles for its timeout. Other MCUs, however, use +the `2^timeout` value internally, and the HAL rounds up the timeout to the next appropriate value. + +## UART changes + +### The `uart::config` module has been removed + +The module's contents have been moved into `uart`. + +```diff +-use esp_hal::uart::config::Config; ++use esp_hal::uart::Config; +``` + +If you work with multiple configurable peripherals, you may want to import the `uart` module and +refer to the `Config` struct as `uart::Config`. + +### UART event listening + +The following functions have been removed: + +- `listen_at_cmd` +- `unlisten_at_cmd` +- `listen_tx_done` +- `unlisten_tx_done` +- `listen_rx_fifo_full` +- `unlisten_rx_fifo_full` +- `at_cmd_interrupt_set` +- `tx_done_interrupt_set` +- `rx_fifo_full_interrupt_set` +- `reset_at_cmd_interrupt` +- `reset_tx_done_interrupt` +- `reset_rx_fifo_full_interrupt` + +You can now use the `UartInterrupt` enum and the corresponding `listen`, `unlisten`, `interrupts` and `clear_interrupts` functions. + +Use `interrupts` in place of `_interrupt_set` and `clear_interrupts` in place of the old `reset_` functions. + +`UartInterrupt`: +- `AtCmd` +- `TxDone` +- `RxFifoFull` + +Checking interrupt bits is now done using APIs provided by [enumset](https://docs.rs/enumset/1.1.5/enumset/index.html). For example, to see if +a particular interrupt bit is set, use `contains`: + +```diff +-serial.at_cmd_interrupt_set() ++serial.interupts().contains(UartInterrupt::AtCmd) +``` + +You can now listen/unlisten multiple interrupt bits at once: + +```diff +-uart0.listen_at_cmd(); +-uart0.listen_rx_fifo_full(); ++uart0.listen(UartInterrupt::AtCmd | UartConterrupt::RxFifoFull); +``` + +## SPI changes + +### SPI drivers can now be configured using `spi::master::Config` + +- The old methods to change configuration have been removed. +- The `new` and `new_typed` constructor no longer takes `frequency` and `mode`. +- The default configuration is now: + - bus frequency: 1 MHz + - bit order: MSB first + - mode: SPI mode 0 +- There are new constructors (`new_with_config`, `new_typed_with_config`) and a new `apply_config` method to apply custom configuration. + +```diff +-use esp_hal::spi::{master::Spi, SpiMode}; ++use esp_hal::spi::{master::{Config, Spi}, SpiMode}; +-Spi::new(SPI2, 100.kHz(), SpiMode::Mode1); ++Spi::new_with_config( ++ SPI2, ++ Config { ++ frequency: 100.kHz(), ++ mode: SpiMode::Mode0, ++ ..Config::default() ++ }, ++) +``` + +### Half-duplex SPI type signature The `HalfDuplexMode` and `FullDuplexMode` type parameters have been removed from SPI master and slave drivers. It is now possible to execute half-duplex and full-duplex operations on the same SPI bus. -### Driver construction +### Half-duplex SPI driver construction - The `Spi::new_half_duplex` constructor has been removed. Use `new` (or `new_typed`) instead. - The `with_pins` methods have been removed. Use the individual `with_*` functions instead. @@ -121,7 +240,7 @@ drivers. It is now possible to execute half-duplex and full-duplex operations on + .with_sio3(sio3); ``` -### Transfer functions +### Half-duplex SPI transfer functions The `Spi<'_, SPI, HalfDuplexMode>::read` and `Spi<'_, SPI, HalfDuplexMode>::write` functions have been replaced by `half_duplex_read` and `half_duplex_write`. @@ -154,48 +273,6 @@ The `Spi<'_, SPI, HalfDuplexMode>::read` and `Spi<'_, SPI, HalfDuplexMode>::writ .unwrap(); ``` -## UART event listening - -The following functions have been removed: - -- `listen_at_cmd` -- `unlisten_at_cmd` -- `listen_tx_done` -- `unlisten_tx_done` -- `listen_rx_fifo_full` -- `unlisten_rx_fifo_full` -- `at_cmd_interrupt_set` -- `tx_done_interrupt_set` -- `rx_fifo_full_interrupt_set` -- `reset_at_cmd_interrupt` -- `reset_tx_done_interrupt` -- `reset_rx_fifo_full_interrupt` - -You can now use the `UartInterrupt` enum and the corresponding `listen`, `unlisten`, `interrupts` and `clear_interrupts` functions. - -Use `interrupts` in place of `_interrupt_set` and `clear_interrupts` in place of the old `reset_` functions. - -`UartInterrupt`: -- `AtCmd` -- `TxDone` -- `RxFifoFull` - -Checking interrupt bits is now done using APIs provided by [enumset](https://docs.rs/enumset/1.1.5/enumset/index.html). For example, to see if -a particular interrupt bit is set, use `contains`: - -```diff --serial.at_cmd_interrupt_set() -+serial.interupts().contains(UartInterrupt::AtCmd) -``` - -You can now listen/unlisten multiple interrupt bits at once: - -```diff --uart0.listen_at_cmd(); --uart0.listen_rx_fifo_full(); -+uart0.listen(UartInterrupt::AtCmd | UartConterrupt::RxFifoFull); -``` - ## Circular DMA transfer's `available` returns `Result` now In case of any error you should drop the transfer and restart it. @@ -238,76 +315,3 @@ the GPIO drivers (`Input`, `Output`, `OutputOpenDrain` and `Flex`) instead. (e.g. `use esp_hal::gpio::etm::Event as GpioEtmEvent`). - The old task and event types have been replaced by `Task` and `Event`. - GPIO tasks and events are no longer generic. - -## Changes to peripheral configuration - -### The `uart::config` module has been removed - -The module's contents have been moved into `uart`. - -```diff --use esp_hal::uart::config::Config; -+use esp_hal::uart::Config; -``` - -If you work with multiple configurable peripherals, you may want to import the `uart` module and -refer to the `Config` struct as `uart::Config`. - -### SPI drivers can now be configured using `spi::master::Config` - -- The old methods to change configuration have been removed. -- The `new` and `new_typed` constructor no longer takes `frequency` and `mode`. -- The default configuration is now: - - bus frequency: 1 MHz - - bit order: MSB first - - mode: SPI mode 0 -- There are new constructors (`new_with_config`, `new_typed_with_config`) and a new `apply_config` method to apply custom configuration. - -```diff --use esp_hal::spi::{master::Spi, SpiMode}; -+use esp_hal::spi::{master::{Config, Spi}, SpiMode}; --Spi::new(SPI2, 100.kHz(), SpiMode::Mode1); -+Spi::new_with_config( -+ SPI2, -+ Config { -+ frequency: 100.kHz(), -+ mode: SpiMode::Mode0, -+ ..Config::default() -+ }, -+) -``` - -### I2C drivers can now be configured using `i2c::Config` - -- The old methods to change configuration have been removed. -- The `new` and `new_typed` constructor no longer takes `frequency` and pins. -- The default configuration is now: - - bus frequency: 100 kHz - - timeout: about 10 bus clock cycles -- There are new constructors (`new_with_config`, `new_typed_with_config`) and a new `apply_config` method to apply custom configuration. -- Pins can now be configured using `with_sda` and `with_scl` - -```diff --use esp_hal::i2c::I2c; -+use esp_hal::i2c::{Config, I2c}; --I2c::new(I2C0, sda, scl, 100.kHz()); -+I2c::new_with_config( -+ I2C0, -+ Config { -+ frequency: 400.kHz(), -+ ..Config::default() -+ }, -+) -+.with_sda(sda) -+.with_scl(scl); -``` - -### The calculation of I2C timeout has changed - -Previously, I2C timeouts were counted in increments of I2C peripheral clock cycles. This meant that -the configure value meant different lengths of time depending on the device. With this update, the -I2C configuration now expects the timeout value in number of bus clock cycles, which is consistent -between devices. - -ESP32 and ESP32-S2 use an exact number of clock cycles for its timeout. Other MCUs, however, use -the `2^timeout` value internally, and the HAL rounds up the timeout to the next appropriate value. diff --git a/esp-hal/src/i2c/lp_i2c.rs b/esp-hal/src/i2c/master/lp_i2c.rs similarity index 100% rename from esp-hal/src/i2c/lp_i2c.rs rename to esp-hal/src/i2c/master/lp_i2c.rs diff --git a/esp-hal/src/i2c/master/mod.rs b/esp-hal/src/i2c/master/mod.rs new file mode 100644 index 00000000000..d55a2327a03 --- /dev/null +++ b/esp-hal/src/i2c/master/mod.rs @@ -0,0 +1,1439 @@ +//! # Inter-Integrated Circuit (I2C) - Master Mode +//! +//! I2C is a serial, synchronous, multi-device, half-duplex communication +//! protocol that allows co-existence of multiple masters and slaves on the +//! same bus. I2C uses two bidirectional open-drain lines: serial data line +//! (SDA) and serial clock line (SCL), pulled up by resistors. +//! +//! Espressif devices sometimes have more than one I2C controller, responsible +//! for handling communication on the I2C bus. A single I2C controller can be +//! a master or a slave. +//! +//! Typically, an I2C slave device has a 7-bit address or 10-bit address. +//! Devices supports both I2C Standard-mode (Sm) and Fast-mode (Fm) which can +//! go up to 100KHz and 400KHz respectively. +//! +//! ## Configuration +//! +//! Each I2C controller is individually configurable, and the usual setting +//! such as frequency, timeout, and SDA/SCL pins can easily be configured. +//! +//! ## Usage +//! +//! The I2C driver implements a number of third-party traits, with the +//! intention of making the HAL inter-compatible with various device drivers +//! from the community. This includes the [`embedded-hal`] for both 0.2.x and +//! 1.0.x versions. +//! +//! ## Examples +//! +//! ### Read Data from a BMP180 Sensor +//! +//! ```rust, no_run +#![doc = crate::before_snippet!()] +//! # use esp_hal::i2c::master::{Config, I2c}; +//! # use esp_hal::gpio::Io; +//! let io = Io::new(peripherals.GPIO, peripherals.IO_MUX); +//! +//! // Create a new peripheral object with the described wiring +//! // and standard I2C clock speed. +//! let mut i2c = I2c::new_with_config(peripherals.I2C0, Config { +//! frequency: 100.kHz(), +//! timeout: None, +//! }) +//! .with_sda(io.pins.gpio1) +//! .with_scl(io.pins.gpio2); +//! +//! loop { +//! let mut data = [0u8; 22]; +//! i2c.write_read(0x77, &[0xaa], &mut data).ok(); +//! } +//! # } +//! ``` +//! [`embedded-hal`]: https://crates.io/crates/embedded-hal + +mod support; +mod version; + +use core::{convert::Infallible, marker::PhantomData}; + +use embassy_embedded_hal::SetConfig; +use embassy_sync::waitqueue::AtomicWaker; +use fugit::HertzU32; +use version::{I2C_CHUNK_SIZE, I2C_LL_INTR_MASK}; + +use crate::{ + clock::Clocks, + dma::PeripheralMarker, + gpio::{interconnect::PeripheralOutput, InputSignal, OutputSignal, Pull}, + interrupt::InterruptHandler, + peripheral::{Peripheral, PeripheralRef}, + peripherals::{ + i2c0::{RegisterBlock, COMD}, + Interrupt, + }, + private, + system::PeripheralClockControl, + Async, + Blocking, + Cpu, + InterruptConfigurable, + Mode, +}; + +// on ESP32 there is a chance to get trapped in `wait_for_completion` forever +const MAX_ITERATIONS: u32 = 1_000_000; + +/// I2C-specific transmission errors +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The transmission exceeded the FIFO size. + ExceedingFifo, + /// The acknowledgment check failed. + AckCheckFailed, + /// A timeout occurred during transmission. + TimeOut, + /// The arbitration for the bus was lost. + ArbitrationLost, + /// The execution of the I2C command was incomplete. + ExecIncomplete, + /// The number of commands issued exceeded the limit. + CommandNrExceeded, + /// Zero length read or write operation. + InvalidZeroLength, +} + +#[derive(PartialEq)] +// This enum is used to keep track of the last/next operation that was/will be +// performed in an embedded-hal(-async) I2c::transaction. It is used to +// determine whether a START condition should be issued at the start of the +// current operation and whether a read needs an ack or a nack for the final +// byte. +enum OpKind { + Write, + Read, +} + +/// I2C operation. +/// +/// Several operations can be combined as part of a transaction. +pub enum Operation<'a> { + /// Write data from the provided buffer. + Write(&'a [u8]), + + /// Read data into the provided buffer. + Read(&'a mut [u8]), +} + +impl<'a, 'b> From<&'a mut Operation<'b>> for Operation<'a> { + fn from(value: &'a mut Operation<'b>) -> Self { + match value { + Operation::Write(buffer) => Operation::Write(buffer), + Operation::Read(buffer) => Operation::Read(buffer), + } + } +} + +impl Operation<'_> { + fn is_write(&self) -> bool { + matches!(self, Operation::Write(_)) + } + + fn kind(&self) -> OpKind { + match self { + Operation::Write(_) => OpKind::Write, + Operation::Read(_) => OpKind::Read, + } + } + + fn is_empty(&self) -> bool { + match self { + Operation::Write(buffer) => buffer.is_empty(), + Operation::Read(buffer) => buffer.is_empty(), + } + } +} + +/// A generic I2C Command +#[cfg_attr(feature = "debug", derive(Debug))] +enum Command { + Start, + Stop, + End, + Write { + /// This bit is to set an expected ACK value for the transmitter. + ack_exp: Ack, + /// Enables checking the ACK value received against the ack_exp value. + ack_check_en: bool, + /// Length of data (in bytes) to be written. The maximum length is 255, + /// while the minimum is 1. + length: u8, + }, + Read { + /// Indicates whether the receiver will send an ACK after this byte has + /// been received. + ack_value: Ack, + /// Length of data (in bytes) to be read. The maximum length is 255, + /// while the minimum is 1. + length: u8, + }, +} + +enum OperationType { + Write = 0, + Read = 1, +} + +#[derive(Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "debug", derive(Debug))] +enum Ack { + Ack = 0, + Nack = 1, +} +impl From for Ack { + fn from(ack: u32) -> Self { + match ack { + 0 => Ack::Ack, + 1 => Ack::Nack, + _ => unreachable!(), + } + } +} +impl From for u32 { + fn from(ack: Ack) -> u32 { + ack as u32 + } +} + +/// I2C driver configuration +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Config { + /// The I2C clock frequency. + pub frequency: HertzU32, + + /// I2C SCL timeout period. + /// + /// When the level of SCL remains unchanged for more than `timeout` bus + /// clock cycles, the bus goes to idle state. + /// + /// The default value is about 10 bus clock cycles. + #[doc = ""] + #[cfg_attr( + not(esp32), + doc = "Note that the effective timeout may be longer than the value configured here." + )] + #[cfg_attr(not(esp32), doc = "Configuring `None` disables timeout control.")] + #[cfg_attr(esp32, doc = "Configuring `None` equals to the maximum timeout value.")] + // TODO: when supporting interrupts, document that SCL = high also triggers an interrupt. + pub timeout: Option, +} + +impl Default for Config { + fn default() -> Self { + use fugit::RateExtU32; + Config { + frequency: 100.kHz(), + timeout: Some(10), + } + } +} + +/// I2C driver +pub struct I2c<'d, DM: Mode, T = AnyI2c> { + i2c: PeripheralRef<'d, T>, + phantom: PhantomData, + config: Config, +} + +impl SetConfig for I2c<'_, DM, T> { + type Config = Config; + type ConfigError = Infallible; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.apply_config(config); + Ok(()) + } +} + +impl<'d, T, DM: Mode> I2c<'d, DM, T> +where + T: Instance, +{ + fn driver(&self) -> Driver<'_> { + Driver { + info: self.i2c.info(), + state: self.i2c.state(), + } + } + + fn internal_recover(&self) { + PeripheralClockControl::reset(self.i2c.peripheral()); + PeripheralClockControl::enable(self.i2c.peripheral()); + + self.driver().setup(&self.config); + } + + /// Applies a new configuration. + pub fn apply_config(&mut self, config: &Config) { + self.config = *config; + self.driver().setup(&self.config); + } + + fn transaction_impl<'a>( + &mut self, + address: u8, + operations: impl Iterator>, + ) -> Result<(), Error> { + let mut last_op: Option = None; + // filter out 0 length read operations + let mut op_iter = operations + .filter(|op| op.is_write() || !op.is_empty()) + .peekable(); + + while let Some(op) = op_iter.next() { + let next_op = op_iter.peek().map(|v| v.kind()); + let kind = op.kind(); + // Clear all I2C interrupts + self.driver().clear_all_interrupts(); + + let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); + match op { + Operation::Write(buffer) => { + // execute a write operation: + // - issue START/RSTART if op is different from previous + // - issue STOP if op is the last one + self.driver() + .write_operation_blocking( + address, + buffer, + !matches!(last_op, Some(OpKind::Write)), + next_op.is_none(), + cmd_iterator, + ) + .inspect_err(|_| self.internal_recover())?; + } + Operation::Read(buffer) => { + // execute a read operation: + // - issue START/RSTART if op is different from previous + // - issue STOP if op is the last one + // - will_continue is true if there is another read operation next + self.driver() + .read_operation_blocking( + address, + buffer, + !matches!(last_op, Some(OpKind::Read)), + next_op.is_none(), + matches!(next_op, Some(OpKind::Read)), + cmd_iterator, + ) + .inspect_err(|_| self.internal_recover())?; + } + } + + last_op = Some(kind); + } + + Ok(()) + } + + /// Connect a pin to the I2C SDA signal. + pub fn with_sda(self, sda: impl Peripheral

+ 'd) -> Self { + let info = self.driver().info; + let input = info.sda_input; + let output = info.sda_output; + self.with_pin(sda, input, output) + } + + /// Connect a pin to the I2C SCL signal. + pub fn with_scl(self, scl: impl Peripheral

+ 'd) -> Self { + let info = self.driver().info; + let input = info.scl_input; + let output = info.scl_output; + self.with_pin(scl, input, output) + } + + fn with_pin( + self, + pin: impl Peripheral

+ 'd, + input: InputSignal, + output: OutputSignal, + ) -> Self { + crate::into_mapped_ref!(pin); + // avoid the pin going low during configuration + pin.set_output_high(true, private::Internal); + + pin.set_to_open_drain_output(private::Internal); + pin.enable_input(true, private::Internal); + pin.pull_direction(Pull::Up, private::Internal); + + input.connect_to(&mut pin); + output.connect_to(&mut pin); + + self + } +} + +impl<'d> I2c<'d, Blocking> { + /// Creates a new I2C instance. + /// + /// This will enable the peripheral but the peripheral won't get + /// automatically disabled when this gets dropped. + pub fn new(i2c: impl Peripheral

+ 'd) -> Self { + Self::new_with_config(i2c.map_into(), Config::default()) + } + + /// Creates a new I2C instance with a given configuration. + /// + /// This will enable the peripheral but the peripheral won't get + /// automatically disabled when this gets dropped. + pub fn new_with_config(i2c: impl Peripheral

+ 'd, config: Config) -> Self { + Self::new_typed_with_config(i2c.map_into(), config) + } +} + +impl<'d, T> I2c<'d, Blocking, T> +where + T: Instance, +{ + /// Creates a new I2C instance with a given configuration. + /// + /// This will enable the peripheral but the peripheral won't get + /// automatically disabled when this gets dropped. + pub fn new_typed(i2c: impl Peripheral

+ 'd) -> Self { + Self::new_typed_with_config(i2c, Config::default()) + } + + /// Creates a new I2C instance with a given configuration. + /// + /// This will enable the peripheral but the peripheral won't get + /// automatically disabled when this gets dropped. + pub fn new_typed_with_config(i2c: impl Peripheral

+ 'd, config: Config) -> Self { + crate::into_ref!(i2c); + + let i2c = I2c { + i2c, + phantom: PhantomData, + config, + }; + + PeripheralClockControl::reset(i2c.i2c.peripheral()); + PeripheralClockControl::enable(i2c.i2c.peripheral()); + + i2c.driver().setup(&i2c.config); + i2c + } + + // TODO: missing interrupt APIs + + /// Configures the I2C peripheral to operate in asynchronous mode. + pub fn into_async(mut self) -> I2c<'d, Async, T> { + self.set_interrupt_handler(self.driver().info.async_handler); + + I2c { + i2c: self.i2c, + phantom: PhantomData, + config: self.config, + } + } + + /// Reads enough bytes from slave with `address` to fill `buffer` + pub fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { + let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); + for (idx, chunk) in buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { + // Clear all I2C interrupts + self.driver().clear_all_interrupts(); + + let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); + + self.driver() + .read_operation_blocking( + address, + chunk, + idx == 0, + idx == chunk_count - 1, + idx < chunk_count - 1, + cmd_iterator, + ) + .inspect_err(|_| self.internal_recover())?; + } + + Ok(()) + } + + /// Writes bytes to slave with address `address` + pub fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { + let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); + for (idx, chunk) in buffer.chunks(I2C_CHUNK_SIZE).enumerate() { + // Clear all I2C interrupts + self.driver().clear_all_interrupts(); + + let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); + + self.driver() + .write_operation_blocking( + address, + chunk, + idx == 0, + idx == chunk_count - 1, + cmd_iterator, + ) + .inspect_err(|_| self.internal_recover())?; + } + + Ok(()) + } + + /// Writes bytes to slave with address `address` and then reads enough bytes + /// to fill `buffer` *in a single transaction* + pub fn write_read( + &mut self, + address: u8, + write_buffer: &[u8], + read_buffer: &mut [u8], + ) -> Result<(), Error> { + let write_count = write_buffer.len().div_ceil(I2C_CHUNK_SIZE); + let read_count = read_buffer.len().div_ceil(I2C_CHUNK_SIZE); + + for (idx, chunk) in write_buffer.chunks(I2C_CHUNK_SIZE).enumerate() { + // Clear all I2C interrupts + self.driver().clear_all_interrupts(); + + let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); + + self.driver() + .write_operation_blocking( + address, + chunk, + idx == 0, + idx == write_count - 1 && read_count == 0, + cmd_iterator, + ) + .inspect_err(|_| self.internal_recover())?; + } + + for (idx, chunk) in read_buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { + // Clear all I2C interrupts + self.driver().clear_all_interrupts(); + + let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); + + self.driver() + .read_operation_blocking( + address, + chunk, + idx == 0, + idx == read_count - 1, + idx < read_count - 1, + cmd_iterator, + ) + .inspect_err(|_| self.internal_recover())?; + } + + Ok(()) + } + + /// Execute the provided operations on the I2C bus. + /// + /// Transaction contract: + /// - Before executing the first operation an ST is sent automatically. This + /// is followed by SAD+R/W as appropriate. + /// - Data from adjacent operations of the same type are sent after each + /// other without an SP or SR. + /// - Between adjacent operations of a different type an SR and SAD+R/W is + /// sent. + /// - After executing the last operation an SP is sent automatically. + /// - If the last operation is a `Read` the master does not send an + /// acknowledge for the last byte. + /// + /// - `ST` = start condition + /// - `SAD+R/W` = slave address followed by bit 1 to indicate reading or 0 + /// to indicate writing + /// - `SR` = repeated start condition + /// - `SP` = stop condition + pub fn transaction<'a>( + &mut self, + address: u8, + operations: impl IntoIterator>, + ) -> Result<(), Error> { + self.transaction_impl(address, operations.into_iter().map(Operation::from)) + } +} + +impl private::Sealed for I2c<'_, Blocking, T> where T: Instance {} + +impl InterruptConfigurable for I2c<'_, Blocking, T> +where + T: Instance, +{ + fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) { + let interrupt = self.driver().info.interrupt; + for core in Cpu::other() { + crate::interrupt::disable(core, interrupt); + } + unsafe { crate::interrupt::bind_interrupt(interrupt, handler.handler()) }; + unwrap!(crate::interrupt::enable(interrupt, handler.priority())); + } +} + +impl<'d, T> I2c<'d, Async, T> +where + T: Instance, +{ + /// Configure the I2C peripheral to operate in blocking mode. + pub fn into_blocking(self) -> I2c<'d, Blocking, T> { + let interrupt = self.driver().info.interrupt; + crate::interrupt::disable(Cpu::current(), interrupt); + + I2c { + i2c: self.i2c, + phantom: PhantomData, + config: self.config, + } + } + + /// Writes bytes to slave with address `address` + pub async fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { + let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); + for (idx, chunk) in buffer.chunks(I2C_CHUNK_SIZE).enumerate() { + // Clear all I2C interrupts + self.driver().clear_all_interrupts(); + + let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); + + self.driver() + .write_operation( + address, + chunk, + idx == 0, + idx == chunk_count - 1, + cmd_iterator, + ) + .await?; + } + + Ok(()) + } + + /// Reads enough bytes from slave with `address` to fill `buffer` + pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { + let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); + for (idx, chunk) in buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { + // Clear all I2C interrupts + self.driver().clear_all_interrupts(); + + let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); + + self.driver() + .read_operation( + address, + chunk, + idx == 0, + idx == chunk_count - 1, + idx < chunk_count - 1, + cmd_iterator, + ) + .await?; + } + + Ok(()) + } + + /// Writes bytes to slave with address `address` and then reads enough + /// bytes to fill `buffer` *in a single transaction* + pub async fn write_read( + &mut self, + address: u8, + write_buffer: &[u8], + read_buffer: &mut [u8], + ) -> Result<(), Error> { + let write_count = write_buffer.len().div_ceil(I2C_CHUNK_SIZE); + let read_count = read_buffer.len().div_ceil(I2C_CHUNK_SIZE); + for (idx, chunk) in write_buffer.chunks(I2C_CHUNK_SIZE).enumerate() { + // Clear all I2C interrupts + self.driver().clear_all_interrupts(); + + let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); + + self.driver() + .write_operation( + address, + chunk, + idx == 0, + idx == write_count - 1 && read_count == 0, + cmd_iterator, + ) + .await?; + } + + for (idx, chunk) in read_buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { + // Clear all I2C interrupts + self.driver().clear_all_interrupts(); + + let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); + + self.driver() + .read_operation( + address, + chunk, + idx == 0, + idx == read_count - 1, + idx < read_count - 1, + cmd_iterator, + ) + .await?; + } + + Ok(()) + } + + /// Execute the provided operations on the I2C bus as a single + /// transaction. + /// + /// Transaction contract: + /// - Before executing the first operation an ST is sent automatically. This + /// is followed by SAD+R/W as appropriate. + /// - Data from adjacent operations of the same type are sent after each + /// other without an SP or SR. + /// - Between adjacent operations of a different type an SR and SAD+R/W is + /// sent. + /// - After executing the last operation an SP is sent automatically. + /// - If the last operation is a `Read` the master does not send an + /// acknowledge for the last byte. + /// + /// - `ST` = start condition + /// - `SAD+R/W` = slave address followed by bit 1 to indicate reading or 0 + /// to indicate writing + /// - `SR` = repeated start condition + /// - `SP` = stop condition + pub async fn transaction<'a>( + &mut self, + address: u8, + operations: impl IntoIterator>, + ) -> Result<(), Error> { + self.transaction_impl_async(address, operations.into_iter().map(Operation::from)) + .await + } + + async fn transaction_impl_async<'a>( + &mut self, + address: u8, + operations: impl Iterator>, + ) -> Result<(), Error> { + let mut last_op: Option = None; + // filter out 0 length read operations + let mut op_iter = operations + .filter(|op| op.is_write() || !op.is_empty()) + .peekable(); + + while let Some(op) = op_iter.next() { + let next_op = op_iter.peek().map(|v| v.kind()); + let kind = op.kind(); + // Clear all I2C interrupts + self.driver().clear_all_interrupts(); + + let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); + match op { + Operation::Write(buffer) => { + // execute a write operation: + // - issue START/RSTART if op is different from previous + // - issue STOP if op is the last one + self.driver() + .write_operation( + address, + buffer, + !matches!(last_op, Some(OpKind::Write)), + next_op.is_none(), + cmd_iterator, + ) + .await?; + } + Operation::Read(buffer) => { + // execute a read operation: + // - issue START/RSTART if op is different from previous + // - issue STOP if op is the last one + // - will_continue is true if there is another read operation next + self.driver() + .read_operation( + address, + buffer, + !matches!(last_op, Some(OpKind::Read)), + next_op.is_none(), + matches!(next_op, Some(OpKind::Read)), + cmd_iterator, + ) + .await?; + } + } + + last_op = Some(kind); + } + + Ok(()) + } +} + +fn async_handler(info: &Info, state: &State) { + let regs = info.register_block(); + regs.int_ena().modify(|_, w| { + w.end_detect().clear_bit(); + w.trans_complete().clear_bit(); + w.arbitration_lost().clear_bit(); + w.time_out().clear_bit(); + + #[cfg(not(any(esp32, esp32s2)))] + w.txfifo_wm().clear_bit(); + + cfg_if::cfg_if! { + if #[cfg(esp32)] { + w.ack_err().clear_bit() + } else { + w.nack().clear_bit() + } + } + }); + + state.waker.wake(); +} + +#[allow(clippy::too_many_arguments, unused)] +/// Configures the clock and timing parameters for the I2C peripheral. +fn configure_clock( + register_block: &RegisterBlock, + sclk_div: u32, + scl_low_period: u32, + scl_high_period: u32, + scl_wait_high_period: u32, + sda_hold_time: u32, + sda_sample_time: u32, + scl_rstart_setup_time: u32, + scl_stop_setup_time: u32, + scl_start_hold_time: u32, + scl_stop_hold_time: u32, + timeout: Option, +) { + unsafe { + // divider + #[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2, esp32s3))] + register_block.clk_conf().modify(|_, w| { + w.sclk_sel().clear_bit(); + w.sclk_div_num().bits((sclk_div - 1) as u8) + }); + + // scl period + register_block + .scl_low_period() + .write(|w| w.scl_low_period().bits(scl_low_period as u16)); + + register_block.scl_high_period().write(|w| { + #[cfg(not(esp32))] // ESP32 does not have a wait_high field + w.scl_wait_high_period() + .bits(scl_wait_high_period.try_into().unwrap()); + w.scl_high_period().bits(scl_high_period as u16) + }); + + // sda sample + register_block + .sda_hold() + .write(|w| w.time().bits(sda_hold_time as u16)); + register_block + .sda_sample() + .write(|w| w.time().bits(sda_sample_time as u16)); + + // setup + register_block + .scl_rstart_setup() + .write(|w| w.time().bits(scl_rstart_setup_time as u16)); + register_block + .scl_stop_setup() + .write(|w| w.time().bits(scl_stop_setup_time as u16)); + + // hold + register_block + .scl_start_hold() + .write(|w| w.time().bits(scl_start_hold_time as u16)); + register_block + .scl_stop_hold() + .write(|w| w.time().bits(scl_stop_hold_time as u16)); + + // The ESP32 variant does not have an enable flag for the + // timeout mechanism + cfg_if::cfg_if! { + if #[cfg(esp32)] { + register_block + .to() + .write(|w| w.time_out().bits(unwrap!(timeout))); + } else { + register_block + .to() + .write(|w| w.time_out_en().bit(timeout.is_some()) + .time_out_value() + .bits(timeout.unwrap_or(1) as _) + ); + } + } + } +} + +/// Peripheral data describing a particular I2C instance. +pub struct Info { + /// Pointer to the register block for this I2C instance. + /// + /// Use [Self::register_block] to access the register block. + pub register_block: *const RegisterBlock, + + /// Interrupt handler for the asynchronous operations of this I2C instance. + pub async_handler: InterruptHandler, + + /// Interrupt for this I2C instance. + pub interrupt: Interrupt, + + /// SCL output signal. + pub scl_output: OutputSignal, + + /// SCL input signal. + pub scl_input: InputSignal, + + /// SDA output signal. + pub sda_output: OutputSignal, + + /// SDA input signal. + pub sda_input: InputSignal, +} + +impl Info { + /// Returns the register block for this I2C instance. + pub fn register_block(&self) -> &RegisterBlock { + unsafe { &*self.register_block } + } +} + +#[allow(dead_code)] // Some versions don't need `state` +struct Driver<'a> { + info: &'a Info, + state: &'a State, +} + +impl Driver<'_> { + /// Configures the I2C peripheral with the specified frequency, clocks, and + /// optional timeout. + fn setup(&self, config: &Config) { + self.info.register_block().ctr().write(|w| { + // Set I2C controller to master mode + w.ms_mode().set_bit(); + // Use open drain output for SDA and SCL + w.sda_force_out().set_bit(); + w.scl_force_out().set_bit(); + // Use Most Significant Bit first for sending and receiving data + w.tx_lsb_first().clear_bit(); + w.rx_lsb_first().clear_bit(); + // Ensure that clock is enabled + #[cfg(esp32s2)] + w.ref_always_on().set_bit(); + w.clk_en().set_bit() + }); + + // Configure filter + // FIXME if we ever change this we need to adapt `set_frequency` for ESP32 + self.set_filter(Some(7), Some(7)); + + // Configure frequency + self.set_frequency(config.frequency, config.timeout); + + self.update_config(); + + // Reset entire peripheral (also resets fifo) + self.reset(); + } + + /// Resets the I2C peripheral's command registers + fn reset_command_list(&self) { + // Confirm that all commands that were configured were actually executed + for cmd in self.info.register_block().comd_iter() { + cmd.reset(); + } + } + + /// Configures the I2C peripheral for a write operation. + /// - `addr` is the address of the slave device. + /// - `bytes` is the data two be sent. + /// - `start` indicates whether the operation should start by a START + /// condition and sending the address. + /// - `cmd_iterator` is an iterator over the command registers. + fn setup_write<'a, I>( + &self, + addr: u8, + bytes: &[u8], + start: bool, + cmd_iterator: &mut I, + ) -> Result<(), Error> + where + I: Iterator, + { + // if start is true we can only send 254 additional bytes with the address as + // the first + let max_len = if start { 254usize } else { 255usize }; + if bytes.len() > max_len { + // we could support more by adding multiple write operations + return Err(Error::ExceedingFifo); + } + + let write_len = if start { bytes.len() + 1 } else { bytes.len() }; + // don't issue write if there is no data to write + if write_len > 0 { + // WRITE command + self.add_cmd( + cmd_iterator, + Command::Write { + ack_exp: Ack::Ack, + ack_check_en: true, + length: write_len as u8, + }, + )?; + } + + self.update_config(); + + if start { + // Load address and R/W bit into FIFO + self.write_fifo(addr << 1 | OperationType::Write as u8); + } + Ok(()) + } + + /// Configures the I2C peripheral for a read operation. + /// - `addr` is the address of the slave device. + /// - `buffer` is the buffer to store the read data. + /// - `start` indicates whether the operation should start by a START + /// condition and sending the address. + /// - `will_continue` indicates whether there is another read operation + /// following this one and we should not nack the last byte. + /// - `cmd_iterator` is an iterator over the command registers. + fn setup_read<'a, I>( + &self, + addr: u8, + buffer: &mut [u8], + start: bool, + will_continue: bool, + cmd_iterator: &mut I, + ) -> Result<(), Error> + where + I: Iterator, + { + if buffer.is_empty() { + return Err(Error::InvalidZeroLength); + } + let (max_len, initial_len) = if will_continue { + (255usize, buffer.len()) + } else { + (254usize, buffer.len() - 1) + }; + if buffer.len() > max_len { + // we could support more by adding multiple read operations + return Err(Error::ExceedingFifo); + } + + if start { + // WRITE command + self.add_cmd( + cmd_iterator, + Command::Write { + ack_exp: Ack::Ack, + ack_check_en: true, + length: 1, + }, + )?; + } + + if initial_len > 0 { + // READ command + self.add_cmd( + cmd_iterator, + Command::Read { + ack_value: Ack::Ack, + length: initial_len as u8, + }, + )?; + } + + if !will_continue { + // this is the last read so we need to nack the last byte + // READ w/o ACK + self.add_cmd( + cmd_iterator, + Command::Read { + ack_value: Ack::Nack, + length: 1, + }, + )?; + } + + self.update_config(); + + if start { + // Load address and R/W bit into FIFO + self.write_fifo(addr << 1 | OperationType::Read as u8); + } + Ok(()) + } + + /// Clears all pending interrupts for the I2C peripheral. + fn clear_all_interrupts(&self) { + self.info + .register_block() + .int_clr() + .write(|w| unsafe { w.bits(I2C_LL_INTR_MASK) }); + } + + /// Waits for the completion of an I2C transaction. + fn wait_for_completion_blocking(&self, end_only: bool) -> Result<(), Error> { + let mut tout = MAX_ITERATIONS; + loop { + let interrupts = self.info.register_block().int_raw().read(); + + self.check_errors()?; + + // Handle completion cases + // A full transmission was completed (either a STOP condition or END was + // processed) + if (!end_only && interrupts.trans_complete().bit_is_set()) + || interrupts.end_detect().bit_is_set() + { + break; + } + + tout -= 1; + if tout == 0 { + return Err(Error::TimeOut); + } + } + self.check_all_commands_done()?; + Ok(()) + } + + /// Checks whether all I2C commands have completed execution. + fn check_all_commands_done(&self) -> Result<(), Error> { + // NOTE: on esp32 executing the end command generates the end_detect interrupt + // but does not seem to clear the done bit! So we don't check the done + // status of an end command + for cmd_reg in self.info.register_block().comd_iter() { + let cmd = cmd_reg.read(); + + if cmd.bits() != 0x0 && !cmd.opcode().is_end() && !cmd.command_done().bit_is_set() { + return Err(Error::ExecIncomplete); + } + } + + Ok(()) + } + + /// Starts an I2C transmission. + fn start_transmission(&self) { + // Start transmission + self.info + .register_block() + .ctr() + .modify(|_, w| w.trans_start().set_bit()); + } + + /// Executes an I2C write operation. + /// - `addr` is the address of the slave device. + /// - `bytes` is the data two be sent. + /// - `start` indicates whether the operation should start by a START + /// condition and sending the address. + /// - `stop` indicates whether the operation should end with a STOP + /// condition. + /// - `cmd_iterator` is an iterator over the command registers. + fn write_operation_blocking<'a>( + &self, + address: u8, + bytes: &[u8], + start: bool, + stop: bool, + cmd_iterator: &mut impl Iterator, + ) -> Result<(), Error> { + // Short circuit for zero length writes without start or end as that would be an + // invalid operation write lengths in the TRM (at least for ESP32-S3) are 1-255 + if bytes.is_empty() && !start && !stop { + return Ok(()); + } + + let index = self.start_write_operation(address, bytes, start, stop, cmd_iterator)?; + + // Fill the FIFO with the remaining bytes: + self.write_remaining_tx_fifo_blocking(index, bytes)?; + self.wait_for_completion_blocking(!stop)?; + Ok(()) + } + + /// Executes an I2C write operation. + /// - `addr` is the address of the slave device. + /// - `bytes` is the data two be sent. + /// - `start` indicates whether the operation should start by a START + /// condition and sending the address. + /// - `stop` indicates whether the operation should end with a STOP + /// condition. + /// - `cmd_iterator` is an iterator over the command registers. + async fn write_operation<'a>( + &self, + address: u8, + bytes: &[u8], + start: bool, + stop: bool, + cmd_iterator: &mut impl Iterator, + ) -> Result<(), Error> { + // Short circuit for zero length writes without start or end as that would be an + // invalid operation write lengths in the TRM (at least for ESP32-S3) are 1-255 + if bytes.is_empty() && !start && !stop { + return Ok(()); + } + + let index = self.start_write_operation(address, bytes, start, stop, cmd_iterator)?; + + // Fill the FIFO with the remaining bytes: + self.write_remaining_tx_fifo(index, bytes).await?; + self.wait_for_completion(!stop).await?; + Ok(()) + } + + /// Executes an I2C read operation. + /// - `addr` is the address of the slave device. + /// - `buffer` is the buffer to store the read data. + /// - `start` indicates whether the operation should start by a START + /// condition and sending the address. + /// - `stop` indicates whether the operation should end with a STOP + /// condition. + /// - `will_continue` indicates whether there is another read operation + /// following this one and we should not nack the last byte. + /// - `cmd_iterator` is an iterator over the command registers. + fn read_operation_blocking<'a>( + &self, + address: u8, + buffer: &mut [u8], + start: bool, + stop: bool, + will_continue: bool, + cmd_iterator: &mut impl Iterator, + ) -> Result<(), Error> { + // Short circuit for zero length reads as that would be an invalid operation + // read lengths in the TRM (at least for ESP32-S3) are 1-255 + if buffer.is_empty() { + return Ok(()); + } + + self.start_read_operation(address, buffer, start, stop, will_continue, cmd_iterator)?; + self.read_all_from_fifo_blocking(buffer)?; + self.wait_for_completion_blocking(!stop)?; + Ok(()) + } + + /// Executes an async I2C read operation. + /// - `addr` is the address of the slave device. + /// - `buffer` is the buffer to store the read data. + /// - `start` indicates whether the operation should start by a START + /// condition and sending the address. + /// - `stop` indicates whether the operation should end with a STOP + /// condition. + /// - `will_continue` indicates whether there is another read operation + /// following this one and we should not nack the last byte. + /// - `cmd_iterator` is an iterator over the command registers. + async fn read_operation<'a>( + &self, + address: u8, + buffer: &mut [u8], + start: bool, + stop: bool, + will_continue: bool, + cmd_iterator: &mut impl Iterator, + ) -> Result<(), Error> { + // Short circuit for zero length reads as that would be an invalid operation + // read lengths in the TRM (at least for ESP32-S3) are 1-255 + if buffer.is_empty() { + return Ok(()); + } + + self.start_read_operation(address, buffer, start, stop, will_continue, cmd_iterator)?; + self.read_all_from_fifo(buffer).await?; + self.wait_for_completion(!stop).await?; + Ok(()) + } + + fn start_read_operation<'a>( + &self, + address: u8, + buffer: &mut [u8], + start: bool, + stop: bool, + will_continue: bool, + cmd_iterator: &mut impl Iterator, + ) -> Result<(), Error> { + // Reset FIFO and command list + self.reset_fifo(); + self.reset_command_list(); + + if start { + self.add_cmd(cmd_iterator, Command::Start)?; + } + + self.setup_read(address, buffer, start, will_continue, cmd_iterator)?; + + self.add_cmd( + cmd_iterator, + if stop { Command::Stop } else { Command::End }, + )?; + self.start_transmission(); + + Ok(()) + } + + fn start_write_operation<'a>( + &self, + address: u8, + bytes: &[u8], + start: bool, + stop: bool, + cmd_iterator: &mut impl Iterator, + ) -> Result { + // Reset FIFO and command list + self.reset_fifo(); + self.reset_command_list(); + + if start { + self.add_cmd(cmd_iterator, Command::Start)?; + } + self.setup_write(address, bytes, start, cmd_iterator)?; + self.add_cmd( + cmd_iterator, + if stop { Command::Stop } else { Command::End }, + )?; + let index = self.fill_tx_fifo(bytes); + self.start_transmission(); + + Ok(index) + } + + /// Adds a command to the I2C command sequence. + fn add_cmd<'a, I>(&self, cmd_iterator: &mut I, command: Command) -> Result<(), Error> + where + I: Iterator, + { + let cmd = cmd_iterator.next().ok_or(Error::CommandNrExceeded)?; + + cmd.write(|w| match command { + Command::Start => w.opcode().rstart(), + Command::Stop => w.opcode().stop(), + Command::End => w.opcode().end(), + Command::Write { + ack_exp, + ack_check_en, + length, + } => unsafe { + w.opcode().write(); + w.ack_exp().bit(ack_exp == Ack::Nack); + w.ack_check_en().bit(ack_check_en); + w.byte_num().bits(length); + w + }, + Command::Read { ack_value, length } => unsafe { + w.opcode().read(); + w.ack_value().bit(ack_value == Ack::Nack); + w.byte_num().bits(length); + w + }, + }); + + Ok(()) + } +} + +impl PartialEq for Info { + fn eq(&self, other: &Self) -> bool { + self.register_block == other.register_block + } +} + +unsafe impl Sync for Info {} + +/// Peripheral state for an I2C instance. +pub struct State { + /// Waker for the asynchronous operations. + pub waker: AtomicWaker, +} + +/// I2C Peripheral Instance +#[doc(hidden)] +pub trait Instance: Peripheral

+ PeripheralMarker + Into + 'static { + /// Returns the peripheral data and state describing this instance. + fn parts(&self) -> (&Info, &State); + + /// Returns the peripheral data describing this instance. + #[inline(always)] + fn info(&self) -> &Info { + self.parts().0 + } + + /// Returns the peripheral state for this instance. + #[inline(always)] + fn state(&self) -> &State { + self.parts().1 + } +} + +macro_rules! instance { + ($inst:ident, $scl:ident, $sda:ident, $interrupt:ident) => { + impl Instance for crate::peripherals::$inst { + fn parts(&self) -> (&Info, &State) { + #[crate::macros::handler] + pub(super) fn irq_handler() { + async_handler(&PERIPHERAL, &STATE); + } + + static STATE: State = State { + waker: AtomicWaker::new(), + }; + + static PERIPHERAL: Info = Info { + register_block: crate::peripherals::$inst::ptr(), + async_handler: irq_handler, + interrupt: Interrupt::$interrupt, + scl_output: OutputSignal::$scl, + scl_input: InputSignal::$scl, + sda_output: OutputSignal::$sda, + sda_input: InputSignal::$sda, + }; + (&PERIPHERAL, &STATE) + } + } + }; +} + +#[cfg(i2c0)] +instance!(I2C0, I2CEXT0_SCL, I2CEXT0_SDA, I2C_EXT0); +#[cfg(i2c1)] +instance!(I2C1, I2CEXT1_SCL, I2CEXT1_SDA, I2C_EXT1); + +crate::any_peripheral! { + /// Represents any I2C peripheral. + pub peripheral AnyI2c { + #[cfg(i2c0)] + I2c0(crate::peripherals::I2C0), + #[cfg(i2c1)] + I2c1(crate::peripherals::I2C1), + } +} + +impl Instance for AnyI2c { + delegate::delegate! { + to match &self.0 { + AnyI2cInner::I2c0(i2c) => i2c, + #[cfg(i2c1)] + AnyI2cInner::I2c1(i2c) => i2c, + } { + fn parts(&self) -> (&Info, &State); + } + } +} + +#[cfg(lp_i2c0)] +pub mod lp_i2c; diff --git a/esp-hal/src/i2c/support/eh.rs b/esp-hal/src/i2c/master/support/eh.rs similarity index 96% rename from esp-hal/src/i2c/support/eh.rs rename to esp-hal/src/i2c/master/support/eh.rs index 39a53879b13..63d950bb25a 100644 --- a/esp-hal/src/i2c/support/eh.rs +++ b/esp-hal/src/i2c/master/support/eh.rs @@ -1,5 +1,5 @@ use crate::{ - i2c::{Error, I2c, Instance, Operation}, + i2c::master::{Error, I2c, Instance, Operation}, Mode, }; diff --git a/esp-hal/src/i2c/support/eh02.rs b/esp-hal/src/i2c/master/support/eh02.rs similarity index 95% rename from esp-hal/src/i2c/support/eh02.rs rename to esp-hal/src/i2c/master/support/eh02.rs index 4f572d9aadb..8e1bb5f71fc 100644 --- a/esp-hal/src/i2c/support/eh02.rs +++ b/esp-hal/src/i2c/master/support/eh02.rs @@ -1,5 +1,5 @@ use crate::{ - i2c::{Error, I2c, Instance}, + i2c::master::{Error, I2c, Instance}, Blocking, }; diff --git a/esp-hal/src/i2c/support/eh_a.rs b/esp-hal/src/i2c/master/support/eh_a.rs similarity index 92% rename from esp-hal/src/i2c/support/eh_a.rs rename to esp-hal/src/i2c/master/support/eh_a.rs index f12053c9fa9..4debbecb8ab 100644 --- a/esp-hal/src/i2c/support/eh_a.rs +++ b/esp-hal/src/i2c/master/support/eh_a.rs @@ -1,7 +1,7 @@ use embedded_hal::i2c::Operation; use crate::{ - i2c::{I2c, Instance}, + i2c::master::{I2c, Instance}, Async, }; diff --git a/esp-hal/src/i2c/support/mod.rs b/esp-hal/src/i2c/master/support/mod.rs similarity index 100% rename from esp-hal/src/i2c/support/mod.rs rename to esp-hal/src/i2c/master/support/mod.rs diff --git a/esp-hal/src/i2c/version/mod.rs b/esp-hal/src/i2c/master/version/mod.rs similarity index 100% rename from esp-hal/src/i2c/version/mod.rs rename to esp-hal/src/i2c/master/version/mod.rs diff --git a/esp-hal/src/i2c/version/v1.rs b/esp-hal/src/i2c/master/version/v1.rs similarity index 99% rename from esp-hal/src/i2c/version/v1.rs rename to esp-hal/src/i2c/master/version/v1.rs index 1f83af03f0d..52bb4ef93ab 100644 --- a/esp-hal/src/i2c/version/v1.rs +++ b/esp-hal/src/i2c/master/version/v1.rs @@ -1,5 +1,5 @@ //! ESP32-specific implementation -use crate::i2c::*; +use crate::i2c::master::*; pub(crate) const I2C_CHUNK_SIZE: usize = 32; pub(crate) const I2C_LL_INTR_MASK: u32 = 0x3ffff; diff --git a/esp-hal/src/i2c/version/v1_v2_common.rs b/esp-hal/src/i2c/master/version/v1_v2_common.rs similarity index 99% rename from esp-hal/src/i2c/version/v1_v2_common.rs rename to esp-hal/src/i2c/master/version/v1_v2_common.rs index a07bb199b3f..43f2b62b899 100644 --- a/esp-hal/src/i2c/version/v1_v2_common.rs +++ b/esp-hal/src/i2c/master/version/v1_v2_common.rs @@ -1,4 +1,4 @@ -use crate::i2c::*; +use crate::i2c::master::*; fn assert_read_length(len: usize) { if len <= 32 { diff --git a/esp-hal/src/i2c/version/v2.rs b/esp-hal/src/i2c/master/version/v2.rs similarity index 98% rename from esp-hal/src/i2c/version/v2.rs rename to esp-hal/src/i2c/master/version/v2.rs index 87156741a78..24121db4e2b 100644 --- a/esp-hal/src/i2c/version/v2.rs +++ b/esp-hal/src/i2c/master/version/v2.rs @@ -1,5 +1,5 @@ //! ESP32-S2-specific implementation -use crate::i2c::*; +use crate::i2c::master::*; pub(crate) const I2C_CHUNK_SIZE: usize = 32; pub(crate) const I2C_LL_INTR_MASK: u32 = 0x1ffff; diff --git a/esp-hal/src/i2c/version/v2_v3_common.rs b/esp-hal/src/i2c/master/version/v2_v3_common.rs similarity index 99% rename from esp-hal/src/i2c/version/v2_v3_common.rs rename to esp-hal/src/i2c/master/version/v2_v3_common.rs index e5a2c816717..b3bbce2f2e0 100644 --- a/esp-hal/src/i2c/version/v2_v3_common.rs +++ b/esp-hal/src/i2c/master/version/v2_v3_common.rs @@ -3,7 +3,7 @@ use core::{ task::{Context, Poll}, }; -use crate::i2c::*; +use crate::i2c::master::*; pub enum Event { EndDetect, diff --git a/esp-hal/src/i2c/version/v3.rs b/esp-hal/src/i2c/master/version/v3.rs similarity index 99% rename from esp-hal/src/i2c/version/v3.rs rename to esp-hal/src/i2c/master/version/v3.rs index 086afcc3247..794a488473d 100644 --- a/esp-hal/src/i2c/version/v3.rs +++ b/esp-hal/src/i2c/master/version/v3.rs @@ -1,4 +1,4 @@ -use crate::i2c::{version::v2_v3_common::*, *}; +use crate::i2c::master::{version::v2_v3_common::*, *}; pub(crate) const I2C_CHUNK_SIZE: usize = 254; pub(crate) const I2C_LL_INTR_MASK: u32 = 0x3ffff; diff --git a/esp-hal/src/i2c/mod.rs b/esp-hal/src/i2c/mod.rs index 5d9f1571fb3..0500f1eeb7d 100644 --- a/esp-hal/src/i2c/mod.rs +++ b/esp-hal/src/i2c/mod.rs @@ -4,1435 +4,5 @@ //! protocol that allows co-existence of multiple masters and slaves on the //! same bus. I2C uses two bidirectional open-drain lines: serial data line //! (SDA) and serial clock line (SCL), pulled up by resistors. -//! -//! Espressif devices sometimes have more than one I2C controller, responsible -//! for handling communication on the I2C bus. A single I2C controller can be -//! a master or a slave. -//! -//! Typically, an I2C slave device has a 7-bit address or 10-bit address. -//! Devices supports both I2C Standard-mode (Sm) and Fast-mode (Fm) which can -//! go up to 100KHz and 400KHz respectively. -//! -//! ## Configuration -//! -//! Each I2C controller is individually configurable, and the usual setting -//! such as frequency, timeout, and SDA/SCL pins can easily be configured. -//! -//! ## Usage -//! -//! The I2C driver implements a number of third-party traits, with the -//! intention of making the HAL inter-compatible with various device drivers -//! from the community. This includes the [`embedded-hal`] for both 0.2.x and -//! 1.0.x versions. -//! -//! ## Examples -//! -//! ### Read Data from a BMP180 Sensor -//! -//! ```rust, no_run -#![doc = crate::before_snippet!()] -//! # use esp_hal::i2c::{Config, I2c}; -//! # use esp_hal::gpio::Io; -//! let io = Io::new(peripherals.GPIO, peripherals.IO_MUX); -//! -//! // Create a new peripheral object with the described wiring -//! // and standard I2C clock speed. -//! let mut i2c = I2c::new_with_config(peripherals.I2C0, Config { -//! frequency: 100.kHz(), -//! timeout: None, -//! }) -//! .with_sda(io.pins.gpio1) -//! .with_scl(io.pins.gpio2); -//! -//! loop { -//! let mut data = [0u8; 22]; -//! i2c.write_read(0x77, &[0xaa], &mut data).ok(); -//! } -//! # } -//! ``` -//! [`embedded-hal`]: https://crates.io/crates/embedded-hal - -mod support; -mod version; - -use core::{convert::Infallible, marker::PhantomData}; - -use embassy_embedded_hal::SetConfig; -use embassy_sync::waitqueue::AtomicWaker; -use fugit::HertzU32; -use version::{I2C_CHUNK_SIZE, I2C_LL_INTR_MASK}; - -use crate::{ - clock::Clocks, - dma::PeripheralMarker, - gpio::{interconnect::PeripheralOutput, InputSignal, OutputSignal, Pull}, - interrupt::InterruptHandler, - peripheral::{Peripheral, PeripheralRef}, - peripherals::{ - i2c0::{RegisterBlock, COMD}, - Interrupt, - }, - private, - system::PeripheralClockControl, - Async, - Blocking, - Cpu, - InterruptConfigurable, - Mode, -}; - -// on ESP32 there is a chance to get trapped in `wait_for_completion` forever -const MAX_ITERATIONS: u32 = 1_000_000; - -/// I2C-specific transmission errors -#[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Error { - /// The transmission exceeded the FIFO size. - ExceedingFifo, - /// The acknowledgment check failed. - AckCheckFailed, - /// A timeout occurred during transmission. - TimeOut, - /// The arbitration for the bus was lost. - ArbitrationLost, - /// The execution of the I2C command was incomplete. - ExecIncomplete, - /// The number of commands issued exceeded the limit. - CommandNrExceeded, - /// Zero length read or write operation. - InvalidZeroLength, -} - -#[derive(PartialEq)] -// This enum is used to keep track of the last/next operation that was/will be -// performed in an embedded-hal(-async) I2c::transaction. It is used to -// determine whether a START condition should be issued at the start of the -// current operation and whether a read needs an ack or a nack for the final -// byte. -enum OpKind { - Write, - Read, -} - -/// I2C operation. -/// -/// Several operations can be combined as part of a transaction. -pub enum Operation<'a> { - /// Write data from the provided buffer. - Write(&'a [u8]), - - /// Read data into the provided buffer. - Read(&'a mut [u8]), -} - -impl<'a, 'b> From<&'a mut Operation<'b>> for Operation<'a> { - fn from(value: &'a mut Operation<'b>) -> Self { - match value { - Operation::Write(buffer) => Operation::Write(buffer), - Operation::Read(buffer) => Operation::Read(buffer), - } - } -} - -impl Operation<'_> { - fn is_write(&self) -> bool { - matches!(self, Operation::Write(_)) - } - - fn kind(&self) -> OpKind { - match self { - Operation::Write(_) => OpKind::Write, - Operation::Read(_) => OpKind::Read, - } - } - - fn is_empty(&self) -> bool { - match self { - Operation::Write(buffer) => buffer.is_empty(), - Operation::Read(buffer) => buffer.is_empty(), - } - } -} - -/// A generic I2C Command -#[cfg_attr(feature = "debug", derive(Debug))] -enum Command { - Start, - Stop, - End, - Write { - /// This bit is to set an expected ACK value for the transmitter. - ack_exp: Ack, - /// Enables checking the ACK value received against the ack_exp value. - ack_check_en: bool, - /// Length of data (in bytes) to be written. The maximum length is 255, - /// while the minimum is 1. - length: u8, - }, - Read { - /// Indicates whether the receiver will send an ACK after this byte has - /// been received. - ack_value: Ack, - /// Length of data (in bytes) to be read. The maximum length is 255, - /// while the minimum is 1. - length: u8, - }, -} - -enum OperationType { - Write = 0, - Read = 1, -} - -#[derive(Eq, PartialEq, Copy, Clone)] -#[cfg_attr(feature = "debug", derive(Debug))] -enum Ack { - Ack = 0, - Nack = 1, -} -impl From for Ack { - fn from(ack: u32) -> Self { - match ack { - 0 => Ack::Ack, - 1 => Ack::Nack, - _ => unreachable!(), - } - } -} -impl From for u32 { - fn from(ack: Ack) -> u32 { - ack as u32 - } -} - -/// I2C driver configuration -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Config { - /// The I2C clock frequency. - pub frequency: HertzU32, - - /// I2C SCL timeout period. - /// - /// When the level of SCL remains unchanged for more than `timeout` bus - /// clock cycles, the bus goes to idle state. - /// - /// The default value is about 10 bus clock cycles. - #[doc = ""] - #[cfg_attr( - not(esp32), - doc = "Note that the effective timeout may be longer than the value configured here." - )] - #[cfg_attr(not(esp32), doc = "Configuring `None` disables timeout control.")] - #[cfg_attr(esp32, doc = "Configuring `None` equals to the maximum timeout value.")] - // TODO: when supporting interrupts, document that SCL = high also triggers an interrupt. - pub timeout: Option, -} - -impl Default for Config { - fn default() -> Self { - use fugit::RateExtU32; - Config { - frequency: 100.kHz(), - timeout: Some(10), - } - } -} - -/// I2C driver -pub struct I2c<'d, DM: Mode, T = AnyI2c> { - i2c: PeripheralRef<'d, T>, - phantom: PhantomData, - config: Config, -} - -impl SetConfig for I2c<'_, DM, T> { - type Config = Config; - type ConfigError = Infallible; - - fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { - self.apply_config(config); - Ok(()) - } -} - -impl<'d, T, DM: Mode> I2c<'d, DM, T> -where - T: Instance, -{ - fn driver(&self) -> Driver<'_> { - Driver { - info: self.i2c.info(), - state: self.i2c.state(), - } - } - - fn internal_recover(&self) { - PeripheralClockControl::reset(self.i2c.peripheral()); - PeripheralClockControl::enable(self.i2c.peripheral()); - - self.driver().setup(&self.config); - } - - /// Applies a new configuration. - pub fn apply_config(&mut self, config: &Config) { - self.config = *config; - self.driver().setup(&self.config); - } - - fn transaction_impl<'a>( - &mut self, - address: u8, - operations: impl Iterator>, - ) -> Result<(), Error> { - let mut last_op: Option = None; - // filter out 0 length read operations - let mut op_iter = operations - .filter(|op| op.is_write() || !op.is_empty()) - .peekable(); - - while let Some(op) = op_iter.next() { - let next_op = op_iter.peek().map(|v| v.kind()); - let kind = op.kind(); - // Clear all I2C interrupts - self.driver().clear_all_interrupts(); - - let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - match op { - Operation::Write(buffer) => { - // execute a write operation: - // - issue START/RSTART if op is different from previous - // - issue STOP if op is the last one - self.driver() - .write_operation_blocking( - address, - buffer, - !matches!(last_op, Some(OpKind::Write)), - next_op.is_none(), - cmd_iterator, - ) - .inspect_err(|_| self.internal_recover())?; - } - Operation::Read(buffer) => { - // execute a read operation: - // - issue START/RSTART if op is different from previous - // - issue STOP if op is the last one - // - will_continue is true if there is another read operation next - self.driver() - .read_operation_blocking( - address, - buffer, - !matches!(last_op, Some(OpKind::Read)), - next_op.is_none(), - matches!(next_op, Some(OpKind::Read)), - cmd_iterator, - ) - .inspect_err(|_| self.internal_recover())?; - } - } - - last_op = Some(kind); - } - - Ok(()) - } - - /// Connect a pin to the I2C SDA signal. - pub fn with_sda(self, sda: impl Peripheral

+ 'd) -> Self { - let info = self.driver().info; - let input = info.sda_input; - let output = info.sda_output; - self.with_pin(sda, input, output) - } - - /// Connect a pin to the I2C SCL signal. - pub fn with_scl(self, scl: impl Peripheral

+ 'd) -> Self { - let info = self.driver().info; - let input = info.scl_input; - let output = info.scl_output; - self.with_pin(scl, input, output) - } - - fn with_pin( - self, - pin: impl Peripheral

+ 'd, - input: InputSignal, - output: OutputSignal, - ) -> Self { - crate::into_mapped_ref!(pin); - // avoid the pin going low during configuration - pin.set_output_high(true, private::Internal); - - pin.set_to_open_drain_output(private::Internal); - pin.enable_input(true, private::Internal); - pin.pull_direction(Pull::Up, private::Internal); - - input.connect_to(&mut pin); - output.connect_to(&mut pin); - - self - } -} - -impl<'d> I2c<'d, Blocking> { - /// Creates a new I2C instance. - /// - /// This will enable the peripheral but the peripheral won't get - /// automatically disabled when this gets dropped. - pub fn new(i2c: impl Peripheral

+ 'd) -> Self { - Self::new_with_config(i2c.map_into(), Config::default()) - } - - /// Creates a new I2C instance with a given configuration. - /// - /// This will enable the peripheral but the peripheral won't get - /// automatically disabled when this gets dropped. - pub fn new_with_config(i2c: impl Peripheral

+ 'd, config: Config) -> Self { - Self::new_typed_with_config(i2c.map_into(), config) - } -} - -impl<'d, T> I2c<'d, Blocking, T> -where - T: Instance, -{ - /// Creates a new I2C instance with a given configuration. - /// - /// This will enable the peripheral but the peripheral won't get - /// automatically disabled when this gets dropped. - pub fn new_typed(i2c: impl Peripheral

+ 'd) -> Self { - Self::new_typed_with_config(i2c, Config::default()) - } - - /// Creates a new I2C instance with a given configuration. - /// - /// This will enable the peripheral but the peripheral won't get - /// automatically disabled when this gets dropped. - pub fn new_typed_with_config(i2c: impl Peripheral

+ 'd, config: Config) -> Self { - crate::into_ref!(i2c); - - let i2c = I2c { - i2c, - phantom: PhantomData, - config, - }; - - PeripheralClockControl::reset(i2c.i2c.peripheral()); - PeripheralClockControl::enable(i2c.i2c.peripheral()); - - i2c.driver().setup(&i2c.config); - i2c - } - - // TODO: missing interrupt APIs - - /// Configures the I2C peripheral to operate in asynchronous mode. - pub fn into_async(mut self) -> I2c<'d, Async, T> { - self.set_interrupt_handler(self.driver().info.async_handler); - - I2c { - i2c: self.i2c, - phantom: PhantomData, - config: self.config, - } - } - - /// Reads enough bytes from slave with `address` to fill `buffer` - pub fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { - let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); - for (idx, chunk) in buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { - // Clear all I2C interrupts - self.driver().clear_all_interrupts(); - - let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - - self.driver() - .read_operation_blocking( - address, - chunk, - idx == 0, - idx == chunk_count - 1, - idx < chunk_count - 1, - cmd_iterator, - ) - .inspect_err(|_| self.internal_recover())?; - } - - Ok(()) - } - - /// Writes bytes to slave with address `address` - pub fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { - let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); - for (idx, chunk) in buffer.chunks(I2C_CHUNK_SIZE).enumerate() { - // Clear all I2C interrupts - self.driver().clear_all_interrupts(); - - let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - - self.driver() - .write_operation_blocking( - address, - chunk, - idx == 0, - idx == chunk_count - 1, - cmd_iterator, - ) - .inspect_err(|_| self.internal_recover())?; - } - - Ok(()) - } - - /// Writes bytes to slave with address `address` and then reads enough bytes - /// to fill `buffer` *in a single transaction* - pub fn write_read( - &mut self, - address: u8, - write_buffer: &[u8], - read_buffer: &mut [u8], - ) -> Result<(), Error> { - let write_count = write_buffer.len().div_ceil(I2C_CHUNK_SIZE); - let read_count = read_buffer.len().div_ceil(I2C_CHUNK_SIZE); - - for (idx, chunk) in write_buffer.chunks(I2C_CHUNK_SIZE).enumerate() { - // Clear all I2C interrupts - self.driver().clear_all_interrupts(); - - let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - - self.driver() - .write_operation_blocking( - address, - chunk, - idx == 0, - idx == write_count - 1 && read_count == 0, - cmd_iterator, - ) - .inspect_err(|_| self.internal_recover())?; - } - - for (idx, chunk) in read_buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { - // Clear all I2C interrupts - self.driver().clear_all_interrupts(); - - let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - - self.driver() - .read_operation_blocking( - address, - chunk, - idx == 0, - idx == read_count - 1, - idx < read_count - 1, - cmd_iterator, - ) - .inspect_err(|_| self.internal_recover())?; - } - - Ok(()) - } - - /// Execute the provided operations on the I2C bus. - /// - /// Transaction contract: - /// - Before executing the first operation an ST is sent automatically. This - /// is followed by SAD+R/W as appropriate. - /// - Data from adjacent operations of the same type are sent after each - /// other without an SP or SR. - /// - Between adjacent operations of a different type an SR and SAD+R/W is - /// sent. - /// - After executing the last operation an SP is sent automatically. - /// - If the last operation is a `Read` the master does not send an - /// acknowledge for the last byte. - /// - /// - `ST` = start condition - /// - `SAD+R/W` = slave address followed by bit 1 to indicate reading or 0 - /// to indicate writing - /// - `SR` = repeated start condition - /// - `SP` = stop condition - pub fn transaction<'a>( - &mut self, - address: u8, - operations: impl IntoIterator>, - ) -> Result<(), Error> { - self.transaction_impl(address, operations.into_iter().map(Operation::from)) - } -} - -impl private::Sealed for I2c<'_, Blocking, T> where T: Instance {} - -impl InterruptConfigurable for I2c<'_, Blocking, T> -where - T: Instance, -{ - fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) { - let interrupt = self.driver().info.interrupt; - for core in Cpu::other() { - crate::interrupt::disable(core, interrupt); - } - unsafe { crate::interrupt::bind_interrupt(interrupt, handler.handler()) }; - unwrap!(crate::interrupt::enable(interrupt, handler.priority())); - } -} - -impl<'d, T> I2c<'d, Async, T> -where - T: Instance, -{ - /// Configure the I2C peripheral to operate in blocking mode. - pub fn into_blocking(self) -> I2c<'d, Blocking, T> { - crate::interrupt::disable(Cpu::current(), self.driver().info.interrupt); - - I2c { - i2c: self.i2c, - phantom: PhantomData, - config: self.config, - } - } - - /// Writes bytes to slave with address `address` - pub async fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { - let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); - for (idx, chunk) in buffer.chunks(I2C_CHUNK_SIZE).enumerate() { - // Clear all I2C interrupts - self.driver().clear_all_interrupts(); - - let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - - self.driver() - .write_operation( - address, - chunk, - idx == 0, - idx == chunk_count - 1, - cmd_iterator, - ) - .await?; - } - - Ok(()) - } - - /// Reads enough bytes from slave with `address` to fill `buffer` - pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { - let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); - for (idx, chunk) in buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { - // Clear all I2C interrupts - self.driver().clear_all_interrupts(); - - let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - - self.driver() - .read_operation( - address, - chunk, - idx == 0, - idx == chunk_count - 1, - idx < chunk_count - 1, - cmd_iterator, - ) - .await?; - } - - Ok(()) - } - - /// Writes bytes to slave with address `address` and then reads enough - /// bytes to fill `buffer` *in a single transaction* - pub async fn write_read( - &mut self, - address: u8, - write_buffer: &[u8], - read_buffer: &mut [u8], - ) -> Result<(), Error> { - let write_count = write_buffer.len().div_ceil(I2C_CHUNK_SIZE); - let read_count = read_buffer.len().div_ceil(I2C_CHUNK_SIZE); - for (idx, chunk) in write_buffer.chunks(I2C_CHUNK_SIZE).enumerate() { - // Clear all I2C interrupts - self.driver().clear_all_interrupts(); - - let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - - self.driver() - .write_operation( - address, - chunk, - idx == 0, - idx == write_count - 1 && read_count == 0, - cmd_iterator, - ) - .await?; - } - - for (idx, chunk) in read_buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { - // Clear all I2C interrupts - self.driver().clear_all_interrupts(); - - let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - - self.driver() - .read_operation( - address, - chunk, - idx == 0, - idx == read_count - 1, - idx < read_count - 1, - cmd_iterator, - ) - .await?; - } - - Ok(()) - } - - /// Execute the provided operations on the I2C bus as a single - /// transaction. - /// - /// Transaction contract: - /// - Before executing the first operation an ST is sent automatically. This - /// is followed by SAD+R/W as appropriate. - /// - Data from adjacent operations of the same type are sent after each - /// other without an SP or SR. - /// - Between adjacent operations of a different type an SR and SAD+R/W is - /// sent. - /// - After executing the last operation an SP is sent automatically. - /// - If the last operation is a `Read` the master does not send an - /// acknowledge for the last byte. - /// - /// - `ST` = start condition - /// - `SAD+R/W` = slave address followed by bit 1 to indicate reading or 0 - /// to indicate writing - /// - `SR` = repeated start condition - /// - `SP` = stop condition - pub async fn transaction<'a>( - &mut self, - address: u8, - operations: impl IntoIterator>, - ) -> Result<(), Error> { - self.transaction_impl_async(address, operations.into_iter().map(Operation::from)) - .await - } - - async fn transaction_impl_async<'a>( - &mut self, - address: u8, - operations: impl Iterator>, - ) -> Result<(), Error> { - let mut last_op: Option = None; - // filter out 0 length read operations - let mut op_iter = operations - .filter(|op| op.is_write() || !op.is_empty()) - .peekable(); - - while let Some(op) = op_iter.next() { - let next_op = op_iter.peek().map(|v| v.kind()); - let kind = op.kind(); - // Clear all I2C interrupts - self.driver().clear_all_interrupts(); - - let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - match op { - Operation::Write(buffer) => { - // execute a write operation: - // - issue START/RSTART if op is different from previous - // - issue STOP if op is the last one - self.driver() - .write_operation( - address, - buffer, - !matches!(last_op, Some(OpKind::Write)), - next_op.is_none(), - cmd_iterator, - ) - .await?; - } - Operation::Read(buffer) => { - // execute a read operation: - // - issue START/RSTART if op is different from previous - // - issue STOP if op is the last one - // - will_continue is true if there is another read operation next - self.driver() - .read_operation( - address, - buffer, - !matches!(last_op, Some(OpKind::Read)), - next_op.is_none(), - matches!(next_op, Some(OpKind::Read)), - cmd_iterator, - ) - .await?; - } - } - - last_op = Some(kind); - } - - Ok(()) - } -} - -fn async_handler(info: &Info, state: &State) { - let regs = info.register_block(); - regs.int_ena().modify(|_, w| { - w.end_detect().clear_bit(); - w.trans_complete().clear_bit(); - w.arbitration_lost().clear_bit(); - w.time_out().clear_bit(); - - #[cfg(not(any(esp32, esp32s2)))] - w.txfifo_wm().clear_bit(); - - cfg_if::cfg_if! { - if #[cfg(esp32)] { - w.ack_err().clear_bit() - } else { - w.nack().clear_bit() - } - } - }); - - state.waker.wake(); -} - -#[allow(clippy::too_many_arguments, unused)] -/// Configures the clock and timing parameters for the I2C peripheral. -fn configure_clock( - register_block: &RegisterBlock, - sclk_div: u32, - scl_low_period: u32, - scl_high_period: u32, - scl_wait_high_period: u32, - sda_hold_time: u32, - sda_sample_time: u32, - scl_rstart_setup_time: u32, - scl_stop_setup_time: u32, - scl_start_hold_time: u32, - scl_stop_hold_time: u32, - timeout: Option, -) { - unsafe { - // divider - #[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2, esp32s3))] - register_block.clk_conf().modify(|_, w| { - w.sclk_sel().clear_bit(); - w.sclk_div_num().bits((sclk_div - 1) as u8) - }); - - // scl period - register_block - .scl_low_period() - .write(|w| w.scl_low_period().bits(scl_low_period as u16)); - - register_block.scl_high_period().write(|w| { - #[cfg(not(esp32))] // ESP32 does not have a wait_high field - w.scl_wait_high_period() - .bits(scl_wait_high_period.try_into().unwrap()); - w.scl_high_period().bits(scl_high_period as u16) - }); - - // sda sample - register_block - .sda_hold() - .write(|w| w.time().bits(sda_hold_time as u16)); - register_block - .sda_sample() - .write(|w| w.time().bits(sda_sample_time as u16)); - - // setup - register_block - .scl_rstart_setup() - .write(|w| w.time().bits(scl_rstart_setup_time as u16)); - register_block - .scl_stop_setup() - .write(|w| w.time().bits(scl_stop_setup_time as u16)); - - // hold - register_block - .scl_start_hold() - .write(|w| w.time().bits(scl_start_hold_time as u16)); - register_block - .scl_stop_hold() - .write(|w| w.time().bits(scl_stop_hold_time as u16)); - - // The ESP32 variant does not have an enable flag for the - // timeout mechanism - cfg_if::cfg_if! { - if #[cfg(esp32)] { - register_block - .to() - .write(|w| w.time_out().bits(unwrap!(timeout))); - } else { - register_block - .to() - .write(|w| w.time_out_en().bit(timeout.is_some()) - .time_out_value() - .bits(timeout.unwrap_or(1) as _) - ); - } - } - } -} - -/// Peripheral data describing a particular I2C instance. -pub struct Info { - /// Pointer to the register block for this I2C instance. - /// - /// Use [Self::register_block] to access the register block. - pub register_block: *const RegisterBlock, - - /// Interrupt handler for the asynchronous operations of this I2C instance. - pub async_handler: InterruptHandler, - - /// Interrupt for this I2C instance. - pub interrupt: Interrupt, - - /// SCL output signal. - pub scl_output: OutputSignal, - - /// SCL input signal. - pub scl_input: InputSignal, - - /// SDA output signal. - pub sda_output: OutputSignal, - - /// SDA input signal. - pub sda_input: InputSignal, -} - -impl Info { - /// Returns the register block for this I2C instance. - pub fn register_block(&self) -> &RegisterBlock { - unsafe { &*self.register_block } - } -} - -#[allow(dead_code)] // Some versions don't need `state` -struct Driver<'a> { - info: &'a Info, - state: &'a State, -} - -impl Driver<'_> { - /// Configures the I2C peripheral with the specified frequency, clocks, and - /// optional timeout. - fn setup(&self, config: &Config) { - self.info.register_block().ctr().write(|w| { - // Set I2C controller to master mode - w.ms_mode().set_bit(); - // Use open drain output for SDA and SCL - w.sda_force_out().set_bit(); - w.scl_force_out().set_bit(); - // Use Most Significant Bit first for sending and receiving data - w.tx_lsb_first().clear_bit(); - w.rx_lsb_first().clear_bit(); - // Ensure that clock is enabled - #[cfg(esp32s2)] - w.ref_always_on().set_bit(); - w.clk_en().set_bit() - }); - - // Configure filter - // FIXME if we ever change this we need to adapt `set_frequency` for ESP32 - self.set_filter(Some(7), Some(7)); - - // Configure frequency - self.set_frequency(config.frequency, config.timeout); - - self.update_config(); - - // Reset entire peripheral (also resets fifo) - self.reset(); - } - - /// Resets the I2C peripheral's command registers - fn reset_command_list(&self) { - // Confirm that all commands that were configured were actually executed - for cmd in self.info.register_block().comd_iter() { - cmd.reset(); - } - } - - /// Configures the I2C peripheral for a write operation. - /// - `addr` is the address of the slave device. - /// - `bytes` is the data two be sent. - /// - `start` indicates whether the operation should start by a START - /// condition and sending the address. - /// - `cmd_iterator` is an iterator over the command registers. - fn setup_write<'a, I>( - &self, - addr: u8, - bytes: &[u8], - start: bool, - cmd_iterator: &mut I, - ) -> Result<(), Error> - where - I: Iterator, - { - // if start is true we can only send 254 additional bytes with the address as - // the first - let max_len = if start { 254usize } else { 255usize }; - if bytes.len() > max_len { - // we could support more by adding multiple write operations - return Err(Error::ExceedingFifo); - } - - let write_len = if start { bytes.len() + 1 } else { bytes.len() }; - // don't issue write if there is no data to write - if write_len > 0 { - // WRITE command - self.add_cmd( - cmd_iterator, - Command::Write { - ack_exp: Ack::Ack, - ack_check_en: true, - length: write_len as u8, - }, - )?; - } - - self.update_config(); - - if start { - // Load address and R/W bit into FIFO - self.write_fifo(addr << 1 | OperationType::Write as u8); - } - Ok(()) - } - - /// Configures the I2C peripheral for a read operation. - /// - `addr` is the address of the slave device. - /// - `buffer` is the buffer to store the read data. - /// - `start` indicates whether the operation should start by a START - /// condition and sending the address. - /// - `will_continue` indicates whether there is another read operation - /// following this one and we should not nack the last byte. - /// - `cmd_iterator` is an iterator over the command registers. - fn setup_read<'a, I>( - &self, - addr: u8, - buffer: &mut [u8], - start: bool, - will_continue: bool, - cmd_iterator: &mut I, - ) -> Result<(), Error> - where - I: Iterator, - { - if buffer.is_empty() { - return Err(Error::InvalidZeroLength); - } - let (max_len, initial_len) = if will_continue { - (255usize, buffer.len()) - } else { - (254usize, buffer.len() - 1) - }; - if buffer.len() > max_len { - // we could support more by adding multiple read operations - return Err(Error::ExceedingFifo); - } - - if start { - // WRITE command - self.add_cmd( - cmd_iterator, - Command::Write { - ack_exp: Ack::Ack, - ack_check_en: true, - length: 1, - }, - )?; - } - - if initial_len > 0 { - // READ command - self.add_cmd( - cmd_iterator, - Command::Read { - ack_value: Ack::Ack, - length: initial_len as u8, - }, - )?; - } - - if !will_continue { - // this is the last read so we need to nack the last byte - // READ w/o ACK - self.add_cmd( - cmd_iterator, - Command::Read { - ack_value: Ack::Nack, - length: 1, - }, - )?; - } - - self.update_config(); - - if start { - // Load address and R/W bit into FIFO - self.write_fifo(addr << 1 | OperationType::Read as u8); - } - Ok(()) - } - - /// Clears all pending interrupts for the I2C peripheral. - fn clear_all_interrupts(&self) { - self.info - .register_block() - .int_clr() - .write(|w| unsafe { w.bits(I2C_LL_INTR_MASK) }); - } - - /// Waits for the completion of an I2C transaction. - fn wait_for_completion_blocking(&self, end_only: bool) -> Result<(), Error> { - let mut tout = MAX_ITERATIONS; - loop { - let interrupts = self.info.register_block().int_raw().read(); - - self.check_errors()?; - - // Handle completion cases - // A full transmission was completed (either a STOP condition or END was - // processed) - if (!end_only && interrupts.trans_complete().bit_is_set()) - || interrupts.end_detect().bit_is_set() - { - break; - } - - tout -= 1; - if tout == 0 { - return Err(Error::TimeOut); - } - } - self.check_all_commands_done()?; - Ok(()) - } - - /// Checks whether all I2C commands have completed execution. - fn check_all_commands_done(&self) -> Result<(), Error> { - // NOTE: on esp32 executing the end command generates the end_detect interrupt - // but does not seem to clear the done bit! So we don't check the done - // status of an end command - for cmd_reg in self.info.register_block().comd_iter() { - let cmd = cmd_reg.read(); - - if cmd.bits() != 0x0 && !cmd.opcode().is_end() && !cmd.command_done().bit_is_set() { - return Err(Error::ExecIncomplete); - } - } - - Ok(()) - } - - /// Starts an I2C transmission. - fn start_transmission(&self) { - // Start transmission - self.info - .register_block() - .ctr() - .modify(|_, w| w.trans_start().set_bit()); - } - - /// Executes an I2C write operation. - /// - `addr` is the address of the slave device. - /// - `bytes` is the data two be sent. - /// - `start` indicates whether the operation should start by a START - /// condition and sending the address. - /// - `stop` indicates whether the operation should end with a STOP - /// condition. - /// - `cmd_iterator` is an iterator over the command registers. - fn write_operation_blocking<'a>( - &self, - address: u8, - bytes: &[u8], - start: bool, - stop: bool, - cmd_iterator: &mut impl Iterator, - ) -> Result<(), Error> { - // Short circuit for zero length writes without start or end as that would be an - // invalid operation write lengths in the TRM (at least for ESP32-S3) are 1-255 - if bytes.is_empty() && !start && !stop { - return Ok(()); - } - - let index = self.start_write_operation(address, bytes, start, stop, cmd_iterator)?; - - // Fill the FIFO with the remaining bytes: - self.write_remaining_tx_fifo_blocking(index, bytes)?; - self.wait_for_completion_blocking(!stop)?; - Ok(()) - } - - /// Executes an I2C write operation. - /// - `addr` is the address of the slave device. - /// - `bytes` is the data two be sent. - /// - `start` indicates whether the operation should start by a START - /// condition and sending the address. - /// - `stop` indicates whether the operation should end with a STOP - /// condition. - /// - `cmd_iterator` is an iterator over the command registers. - async fn write_operation<'a>( - &self, - address: u8, - bytes: &[u8], - start: bool, - stop: bool, - cmd_iterator: &mut impl Iterator, - ) -> Result<(), Error> { - // Short circuit for zero length writes without start or end as that would be an - // invalid operation write lengths in the TRM (at least for ESP32-S3) are 1-255 - if bytes.is_empty() && !start && !stop { - return Ok(()); - } - - let index = self.start_write_operation(address, bytes, start, stop, cmd_iterator)?; - - // Fill the FIFO with the remaining bytes: - self.write_remaining_tx_fifo(index, bytes).await?; - self.wait_for_completion(!stop).await?; - Ok(()) - } - - /// Executes an I2C read operation. - /// - `addr` is the address of the slave device. - /// - `buffer` is the buffer to store the read data. - /// - `start` indicates whether the operation should start by a START - /// condition and sending the address. - /// - `stop` indicates whether the operation should end with a STOP - /// condition. - /// - `will_continue` indicates whether there is another read operation - /// following this one and we should not nack the last byte. - /// - `cmd_iterator` is an iterator over the command registers. - fn read_operation_blocking<'a>( - &self, - address: u8, - buffer: &mut [u8], - start: bool, - stop: bool, - will_continue: bool, - cmd_iterator: &mut impl Iterator, - ) -> Result<(), Error> { - // Short circuit for zero length reads as that would be an invalid operation - // read lengths in the TRM (at least for ESP32-S3) are 1-255 - if buffer.is_empty() { - return Ok(()); - } - - self.start_read_operation(address, buffer, start, stop, will_continue, cmd_iterator)?; - self.read_all_from_fifo_blocking(buffer)?; - self.wait_for_completion_blocking(!stop)?; - Ok(()) - } - - /// Executes an async I2C read operation. - /// - `addr` is the address of the slave device. - /// - `buffer` is the buffer to store the read data. - /// - `start` indicates whether the operation should start by a START - /// condition and sending the address. - /// - `stop` indicates whether the operation should end with a STOP - /// condition. - /// - `will_continue` indicates whether there is another read operation - /// following this one and we should not nack the last byte. - /// - `cmd_iterator` is an iterator over the command registers. - async fn read_operation<'a>( - &self, - address: u8, - buffer: &mut [u8], - start: bool, - stop: bool, - will_continue: bool, - cmd_iterator: &mut impl Iterator, - ) -> Result<(), Error> { - // Short circuit for zero length reads as that would be an invalid operation - // read lengths in the TRM (at least for ESP32-S3) are 1-255 - if buffer.is_empty() { - return Ok(()); - } - - self.start_read_operation(address, buffer, start, stop, will_continue, cmd_iterator)?; - self.read_all_from_fifo(buffer).await?; - self.wait_for_completion(!stop).await?; - Ok(()) - } - - fn start_read_operation<'a>( - &self, - address: u8, - buffer: &mut [u8], - start: bool, - stop: bool, - will_continue: bool, - cmd_iterator: &mut impl Iterator, - ) -> Result<(), Error> { - // Reset FIFO and command list - self.reset_fifo(); - self.reset_command_list(); - - if start { - self.add_cmd(cmd_iterator, Command::Start)?; - } - - self.setup_read(address, buffer, start, will_continue, cmd_iterator)?; - - self.add_cmd( - cmd_iterator, - if stop { Command::Stop } else { Command::End }, - )?; - self.start_transmission(); - - Ok(()) - } - - fn start_write_operation<'a>( - &self, - address: u8, - bytes: &[u8], - start: bool, - stop: bool, - cmd_iterator: &mut impl Iterator, - ) -> Result { - // Reset FIFO and command list - self.reset_fifo(); - self.reset_command_list(); - - if start { - self.add_cmd(cmd_iterator, Command::Start)?; - } - self.setup_write(address, bytes, start, cmd_iterator)?; - self.add_cmd( - cmd_iterator, - if stop { Command::Stop } else { Command::End }, - )?; - let index = self.fill_tx_fifo(bytes); - self.start_transmission(); - - Ok(index) - } - - /// Adds a command to the I2C command sequence. - fn add_cmd<'a, I>(&self, cmd_iterator: &mut I, command: Command) -> Result<(), Error> - where - I: Iterator, - { - let cmd = cmd_iterator.next().ok_or(Error::CommandNrExceeded)?; - - cmd.write(|w| match command { - Command::Start => w.opcode().rstart(), - Command::Stop => w.opcode().stop(), - Command::End => w.opcode().end(), - Command::Write { - ack_exp, - ack_check_en, - length, - } => unsafe { - w.opcode().write(); - w.ack_exp().bit(ack_exp == Ack::Nack); - w.ack_check_en().bit(ack_check_en); - w.byte_num().bits(length); - w - }, - Command::Read { ack_value, length } => unsafe { - w.opcode().read(); - w.ack_value().bit(ack_value == Ack::Nack); - w.byte_num().bits(length); - w - }, - }); - - Ok(()) - } -} - -impl PartialEq for Info { - fn eq(&self, other: &Self) -> bool { - self.register_block == other.register_block - } -} - -unsafe impl Sync for Info {} - -/// Peripheral state for an I2C instance. -pub struct State { - /// Waker for the asynchronous operations. - pub waker: AtomicWaker, -} - -/// I2C Peripheral Instance -#[doc(hidden)] -pub trait Instance: Peripheral

+ PeripheralMarker + Into + 'static { - /// Returns the peripheral data and state describing this instance. - fn parts(&self) -> (&Info, &State); - - /// Returns the peripheral data describing this instance. - #[inline(always)] - fn info(&self) -> &Info { - self.parts().0 - } - - /// Returns the peripheral state for this instance. - #[inline(always)] - fn state(&self) -> &State { - self.parts().1 - } -} - -macro_rules! instance { - ($inst:ident, $scl:ident, $sda:ident, $interrupt:ident) => { - impl Instance for crate::peripherals::$inst { - fn parts(&self) -> (&Info, &State) { - #[crate::macros::handler] - pub(super) fn irq_handler() { - async_handler(&PERIPHERAL, &STATE); - } - - static STATE: State = State { - waker: AtomicWaker::new(), - }; - - static PERIPHERAL: Info = Info { - register_block: crate::peripherals::$inst::ptr(), - async_handler: irq_handler, - interrupt: Interrupt::$interrupt, - scl_output: OutputSignal::$scl, - scl_input: InputSignal::$scl, - sda_output: OutputSignal::$sda, - sda_input: InputSignal::$sda, - }; - (&PERIPHERAL, &STATE) - } - } - }; -} - -#[cfg(i2c0)] -instance!(I2C0, I2CEXT0_SCL, I2CEXT0_SDA, I2C_EXT0); -#[cfg(i2c1)] -instance!(I2C1, I2CEXT1_SCL, I2CEXT1_SDA, I2C_EXT1); - -crate::any_peripheral! { - /// Represents any I2C peripheral. - pub peripheral AnyI2c { - #[cfg(i2c0)] - I2c0(crate::peripherals::I2C0), - #[cfg(i2c1)] - I2c1(crate::peripherals::I2C1), - } -} - -impl Instance for AnyI2c { - delegate::delegate! { - to match &self.0 { - AnyI2cInner::I2c0(i2c) => i2c, - #[cfg(i2c1)] - AnyI2cInner::I2c1(i2c) => i2c, - } { - fn parts(&self) -> (&Info, &State); - } - } -} -#[cfg(lp_i2c0)] -pub mod lp_i2c; +pub mod master; diff --git a/esp-hal/src/prelude.rs b/esp-hal/src/prelude.rs index 06dd5855cad..5a4d41a8505 100644 --- a/esp-hal/src/prelude.rs +++ b/esp-hal/src/prelude.rs @@ -26,7 +26,7 @@ mod imp { Pin as _esp_hal_gpio_Pin, }; #[cfg(any(i2c0, i2c1))] - pub use crate::i2c::Instance as _esp_hal_i2c_Instance; + pub use crate::i2c::master::Instance as _esp_hal_i2c_master_Instance; #[cfg(ledc)] pub use crate::ledc::{ channel::{ diff --git a/examples/src/bin/embassy_i2c.rs b/examples/src/bin/embassy_i2c.rs index f0fe41b8673..3f9604fcfdb 100644 --- a/examples/src/bin/embassy_i2c.rs +++ b/examples/src/bin/embassy_i2c.rs @@ -19,7 +19,7 @@ use embassy_executor::Spawner; use embassy_time::{Duration, Timer}; use esp_backtrace as _; -use esp_hal::{gpio::Io, i2c::I2c, prelude::*, timer::timg::TimerGroup}; +use esp_hal::{gpio::Io, i2c::master::I2c, prelude::*, timer::timg::TimerGroup}; use lis3dh_async::{Lis3dh, Range, SlaveAddr}; #[esp_hal_embassy::main] diff --git a/examples/src/bin/embassy_i2c_bmp180_calibration_data.rs b/examples/src/bin/embassy_i2c_bmp180_calibration_data.rs index fbc5de7a950..a3e6cf982c9 100644 --- a/examples/src/bin/embassy_i2c_bmp180_calibration_data.rs +++ b/examples/src/bin/embassy_i2c_bmp180_calibration_data.rs @@ -20,7 +20,7 @@ use embassy_executor::Spawner; use embassy_time::{Duration, Timer}; use esp_backtrace as _; -use esp_hal::{gpio::Io, i2c::I2c, prelude::*, timer::timg::TimerGroup}; +use esp_hal::{gpio::Io, i2c::master::I2c, prelude::*, timer::timg::TimerGroup}; #[esp_hal_embassy::main] async fn main(_spawner: Spawner) { diff --git a/examples/src/bin/i2c_bmp180_calibration_data.rs b/examples/src/bin/i2c_bmp180_calibration_data.rs index 86f4ea7b94d..16da0f9d21a 100644 --- a/examples/src/bin/i2c_bmp180_calibration_data.rs +++ b/examples/src/bin/i2c_bmp180_calibration_data.rs @@ -12,7 +12,7 @@ #![no_main] use esp_backtrace as _; -use esp_hal::{gpio::Io, i2c::I2c, prelude::*}; +use esp_hal::{gpio::Io, i2c::master::I2c, prelude::*}; use esp_println::println; #[entry] diff --git a/examples/src/bin/i2c_display.rs b/examples/src/bin/i2c_display.rs index 72821a4808d..4b94a59be7d 100644 --- a/examples/src/bin/i2c_display.rs +++ b/examples/src/bin/i2c_display.rs @@ -22,7 +22,7 @@ use embedded_graphics::{ text::{Alignment, Text}, }; use esp_backtrace as _; -use esp_hal::{delay::Delay, gpio::Io, i2c::I2c, prelude::*}; +use esp_hal::{delay::Delay, gpio::Io, i2c::master::I2c, prelude::*}; use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306}; #[entry] diff --git a/examples/src/bin/lcd_cam_ov2640.rs b/examples/src/bin/lcd_cam_ov2640.rs index f3dd74cc518..9cfa431e940 100644 --- a/examples/src/bin/lcd_cam_ov2640.rs +++ b/examples/src/bin/lcd_cam_ov2640.rs @@ -29,8 +29,7 @@ use esp_hal::{ dma::{Dma, DmaPriority}, dma_rx_stream_buffer, gpio::Io, - i2c, - i2c::I2c, + i2c::{self, master::I2c}, lcd_cam::{ cam::{Camera, RxEightBits}, LcdCam, @@ -172,17 +171,17 @@ pub struct Sccb<'d, T> { impl<'d, T> Sccb<'d, T> where - T: i2c::Instance, + T: i2c::master::Instance, { pub fn new(i2c: I2c<'d, Blocking, T>) -> Self { Self { i2c } } - pub fn probe(&mut self, address: u8) -> Result<(), i2c::Error> { + pub fn probe(&mut self, address: u8) -> Result<(), i2c::master::Error> { self.i2c.write(address, &[]) } - pub fn read(&mut self, address: u8, reg: u8) -> Result { + pub fn read(&mut self, address: u8, reg: u8) -> Result { self.i2c.write(address, &[reg])?; let mut bytes = [0u8; 1]; @@ -190,7 +189,7 @@ where Ok(bytes[0]) } - pub fn write(&mut self, address: u8, reg: u8, data: u8) -> Result<(), i2c::Error> { + pub fn write(&mut self, address: u8, reg: u8, data: u8) -> Result<(), i2c::master::Error> { self.i2c.write(address, &[reg, data]) } } diff --git a/hil-test/tests/i2c.rs b/hil-test/tests/i2c.rs index 9cf9991764d..46d493eed49 100644 --- a/hil-test/tests/i2c.rs +++ b/hil-test/tests/i2c.rs @@ -7,7 +7,7 @@ use esp_hal::{ gpio::Io, - i2c::{I2c, Operation}, + i2c::master::{I2c, Operation}, Async, Blocking, }; From bc9a7bf91c21c670f892287b236d9a5fdd831d39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 4 Nov 2024 17:49:21 +0100 Subject: [PATCH 11/18] Fix examples, move lp i2c back to i2c root --- esp-hal/src/i2c/{master => }/lp_i2c.rs | 0 esp-hal/src/i2c/master/mod.rs | 3 --- esp-hal/src/i2c/mod.rs | 3 +++ examples/src/bin/embassy_i2c.rs | 2 +- examples/src/bin/embassy_i2c_bmp180_calibration_data.rs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename esp-hal/src/i2c/{master => }/lp_i2c.rs (100%) diff --git a/esp-hal/src/i2c/master/lp_i2c.rs b/esp-hal/src/i2c/lp_i2c.rs similarity index 100% rename from esp-hal/src/i2c/master/lp_i2c.rs rename to esp-hal/src/i2c/lp_i2c.rs diff --git a/esp-hal/src/i2c/master/mod.rs b/esp-hal/src/i2c/master/mod.rs index d55a2327a03..1fcdf26f856 100644 --- a/esp-hal/src/i2c/master/mod.rs +++ b/esp-hal/src/i2c/master/mod.rs @@ -1434,6 +1434,3 @@ impl Instance for AnyI2c { } } } - -#[cfg(lp_i2c0)] -pub mod lp_i2c; diff --git a/esp-hal/src/i2c/mod.rs b/esp-hal/src/i2c/mod.rs index 0500f1eeb7d..69d84bc5576 100644 --- a/esp-hal/src/i2c/mod.rs +++ b/esp-hal/src/i2c/mod.rs @@ -6,3 +6,6 @@ //! (SDA) and serial clock line (SCL), pulled up by resistors. pub mod master; + +#[cfg(lp_i2c0)] +pub mod lp_i2c; diff --git a/examples/src/bin/embassy_i2c.rs b/examples/src/bin/embassy_i2c.rs index 3f9604fcfdb..5ec0d529c84 100644 --- a/examples/src/bin/embassy_i2c.rs +++ b/examples/src/bin/embassy_i2c.rs @@ -19,7 +19,7 @@ use embassy_executor::Spawner; use embassy_time::{Duration, Timer}; use esp_backtrace as _; -use esp_hal::{gpio::Io, i2c::master::I2c, prelude::*, timer::timg::TimerGroup}; +use esp_hal::{gpio::Io, i2c::master::I2c, timer::timg::TimerGroup}; use lis3dh_async::{Lis3dh, Range, SlaveAddr}; #[esp_hal_embassy::main] diff --git a/examples/src/bin/embassy_i2c_bmp180_calibration_data.rs b/examples/src/bin/embassy_i2c_bmp180_calibration_data.rs index a3e6cf982c9..4e135dd4938 100644 --- a/examples/src/bin/embassy_i2c_bmp180_calibration_data.rs +++ b/examples/src/bin/embassy_i2c_bmp180_calibration_data.rs @@ -20,7 +20,7 @@ use embassy_executor::Spawner; use embassy_time::{Duration, Timer}; use esp_backtrace as _; -use esp_hal::{gpio::Io, i2c::master::I2c, prelude::*, timer::timg::TimerGroup}; +use esp_hal::{gpio::Io, i2c::master::I2c, timer::timg::TimerGroup}; #[esp_hal_embassy::main] async fn main(_spawner: Spawner) { From d769311847c9bafe5859ffc827423a2d6578e0d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 4 Nov 2024 20:57:56 +0100 Subject: [PATCH 12/18] Clean up --- esp-hal/src/i2c/master/mod.rs | 94 +++++------------------------------ 1 file changed, 12 insertions(+), 82 deletions(-) diff --git a/esp-hal/src/i2c/master/mod.rs b/esp-hal/src/i2c/master/mod.rs index 1fcdf26f856..2a32ad1dfcc 100644 --- a/esp-hal/src/i2c/master/mod.rs +++ b/esp-hal/src/i2c/master/mod.rs @@ -295,10 +295,7 @@ where while let Some(op) = op_iter.next() { let next_op = op_iter.peek().map(|v| v.kind()); let kind = op.kind(); - // Clear all I2C interrupts - self.driver().clear_all_interrupts(); - let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); match op { Operation::Write(buffer) => { // execute a write operation: @@ -310,7 +307,6 @@ where buffer, !matches!(last_op, Some(OpKind::Write)), next_op.is_none(), - cmd_iterator, ) .inspect_err(|_| self.internal_recover())?; } @@ -326,7 +322,6 @@ where !matches!(last_op, Some(OpKind::Read)), next_op.is_none(), matches!(next_op, Some(OpKind::Read)), - cmd_iterator, ) .inspect_err(|_| self.internal_recover())?; } @@ -442,11 +437,6 @@ where pub fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); for (idx, chunk) in buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { - // Clear all I2C interrupts - self.driver().clear_all_interrupts(); - - let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - self.driver() .read_operation_blocking( address, @@ -454,7 +444,6 @@ where idx == 0, idx == chunk_count - 1, idx < chunk_count - 1, - cmd_iterator, ) .inspect_err(|_| self.internal_recover())?; } @@ -466,19 +455,8 @@ where pub fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); for (idx, chunk) in buffer.chunks(I2C_CHUNK_SIZE).enumerate() { - // Clear all I2C interrupts - self.driver().clear_all_interrupts(); - - let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - self.driver() - .write_operation_blocking( - address, - chunk, - idx == 0, - idx == chunk_count - 1, - cmd_iterator, - ) + .write_operation_blocking(address, chunk, idx == 0, idx == chunk_count - 1) .inspect_err(|_| self.internal_recover())?; } @@ -497,28 +475,17 @@ where let read_count = read_buffer.len().div_ceil(I2C_CHUNK_SIZE); for (idx, chunk) in write_buffer.chunks(I2C_CHUNK_SIZE).enumerate() { - // Clear all I2C interrupts - self.driver().clear_all_interrupts(); - - let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - self.driver() .write_operation_blocking( address, chunk, idx == 0, idx == write_count - 1 && read_count == 0, - cmd_iterator, ) .inspect_err(|_| self.internal_recover())?; } for (idx, chunk) in read_buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { - // Clear all I2C interrupts - self.driver().clear_all_interrupts(); - - let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - self.driver() .read_operation_blocking( address, @@ -526,7 +493,6 @@ where idx == 0, idx == read_count - 1, idx < read_count - 1, - cmd_iterator, ) .inspect_err(|_| self.internal_recover())?; } @@ -597,19 +563,8 @@ where pub async fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); for (idx, chunk) in buffer.chunks(I2C_CHUNK_SIZE).enumerate() { - // Clear all I2C interrupts - self.driver().clear_all_interrupts(); - - let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - self.driver() - .write_operation( - address, - chunk, - idx == 0, - idx == chunk_count - 1, - cmd_iterator, - ) + .write_operation(address, chunk, idx == 0, idx == chunk_count - 1) .await?; } @@ -620,11 +575,6 @@ where pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); for (idx, chunk) in buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { - // Clear all I2C interrupts - self.driver().clear_all_interrupts(); - - let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - self.driver() .read_operation( address, @@ -632,7 +582,6 @@ where idx == 0, idx == chunk_count - 1, idx < chunk_count - 1, - cmd_iterator, ) .await?; } @@ -651,28 +600,17 @@ where let write_count = write_buffer.len().div_ceil(I2C_CHUNK_SIZE); let read_count = read_buffer.len().div_ceil(I2C_CHUNK_SIZE); for (idx, chunk) in write_buffer.chunks(I2C_CHUNK_SIZE).enumerate() { - // Clear all I2C interrupts - self.driver().clear_all_interrupts(); - - let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - self.driver() .write_operation( address, chunk, idx == 0, idx == write_count - 1 && read_count == 0, - cmd_iterator, ) .await?; } for (idx, chunk) in read_buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { - // Clear all I2C interrupts - self.driver().clear_all_interrupts(); - - let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); - self.driver() .read_operation( address, @@ -680,7 +618,6 @@ where idx == 0, idx == read_count - 1, idx < read_count - 1, - cmd_iterator, ) .await?; } @@ -730,10 +667,7 @@ where while let Some(op) = op_iter.next() { let next_op = op_iter.peek().map(|v| v.kind()); let kind = op.kind(); - // Clear all I2C interrupts - self.driver().clear_all_interrupts(); - let cmd_iterator = &mut self.driver().info.register_block().comd_iter(); match op { Operation::Write(buffer) => { // execute a write operation: @@ -745,7 +679,6 @@ where buffer, !matches!(last_op, Some(OpKind::Write)), next_op.is_none(), - cmd_iterator, ) .await?; } @@ -761,7 +694,6 @@ where !matches!(last_op, Some(OpKind::Read)), next_op.is_none(), matches!(next_op, Some(OpKind::Read)), - cmd_iterator, ) .await?; } @@ -1144,21 +1076,21 @@ impl Driver<'_> { /// condition and sending the address. /// - `stop` indicates whether the operation should end with a STOP /// condition. - /// - `cmd_iterator` is an iterator over the command registers. - fn write_operation_blocking<'a>( + fn write_operation_blocking( &self, address: u8, bytes: &[u8], start: bool, stop: bool, - cmd_iterator: &mut impl Iterator, ) -> Result<(), Error> { + self.clear_all_interrupts(); // Short circuit for zero length writes without start or end as that would be an // invalid operation write lengths in the TRM (at least for ESP32-S3) are 1-255 if bytes.is_empty() && !start && !stop { return Ok(()); } + let cmd_iterator = &mut self.info.register_block().comd_iter(); let index = self.start_write_operation(address, bytes, start, stop, cmd_iterator)?; // Fill the FIFO with the remaining bytes: @@ -1174,14 +1106,12 @@ impl Driver<'_> { /// condition and sending the address. /// - `stop` indicates whether the operation should end with a STOP /// condition. - /// - `cmd_iterator` is an iterator over the command registers. - async fn write_operation<'a>( + async fn write_operation( &self, address: u8, bytes: &[u8], start: bool, stop: bool, - cmd_iterator: &mut impl Iterator, ) -> Result<(), Error> { // Short circuit for zero length writes without start or end as that would be an // invalid operation write lengths in the TRM (at least for ESP32-S3) are 1-255 @@ -1189,6 +1119,7 @@ impl Driver<'_> { return Ok(()); } + let cmd_iterator = &mut self.info.register_block().comd_iter(); let index = self.start_write_operation(address, bytes, start, stop, cmd_iterator)?; // Fill the FIFO with the remaining bytes: @@ -1206,22 +1137,22 @@ impl Driver<'_> { /// condition. /// - `will_continue` indicates whether there is another read operation /// following this one and we should not nack the last byte. - /// - `cmd_iterator` is an iterator over the command registers. - fn read_operation_blocking<'a>( + fn read_operation_blocking( &self, address: u8, buffer: &mut [u8], start: bool, stop: bool, will_continue: bool, - cmd_iterator: &mut impl Iterator, ) -> Result<(), Error> { + self.clear_all_interrupts(); // Short circuit for zero length reads as that would be an invalid operation // read lengths in the TRM (at least for ESP32-S3) are 1-255 if buffer.is_empty() { return Ok(()); } + let cmd_iterator = &mut self.info.register_block().comd_iter(); self.start_read_operation(address, buffer, start, stop, will_continue, cmd_iterator)?; self.read_all_from_fifo_blocking(buffer)?; self.wait_for_completion_blocking(!stop)?; @@ -1237,15 +1168,13 @@ impl Driver<'_> { /// condition. /// - `will_continue` indicates whether there is another read operation /// following this one and we should not nack the last byte. - /// - `cmd_iterator` is an iterator over the command registers. - async fn read_operation<'a>( + async fn read_operation( &self, address: u8, buffer: &mut [u8], start: bool, stop: bool, will_continue: bool, - cmd_iterator: &mut impl Iterator, ) -> Result<(), Error> { // Short circuit for zero length reads as that would be an invalid operation // read lengths in the TRM (at least for ESP32-S3) are 1-255 @@ -1253,6 +1182,7 @@ impl Driver<'_> { return Ok(()); } + let cmd_iterator = &mut self.info.register_block().comd_iter(); self.start_read_operation(address, buffer, start, stop, will_continue, cmd_iterator)?; self.read_all_from_fifo(buffer).await?; self.wait_for_completion(!stop).await?; From 93e915c64a255a2a567fe8a16731d0569500bd0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 4 Nov 2024 21:07:02 +0100 Subject: [PATCH 13/18] Chunk transfers in transaction fns, clean up further --- esp-hal/src/i2c/master/mod.rs | 104 +++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 46 deletions(-) diff --git a/esp-hal/src/i2c/master/mod.rs b/esp-hal/src/i2c/master/mod.rs index 2a32ad1dfcc..440851e5d31 100644 --- a/esp-hal/src/i2c/master/mod.rs +++ b/esp-hal/src/i2c/master/mod.rs @@ -301,29 +301,37 @@ where // execute a write operation: // - issue START/RSTART if op is different from previous // - issue STOP if op is the last one - self.driver() - .write_operation_blocking( - address, - buffer, - !matches!(last_op, Some(OpKind::Write)), - next_op.is_none(), - ) - .inspect_err(|_| self.internal_recover())?; + let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); + for (idx, chunk) in buffer.chunks(I2C_CHUNK_SIZE).enumerate() { + let last_chunk = idx == chunk_count - 1; + self.driver() + .write_operation_blocking( + address, + chunk, + idx == 0 && !matches!(last_op, Some(OpKind::Write)), + last_chunk && next_op.is_none(), + ) + .inspect_err(|_| self.internal_recover())?; + } } Operation::Read(buffer) => { // execute a read operation: // - issue START/RSTART if op is different from previous // - issue STOP if op is the last one // - will_continue is true if there is another read operation next - self.driver() - .read_operation_blocking( - address, - buffer, - !matches!(last_op, Some(OpKind::Read)), - next_op.is_none(), - matches!(next_op, Some(OpKind::Read)), - ) - .inspect_err(|_| self.internal_recover())?; + let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); + for (idx, chunk) in buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { + let last_chunk = idx == chunk_count - 1; + self.driver() + .read_operation_blocking( + address, + chunk, + idx == 0 && !matches!(last_op, Some(OpKind::Read)), + last_chunk && next_op.is_none(), + matches!(next_op, Some(OpKind::Read)) || !last_chunk, + ) + .inspect_err(|_| self.internal_recover())?; + } } } @@ -673,29 +681,37 @@ where // execute a write operation: // - issue START/RSTART if op is different from previous // - issue STOP if op is the last one - self.driver() - .write_operation( - address, - buffer, - !matches!(last_op, Some(OpKind::Write)), - next_op.is_none(), - ) - .await?; + let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); + for (idx, chunk) in buffer.chunks(I2C_CHUNK_SIZE).enumerate() { + let last_chunk = idx == chunk_count - 1; + self.driver() + .write_operation( + address, + chunk, + idx == 0 && !matches!(last_op, Some(OpKind::Write)), + last_chunk && next_op.is_none(), + ) + .await?; + } } Operation::Read(buffer) => { // execute a read operation: // - issue START/RSTART if op is different from previous // - issue STOP if op is the last one // - will_continue is true if there is another read operation next - self.driver() - .read_operation( - address, - buffer, - !matches!(last_op, Some(OpKind::Read)), - next_op.is_none(), - matches!(next_op, Some(OpKind::Read)), - ) - .await?; + let chunk_count = buffer.len().div_ceil(I2C_CHUNK_SIZE); + for (idx, chunk) in buffer.chunks_mut(I2C_CHUNK_SIZE).enumerate() { + let last_chunk = idx == chunk_count - 1; + self.driver() + .read_operation( + address, + chunk, + idx == 0 && !matches!(last_op, Some(OpKind::Read)), + last_chunk && next_op.is_none(), + matches!(next_op, Some(OpKind::Read)) || !last_chunk, + ) + .await?; + } } } @@ -1090,8 +1106,7 @@ impl Driver<'_> { return Ok(()); } - let cmd_iterator = &mut self.info.register_block().comd_iter(); - let index = self.start_write_operation(address, bytes, start, stop, cmd_iterator)?; + let index = self.start_write_operation(address, bytes, start, stop)?; // Fill the FIFO with the remaining bytes: self.write_remaining_tx_fifo_blocking(index, bytes)?; @@ -1119,8 +1134,7 @@ impl Driver<'_> { return Ok(()); } - let cmd_iterator = &mut self.info.register_block().comd_iter(); - let index = self.start_write_operation(address, bytes, start, stop, cmd_iterator)?; + let index = self.start_write_operation(address, bytes, start, stop)?; // Fill the FIFO with the remaining bytes: self.write_remaining_tx_fifo(index, bytes).await?; @@ -1152,8 +1166,7 @@ impl Driver<'_> { return Ok(()); } - let cmd_iterator = &mut self.info.register_block().comd_iter(); - self.start_read_operation(address, buffer, start, stop, will_continue, cmd_iterator)?; + self.start_read_operation(address, buffer, start, stop, will_continue)?; self.read_all_from_fifo_blocking(buffer)?; self.wait_for_completion_blocking(!stop)?; Ok(()) @@ -1182,25 +1195,24 @@ impl Driver<'_> { return Ok(()); } - let cmd_iterator = &mut self.info.register_block().comd_iter(); - self.start_read_operation(address, buffer, start, stop, will_continue, cmd_iterator)?; + self.start_read_operation(address, buffer, start, stop, will_continue)?; self.read_all_from_fifo(buffer).await?; self.wait_for_completion(!stop).await?; Ok(()) } - fn start_read_operation<'a>( + fn start_read_operation( &self, address: u8, buffer: &mut [u8], start: bool, stop: bool, will_continue: bool, - cmd_iterator: &mut impl Iterator, ) -> Result<(), Error> { // Reset FIFO and command list self.reset_fifo(); self.reset_command_list(); + let cmd_iterator = &mut self.info.register_block().comd_iter(); if start { self.add_cmd(cmd_iterator, Command::Start)?; @@ -1217,17 +1229,17 @@ impl Driver<'_> { Ok(()) } - fn start_write_operation<'a>( + fn start_write_operation( &self, address: u8, bytes: &[u8], start: bool, stop: bool, - cmd_iterator: &mut impl Iterator, ) -> Result { // Reset FIFO and command list self.reset_fifo(); self.reset_command_list(); + let cmd_iterator = &mut self.info.register_block().comd_iter(); if start { self.add_cmd(cmd_iterator, Command::Start)?; From 648f6ed3058b91a757a0dc4dd38e5c943cea419e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Wed, 6 Nov 2024 16:01:59 +0100 Subject: [PATCH 14/18] ConfigError --- esp-hal/src/i2c/master/mod.rs | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/esp-hal/src/i2c/master/mod.rs b/esp-hal/src/i2c/master/mod.rs index 440851e5d31..228578b940f 100644 --- a/esp-hal/src/i2c/master/mod.rs +++ b/esp-hal/src/i2c/master/mod.rs @@ -55,7 +55,7 @@ mod support; mod version; -use core::{convert::Infallible, marker::PhantomData}; +use core::marker::PhantomData; use embassy_embedded_hal::SetConfig; use embassy_sync::waitqueue::AtomicWaker; @@ -104,6 +104,11 @@ pub enum Error { InvalidZeroLength, } +/// I2C-specific configuration errors +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ConfigError {} + #[derive(PartialEq)] // This enum is used to keep track of the last/next operation that was/will be // performed in an embedded-hal(-async) I2c::transaction. It is used to @@ -249,11 +254,10 @@ pub struct I2c<'d, DM: Mode, T = AnyI2c> { impl SetConfig for I2c<'_, DM, T> { type Config = Config; - type ConfigError = Infallible; + type ConfigError = ConfigError; fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { - self.apply_config(config); - Ok(()) + self.apply_config(config) } } @@ -272,13 +276,16 @@ where PeripheralClockControl::reset(self.i2c.peripheral()); PeripheralClockControl::enable(self.i2c.peripheral()); - self.driver().setup(&self.config); + // It's okay to ignore the result, we've already validated the config + // either in `apply_config` or in `new_typed_with_config`. + _ = self.driver().setup(&self.config); } /// Applies a new configuration. - pub fn apply_config(&mut self, config: &Config) { + pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> { + self.driver().setup(config)?; self.config = *config; - self.driver().setup(&self.config); + Ok(()) } fn transaction_impl<'a>( @@ -424,7 +431,7 @@ where PeripheralClockControl::reset(i2c.i2c.peripheral()); PeripheralClockControl::enable(i2c.i2c.peripheral()); - i2c.driver().setup(&i2c.config); + unwrap!(i2c.driver().setup(&i2c.config)); i2c } @@ -866,7 +873,7 @@ struct Driver<'a> { impl Driver<'_> { /// Configures the I2C peripheral with the specified frequency, clocks, and /// optional timeout. - fn setup(&self, config: &Config) { + fn setup(&self, config: &Config) -> Result<(), ConfigError> { self.info.register_block().ctr().write(|w| { // Set I2C controller to master mode w.ms_mode().set_bit(); @@ -893,6 +900,8 @@ impl Driver<'_> { // Reset entire peripheral (also resets fifo) self.reset(); + + Ok(()) } /// Resets the I2C peripheral's command registers From 0eebdc46e37cb01163dcbe68ffe6078e2083516a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Wed, 6 Nov 2024 16:21:36 +0100 Subject: [PATCH 15/18] Merge eha and eh modules --- esp-hal/src/i2c/master/support/eh.rs | 15 +++++++++++++++ esp-hal/src/i2c/master/support/eh_a.rs | 20 -------------------- esp-hal/src/i2c/master/support/mod.rs | 1 - 3 files changed, 15 insertions(+), 21 deletions(-) delete mode 100644 esp-hal/src/i2c/master/support/eh_a.rs diff --git a/esp-hal/src/i2c/master/support/eh.rs b/esp-hal/src/i2c/master/support/eh.rs index 63d950bb25a..9a5b8bf6780 100644 --- a/esp-hal/src/i2c/master/support/eh.rs +++ b/esp-hal/src/i2c/master/support/eh.rs @@ -1,5 +1,6 @@ use crate::{ i2c::master::{Error, I2c, Instance, Operation}, + Async, Mode, }; @@ -41,3 +42,17 @@ where self.transaction_impl(address, operations.iter_mut().map(Into::into)) } } + +impl embedded_hal_async::i2c::I2c for I2c<'_, Async, T> +where + T: Instance, +{ + async fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + self.transaction_impl_async(address, operations.iter_mut().map(Into::into)) + .await + } +} diff --git a/esp-hal/src/i2c/master/support/eh_a.rs b/esp-hal/src/i2c/master/support/eh_a.rs deleted file mode 100644 index 4debbecb8ab..00000000000 --- a/esp-hal/src/i2c/master/support/eh_a.rs +++ /dev/null @@ -1,20 +0,0 @@ -use embedded_hal::i2c::Operation; - -use crate::{ - i2c::master::{I2c, Instance}, - Async, -}; - -impl embedded_hal_async::i2c::I2c for I2c<'_, Async, T> -where - T: Instance, -{ - async fn transaction( - &mut self, - address: u8, - operations: &mut [Operation<'_>], - ) -> Result<(), Self::Error> { - self.transaction_impl_async(address, operations.iter_mut().map(Into::into)) - .await - } -} diff --git a/esp-hal/src/i2c/master/support/mod.rs b/esp-hal/src/i2c/master/support/mod.rs index 6920dd9b064..57528f54bbe 100644 --- a/esp-hal/src/i2c/master/support/mod.rs +++ b/esp-hal/src/i2c/master/support/mod.rs @@ -1,3 +1,2 @@ pub mod eh; pub mod eh02; -pub mod eh_a; From 46f087e9000a1391978bcbee4b0d71d6c04f7354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Wed, 6 Nov 2024 16:52:13 +0100 Subject: [PATCH 16/18] PAC update --- esp-hal/Cargo.toml | 14 +++++++------- esp-hal/src/i2c/master/mod.rs | 8 +------- esp-hal/src/i2c/master/version/v1.rs | 2 +- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/esp-hal/Cargo.toml b/esp-hal/Cargo.toml index 307ec0c9181..13bf57e291e 100644 --- a/esp-hal/Cargo.toml +++ b/esp-hal/Cargo.toml @@ -56,13 +56,13 @@ xtensa-lx = { version = "0.9.0", optional = true } # IMPORTANT: # Each supported device MUST have its PAC included below along with a # corresponding feature. -esp32 = { version = "0.33.0", git = "https://github.com/esp-rs/esp-pacs.git", rev = "390c88b", features = ["critical-section", "rt"], optional = true } -esp32c2 = { version = "0.22.0", git = "https://github.com/esp-rs/esp-pacs.git", rev = "390c88b", features = ["critical-section", "rt"], optional = true } -esp32c3 = { version = "0.25.0", git = "https://github.com/esp-rs/esp-pacs.git", rev = "390c88b", features = ["critical-section", "rt"], optional = true } -esp32c6 = { version = "0.16.0", git = "https://github.com/esp-rs/esp-pacs.git", rev = "390c88b", features = ["critical-section", "rt"], optional = true } -esp32h2 = { version = "0.12.0", git = "https://github.com/esp-rs/esp-pacs.git", rev = "390c88b", features = ["critical-section", "rt"], optional = true } -esp32s2 = { version = "0.24.0", git = "https://github.com/esp-rs/esp-pacs.git", rev = "390c88b", features = ["critical-section", "rt"], optional = true } -esp32s3 = { version = "0.28.0", git = "https://github.com/esp-rs/esp-pacs.git", rev = "390c88b", features = ["critical-section", "rt"], optional = true } +esp32 = { version = "0.33.0", git = "https://github.com/esp-rs/esp-pacs.git", rev = "58e9556", features = ["critical-section", "rt"], optional = true } +esp32c2 = { version = "0.22.0", git = "https://github.com/esp-rs/esp-pacs.git", rev = "58e9556", features = ["critical-section", "rt"], optional = true } +esp32c3 = { version = "0.25.0", git = "https://github.com/esp-rs/esp-pacs.git", rev = "58e9556", features = ["critical-section", "rt"], optional = true } +esp32c6 = { version = "0.16.0", git = "https://github.com/esp-rs/esp-pacs.git", rev = "58e9556", features = ["critical-section", "rt"], optional = true } +esp32h2 = { version = "0.12.0", git = "https://github.com/esp-rs/esp-pacs.git", rev = "58e9556", features = ["critical-section", "rt"], optional = true } +esp32s2 = { version = "0.24.0", git = "https://github.com/esp-rs/esp-pacs.git", rev = "58e9556", features = ["critical-section", "rt"], optional = true } +esp32s3 = { version = "0.28.0", git = "https://github.com/esp-rs/esp-pacs.git", rev = "58e9556", features = ["critical-section", "rt"], optional = true } [target.'cfg(target_arch = "riscv32")'.dependencies] esp-riscv-rt = { version = "0.9.0", path = "../esp-riscv-rt" } diff --git a/esp-hal/src/i2c/master/mod.rs b/esp-hal/src/i2c/master/mod.rs index 228578b940f..c174b091ca3 100644 --- a/esp-hal/src/i2c/master/mod.rs +++ b/esp-hal/src/i2c/master/mod.rs @@ -740,13 +740,7 @@ fn async_handler(info: &Info, state: &State) { #[cfg(not(any(esp32, esp32s2)))] w.txfifo_wm().clear_bit(); - cfg_if::cfg_if! { - if #[cfg(esp32)] { - w.ack_err().clear_bit() - } else { - w.nack().clear_bit() - } - } + w.nack().clear_bit() }); state.waker.wake(); diff --git a/esp-hal/src/i2c/master/version/v1.rs b/esp-hal/src/i2c/master/version/v1.rs index 52bb4ef93ab..7eea1fde45b 100644 --- a/esp-hal/src/i2c/master/version/v1.rs +++ b/esp-hal/src/i2c/master/version/v1.rs @@ -64,7 +64,7 @@ impl Driver<'_> { let retval = if interrupts.time_out().bit_is_set() { Error::TimeOut - } else if interrupts.ack_err().bit_is_set() { + } else if interrupts.nack().bit_is_set() { Error::AckCheckFailed } else if interrupts.arbitration_lost().bit_is_set() { Error::ArbitrationLost From b508aeab258261addbfd2d811a6c270a16337a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Wed, 6 Nov 2024 19:53:37 +0100 Subject: [PATCH 17/18] Mark State and Info as #[non_exhaustive] --- esp-hal/src/i2c/master/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esp-hal/src/i2c/master/mod.rs b/esp-hal/src/i2c/master/mod.rs index c174b091ca3..72e0f9ca36f 100644 --- a/esp-hal/src/i2c/master/mod.rs +++ b/esp-hal/src/i2c/master/mod.rs @@ -826,6 +826,7 @@ fn configure_clock( } /// Peripheral data describing a particular I2C instance. +#[non_exhaustive] pub struct Info { /// Pointer to the register block for this I2C instance. /// @@ -1301,6 +1302,7 @@ impl PartialEq for Info { unsafe impl Sync for Info {} /// Peripheral state for an I2C instance. +#[non_exhaustive] pub struct State { /// Waker for the asynchronous operations. pub waker: AtomicWaker, From 13cba2f30037f2d547e38914b61df5b6207cc69e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Wed, 6 Nov 2024 23:35:56 +0100 Subject: [PATCH 18/18] Remove doc(hidden) from Instance --- esp-hal/src/i2c/master/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/esp-hal/src/i2c/master/mod.rs b/esp-hal/src/i2c/master/mod.rs index 72e0f9ca36f..a22cd524da3 100644 --- a/esp-hal/src/i2c/master/mod.rs +++ b/esp-hal/src/i2c/master/mod.rs @@ -1309,7 +1309,6 @@ pub struct State { } /// I2C Peripheral Instance -#[doc(hidden)] pub trait Instance: Peripheral

+ PeripheralMarker + Into + 'static { /// Returns the peripheral data and state describing this instance. fn parts(&self) -> (&Info, &State);