Skip to content

fix(arm): do not clear STOPF on the I2C NACK path - #40

Merged
nehalkpatel merged 1 commit into
mainfrom
fix/i2c-nack-stopf
Sep 6, 2026
Merged

fix(arm): do not clear STOPF on the I2C NACK path#40
nehalkpatel merged 1 commit into
mainfrom
fix/i2c-nack-stopf

Conversation

@nehalkpatel

Copy link
Copy Markdown
Owner

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 µs an address phase takes at
100 kHz.

Verified on hardware, not just reasoned about

Flashed i2c_demo to the F767ZI with nothing on the bus and broke on both
failure branches of WaitForFlag to see which one the driver actually takes:

Breakpoint 1, mcu::(anonymous namespace)::WaitForFlag (registers=0x40005400, flag=2)
    at src/libs/mcu/arm_cm7/i2c.cpp:82
#0  WaitForFlag ... i2c.cpp:82          <- the NACK branch
#1  mcu::I2CBus::SendData (address=80)  ... i2c.cpp:167
#2  app::I2CDemo::Run                   ... i2c_demo.cpp:41
ISR now = 0x00000031  (NACKF=1)

ISR = 0x31 is the whole argument for this change: TXE (bit 0), NACKF
(bit 4) and STOPF (bit 5) are set at the same instant
. The STOP has already
happened by the time the NACK is detected — so the old code was clearing a flag
FinishTransfer was about to wait for, guaranteeing the timeout every time.

Incidentally this also confirms the I2C driver end to end on real silicon: the
address goes out at 0x50, nothing answers, the peripheral raises NACKF, and
SendData returns kOperationFailed without hanging.

Checks

  • Host: 32/32
  • ARM: Debug and Release build clean, 6 images pass tools/verify-firmware.sh
  • tools/format.sh --check clean

🤖 Generated with Claude Code

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) <noreply@anthropic.com>
@nehalkpatel
nehalkpatel merged commit 0206e87 into main Sep 6, 2026
2 checks passed
@nehalkpatel
nehalkpatel deleted the fix/i2c-nack-stopf branch September 6, 2026 04:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant