Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

I2C error recovery #2141

Merged
merged 7 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions esp-hal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,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)
- Fix i2c embedded-hal transaction (#2028)
- 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

Expand Down
122 changes: 86 additions & 36 deletions esp-hal/src/i2c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ impl From<Ack> for u32 {
pub struct I2C<'d, T, DM: crate::Mode> {
peripheral: PeripheralRef<'d, T>,
phantom: PhantomData<DM>,
frequency: HertzU32,
timeout: Option<u32>,
}

impl<T> I2C<'_, T, crate::Blocking>
Expand All @@ -195,12 +197,18 @@ where
{
/// Reads enough bytes from slave with `address` to fill `buffer`
pub fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> {
self.peripheral.master_read(address, buffer)
self.peripheral.master_read(address, buffer).map_err(|e| {
self.recover();
e
})
}

/// Writes bytes to slave with address `address`
pub fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> {
self.peripheral.master_write(addr, bytes)
self.peripheral.master_write(addr, bytes).map_err(|e| {
self.recover();
e
})
}

/// Writes bytes to slave with address `address` and then reads enough bytes
Expand All @@ -211,7 +219,12 @@ where
bytes: &[u8],
buffer: &mut [u8],
) -> Result<(), Error> {
self.peripheral.master_write_read(address, bytes, buffer)
self.peripheral
.master_write_read(address, bytes, buffer)
.map_err(|e| {
self.recover();
e
})
}
}

Expand All @@ -222,7 +235,10 @@ where
type Error = 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).map_err(|e| {
self.recover();
e
})
}
}

Expand All @@ -233,7 +249,10 @@ where
type Error = Error;

fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> {
self.peripheral.master_write(addr, bytes)
self.peripheral.master_write(addr, bytes).map_err(|e| {
self.recover();
e
})
}
}

Expand All @@ -249,7 +268,12 @@ where
bytes: &[u8],
buffer: &mut [u8],
) -> Result<(), Self::Error> {
self.peripheral.master_write_read(address, bytes, buffer)
self.peripheral
.master_write_read(address, bytes, buffer)
.map_err(|e| {
self.recover();
e
})
}
}

Expand Down Expand Up @@ -287,28 +311,38 @@ where
// execute a write operation:
// - issue START/RSTART if op is different from previous
// - issue STOP if op is the last one
self.peripheral.write_operation(
address,
bytes,
last_op != Op::Write,
next_op == Op::None,
cmd_iterator,
)?;
self.peripheral
.write_operation(
address,
bytes,
last_op != Op::Write,
next_op == Op::None,
cmd_iterator,
)
.map_err(|e| {
self.recover();
e
})?;
last_op = Op::Write;
}
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.peripheral.read_operation(
address,
buffer,
last_op != Op::Read,
next_op == Op::None,
next_op == Op::Read,
cmd_iterator,
)?;
self.peripheral
.read_operation(
address,
buffer,
last_op != Op::Read,
next_op == Op::None,
next_op == Op::Read,
cmd_iterator,
)
.map_err(|e| {
self.recover();
e
})?;
last_op = Op::Read;
}
}
Expand Down Expand Up @@ -344,9 +378,11 @@ where
_ => unreachable!(), // will never happen
});

let mut i2c = I2C {
let i2c = I2C {
peripheral: i2c,
phantom: PhantomData,
frequency,
timeout,
};

// avoid SCL/SDA going low during configuration
Expand Down Expand Up @@ -387,6 +423,25 @@ where
crate::interrupt::enable(T::interrupt(), handler.priority()).unwrap();
}
}

/// Recover from error condition
pub fn recover(&self) {
MabezDev marked this conversation as resolved.
Show resolved Hide resolved
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>
Expand Down Expand Up @@ -1075,7 +1130,7 @@ pub trait Instance: crate::private::Sealed {

/// Configures the I2C peripheral with the specified frequency, clocks, and
/// 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 {
// Clear register
w.bits(0)
Expand Down Expand Up @@ -1156,7 +1211,7 @@ pub trait Instance: crate::private::Sealed {

/// Sets the filter with a supplied threshold in clock cycles for which a
/// 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! {
if #[cfg(any(esp32, esp32s2))] {
let sda_register = &self.register_block().sda_filter_cfg();
Expand Down Expand Up @@ -1188,7 +1243,7 @@ pub trait Instance: crate::private::Sealed {
/// 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(&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 bus_freq = bus_freq.raw();

Expand Down Expand Up @@ -1266,7 +1321,7 @@ pub trait Instance: crate::private::Sealed {
/// 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(&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 bus_freq = bus_freq.raw();

Expand Down Expand Up @@ -1324,7 +1379,7 @@ pub trait Instance: crate::private::Sealed {
/// 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(&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 bus_freq = bus_freq.raw();

Expand Down Expand Up @@ -1397,7 +1452,7 @@ pub trait Instance: crate::private::Sealed {
#[allow(clippy::too_many_arguments, unused)]
/// Configures the clock and timing parameters for the I2C peripheral.
fn configure_clock(
&mut self,
&self,
sclk_div: u32,
scl_low_period: u32,
scl_high_period: u32,
Expand Down Expand Up @@ -2064,7 +2119,7 @@ pub trait Instance: crate::private::Sealed {

/// Send data bytes from the `bytes` array to a target slave with the
/// 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
self.clear_all_interrupts();
self.write_operation(
Expand All @@ -2080,7 +2135,7 @@ pub trait Instance: crate::private::Sealed {
/// Read bytes from a target slave with the address `addr`
/// The number of read bytes is deterimed by the size of the `buffer`
/// 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
self.clear_all_interrupts();
self.read_operation(
Expand All @@ -2096,12 +2151,7 @@ pub trait Instance: crate::private::Sealed {

/// Write bytes from the `bytes` array first and then read n bytes into
/// the `buffer` array with n being the size of the array.
fn master_write_read(
&mut self,
addr: u8,
bytes: &[u8],
buffer: &mut [u8],
) -> Result<(), Error> {
fn master_write_read(&self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> {
// it would be possible to combine the write and read
// in one transaction but filling the tx fifo with
// the current code is somewhat slow even in release mode
Expand Down
5 changes: 5 additions & 0 deletions hil-test/tests/i2c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ mod tests {
fn test_read_cali(mut ctx: Context) {
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();

assert_ne!(read_data, [0u8; 22])
Expand Down
Loading