0

I am using an HMC-5983 magnetometer compass on my arduino uno to find the heading of a car I'm building.But my compass readings aren't varying linearly.For example when I physically turn my compass by 90 degrees the change in the reading doesn't correspond to 90 degrees.I tried mapping the raw output of the compass to the actual heading but when I do this , the headings in between the points I mapped aren't accurate enough thereby causing my car to behave abnormally.I also looked up on google but couldn't find anything helpful though there was some mention about using an algorithm to sort this issue.Please help me fix this problem.

    #include <Wire.h> //I2C Arduino Library
#define Magnetometer_mX0 0x03  
#define Magnetometer_mX1 0x04  
#define Magnetometer_mZ0 0x05  
#define Magnetometer_mZ1 0x06  
#define Magnetometer_mY0 0x07  
#define Magnetometer_mY1 0x08  
int mX0, mX1, mX_out;
int mY0, mY1, mY_out;
int mZ0, mZ1, mZ_out;
float heading, headingDegrees, headingFiltered, declination;
float Xm,Ym,Zm;
#define Magnetometer 0x1E //I2C 7bit address of HMC5883
void setup(){
  //Initialize Serial and I2C communications
  Serial.begin(9600);
  Wire.begin();
  delay(100);

  Wire.beginTransmission(Magnetometer); 
  Wire.write(0x02); // Select mode register
  Wire.write(0x00); // Continuous measurement mode
  Wire.endTransmission();
}
void loop(){

//---- X-Axis
  Wire.beginTransmission(Magnetometer); // transmit to device
  Wire.write(Magnetometer_mX1);
  Wire.endTransmission();
  Wire.requestFrom(Magnetometer,1); 
    if(Wire.available()<=1)   
  {
    mX0 = Wire.read();
  }
  Wire.beginTransmission(Magnetometer); // transmit to device
  Wire.write(Magnetometer_mX0);
  Wire.endTransmission();
  Wire.requestFrom(Magnetometer,1); 
  if(Wire.available()<=1)   
  {
    mX1 = Wire.read();
  }
  //---- Y-Axis
  Wire.beginTransmission(Magnetometer); // transmit to device
  Wire.write(Magnetometer_mY1);
  Wire.endTransmission();
  Wire.requestFrom(Magnetometer,1); 
  if(Wire.available()<=1)   
  {
    mY0 = Wire.read();
  }
  Wire.beginTransmission(Magnetometer); // transmit to device
  Wire.write(Magnetometer_mY0);
  Wire.endTransmission();
  Wire.requestFrom(Magnetometer,1); 
  if(Wire.available()<=1)   
  {
    mY1 = Wire.read();
  }

  //---- Z-Axis
  Wire.beginTransmission(Magnetometer); // transmit to device
  Wire.write(Magnetometer_mZ1);
  Wire.endTransmission();
  Wire.requestFrom(Magnetometer,1); 
  if(Wire.available()<=1)   
  {
    mZ0 = Wire.read();
  }
  Wire.beginTransmission(Magnetometer); // transmit to device
  Wire.write(Magnetometer_mZ0);
  Wire.endTransmission();
  Wire.requestFrom(Magnetometer,1); 
  if(Wire.available()<=1)   
  {
    mZ1 = Wire.read();
  }

  //---- X-Axis
  mX1=mX1<<8;
  mX_out =mX0+mX1; // Raw data
  // From the datasheet: 0.92 mG/digit
  Xm = mX_out*0.00092; // Gauss unit
  //* Earth magnetic field ranges from 0.25 to 0.65 Gauss, so these are the values that we need to get approximately.
  //---- Y-Axis
  mY1=mY1<<8;
  mY_out =mY0+mY1;
  Ym = mY_out*0.00092;
  //---- Z-Axis
  mZ1=mZ1<<8;
  mZ_out =mZ0+mZ1;
  Zm = mZ_out*0.00092;
  // ==============================
  //Calculating Heading
  heading = filtered_angle(Zm, Xm);// arc tangent of z/x

  // Correcting the heading with the declination angle depending on your location
  // You can find your declination angle at: http://www.ngdc.noaa.gov/geomag-web/
  // At my location it's 4.2 degrees => 0.073 rad
  declination = 0.03717551307 ;
  heading += declination;

  // Correcting when signs are reveresed
  if(heading <0) heading += 2*PI;
  // Correcting due to the addition of the declination angle
  if(heading > 2*PI)heading -= 2*PI;
  headingDegrees = heading * 180/PI; // The heading in Degrees unit
  // Smoothing the output angle / Low pass filter 
  headingFiltered = headingDegrees;
  //Sending the heading value through the Serial Port to Processing IDE

  if(headingFiltered >= 0 && headingFiltered <= 89){
                c_head=map(headingFiltered,0,90,270,359);
        }   
    if(headingFiltered > 89 && headingFiltered <= 204){
                c_head=map(headingFiltered,90,204,0,90);
        }     
    if(headingFiltered > 204 && headingFiltered <= 290){
                c_head=map(headingFiltered,205,290,91,180);
        }         
      if(headingFiltered > 290){
               c_head=map(headingFiltered,291,359,181,269);
        }   




// Serial.print("Degrees - ");
 Serial.println(headingDegrees);
delay(500);
}

float filtered_angle(float p, float q) {
         const float filter_constant = 0.15;
         static float q_f, p_f; // filtered components
         q_f += filter_constant * (q - q_f);
         p_f += filter_constant * (p - p_f);
         return atan2(p, q); 
        }
  • 1
    Can you give us a table with degrees, that you rotated, and the degrees, that you measured with them? – chrisl Nov 30 '19 at 15:16
  • You are applying a non-linear (piecewise linear, with map()) transformation in your code. Why are you doing that? Why do you expect your non-linear transformation to yield a linear result? – Edgar Bonet Nov 30 '19 at 17:39
0

If you can't find and eliminate a systemic error, such as nearby ferrous, magnetic, or electromagnetic sources, causing the errors you see, you'll need to do what navigators have done for ages: swing the compass.

Swinging a compass involves taking the vehicle - your robo-car - somewhere away from external error sources (building wiring, iron/steel masses, f/ex) and taking a set of measurements by heading the vehicle in a number of directions known by a means independent of its own compass. Eg., with a boat, you'd choose a known place in the middle of a harbor and plot a line from that known position to each landmark (for a car, you could use a large playing field). That gives you the True bearing (bearing relative to the north pole) from the chosen position to the landmark. Make a list of those landmarks and their true bearings in columns 1 & 2 of sheet of paper, in clockwise order starting a true north.

Convert the True bearings to Magnetic bearings (relative to the direction from where you are now to the earth's magnetic north pole): Navigation charts will show the difference (in degrees) between True bearings and magnetic bearings for the location(s) covered by that chart; the difference is called "variation" (sometimes called declination). If the magnetic variation at your location is westerly (compasses point 15 degrees W of True North, f/ex), add that value to those bearings you just listed. This third column of numbers is the magnetic bearing to each of those landmarks, and the value an error-free compass should read.

Now take your vehicle to that known point (playing field, middle of the harbor, etc.) and point its center line at each of those landmarks and read its compass for each one. Write these numbers in the fourth column - the compass headings to the landmarks, for this compass in this vehicle.

Columns 3 & 4 are now a compass correction table. When the compass reads a number that is in column 4 (compass bearing), the actual (corrected) magnetic heading is the corresponding number in column 3 (the magnetic headings).

But of course you don't have every possible heading in your compass correction table. So when you need to know your car's direction, you have to interpolate between a pair of bearings you do have:

  1. Find a pair of bearings in column 4, the compass headings, that bracket the reading you just took.
  2. Find the percentage that reading is of the difference between the bracketing compass bearings.
  3. Apply that same percentage to the difference between the adjacent magnetic bearings in column 3 to find the actual magnetic bearing.

I glossed over the process of interpolation pretty quickly as the question was about correcting the compass. Interpolation, or other ways of applying a correction, is a whole 'nuther topic but the Arduino map() function will be great help here. Also, the more correction values (landmarks) you use to make your table, and the more evenly they are spread around the compass, the more accurate your corrections will be.

| improve this answer | |
  • Thanks.it worked pretty well.I used the switch case control statement and the map( ) function to get it done. – AfiJaabb Dec 2 '19 at 16:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.