I2C maximum speed

pd0lew

Well-known member
Hi all,

For my projects I must know the maximum speed of the I2C bus Teensy 4 this is needed to read my external A/D converter.

If someone know please replay.

Best regards,
Johan
 
If you use the Wire library, 1 MHz is the fastest speed it supports. Of course, you'll need strong pullup resistors and moderate to short length wires and not many chips connected for the signals to actually work that fast.

If you hack the WireIMXRT.cpp or directly write to the MCCR0, MCFGR1, MCFGR2 registers, I hope you'll share anything you learn about the actual practical speed limits. The speed setting in this new chip isn't just 1 simple number. It's a bunch of settings that control various timing aspects of the waveform. Details can be found in the chip's reference manual.

Here's the code you'll find inside WireIMXRT.cpp:

Code:
        if (frequency < 400000) {
                // 100 kHz
                port->MCCR0 = LPI2C_MCCR0_CLKHI(55) | LPI2C_MCCR0_CLKLO(59) |
                        LPI2C_MCCR0_DATAVD(25) | LPI2C_MCCR0_SETHOLD(40);
                port->MCFGR1 = LPI2C_MCFGR1_PRESCALE(1);
                port->MCFGR2 = LPI2C_MCFGR2_FILTSDA(5) | LPI2C_MCFGR2_FILTSCL(5) |
                        LPI2C_MCFGR2_BUSIDLE(3900);
        } else if (frequency < 1000000) {
                // 400 kHz
                port->MCCR0 = LPI2C_MCCR0_CLKHI(26) | LPI2C_MCCR0_CLKLO(28) |
                        LPI2C_MCCR0_DATAVD(12) | LPI2C_MCCR0_SETHOLD(18);
                port->MCFGR1 = LPI2C_MCFGR1_PRESCALE(0);
                port->MCFGR2 = LPI2C_MCFGR2_FILTSDA(2) | LPI2C_MCFGR2_FILTSCL(2) |
                        LPI2C_MCFGR2_BUSIDLE(3900);
        } else {
                // 1 MHz
                port->MCCR0 = LPI2C_MCCR0_CLKHI(9) | LPI2C_MCCR0_CLKLO(10) |
                        LPI2C_MCCR0_DATAVD(4) | LPI2C_MCCR0_SETHOLD(7);
                port->MCFGR1 = LPI2C_MCFGR1_PRESCALE(0);
                port->MCFGR2 = LPI2C_MCFGR2_FILTSDA(1) | LPI2C_MCFGR2_FILTSCL(1) |
                        LPI2C_MCFGR2_BUSIDLE(3900);
        }

If you reduce those numbers, it very likely will run faster. How good the waveforms really look, or how reliably it really works is a good question. The reality is Teensy 4.0 is very new. So far it's only been tested with those 3 settings. As far as I know, nobody using this forum has really tested it at other settings. I know I sure haven't. But I did run tests with all 3 of those settings during the beta test. 1 MHz definitely does work.
 
Hi all,

I like to run my AD7991 I2C on 1MHz Paul says change some numbers but what to change to get 1MHz for the I2C bus? In my case I use SDA1 an SCL1..

Best,
Johan
 
Hi, I don't think it's that simple for a Teensy 4.... see picture
about 100KHz

DeepinScreenshot_select-area_20190827192253.png
Best,
Johan
 
The maximum I can get is 375 KHz, instead of 1MHz.......


void TwoWire::setClock(uint32_t frequency)
{
port->MCR = 0;
if (frequency < 100000) {
// 100 kHz
port->MCCR0 = LPI2C_MCCR0_CLKHI(55) | LPI2C_MCCR0_CLKLO(59) |
LPI2C_MCCR0_DATAVD(25) | LPI2C_MCCR0_SETHOLD(40);
port->MCFGR1 = LPI2C_MCFGR1_PRESCALE(1);
port->MCFGR2 = LPI2C_MCFGR2_FILTSDA(5) | LPI2C_MCFGR2_FILTSCL(5) |
LPI2C_MCFGR2_BUSIDLE(3900);
} else if (frequency < 1000000) {
// 400 kHz
port->MCCR0 = LPI2C_MCCR0_CLKHI(26) | LPI2C_MCCR0_CLKLO(28) |
LPI2C_MCCR0_DATAVD(12) | LPI2C_MCCR0_SETHOLD(18);
port->MCFGR1 = LPI2C_MCFGR1_PRESCALE(0);
port->MCFGR2 = LPI2C_MCFGR2_FILTSDA(2) | LPI2C_MCFGR2_FILTSCL(2) |
LPI2C_MCFGR2_BUSIDLE(3900);
} else {
// 1 MHz
port->MCCR0 = LPI2C_MCCR0_CLKHI(9) | LPI2C_MCCR0_CLKLO(10) |
LPI2C_MCCR0_DATAVD(4) | LPI2C_MCCR0_SETHOLD(7);
port->MCFGR1 = LPI2C_MCFGR1_PRESCALE(0);
port->MCFGR2 = LPI2C_MCFGR2_FILTSDA(1) | LPI2C_MCFGR2_FILTSCL(1) |
LPI2C_MCFGR2_BUSIDLE(3900);
}
port->MCCR1 = port->MCCR0;
port->MCFGR0 = 0;
port->MCFGR3 = LPI2C_MCFGR3_PINLOW(3900);
port->MFCR = LPI2C_MFCR_RXWATER(1) | LPI2C_MFCR_TXWATER(1);
port->MCR = LPI2C_MCR_MEN;
}

#endif
 
Hi, I don't think it's that simple for a Teensy 4.... see picture
about 100KHz
Best,
Johan

Works for me. you need to do Wire1.setClock() after Wire1.begin()
With nothing connected to I2C pins and runnning I2C Scanner, scope shows 86 khz for clock 100khz, 238khz for clock 400khz, 380khz for clock 1mhz. With BMP085 breakout (10k pullups), scope shows 96KHz for I2C clock @100KHz, 325KHz for clock @400KHz, and 625KHz for clock 1MHz. Adding 2.2Kohm pullups in parallel on SDA and SCL, scope shows 99.2khz @100khz, 379khz @400khz, and 893khz @1mhz.
 
Last edited:
The maximum I can get is 375 KHz, instead of 1MHz.......
It's not uncommon for the actual clock rate to be less than requested on T3* and T4 and maybe many other MCU's. You can try and hack the core I2C driver code to increase the I2C clock closer to your target (read ref manual). But as Paul noted, changing I2C clock may also require changing high and low times for SDA and SCL waveforms.

early in T4 beta testing, i experimented with I2C clock (pins 18,19) see
https://forum.pjrc.com/threads/54711-Teensy-4-0-First-Beta-Test?p=194538&viewfull=1#post194538
scope measured 926KHz with I2C@1MHz
 
Last edited:
I think there can be a clear answer to the I2C bus instead of reading 1000 pages.

This needs more research....

DeepinScreenshot_select-area_20190827202305.jpg
 
I2C can be pretty finicky. In your graph it looks like you have some bus capacitance which will interfere with the signal.
 
Currently Wire.setClock supports up to 1 MBit. To go faster, you'll need to edit the Wire library or write directly to the hardware registers.

Months ago some work was done to configure higher speeds, including running the I2C hardware from a 60 MHz clock from the USB PLL, instead of directly from the 24 MHz oscillator. Maybe you can find that with the forum search? Or maybe Kurt or Defragster will see this and comment.

However, that work (as far as I know) was merely to increase the SCL clock speed. I2C high speed mode is a special protocol, not just the same thing at faster clock rates. Teensy 4.0's I2C hardware can do HS mode. But there is currently no software support. For example, if you look at page 2780 in the reference manual (IMXRT1060RM_rev2.pdf), you'll see the MTDR register's CMD field takes special commands to initiate HS mode. So far, the Wire library has no support for using those special commands. It only uses the normal one (100b) for all data transfer. Maybe someday the library will be extended to support Wire.setClock(3400000) and selecting that speed will cause the rest of the library to use those special commands. But so far, all work that's been attempted has simply increased the clock speeds without doing this special protocol stuff that's supposed to be used for HS mode. (and that is the reason why I've been reluctant to merge those changes into the Wire lib)

Of course, you'll also need hardware capable of such speeds. That means lower value pullup resistors and keeping the capacitance on the SDA & SCL lines as low as possible. I believe HS mode is supposed to be done with a special active current source circuit, rather than a passive resistor.

But using the normal Wire library we publish today, the fastest supported speed is 1 Mbit/sec.
 
I have been able to push SSD1306 OLEDs to run at 1-2Mhz on other MCUs, so I wanted to see how far I could get with the Teensy 4. I did some experiments with the clock values and was able to get what appears to be a stable 2MHz. Here's the code change to WireIMXRT.cpp:

Code:
// 2 MHz
                port->MCCR0 = LPI2C_MCCR0_CLKHI(3) | LPI2C_MCCR0_CLKLO(4) |
                        LPI2C_MCCR0_DATAVD(2) | LPI2C_MCCR0_SETHOLD(3);
                port->MCFGR1 = LPI2C_MCFGR1_PRESCALE(0);
                port->MCFGR2 = LPI2C_MCFGR2_FILTSDA(1) | LPI2C_MCFGR2_FILTSCL(1) |
                        LPI2C_MCFGR2_BUSIDLE(1200); // idle timeout 50 us
                port->MCFGR3 = LPI2C_MCFGR3_PINLOW(CLOCK_STRETCH_TIMEOUT * 24 / 256 + 1);

Here's a video of it in action:

 
I have been able to push SSD1306 OLEDs to run at 1-2Mhz on other MCUs, so I wanted to see how far I could get with the Teensy 4. I did some experiments with the clock values and was able to get what appears to be a stable 2MHz. Here's the code change to WireIMXRT.cpp:

Code:
// 2 MHz
                port->MCCR0 = LPI2C_MCCR0_CLKHI(3) | LPI2C_MCCR0_CLKLO(4) |
                        LPI2C_MCCR0_DATAVD(2) | LPI2C_MCCR0_SETHOLD(3);
                port->MCFGR1 = LPI2C_MCFGR1_PRESCALE(0);
                port->MCFGR2 = LPI2C_MCFGR2_FILTSDA(1) | LPI2C_MCFGR2_FILTSCL(1) |
                        LPI2C_MCFGR2_BUSIDLE(1200); // idle timeout 50 us
                port->MCFGR3 = LPI2C_MCFGR3_PINLOW(CLOCK_STRETCH_TIMEOUT * 24 / 256 + 1);

Here's a video of it in action:

 
Just seeing this with Paul's March post ... post #13 above has the pull request #17 to speed up i2c ... that was replaced by PR #18

Seems SSD1306 was running at the "3" mhz speed of 2.5 Mhz actual.

That change didn't get incorporated ... IIRC because: Picking the higher base clock reduced access to lower 100 Mhz clock rate
 
I would like to see this become 3 fixed cases for the 3 official I2C speeds, and "else" code that tries to compute values for those registers for any unofficial speed. But maybe adding more fixed cases for now won't hurt too much, just slightly larger code size?

A concern I have is long-term API planning for the Wire library to someday support I2C high speed mode. HS mode implies a different protocol, where a start byte is transmitted at 400 kHz. Then the speed switches to much faster (up to 3.4 Mbit according to the spec) with some changes to the protocol, mostly electrical and when clock stretching is allowed. If we start supporting faster than 1 Mbit speed now (the highest the I2C standard says is allowed for the normal protocol), then in the future we can't use only setClock() to activate this special high speed mode.
 
I would like to see this become 3 fixed cases for the 3 official I2C speeds, and "else" code that tries to compute values for those registers for any unofficial speed. But maybe adding more fixed cases for now won't hurt too much, just slightly larger code size?

A concern I have is long-term API planning for the Wire library to someday support I2C high speed mode. HS mode implies a different protocol, where a start byte is transmitted at 400 kHz. Then the speed switches to much faster (up to 3.4 Mbit according to the spec) with some changes to the protocol, mostly electrical and when clock stretching is allowed. If we start supporting faster than 1 Mbit speed now (the highest the I2C standard says is allowed for the normal protocol), then in the future we can't use only setClock() to activate this special high speed mode.

Here's a screenshot of the I2C clock running at ~2MHz with the settings as above. Note that this is with weak 4k7 pullups hence the inadequate rise-time. I'll be doing some experiments with the clock speed in non-HS mode and a bus with 8 x MCP4728 DACs. These DACs support HS mode, but as Paul noted you need to enable this with slower commands...
image_2020-10-23_203237.jpg
 
Currently Wire.setClock supports up to 1 MBit. To go faster, you'll need to edit the Wire library or write directly to the hardware registers.

Months ago some work was done to configure higher speeds, including running the I2C hardware from a 60 MHz clock from the USB PLL, instead of directly from the 24 MHz oscillator. Maybe you can find that with the forum search? Or maybe Kurt or Defragster will see this and comment.

However, that work (as far as I know) was merely to increase the SCL clock speed. I2C high speed mode is a special protocol, not just the same thing at faster clock rates. Teensy 4.0's I2C hardware can do HS mode. But there is currently no software support. For example, if you look at page 2780 in the reference manual (IMXRT1060RM_rev2.pdf), you'll see the MTDR register's CMD field takes special commands to initiate HS mode. So far, the Wire library has no support for using those special commands. It only uses the normal one (100b) for all data transfer. Maybe someday the library will be extended to support Wire.setClock(3400000) and selecting that speed will cause the rest of the library to use those special commands. But so far, all work that's been attempted has simply increased the clock speeds without doing this special protocol stuff that's supposed to be used for HS mode. (and that is the reason why I've been reluctant to merge those changes into the Wire lib)

Of course, you'll also need hardware capable of such speeds. That means lower value pullup resistors and keeping the capacitance on the SDA & SCL lines as low as possible. I believe HS mode is supposed to be done with a special active current source circuit, rather than a passive resistor.

But using the normal Wire library we publish today, the fastest supported speed is 1 Mbit/sec.


Here's a T4.1 using Wire1 to drive a Quad DAC MCP4728 at 1MHz I2C clock speed. Often the I2C bus only states 400kHz max speed but faster rates work fine. In this case, 1MHz works with a total FastWrite (4 x 12-bit DACs) taking ~85uS. I'll try and push the DAC to 2MHz and see how we get on...

image_2020-10-24_022200.jpg
 
Hi Paul,
Is there any change that the I2C bus will run on high speed at 3.4 MHz soon, for my projects it would be very nice to have!

Thanks and best regards,
Johan
 
Microchip lists the three speed ranges for their MCP23017 as 100khz (1.8-5.5v), 400khz (2.7-5.5v) and 1.7mhz (4.5-5.5v). BUT the teensy4.x is not 5v tolerant. SO to run the MCP23017 at 5v (needed for IT to drive 5v GPIO), I'll have to hang a pair of 2N7000's (or similar nch MOSFETS) and pull up resistors on the SCL lines to the MCP23017. I wonder if it will work at 1mhz with the T4.x? I'm going to use the I2C expander to drive a 64x192 GLCD that has a parallel interface (the E* pin will also be driven by a 2N7000 level converter as I don't want THAT pin being toggled by the I2C expander for throughput reasons.)
 
I have been able to push SSD1306 OLEDs to run at 1-2Mhz on other MCUs, so I wanted to see how far I could get with the Teensy 4. I did some experiments with the clock values and was able to get what appears to be a stable 2MHz. Here's the code change to WireIMXRT.cpp:

Code:
// 2 MHz
                port->MCCR0 = LPI2C_MCCR0_CLKHI(3) | LPI2C_MCCR0_CLKLO(4) |
                        LPI2C_MCCR0_DATAVD(2) | LPI2C_MCCR0_SETHOLD(3);
                port->MCFGR1 = LPI2C_MCFGR1_PRESCALE(0);
                port->MCFGR2 = LPI2C_MCFGR2_FILTSDA(1) | LPI2C_MCFGR2_FILTSCL(1) |
                        LPI2C_MCFGR2_BUSIDLE(1200); // idle timeout 50 us
                port->MCFGR3 = LPI2C_MCFGR3_PINLOW(CLOCK_STRETCH_TIMEOUT * 24 / 256 + 1);

Here's a video of it in action:


Hello
I need to push my lcd too I use teensy 3,2
Can you please told me what library you use?
And were this file is located?
 
Back
Top