Compass Honeywell HMC5883L
From
| is classified as | hardware |
|---|---|
| has description | Compass Honeywell HMC5883L |
This is a 3 axis compass that requires some effort to configure and calibrate.
This page builds on currently available information and code to describe how to configure and calibrate the device.
Sample Processing data display code
Notes:
- The HMC5883 is NOT compatable with the HMC5883L - apparently the I2C address / commands are different.
Still to do includes:-
- Z axis calibration -
Sample Arduino code:
/*
3-Axis Compass Module (HMC5883) Demo code.
2010 Copyright (c) Seeed Technology Inc. All right reserved.
Original Author: Leo
Contribution: Visweswara R ( Code Redesign and Comments)
This demo code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
For more details about the product please check http://www.seeedstudio.com/depot/
---------------------------------
Updates:- Spanner888 http://usabledevices.com/
- Arduino v1.0
- much needed calibration
- added version# & date
version 0.2
Date 2012-03-06
Usage:
1. Run as is - it prints out detailed info on heading (original Seeed functionality)
2. To calibrate YOUR sensor for OFFSET - XY scatter plot method:-
- uncomment "//#define RAW_DATA_CSV" to get data for calibration in CSV format.
- load sketch to your arduino
- while running the sketch, with the serial terminal open, rotate your arduino through a FULL 360 degrees (and a bit extra!)
- Copy ALL data from the serial terminal and do an XY scatter plot in your favour tool (Libre Office Calc etc)
- you may find it easiest to press & hhold the Arduino reset button, then press ctl A, ctl C to copy the data.
- you may also need to tidy the start & end of the data for stray characters!
3. To calibrate YOUR sensor for OFFSET - using the processing XY scatter plot method:-
- uncomment "//#define RAW_DATA_CSV" to get data for calibration in CSV format.
- Also uncomment "//#define RAW_DATA_ARRAY" to get data for calibration in CSV format.
- load sketch to your arduino
- while running the sketch, with the serial terminal open, rotate your arduino through a FULL 360 degrees (and a bit extra!)
- Copy ALL data from the serial terminal into your processing sketch. My example code is at usabledevices.com
- you may find it easiest to press & hhold the Arduino reset button, then press ctl A, ctl C to copy the data.
- you may also need to tidy the start & end of the data for stray characters!
4. Then work out the offset values for x and y and add below.
5. If the xy plot is NOT circular then:-
- uncomment "//#define SCALE" and adjust the scale values until your XY scatter plots become circular.
To Do
- z- axis calibration technique for offset and scale (code is in place, but need to work out how to interpret the data!)
- investigate GAUSS settings and autocalibrate using the compass builtin self test
---------------------------------
*/
/* Background information on the HMC5883 compass sensor & configuration / calibration
Here are most of the sources I used for these updates - apologies if I forgot anyone :(, please let me know - Spanner888 at usabledevices.com
https://www.loveelectronics.co.uk/Tutorials/8/hmc5883l-tutorial-and-arduino-library
I have put one of these together and what I found was as I rotated the compass, it would go from 0-180 in about 90 degrees, and then take the remaining 270 degrees to go the remaining 90. I have tried different values for the gauss to no avail. Anyone have thoughts as to why this oddness could be taking place?
...Try this:
// Calculate heading when the magnetometer is level, then correct for signs of axis.
float heading = atan2(raw.YAxis - y_offset, raw.XAxis - x_offset);
with x_offset = (x_max_value_measured - x_min_value_measured)/2
same with y_offset
You can get the max & min value of each channel (x, y, z) by rotating the HMC5883L board over all axis.
http://hackaday.com/2011/07/14/a-beginners-guide-to-magnetometers/
it is important to calibrate the magnetormeters on the site where you use it.
The process:
* Attach your magnetometer to its final place (your vehicle may have a constant magnetic field)
* Do some turns with your vehicle (we have done it in 2D, but 3D patterns may also work)
* Draw the measured points. They should align with a circle.
* Find the center of the circle. Save the coordinates.
* When measuring the heading you have to count with that the heading is relative to the middle that you have previously measured!
So substract the center coordinates from what you have measured before counting atan2 to find the heading angle.
...somewhere ... if NOT a circle, also need to seperately scale x,y...
Have not tried these:
https://www.loveelectronics.co.uk/Tutorials/13/tilt-compensated-compass-arduino-tutorial
http://www.timzaman.nl/?p=1010&lang=en
this one seems to use offset & scale + set gauss
http://mbed.org/users/BlazeX/libraries/HMC5883/m5o4z3/docs/HMC5883_8cpp_source.html
Grove sketch link to Honeywell no longer works, instead try:
http://www.magneticsensors.com/literature.php?jumpMenu2=http%3A%2F%2Fwww51.honeywell.com%2Faero%2Fcommon%2Fdocuments%2Fmyaerospacecatalog-documents%2FDefense_Brochures-documents%2FMagnetic__Literature_Application_notes-documents%2FMagnetic_Sensor_Overview.pdf&go_button2.x=11&go_button2.y=13
Good explanation of why need to adjust for earth's magnetic field AT YOUR LOCATION!!!
AN203_Compass_Heading_Using_Magnetometers.pdf
*/
#include <Wire.h>
#include <math.h>
// updates to cope with Arduino V1.0( and newer?) and older versions (22 & lower)
#ifdef ARDUINOa
#define I2C_TX write
#define I2C_RX read
#else // just assume older version of Arduino using WProgram_h
#define I2C_TX sendss
#define I2C_RX receive
#endif
//#define SCALE //use this to scale each axis if the test data does not give a circular (spherical 3D) plot
// done as #def for max performance of code not using the scale factors!
// use to switch the serial output data to a CSV format of raw x,y,z data values to help calibrate your compass
#define RAW_DATA_CSV
// ALSO use this #def to format output to easly paste into a data array - eg to display data in Processing. See usabledevices.com for example.
#define RAW_DATA_ARRAY
// Shift the device's documented slave address (0x3C) for write operation
// 1 bit right.This compensates for how the TWI library only wants the
// 7 most significant bits (with the high bit padded with 0)
#define HMC5883_WriteAddress 0x1E // i.e 0x3C >> 1
#define HMC5883_ModeRegisterAddress 0x02
#ifdef ARDUINO
#define HMC5883_ContinuousModeCommand (uint8_t)0x00 // cast to uint8_t added to get code to compile under Arduino v1.0
#else // just assume older version of Arduino using WProgram_h
#define HMC5883_ContinuousModeCommand 0x00
#endif
#define HMC5883_DataOutputXMSBAddress 0x03
int regb=0x01;
int regbdata=0x40;
int outputData[6];
// calibrate YOUR compass, so compass raw x,y (,z) data is centered around 0,0,0 axis!
// To do this read do an XY scatter plot for 2D xy data, and just "read"
// work out or guess the offset to center the data (you need to rotate the compass to get lots of data pairs)
// for my compass the values where x=-100, y=-100 (did not work out z axis!)
const int x_offset = 0;
const int y_offset = 0;
const int z_offset = 0;
#ifdef SCALE
const int x_scale = 1;
const int y_scale = 1;
const int z_scale = 1;
#endif
void setup()
{
Serial.begin(9600);
Wire.begin(); //Initiate the Wire library and join the I2C bus as a master
}
void loop() {
int i,x,y,z;
double angle;
Wire.beginTransmission(HMC5883_WriteAddress);
Wire.I2C_TX(regb);
Wire.I2C_TX(regbdata);
Wire.endTransmission();
delay(1000);
Wire.beginTransmission(HMC5883_WriteAddress); //Initiate a transmission with HMC5883 (Write address).
Wire.I2C_TX(HMC5883_ModeRegisterAddress); //Place the Mode Register Address in send-buffer.
Wire.I2C_TX(HMC5883_ContinuousModeCommand); //Place the command for Continuous operation Mode in send-buffer.
Wire.endTransmission(); //Send the send-buffer to HMC5883 and end the I2C transmission.
delay(100);
Wire.beginTransmission(HMC5883_WriteAddress); //Initiate a transmission with HMC5883 (Write address).
Wire.requestFrom(HMC5883_WriteAddress,6); //Request 6 bytes of data from the address specified.
delay(500);
//Read the value of magnetic components X,Y and Z
if(6 <= Wire.available()) // If the number of bytes available for reading be <=6.
{
for(i=0;i<6;i++)
{
outputData[i]=Wire.I2C_RX(); //Store the data in outputData buffer
}
}
x=outputData[0] << 8 | outputData[1]; //Combine MSB and LSB of X Data output register
z=outputData[2] << 8 | outputData[3]; //Combine MSB and LSB of Z Data output register
y=outputData[4] << 8 | outputData[5]; //Combine MSB and LSB of Y Data output register
#ifdef SCALE
angle= atan2((double)y * y_scale - y_offset,(double)x * x_scale - x_offset)* (180 / 3.14159265) +180; // angle in degrees
#else
angle= atan2((double)y - y_offset,(double)x - x_offset)* (180 / 3.14159265) +180; // angle in degrees
#endif
/*
Refer the following application note for heading calculation.
http://www.ssec.honeywell.com/magnetic/datasheets/lowcost.pdf
----------------------------------------------------------------------------------------
atan2(y, x) is the angle in radians between the positive x-axis of a plane and the point
given by the coordinates (x, y) on it.
----------------------------------------------------------------------------------------
This sketch does not utilize the magnetic component Z as tilt compensation can not be done without an Accelerometer
----------------->y
|
|
|
|
|
|
\/
x
N
NW | NE
|
W----------E
|
SW | SE
S
*/
// only print the detail if NOT doing either sending either calibration data set to serial
#ifndef RAW_DATA_CSV
#ifndef RAW_DATA_ARRAY
//Print the approximate direction
Serial.print("You are heading ");
if((angle < 22.5) || (angle > 337.5 ))
Serial.print("South");
if((angle > 22.5) && (angle < 67.5 ))
Serial.print("South-West");
if((angle > 67.5) && (angle < 112.5 ))
Serial.print("West");
if((angle > 112.5) && (angle < 157.5 ))
Serial.print("North-West");
if((angle > 157.5) && (angle < 202.5 ))
Serial.print("North");
if((angle > 202.5) && (angle < 247.5 ))
Serial.print("NorthEast");
if((angle > 247.5) && (angle < 292.5 ))
Serial.print("East");
if((angle > 292.5) && (angle < 337.5 ))
Serial.print("SouthEast");
Serial.print(": Angle between X-axis and the South direction ");
if((0 < angle) && (angle < 180) )
{
angle=angle;
}
else
{
angle=360-angle;
}
Serial.print(angle,2);
Serial.println(" Deg");
#endif
#endif
#ifndef RAW_DATA_ARRAY
Serial.print("{ ");
#endif
#ifndef RAW_DATA_CSV
Serial.print(angle);
Serial.print(", ");
Serial.print(x);
Serial.print(", ");
Serial.print(y);
Serial.print(", ");
Serial.println(z);
#endif
#ifndef RAW_DATA_ARRAY
Serial.print(" }, ");
#endif
delay(100);
}
Sample Processing data display code:
/* LICENCE - see http://usabledevices.com/2011/11/04/contact-emails-and-legal-and-copyright-information/
Spanner888 at usable devices.com
This code helps you to calibrate your HMC5883 compass by:-
- displaying the raw x, y data
- allow you to adjust for zero offset (circle is not centered around the 0,0 axis intersection)
- allow you to adjust for scaling (circle is squashed - egg shaped)
To do:- extend for 3D so can also do the Z axis
Version: 0.1
Date: 2012-03-06
Usage:-
1. To calibrate YOUR sensor for OFFSET - using the Processing XY scatter plot method:-
- Get raw data for x, y & z WHILE rotating the compass in a full 360+ circle
- place the raw data into the Processing sketch below.
Example arduino code to gather the data was loaded to the SeeedStudio wiki and also at usabledevices.com
2. Then run the Processing sketch and work out the offset values for x and y and add below.
3. repeat until circle is centered.
4. Ditto, but adjust scale in your Arduino code until the data produces a circular XY scatter plot, not egg shaped.
Notes:
- I tried a few different methods of display - code has been left commented below in case it is useful!
- example date is from my testing
- Original attempts us data output as 2 sets of 4 per line = angle, raw x, raw y, raw z, repeated
This was done, as my orignal attempts used line plots - I was hoping to have them pass through the origin,
by rotating the compass 180 degrees between 1s & 2nd dataset on the line.
This technique may work, but did not with the initial offsets I tried and out of time to go back & test again :(
*/
int viewSize = 500; // 0 to 500
int dataMax = 1000; // can be +/-
int scale = 1 * dataMax/viewSize; // you can scale your data to help "see" it better by changing the multiplier here!
// Sets the screen size
size(viewSize, viewSize);
background(0);
fill(150);
//noFill();
stroke(204);
//draw both axis
line(0, height/2, width, height/2);
line(width/2, 0, width/2, height);
int [][] data= {
{267, -88, 219, 300, 259, -33, 249, 281, },
{241, 95, 252, 263, 223, 191, 181, 288, },
{233, 124, 204, 299, 278, -130, 109, 368, },
{214, 263, 146, 274, 75, -109, -134, 421, },
{247, 19, 187, 337, 217, 238, 159, 278, },
{12, -127, -106, 412, 225, 202, 206, 257, },
{313, -200, 6, 375, 309, -192, 13, 380, },
{169, 286, -174, 352, 187, 323, -46, 318, },
{286, -145, 53, 385, 178, 310, -112, 333, },
{287, -148, 49, 384, 195, 292, 12, 321, },
{296, -189, 77, 358, 196, 257, 7, 344, },
{171, 241, -150, 370, 347, -214, -75, 384, },
{31, -234, -181, 361, 153, 128, -212, 411, },
{192, 212, -32, 377, 1, -131, -101, 419, },
{206, 147, 21, 384, 125, -20, -214, 420, },
{96, -85, -239, 411, 348, -208, -79, 385, },
{268, -90, 213, 301, 332, -251, -21, 348, },
{335, -214, -48, 373, 43, -371, -354, -113, },
{68, -219, -401, -197, 67, -222, -398, -198, },
};
for (int i=0;i<17;i++){
stroke(10 + i*40, 100, 100);
int x = data[i][1] / scale;
int y = data[i][2] / scale;
int x1 = data[i][5] / scale;
int y1 = data[i][6] / scale;
print(i); print(":- ");
print(viewSize/2 -x); print(", ");
print(viewSize/2 -y); print(", ");
print(viewSize/2 + x1); print(", ");
print(viewSize/2 + y1);
println();
point(viewSize/2 + x, viewSize/2 -y);
point(viewSize/2 + x1, viewSize/2 -y1);
//text(i, viewSize/2 + x, viewSize/2 - y);
//line(viewSize/2, viewSize/2, viewSize/2 + x, viewSize/2 - y); // draw from 0,0 to x,y
//line(viewSize/2 + x, viewSize/2 -y, viewSize/2 + x1, viewSize/2 - y1); // draw x,y to x1, y1
//text(i+100, viewSize/2 + x1, viewSize/2 -y1);
//stroke(100 , 100+ i*40, 100);
//line(viewSize/2, viewSize/2, viewSize/2 + x1, viewSize/2 - y1); // draws from 0,0 to x1, y1
}
/*
{ 99,500,500,99,99,0,0,99,99 } , //test data
{ 99,500,500,99,99,0,500,99,99 } , //test data
{ 99,500,500,99,99,500,0,99,99 } , //test data
{ 326,-205,130,580,118,94,-166,630,208 } , //0
{ 320,-175,131,577,103,104,-449,540,217 } , //1
{ 325,-345,234,380,160,308,-108,328,165 } , //2
{ 29,-269,-157,562,200,244,92,365,-171 } , //3
{ 25,-350,-167,402,159,272,-100,435,-134 } , //4
{ 8,-359,-59,618,183,249,14,507,-134 } , //5
{ 23,-225,-92,644,162,238,-78,515,-139 } , //6*/
// this block draws lines from 2 pair of x,y data
/*
int [][] data= {
};
for (int i=0;i<51;i++){
// stroke(100 + i*20);
int x = data[i][1] / scale;
int y = data[i][2] / scale;
int x1 = data[i][5] / scale;
int y1 = data[i][6] / scale;
print(i); print(":- ");
print(viewSize/2 -x); print(", ");
print(viewSize/2 -y); print(", ");
print(viewSize/2 + x1); print(", ");
print(viewSize/2 + y1);
println();
text(i, viewSize/2 + x, viewSize/2 -y);
line(viewSize/2 + x, viewSize/2 -y, viewSize/2 + x1, viewSize/2 - y1);
*/
// This block draws teh "compass" heading - ie line from orgin at angle.
/*
{6, -192, -23, 610, 99, 75, -430, 493, },
{99, 67, -418, 504, 340, -178, 64, 565, },
{352, -339, 46, 443, 143, 244, -178, 373, },
{180, 229, 1, 407, 29, -353, -198, 430, },
{32, -300, -193, 448, 32, -300, -193, 452, },
{32, -302, -195, 446, 32, -303, -194, 444, },
{32, -301, -195, 446, 32, -301, -194, 447, },
{32, -300, -193, 447, 45, -273, -277, 354, },
{310, -157, 184, 559, 311, -159, 179, 562, },
{312, -162, 178, 560, 312, -161, 178, 565, },
{101, 92, -449, 502, 101, 93, -446, 506, },
{101, 93, -442, 508, 102, 94, -438, 509, },
{166, 280, -192, 345, 167, 283, -185, 341, },
{169, 300, -173, 334, 170, 302, -170, 331, },
{170, 305, -168, 329, 168, 306, -181, 328, },
{166, 300, -196, 329, 166, 288, -190, 338, },
{166, 283, -194, 339, 165, 269, -195, 348, },
{167, 266, -181, 351, 168, 259, -172, 355, },
{168, 258, -175, 354, 167, 254, -181, 356, },
{166, 256, -188, 353, 165, 253, -191, 357, },
{164, 241, -197, 361, 163, 234, -201, 365, },
{162, 227, -204, 368, 161, 224, -207, 370, },
{161, 220, -207, 371, 161, 218, -205, 373, },
{161, 215, -207, 373, 161, 214, -206, 373, },
{161, 213, -204, 375, 161, 213, -207, 374, },
{160, 211, -208, 375, 159, 208, -213, 376, },
{158, 205, -223, 374, 156, 200, -232, 374, },
{155, 196, -237, 374, 155, 190, -234, 377, },
{154, 185, -236, 379, 153, 182, -242, 380, },
{151, 178, -248, 379, 150, 171, -253, 380, },
{151, 166, -247, 382, 150, 163, -250, 382, },
{149, 154, -252, 385, 148, 149, -254, 385, },
{148, 143, -251, 388, 147, 137, -250, 390, },
{143, 118, -261, 392, 142, 110, -264, 392, },
{142, 107, -259, 393, 140, 95, -261, 394, },
{139, 92, -266, 394, 137, 80, -266, 395, },
{131, 58, -276, 395, 131, 57, -277, 396, },
{131, 52, -271, 398, 130, 47, -271, 400, },
{130, 48, -273, 397, 126, 30, -274, 398, },
{121, 2, -268, 399, 118, -5, -273, 398, },
{116, -15, -273, 397, 109, -39, -268, 398, },
{112, -36, -258, 400, 108, -46, -260, 398, },
{107, -51, -255, 399, 107, -57, -237, 403, },
{103, -66, -242, 400, 100, -73, -252, 396, },
{96, -82, -255, 392, 94, -88, -250, 393, },
{92, -94, -242, 395, 90, -99, -244, 392, },
{83, -115, -238, 390, 78, -128, -233, 388, },
{71, -140, -222, 385, 68, -144, -210, 386, },
{56, -158, -187, 385, 54, -162, -186, 385, },
{53, -158, -177, 388, 47, -166, -173, 385, },
{41, -184, -174, 379, 25, -191, -144, 379, },
{21, -203, -140, 375, 11, -212, -122, 372, },
{7, -226, -116, 364, 358, -232, -96, 361, },
{351, -236, -79, 358, 341, -234, -55, 358, },
{339, -234, -50, 357, 333, -233, -35, 356, },
{330, -231, -26, 356, 327, -232, -17, 354, },
{323, -232, -2, 352, 321, -230, 5, 350, },
{319, -230, 10, 350, 317, -228, 19, 349, },
{315, -228, 28, 347, 312, -226, 38, 344, },
{310, -225, 45, 342, 308, -225, 57, 338, },
{306, -223, 68, 336, 304, -219, 75, 336, },
{302, -217, 86, 333, 300, -212, 91, 336, },
{295, -198, 102, 337, 292, -185, 106, 341, },
{287, -173, 132, 333, 281, -154, 163, 323, },
{275, -128, 186, 314, 271, -110, 193, 314, },
{270, -104, 201, 309, 270, -104, 192, 314, },
{270, -103, 189, 315, 268, -95, 185, 319, },
{268, -90, 201, 308, 265, -75, 214, 301, },
{265, -72, 232, 282, 264, -66, 235, 282, },
{262, -56, 234, 282, 261, -52, 228, 290, },
{259, -42, 228, 291, 260, -45, 225, 296, },
*/