[Solved] LSM303DLH values stuck at 0h31 and 0h3D (AVR)

Hello forum !

I just got my hands on a LSM303DLH (catalog page here). Since then, I’ve browsed several documents, the one on the I2C protocol and this particular part.

Also, read the case where an Arduino simply got frozen in the I2C commands, which later resulted in voltage level difference problems, that got solved later.

Background:
I have adapted the example code provided for the Orangutan to be used in a single ATmega8 chip. I changed the code so that the TWI interface produced a 100kHz clock (Standard I2C mode). The whole circuit is supplied with 5V from a regulated switching supply.

My testbed:

With a greatly stripped-down version of the code -for testing purposes- It appeared that the sensor was actually ACK-ing my AVR because it completed the commands used to write and read bytes from the I2C bus. I did this by turning on a LED before the command and turning it off afterwards.

The test:
Finally, I changed the code so that the value of one of the bytes read was sent to a port (portD) to view it with a LED-bar. Then, I’ve tested by outputting each of the bytes (high and low parts of each of the 3 axis) for both the accelerometer and the magnetometer.

The issue:
Every byte I read from the accelerometer is 0h31, and every byte I read from the magnetometer reads 0h3D. And these values are FIXED. I’ve tried moving the module, tilting it in order to alter the readings. Nothing happened.

As a curious fact… that 0h31 is the reading address for the Accelerometer and 0h3D, for the magnetometer.

Now, as far as I understand, the configuration commands provided in the example code put the sensors in “free running” so they auto-update their registers with new measurements, right? So, if I sample them periodically I’d expect to see changing values (and different LEDs lit-on) by changing the sensor position.

I hope this error is just a misunderstanding of the I2C protocol or how it works, or even how the sensors are designed to work. This rather than having a malfunctioning part.

The code:
(The code is essentialy the same as the Orangutan’s example, I’ve just changed the main() function and added the delay_ms() function for the time delays. I’m pasting the changed part of the code.

// ### OMMITTED COMMENTS ##//

// HEADERS AND DEFINES
#define F_CPU 4000000 // 4mhz, for delay_ms()

#include <avr/io.h>  
#include <util/delay.h>
// #include <pololu/orangutan.h>  // Not using Orangutan, just an ATmega8
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "vector.h"


// ### OMMITTED CODE ##//

void delay_ms(unsigned int time_ms)
{
  while (time_ms--)
    _delay_ms(1);
}

int main()
{
	// DATA OUTPUT TO LED BAR
	DDRD = 255;
	PORTD = 0;

	DDRC = 1; // PC0 as blinking LED
	
	// IT IS NOT NECCESARY TO ENABLE THIS, THE CIRCUIT HAS EXTERNAL 4.7K PULLUPS
	// PORTC = (1 << PORTC4) | (1 << PORTC5); // enable pull-ups on SDA and SCL, respectively

	TWSR = 0;  // clear bit-rate prescale bits
	
	// FOR ATMEGA8
	TWBR = 12; // produces an SCL frequency of 100 kHz with a 4 MHz CPU clock speed

	//enable accelerometer
	i2c_start(); 
	i2c_write_byte(0x30); // write acc
	i2c_write_byte(0x20); // CTRL_REG1_A
	i2c_write_byte(0x27); // normal power mode, 50 Hz data rate, all axes enabled
	i2c_stop();

	//enable magnetometer
	i2c_start(); 
	i2c_write_byte(0x3C); // write mag
	i2c_write_byte(0x02); // MR_REG_M
	i2c_write_byte(0x00); // continuous conversion mode
	i2c_stop();

	// Data variables (high and low) for each axis
	unsigned char axl,axh,ayl,ayh,azl,azh,ret = 0;
	unsigned char mxl,mxh,myl,myh,mzl,mzh = 0;

	// DEBUG CODE TEST BEGIN
	while(1){
		
		// read accelerometer values		
		i2c_start();
		i2c_write_byte(0x30); // write acc
		i2c_write_byte(0xa8); // OUT_X_L_A, MSB set to enable auto-increment
		i2c_start();		  // repeated start
		i2c_write_byte(0x31); // read acc
		axl = i2c_read_byte();
		axh = i2c_read_byte();
		ayl = i2c_read_byte();
		ayh = i2c_read_byte();
		azl = i2c_read_byte();
		azh = i2c_read_last_byte();
		i2c_stop();


		// read magnetometer values
		i2c_start(); 
		i2c_write_byte(0x3C); // write mag
		i2c_write_byte(0x03); // OUTXH_M
		i2c_start();		  // repeated start
		i2c_write_byte(0x3D); // read mag
		mxh = i2c_read_byte();
		mxl = i2c_read_byte();
		myh = i2c_read_byte();
		myl = i2c_read_byte();
		mzh = i2c_read_byte();
		mzl = i2c_read_last_byte();
		i2c_stop();

		PORTD = axl; // Output the read value for the accelerometer X axis low byte

		// Blink led to know that things are running
		for(int r=0;r<5;r++) delay_ms(100); // Wait 1/2 sec
		PORTC = 1;
		for(int r=0;r<5;r++) delay_ms(100); // Wait 1/2 sec
		PORTC = 0;
		
	}
}

(I’ve also attached the whole *.c file to this message If you find it neccesary)

I hope you can give me some advice on how to solve this, I expected to have issues with all the whole I2C voltage thing, but it appears that the voltage shifting circuitry already on the board do all the work.

Best regards, David.
acelerometro.c (7.19 KB)

UPDATE: I’ve found a strange thing.
With the circuit and program as-is, I disconnected the power from the sensor module and still, the AVR managed to get to the blinking LED stage every loop.

I thought previously that without a connection to the other device (or the other device being not powered) would make the AVR to keep on waiting for an ACK, but it appears not to do so.

UPDATE 2:
I’m checking my connections again and sampling the TWI pins with an oscilloscope.
The AVR’s TWI pins stay UP (because of the pullups) but it never pulls them down to ground (to produce the clock or data signals).

Now, I am puzzled by two things:

  1. Why do I get these values even if the signal is not obtained from the wire?
  2. Is there a flaw in the code you provided, or an incompatibility with the registers between the Orangutan’s chip and mine? (This one I doubt is the problem).

By now, I supposed using the Orangutan’s code wasn’t the best idea afterall, there are lots of hidden dependencies and definitions I do not have and can’t use because I’m using a different chip.

Now, I turned to Peter Fleury’s library, which can be found here:
http://homepage.hispeed.ch/peterfleury/avr-software.html

It’s quite simplified and pretty straight-forward, I recommend giving it a look.

Now, I’ve modified the main file to poll for the device and read the samples and… SURPRISE, the device is sending 0x31 at all times, in all components of the acceleration measurements.

That is even backed by the oscilloscope plot, it clearly shows the long pulse for a “3” and the short pulse for the “1” in 0x31. (Yeah, there is a glitch over here).

My new code is as follows: (The only modified file is this one, I kept the header and twimaster.c files as-is)

/****************************************************************************
Title:    Access LSM303DLH Accelerometer
Author:   David Valencia
Based on the code and i2c libraries by:
		Peter Fleury <pfleury@gmx.ch> http://jump.to/fleury
File:     $Id: test_i2cmaster.c,v 1.2 2003/12/06 17:07:18 peter Exp $
Software: AVR-GCC 3.3
Hardware: AT90S8515 at 4 Mhz, any AVR device can be used
			FOR THIS PARTICULAR PROJECT: ATMEGA8 at 4MHz was used

Description:
    This example shows how the I2C library i2cmaster.S can be used to 
    access the accelerometer.

*****************************************************************************/
#include <avr/io.h>
#include "i2cmaster.h"

#define Accel	  0x30	// device address for Accelerometer
#define Magnet    0x3C	// device address for Magnetometer


int main(void)
{
    unsigned char ret;
	unsigned char mode = 1;
	// Mode = 0 --> Accelerometer mode
	// Mode = 1 --> Magnetometer mode    

    DDRD  = 0xff;		// use all pins on port D for output 
    PORTD = 0x00;       // (active high LED's )

	DDRC = 0x01;		// Use PC0 as status LED
	PORTC = 0x00;

    i2c_init();         // init I2C interface
	
	if(mode==0){
		// Test for accelerometer
		// Start a transference sending the accelerometer's address and write mode
		ret = i2c_start(Accel+I2C_WRITE);
	
		if(ret){
			// If ret=0 the start failed, device is not accesible
			i2c_stop();
			PORTC = 0x01;
		}else{
			// If ret!=0 the start succeeded and the device is accesible
			PORTC = 0x00;

			//enable and configure accelerometer
			ret = i2c_start(Accel+I2C_WRITE);
			i2c_write(0x20); // CTRL_REG1_A
			i2c_write(0x27); // normal power mode, 50 Hz data rate, all axes enabled
			i2c_stop();

			unsigned char axl,axh,ayl,ayh,azl,azh = 0;	

			while(1){
			// read accelerometer values
			ret = i2c_start(Accel+I2C_WRITE);
			i2c_write(Accel+I2C_WRITE); // write acc
			i2c_write(0xa8); // OUT_X_L_A, MSB set to enable auto-increment
			ret = i2c_start(Accel+I2C_WRITE);
			i2c_write(Accel+I2C_READ); // Write Read address of acc

			axl = i2c_readAck();
			axh = i2c_readAck();
			ayl = i2c_readAck();
			ayh = i2c_readAck();
			azl = i2c_readAck();
			azh = i2c_readNak();
			i2c_stop();

			PORTD = axh;
			}

		}
	}else{
		// Test for magnetometer
		// Start a transference sending the magnetometer's address and write mode
		ret = i2c_start(Magnet+I2C_WRITE);
	
		if(ret){
			// If ret=0 the start failed, device is not accesible
			i2c_stop();
			PORTC = 0x01;
		}else{
			// If ret!=0 the start succeeded and the device is accesible
			PORTC = 0x00;

			//enable and configure Magnetometer
			ret = i2c_start(Magnet+I2C_WRITE);
			i2c_write(0x02); // MR_REG_M
			i2c_write(0x00); // Continuous mode
			i2c_stop();

			unsigned char mxl,mxh,myl,myh,mzl,mzh = 0;	

			while(1){
			// Read magnetometer values
			ret = i2c_start(Magnet+I2C_WRITE);
			i2c_write(Magnet+I2C_WRITE); // write Magnet
			i2c_write(0x03); // OUTXH_M
			ret = i2c_start(Magnet+I2C_WRITE); // Repeated start
			i2c_write(Magnet+I2C_READ); // Write Read address of mag

			mxl = i2c_readAck();
			mxh = i2c_readAck();
			myl = i2c_readAck();
			myh = i2c_readAck();
			mzl = i2c_readAck();
			mzh = i2c_readNak();
			i2c_stop();

			PORTD = mxh;
			}

		}
	}
    
    for(;;);	
}

When in mode = 1, the displays read 0x3D as before…

What could it be?

Hello, David.

Your I2C code from your latest post looks a little suspicious to me. From the i2cmaster library documentation, i2c_start() “issues a start condition and sends address and transfer direction”, so I don’t think you should be transmitting the address and direction again. For example, the second line in this code is unnecessary:

         ret = i2c_start(Accel+I2C_WRITE);
         i2c_write(Accel+I2C_WRITE); // write acc

Also, this:

         ret = i2c_start(Accel+I2C_WRITE);
         i2c_write(Accel+I2C_READ); // Write Read address of acc

should be replaced by the single line:

         ret = i2c_start(Accel+I2C_READ);

I also noticed you are calling i2c_start() twice in a row: once to test for the device (lines 41 and 82) and once again after you have determined the device is accessible (lines 52 and 93). I don’t expect this to be a problem, but if my first suggestion doesn’t work, you could try commenting out the second calls.

Finally, our LSM303DLH board has pull-ups built in, so you shouldn’t need the 4.7k pull-ups on your breadboard, though again I don’t expect it to cause any problems.

- Kevin

Thanks Kevin !

I’ve clipped the duplicate start calls. I guess they remained because of the “conversion” from the original Orangutan’s file definitions to Pete’s library.

The new code looks like this, BUT the problem remains. The exact same 0x31 and 0x3D readings:

/****************************************************************************
Title:    Access LSM303DLH Accelerometer
Author:   David Valencia
Based on the code and i2c libraries by:
		Peter Fleury <pfleury@gmx.ch> http://jump.to/fleury
File:     $Id: test_i2cmaster.c,v 1.2 2003/12/06 17:07:18 peter Exp $
Software: AVR-GCC 3.3
Hardware: AT90S8515 at 4 Mhz, any AVR device can be used
			FOR THIS PARTICULAR PROJECT: ATMEGA8 at 4MHz was used

Description:
    This example shows how the I2C library i2cmaster.S can be used to 
    access the accelerometer.

*****************************************************************************/
#include <avr/io.h>
#include "i2cmaster.h"
#include <util/delay.h>

// #define F_CPU 4000000 // 4mhz

#define Accel	  0x30	// device address for Accelerometer
#define Magnet    0x3C	// device address for Magnetometer
#define Dummy 	  0x10	// device address for Dummy (non existent)

void delay_ms(unsigned int time_ms)
{
  while (time_ms--)
    _delay_ms(1);
}

int main(void)
{
    unsigned char ret;
	unsigned char mode = 0;
	// Mode = 0 --> Accelerometer mode
	// Mode = 1 --> Magnetometer mode    

    DDRD  = 0xff;		// use all pins on port D for output 
    PORTD = 0x00;       // (active high LED's )

	DDRC = 0x01;		// Use PC0 as status LED
	PORTC = 0x00;

    i2c_init();         // init I2C interface
	
	if(mode==0){
		// Test for accelerometer
		// Start a transference sending the accelerometer's address and write mode
		ret = i2c_start(Accel+I2C_WRITE);
	
		if(ret){
			// If ret=0 the start failed, device is not accesible
			i2c_stop();
			PORTC = 0x01;
		}else{
			// If ret!=0 the start succeeded and the device is accesible
			PORTC = 0x00;

			//enable and configure accelerometer
			// ret = i2c_start(Accel+I2C_WRITE); // Duplicate start
			i2c_write(0x20); // CTRL_REG1_A
			i2c_write(0x27); // normal power mode, 50 Hz data rate, all axes enabled
			i2c_stop();

			unsigned char axl,axh,ayl,ayh,azl,azh = 0;	

			// read accelerometer values
			ret = i2c_start(Accel+I2C_WRITE);
			i2c_write(0xA8); // OUT_X_L_A, MSB set to enable auto-increment
			i2c_write(Accel+I2C_READ); // Write Read address of acc

			axl = i2c_readAck();
			axh = i2c_readAck();
			ayl = i2c_readAck();
			ayh = i2c_readAck();
			azl = i2c_readAck();
			azh = i2c_readNak();
			i2c_stop();

			PORTD = axl;
			delay_ms(20);

		}
	}else{
		// Test for magnetometer
		// Start a transference sending the magnetometer's address and write mode
		ret = i2c_start(Magnet+I2C_WRITE);
	
		if(ret){
			// If ret=0 the start failed, device is not accesible
			i2c_stop();
			PORTC = 0x01;
		}else{
			// If ret!=0 the start succeeded and the device is accesible
			PORTC = 0x00;

			//enable and configure Magnetometer
			// ret = i2c_start(Magnet+I2C_WRITE); // Duplicate start
			i2c_write(0x02); // MR_REG_M
			i2c_write(0x00); // Continuous mode
			i2c_stop();

			unsigned char mxl,mxh,myl,myh,mzl,mzh = 0;	

			// Read magnetometer values
			ret = i2c_start(Magnet+I2C_WRITE);
			i2c_write(0x03); // OUTXH_M
			i2c_write(Magnet+I2C_READ); // Write Read address of mag

			mxl = i2c_readAck();
			mxh = i2c_readAck();
			myl = i2c_readAck();
			myh = i2c_readAck();
			mzl = i2c_readAck();
			mzh = i2c_readNak();
			i2c_stop();

			PORTD = mxl;
			delay_ms(20);

		}
	}
    
    for(;;);	
}

Your i2c_write() calls with read addresses in lines 71 and 109 should be i2c_start() instead. (This would actually be a repeated start, so you could use i2c_rep_start() to make that clear, but i2c_rep_start() is just a wrapper for i2c_start().)

- Kevin

Voilá !

That worked great ! Now nested the code inside a while loop and it reads the values continuously. The LED bar blinks with different values if I tilt the protoboard.

Now I understand what was happening, with the repeated i2c_write() call, I was actually writing 0x31 to the register I later read, so I was reading just what I’ve written to the register. I was writing the ADDRESS to the register I previously called by writing 0xA8 before.

Kevin, I’d invite you a beer if we lived close but for now I can only offer my most sincere gratefulness man !

Since now, I have a very good opinion of Pololu and its workers, I will recommend your store to all who asks me for cool devices and cool support.

And, for the interested, the WORKING code is this:

/****************************************************************************
Title:    Access LSM303DLH Accelerometer and Magnetometer
Author:   David Valencia <ing.jdvp@gmail.com>
Based on the code and i2c libraries by:
		Peter Fleury <pfleury@gmx.ch> http://jump.to/fleury
File:     $Id: test_i2cmaster.c,v 1.2 2011/12/09 10:04 AM
Software: AVR-GCC 3.3
Hardware: ATMEGA8 at 4 Mhz, any AVR device can be used

Description:
    This example shows how the I2C library twimaster.c can be used to 
    access the accelerometer and magnetometer module provided by
	Pololu at:

	https://www.pololu.com/catalog/product/1250

*****************************************************************************/
#include <avr/io.h>
#include "i2cmaster.h"
#include <util/delay.h>

#define Accel	  0x30	// device address for Accelerometer
#define Magnet    0x3C	// device address for Magnetometer

// Wrapper for AVR's delay function
void delay_ms(unsigned int time_ms)
{
  while (time_ms--)
    _delay_ms(1);
}

int main(void)
{
    unsigned char ret;
	unsigned char mode = 0;
	// Mode = 0 --> Accelerometer mode
	// Mode = 1 --> Magnetometer mode    

    DDRD  = 0xff;		// use all pins on port D for output 
    PORTD = 0x00;       // (active high LED's )

	DDRC = 0x01;		// Use PC0 as status LED
	PORTC = 0x00;

    i2c_init();         // init I2C interface
	
	if(mode==0){
		// Test for accelerometer
		// Start a transference sending the accelerometer's address and write mode
		ret = i2c_start(Accel+I2C_WRITE);
	
		if(ret){
			// If ret=0 the start failed, device is not accesible
			i2c_stop();
			PORTC = 0x01; // Turn on debug LED
		}else{
			// If ret!=0 the start succeeded and the device is accesible
			PORTC = 0x00; // Turn off debug LED

			//enable and configure accelerometer
			i2c_write(0x20); // Call for CTRL_REG1_A (accelerometer control register)
			i2c_write(0x27); // normal power mode, 50 Hz data rate, all axes enabled
			i2c_stop(); // End transaction

			unsigned char axl,axh,ayl,ayh,azl,azh = 0;	
			
			while(1){
				// read accelerometer values
				ret = i2c_start(Accel+I2C_WRITE); // Repeated start to call for register
				i2c_write(0xA8); // Call for OUT_X_L_A register, MSB set to enable auto-increment
				ret = i2c_rep_start(Accel+I2C_READ); // RepStart & Write Read address of acc

				axl = i2c_readAck();
				axh = i2c_readAck();
				ayl = i2c_readAck();
				ayh = i2c_readAck();
				azl = i2c_readAck();
				azh = i2c_readNak();
				i2c_stop();

				PORTD = azh;
				delay_ms(20);
			}

		}
	}else{
		// Test for magnetometer
		// Start a transference sending the magnetometer's address and write mode
		ret = i2c_start(Magnet+I2C_WRITE);
	
		if(ret){
			// If ret=0 the start failed, device is not accesible
			i2c_stop();
			PORTC = 0x01; // Turn on Debug LED
		}else{
			// If ret!=0 the start succeeded and the device is accesible
			PORTC = 0x00;

			//enable and configure Magnetometer
			i2c_write(0x02); // Call for MR_REG_M register (magnetometer)
			i2c_write(0x00); // Continuous mode
			i2c_stop(); // End transaction

			unsigned char mxl,mxh,myl,myh,mzl,mzh = 0;	

			while(1){
				// Read magnetometer values
				ret = i2c_start(Magnet+I2C_WRITE);
				i2c_write(0x03); // Call for OUTXH_M register (results)
				ret = i2c_rep_start(Magnet+I2C_READ); // RepStart & Write Read address of mag

				mxl = i2c_readAck();
				mxh = i2c_readAck();
				myl = i2c_readAck();
				myh = i2c_readAck();
				mzl = i2c_readAck();
				mzh = i2c_readNak();
				i2c_stop();

				PORTD = mxh;
				delay_ms(20);
			}

		}
	}
    
    // for(;;);	
}

NOTE to Kevin: Feel free to share this code at your catalog page for any client interested in running your module with bare AVR’s.

Again, thanks a lot !