Skip to content

Commit 79bbad4

Browse files
committed
i2c: check for stuck SCL during recovery
1 parent 2ee48bc commit 79bbad4

File tree

1 file changed

+26
-3
lines changed

1 file changed

+26
-3
lines changed

openemc-firmware/src/i2c_master.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ pub enum I2cError {
2929
Overrun,
3030
/// Timeout
3131
Timeout,
32-
/// SDA line stuck high during recovery.
32+
/// SCL line stuck low during recovery.
33+
SclStuckLow,
34+
/// SDA line stuck low during recovery.
3335
SdaStuckLow,
3436
/// Other error.
3537
Other,
@@ -80,6 +82,17 @@ impl I2c2Gpio {
8082
self.gpiob.crh.modify(|_, w| w.cnf10().open_drain().mode10().output());
8183
}
8284

85+
/// Reads the SCL level.
86+
pub fn read_scl(&self) -> bool {
87+
self.gpiob.crh.modify(|_, w| w.cnf10().open_drain().mode10().input());
88+
89+
for _ in 0..32 {
90+
self.gpiob.idr.read();
91+
}
92+
93+
self.gpiob.idr.read().idr10().is_high()
94+
}
95+
8396
/// Sets the SDA level.
8497
pub fn sda(&self, level: bool) {
8598
self.gpiob.odr.modify(|_, w| w.odr11().variant(if level { High } else { Low }));
@@ -88,7 +101,7 @@ impl I2c2Gpio {
88101

89102
/// Reads the SDA level.
90103
pub fn read_sda(&self) -> bool {
91-
self.gpiob.crh.modify(|_, w| w.mode11().input());
104+
self.gpiob.crh.modify(|_, w| w.cnf11().open_drain().mode11().input());
92105

93106
for _ in 0..32 {
94107
self.gpiob.idr.read();
@@ -128,6 +141,12 @@ pub fn recover(i2c: &mut I2c2Master, clocks: &Clocks) -> Result<(), I2cError> {
128141
i2c_gpio.sda(true);
129142
dwt.delay(CLOCK_HALF_PERIOD_US);
130143

144+
// Verify SCL is high.
145+
if !i2c_gpio.read_scl() {
146+
defmt::error!("I2C SCL is stuck low");
147+
return Err(I2cError::SclStuckLow);
148+
}
149+
131150
// Output clock so that all in-progress transfers end.
132151
defmt::debug!("Cycling SCL to end all I2C transfers");
133152
for _ in 0..15 {
@@ -167,11 +186,15 @@ pub fn recover(i2c: &mut I2c2Master, clocks: &Clocks) -> Result<(), I2cError> {
167186
i2c_gpio.sda(true);
168187
dwt.delay(CLOCK_HALF_PERIOD_US);
169188

189+
// Reset I2C master peripheral.
190+
defmt::debug!("Performing I2C master reset before GPIO release");
191+
i2c.reset();
192+
170193
// Release I2C GPIO control.
171194
drop(i2c_gpio);
172195

173196
// Reset I2C master peripheral.
174-
defmt::debug!("Performing I2C master reset");
197+
defmt::debug!("Performing I2C master reset after GPIO release");
175198
i2c.reset();
176199

177200
Ok(())

0 commit comments

Comments
 (0)