It's a compass when it's held level. It's repeatable to within 1 degree when you hold it level and don't move, in any direction. But it can be off by 90 degrees in an absolute sense. Not accurate at all for finding North. Any ideas? Mistakes in code?
It's mounted upside down in my application so I fixed it in the code by 360-heading. It seems to work very differently when I mount it right side up, but upon further analysis, the error is the same just reversed?
Could I use a simple translation table to translate the error degrees to the correct number?
At least it's consistent!
I don't see the point of making it 3D using accell data on my board. The Z data is consistent and constant when I hold it level, it is near the maximum amount when you rotate it. So I don't understand how knowing the orientation, if it's changing, plus Z data, will help?
It's not as accurate in an absolute sense as this:
HMC6452
No complicated math if you hold it near level!
/*
Arduino example for interfacing with the HMC5883L
by: Jordan McConnell SparkFun Electronics
created on: 6/30/11
*/
#include <Wire.h> //I2C Arduino Library
#define address 0x1E //0011110b, I2C 7bit address of HMC5883
int cnt=0;
int x,y,z;
long xx,yy,zz;
void setup(){
//Initialize Serial and I2C communications
Serial.begin(9600);
Wire.begin();
//Put the HMC5883 IC into the correct operating mode
Wire.beginTransmission(address); //open communication with HMC5883
Wire.send(0x02); //select mode register
Wire.send(0x00); //continuous measurement mode
Wire.endTransmission();
}
void loop(){
//Tell the HMC5883 where to begin reading data
Wire.beginTransmission(address);
Wire.send(0x03); //select register 3, X MSB register
Wire.endTransmission();
//Read data from each axis, 2 registers per axis
Wire.requestFrom(address, 6);
if(6<=Wire.available()){
x = Wire.receive()<<8; //X msb
x |= Wire.receive(); //X lsb
z = Wire.receive()<<8; //Z msb
z |= Wire.receive(); //Z lsb
y = Wire.receive()<<8; //Y msb
y |= Wire.receive(); //Y lsb
}
/*
//Print out values of each axis
Serial.print("x: ");
Serial.print(x);
Serial.print(" y: ");
Serial.print(y);
Serial.print(" z: ");
Serial.println(z);
*/
//Serial.print(z);Serial.write(32);
//repeatable within 1 deg, but off by 90 deg?
if(avgxyz())
if(zz>4800){ //near level 5200 max down, in MA
//Serial.print(xx);Serial.write(32);Serial.println(yy);
float heading=atan2(yy,xx);
heading*=(180/M_PI);
//declination here degrees
if(heading<0)heading+=360;
if(heading>360)heading-=360; //for + declination only
//upside down on board so...
heading=360-heading;
Serial.println(heading);
}
delay(50); //50 or 150 more acc 3s
}
boolean avgxyz(){
const int N=20; //matches delay 20*50=1s
boolean b;
cnt++;
if(cnt%N==1)xx=yy=zz=0;
xx+=x;
yy+=y;
zz+=z;
if(b=!(cnt%N)){
xx=xx*10/N;yy=yy*10/N;zz=zz*10/N; }
return(b);
}
The processor may not have read all 6 bytes when you get here but you don't stop and wait. You will go straight through and process the variable x, y and z as if you had read something.
Try changing this:
if(6<=Wire.available()){
x = Wire.receive()<<8; //X msb
x |= Wire.receive(); //X lsb
z = Wire.receive()<<8; //Z msb
z |= Wire.receive(); //Z lsb
y = Wire.receive()<<8; //Y msb
y |= Wire.receive(); //Y lsb
}
to this:
while(Wire.available() < 6);
x = Wire.receive()<<8; //X msb
x |= Wire.receive(); //X lsb
z = Wire.receive()<<8; //Z msb
z |= Wire.receive(); //Z lsb
y = Wire.receive()<<8; //Y msb
y |= Wire.receive(); //Y lsb
Wire.requestFrom is blocking. That part is fine. Changing to waiting for it to become available simply builds in a potential endless loop. Either you get 6 bytes or you don't. Waiting doesn't change that.
Good points to both of you. I didn't like the way the code looked so I fixed it anyway. I don't see how that can possibly be related to my problem. Funny, it seemed to help a little! I'd like to point out that it is probably a hardware issue. I have used other example code without modification and it still cannot find North and South correctly. How can I fix this problem? Lookup table? The number are consistently wrong. When I draw lines on a paper and put it back to those lines, it always reads exactly the same number of degrees when it's kept level. But off by 20-90 degrees from the orientation of my house. I've moved it 2 feet from my laptop to avoid interference. It seems consistent no matter where I place the unit before I rotate it. Any more ideas?
Tried all the gain settings using a different Example and library code.
When I face it's arbitrary North I get 0, 50, 130, 320 for N,E,S,W
That is 0,-40,-50,+50 errors
or 50,0,0,100 errors if I use S as my reference.
What next?
Use the other chip...
You are taking me out of my area of expertise here, and I don't have the chip to hand. All I can suggest is Googling for someone who might have sample code. If their exact code works, and yours doesn't maybe a bad chip?
I must admit I couldn't find much except a blog written in Swedish.
It's a popular chip. I've tried 3 different examples. All the same results.
The X reading is consistent. The Y reading is consistent. Perhaps they need to be scaled to match each other in magnitude?
I don't think just scaling will do it for me. Here's the results I've obtained so far. It is very much dependent on holding it exactly level. Even a few degrees of tilt effect the heading, although it does not effect the Z data. Which means I cannot use tilt compensation to fix this problem? The older 2D chip does not suffer from this problem, it can tilt 10-20 degrees from level without much difference.
North is arbitrary where y=0. It's not far off from reality.
For W,S - I did not turn exactly 90 degrees, instead I turned until the appropriate? variable was 0.
Direction X Y
N 2800 0
W 0 1100 peak value is 1400 further south when x>0 about 200
S 0 -2800 this makes no sense compared to North. The wrong value x is 0, should be y?
E 2000 -2000 exactly 90 degrees from N
0 +max and -max 0
are right next to each other when facing West
These should be W and S.
It reads 0 -max when it's facing about S, should read this when facing E.
It doesn't matter which library or sample code I use.
I need some tissues for my issues...
Any ideas to work AROUND this bug?
Many others have a similar problem online. Could it be they shipped 100's of bad chips with this defect?
Could it be too close to the 328 processor? The other chip didn't mind.
It's a simple algorithm to transform the wrong heading to the right one:
if heading <90 then heading*=2;
if heading >=90 <270
heading=180+(heading-90)/2
//else already correct within degree or 2
But why?
New problem:
At E,W it's extremely sensitive to tilt, but not N,S;
The Z value does not change when it's level or near level, so there's no way to adjust the X,Y heading?
I think this chip is a piece of junk, and many others agree?
Just tilt it 90 degrees around the Y axis marked on the board.
Modify the code like this:
//if(zz>4800)
{
float heading=atan2(yy,zz); //zz instead of xx
Not so sensitive to tilting anymore!
But still not as good as old chip...
Tilting problem solved without any complex calculations (below) !!!
Pretend the board/chip is a solar panel. If you first tilt it 0-30 degrees up towards the N or W, not down toward the ground, then tilting +-15 doesn't change the heading output AT ALL. This solves the tilt problem without any calculation. I noticed this with my 2-axis chip as well. It has to do with Earth's inclination angle of the magnetic field.
If you first tilt it 5 degrees in the wrong direction, then +-1 degree makes a huge difference in heading, this is bad.
My other chip should be tilted towards the North. Why this difference between chips?
70 degrees in USA??? Oh, 20 degrees from straight down.
Let's say you had a servo to tilt the sensor any angle you want around the Y axis. If you don't know which way you're facing, how would you know which way to tilt it??? The answer is simple. Maximize the raw X value (the new orientation).
Let's say you don't have a servo, and can't control the tilt angle. But it's on a moving Jeep or Quadcopter/plane.
Just WAIT until the X value is near it's const int maximum value. Then read Y,Z.
Tilt problem solved!
Next minor issue:
My breakout board has 3 chips on it. Accell, Gyro, and this one.
This chip requires the board to stand upright.
The other 2 work best when it's lying flat.
They have been calibrated that way.
Otherwise I have to change the code to swap the sensors.
That's a problem...
Learned some more from experimenting:
If you turn my board upside down, it doesn't work as well, even after fixing 360-heading.
If you tilt it too far, much past 30, even in the "good" direction, it reads the OPPOSITE heading!
Tricky.
You will want to implement some tilt compensation for the compass, using the accelerometer to figure roll, pitch and yaw.
Here's a library we (my friend and I) wrote: Loading...
and plug them into here directly? Combining the 2 sketches.
sixDOF.compCompass(-(raw.ZAxis), -(raw.XAxis), raw.YAxis, -(accel.z()), -(accel.x()), accel.y(), true);
Mine are raw values also?
Why do the first 2 - Z,X have minus signs, Y does not?
Do I include these signs before my x,y,z too?
I don't see how this can work for small tilt angles, because the Z value doesn't change, in my case.
Can you explain how it works without this value?
Let's say you have tilt and X,Y from HMC5883, but no Z.
Yes, that is correct. Raw values or scaled doesn't matter, raw is faster and more accurate.
In process, the library scales everything up with an equal multiplier to reduce quantization losses.
sixDOF.compCompass(-(raw.ZAxis), -(raw.XAxis), raw.YAxis, -(accel.z()), -(accel.x()), accel.y(), true);
Mine are raw values also?
Why do the first 2 - Z,X have minus signs, Y does not?
Do I include these signs before my x,y,z too?
You may want to play with the arrangement and sign of xyz depending on the orientation of your accelerometer and compass.
My PCB is mounted vertically and the xy of the accelerometer was not the same as the xy of the compass.
This is why I have (-z, -x, +y). The default order is (+x, +y, +z).
I don't see how this can work for small tilt angles, because the Z value doesn't change, in my case.
Can you explain how it works without this value?
Let's say you have tilt and X,Y from HMC5883, but no Z.
The libraries trigonometry is non-linear and ratiometric.
Non-linear as in on axis effect is the lowest, while a right angle axis will have the highest effect.
Ratiometric as in a change in only one axis effects the ratio and therefore the end result.
I understand. But when you tilt it a little from level the X mag value does not change, of course.
When you first tilt it 15 deg towards N, then back and forth another 15, the Y,Z do not change.
Only the orientation values change in this case.
Will see if your code handles this.
Reading the source looks great!
While on the topic of this magnetometer, I have a Processing sketch that plots the x/y/z/heading and Bt. I'm using scaled values to determin my heading and getting pretty good results.
My problem is that I'm using Serial.write() to send the signed integers to Processing. Since each axis has two registers, and sends 2 bytes, I'm getting mixed values on the Processing end.
How do I go about sending each axis MSB and LSB, and then join the two once they are read in Processing? I have searched forums and reference pages for days, and have not made
any progress. I am storing the incoming bytes into an array in processing. Any help would be appreciated.
Also, the good results I am getting are via the HMC5883L library instead of calling addresses on the I2C directly. I'm allowing the library to do the heavy lifting. Thanks in advance for
any assistance