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:
parent
7a733a75db
commit
24c545db4a
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
.write_operation(
|
||||||
address,
|
address,
|
||||||
bytes,
|
bytes,
|
||||||
last_op != Op::Write,
|
last_op != Op::Write,
|
||||||
next_op == Op::None,
|
next_op == Op::None,
|
||||||
cmd_iterator,
|
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
|
||||||
|
.read_operation(
|
||||||
address,
|
address,
|
||||||
buffer,
|
buffer,
|
||||||
last_op != Op::Read,
|
last_op != Op::Read,
|
||||||
next_op == Op::None,
|
next_op == Op::None,
|
||||||
next_op == Op::Read,
|
next_op == Op::Read,
|
||||||
cmd_iterator,
|
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
|
||||||
|
|||||||
@ -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])
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user