Sending I2C data to Accelerometer

05 Aug 2011

Hi, I'm having trouble getting accelerometer data from the MMA7455 using I2C mode and I wonder if anyone could help me interpret the datasheet? The datasheet says (to write a byte hence set up the device):

Quote:

To start a write command, the Master transmits a start condition (ST) to the MMA7455L, slave address ($1D) with the R/W bit set to “0” for a write, the MMA7455L sends an acknowledgement. Then the Master (MCU) transmits the 8-bit address of the register to write to, and the MMA7455L sends an acknowledgement. Then the Master (or MCU) transmits the 8-bit data to write to the designated register and the MMA7455L sends an acknowledgement that it has received the data. Since this transmission is complete, the Master transmits a stop condition (SP) to the data transfer. The data sent to the MMA7455L is now stored in the appropriate register.

I have interpreted this as:

        i2c.start();
        a=i2c.write(0x1C);
        a1=i2c.write(0x16);  //This is a register address to send data to
        a2=i2c.write(0x01);  //This is the data to send (sets up device)
        i2c.stop();
        pc.printf("Acknowledgement: %d %d %d\n\r",a,a1,a2);
        wait(2);   

The acknowledgement bytes all read 0, which I believe means the device has acknowledged the data.

The problem occurs when I try to read the data, the datasheet says:

Quote:

The MMA7455L has an 10-bit ADC that can sample, convert and return sensor data on request. The transmission of an 8-bit command begins on the falling edge of SCL. After the eight clock cycles are used to send the command, note that the data returned is sent with the MSB first once the data is received. Figure 6 shows the timing diagram for the accelerometer 8-bit I2C read operation. The Master (or MCU) transmits a start condition (ST) to the MMA7455L, slave address ($1D), with the R/W bit set to “0” for a write, and the MMA7455L sends an acknowledgement. Then the Master (or MCU) transmits the 10-bit address of the register to read and the MMA7455L sends an acknowledgement. The Master (or MCU) transmits a repeated start condition (SR) and then addresses the MMA7455L ($1D) with the R/W bit set to “1” for a read from the previously selected register. The Slave then acknowledges and transmits the data from the requested register. The Master does not acknowledge (NAK) it received the transmitted data, but transmits a stop condition to end the data transfer.

I have interpreted this as:

        i2c.start();
        a=i2c.write(0x1C); //Write to address of device, write bit reset
        a1=i2c.write(0x06); //Register to read from (acceleration in X)
        a2=i2c.write(0x1D); //Device address with read bit set
        c=i2c.read(0);      //Read the data with NACK
        i2c.stop();

Returns a,a1 and a2 all=0 (hence an acknowledgement), but 'c' returns 157 regardless of the motion of the device.

Any suggestions on if I am interpreting the datasheet correctly into code?

PS: I have also tried:

 // i2c.write(addr, cmd, 1);
 // i2c.read(addr, cmd, 1,1);

to no avail.

Regards, Sam Wane

05 Aug 2011

Hi,..

have you seen this thread here ?

http://mbed.org/forum/mbed/topic/2209/?page=1#comment-11197

cheers

Dave.

05 Aug 2011

Thanks for the link. I had seen some links to using the SPI with the MMA7455L but I'm trying to use the I2C instead and I believe my problem lies with the calling convention. Also, the chap in the thread linked above still doesn't find a solution using the SPI. I often have to use the I2C bus for devices (e.g. SP03 speech synthesiser) and always seem to have problems interpreting the I2C protocol into the C-language, so I'm trying to understand I2C too. Sam

07 Aug 2011

Sam,

For I2C, the device address field is the upper 7 bits (7:1) of the first byte, the LSB is Read/Write where Write == 0. So for your part with a device address of 0x1D you should be using 0x3A (0x1D << 1) for Write and 0x3B ((0x1D << 1) | 0x01) for Read. In order to read from a particular address of the part you have to "Write" the register address, then send a start condition, the device address with read request, then read the byte.

    i2c.start();
    a=i2c.write(0x3A);  // A write to device 0x1D
    a1=i2c.write(0x06); // Register to read from (acceleration in X)
    i2c.start();        // Need to send start condition here
    a2=i2c.write(0x3B); // Read from device 0x1D
    c=i2c.read(0);      // Read the data with NACK
    i2c.stop();

cheers,

Don

07 Aug 2011
07 Aug 2011

Sam,

Ignore everything in my previous post about addressing, it's already handled by the read and write routines. The only thing you were missing was the extra i2c.start.

Don

    i2c.start();
    a=i2c.write(0x1D);  // A write to device
    a1=i2c.write(0x06); // Register to read from (acceleration in X)
    i2c.start();        // Need to send start condition here
    a2=i2c.write(0x1D); // tell devide you want to read
    c=i2c.read(0);      // Read the data with NACK
    i2c.stop();
04 Oct 2012

Sam, I'm using an MMA8452Q, which has the same I2C protocol as the MMA7455 that you were using last year. Did you ever get it to work? My code looks almost like what Don has posted, except that in my test, I'm reading the WHO_AM_I register to confirm that I get 0x2A back. I always get back 0xFF, which is strange. I'm tried this with and without a 100ms delay in between I2C calls. Did you ever get your accelerometer going?

char MMA8452::GetDeviceId()
{
    _i2c.start();
    wait( 0.1);
    if( _i2c.write( _address) != ACK) {
        printf( "GetDeviceId NAK on writing address");
        wait( 2);
        return 0;
    }
    wait( 0.1);
    if( _i2c.write( WHO_AM_I) != ACK) {
        printf( "GetDeviceId NAK on writing register address");
        wait( 2);
        return 0;
    }
    wait( 0.1);
    _i2c.start();
    wait( 0.1);
    if( _i2c.write( _address) != ACK) {
        printf( "GetDeviceId NAK on writing address");
        wait( 2);
        return 0;
    }
    wait( 0.1);
    int data = _i2c.read( 0);
    wait( 0.1);
    _i2c.stop();
    return data;
}
04 Oct 2012

Dave M wrote:

I'm reading the WHO_AM_I register to confirm that I get 0x2A back. I always get back 0xFF, which is strange. I'm tried this with and without a 100ms delay in between I2C calls. Did you ever get your accelerometer going?

Your code basically looks like this:

    _i2c.start();
    _i2c.write( _address);
    _i2c.write( WHO_AM_I);
    _i2c.start();
    _i2c.write( _address);
    int data = _i2c.read( 0);
    _i2c.stop();

You need to use different addresses for read and write. Assuming that your SA0 pin is 1, then your 8bit I2C slaveaddress is 0x3A. You need to set bit0 to 0 for write operations and you need bit0 set to 1 for read operations:

    _address = 0x3A;
    _i2c.start();                 // Start
    _i2c.write( _address & 0xFE); // slave write address
    _i2c.write( WHO_AM_I);        // selected register  
    _i2c.start();                 // Restart, switch from write to read
    _i2c.write( _address | 0x01); // slave read
    int data = _i2c.read( 0);     // read from selected register
    _i2c.stop();                  // done

Note: The first comment by Don Davis was correct. He was wrong when he said to ignore that in his second comment! The mbed libs always expect 8 bit i2c slaveaddress (meaning 0x3A instead of the 7bit address 0x1D). The libs for writing/reading multiple bytes include the address parameter and automatically set bit0 to 0 for write and 1 for read. This is NOT the case for the single byte write/read operations that you are using. The lib has no clue whether you are sending regular data or an address and should not touch the bit0 value.

04 Oct 2012

Thanks, Wim. I had also done it your way as well (except I just left shifted by 1 bit), and in that case I got NAKs from the device. So I figured that what Don was saying was correct, since I at least got data back. I'll look over the comments and what you have said in more detail and hopefully I'll be able to get this working. I'll also use my open logic sniffer and see if that tells me something useful.

04 Oct 2012

While Wim is right, you are making it way too hard on yourself. The same code that Wim wrote, now the easy version:

int _address = 0x3A;
char data = WHO_AM_I;
_i2c.write(_address, &data, 1, true);
_i2c.read(_address, &data, 1);

If you use these commands you dont have to worry about starting, stopping, LSB value, etc. (You do need to have 8-bit i2c addresses as Wim said). The code simply puts WHO_AM_I in a char. Next command sends the contents of data to the address specified, it is 1 byte you want to send (you can also use arrays for several bytes), and the true means you will use a restart, and not a stop condition.

Next you do the same with read, here I use the same register to store the data that is read in, but generally you will put that in another variable.

04 Oct 2012

Thanks, Erik, I'll try using the multi-byte methods. I started with them, but since I wasn't sure if SR was handled properly, I fell back on the single byte methods.

04 Oct 2012

SR is handled properly if you don't forget to add the true behind the write command :)

Btw I just noticed that if you use the single byte write command it should return a '1' when it has ACK, if you use the complete one with address (so several function arguments), it should return a '0' when it has an ACK. That really is kinda confusing. Regarding i2c addresses, to be sure you can always write a loop that checks every address.

04 Oct 2012

I'm starting to think that perhaps I am missing something fundamental. I thought that checking WHO_AM_I would be the best place to start, and I have tried using the I2C lib with the frequency set to 100000 and 400000, but neither works. I'm looking through the manual again to see if there's a config register to tinker with, but unless I can verify that the I2C lib is working properly, I'll just be chasing my tail.

04 Oct 2012

Erik - wrote:

SR is handled properly if you don't forget to add the true behind the write command :)

Btw I just noticed that if you use the single byte write command it should return a '1' when it has ACK, if you use the complete one with address (so several function arguments), it should return a '0' when it has an ACK. That really is kinda confusing. Regarding i2c addresses, to be sure you can always write a loop that checks every address.

I did see that when I read through the manual, and I had ACK defined properly per my previous single-byte code that I had posted. Thanks for bringing that up, though!

EDIT - I stand corrected, I had adjusted that at one point and didn't test it again after going to multibyte.. gonna try single byte again now!

04 Oct 2012

Erik, thanks again for that tip. While I had been aware of it, I didn't switch back to the different definition of ACK after trying multibyte, and I think that along with not shifting the address caused my problem. I can read the device ID now! Ok, now on to reading actual data... :) Not sure why the multibyte code doesn't work, however.

04 Oct 2012

Good to hear! Regarding multibyte, how doesn't it work exactly? Does the write function not get an ACK back? Or the read function? Or do they get ACKs but can't you actually read anything? (If thats the case I would suspect you forgot to add the true part in the write statement). Since that really is alot easier to use than having to write every single byte manually.

04 Oct 2012

Erik - wrote:

Good to hear! Regarding multibyte, how doesn't it work exactly? Does the write function not get an ACK back? Or the read function? Or do they get ACKs but can't you actually read anything? (If thats the case I would suspect you forgot to add the true part in the write statement). Since that really is alot easier to use than having to write every single byte manually.

not sure, but it's working now! I just copied and pasted your code in my earlier attempt, but it's now working. I must have done something wrong. Of course, now that I can read the device ID, I can't just go straight into reading data from the X, Y, and Z registers, so I am probably missing some config steps, but at least I know the I2C is working! Thanks again for your help.

04 Oct 2012

A good debugging step is to next implement a GetStatus method that just reads the STATUS register. This tells you if there is any new acceleration data available. It turns out that it was returning 0, which means no data available. So then I realized that I hadn't set the ACTIVE bit in CTRL_REG1, and after I did that, I got data. However, it's a little hard to interpret because it doesn't smoothly change throughout the range of motion for each of the axes, so I need to look into this next.

08 Oct 2013

Erik - wrote:

SR is handled properly if you don't forget to add the true behind the write command :)

Btw I just noticed that if you use the single byte write command it should return a '1' when it has ACK, if you use the complete one with address (so several function arguments), it should return a '0' when it has an ACK. That really is kinda confusing. Regarding i2c addresses, to be sure you can always write a loop that checks every address.

Hi, I'm a bit late to the party! And still wet behind the ears in learning I2C.

However this seems wrong to have an API where two methods for writing behave so differently! i.e. When a '0' represents success for a multiple write, and a '1' represents success for a single byte write. Furthermore - why would single byte not take into account the read/write bit? You might as well have a 'put' and 'get' method where you encode the byte on your own - and have the API explicitly document this.

It's a real problem for people coming from the web world into embedded! :-( .... I've ended up spending a day with an oscilloscope at work trying to debug this stuff before resorting to reading every forum post on the site to get more info on this.

Can anyone explain the logic on why this was done this way?

Kind regards, Nicholas.

08 Oct 2013

The difference between single byte and multiple byte reads/writes in what they return is indeed a bit confusing. It is a bit a matter of different definitions. The multiple byte one returns if it was succesfull, which generally uses '0; to indicate success. The single byte one doesn't return success, but it returns if an ACK was received.

What do you mean single byte does not take into account Read/Write bit? If you only use the single byte commands (so read/write with only one argument), then you do need to manually set the read/write bit. Only if you use the easy commands, that do all the start/stop stuff also for you, then you can ignore setting it since it does so automatically for you. In general I would always advice to use them, so these:

_i2c.write(_address, &data, 1, true);
_i2c.read(_address, &data, 1);
08 Oct 2013

Wim Huiskamp wrote:

Dave M wrote:

I'm reading the WHO_AM_I register to confirm that I get 0x2A back. I always get back 0xFF, which is strange. I'm tried this with and without a 100ms delay in between I2C calls. Did you ever get your accelerometer going?

Your code basically looks like this:

    _i2c.start();
    _i2c.write( _address);
    _i2c.write( WHO_AM_I);
    _i2c.start();
    _i2c.write( _address);
    int data = _i2c.read( 0);
    _i2c.stop();

You need to use different addresses for read and write. Assuming that your SA0 pin is 1, then your 8bit I2C slaveaddress is 0x3A. You need to set bit0 to 0 for write operations and you need bit0 set to 1 for read operations:

    _address = 0x3A;
    _i2c.start();                 // Start
    _i2c.write( _address & 0xFE); // slave write address
    _i2c.write( WHO_AM_I);        // selected register  
    _i2c.start();                 // Restart, switch from write to read
    _i2c.write( _address | 0x01); // slave read
    int data = _i2c.read( 0);     // read from selected register
    _i2c.stop();                  // done

Note: The first comment by Don Davis was correct. He was wrong when he said to ignore that in his second comment! The mbed libs always expect 8 bit i2c slaveaddress (meaning 0x3A instead of the 7bit address 0x1D). The libs for writing/reading multiple bytes include the address parameter and automatically set bit0 to 0 for write and 1 for read. This is NOT the case for the single byte write/read operations that you are using. The lib has no clue whether you are sending regular data or an address and should not touch the bit0 value.

Hi, still a bit new to embedded systems. I'm trying to figure out why Wim wrote the line:

    _i2c.write( _address & 0xFE); // slave write address

If you AND 0011 - 1010 with 1111 - 1110 you get the same number? :-( ... In other words if your number is even and you are doing a 'write' why do you 'AND' it with FE?

Is this a 'good practice' why of carrying out this type of bit manipulation?

Kind regards, Nicholas.

08 Oct 2013

It is good practise to make sure that regardless of the given address the LSB is zero this way.