Problems with HMC5883L Compass - Weird Results
reppig
Posts: 35
I have the HMC5883L working. I want to use this as part of an antenna rotor azimuth drive.
When I test this. It is not repeatable - 0 degrees varies by 15 to 20 degrees over a 5 minute period. N, E, S, & W are not 90 degrees apart.
I also notice that the x and y raw output changes in magnitude from one minute to the next.
I have two of these units and they both operate similar yet magnitudes, and compass points are different.
Has anyone gotten this unit to work reliably??
When I test this. It is not repeatable - 0 degrees varies by 15 to 20 degrees over a 5 minute period. N, E, S, & W are not 90 degrees apart.
I also notice that the x and y raw output changes in magnitude from one minute to the next.
I have two of these units and they both operate similar yet magnitudes, and compass points are different.
Has anyone gotten this unit to work reliably??
Comments
Do some tests away from sources of metal, including things on your workbench, to ensure things are working as they should. You can then experiment from there.
-- Gordon
You haven't mentioned if you can get repeatability and reasonable accuracy on your bench, without nearby sources of metal. Start there. If you're still getting errors, there's something else wrong. Start from a known, and work your way toward being able to use them near metal.
-- Gordon
I haven't said anything of the sort. I did say you will need to work through problems if they are mounted near heavy gauge steel and motors (what I imagine to be involved in your antenna rotor installation), just like what the documentation says. I am not saying anything that isn't already in the docs.
-- Gordon
You might try some MuMetal shielding. You can find it on eBay.
Just a thought.
Regards,
TCIII
Thanks,
Greg
Here is the piece of code you need to focus on, it's in the getraw method.
x := (x-226) '******* Mod by damo, -220 to offset min and max readings
z := z
y := (y+236) '********Mod by damo, +236 to offset min and max readings
Basically, you need to calibrate the compass. Have the raw X and Y data display on the serial terminal.
rotate the compass to find the max X value, then rotate the compass 180 degrees and note this value. Also note the value of Y at this point.
You need to offset the raw X and Y values, so that the magnitude of max X and Min X are similar. You also need to have the Y value near zero, as the X value is near its max or min value.
Same goes for X , as Y is near it's min or max value X should be close to zero. Just keep adjusting the offset so you get as close as possible.
I had no use for the Z data, so I ignored it.
Then you use ATAN2 to calculate the degrees. I use the one in the F32 obex, it's fast and uses only one cog.
I hope this makes sense.
{{
+
+
¦ HMC5883.spin ¦ (C)2011 Parallax, Inc. ¦ V1.0 ¦ Sept 1 ¦
+
¦
¦ Controls a Honeywell HMC5883 3-Axis compass over an I2C bus. ¦
¦ Demo shows RAW X,Y,Z, and calulated Aziumth plus a heading in Degrees. ¦
+
+
+
+
¦ H SDA ?--- I²C Data pin, I²C Master/Slave Data (Data I/O)
¦ M SCL ?--- Serial Clock - I²C Master/Slave Clock (Clock 160Hz Default)
¦ C DRDY ?--- Data Ready, interrupt pin. Internaly pulled high. (optional, see datasheet for details)
¦ 5 VIN ?--- 2.7 - 6.5VDC (module is regulated to 2.5VDC)
¦ 8 GND ?--+
¦ 8 ¦ ?
¦ 3 -Module ¦
+
+
}}
CON
_clkmode = xtal1 + pll16x
_clkfreq = 80_000_000
datapin = 18 'SDA
clockPin = 19 'SCL
'' All available registers on the HMC5883 are listed below: (Check datasheet for detailed information)
WRITE_DATA = $3C 'Used to perform a Write operation
READ_DATA = $3D 'Used to perform a Read operation
CNFG_A = $00 'Read/Write Register, Sets Data Output Rate. Default = 15Hz & 8 samples per measurement
'160Hz can be achieved by monitoring DRDY pin in single measurement mode.
CNFG_B = $01 'Read/Write Register, Sets the Device Gain(230-1370 Gauss). Default = 1090 Gauss
MODE = $02 'Read/Write Register, Selects the operating mode. Default = Single measurement
'Send $3C $02 $00 on power up to change to continuous measurement mode.
OUTPUT_X_MSB = $03 'Read Register, Output of X MSB 8-bit value. (Will read -4096 if math overflow during bias measurement)
OUTPUT_X_LSB = $04 'Read Register, Output of X LSB 8-bit value. (Will read -4096 if math overflow during bias measurement)
OUTPUT_Z_MSB = $05 'Read Register, Output of Z MSB 8-bit value. (Will read -4096 if math overflow during bias measurement)
OUTPUT_Z_LSB = $06 'Read Register, Output of Z LSB 8-bit value. (Will read -4096 if math overflow during bias measurement)
OUTPUT_Y_MSB = $07 'Read Register, Output of Y MSB 8-bit value. (Will read -4096 if math overflow during bias measurement)
OUTPUT_Y_LSB = $08 'Read Register, Output of Y LSB 8-bit value. (Will read -4096 if math overflow during bias measurement)
STATUS = $09 'Read Register, indicates device status.
ID_A = $0A 'Read Register, (ASCII value H)
ID_B = $0B 'Read Register, (ASCII value 4)
ID_C = $0C 'Read Register, (ASCII value 3)
VAR
long x
long y
long z
long Iangle_heading
long max_x
long max_y
long min_x
long min_y
byte NE
byte SE
byte SW
byte NW
byte cog
long stack[50]
'long compavg[30]
OBJ
' Term : "FullDuplexSerial"
' math : "SL32_INTEngine_2"
' math : "float32full"
' floatstring : "floatstring"
'spintrig : "spin_trigpack"
math : "f32"
PUB Startcomp(heading_adr,tx_info_adr) : success
Stopcomp
success := (cog := cognew(main(heading_adr,tx_info_adr) ,@stack) + 1)
PUB Stopcomp ''Stop compass process, if any.
math.stop
if cog
cogstop(cog~ - 1)
PUB Main(heading_adr,tx_info_adr)
min_x:=100
waitcnt(clkfreq/100_000 + cnt)
'term.start(15, 14, 0, 9600)
setcont 'sets
setpointer(OUTPUT_X_MSB) 'Start with Register OUT_X_MSB
' rawterm
direction(heading_adr,tx_info_adr)
PUB direction(heading_adr,tx_info_adr) |index ,sum
' repeat
' setpointer(OUTPUT_X_MSB) 'Start with Register OUT_X_MSB
'getraw
'long[heading_adr]:=azimuth
'term.dec(calc_az)
'term.tx(13)
repeat
' setpointer(OUTPUT_X_MSB) 'Start with Register OUT_X_MSB
' getraw
' compavg[0]:=azimuth
'if ((compavg[0] >7992) or (compavg[0]<200))
' long[heading_adr]:=azimuth
'else
' repeat index from 0 to 9
setpointer(OUTPUT_X_MSB) 'Start with Register OUT_X_MSB
getraw
'ser.dec(heading)
'ser.tx(13)
' compavg[index] := azimuth
' sum:=compavg[0]+compavg[1]+compavg[2]+compavg[3]+compavg[4]+compavg[5] +compavg[6]+compavg[7]+compavg[8]+compavg[9]
'sum:=sum+compavg[10]+compavg[11]+compavg[12]+compavg[13]+compavg[14]+compavg[15] +compavg[16]+compavg[17]+compavg[18]+compavg[19]
'sum:=sum+compavg[20]+compavg[21]+compavg[22]+compavg[23]+compavg[24]+compavg[25] +compavg[26]+compavg[27]+compavg[28]+compavg[29]
'long[heading_adr]:=(sum/30)
' if ((compavg[0] >8092) or (compavg[0]<100))
' long[heading_adr]:=(sum/10)
' else
long[heading_adr]:=azimuth
long[tx_info_adr][10]:=azimuth
long[tx_info_adr][12]:=cnt
'PUB Main
' waitcnt(clkfreq/100_000 + cnt) 'Wait while compass has time to startup.
'term.start(15, 14, 0, 9600) 'start a terminal Object (rxpin, txpin, mode, baud rate) Mod by damo for bt pins
'mathfloat.start
'setcont 'sets
' repeat 'Repeat indefinitely
' setpointer(OUTPUT_X_MSB) 'Start with Register OUT_X_MSB
' getRaw 'Gather raw data from compass
' term.tx(13) 'Set Terminal data at top of screen
' RawTerm 'Terminal window display X,Y,Z Raw Data
' HeadingTerm 'Terminal window display of heading in degrees.
' azimuthterm
'azimuth
{{PUB HeadingTerm
''Terminal window display of heading in degrees.
term.str(string("Heading in Degrees:",11))
term.tx(13)
term.tx(13)
heading
PUB AzimuthTerm
''Terminal window display of calculated arcTan(y/x)
'term.str(string("This is the calculated azimuth:",11))
'term.tx(13)
'term.tx(13)
'term.str(@Azm)
term.dec(azimuth)
'term.tx(13)
'term.tx(13)
}}
{{
PUB RawTerm
term.start(31, 30, 0, 9600)
repeat
setpointer(OUTPUT_X_MSB) 'Start with Register OUT_X_MSB
getraw
'' Terminal window display X,Y,Z Raw Data
' if x=>max_x
' max_x:=x
term.tx(1)
term.str(string(" Raw X,Y,Z:",11))
term.tx(13)
term.tx(13)
term.tx(32)
term.str(@XRaw)
term.dec(x)
'term.str(string("Max X = "))
'term.dec(max_x)
'term.str(string(" Min X = "))
'term.dec(min_x)
term.tx(29)
term.tx(32)
term.str(@YRaw)
' term.str(string("Max Y = "))
'term.dec(max_y)
'term.str(string(" Min Y = "))
'term.dec(min_y)
term.dec(y)
term.tx(29)
term.tx(32)
'term.str(@ZRaw)
term.str(string("heading = "))
term.dec(azimuth)
term.str(string(" "))
term.tx(13)
'term.tx(13)
}}
{{
{{PPUB aziadjust : value
'{{ Converts the Azimuth to Degrees from 0 - 360.
NW~
NE~
SE~
SW~
if x =< 0
if azimuth =< 0
value := AZ_A
NW := 1
else
NW~
if azimuth > 0
value := AZ_D
SW := 1
else
SW~
if x > 0
if azimuth =< 0
value := AZ_B
NE := 1
else
NE~
if azimuth > 0
value := AZ_C
SE := 1
else
SE~
value := 1 #> value <# 360
value := (value + 270) // 360
}}
{{PUB PUB Heading | t1, t2
'' Gives a heading in alpha numeric format. From 0 - 90 degrees for NE,NW,SE,SW directions.
t1~
t2~
t1 := aziadjust
'term.str(string("Degree Heading is:",11)) '****************mod
' term.dec(aziadjust) '*************mod
'term.tx(13) '***************mod
if NE == 1
term.str(@N)
t2 := aziadjust
term.dec(t2)
term.str(@E)
term.tx(11)
if SE == 1
term.str(@S)
t2 := aziadjust - 180
term.dec(||t2)
term.str(@E)
term.tx(11)
if SW == 1
term.str(@S)
t2 := aziadjust - 180
term.dec(t2)
term.str(@W)
term.tx(11)
if (NW == 1)
term.str(@N)
t2 := 360 - aziadjust <#90
if t2 == 90
t2 := 0
term.dec(t2)
term.str(@W)
term.tx(11)
if (t1 == 0)
term.tx(11)
term.tx(8)
term.tx(8)
term.tx(8)
term.str(@NORTH)
term.tx(11)
if t1 == 90
term.tx(12)
term.tx(8)
term.tx(8)
term.tx(8)
term.tx(8)
term.str(@EAST)
term.tx(11)
if t1 == 180
term.tx(12)
term.tx(8)
term.tx(8)
term.tx(8)
term.str(@SOUTH)
term.tx(11)
if t1 == 271
term.tx(12)
term.tx(8)
term.tx(8)
term.tx(8)
term.tx(8)
term.str(@WEST)
term.tx(11)
}}
PUB SetCont
{{ Sets the Compass to Continuous output mode.}}
start
send(WRITE_DATA)
send(MODE)
send($00)
stop
PUB SetPointer(Register)
{{ Start pointer at user specified Register. }}
start
send(WRITE_DATA)
send(Register)
stop
PUB GetRaw
{{ Get raw data from continous output.}}
start
send(READ_DATA)
x := ((receive(true) << 8) | receive(true)) 'RegisterA and RegisterB
z := ((receive(true) << 8) | receive(true))
y := ((receive(true) << 8) | receive(false))
stop
~~x
~~z
~~y
x := (x-226) '******* Mod by damo, -240 to offset min and max readings
z := z
y := (y+236) '********Mod by damo, +212 to offset min and max readings
{{ if x>max_x
max_x:=x
if x<min_x
min_x:=x
if y>max_y
max_y:=y
if y<min_y
min_y:=y
}}
'PRI Azimuth ' | degree_heading
'Azimuth = arcTan(y/x)
' result := calc_az
' repeat
' getraw
' if (x=>0 and y=>0)
' Iangle_heading := calc_az
' Iangle_heading:=calc_az+4096 '***************
'if Iangle_heading<0
' Iangle_heading:=Iangle_heading+4096
' result:=Iangle_heading '**************
' term.dec(calc_az)
' term.tx(13)
' if (x=<0 and y=>0)
' x:=-x
' degree_heading:=calc_az+90
' if (x=<0 and y=<0)
' x:=-x
' y:=-y
' degree_heading:=calc_az+180
' if (x=>0 and y=<0)
' y:=-y
' degree_heading:=calc_az+270
'degree_heading:= mathfloat.Atan2(y,x)
'term.str(string("Degree Heading is:",11))
'result := term.dec(mathfloat.degrees(mathfloat.Atan2(y,x))*100)
PUB azimuth |t1 ,t2
math.start
' result := math.arctan(y,x)
't1 := (mathfloat.degrees(mathfloat.Atan2(y,x)))
' t2 := floatstring.floattostring(mathfloat.degrees(mathfloat.Atan2(y,x)))
' t2:=trunc(t1)
t1:=(math.fmul(math.ffloat(10), math.degrees(math.Atan2(math.ffloat(x),math.ffloat(y)))))
t1:=math.fadd(math.ffloat(1800),t1)
t1:=math.fsub(t1,math.ffloat(910)) ' 910 this is the magic number for compass offset correction
if t1<0
t1:= math.fadd(t1,math.ffloat(3600))
result:=math.fround(t1)
'math.stop
' result:=(spintrig.qvaltoiangle((spintrig.deg_atan2(spintrig.qval(y),spintrig.qval(x))))) '****************
'term.dec(t2)
'term.tx(11)
' term.tx(13)
{{
PRI AZ_A 'NE
result := ||azimuth
PRI AZ_B 'SE
result := azimuth + 180
PRI AZ_C 'SW
result := azimuth + 180
PRI AZ_D 'NW
result := -azimuth + 360
}}
PRI send(value) ' I²C Send data - 4 Stack Longs
value := ((!value) >< 8)
repeat 8
dira[dataPin] := value
dira[clockPin] := false
dira[clockPin] := true
value >>= 1
dira[dataPin] := false
dira[clockPin] := false
result := !(ina[dataPin])
dira[clockPin] := true
dira[dataPin] := true
PRI receive(aknowledge) ' I²C receive data - 4 Stack Longs
dira[dataPin] := false
repeat 8
result <<= 1
dira[clockPin] := false
result |= ina[dataPin]
dira[clockPin] := true
dira[dataPin] := aknowledge
dira[clockPin] := false
dira[clockPin] := true
dira[dataPin] := true
PRI start ' 3 Stack Longs
outa[dataPin] := false
outa[clockPin] := false
dira[dataPin] := true
dira[clockPin] := true
PRI stop ' 3 Stack Longs
dira[clockPin] := false
dira[dataPin] := false
DAT
E byte "E",0
N byte "N",0
S byte "S",0
W byte "W",0
Azm byte "Azimuth = ",0
XRaw byte "X = ",0
YRaw byte "Y = ",0
ZRaw byte "Z = ",0
NORTH byte "NORTH",0
SOUTH byte "SOUTH",0
EAST byte "EAST",0
WEST byte "WEST",0