HMC5883L 3-Axis Compass


15 posts
by messinwu » Tue Sep 11, 2012 11:57 pm
Hey everyone

I'm very new to programming, and I mostly know how to adapt from example. So, I'm pulling my hair out trying to get the HMC5883L to work with the Pi. Since it has 3 axis's and uses the first 6 byes from the output just like the ADXL345 accelerometer, I started out by cloning most of the code I was give which works very well for that sensor. However, while I am getting values from the compass, I don't think they're correct, or I'm mis-handling them in the code or calculations to arrive at a correct heading (0-360 in degress).

Has anyone got this sensor working correctly on the Pi yet? I'm hoping someone can help me work through the problem on here, and hopefully I can learn some things along the way. Here's the code I have working so far, but as I mentioned, as I rotate the module the values do change, but they are wrong, they keep going up and down, and don't seem to make it past 90 I'd say.

Code: Select all
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/i2c-dev.h>

#define HMC5883L_I2C_ADDR 0x1e

void selectDevice(int fd, int addr, char * name)
{
   if (ioctl(fd, I2C_SLAVE, addr) < 0)
   {
      fprintf(stderr, "%s not present\n", name);
      //exit(1);
   }
}

void writeToDevice(int fd, int reg, int val)
{
   char buf[2];
   buf[0]=reg; buf[1]=val;
   if (write(fd, buf, 2) != 2)
   {
      fprintf(stderr, "Can't write to HMC5883L\n");
      //exit(1);
   }
}

int main(int argc, char **argv)
{
   int x, y, z;
   float head;
   int fd;
   int buf[16];
   
   if ((fd = open("/dev/i2c-0", O_RDWR)) < 0)
   {
      // Open port for reading and writing
      fprintf(stderr, "Failed to open i2c bus\n");
      exit(1);
   }
   
   /* initialise HMC5883L */

   selectDevice(fd, HMC5883L_I2C_ADDR, "HMC5883L");

     writeToDevice(fd, 0x3c, 0x70);
     writeToDevice(fd, 0x3c, 0xA0);
     writeToDevice(fd, 0x3c, 0x00);

usleep(67000);
     writeToDevice(fd, 0x3c, 0x03);

   while (1)
   {   

      /* select HMC5883L */

      selectDevice(fd, HMC5883L_I2C_ADDR, "HMC5883L");

      buf[0] = 0x06;
   
      if ((write(fd, buf, 1)) != 1)
      {
         // Send the register to read from
         fprintf(stderr, "Error writing to i2c slave\n");
         //exit(1);
      }
   
      if (read(fd, buf, 6) != 6)
      {
         //  X, Y, Z readings
         fprintf(stderr, "Unable to read from HMC5883L\n");
         //exit(1);
      }
      else
      {
    x = buf[0]<<8| buf[1];
         y = buf[2]<<8| buf[3];
         z = buf[4]<<8| buf[5];
         head = atan2 (y,x) * 180 / 3.14159265358979323846;

         printf("%4.0f\n", head);
    writeToDevice(fd, 0x3c, 0x03);
      }
      usleep(67000);
   }
   
   return 0;
}


Of course, as is my style, I've scoured the web for code examples, not all of them include a heading calculation, but that's really what I want. There's a correction for magnetic declination to include as well, but I figured I better get this part working correctly first. Some of the links I've been using to derive commands and code, in addition to the datasheet, are:

https://www.loveelectronics.co.uk/Tutor ... no-library
http://www.seeedstudio.com/wiki/Grove_- ... pass_v1.0b
http://dakax.googlecode.com/svn-history ... C5883L.cpp

I know it's frowned upon to ask for help without trying enough first, so I've put in about two weeks of playing around with this module and C Code tweaks, and just can't figure it out myself.

Would anyone care to assist? Even if you don't have this module yourself, maybe you can spot obvious errors in the code? Help! :)
Posts: 17
Joined: Wed Aug 08, 2012 1:33 pm
by jkp » Wed Sep 12, 2012 1:06 pm
I am having problems of my own with the same compass sensor and I am getting to the point, where I believe, that my sensor is broken. I get random values for x,y,z, jumping up and down between seemingly random values, no matter how I rotate the sensor.

I have tried your code + other code with no luck. One error I spotted, though, is that you read the values in the order x,y,z. If you look at the data sheet, the sensor returns the values in the order x,z,y so try to swap z and y where you read the values from the sensor.
Posts: 2
Joined: Wed Sep 12, 2012 1:02 pm
by jkp » Wed Sep 12, 2012 1:15 pm
...And by the way, you need to make the buffer unsigned char, not int. You are reading bytes from the sensor, not 32 bit integers.
Posts: 2
Joined: Wed Sep 12, 2012 1:02 pm
by messinwu » Wed Sep 12, 2012 4:29 pm
jkp - Thanks!

I guess of all the combing through I did with the datasheet, I missed that! As I mentioned, I did clone the ADXL345 code, which actually produced output almost right away as-is, but you're right, that should be swapped.

I'll do that, and change the variable type and see what I get. If I get good values all of a sudden, then yes, I'd say yours might be defective if it doesn't work with the same code....

I'll post back hopefully later today!
Posts: 17
Joined: Wed Aug 08, 2012 1:33 pm
by Tiger » Fri Mar 29, 2013 10:56 am
If think:
buf[0] = 0x06;

should be:
buf[0] = 0x03;

to point to the most significant byte of the X measurement.
Posts: 2
Joined: Tue Oct 09, 2012 9:29 pm
by timr » Wed Apr 03, 2013 2:52 pm
Just a guess... have you included <math.h> ?
Posts: 22
Joined: Wed May 30, 2012 10:11 am
by timr » Wed Apr 03, 2013 4:45 pm
Ok, I think I have something to report.
I actually have one of these (real bargain!) but I was using python, so it took a while to get the C working

The read()/write() interface for I2C doesn't work like you have it, see e.g. http://www.mjmwired.net/kernel/Document ... -interface

So I hacked up the original code, and got it working enough to report the device id:

Code: Select all
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/i2c-dev.h>

#include <math.h>

#define HMC5883L_I2C_ADDR 0x1e

void selectDevice(int fd, int addr, char * name)
{
   if (ioctl(fd, I2C_SLAVE, addr) < 0)
   {
      fprintf(stderr, "%s not present\n", name);
      //exit(1);
   }
}

void writeToDevice(int fd, int reg, int val)
{
   char buf[2];
   buf[0]=reg; buf[1]=val;
   if (write(fd, buf, 2) != 2)
   {
      fprintf(stderr, "Can't write to HMC5883L\n");
      //exit(1);
   }
}

int main(int argc, char **argv)
{
   int x, y, z;
   float head;
   int fd;
   int res;
   unsigned char buf[16];
   unsigned char cmd[16];

   if ((fd = open("/dev/i2c-0", O_RDWR)) < 0)
   {
      // Open port for reading and writing
      fprintf(stderr, "Failed to open i2c bus\n");
      exit(1);
   }
       
   /* initialise HMC5883L */

   selectDevice(fd, HMC5883L_I2C_ADDR, "HMC5883L");


   /* Set address pointer to ident register (10) */

   /* Device documentation says
      To move the address pointer to a random register location, first issue a write
      to that location with no data byte following the command.
   */

   cmd[0] = 10;
   res = write(fd, cmd, 1);
   if (res != 1) {
     printf("Failed to write address");
     exit(-1);
   }

   /* Read device ident back */
   res = read(fd, buf, 3);
   if (res != 3) {
     printf("Failed to read");
     exit(-1);
   }

   printf("%c %c %c\n", buf[0], buf[1], buf[2]);
   if (buf[0] != 'H' || buf[1] != '4' || buf[2] != '3') {
     printf("Incorrect device id\n");
     exit(-1);
   }
   return 0;
}



Hope that helps. I'll leave the rest to you, though I may carry on hacking out of interest

Thanks

tim
Posts: 22
Joined: Wed May 30, 2012 10:11 am
by timr » Wed Apr 03, 2013 6:03 pm
And if you use unsigned char for the buffer, and int for the x,y,z field strengths, you need to handle sign extension from 16 bit (2 bytes) to 32bits.
I left the buffer as signed char, then when the msb and lsb are combined, the sign is extended properly:

x = buf[0] << 8 | (buf[1] & 0xff);
Posts: 22
Joined: Wed May 30, 2012 10:11 am
by aszinovyev » Sun May 05, 2013 8:17 pm
This code works for me:
Code: Select all
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/i2c-dev.h>
#include <math.h>

const int HMC5883L_I2C_ADDR = 0x1E;

void selectDevice(int fd, int addr, char * name)
{
    if (ioctl(fd, I2C_SLAVE, addr) < 0)
    {
        fprintf(stderr, "%s not present\n", name);
        //exit(1);
    }
}

void writeToDevice(int fd, int reg, int val)
{
    char buf[2];
    buf[0]=reg;
    buf[1]=val;

    if (write(fd, buf, 2) != 2)
    {
        fprintf(stderr, "Can't write to ADXL345\n");
        //exit(1);
    }
}

int main(int argc, char **argv)
{
    int fd;
    unsigned char buf[16];

    if ((fd = open("/dev/i2c-1", O_RDWR)) < 0)
    {
        // Open port for reading and writing
        fprintf(stderr, "Failed to open i2c bus\n");

        return 1;
    }

    /* initialise ADXL345 */

    selectDevice(fd, HMC5883L_I2C_ADDR, "HMC5883L");

    //writeToDevice(fd, 0x01, 0);
    writeToDevice(fd, 0x01, 32);
    writeToDevice(fd, 0x02, 0);
       
    for (int i = 0; i < 10000; ++i) {   
        buf[0] = 0x03;

        if ((write(fd, buf, 1)) != 1)
        {
            // Send the register to read from
            fprintf(stderr, "Error writing to i2c slave\n");
        }

        if (read(fd, buf, 6) != 6) {
            fprintf(stderr, "Unable to read from HMC5883L\n");
        } else {
            short x = (buf[0] << 8) | buf[1];
            short y = (buf[4] << 8) | buf[5];
            short z = (buf[2] << 8) | buf[3];
           
            float angle = atan2(y, x) * 180 / M_PI;

            //for (int b=0; b<6; ++b)
            //{
            //    printf("%02x ",buf[b]);
            //}
            //printf("\n");
           
            printf("x=%d, y=%d, z=%d\n", x, y, z);
            printf("angle = %0.1f\n\n", angle);
        }
       
        usleep(600 * 1000);
    }

    return 0;
}
Posts: 1
Joined: Tue Apr 30, 2013 5:32 pm
by alexellis » Wed Jan 15, 2014 8:44 pm
I've tried building your code example but I get an error M_PI is not defined, I defined this by copying it from the internet and then I get an error atan2 is not defined. Can you help?
Posts: 53
Joined: Tue Nov 26, 2013 10:00 am
Location: United Kingdom
by DougieLawson » Wed Jan 15, 2014 8:50 pm
Do you have the maths development library installed?

sudo apt-get install libc6-dev
Microprocessor, Raspberry Pi & Arduino Hacker
Mainframe database troubleshooter
MQTT Evangelist
Twitter: @DougieLawson

Since 2012: 1B*5, 2B*2, B+, A+, Zero*2, 3B*3

Please post ALL technical questions on the forum. Do not send private messages.
User avatar
Posts: 26902
Joined: Sun Jun 16, 2013 11:19 pm
Location: Basingstoke, UK
by alexellis » Thu Jan 16, 2014 9:40 am
DougieLawson wrote:Do you have the maths development library installed?

sudo apt-get install libc6-dev


Yes, I do. I managed to get around it by defining M_PI myself and then using this code to compile:

Code: Select all
gcc -lm tester.c -o ./tester -std=c99


Once compiled, error is as follows (this is on a Rev 2 Model A RPi) - i2cdetect did show the device.

~ $ sudo ./tester
Can't write to ADXL345
Can't write to ADXL345
Error writing to i2c slave
Unable to read from HMC5883L
Error writing to i2c slave
Unable to read from HMC5883L
Error writing to i2c slave
Unable to read from HMC5883L
Error writing to i2c slave
Unable to read from HMC5883L
Error writing to i2c slave
Unable to read from HMC5883L
Posts: 53
Joined: Tue Nov 26, 2013 10:00 am
Location: United Kingdom
by alexellis » Tue Jan 21, 2014 10:50 pm
I fiddled with the pins and now I get some output. What does the angle output correspond to? The chip is facing up showing the IC.

If I turn the breadboard anti-clockwise the number goes down to zero then negative. if I continue to turn it goes to about -50 then goes to -57 then round to 1.
Posts: 53
Joined: Tue Nov 26, 2013 10:00 am
Location: United Kingdom
by chri14_8 » Thu Mar 05, 2015 3:38 pm
Hi,

I have a problem as the sensor is giving me readings but they doesn't make any sense. If I rotate the sensor by 90 degrees, the change is much less than 90.

Any ideas please?

Thank you!
Posts: 1
Joined: Thu Mar 05, 2015 3:36 pm
by liderbug » Tue Jul 21, 2015 1:24 am
A. I'm waiting for my chip to ship.
B. I've been able to get the code to compile. My question is: if I align my chip to "N" then rotate a magnet round the chip - i.e. a weathervane - thoughts on how that might work?
C. Can my magnet, if too strong/close, damage my chip?

Thanks
Posts: 88
Joined: Sat Oct 08, 2011 4:47 pm