KL25Z and I2C: Missing Repeated Start Condition

I really hate this kind of stuff: I know it should work, but it does not. I’m loosing a lot of time (hours, days, even weeks) to track it down to the root cause. Yes, I create my own bugs. Yes, there are bugs in tools, sources, libraries and components. But what many might not believe: there are bugs in silicon too :-(. If you do not believe, here is one: there is a hardware I2C problem on the KL25Z used on the Freedom board. It worked in one project, but not in another.

❗ The silicon bug described here is present on many Kinetis devices, not only the KL25Z!

Logic Analyzer attached to the FRDM-KL25Z board

Logic Analyzer attached to the FRDM-KL25Z board

So if you are facing a problem where you read 0xFF or wrong values from the I2C bus with the KL25Z, here is probably why (and how to workaround it). The problem showed up with a modified version of the Freedom Accelerometer tutorial….

Start or not Start, that’s the question

In my application I need to read through I2C from a device. It is using the I2C1_MasterSendBlock() followed by a I2C1_MasterReceiveBlock() (see as well this post).

byte I2C2_ReadAddress(byte i2cAddr, byte *memAddr, byte memAddrSize, byte *data, word dataSize)
{
  byte res = ERR_OK;
  TMOUT1_CounterHandle timeout;
  bool isTimeout;

  if (I2C2_SelectSlave(i2cAddr)!=ERR_OK) {
    return ERR_FAILED;
  }
  for(;;) { /* breaks */
    /* send device address and memory address */
    I2C2_deviceData.dataTransmittedFlg = FALSE;
    res = I2C1_MasterSendBlock(I2C2_deviceData.handle, memAddr, memAddrSize, LDD_I2C_NO_SEND_STOP);
    if (res!=ERR_OK) {
      break; /* break for(;;) */
    }
    timeout = TMOUT1_GetCounter(I2C2_TIMEOUT_MS/TMOUT1_TICK_PERIOD_MS); /* set up timeout counter */
    if (timeout==TMOUT1_OUT_OF_HANDLE) {
      res = ERR_FAILED;
      break; /* break for(;;) */
    }
    do { /* Wait until data is sent */
      isTimeout = TMOUT1_CounterExpired(timeout);
      if (isTimeout) {
        break; /* break while() */
      }
    } while (!I2C2_deviceData.dataTransmittedFlg);
    TMOUT1_LeaveCounter(timeout);
    if (isTimeout) {
      res = ERR_FAILED;
      break; /* break for(;;) */
    }
    /* receive data */
    I2C2_deviceData.dataReceivedFlg = FALSE;
    res = I2C1_MasterReceiveBlock(I2C2_deviceData.handle, data, dataSize, LDD_I2C_SEND_STOP);
    if (res!=ERR_OK) {
      break; /* break for(;;) */
    }
    timeout = TMOUT1_GetCounter(I2C2_TIMEOUT_MS/TMOUT1_TICK_PERIOD_MS); /* set up timeout counter */
    if (timeout==TMOUT1_OUT_OF_HANDLE) {
      res = ERR_FAILED;
      break; /* break for(;;) */
    }
    do { /* Wait until data is received */
      isTimeout = TMOUT1_CounterExpired(timeout);
      if (isTimeout) {
        break; /* break while() */
      }
    } while (!I2C2_deviceData.dataReceivedFlg);
    TMOUT1_LeaveCounter(timeout);
    if (isTimeout) {
      res = ERR_FAILED;
      break; /* break for(;;) */
    }
    break; /* break for(;;) */
  } /* for(;;) */
  if (I2C2_UnselectSlave()!=ERR_OK) {
    return ERR_FAILED;
  }
  return res;
}

There is nothing wrong with that code, as it worked for other devices very well.

Below is a logic analyzer output from that code sequence, which reads from the device a value:

Working I2C Bus Communication

Working I2C Bus Communication

It sends a start condition (first green dot on the SDA line), followed by the I2C device address (0x68), then the memory address (0x00), followed by a ‘repeated start’ condition (the second green dot), then again the I2C device address (0x68), and then reads the data from the device (0x26). So this one is working.

Now here is the same thing on the KL25Z, same code, but this time things are screwed up:

Does not work: Start Condition is missing

Does not work: Start Condition is missing

The problem is: somehow the start condition is not sent, screwing up the communication, and the software just reads back 0xff (wrong!) :-(.

It took me a while to find out what is the difference between the ‘working’ and ‘non-working’ version: it is the pre-scaler setting for the I2C peripheral on the KL25Z. It works if the prescaler to the I2C bus is set to 1!

Workaround

To verify the settings, I inspect the ‘Internal Frequency’ Settings of the I2C_LDD component:

Internal Frequency Settings

Internal Frequency Settings

Here I have a dialog with which has a ‘Clock path’ tab which shows the clock used for the peripheral:

💡 The ‘Clock path’ is a really, really nice feature of Processor Expert!

Internal Frequency Settings with prescaler of 1

Internal Frequency Settings with prescaler of 1

If that bottom prescaler setting is set to one, I’m fine.

But if that setting is set to anything other than one, the bus communication does not work reliably, as shown above:

I2C prescaler not 1, will not work

I2C prescaler not 1, will not work!

Looks like the silicon has a real issue with the internal clock distribution :-(.
So the workaround is to make sure that a prescaler of 1 is used. This means to select the maximum clock frequency as offered in the above dialog. And to achieve the desired I2C bus clock frequency, to use the frequency divider bits e.g. to reach a desired I2C clock frequency:

Frequency Divider to achieve 109 kHz I2C clock

Frequency Divider to make a 109 kHz I2C clock

Summary

  1. Always have a working example (that’s why tutorials or example projects are great!)
  2. It is possible that my code has bugs (yep! I admit.)
  3. Libraries or sources from somebody else has bugs too (yep! they are not better than I am ;-).)
  4. It is not unlikely that silicon has bugs too, especially if the silicon is brand new (absolutely! that’s why companies wait until Rev 2.0 of the silicon comes out)
  5. The debugger combined with a logic analyzer is my best friend (yes, indeed! and makes a prefect Christmas present.)

Thinking that only software can be flawed is simply wrong: silicon has bugs too, and this is obvious with reading the silicon erratas. The current KL25Z erratas are available here (but does not list above issue (yet?)).

Happy Starting 🙂

PS: My Saleae Logic16 was my ‘self-wished-and-purchased’ Christmas gift for 2011. Still thinking what I should wish (and buy) for 2012 :-). Suggestions and donations are welcome. 🙂

53 thoughts on “KL25Z and I2C: Missing Repeated Start Condition

  1. Thank you for making all this very clear !
    I can’t wait to retry the ‘Accelerating …’ tutorial with various different frequencies as shown (have you already tried it ?).

    Also, you might want to add a link to this topic in your ‘Accelerating …’ tutorial.

    Like

  2. Pingback: Tutorial: Accelerating the KL25Z Freedom Board | MCU on Eclipse

  3. I2C is a very hard system to troubleshoot when it goes wrong. That is great that you found the error. One suggestion for Christmas… the Google Nexus 7 pad. I use it to keep my schematics, doc sets, for projects, source code, email …everything. The disadvantage is you can’t escape having your work with you at all times. I have the Saleae analzyer as well; I wish they would add the conditional software triggers… it has been over a year they have been promising… that would be a good Christmas gift.

    Like

    • Hi Robert, yes, I miss the conditional triggers on the Saleae analyzer as well. And good suggestion with the Google Nexus 7 pad: I’m already using an iPad for this :-). The challenge with it is that the whole family wants to use it too 😉

      Like

  4. Pingback: Processor Expert Maxim I2C RTC Driver the Arduino Data Logger Shield | MCU on Eclipse

  5. I’ve been trying to get a Sensirion SHT21 humidity sensor to work on the KL15, wasn’t working and this is why.

    I don’t know whether to drink, or go on a monkey sh*t throwing rampage, or both.

    Like

    • Yes, Freescale is using the same IP blocks/silicon for multiple devices. So silicon bugs get easily propagated :-(. The good thing is that the most recent Processor Expert has a built in warning (at least for some known cases).

      Like

  6. Pingback: Bit-Banging I2C with ResetBus() Functionality | MCU on Eclipse

  7. Hi Erich.
    I’m running “very low power” mode in KL25Z-FRDM.
    Core Clock: 2 MHz, bus Clock 0.666667 MHz. And Maximum I2C bus clock I can set is 33.33 KHz.
    I’m using I2C to write config and read data on Accelerometer MMA8451.
    Sometimes MMA8451 work correctly, but sometimes when rebooting, MMA8451 can not write config (initialize) –> Accelerometer can not work (hang if I put unlimited while loop at I2C return).
    This case, though I do many reset (simple software reset), it still can not work again. I must cut down power (re-solder battery) then it can work again.

    How can solve this problem? 33.33 KHz is not fine for MMA8451? (recommended in spec is 100 KHz –> 400 KHz)

    Like

  8. Hi Erich,
    I saw that you own a Saleae Logic16. I’m thinking in buy one Logic Analizer, and I saw this and a gwinstek GLA-1016, did you have the oportunity to test this one ? Do you know if one is much better than the other ?

    Best Regards,
    Christian

    Like

  9. Thaks a lot Erich.. i’m begining to make me crazy…
    Yes, with the kl15 succes the same.
    Sometimes sending the restart and sometimes not.
    It has been to put the preescaler value to 1 and work it correctly

    Thanks !!

    Like

  10. Outstanding! I just came across the same issue on the K22 and was pounding my head against the wall trying to figure it out. Showing the clock path screen is the key. I tried manually setting mult to 0 but that did not work. When I did exactly as you said and made sure I2C0 prescale value was set to 1 (it was 2 in my design) everything started working!

    Like

  11. Hey Erich,

    First of all, thank you for all the helpful notes on Kinetis. It really helped!

    I have been getting what appears to be the exact same problem as the I2C LDD component works sometimes but not always (sometimes the SDA line is always pulled low for no reason).

    I am working with a KL46Z board and I tried following your tutorial to solve the bug, but I’ve encountered the following condition:

    My OUTDIV2PRESC = 1 but my OUTDIV4PRESC = 2. Do you know where we could change this value?

    Thank you!

    Thomas

    Like

    • Hi Thomas, what tools/toolchain are you using? Processor Expert with Kinetis Design Studio? With or without Kinetis SDK?
      You can manually change the clock prescaler values in Processor Expert.

      Like

      • Hey Erich,

        I am using Processor Expert with KDS but not SDK.
        I still can’t change the OUTDIV4PRESC even with Advanced option on.

        Interestingly, I enabled auto-initialization for all components of the project and it seems to have solved the problem. But I’m pretty sure it’ll fail again, given how it has failed before with Auto-initialization.

        Thanks.
        Thomas

        Like

        • Hi Thomas,
          if you use auto-initialization, then all what it does is to call the Init() function (and driver handle generation) during PE_low_level_init(). Could it be that you are somehow missing to call the Init() from your application, or using a duplicate device handle?
          It should not affect the prescaler settings in my view anyway, so I’m really wondering what the problem could be.

          Like

  12. Hi Erich,
    Thank you for the prompt replies!

    I made sure that the Init function was called and I didn’t have a duplicate DeviceData handle (I had this error once but the compiler pointed it out).

    What is really interesting is that the Sensor Fusion project that uses the exact same configurations always works, but my project only works some of the time.

    Like

    • ‘Some of the time’ means same source/code/binary works sometimes, but not always. Or do you have a state/configuration/source file set where it does not always work? Would be interesting to know the difference between ‘working’ and ‘non-working’. Or are you saying that one time the clock prescalers are set correctly, one time not?

      Like

      • Hey Erich,

        working = I2C is getting reading from Accelerometer, SDA line is not always low;
        not-working = SDA line always low, I2C not reading anything.

        The clock prescalar OUTDIV4PRESC is still at 2 though, however. I wish I could find some documentation on OUTDIV4PRESC but Google turned up nothing.

        Anyway, after doing the following, the code is miraculously working:

        1. following the work-around in this tutorial and use the highest value for I2C source clock.
        2. Use 010 for both divider to get 375kHz (even though it gives a warning of exceeding the 100kHz limit.
        3. set all flag variables to volatile, which solved another problem of the ADC component of the same project only working with breakpoint present.

        Like

        • Hi Thomas,
          about point 2): you can ignore that warning. The original specs of I2C is for 100 kHz, but this warning does not make any sense to me if the device (like the one you are using) allows a higher speed).
          About 3): if you have to set things to volatile, either the compiler is doing things in a wrong way, or indeed there is a reentrancy problem. Marking the variable volatile prevents compiler optimizations, and will affect timing. So it might be because of optimizations, or because of timing that things work or do not work. Have you optimizations turned off?

          Like

  13. Hi Erich,

    Pardon me if this question is too simple, but how do you see optimization levels?

    I followed a link you replied to on Freescale forum and check the Debug settings and the Other compiler flags blank is blank. Should I manually type in -0 to turn that off? Or maybe I don’t need volatile variable at all, even if they correspond to flags that are being set in events.c?

    Also, in the Tool Settings section under properties of the project, I am seeing ARM GNU Assembler, ARM C compiler, ARM C++ Compiler and ARM C++ Linker. Is this what I am supposed to be seeing?

    Apologies in advance for not know what appears to be basics of C.

    Thanks again for all the help.
    Thomas

    Like

    • Hi Thomas,
      you find the compiler optimization settings under Project > Properties > C/C++ Build > Settings > Optimization. The Optimization level should be ‘None -O0’ to disable optimizations. And yes, you should not need to use ‘volatile’, as this would not be a proper solution for a problem.

      Erich

      Like

  14. Is there any example of use kl25 or kl26 as I2C Slave? I try use I2C_LLD with MASTER-SLAVE mode, but when I try detect address with i2cdetect on my Linux I don’t have any results 😦

    Like

  15. I’m getting this exact lack of repeat start condition on an MKL16, but setting the I2Cx_F[MULT] to 0 or 1 does nothing to resolve it 😦 I suppose will continue to modify the clock path until I find a solution

    Like

  16. Hi Erich!

    I had a trouble very similar with the KL03Z, my problem is when i send the repeated start, the bus sends the last byte that i sent instead of the byte that i want to send. But the trouble it’s only when i run the application, but when i debug step by step it works! I’m not using Processor Expert.

    Like

  17. Hi Erich,
    Thanks for the post.
    I want to read two bytes from I2C slave device (using KL03 as master). If I try for reading only one byte it is working fine, but reading of two bytes is showing “1 FF” data. Is it because of repeated start conditions?
    and how to change I2C prescalar to 1 in KDS 3.0.0 ?
    I dont find “Internal clock frequency” in I2C component inspector.

    Like

      • Hi Syed,
        I have not used that fsl_i2c component in my projects. Is it not possible to use the one I have used because there I can set the clock divider?
        Otherwise, you need to step through th fsl_i2c component code to see where the clock is configured.
        I recommend that you check with a logic analyzer the signals on the i2c bus so you see that is going on: without such a tool, your are more or less blind and don’t know what is going on 😦

        Erich

        Like

  18. Hello Erich,

    Can this problem happen in the mke02z family?
    In my case it does not go into the OnMasterBlockSent event and does not return errors.

    Like

    • I don’t have any KE02Z at hand. I have seen that silicon bug present on all Kinetis I have used so far, but the KE series uses very different peripherals. The only way to find out is to hook up a logic analyzer/scope and see if the repeated start condition is sent or not. If you don’t have a scope, try an I2C clock settings with no divider and check if the problem goes away.
      I hope this helps,
      Erich

      Like

What do you think?

This site uses Akismet to reduce spam. Learn how your comment data is processed.