HMC5883L not accurate

I have the following code and everytime I re-read the new values the are always a little bit different. (not touching the HMC5883L!)

My plan was to use the HMC5883L to let my robot drive in a straight line, but since the value is always different, I don't see how this will work.

 

[code]

/*
 * Using the HMC5883L (GY-273)
 *
 * Connection on a Raspberry Pi 3 model B v1.2 :
 * - VCC -> 3V3 (1)
 * - GND -> GND (6)
 * - SCL -> GPIO3 - SCL1 I2C (5)
 * - SDA -> GPIO2 - SDA1 I2C (3)
 *
 * Compiled in Netbeans IDE 8.1
 * Also possible to compile with GCC :
 * 'GCC main.cpp -o HMC5883L -lm'
 */

/*
 * File:   main.cpp
 * Author: Steven Noppe
 *
 * Revision date : 13/11/2016
 */

/*
 * Compiling with GCC, gives a fault because M_PI is not defined
 * With an IDE like Netbeans, we do not have that problem
 * Thats why we define it here
 */
#ifndef M_PI
 #define M_PI 3.14159265358979323846
#endif

/*
 * Same problem here with GCC, it seems he doesn't know what 'true' is
 */
#ifndef true
 #define true 1
#endif

#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>

class HMC5883L
{
 private:
  short x ;
  short y ;
  short z ;
  
  float angle ;
  
  int fd ; // 'fd' stands for 'file descriptor'
  unsigned char buf[16] ;

  /*
   * Find out which address the HMC5883L is on with the command :
   * 'sudo i2cdetect -y 1' on a Raspberry Pi - model A, B rev 2 of B+
   * 'sudo i2cdetect -y 0' on a Raspberry Pi - model B rev 1
   * 1E => 0x1E
   */
  const int HMC5883L_I2C_ADDR = 0x1E ;

  void selectDevice(int fd, int addr, char *name)
  {
   /*
    * With the ioctl() function call we initialize the HMC3882L as
    * a i2c slave on the address that we found with
    * 'sudo i2cdetect -y 1' and declared in the integer HMC5883L_I2C_ADDR
    * see also : http://man7.org/linux/man-pages/man2/ioctl.2.html 
    */
   if (ioctl(fd, I2C_SLAVE, addr) < 0)
   {
    // if ioctl() returns < 0 then we had a problem
    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) ;
   }
  }
  
 public:
  short getX()
  {
   return x ;
  }
  
  short getY()
  {
   return y ;
  }
  
  short getZ()
  {
   return z ;
  }
  
  float getAngle()
  {
   return atan2(y, x) * 180 / M_PI ;
  }
  
  void initializeHMC5883L()
  {
   /*
    * Open i2c-1 port for reading and writing with the open system call
    * The open system call returns a number which you will need in future
    * functions for reading and writing
    * see also : https://en.wikipedia.org/wiki/Open_(system_call)
    */
 
   if ((fd = open("/dev/i2c-1", O_RDWR)) < 0)
   // on a Raspberry Pi - model B rev 1 use : '/dev/i2c-0'
   {
    // If it returns < 0 then something was wrong
    fprintf(stderr, "Failed to open i2c bus\n") ;
    // exit program with a 1 (failure)
    exit(1) ;
   }
   else
   {
    // successfull !
    printf("Succesfully openend i2c bus\n") ;
   }
 
   /*
    * Initialise the HMC5883L with the integer 'fd' that was returned from the
    * open system call
    */
   selectDevice(fd, HMC5883L_I2C_ADDR, "HMC5883L") ;

   /*
    * Now write some values to the HMC5883L to initalize it
    */
 
   /*
    * First write the value 32 to the configuration register A
    * 32 = Gain = 1090 Lsb/Gauss
    * We send the following byte : 00100000
    */
   writeToDevice(fd, 0x01, 32) ; // 0x01 = configuration register A
   
   /*
    * Second write the value 0 to the mode register
    * 0 = Continuous-Measurement Mode
    * We send the following byte : 0000000
    */
   writeToDevice(fd, 0x02, 0) ; // 0x02 = Mode register
  }
  
  void updateData()
  {
   buf[0] = 0x03 ;

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

  if (read(fd, buf, 6) != 6)
  {
   fprintf(stderr, "Unable to read from HMC5883L\n") ;
  }
  else
  {
   // Received the values
            x = (buf[0] << 8) | buf[1] ;
            z = (buf[2] << 8) | buf[3] ;
   y = (buf[4] << 8) | buf[5] ;
   
            //angle = atan2(y, x) * 180 / M_PI ;
   float declDegree = 0 ;
   float declMin = 35 ;
   float decl = (declDegree+declMin/60) * (M_PI/180) ;
   
   float headingRad = atan2(y, x) ;
   headingRad += decl ;
   
   if (headingRad < 0)
    headingRad += 2*M_PI ;
   
   if (headingRad > 2*M_PI)
    headingRad -= 2*M_PI ;
   
   float headingDeg = headingRad * 180/M_PI ;
   float degrees = floor(headingDeg) ;
   float minutes = round((headingDeg - degrees)*60) ;
   
   printf("degrees : %0.2f, minutes : %0.2f \n", degrees, minutes) ;

   // with GCC you need to declare b outside the for loop
   // or compile with the option -std=c11
   int b ; 
   for (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) ;
        }
 } 
};

// Main program
int main(int argc, char **argv)
{
    HMC5883L compass ;
 
 compass.initializeHMC5883L() ;
        
    while (true)
 {
        compass.updateData() ;
  
  printf("Angle : %f \n", compass.getAngle()) ;
  fflush(stdout) ;
  getchar() ;
    }
 
    return 0 ;
}

[/code]

Not accurate

Please post some sample data. What are your expectations? All the digital compasses that I have used generate small random errors when stationary and larger when in motion. Particularly bad when tilting. 

I have occasionally thought that by monitoring acceleration and compass angle I could compute a relative good estimate of position. Works ok in simulation, not so good is real life. If you find a reliable algorithm and hardware I would be very interested in learning more.

degrees : 271.00, minutes :

degrees : 271.00, minutes : 28.00
Angle : -89.111763
degrees : 272.00, minutes : 56.00
Angle : -87.643478
degrees : 271.00, minutes : 22.00
Angle : -89.222778
degrees : 272.00, minutes : 26.00
Angle : -88.148659
degrees : 271.00, minutes : 35.00
Angle : -89.006531
degrees : 272.00, minutes : 17.00
Angle : -88.302010
degrees : 271.00, minutes : 22.00
Angle : -89.221268
degrees : 272.00, minutes : 9.00
Angle : -88.430649
degrees : 271.00, minutes : 22.00
Angle : -89.221268
degrees : 270.00, minutes : 48.00
Angle : -89.783379

 

These are my readings with a stationary robot, each second.

I want to use a PD regulation for my motors, so my robot drives in a straight line.

**Compass accuracy is a **

Looks to me that your compass is working perfectly. ± 2 degrees is very good.

Add some wheel encoders for speed and distance measurements and some sonar or IR distance sensors for obstacle detection and start roboting!! Expecting perfection from an imperfect sensor can be a huge time sink.

Adding wheel encoders is not

Adding wheel encoders is not possible with the current motors.

I was thinking of just using the compass and a P(I)D regulation to let my robot drive straight.
Or should i really consider adding encoders?

After I achieve the goal of driving in a straight line, my next step will indeed be adding ultrasonic sensors.

Straight line

There is nothing stopping you from trying. I think the device can be sampled up to 160 times per second. Read the compass at 160Hz and digital filter (average) the previous 32 readings. Run the PID at 10Hz. Might give a stable result.

hmmm, it seems that

hmmm, it seems that something is wrong with my code, because when I turn my robot to 0, 90, 180, 270 degrees I do not get the right values :

Angle : 166.89
Heading in degrees : 337.00

Angle : 152.97
Heading in degrees : 278.00

Angle : 169.99
Heading in degrees : 252.00

Angle : 187.99
Heading in degrees : 32.00

I already checked my code and it should be correct :frowning:

edit : I tried another library from here : https://github.com/sanzaru/hmc5883l
and get the same results more or less :frowning:

X, Y, Z: 437.000000 | -227.000000 | 864.000000
Scaled:  402.040009 | -208.839996 | 794.880005
Orientation: Deg: 332.550368 | Rad: 5.804099

X, Y, Z: -34.000000 | -488.000000 | 924.000000
Scaled:  -31.280001 | -448.960022 | 850.080017
Orientation: Deg: 266.014529 | Rad: 4.642829

X, Y, Z: -56.000000 | -97.000000 | 902.000000
Scaled:  -51.520000 | -89.239998 | 829.840027
Orientation: Deg: 240.001322 | Rad: 4.188813

X, Y, Z: 209.000000 | 104.000000 | 838.000000
Scaled:  192.279999 | 95.680000 | 770.960022
Orientation: Deg: 26.455289 | Rad: 0.461732

Compass

Welcome to the wonderful world of digital compasses. They are influenced by almost anything and usually require calibration and tilt compensation. In my basement, the HMC5883 “North” changes significantly if I move the robot 3 meters.

Robotshop sells the tilt compensated CMPS11 for around $40US. I have had much better success with it but still think ±5 degrees when in motion is about the best one can expect. I also filter the angle and discard obviously bad results.