Shop OBEX P1 Docs P2 Docs Learn Events
Problems with HMC5883L Compass - Weird Results — Parallax Forums

Problems with HMC5883L Compass - Weird Results

reppigreppig Posts: 35
edited 2012-08-22 13:56 in Accessories
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??

Comments

  • GordonMcCombGordonMcComb Posts: 3,366
    edited 2012-06-07 08:52
    Magnetometers are sensitive to nearby metal. The metal acts to alter the lines of magnetic force, which skews the results. You must separate the device from metal and magnetic sources (including the antenna mast, mounting brackets, and motor) as far as possible. Several feet would be best. You also need to make sure the module is completely level. The 8553 does not include tilt compensation.

    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
  • reppigreppig Posts: 35
    edited 2012-06-07 09:57
    Yes, but if nothing else moves and I always put things back exactly where they were a distorted North should always be the same distorted North. It should not move around 15 to 20 degrees. That the kind of repeatability I am getting.
  • GordonMcCombGordonMcComb Posts: 3,366
    edited 2012-06-07 12:32
    It's not that simple. These sensors are extraordinarily sensitive to even the slightest changes, which are occuring constantly, and for expected results they need to be operated away from dense metal, even non-ferrous. The Parallax docs point out, "This module is VERY sensitive to ferrous material, metal objects, magnets and power supplies." Even current going through a power cable will influence these things.

    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
  • reppigreppig Posts: 35
    edited 2012-06-08 09:57
    Then what are they used for? How can they be used for robotics? From what you are saying they need to be in closed isolated environment in order to work.
  • GordonMcCombGordonMcComb Posts: 3,366
    edited 2012-06-08 10:17
    reppig wrote: »
    From what you are saying they need to be in closed isolated environment in order to work.

    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
  • Mike GreenMike Green Posts: 23,101
    edited 2012-06-08 11:14
    Compasses have been used on metal ships for as long as there have been metal ships. You have to carefully calibrate them and use various compensation tricks like mounting iron balls around the compass that can be adjusted in position to offset the magnetic field distortions from other metal masses on the ship. With a digital compass, it's easier because you can measure the readings in a known stable environment, then in the "real" environment and numerically adjust the readings. If you've got varying magnetic fields nearby, like power cables and motors, the compensation may need to be adjusted depending on current flow to the motors or through the nearby cables.
  • Tom CTom C Posts: 461
    edited 2012-06-23 04:57
    reppig,
    You might try some MuMetal shielding. You can find it on eBay.
    Just a thought.
    Regards,
    TCIII
  • DamoDamo Posts: 16
    edited 2012-07-24 04:22
    I'm using this compass on my house navigating robot. It took some tweaking, but it works very well. I found that I needed to offset the raw X and Y values so they cross zero when the other plane is at it's max or min. You also need to offset raw values so that max and min values are near equal magnitudes. Then a simple trig function will give you degrees. I was not interested in the Z value for my application. I believe the docs so that no calibration is needed, but I don't find that to be true. They are very sensitive to AC power cables, anything within 3-4 feet will give you problems. I can post my "tweaked code" if you wish.
  • BonanzaManBonanzaMan Posts: 17
    edited 2012-08-13 19:47
    Damo - I would love to see your sample code - can you share? I too have been struggling with these modules casually for months trying to get them to provide repeatable results and would love to see how you apply the compensating factors to the raw x,y,z values to achieve a reliable reading.

    Thanks,
    Greg
  • DamoDamo Posts: 16
    edited 2012-08-22 13:56
    I have not posted code on the forums before, so forgive me it turns into a garbled mess.
    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
Sign In or Register to comment.