Skip to content

ESP32 I2C: keep the bus timing values within their register fields - #373

Merged
lovyan03 merged 1 commit into
m5stack:developfrom
ainyan03:i2c_timing_width
Oct 22, 2026
Merged

lovyan03 merged 1 commit into
m5stack:developfrom
ainyan03:i2c_timing_width

Conversation

@ainyan03

Copy link
Copy Markdown
Contributor

lgfx::i2c::restart() sized the I2C timing values for the ESP32 / ESP32-S2 register fields (14 bit SCL periods, 10 bit START / STOP). On every later chip (S3, C2, C3, C5, C6, C61, H2, P4, including the LP I2C ports) these fields are 9 bits (7 bits for the SCL high wait), and the bitfield drops the upper bits instead of saturating. With a 40 MHz source clock:

  • START hold / RSTART setup / STOP setup wrapped below about 78 kHz (75 kHz: 534 -> 22 clocks = 0.55 us), and the STOP hold below about 156 kHz (50 kHz: 1.6 us)
  • on ESP32-C3 the SCL high wait wrapped below about 133 kHz, so the bus ran faster than requested
  • below about 39 kHz the SCL low / high periods wrapped as well

The fix caps the cycle so that both SCL halves fit (about 39 kHz is now the slowest bus on these chips from a 40 MHz clock) and saturates each timing value at its own field width, using the *_V masks from soc/i2c_reg.h. The STOP hold is still derived from the START / STOP value after the existing 1023 limit. Comparing the register values before and after for every chip and 1 kHz to 1 MHz (20 to 80 MHz source clocks), nothing changes where the old value already fit: ESP32 / ESP32-S2 are not affected, and 400 kHz is unchanged on every chip.

Measured with a logic analyzer (address scan on an external I2C unit):

Setting ESP32-C3 before ESP32-C3 after ESP32-P4 before ESP32-P4 after
50 kHz SCL 74.6 kHz, START hold 7.2 us 50.5 kHz, 12.8 us 50.5 kHz, 7.2 us 50.5 kHz, 12.8 us
75 kHz SCL 100.4 kHz, START hold 0.56 us, device NACK 76.0 kHz, 12.8 us, ACK 76.0 kHz, 0.56 us 76.0 kHz, 12.8 us
100 kHz SCL 151.5 kHz 102.0 kHz 102.0 kHz 102.0 kHz
400 kHz 431 kHz 431 kHz 431 kHz 431 kHz

Local build check across the upstream CI versions passed. Fork CI passed.

The SCL period and START / STOP timing fields are 9 bits wide (7 bits for
the SCL high wait) on every chip after ESP32-S2, while the values were
sized for the 10 / 14 bit fields of ESP32. A bitfield drops the upper bits
instead of saturating, so (figures for a 40 MHz source clock; they scale
with the clock, e.g. on a 32 MHz XTAL or a low power port):

- below about 78 kHz the START hold / RSTART setup / STOP setup wrapped
  (75 kHz: 534 -> 22 clocks = 0.55 us, the device stopped answering), and
  the STOP hold wrapped below about 156 kHz (50 kHz: 1.6 us)
- on ESP32-C3 the SCL high wait overflowed below about 130 kHz, so the bus
  ran faster than requested (100 kHz setting -> 151 kHz measured)
- below about 39 kHz the SCL low / high periods wrapped as well

Cap the cycle so that both SCL halves fit (the slowest bus on these chips
is now about 39 kHz from a 40 MHz XTAL), and saturate each timing value at
its field width. The STOP hold is still derived from the START / STOP
value after the 1023 limit, as before. Values that already fit are
unchanged: ESP32 / S2 are not affected, and at 400 kHz nothing changes on
any chip. At 100 kHz the SCL
clock stays the same on the 9 bit chips other than C3, and only the STOP
hold grows from the wrapped 7.2 us to 12.8 us.
@lovyan03
lovyan03 merged commit 07f4c55 into m5stack:develop Oct 22, 2026
25 checks passed
@ainyan03
ainyan03 deleted the i2c_timing_width branch October 22, 2026 03:48
@ainyan03 ainyan03 mentioned this pull request Oct 25, 2026
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.

2 participants