From a5e2c77b39f6d60b706770e8571cc0c6b1f8dfc5 Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Sun, 6 Sep 2026 03:47:54 +0000 Subject: [PATCH] fix(arm): do not clear STOPF on the I2C NACK path WaitForFlag cleared NACKCF and STOPCF together when it saw a NACK. But autoend generates the STOP itself in response to that NACK, and FinishTransfer is what waits for the resulting STOPF -- so clearing it there left that wait with nothing to observe, burning the full 25 ms transfer timeout on every failed transfer. Not a hang: the timeout bounds it, and the error the caller sees is the NACK's kOperationFailed rather than the discarded kTimeout. But it made a missing device cost 25 ms per attempt instead of the ~90 us an address phase takes at 100 kHz. Clear only NACKCF and let FinishTransfer see the STOP it was waiting for. Co-Authored-By: Claude Opus 5 (1M context) --- src/libs/mcu/arm_cm7/i2c.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libs/mcu/arm_cm7/i2c.cpp b/src/libs/mcu/arm_cm7/i2c.cpp index 7f5b441..480e6de 100644 --- a/src/libs/mcu/arm_cm7/i2c.cpp +++ b/src/libs/mcu/arm_cm7/i2c.cpp @@ -75,7 +75,11 @@ auto EnablePeripheralClock(I2CId id) -> void { const std::uint32_t start = Millis(); while ((registers->ISR & flag) == 0U) { if ((registers->ISR & I2C_ISR_NACKF) != 0U) { - registers->ICR = I2C_ICR_NACKCF | I2C_ICR_STOPCF; + // Clear only NACKF. Autoend generates the STOP itself in response to the + // NACK, and FinishTransfer is what waits for and clears the resulting + // STOPF -- clearing it here would leave that wait with nothing to + // observe, burning the whole transfer timeout on every failed transfer. + registers->ICR = I2C_ICR_NACKCF; return std::unexpected(common::Error::kOperationFailed); } if ((Millis() - start) > kTransferTimeoutMs) {