Sending I2C data to Accelerometer
Topic last updated
04 Oct 2012, by
Sam Wane.
17 replies
I2C
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
Replies
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
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
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();
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;
}
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.
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.
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.
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.
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'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.
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!
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.
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.
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.
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.
Please log in to post a reply.
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:
to no avail.
Regards, Sam Wane