New I2C library for Teensy3

i will try it tonight, i only add the red line right? i already have i2c auto retry defined as well

Ok. Try the resetBus() routine that hw999 posted. It is unclear to me what condition requires the I2C module to be disabled and re-enabled, but it if works for a larger set of error conditions then it is better to do it that way. Once a patch is determined I'll do a new release.
 
i used to get lockups within an hour or so, so far 3 hours still working, just to let you guys know this is being tested 24/7 in noisy environment in -20 temperatures, using only Wire1 with 12 I2C devices attached with 10k pullups. like (mcp23017x5,pca9685x4,ds3231x1,24lc256x2) ill be home later after work to post a follow up

note: i2c auto retry is still defined fyi for this test
 
Last edited:
Ok. Try the resetBus() routine that hw999 posted. It is unclear to me what condition requires the I2C module to be disabled and re-enabled, but it if works for a larger set of error conditions then it is better to do it that way. Once a patch is determined I'll do a new release.

Hi Brian
I check for return of 0 after endTransmission and number of received bytes after requestFrom. If these values are not what I expect I call the original resetBus. After this I check the status register (4006_6003 for bus 0) for a value of 1000 0000. I obeserved that the value to correspond with I2C_WAITING, it might be also 0000 0000. I do

if (i2cstatus & (uint8_t)0x7F){

and eventually do

*(i2c->C1) = (uint8_t)0x00;
delayMicroseconds(5);
*(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled
delayMicroseconds(5);

This resets the registers. I overlooked yesterday that I called i2c_t3.begin afterwards.

I let this run on a T32 and T36 with the same slaves, but different bus quality. On the T32 I counted around 7million read and 11 million write operations - which resulted in 5 resets, 4 of them were solved hard with disabling the i2c module. On the T36 I counted more than 50000 resets, of which more than 1700 were resolved hard.
 
Last edited:
Hi Brian

I was checking the bits out of the status register right before and after begin-/endTransmission and requestFrom.

begin-/endTransmission T32 and T36 - before 1000 0000 after 1000 0000

requestFrom T32 before : 1000 0000 after 1000 0101

requestFrom T36 before : 1000 0000 after 1000 0100

Looks like this is normal operation but I wonder why it is not 1000 0000 after the requestFrom and why there is a difference between T32 and T36.

Do you get similar results ?
 
its still working!! :) ill keep you guys posted if it ever fails, but now it runs nice now,

id like to mention when i was using a mega2560 i was using a modified wire library with timeouts which ran fine before i converted the project over to teensy, this is been driving me crazy for a long while but i never had the bus continually running on the teensy until now. if you do chabge any of your code later ill let you know the updates

hw999 im pretty sure im giving this mod a good run, in an automotive field, considering the amount of chips and registers are constantly accessed/written every cycle, most wiring is 22 gauge
 
Last edited:
I was checking the bits out of the status register right before and after begin-/endTransmission and requestFrom.
begin-/endTransmission T32 and T36 - before 1000 0000 after 1000 0000
requestFrom T32 before : 1000 0000 after 1000 0101
requestFrom T36 before : 1000 0000 after 1000 0100
Looks like this is normal operation but I wonder why it is not 1000 0000 after the requestFrom and why there is a difference between T32 and T36.

The SRW bit (Status bit 2) will be set when the Master receives data from the Slave. That is normal. The RXAK bit (Status bit 0) is used to check for Slave acknowledge, and it may be set as a artifact of earlier transfers. For many of these bits their value is only relevant at certain points in time. Depending on how the HW is designed they may or may not reset, but they should always update at the proper times. So I wouldn't be too concerned with residual values in bits like these.

Thanks for all the testing guys. Making the communication channel robust to transmission errors is a really big deal. This really helps out a lot. I'll update the resetBus() routine to the following, and upload a new library:
Code:
void i2c_t3::resetBus_(struct i2cStruct* i2c, uint8_t bus)
{
    uint8_t scl=0, sda=0, count=0;

    switch(i2c->currentPins)
    {
    case I2C_PINS_16_17: sda = 17; scl = 16; break;
    case I2C_PINS_18_19: sda = 18; scl = 19; break;
    #if defined(__MK64FX512__) || defined(__MK66FX1M0__)  // 3.5/3.6
    case I2C_PINS_7_8:   sda =  8; scl =  7; break;
    case I2C_PINS_33_34: sda = 34; scl = 33; break;
    case I2C_PINS_47_48: sda = 48; scl = 47; break;
    #endif
    #if defined(__MKL26Z64__) // LC
    case I2C_PINS_22_23: sda = 23; scl = 22; break;
    #endif
    #if defined(__MK20DX256__) // 3.1/3.2
    case I2C_PINS_29_30: sda = 30; scl = 29; break;
    case I2C_PINS_26_31: sda = 31; scl = 26; break;
    #endif
    #if defined(__MK64FX512__) || defined(__MK66FX1M0__)  // 3.5/3.6
    case I2C_PINS_37_38: sda = 38; scl = 37; break;
    case I2C_PINS_3_4:   sda =  4; scl =  3; break;
    #endif
    #if defined(__MK66FX1M0__) // 3.6
    case I2C_PINS_56_57: sda = 56; scl = 57; break;
    #endif
    }
    if(sda && scl)
    {
        // change pin mux to digital I/O
        pinMode(sda,((i2c->currentPullup == I2C_PULLUP_EXT) ? INPUT : INPUT_PULLUP));
        digitalWrite(scl,HIGH);
        pinMode(scl,OUTPUT);

        while(digitalRead(sda) == 0 && count++ < 10)
        {
            digitalWrite(scl,LOW);
            delayMicroseconds(5);       // 10us period == 100kHz
            digitalWrite(scl,HIGH);
            delayMicroseconds(5);
        }

        // reconfigure pins for I2C
        pinConfigure_(i2c, bus, i2c->currentPins, i2c->currentPullup, 0);

        // reset config and status
        if(*(i2c->S) & 0x7F) // reset config if any residual status bits are set
        {
            *(i2c->C1) = 0x00; // disable I2C, intr disabled
            delayMicroseconds(5);
            *(i2c->C1) = I2C_C1_IICEN; // enable I2C, intr disabled, Rx mode
            delayMicroseconds(5);
        }
        i2c->currentStatus = I2C_WAITING;
    }
}
 
The SRW bit (Status bit 2) will be set when the Master receives data from the Slave. That is normal. The RXAK bit (Status bit 0) is used to check for Slave acknowledge, and it may be set as a artifact of earlier transfers. For many of these bits their value is only relevant at certain points in time. Depending on how the HW is designed they may or may not reset, but they should always update at the proper times. So I wouldn't be too concerned with residual values in bits like these.

Thanks for information. In this case the mask byte could be adjusted. In my coding I also called i2c_t3.begin to restore registers. I am pretty sure that disabling the i2c module resets all its registers.

May I make some wishes ?
- a counter for soft and hard resets each and a routine for accessing these values. Helps a lot for assessing the bus quality.
- a routine that gives the bits out of the status register. Helps for debugging.

I have this stuff coded partly inside and outside i2c_t3 and used it a lot. Well - I think it would be better to have all in one place.

I know - small stuff that has to be done first. If I can help - let me know.

Andi
 
Last edited:
Thanks for information. In this case the mask byte could be adjusted. In my coding I also called i2c_t3.begin to restore registers. I am pretty sure that disabling the i2c module resets all its registers.

I tested the registers and disabling/re-enabling the module does not appear to change the settings. Specifically I added these lines around the new section (the if() is commented out to force the code to run):
Code:
        // reconfigure pins for I2C
        pinConfigure_(i2c, bus, i2c->currentPins, i2c->currentPullup, 0);

        // reset config and status
        //if(*(i2c->S) & 0x7F) // reset config if any residual status bits are set
        //{
            [COLOR=#ff0000]Serial.printf("%02X %02X %02X %02X %02X %02X %02X %02X %02X\n",*(i2c->A1),*(i2c->F),*(i2c->C1),*(i2c->S),*(i2c->D),*(i2c->C2),*(i2c->FLT),*(i2c->RA),*(i2c->A2));[/COLOR]
            *(i2c->C1) = 0x00; // disable I2C, intr disabled
            delayMicroseconds(5);
            *(i2c->C1) = I2C_C1_IICEN; // enable I2C, intr disabled, Rx mode
            delayMicroseconds(5);
            [COLOR=#ff0000]Serial.printf("%02X %02X %02X %02X %02X %02X %02X %02X %02X\n",*(i2c->A1),*(i2c->F),*(i2c->C1),*(i2c->S),*(i2c->D),*(i2c->C2),*(i2c->FLT),*(i2c->RA),*(i2c->A2));[/COLOR]
        //}
        i2c->currentStatus = I2C_WAITING;

This yields the following (modified basic_master sketch which calls resetBus() after transmit):
Code:
Sending to Slave: 'Data Message #0' OK
00 45 80 80 00 20 52 00 C2
00 45 80 80 00 20 02 00 C2
Sending to Slave: 'Data Message #1' OK
00 45 80 80 00 20 52 00 C2
00 45 80 80 00 20 02 00 C2
Sending to Slave: 'Data Message #2' OK
00 45 80 80 00 20 52 00 C2
00 45 80 80 00 20 02 00 C2
...
This indicates the registers are retaining their values. The FLT register is different, but that is due to STARTF and STOPF detects which will get cleared (FYI - FLT register docs are wrong on LC, the K66 docs have a proper description). I don't want to call begin() unnecessarily. If there is some other evidence it needs to be there let me know.

May I make some wishes ?
- a counter for soft and hard resets each and a routine for accessing these values. Helps a lot for assessing the bus quality.
- a routine that gives the bits out of the status register. Helps for debugging.

I have this stuff coded partly inside and outside i2c_t3 and used it a lot. Well - I think it would be better to have all in one place.

A more general diagnostic routine could be added, but it is not clear what form it would take. The i2c structure is a public member specifically so info could be taken out of it. For instance instead of the above code inside i2c_t3.cpp I can instead put this in my main code and get the same result:
Code:
        Serial.printf("%02X %02X %02X %02X %02X %02X %02X %02X %02X\n",
                      *(Wire.i2c->A1),*(Wire.i2c->F),*(Wire.i2c->C1),
                      *(Wire.i2c->S),*(Wire.i2c->D),*(Wire.i2c->C2),
                      *(Wire.i2c->FLT),*(Wire.i2c->RA),*(Wire.i2c->A2));
        Wire.resetBus();
        Serial.printf("%02X %02X %02X %02X %02X %02X %02X %02X %02X\n",
                      *(Wire.i2c->A1),*(Wire.i2c->F),*(Wire.i2c->C1),
                      *(Wire.i2c->S),*(Wire.i2c->D),*(Wire.i2c->C2),
                      *(Wire.i2c->FLT),*(Wire.i2c->RA),*(Wire.i2c->A2));
 
hi nox, the patch works good but i still do get a lockup here or there, just not as often as before the patch
restarting the teensy fixes it, even if the bus locks up, i know the mcu didnt, because i get data to/from the spi expanders fine during that period. ive set my mcp23017 on the i2c bus(direct without libraries) to monitor their registers (inputs outputs), and if they dont match a hardcoded value to reinitialize them. even with the chip recover code it wont come back online, so im pretty sure its a bus issue

im using 4.7k now puullups, before 10k, with and without level shifter (T3.5) ive even dropped the i2c bus speed to 10khz (10000 in Wire1 begin)i will see how it progresses
 
Last edited:
hi nox, the patch works good but i still do get a lockup here or there, just not as often as before the patch
restarting the teensy fixes it, even if the bus locks up, i know the mcu didnt, because i get data to/from the spi expanders fine during that period. ive set my mcp23017 on the i2c bus(direct without libraries) to monitor their registers (inputs outputs), and if they dont match a hardcoded value to reinitialize them. even with the chip recover code it wont come back online, so im pretty sure its a bus issue

im using 4.7k now puullups, before 10k, with and without level shifter (T3.5) ive even dropped the i2c bus speed to 10khz (10000 in Wire1 begin)i will see how it progresses

Try this resetBus_() function, just copy/paste in place of the original, or modify as shown in red (at the bottom - comment out the first section and add the three lines at the end). This will force reinitialize everything on a resetBus() call. I'm not sure if it will change anything, but I would be interested to know if it works:

Code:
void i2c_t3::resetBus_(struct i2cStruct* i2c, uint8_t bus)
{
    uint8_t scl=0, sda=0, count=0;

    switch(i2c->currentPins)
    {
    case I2C_PINS_16_17: sda = 17; scl = 16; break;
    case I2C_PINS_18_19: sda = 18; scl = 19; break;
    #if defined(__MK64FX512__) || defined(__MK66FX1M0__)  // 3.5/3.6
    case I2C_PINS_7_8:   sda =  8; scl =  7; break;
    case I2C_PINS_33_34: sda = 34; scl = 33; break;
    case I2C_PINS_47_48: sda = 48; scl = 47; break;
    #endif
    #if defined(__MKL26Z64__) // LC
    case I2C_PINS_22_23: sda = 23; scl = 22; break;
    #endif
    #if defined(__MK20DX256__) // 3.1/3.2
    case I2C_PINS_29_30: sda = 30; scl = 29; break;
    case I2C_PINS_26_31: sda = 31; scl = 26; break;
    #endif
    #if defined(__MK64FX512__) || defined(__MK66FX1M0__)  // 3.5/3.6
    case I2C_PINS_37_38: sda = 38; scl = 37; break;
    case I2C_PINS_3_4:   sda =  4; scl =  3; break;
    #endif
    #if defined(__MK66FX1M0__) // 3.6
    case I2C_PINS_56_57: sda = 56; scl = 57; break;
    #endif
    }
    if(sda && scl)
    {
        // change pin mux to digital I/O
        pinMode(sda,((i2c->currentPullup == I2C_PULLUP_EXT) ? INPUT : INPUT_PULLUP));
        digitalWrite(scl,HIGH);
        pinMode(scl,OUTPUT);

        while(digitalRead(sda) == 0 && count++ < 10)
        {
            digitalWrite(scl,LOW);
            delayMicroseconds(5);       // 10us period == 100kHz
            digitalWrite(scl,HIGH);
            delayMicroseconds(5);
        }

        // reconfigure pins for I2C
        pinConfigure_(i2c, bus, i2c->currentPins, i2c->currentPullup, 0);

[COLOR=#ff0000]//        // reset config and status
//        if(*(i2c->S) & 0x7F) // reset config if any residual status bits are set
//        {
//            *(i2c->C1) = 0x00; // disable I2C, intr disabled
//            delayMicroseconds(5);
//            *(i2c->C1) = I2C_C1_IICEN; // enable I2C, intr disabled, Rx mode
//            delayMicroseconds(5);
//        }
//        i2c->currentStatus = I2C_WAITING;

        *(i2c->C1) = 0x00; // disable I2C, intr disabled
        delayMicroseconds(5);
        begin_(i2c, bus, i2c->currentMode, *(i2c->A1), *(i2c->RA), i2c->currentPins, i2c->currentPullup, i2c->currentRate, i2c->opMode);
[/COLOR]    }
}
 
one more thing i wanted to ask nox, if i use i2c auto retry define do i still have to use Wire1.resetBus()? i was never using it (under impression it was implied automatic) but what i did so far was i have my code verify is my chip expanders registers are wrong, if so, i call Wire1.resetBus(); right before updating the chips registers followed by a return; then on next cycle the registers are verified again, if registers are good, before processing the expanders inputs/outputs. if it fails ill try the above code and keep you updated thanks
 
Last edited:
i upped the frequency to 100khz instead of 10khz and will continue using hw999's patch over the weekend, i will let you know updates, the code is working so far with the implementation of resetBus in my code that verifies the register, i dont think i2c auto retry define worked since its not locking up anymore when i put the resetBus in the failed register check trigger of the i2c chip recover code, resetBus only gets called on a chip register failed compare attempt. this is looking good so far, the best part is i only have to implement it on 1 i2c device function, all other devices will be fine on the bus after
 
Can you post what resetBus_() code you are using? The auto retry is not a robust error recovery system in itself. All it is doing is this - if a Master device tries to send a command and SDA is stuck low (causing a bus busy flag), it will try to unstick the Slave holding it low, and then reinitialize the bus. It only happens once, and if that fails it signals a timeout. It is difficult to code a general case recovery system, since it is so application dependent. The routine you are using to verify registers in your system is more robust for your application so that is certainly the way to go. There are multiple slightly different variants of the resetBus_() procedure, so if you could post exactly what you are using I'd like to see that.
 
nox771, its still working good with your v9.2, just wanted to keep you updated

im not using define i2c_auto_retry and im verifying the mcp23017 registers using Wire1.resetBus(); if they dont match.

all in all i havnt had any issues since thx :)
 
How to update the documentation for i2c_t3?

Specifically, the doc for Wire.status() does not match the actual code in i2c_t3.h, so the status return values in this document are wrong. There are some other tweaks to the document I'd like to make to clarify some things such as how endTransmission() calls finish() which calls done() so there is no point in doing so yourself if you have already called endTransmission(), and so on. If that would be helpful...

@nox771 Thanks again for an awesome library!
 
Specifically, the doc for Wire.status() does not match the actual code in i2c_t3.h, so the status return values in this document are wrong. There are some other tweaks to the document I'd like to make to clarify some things such as how endTransmission() calls finish() which calls done() so there is no point in doing so yourself if you have already called endTransmission(), and so on. If that would be helpful...

@nox771 Thanks again for an awesome library!

What document are you referring to? I see a minor discrepancy, with I2C_BUF_OVF omitted near the end, is this what you are talking about?

i2c_t3.h has this:
Code:
    // ------------------------------------------------------------------------------------------------------
    // Return Status - returns current status of I2C (enum return value)
    // return: I2C_WAITING, I2C_SENDING, I2C_SEND_ADDR, I2C_RECEIVING,
    //         I2C_TIMEOUT, I2C_ADDR_NAK, I2C_DATA_NAK, I2C_ARB_LOST,
    //         I2C_SLAVE_TX, I2C_SLAVE_RX
    //
    inline i2c_status status(void) { return i2c->currentStatus; }

Of which currentStatus is an enum with this definition:
Code:
enum i2c_status   {I2C_WAITING,
                   I2C_SENDING,
                   I2C_SEND_ADDR,
                   I2C_RECEIVING,
                   I2C_TIMEOUT,
                   I2C_ADDR_NAK,
                   I2C_DATA_NAK,
                   I2C_ARB_LOST,
                   I2C_BUF_OVF,
                   I2C_SLAVE_TX,
                   I2C_SLAVE_RX};

It is only documented in two places (aside from the code itself), on the first post of this thread, where it says this:
Wire.status(); - returns current status of I2C (enum return value)
return: I2C_WAITING, I2C_SENDING, I2C_SEND_ADDR, I2C_RECEIVING, I2C_TIMEOUT, I2C_ADDR_NAK, I2C_DATA_NAK, I2C_ARB_LOST, I2C_SLAVE_TX, I2C_SLAVE_RX

And on GitHub where it says the same thing.

As far as library usage, and not making redundant calls, the examples are the only place that would indicate such. I don't think I have such coding in the examples, but if I do then let me know. A line could be added to description of endTransmission() to indicate it calls other functions, but at the same time this happens across many of the functions (sometimes you want fine granularity control, and other times just a single call that takes care of everything). That behavior should be already indicated in the function description.

The only one that updates the documentation mentioned above is me, although if you want to create some kind of usage document, or examples page, I can put a link to it.
 
@nox771 yes the missing I2C_BUF_OVF and my fork was out of synch which was part of the problem.

Nothing in my comments meant to at all be critical. I am really impressed with i2c_t3, and thankful you have done such a good job with documentation and making the code readable. If we ever meet, dinner is on me. Seriously. If you are ever passing through Salt Lake City give a holler. My Lab from Hell is just minutes from the airport.

I know documentation is a pain (I write a lot of it) and I am just offering to help.

If there was a way to get your GitHub readme documentation into the first message here, then I could just issue a pull request to the readme. Sadly there doesn't seem to be an easy way to include font coloring in GitHub Markdown.

I'm working on a temp monitoring/logging project that needs to get "done" (as much as firmware ever is) this week so I can ship to customer. It has a PCA9600 buffer from Teensy 3.2 to a board with PC9600 and PCA9548A MUX. Each branch of the MUX output is not buffered and has a short string of 2 to 6 TMP275, so that is up to 48 temperature sensors total. It needs to tolerate missing sensors or someone not setting it up correctly and my code mustn't hang the I2C. Output is on ILI9341 touchscreen, and I also want to have a webserved page with temp displays. Also logging to uSD. This it to verify control of temperatures in up to 24 separate compartments, with up to 2 sensors per compartment. I'm a bit wrapped around the axle at the moment. So to make this all robust we have been staring at your source code. Thanks for making it so clean and readable!

Some other details I was trying to understand is where blocking occurs, how to recover from a partly-mangled I2C transaction, and how finish, done, and status relate, and how to mash up all the possible error and status return values into one function which can handle and report them to my customer. I think I have that all figured out thanks to your source. resetBus is something I've wanted for a while. I have multiple Teensies running some kind of I2C test 24/7 and on a version of https://github.com/systronix/MAX31855 the 14-segment example, which just uses Wire and does not have good recovery implemented, I do sometimes see a stuck I2C bus after 24+ hours of running. Fixing that is on my todo list. I have found at least one spot in Wire lib where it appears to wait forever in case of unexpected results... So I appreciate how you have thought of this in i2c_t3!
 
Last edited:
Will repo https://github.com/nox771/i2c_t3 always be i2c_t3 master?

@nox771 will your i2C_t3 always be the master repo for i2c_t3?

If so then I will start using my clone of it as an arduino library so I can have the latest version independent of Arduino/TD.

(I know it gets baked into TD on Paul's release schedule.)

Thanks...
 
bboyes, the best solution is to verify register addresses, if they or wrong or tampered with, have your bus be reinitialized (resetBus()) and reconfigure that chip, that is how mine is setup.. 24/7 :)
 
bboyes, the best solution is to verify register addresses
@tonton81 how do you verify? Periodically try to write/read a register which has some expected value? I wonder if it would be useful to have a function which would check for SDA stuck low: after a cycle is done, or if there has been a timeout, and SCL is known high, see if the SDA pin is low. But is it possible to read that without clashing with i2c_t3 use of that pin? I would think so since it is an input when reading from the slave... I will try that.
 
@nox771 will your i2C_t3 always be the master repo for i2c_t3?

If so then I will start using my clone of it as an arduino library so I can have the latest version independent of Arduino/TD.

Yes the GitHub files should be current. When I do updates to the library, I'll post them here, and on GitHub at the same time (and as you say, Paul will include them in Teensyduino, but only when TD releases). The library usually only has massive changes when Paul releases a new board type. I try to make the docs match between first post and GitHub, but as you mentioned GH markdown is limited.

The method tonton gives for slave integrity is a good approach, because it both detects and corrects the errors. I2C is just the lossy middleman in that system.
 
Back
Top