Where does I2C stand today?

I’ve read ton of threads and been occasionally trying out my Core and now Photon over the past year. Honestly I’m lost as to what the current state of I2C is, whether some of the recent fixes ought to put it in a working state or if there’s still known issues being debugged. Can anyone help?

I’ve tried dozens of combinations at this point, but generally I’m testing a very simple USB-powered Core/Photon on a breadboard with a single HIH61xx or TSL2561 sensor hooked up to power and the I2C pins. What I observe is that I can read values from them for a little while, but after several minutes or overnight then it stops reading and I get junk values.

I’m using Sparkfun breakouts for each so they’ve got pull-ups and capacitors built in. I’ve got them powered continuously and reading the value then delaying 10s, in a loop. It seems to happen for two very different I2C devices and even among multiple HIH devices.

Where could I go from here?
Non public build system firmware to try?
I2C subsystem configurations to make it more fault tolerant?
Is there a way to reset a hung I2C connection if/when I detect I’m getting junk?
Is it better to power these sensors from a ‘D’ pin so I can power them on only when communicating with them, to sort of reset the communication?

Thanks in advance.

@chasc, the Particle team is working on the problem right now which may be related to an STM32F20x hardware issue. There is no 100% solution just yet but @BDub is working to get there.

1 Like

Thanks for the note @peekay123. There are a lot of people here with much more experience than I, so it was hard to tell if I’m not doing it right or if it’s something out of my control.

Is there a particular thread or other location that’s best if I want to keep tabs on @BDub’s updates to the I2C stuff?

I took a look at the STM32F20X Reference Manual (RM0033) and the current I2C_hal.c implementation in the Develop branch and have a theory on what the photon I2C issues might be:

Assumptions:

  • HAL_I2C_Request_Data() is being called with stop parameter true, as it is extremely unlikely to be doing a repeated read operation.

RM0033 pages 597/598 and Figure 221 describe the Master Receiver transaction. Of interest is what happens when there are two bytes left to be received, or when this is a single byte read.

2 Bytes left:

  • When RXNE (Data Register status) is set, the 2nd to last byte is in the DR, it has already been ACKED, and the last byte is being shifted into the shift register. As soon as the DR is emptied by reading out the 2nd last byte, the DR is available to take the last byte. This last byte will be ACKED according to the setting the Acknowledge bit in CR1at the time the transfer to DR occurs. Therefore the Acknowledge Bit must be reset, and the Stop condition set, prior to reading out the 2nd last byte from the DR. Otherwise the sending slave device will receive an ACK for the last byte and assume it still has control of SDA. It is most likely it will hold SDA low, awaiting a Clock pulse it will never get. It would probably take a power cycle of the peripheral to clear this condition.

1 Byte left:

  • With only one byte and the DR empty, it will be ACKED as soon as it is received. Therefore the Acknowledge bit must be reset and the Stop condition set immediately after the ADDR bit is cleared (I2C_CheckEvent() will always clear ADDR if set.

Here is the relevant code snippets from I2C_hal.c:

    /* Send Slave address for read */
    I2C_Send7bitAddress(I2C1, address << 1, I2C_Direction_Receiver);

    _millis = HAL_Timer_Get_Milli_Seconds();
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
    {
        if(EVENT_TIMEOUT < (HAL_Timer_Get_Milli_Seconds() - _millis))
        {
            /* Send STOP Condition */
            I2C_GenerateSTOP(I2C1, ENABLE);

            /* SW Reset the I2C Peripheral */
            HAL_I2C_SoftwareReset();

            return 0;
        }
    }

    /* perform blocking read into buffer */
    uint8_t *pBuffer = rxBuffer;
    uint8_t numByteToRead = quantity;

    /* While there is data to be read */
    while(numByteToRead)
    {

There needs to be a check for a single byte request immediately after the while(I2C_CheckEvent() loop and if so, reset the acknowledge bit and set stop before anything else happens. At 400 KHz clock, the first data byte will be in the DR and the ack/nack already sent within 20us after ADDR is cleared by I2C_CheckEvent().

Further down in the code, within a while loop controlled by NumbytetoRead being non zero, we check the DR register for having data, store the data in the receive buffer, do some checking and loop:

        /* Wait for the byte to be received */
        _millis = HAL_Timer_Get_Milli_Seconds();
        //while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED))
        while(I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE) == RESET)
        {
            if(EVENT_TIMEOUT < (HAL_Timer_Get_Milli_Seconds() - _millis))
            {
                /* SW Reset the I2C Peripheral */
                HAL_I2C_SoftwareReset();

                return 0;
            }
        }

        /* Read the byte from the Slave */
        *pBuffer = I2C_ReceiveData(I2C1);

        bytesRead++;

        /* Point to the next location where the byte read will be saved */
        pBuffer++;

        /* Decrement the read bytes counter */
        numByteToRead--;

We don’t check whether we just processed the 2nd last byte until we get back around to the top of the loop. This is too late, especially with the faster internal clock of the photon. We need to move and refine that test to immediately prior to reading the 2nd last byte:

        /* Wait for the byte to be received */
        _millis = HAL_Timer_Get_Milli_Seconds();
        //while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED))
        while(I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE) == RESET)
        {
            if(EVENT_TIMEOUT < (HAL_Timer_Get_Milli_Seconds() - _millis))
            {
                /* SW Reset the I2C Peripheral */
                HAL_I2C_SoftwareReset();

                return 0;
            }
        }

        if(numByteToRead == 2 && stop == true)
        {
            /* Disable Acknowledgement */
            I2C_AcknowledgeConfig(I2C1, DISABLE);

            /* Send STOP Condition */
            I2C_GenerateSTOP(I2C1, ENABLE);
        }

        /* Read the byte from the Slave */
        *pBuffer = I2C_ReceiveData(I2C1);

        bytesRead++;

        /* Point to the next location where the byte read will be saved */
        pBuffer++;

        /* Decrement the read bytes counter */
        numByteToRead--;

If these mods are made, the check for the stop condition has to moved outside the while loop as we still have work to do (i.e. storing the last byte of data from the DR after the stop condition was set). It is a somewhat redundant check anyway and probably can be removed.

Hope this helps

Peter

As far as we've tested over the last 24 hours, there are no errors being found with I2C using the latest code in the develop branch. One test I have running is currently up to 0 errors out of 850,000 reads. Each "read" is a sensor reading which takes MANY transactions on the bus, polling for status bits and reading/writing bytes.

Actually the Sparkfun library that we are heavily testing with does in fact do a repeated read because there are a lot of 3-byte values that need to be consumed and continuous read is the fastest way to do it.

It's going to take me a bit to digest what you are referencing from the RM0033, so when I do I'll come back :smile:

Thanks for digging in @pra

4 Likes

Testing this now…

ITS WORKING!!

there is no way it would run for this long for me last week! BEST birthday present ever (besides my 3d printer from my wife last year :wink: )

3 Likes

Happy Bday @Hootie81!!!

This could solve my chip select SPI issues. I’m going to try using IC2 instead.

Was there just an update by the way? I’m having a much easier time flashing than a day ago and the code isn’t that different?

Thanks @peekay123

Great Work @BDub and others who helped! 4hours later its still running thats something like 640000 reads of 42bytes :slight_smile:

now i have no excuses

3 Likes

Came here to see if there are any updates, and found this fantastic news…

@BDub, is this working on the P1 as well?? Can I flash my P1 with 0.4.4-rc2 and expect this to work??

I’m not sure exactly how to build the latest firmware locally for the P1, so if you can provide binaries, that would be great. I’ve looked at the documentation, but still going through it.

BTW, particle’s makefile documentation link gives me a 404: https://github.com/spark/firmware/blob/develop/docs/docs/build.md
https://github.com/spark/firmware/blob/develop/docs/build.md (EDITED by BDub)

Thats the link that is shown in this page: https://github.com/spark/firmware/blob/develop/docs/gettingstarted.md

Thanks!

This I2C fix is not in v0.4.4-rc.2, but will be in v0.4.4-rc.3, and does in fact work on the P1 the same as the Photon. If you can wait a day or so the binaries should be up on the releases page. And thanks for the tip about the Docs link error… I edited your post with the proper link and will get this fixed in the docs :sun_with_face:

2 Likes

Hi, i’m a bit of a photon noob, and ended up here while trying to fix I2C communication issues I was running into with an IMU.
Can someone please summarize the steps one can take to get I2C up and running with firmware updates / code modifications. Thanks in advance!

About the same boat as you @sarvagyavaish, here’s what I’ve done so far:

Go to the releases page linked just above by BDub. Download parts 1 & 2 of the “-photon.bin” files from the downloads section in that post for the RC3 release that was made today. I’ve already installed SparkJS so I ran the commands that start with “particle flash”. That updated the system firmware to include the I2C fixes discussed above. It’s been a few hours but so far my I2C device is still responding…

1 Like

@chasc Great, thanks a ton! Will try it out and report back. Quick follow up… in your I2C test, are you also printing to Serial?

This is definitely looking good for me, previously the data from a bmp085 sensor would turn to gibberish after approx 10 minutes of running. With the rc3 firmware the same setup has been running for approximately two hours so far and no errors at all.

I just wanted to say a big thanks to all who have worked hard figuring this one out. Go grab yourselves a beer, you deserve it :grinning:

Cheers
Robin

2 Likes

Replying to myself now… must be losing it… Just to add that the setup above has been running overnight with no errors.

1 Like

@BDub, quick question: Is it expected that the Core will benefit from these new I2C fixes as well, or was the root issue something specific to the Photon. I have a couple Cores I want to revisit soon too and just curious what to expect.

These I2C changes were also made to the Core and will be available in the new HAL implementation of the Core firmware. If you’d like to try this out before the release (which is coming soon), you can read up on how to set up everything here: https://github.com/spark/firmware/tree/latest