STM32F401CCU6 I2C Reads Need Different End Sequences for One Two and Many Bytes
A controller that reads a long register block correctly can still mishandle a one-byte status read or a two-byte sensor value. On STM32F401CCU6, the final acknowledgment is part of a length-dependent peripheral sequence. A generic loop that drains receive data and requests STOP afterward can make that decision too late.
Scope: the legacy STM32F401 I2C peripheral in controller-receiver mode, RM0368 Rev 6, with ST's stm32f4xx-hal-driver tag v1.8.3 as an implementation reference. This discussion is about ordinary receive termination, not SMBus PEC, ten-bit-address details, or the newer NBYTES/AUTOEND peripheral found on other STM32 families.

Original sequence overview. It identifies control boundaries, not measured timing or drop-in register code.
Treat the receive count as a state-machine input
The address phase is not merely a common preamble to every read. For a one-byte reception, RM0368 requires acknowledgment to be disabled before clearing ADDR, followed by the STOP request. Its two-byte procedure uses ACK low and POS high before ADDR is cleared, then waits for BTF, requests STOP, and reads the two bytes. ST RM0368 Rev 6 section 18.3.3
For longer receptions, the same manual provides a final-three-byte sequence using BTF, disabling ACK, consuming the third-last byte, waiting for BTF again, requesting STOP, and reading the remaining two bytes. This deliberately uses the peripheral's data-register and shift-register states rather than assuming every RXNE event has identical consequences.
Keep those cases explicit in a driver review. A one-byte read is not a long read with a smaller loop counter. Nor is it safe to move acknowledgment setup after a generic address-clear helper because that makes the function look more uniform.
Use the tagged driver as evidence rather than a universal recipe
The v1.8.3 HAL_I2C_Master_Receive implementation clears POS at entry and branches on receive length. Its receive loop has separate one-byte, two-byte, and final-three-byte handling. It also contains a BTF fast path for longer transfers, including an acknowledgment adjustment when the remaining length reaches the tail. ST HAL I2C source at v1.8.3
That source provides a useful audit target: compare the function actually linked into the product, not a similarly named file in a different SDK checkout. Record the driver tag or commit, local modifications, compiler options, and API used. Blocking, interrupt-driven, DMA, and memory-read paths must be reviewed as the paths they are; a check of one function does not qualify all of them.
Do not combine fragments from several examples into a new driver and assume the resulting order remains valid. If replacing a vendor implementation, write down the required peripheral states and then prove that each transition preserves them.
Test sequences of lengths not only isolated lengths
Construct a controlled fixture with a target that exposes stable, side-effect-free test data. Read lengths one, two, three, four, and a longer block. Then alternate two-byte and one-byte reads, and mix short reads with long reads. These transitions are more informative than thousands of identical requests because they can expose control state left over from the previous operation.
A useful expected record contains target address, selector bytes where required, requested length, returned length, result code, and a transaction identifier. Keep the target's register-pointer behavior separate from controller termination. If the target requires a repeated START, preserve that requirement too, but do not interpret correct framing at the beginning as proof of the final NACK.
Use distinguishable adjacent values. Repeated zeros or an unchanging status byte can hide an extra byte, an offset error, or a stale result. Never use a FIFO or clear-on-read register as a supposedly stable oracle without accounting for its side effects.
Observe the final acknowledgment on the wire
Capture SDA and SCL, then label the last three byte positions and their ACK/NACK clocks. Compare the received buffer with the capture and the intended count. Look for an extra acknowledged byte, a STOP at the wrong boundary, or a line that remains stretched because the expected software transition never happened.
Run the same workload with representative interrupt activity. Logging inside the critical receive path can alter the interval being investigated, so use bounded tracing or an independent observation point. Do not disable all interrupts as an unexplained production fix; any required exclusion must be limited, justified, and compatible with the rest of the system.
Avoid casually watching live I2C status and data registers in an auto-refreshing debugger window. Some register reads participate in clearing status or consuming received data. A debugger view that perturbs the sequence is poor evidence for the unobserved failure.
Keep timing errata separate from software-tail defects
ES0222 Rev 9 lists a repeated-START setup-time limitation for specified standard-mode rates and conditions. Check its applicability before treating every malformed capture as an ACK/POS bug. Conversely, a timing workaround does not repair an incorrect receive-tail state machine. ST ES0222 Rev 9
Maintain separate findings for electrical timing, target protocol, length-dependent control, timeout handling, and post-error recovery. On failure, invalidate the incomplete result and preserve enough status to identify the failed phase. Start the next request only after the driver's documented recovery has restored a known state.
The acceptance package is a pinned driver review, a mixed-length test sequence, and matched waveform captures showing the requested count and final NACK/STOP. No bus transactions were executed here. The proposed checks close a concrete F401 integration gap without pretending that one successful register read qualifies the entire I2C path.