I2C error recovery (#2141)

* Recover from I2C errors (the hard way)

* Adjust test to cover the problematic case

* Replace macro-usage

* CHANGELOG.md entry

* Appease Clippy

* TIL: `Result::inspect_err` exists

* Turn public `recover` into private `internal_recover`
This commit is contained in:
Björn Quentin 2024-09-11 17:50:25 +02:00 committed by GitHub
parent 7a733a75db
commit 24c545db4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 75 additions and 36 deletions

View File

@ -47,6 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed an issue with LCD_CAM i8080 where it would send double the clocks in 16bit mode (#2085) - Fixed an issue with LCD_CAM i8080 where it would send double the clocks in 16bit mode (#2085)
- Fix i2c embedded-hal transaction (#2028) - Fix i2c embedded-hal transaction (#2028)
- Fix SPI DMA alternating `write` and `read` for ESP32 and ESP32-S2 (#2131) - Fix SPI DMA alternating `write` and `read` for ESP32 and ESP32-S2 (#2131)
- Fix I2C ending up in a state when only re-creating the peripheral makes it useable again (#2141)
### Removed ### Removed

View File

@ -187,6 +187,8 @@ impl From<Ack> for u32 {
pub struct I2C<'d, T, DM: crate::Mode> { pub struct I2C<'d, T, DM: crate::Mode> {
peripheral: PeripheralRef<'d, T>, peripheral: PeripheralRef<'d, T>,
phantom: PhantomData<DM>, phantom: PhantomData<DM>,
frequency: HertzU32,
timeout: Option<u32>,
} }
impl<T> I2C<'_, T, crate::Blocking> impl<T> I2C<'_, T, crate::Blocking>
@ -195,12 +197,16 @@ where
{ {
/// Reads enough bytes from slave with `address` to fill `buffer` /// Reads enough bytes from slave with `address` to fill `buffer`
pub fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { pub fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> {
self.peripheral.master_read(address, buffer) self.peripheral
.master_read(address, buffer)
.inspect_err(|_| self.internal_recover())
} }
/// Writes bytes to slave with address `address` /// Writes bytes to slave with address `address`
pub fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { pub fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> {
self.peripheral.master_write(addr, bytes) self.peripheral
.master_write(addr, bytes)
.inspect_err(|_| self.internal_recover())
} }
/// Writes bytes to slave with address `address` and then reads enough bytes /// Writes bytes to slave with address `address` and then reads enough bytes
@ -211,7 +217,9 @@ where
bytes: &[u8], bytes: &[u8],
buffer: &mut [u8], buffer: &mut [u8],
) -> Result<(), Error> { ) -> Result<(), Error> {
self.peripheral.master_write_read(address, bytes, buffer) self.peripheral
.master_write_read(address, bytes, buffer)
.inspect_err(|_| self.internal_recover())
} }
} }
@ -222,7 +230,9 @@ where
type Error = Error; type Error = Error;
fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
self.peripheral.master_read(address, buffer) self.peripheral
.master_read(address, buffer)
.inspect_err(|_| self.internal_recover())
} }
} }
@ -233,7 +243,9 @@ where
type Error = Error; type Error = Error;
fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> { fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> {
self.peripheral.master_write(addr, bytes) self.peripheral
.master_write(addr, bytes)
.inspect_err(|_| self.internal_recover())
} }
} }
@ -249,7 +261,9 @@ where
bytes: &[u8], bytes: &[u8],
buffer: &mut [u8], buffer: &mut [u8],
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
self.peripheral.master_write_read(address, bytes, buffer) self.peripheral
.master_write_read(address, bytes, buffer)
.inspect_err(|_| self.internal_recover())
} }
} }
@ -287,13 +301,15 @@ where
// execute a write operation: // execute a write operation:
// - issue START/RSTART if op is different from previous // - issue START/RSTART if op is different from previous
// - issue STOP if op is the last one // - issue STOP if op is the last one
self.peripheral.write_operation( self.peripheral
address, .write_operation(
bytes, address,
last_op != Op::Write, bytes,
next_op == Op::None, last_op != Op::Write,
cmd_iterator, next_op == Op::None,
)?; cmd_iterator,
)
.inspect_err(|_| self.internal_recover())?;
last_op = Op::Write; last_op = Op::Write;
} }
Operation::Read(buffer) => { Operation::Read(buffer) => {
@ -301,14 +317,16 @@ where
// - issue START/RSTART if op is different from previous // - issue START/RSTART if op is different from previous
// - issue STOP if op is the last one // - issue STOP if op is the last one
// - will_continue is true if there is another read operation next // - will_continue is true if there is another read operation next
self.peripheral.read_operation( self.peripheral
address, .read_operation(
buffer, address,
last_op != Op::Read, buffer,
next_op == Op::None, last_op != Op::Read,
next_op == Op::Read, next_op == Op::None,
cmd_iterator, next_op == Op::Read,
)?; cmd_iterator,
)
.inspect_err(|_| self.internal_recover())?;
last_op = Op::Read; last_op = Op::Read;
} }
} }
@ -347,9 +365,11 @@ where
_ => unreachable!(), // will never happen _ => unreachable!(), // will never happen
}); });
let mut i2c = I2C { let i2c = I2C {
peripheral: i2c, peripheral: i2c,
phantom: PhantomData, phantom: PhantomData,
frequency,
timeout,
}; };
// avoid SCL/SDA going low during configuration // avoid SCL/SDA going low during configuration
@ -392,6 +412,24 @@ where
crate::interrupt::enable(T::interrupt(), handler.priority()).unwrap(); crate::interrupt::enable(T::interrupt(), handler.priority()).unwrap();
} }
} }
fn internal_recover(&self) {
PeripheralClockControl::reset(match self.peripheral.i2c_number() {
0 => crate::system::Peripheral::I2cExt0,
#[cfg(i2c1)]
1 => crate::system::Peripheral::I2cExt1,
_ => unreachable!(), // will never happen
});
PeripheralClockControl::enable(match self.peripheral.i2c_number() {
0 => crate::system::Peripheral::I2cExt0,
#[cfg(i2c1)]
1 => crate::system::Peripheral::I2cExt1,
_ => unreachable!(), // will never happen
});
self.peripheral.setup(self.frequency, self.timeout);
}
} }
impl<'d, T> I2C<'d, T, crate::Blocking> impl<'d, T> I2C<'d, T, crate::Blocking>
@ -1089,7 +1127,7 @@ pub trait Instance: crate::private::Sealed {
/// Configures the I2C peripheral with the specified frequency, clocks, and /// Configures the I2C peripheral with the specified frequency, clocks, and
/// optional timeout. /// optional timeout.
fn setup(&mut self, frequency: HertzU32, timeout: Option<u32>) { fn setup(&self, frequency: HertzU32, timeout: Option<u32>) {
self.register_block().ctr().modify(|_, w| unsafe { self.register_block().ctr().modify(|_, w| unsafe {
// Clear register // Clear register
w.bits(0) w.bits(0)
@ -1170,7 +1208,7 @@ pub trait Instance: crate::private::Sealed {
/// Sets the filter with a supplied threshold in clock cycles for which a /// Sets the filter with a supplied threshold in clock cycles for which a
/// pulse must be present to pass the filter /// pulse must be present to pass the filter
fn set_filter(&mut self, sda_threshold: Option<u8>, scl_threshold: Option<u8>) { fn set_filter(&self, sda_threshold: Option<u8>, scl_threshold: Option<u8>) {
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(any(esp32, esp32s2))] { if #[cfg(any(esp32, esp32s2))] {
let sda_register = &self.register_block().sda_filter_cfg(); let sda_register = &self.register_block().sda_filter_cfg();
@ -1202,7 +1240,7 @@ pub trait Instance: crate::private::Sealed {
/// Sets the frequency of the I2C interface by calculating and applying the /// Sets the frequency of the I2C interface by calculating and applying the
/// associated timings - corresponds to i2c_ll_cal_bus_clk and /// associated timings - corresponds to i2c_ll_cal_bus_clk and
/// i2c_ll_set_bus_timing in ESP-IDF /// i2c_ll_set_bus_timing in ESP-IDF
fn set_frequency(&mut self, source_clk: HertzU32, bus_freq: HertzU32, timeout: Option<u32>) { fn set_frequency(&self, source_clk: HertzU32, bus_freq: HertzU32, timeout: Option<u32>) {
let source_clk = source_clk.raw(); let source_clk = source_clk.raw();
let bus_freq = bus_freq.raw(); let bus_freq = bus_freq.raw();
@ -1280,7 +1318,7 @@ pub trait Instance: crate::private::Sealed {
/// Sets the frequency of the I2C interface by calculating and applying the /// Sets the frequency of the I2C interface by calculating and applying the
/// associated timings - corresponds to i2c_ll_cal_bus_clk and /// associated timings - corresponds to i2c_ll_cal_bus_clk and
/// i2c_ll_set_bus_timing in ESP-IDF /// i2c_ll_set_bus_timing in ESP-IDF
fn set_frequency(&mut self, source_clk: HertzU32, bus_freq: HertzU32, timeout: Option<u32>) { fn set_frequency(&self, source_clk: HertzU32, bus_freq: HertzU32, timeout: Option<u32>) {
let source_clk = source_clk.raw(); let source_clk = source_clk.raw();
let bus_freq = bus_freq.raw(); let bus_freq = bus_freq.raw();
@ -1338,7 +1376,7 @@ pub trait Instance: crate::private::Sealed {
/// Sets the frequency of the I2C interface by calculating and applying the /// Sets the frequency of the I2C interface by calculating and applying the
/// associated timings - corresponds to i2c_ll_cal_bus_clk and /// associated timings - corresponds to i2c_ll_cal_bus_clk and
/// i2c_ll_set_bus_timing in ESP-IDF /// i2c_ll_set_bus_timing in ESP-IDF
fn set_frequency(&mut self, source_clk: HertzU32, bus_freq: HertzU32, timeout: Option<u32>) { fn set_frequency(&self, source_clk: HertzU32, bus_freq: HertzU32, timeout: Option<u32>) {
let source_clk = source_clk.raw(); let source_clk = source_clk.raw();
let bus_freq = bus_freq.raw(); let bus_freq = bus_freq.raw();
@ -1411,7 +1449,7 @@ pub trait Instance: crate::private::Sealed {
#[allow(clippy::too_many_arguments, unused)] #[allow(clippy::too_many_arguments, unused)]
/// Configures the clock and timing parameters for the I2C peripheral. /// Configures the clock and timing parameters for the I2C peripheral.
fn configure_clock( fn configure_clock(
&mut self, &self,
sclk_div: u32, sclk_div: u32,
scl_low_period: u32, scl_low_period: u32,
scl_high_period: u32, scl_high_period: u32,
@ -2078,7 +2116,7 @@ pub trait Instance: crate::private::Sealed {
/// Send data bytes from the `bytes` array to a target slave with the /// Send data bytes from the `bytes` array to a target slave with the
/// address `addr` /// address `addr`
fn master_write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { fn master_write(&self, addr: u8, bytes: &[u8]) -> Result<(), Error> {
// Clear all I2C interrupts // Clear all I2C interrupts
self.clear_all_interrupts(); self.clear_all_interrupts();
self.write_operation( self.write_operation(
@ -2094,7 +2132,7 @@ pub trait Instance: crate::private::Sealed {
/// Read bytes from a target slave with the address `addr` /// Read bytes from a target slave with the address `addr`
/// The number of read bytes is deterimed by the size of the `buffer` /// The number of read bytes is deterimed by the size of the `buffer`
/// argument /// argument
fn master_read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Error> { fn master_read(&self, addr: u8, buffer: &mut [u8]) -> Result<(), Error> {
// Clear all I2C interrupts // Clear all I2C interrupts
self.clear_all_interrupts(); self.clear_all_interrupts();
self.read_operation( self.read_operation(
@ -2110,12 +2148,7 @@ pub trait Instance: crate::private::Sealed {
/// Write bytes from the `bytes` array first and then read n bytes into /// Write bytes from the `bytes` array first and then read n bytes into
/// the `buffer` array with n being the size of the array. /// the `buffer` array with n being the size of the array.
fn master_write_read( fn master_write_read(&self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> {
&mut self,
addr: u8,
bytes: &[u8],
buffer: &mut [u8],
) -> Result<(), Error> {
// it would be possible to combine the write and read // it would be possible to combine the write and read
// in one transaction but filling the tx fifo with // in one transaction but filling the tx fifo with
// the current code is somewhat slow even in release mode // the current code is somewhat slow even in release mode

View File

@ -37,6 +37,11 @@ mod tests {
fn test_read_cali(mut ctx: Context) { fn test_read_cali(mut ctx: Context) {
let mut read_data = [0u8; 22]; let mut read_data = [0u8; 22];
// have a failing read which might could leave the peripheral in an undesirable
// state
ctx.i2c.write_read(0x55, &[0xaa], &mut read_data).ok();
// do the real read which should succeed
ctx.i2c.write_read(0x77, &[0xaa], &mut read_data).ok(); ctx.i2c.write_read(0x77, &[0xaa], &mut read_data).ok();
assert_ne!(read_data, [0u8; 22]) assert_ne!(read_data, [0u8; 22])