From 83532a157bdec93d08a16ea8c69ba7dde4095cbd Mon Sep 17 00:00:00 2001 From: Benjamin Koch <bbbsnowball@gmail.com> Date: Sat, 13 May 2023 06:53:47 +0200 Subject: [PATCH] implement recovery for bus errors I don't expect that we will need it but it is good practice to do it. --- firmware/rust1/src/bin/heizung.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/firmware/rust1/src/bin/heizung.rs b/firmware/rust1/src/bin/heizung.rs index 2ce1775..bb32d66 100644 --- a/firmware/rust1/src/bin/heizung.rs +++ b/firmware/rust1/src/bin/heizung.rs @@ -151,8 +151,35 @@ async fn i2c_write_zero_bytes<SCL: gpio::Pin, SDA: gpio::Pin, const FREQ : u64>( scl.set_low(); Timer::after(delay).await; - if sda.is_low() || scl.is_low() { - return Err(Abort(ArbitrationLoss)) + + // abort if SCL is stuck low + handle_clock_stretching(&mut scl).await?; + + // handle SDA being stuck low + // (This can happen for very simple I2C devices that only use SCL and don't have any other clocks that + // they can use to handle timeouts, e.g. simple port expanders.) + if sda.is_low() { + // I think I have read that DW_apb_i2c does nine clocks. This seems resonable so let's do the same. + for _ in 0..9 { + scl.set_as_output(); + Timer::after(delay).await; + scl.set_as_input(); + Timer::after(delay).await; + + if sda.is_high() { + break; + } + } + + if sda.is_low() || scl.is_low() { + // There is isn't much that we can do here. A start condition would reset the state but we cannot + // generate it while one of the lines is stuck low. + return Err(Abort(ArbitrationLoss)) + } + + // SDA is high, again. Good. We will generate a start condition soon. This should abort any + // partial transfers that some devices might think are ongoing. We have already waited for half + // a clock so we are good to go. } let result = i2c_write_zero_bytes_middle_part::<_, _, FREQ>(addr, delay, &mut scl, &mut sda).await; -- GitLab