10 years, 7 months ago.

Inconsistent results when attempting write to I2C, dependent on compile event

I have reproduced this problem on a KL25Z and KL46 and I'm totally lost.

Trying to write to the accelerometer using i2c and write our own driver, but it's not working out so easily. In this code, we use i2c writes in both forms (long and short) as per the i2c library documentation.

Sometimes when I compile, the code will give the correct output: "1a" Sometimes when I compile, the code will give incorrect output:"0" or "3b" Sometimes I can re-load the same binary that gave "1a" previously (no changes) and it can give "0"

I'm not sure if this is a timing issue or what.

I have no idea what is going on. Please help! Thank you!

#include "mbed.h"

#define MMA8451_I2C_ADDRESS (0x1d<<1)
#define REG_WHO_AM_I        0x0D


DigitalOut myled(LED_GREEN);
Serial pc(USBTX, USBRX);
I2C i2c(PTE25, PTE24); //MMA8451 Accelerometer

int main() {
    
    pc.baud(9600);
    
    while (true) {
        
        //This uses the I2C library function long forms
        uint8_t who_am_i;
        char t[1] = {REG_WHO_AM_I};
        i2c.write(MMA8451_I2C_ADDRESS, t, 1, true);
        i2c.read(MMA8451_I2C_ADDRESS, (char*) &who_am_i, 1);
        //End of Long form
        
        //This uses the I2C library function short forms
        uint8_t who_am_i_2;
        i2c.start();
        i2c.write(MMA8451_I2C_ADDRESS);         // Slave address with direction=write
        i2c.write(REG_WHO_AM_I);             // Subaddress (register)
        
        i2c.start();                // Break transmission to change bus direction
        i2c.write(MMA8451_I2C_ADDRESS + 1);     // Slave address with direction=read [bit0=1]
        
        who_am_i_2 = i2c.read(0);
        i2c.stop();
        //End of short form
        
        wait(0.5);
        pc.printf("Short: %x Long: %x\n", who_am_i, who_am_i_2);  //Print both to screen
        myled = !myled; //Heartbeat
    }
}

Thank you Donovan for sharing programs to replicate the issue you were seeing. Erik fixed them and will be merged to master branch soon.

Regards,
0xc0170

posted by Martin Kojtal 03 May 2014

2 Answers

10 years, 7 months ago.

Sadly I can reproduce it. The long one on the KL25Z doesn't have the greatest implementation, since the hardware wasn't really intended for it to be used like that, still it should work. Aditionally the MMA is a bit of a snob what it wants regarding I2C waveforms, and once it doesn't understand something, it will always keep returning zero until a power reset (even while it should detect a stop condition and restart its I2C circuits).

I did some checking, and first of all, the official code seems to work fine: http://mbed.org/teams/Freescale/code/FRDM_MMA8451Q/. Second, if I comment the 'long' I2C commands it also works fine. It is just them confusing the MMA, and when it gets confused it doesn't recover generally from it.

I think I found the bug in the mbed source code in the byte write functions (which tbh would have been removed long ago if I was in charge, they serve no purpose when there is also the block write functions). And it is hard to blame the source code, the device was never intended to be used like this with single byte write operations. Also the fix only would fix it for single byte read operations, multi-byte read with the byte-read commands would still be problematic, especially with the MMA which doesn't like errors on its I2C bus.

The issue is that the KL25Z does two things when reading its I2C data, it reads the last byte, and starts the next read operation. Unless it is set to send a NACK, then it prevents another byte from being read. The first read operation has a dummy read, to initiate the first reading of a byte. This currently does not set the ACK/NACK flag, causing the MMA to go bad. Adding the ACK/NACK flag there does seem to solve the issue you are seeing.

The problem however is if you do multiple reads. The block function knows what is coming next, and handles it fine. The byte-read function doesn't know what is coming. Since a byte-read also starts the next read operation, the issue is then that the one before the last read already starts the last read, without knowing that the next one should be the last, so the ACK/NACK flag again isn't set properly.

Now I might be able to force it similar to when the last byte is read, to always only read one byte, and not initiate the next read. But I really prefer to make such changes only when I got a logic analyzer. And that should arrive somewhere in the coming weeks. Since I don't think this is a critical issue for you right now, I personally prefer also to not do a pull-request with the solution for the single read, and wait until I can properly try to solve it. And the reason that I don't think it is a critical issue for you, is that the block writes should work fine. If you would really need to use the single byte writes/reads for some reason I can publish a modified mbed-src lib you can use.

Edit: Wow that became a long one :P. I hope it is a bit clear. You really need some kind of flow diagram to properly explain it, but that is too much work :)

And the TL;DR, use block writes/reads, they should work fine. If you manage to get the MMA confused (for example resetting in the middle of a transaction), you need to power cycle it.

Accepted Answer

Thanks for your help!

For documentation sake, I found something else weird. The following code sends an error ("3b") and eventually changes to another error ("0"):

#include "mbed.h"

#define MMA8451_I2C_ADDRESS (0x1d<<1)
#define REG_WHO_AM_I        0x0D


DigitalOut myled(LED_GREEN);
Serial pc(USBTX, USBRX);
I2C i2c(PTE25, PTE24); //MMA8451 Accelerometer

int main() {
    
    pc.baud(9600);
    
    while (true) {
        
        //This uses the I2C library function long forms
        //uint8_t who_am_i;
        //char t[1] = {REG_WHO_AM_I};
        //i2c.write(MMA8451_I2C_ADDRESS, t, 1, true);
        //i2c.read(MMA8451_I2C_ADDRESS, (char*) &who_am_i, 1);
        //End of Long form
        
        //This uses the I2C library function short forms
        
        uint8_t who_am_i_2;
        i2c.start();
        i2c.write(MMA8451_I2C_ADDRESS);         // Slave address with direction=write
        i2c.write(REG_WHO_AM_I);             // Subaddress (register)
        
        i2c.start();                // Break transmission to change bus direction
        i2c.write(MMA8451_I2C_ADDRESS + 1);     // Slave address with direction=read [bit0=1]
        
        who_am_i_2 = i2c.read(0);
        i2c.stop();
        //End of short form
        
        
        wait(0.5);
        pc.printf("Short: %x\n", who_am_i_2);  //Print SHORT to screen
              

        myled = !myled; //Heartbeat
    }
}

While, as you said, the following code works well for the most part, returning the correct ID ("1a"):

#include "mbed.h"

#define MMA8451_I2C_ADDRESS (0x1d<<1)
#define REG_WHO_AM_I        0x0D


DigitalOut myled(LED_GREEN);
Serial pc(USBTX, USBRX);
I2C i2c(PTE25, PTE24); //MMA8451 Accelerometer

int main() {
    
    pc.baud(9600);
    
    while (true) {
        
        //This uses the I2C library function long forms
        uint8_t who_am_i;
        char t[1] = {REG_WHO_AM_I};
        i2c.write(MMA8451_I2C_ADDRESS, t, 1, true);
        i2c.read(MMA8451_I2C_ADDRESS, (char*) &who_am_i, 1);
        //End of Long form
        
        //This uses the I2C library function short forms
        /*
        uint8_t who_am_i_2;
        i2c.start();
        i2c.write(MMA8451_I2C_ADDRESS);         // Slave address with direction=write
        i2c.write(REG_WHO_AM_I);             // Subaddress (register)
        
        i2c.start();                // Break transmission to change bus direction
        i2c.write(MMA8451_I2C_ADDRESS + 1);     // Slave address with direction=read [bit0=1]
        
        who_am_i_2 = i2c.read(0);
        i2c.stop();
        //End of short form
        */
        
        wait(0.5);
        pc.printf("Long: %x\n", who_am_i);  //Print both to screen
                

        myled = !myled; //Heartbeat
    }
}

Thank you very much for your help!!! I have spent almost 10 hours on this problem thinking that I was going crazy. I was about to throw my computer across the room.

posted by Donovan Lee 16 Apr 2014

Same story there, byte reads do not return a correct NACK at least read. Many sensors won't care about that (since they should simply reset the statemachine of their I2C hardware once a stop condition occurs), the MMA clearly does care.

posted by Erik - 17 Apr 2014
10 years, 7 months ago.

Hi, maybe you are experiencing the same problems I mentioned a couple of days ago. A simple read/write I2C task on KL25Z and DS temperature sensor fails, but the same code with the same sensor on LPC1768 works fine. Obviously there might be something wrong with I2C library. I haven't checked yet due to the lack of time.

RGDS