Adding an I2C Compass to the AVR Atmega32

By aliasmrjones - Last updated: Monday, April 6, 2009 - Save & Share - 4 Comments

The new 5hz gps module I added in the last post is working.  Now, to tackle the biggest problem discovered during Deathpod3000’s maiden voyage, we need a way to get very fast heading updates during turns.  While the faster refresh rate of the new gps will help, it is never going to give us accurate heading data in a turn.  In order to do that we need a compass.  In this post, I’ll describe how I added a digital i2c compass to Deathpod.

Even with 5hz updates, the gps isn’t going to be able to give us accurate heading data during a turn.  The reason is a gps figures out heading based on a line drawn from where you were during the last update to where you are this update.  In a turn, as the car goes around in a circle, the nose is always pointing beyond this line so the gps heading will always lag behind the true direction the car is pointing.

The way to get an accurate heading reading in a turn is with a compass.  Our UART (regular serial port) is already being used by the gps, so we need a compass with some other interface.  The Atmega32 has both spi and i2c synchronous serial.  Sparkfun carries a digital compass chip with an i2c interface mounted on a small board with spots to solder pins at standard .1″ spacing.  The Honywell HMC6352 mounted on a breakout board requires only 4 connections:  5v, GND, clock (SCL) and data (SDA).

Honeywell HMC6352 I2C Compass

I2c is a 2 wire synchronous serial interface.  It is actually a bus since it can accomodate multiple slave devices and a master device on only 2 wires.  I2c was originally developed by Philips and to get around  licensing costs, other manufacturers have released their own implementations.  Smbus is compatible with i2c and is used on many motherboards to communicate with temperature sensors.  Atmel calls it TWI (Two Wire Interface).

The two wires  that make up the i2c bus are clock and data.  The master drives the clock and both master and slave transmit using the data line.  In this case, the Atmega32 will be the master and the compass chip will be the slave.  Because it is a synchronous interface, much higher transfer speeds are possible than with an async serial interface like rs232.  The “slow” speed is 100 Kbps and you can go up to 1 Mbps with some devices.  Because of the fast speed, small number of connections required and ability to connect multiple devices on the same bus, i2c is a very popular communication mechanism for microcontrollers.  You can find devices that use i2c ranging from eeprom chips to sonars to motor controllers and compasses.

I soldered standard straight pins onto the compass breakout board thinking I’d use one of the 10 pin header ribbon cables I had to connect to similar pins on the final board.  This will allow me to connect and disconnect the compass easily if I want to use it on another project in the future.  On the Atmega32, pin PC0 is SCL and PC1 is SDA.  I used breadboard wires to connect the 10 pin header to the correct pins on the STK500 dev board.

I2c handles multiple devices on a single 2 wire interface by assigning each device a unique 7 bit address.  The compass by default uses address hex 42.  The master device doesn’t need an address.

AVRlib includes a module to handle i2c communication both as a master and slave.  We’ll use this module to communicate with the compass.  The library makes this pretty easy and we will only need to call one fuction to write to the device and 1 function to read from the device.  All the low-level stuff is taken care of by the library and the AVR hardware.  By default, the compass is in “query” mode.  It will take and return a reading on command.  This sounds fine for what we want to do.

To take a reading you must first send hex 41, then read 2 bytes (MSB, LSB) of heading.  The heading has an implied 1 decimal point so a reading of 2436 would be a heading of 243.6 degrees.  It takes 6 ms for the compass to compute the heading so we’ll put in a little delay of 10ms to let it do it’s magic.  First, in order to make changes easier, we’ll put in a define for the target address of our i2c commands.

//define for compass iic addresses
#define TARGET_ADDR 0×42

I put the code to read a compass heading in a function so it will be clean and easy to read the heading in our main processing loop.  In order to account for errors in compass placement (where the compass isn’t pointing directly forward on the car), I included an offset passed to the function that we can use later if we need to correct for some heading error.  Here is the code in my compass reading function.

float GetCompassHeading(float offset)
{
u08 c=0;
u08 in[2];
int reading = 0;
float returnHeading = 0.0;
c = 0×41;
i2cMasterSend(TARGET_ADDR, 1, &c);
timerPause(10);
i2cMasterReceive(TARGET_ADDR, 2, &in[0]);
reading = in[0];
reading = reading << 8;
reading += in[1];
returnHeading = (float) reading;
returnHeading = returnHeading / 10;
returnHeading += offset;
if(returnHeading < 0)
{
returnHeading += 360;
}
if(returnHeading >= 360)
{
returnHeading -= 360;
}

return returnHeading;
}

No indenting for some reason.  Still readable I guess.  Anyway, set up unsigned char variable c to hold the “command” to send and 2 byte array in for the returned data.  We’ll convert the reading to an in int called reading and use a float to hold the final reaturn heading.  We set the command to 0×41 (hex 41 or the character ‘A’) and then call i2cMasterSend, the AVRlib command to transmit.  The three parameters are i2c address, bytes to send and a pointer to the data.  Next we wait 10ms and then read the result.  i2cMasterReceive takes the same 3 parameters as send.  We read the first byte (MSB), then shift it 8 bits to the left and then add the next byte we received to construct the 16 bit int.  Next we convert it to a float and load it into our returnHeading, then divide by 10 to get our xxx.x heading.

Now we will deal with the offset passed into the function.  First we simplyadd it to the heading.  At this point, if the offset is negative  and the heading is close to 0, we could end up with negative heading.  For example, let’s say the heading is 006.0 and offset is -9.  The returned heading would be -003.0, which doesn’t make any sense.  To account for this, if the heading ends up less than 0, we add 360.  So in our example, -003.0 + 360 = 357.0.  In the same way, with a positive offset and a heading near 360, we could end up with a heading that greater than 360.  We take care of this situation by subtracting 360 if the heading is greater than or equal to 360.  Now we will always return a heading between 0 and 359.9 degrees.

I changed the main navigation loop to read the compass, use the compass heading for steering rather than the gps heading and display the compass heading on the LCD.  Turning on the system gave me…nothing.  I was getting 0 no matter which way I turned the compass.  I searched the web, I double-check the code, I tried non-interrupt driven i2c calls.  Then I checked the wiring.  It turns out I had the SDA line connected to the wrong spot on the header.  I moved the wire over one spot and bingo – rapidly changing heading readings.

The compass chip has a calibration mode where you spin the chip around for several seconds and it calibrates to it’s environment in case there is metal around, etc.  I added an option when the system starts up to allow the user to do the calibration and then point to the first waypoint stored in eeprom.  The car calculates an offset based on this and stores the offset to feed to the compass heading reading function.

Testing the new setup with the faster gps and digital compass proved successful.  The car no longer drove around in circles, but once it figured out bearing to the waypoint, it drove straight toward it.  The waypoint tended to move around because of gps error, but the car was able to track directly to where it thought the waypoint was.

Navigation is working.  The next step is to ditch the STK500 dev board and wire up the final, much smaller and lighter board and start attaching things more permanently to the car.

Share and Enjoy:
Posted in AVR Sensors • Tags: , , , Top Of Page

4 Responses to “Adding an I2C Compass to the AVR Atmega32”

Comment from Hanno Schäfer
Time June 18, 2009 at 9:18 am

Hi there,

we are working on a project for our university. We try to build a small gps handheld which guides you to saved positions. The gps module is already working fine, but the compass module is not working at all.
We are using an Atmega16, STK500 Board and the HMC6352.

Could you help us out with the c code.
What is the exact name of the module you include for the I2C communication?

Could you sent us the whole “read direction” code?

Thanks a lot!

Greetings from Germany

Comment from aliasmrjones
Time June 18, 2009 at 9:35 am

Yes, I will email you the code. The code I use for reading the compass is exactly what I posted in the article. I use a library by Pascal Stang called AVRLib that handles the I2C communication so my code is very short. I did find that the compass works best if you calibrate it. After calibration I got very good results. Also, note that it is very sensitive to tilt. A tilt-compensating compass costs 3 times as much, though, and most of the road my car was driving on was relatively flat. For a handheld device, the tilt might be an issue. Also, for a handheld device, I don’t think a company is as necessary. My car was turning very fast, a person doesn’t turn that fast. I have a store-bought handheld gps without compass and the heading readings I get just from gps are good enough for me when I’m walking.

Comment from Mike Scheuer
Time July 16, 2009 at 8:40 am

I’ve been working with a GPS module and was thinking of adding a compass for direction purposed. I am already able to calcualted bearing and distance between points but want to know compass direction as well.
I picked up a keyboard from liquidwire to input coordinates to an arduino diecimila with an ATMega328. The compass will work well with what I’m currently using! Especially since it is I2C!

The info that you have here will be very helpful!

Thanks!

Comment from Jonathan Swaby
Time August 22, 2009 at 5:15 am

Would you mind posting the calibration code for the compass? Still searching the net for more details on calibrating the device. Do I just rotate 360 degrees once, then exit C mode, or perhaps I rotate it a couple of time before exiting.

Thanks

Write a comment