I2C SCL frequency 10% less than it should be at 400kHz
Posted: Fri Mar 03, 2017 6:37 am
I've been investigating this for a few days now, in both Arduino and in the IDF, and it's clear that the I2C clock significantly lags its set value. There's also another problem in terms of the duty cycle itself.
I've looked at the ESP32 I2C HAL code and the way the clock is set is pretty straightforward (it's actually over-simplified, as I'll talk about below). The base clock is APB_CLK_FREQ (which should be 80MHz), and the calculation of the total SCL cycle period comes directly from that (which is divided to become scl_high_period + scl_low_period). However, the net result is an actual I2C clock that's (at 400kHz) about 10% slower than it should be.
Below we see the comparison between the I2C clock as seen on a Particle Photon (which uses a typical ARM processor, the STM32F205RG) and the ESP32 Core Board v2. Both are connected to the same type of device (a 128x32 SSD1306-based OLED) with no other I2C loads. Each board is using its USB-derived 3.3V supply (shared grounds on the board and via USB) and each is running the same Arduino code (there's a small difference in how the frequency gets passed to the HAL, as the Photon uses a parameter to Wire.begin() while the ESP32 uses setFrequency() which passes the frequency to the HAL's i2cSetFrequency() method unmodified, and where it is directly used to set the clock periods):
The readings were taken first with a 100MHz logic analyzer, and then with a 100MHz scope (leaving the leads connected to both analyzers will otherwise a slight slowing of both clocks due to RC loading). We see the clock speed of the Photon is a solid 400kHz, while the ESP32 is closer to 363kHz (which is about 10% less than it ought to be).
I've crossed checked this by swapping the two displays (originally I was just using one display but it became clear the display wasn't the problem). Clock stretching isn't occurring (nor is this what it'd look like).
Most notably, this I2C display works particularly poorly with the ESP32 - it becomes glitchy within a minute if not seconds, and eventually requires a hard reset. The display has no such problems with the Photon. It's a generic display and isn't specced for either device in particular, it's just supposed to work with I2C up to 400kHz. In fact, I have as much trouble at 100kHz, it just takes a little longer to become evident.
Now, it might be a sensitive device but again, these displays works quite solidly with the Photon. The only glitch I've seen is when SCL was abnormally loaded (which one would expect to be a problem for any I2C device). The fact that it's so erratic with the ESP32 led me to investigate the I2C signal strength, shape, and the code involved. In addition to the frequency being odd, I noticed something else - the duty cycle.
The typical duty cycle for I2C at 100kHz and 400kHz is not normally 1:1 but closer to 1:2. I'm working on making the appropriate updates to the Arduino-ESP32 and ESP-IDF HALs for this, to better match standard I2C devices and properties, but the current respective HALs use the 1:1 duty cycle across the board, which led me to wonder if this is part of the problem.
Preliminary code to correct the duty cycle shows a waveform more in line with expectation (ignore the analog frequencies shown - the respective values are lower due to effect of probing with both DLA and scope):
The clock frequency problem is vexing and persistent. I can't tell if it's some internal clock that's incorrectly set or if the lines assigned to I2C on the ESP32 are loaded incorrectly internally (either a hardware problem or a pin config issue). As we see in the mod above, when I loaded the respective devices with both probes the clocks both slowed somewhat. An I2C master will de-rate the clock based on the RC characteristics of the load it's driving, so this isn't unexpected... but it makes me wonder if the ESP32 is overcompensating for some reason in a way the Photon isn't (for a given external load).
I also tried this with the ESP-WROVER-KIT off to the side and saw the same exact issues (the same kinds of glitches and timing discrepancies). I also tested with with some native ESP IDF I2C code - same problem (though not surprisingly, the ESP32 I2C HAL for the IDF and the Arduino are *very* similar when it comes to setting the frequency and other I2C parameters).
tl;dr - The I2C on the ESP32 underclocks significantly versus its set clock speed, and it also has a duty cycle that seems to be out of spec. I'm working to understand both issues, but while I am able to correct the duty cycle and other timing issues relative to the set clock speed in code, the fact that the I2C clock is significantly slower appears to be either a hardware issue or a pin config issue and thus is a different bug of some sort.
Edit: Both analyzers used (Owon SDS7102 scope using the 10x attenuation setting and the Saleae Logic Pro 16) present a load of about 1Mohm at 10pF. With both devices hooked up at once the load should be about 500Kohm and 20pF. While that doesn't appreciably affect the resistive load (the pull-ups are 4.7Kohm on SDA and SCL respectively) it's a potentially significant capacitive load. I did notice the I2C lines between the ESP32 and the display is very sensitive to capacitive effects when it comes to glitchiness. The Photon seems immune to that, which again makes me wonder why the ESP32 appears to be more sensitive to this and whether it's some pin setting or a silicon problem.
I've looked at the ESP32 I2C HAL code and the way the clock is set is pretty straightforward (it's actually over-simplified, as I'll talk about below). The base clock is APB_CLK_FREQ (which should be 80MHz), and the calculation of the total SCL cycle period comes directly from that (which is divided to become scl_high_period + scl_low_period). However, the net result is an actual I2C clock that's (at 400kHz) about 10% slower than it should be.
Below we see the comparison between the I2C clock as seen on a Particle Photon (which uses a typical ARM processor, the STM32F205RG) and the ESP32 Core Board v2. Both are connected to the same type of device (a 128x32 SSD1306-based OLED) with no other I2C loads. Each board is using its USB-derived 3.3V supply (shared grounds on the board and via USB) and each is running the same Arduino code (there's a small difference in how the frequency gets passed to the HAL, as the Photon uses a parameter to Wire.begin() while the ESP32 uses setFrequency() which passes the frequency to the HAL's i2cSetFrequency() method unmodified, and where it is directly used to set the clock periods):
The readings were taken first with a 100MHz logic analyzer, and then with a 100MHz scope (leaving the leads connected to both analyzers will otherwise a slight slowing of both clocks due to RC loading). We see the clock speed of the Photon is a solid 400kHz, while the ESP32 is closer to 363kHz (which is about 10% less than it ought to be).
I've crossed checked this by swapping the two displays (originally I was just using one display but it became clear the display wasn't the problem). Clock stretching isn't occurring (nor is this what it'd look like).
Most notably, this I2C display works particularly poorly with the ESP32 - it becomes glitchy within a minute if not seconds, and eventually requires a hard reset. The display has no such problems with the Photon. It's a generic display and isn't specced for either device in particular, it's just supposed to work with I2C up to 400kHz. In fact, I have as much trouble at 100kHz, it just takes a little longer to become evident.
Now, it might be a sensitive device but again, these displays works quite solidly with the Photon. The only glitch I've seen is when SCL was abnormally loaded (which one would expect to be a problem for any I2C device). The fact that it's so erratic with the ESP32 led me to investigate the I2C signal strength, shape, and the code involved. In addition to the frequency being odd, I noticed something else - the duty cycle.
The typical duty cycle for I2C at 100kHz and 400kHz is not normally 1:1 but closer to 1:2. I'm working on making the appropriate updates to the Arduino-ESP32 and ESP-IDF HALs for this, to better match standard I2C devices and properties, but the current respective HALs use the 1:1 duty cycle across the board, which led me to wonder if this is part of the problem.
Preliminary code to correct the duty cycle shows a waveform more in line with expectation (ignore the analog frequencies shown - the respective values are lower due to effect of probing with both DLA and scope):
The clock frequency problem is vexing and persistent. I can't tell if it's some internal clock that's incorrectly set or if the lines assigned to I2C on the ESP32 are loaded incorrectly internally (either a hardware problem or a pin config issue). As we see in the mod above, when I loaded the respective devices with both probes the clocks both slowed somewhat. An I2C master will de-rate the clock based on the RC characteristics of the load it's driving, so this isn't unexpected... but it makes me wonder if the ESP32 is overcompensating for some reason in a way the Photon isn't (for a given external load).
I also tried this with the ESP-WROVER-KIT off to the side and saw the same exact issues (the same kinds of glitches and timing discrepancies). I also tested with with some native ESP IDF I2C code - same problem (though not surprisingly, the ESP32 I2C HAL for the IDF and the Arduino are *very* similar when it comes to setting the frequency and other I2C parameters).
tl;dr - The I2C on the ESP32 underclocks significantly versus its set clock speed, and it also has a duty cycle that seems to be out of spec. I'm working to understand both issues, but while I am able to correct the duty cycle and other timing issues relative to the set clock speed in code, the fact that the I2C clock is significantly slower appears to be either a hardware issue or a pin config issue and thus is a different bug of some sort.
Edit: Both analyzers used (Owon SDS7102 scope using the 10x attenuation setting and the Saleae Logic Pro 16) present a load of about 1Mohm at 10pF. With both devices hooked up at once the load should be about 500Kohm and 20pF. While that doesn't appreciably affect the resistive load (the pull-ups are 4.7Kohm on SDA and SCL respectively) it's a potentially significant capacitive load. I did notice the I2C lines between the ESP32 and the display is very sensitive to capacitive effects when it comes to glitchiness. The Photon seems immune to that, which again makes me wonder why the ESP32 appears to be more sensitive to this and whether it's some pin setting or a silicon problem.