Shop Learn
My Version of a HMC5883L Object — Parallax Forums

My Version of a HMC5883L Object

Duane DegnDuane Degn Posts: 10,484
edited 2014-06-02 23:00 in Propeller 1
I've been meaning to post my version of a HMC5883L object for a while.

Every so often I see posts asking about how to use the raw reading from the sensor to compute heading. The only way I know how to do this is to use the trig arctangent function. Fortunately F32 has both an Atan method and an Atan2 method. The Atan2 method takes two parameters (y and x) and returns the angle (in radians) these two points make with the x axis.

The demo code attached to post #2 provides out such as this:
 course = -146.093


 field angle from horizontal = 46.994



Both numbers have units of degrees. The "course" is based on the compass rose and not the normal Cartesian coordinate system.

I'm going to reserve a few posts to make it easier to add future updates to this object. Please hold any replies until I've made four posts.

Comments

  • Duane DegnDuane Degn Posts: 10,484
    edited 2015-10-16 16:37
    Reserved for code.
    Update May 23, 2014: Uploaded newest version "Hmc5883lDemo140523e". This is the version to try!
    All features of the HMC5883L are accessible. The rate the sensor takes readings, the number of readings to internally average, the gain and the reading mode can all be set from the Parallax Serial Terminal.

    See post #19 below for additional information and sample output.

    16,19, 9, 9, 0 As of 140523.

  • Duane DegnDuane Degn Posts: 10,484
    edited 2014-05-12 14:55
    Reserved for super awesome applications of this code.

    I'm attempting to use a HMC5883L sensor with an aluminum wallet.

    I built a four servo gadget with gimbals in a hope if could keep the sensor stationary while the servo assembly was rotated in various directions. Very limited success.
  • Duane DegnDuane Degn Posts: 10,484
    edited 2014-05-12 14:56
    Reserved for other stuff.
  • Duane DegnDuane Degn Posts: 10,484
    edited 2014-05-12 14:56
    Record of previous version.

    First version "CompassDemo140512b" uploaded May 12, 2014. (removed May 23, 2014 with 16 views)

    Basic version without averaging "CompassBasicDemo140512b" uploaded May 122014.(removed May 23, 2014 with 19 views)

    Versions with calibration corrections.
    "CompassDemo140513a" uploaded May 13, 2014.(removed May 23, 2014 with 9 views)

    "CompasBasicDemo140513c" uploaded May 13, 2014. (removed May 23, 2014 with 9 views)
    I'm not convinced the calibration corrections do much good. These newer version may not be better than the previously uploaded version.

    See discussion about calibration corrections below in post #10.

    Edit: In the comments on both "140512b" versions, I state the I2C lines are actively driven. This is not correct. The I2C lines are controlled by setting the direction of the pins only. There must be pull-up resistors to pull the lines high when the pins are set as inputs. I'll fix these comments with the next revision.
  • BasilBasil Posts: 380
    edited 2014-05-12 20:04
    Hmm this might come in handy. I have put an HMC5883L on a rocket altimeter and am trying to nut out a way to determine angle from vertical.
    This looks like it will allow me to do so fairly easily?
  • Duane DegnDuane Degn Posts: 10,484
    edited 2014-05-12 21:19
    Basil wrote: »
    Hmm this might come in handy. I have put an HMC5883L on a rocket altimeter and am trying to nut out a way to determine angle from vertical.
    This looks like it will allow me to do so fairly easily?

    You should be able to figure out how far from vertical the rocket is is based on the magnetic field's vector. You'd need to know the orientation of the sensor inside the rocket and how far from vertical the field normally is in your area.

    I was surprised to see the magnetic field lines are about 67 degrees from horizontal where I live. Here in Idaho, magnetic north is mostly down.

    I can't think of an equation to let you figure out the rocket's angle off the top of my head, but I don't think it would be too hard to come up with one. Maybe a fellow forum members knows how to do this and will let us know what equation to use.

    I was just playing around with the sensor to see if I could get each axis aligned with the field and I could but they magnitude of the field wasn't measured consistently across all the axes. On my sensor the x axis would read up to about 570 and the y axis would only go up to 460. The z axis seemed to be the most sensitive and would read about 580 when aligned with earth's magnetic field.

    I'm betting to make best use of the sensor, it should be calibrated. Each axis should probably be scaled prior to using the values in the trig equations.

    I'll probably try to figure out some sort of calibration procedure. I know one digital compass I purchased years ago, had a calibration mode. While in the calibration mode you were supposed to slowly rotate the compass about its z axis. I'm thinking I'll just have the user point each axes at the magnetic north one after the other and have them record the magnitude of the field. These magnitude readings could then be entered into the program as a calibration constant.
  • BasilBasil Posts: 380
    edited 2014-05-12 21:29
    I just added it to the altimeter cause I thought it might be interesting to collect data, but it may turn out to give me a nice safety feature.
    Haven't thought about this much, but couldn't one just determine the magnetic field angle on the pad before launch and use that as a starting point? (Again, not very well thought through)

    I'm doing something similar with the accelerometer to determine which way up the altimeter is mounted in the rocket.
  • HughHugh Posts: 363
    edited 2014-05-12 23:38
    Thanks for posting that Duane.

    Do you know, I might just have a use for it... :-)
  • Duane DegnDuane Degn Posts: 10,484
    edited 2014-05-13 12:00
    I added some calibration constants to the original programs. These new versions have been attached to post #2 above.
    MAX_EXPECTED_RAW_X = 604   '' user changeable
      MAX_EXPECTED_RAW_Y = 473   '' user changeable
      MAX_EXPECTED_RAW_Z = 610   '' user changeable
    
    
      NORMALIZED_COOKED = 1000
    

    The way I determined these constants was to point an axis at magnetic north and try to get the other axes value as close to zero as possible. I then read the "raw field strength" from the terminal and used it as the max expected constant.

    Anyone using the program will like need to change these values to match their sensors.

    Here's the output from the two new versions.
     raw X = 170, Y = 44, Z = -503
     cooked X = 281, Y = 93, Z = -824
     course = 16.819
    
    
     field angle from horizontal = -70.241
     raw field strength = 532.771
     normalized field strength = 875.549
    

    The "raw" values are the same as previous versions but the "cooked" values have been scaled so the maximum value should be 1000.

    The "normalized field strength" should be 1000. One thing I've noticed is these sensors are very sensitive to magnetic fields generated by electronics. After seeing how hard it is to get a clean reading in a room filled with electronics, I'm not sure this sort of calibration is really worth the effort.

    I'm interested to hear what others think when comparing a "calibrated" reading vs an unmodified reading.
  • HughHugh Posts: 363
    edited 2014-05-13 12:09
    Hugh wrote: »
    Thanks for posting that Duane.

    Do you know, I might just have a use for it... :-)

    What I was trying to do was to have two magnetometers back-to-back and use the values from each to get to a 'nulled' value. It was an experiment as much as anything (rather than aiming at a specific need*). Your code made it so easy to do this.

    These plots were the result of waving a small pair of side-cutting pliers, about 6" from the board.

    The raw (x, y, z) data from each sensor:

    Raw.png


    The difference between x, y, z:

    RawDifference.png


    With a rolling-average of the difference subtracted

    Averaging Difference - Processed.png


    The reason I used two sensors was to try and increase the sensitivity. It still outputs a stream of values to the PST but it could easily go to an SD card, etc.,

    * The alleged existence of a WWII tank under the field across my house starting the train of thought...

    Many thanks Duane - That must be a candidate for the Obex!
    924 x 629 - 46K
    910 x 578 - 24K
    880 x 578 - 21K
  • BasilBasil Posts: 380
    edited 2014-05-13 13:49
    Fantastic :) This will be put to use
  • Duane DegnDuane Degn Posts: 10,484
    edited 2014-05-16 16:20
    Basil wrote: »
    Haven't thought about this much, but couldn't one just determine the magnetic field angle on the pad before launch and use that as a starting point? (Again, not very well thought through)

    I've thought about this a bit more and I think you're right. You just need to measure the initial field and subtract the initial angle from the current angle to find how far the rocket is tilted.

    I added this method to find the initial field. (I added this coded to the code I'm using, it's not part of the attached code above.)
    PUB FindSteady(readingsToAverage, allowedMovement, maxAttempts) | readingsSampled, total, {
    } failedAttempts, miniTilt, maxTilt
    
    
      
      failedAttempts := 0
      Pst.str(string(13, " Finding Initial Tilt")) 
      Pst.str(string(13, " Please keep compass stationary.")) 
      Compass.CheckCompass
      
      repeat
        total := 0
        readingsSampled := 0
        miniTilt := maxTilt := pseudoTilt
        repeat
          Compass.CheckCompass
          if pseudoTilt < miniTilt
            miniTilt := pseudoTilt
            Pst.str(string(13, " new miniTilt = "))
            DecPoint(miniTilt, Compass#PSEUDOREAL_MULTIPLIER)
            Pst.ClearEnd  
          elseif pseudoTilt > maxTilt
            miniTilt := pseudoTilt
            Pst.str(string(13, " new maxTilt = "))
            DecPoint(maxTilt, Compass#PSEUDOREAL_MULTIPLIER)
            Pst.ClearEnd
          if maxTilt - miniTilt > allowedMovement
            Pst.str(string(13, " Too much movement detected. allowedMovement = "))
            DecPoint(allowedMovement, Compass#PSEUDOREAL_MULTIPLIER)
            Pst.ClearEnd
            Pst.str(string(13, " movement while initializing = "))
            DecPoint(maxTilt - miniTilt, Compass#PSEUDOREAL_MULTIPLIER)
            Pst.ClearEnd
            failedAttempts++
            quit
          readingsSampled++
          total += pseudoTilt       
        until readingsSampled == readingsToAverage
            
      until readingsSampled == readingsToAverage or failedAttempts == maxAttempts
    
    
      if readingsSampled == readingsToAverage
        result := total / readingsSampled
      else
        result := Compass#PSEUDO_CIRCLE
    

    In hopes of getting a good initial value, I averaged multiple readings together. I was going to explain each parameter but I think the names are pretty self explanatory.

    I added these constants which I use when calling the method.
    READING_TO_SAMPLE = 32
       ALLOWED_MOVEMENT = 2 * Compass#PSEUDOREAL_MULTIPLIER
      MAX_INITIAL_TILT_ATTEMPTS = 200
    
    pseudoInitialTilt := FindSteady(READING_TO_SAMPLE, ALLOWED_MOVEMENT, MAX_INITIAL_TILT_ATTEMPTS)
    

    You might want to change the value of "ALLOWED_MOVEMENT" from two degrees to a fraction of a degree. You can make fractional degrees by dividing the constant "Compass#PSEUDOREAL_MULTIPLIER" by the desired fractional value (for example use "Compass#PSEUDOREAL_MULTIPLIER / 2" for half a degree).

    You probably should make sure the method returned a valid reading by checking it against "Compass#PSEUDO_CIRCLE" which is what the method returns if it is unable to get a good initial reading.

    This assumes your vertical axis is the z axis. If you're using a different vertical axis, the "tilt" portion of the child object would need to be modified.
  • BasilBasil Posts: 380
    edited 2014-05-16 20:17
    Looks like it will do the job nicely :)

    I wonder at what altitude the field lines start changing angle as the loop around to the other pole. Hmmm

    But that's a bit specific for this object I suspect so I won't hijack :)
  • Duane DegnDuane Degn Posts: 10,484
    edited 2014-05-20 18:43
    I don't know how high you'd have to go to see a noticeable difference in the angle of the field lines. I bet the altitude would vary with one's latitude.

    There's a bug in version "CompassDemo140513a" of the code above. The speed data isn't being accessed correctly from the parent object. The section of code effected by the bug is commented out in the posted version. I'll post a bug fix once I add a few additional features.

    I've been having a lot of trouble trying to incorporate the HMC5883L sensor into my aluminum wallet project. In the past, whenever I've used the HMC5883L in a project, I keep the sensor at a distance from the other electronics. With this wallet project, the compass sensor is right up against a bunch of other electronic devices. The magnetic fields from the other electronics swamp the sensor and cause the values output to overflow the 12-bit limits.

    From reading the datasheet, I've learned the gain of the sensor can be adjusted. I've hoping I can still get useful data from the sensor by changing the gain settings.

    I seem to get multiple problems when reading the sensor. Sometimes I can overcome the problem by adjusting the gain but other times the raw values returned are all "-1".

    The problem with all negative ones just seems to happen on the small Propeller board I'm using in the aluminum wallet. When I connect the sensor to a QuickStart board, I don't get the problem. I'm pretty sure this problem is some sort of connection problem since I get the same readings returned when the sensor is not connected to the Propeller.

    I noticed the compass includes three ID registers which contain the characters "H43". I've added a method to read these characters (not yet posted) but I still want to add some code to compare the character read from the sensor with the characters expected so the program can use these registers to confirm the correct sensor is being used.

    I've been learning there's a lot more to these sensors than just reading the raw data. Apparently there's some sort of calibration feature in the sensor. Hopefully I'll learn enough about all this to take advantage of all the sensor's capabilities.

    Edit: BTW, the electronic device causing the most trouble for the sensor appears to be the piezo speaker I added to the project. I would really like to figure out a way of keeping the piezo speaker since it will warn me when the battery is getting low.
  • BasilBasil Posts: 380
    edited 2014-05-20 23:21
    Interesting. Did you follow the supplier recommendations regarding removing the copper on all layers beneath the sensor?

    I haven't yet assembled my board, but am likely to run into similar problems (even though I removed the copper and followed supplier recommendations). I also have a piezo, as well as bluetooth and various other bits and pieces.

    I'll see about writing a test program to cycle through each device and see what effect they have on the readings if it helps.
  • Duane DegnDuane Degn Posts: 10,484
    edited 2014-05-21 10:00
    Basil wrote: »
    Did you follow the supplier recommendations regarding removing the copper on all layers beneath the sensor?

    I'm using a small breakout board I purchased from ebay for a few dollars. I just checked and they didn't remove the copper on the bottom layer of the board.

    The board I have from SparkFun has the copper removed. The SparkFun board doesn't bring the ready pin out. I was originally writing this object with the SparkFun board in mind but I think the driver could have better performance if I take advantage of the ready pin. As I think about this a bit, I suppose I don't really need the ready pin since the Propeller can just read the status register to see if the data is ready. I think the ready pin makes more sense for a microcontroller relying on interrupts.
  • BasilBasil Posts: 380
    edited 2014-05-21 13:11
    I would be curious to see if you have different results from the spark fun board, which would help eliminate board design as a possible cause of the problem.
  • Duane DegnDuane Degn Posts: 10,484
    edited 2014-05-23 21:34
    Major update!

    The most recent version "Hmc5883lDemo140523e" lets one access all the features of the HMC58853L sensor.

    Here's some sample output:

     programName = Hmc5883lDemo140523e
     objectName = Hmc5883lCompass140523c
     raw X = -152, Y = -130, Z = -502
     cooked X = -251, Y = -274, Z = -822
    
    
     course = -132.492
    
    
     field angle from horizontal = -65.675
     raw field strength = 540.378
     normalized field strength = 902.087
     z-axis tilt = 0.404
    
    
     Press one of the following keys:
    C) Clear screen
    G) adjust Gain
    A) change Averaging used
    R) change sampling Rate
    M) change Mode
    T) Test registers
    I) read Id registers of compass
    S) Set initial heading and tilt
    P) set Positive bias configuration
    N) set Negative bias configuration
    D) Display all settings
    
    
    
    
     current gain = 0.920 milli-Gauss per count (index = 1)
     current samples averaged = 1 (index = 0)
     current smampling rate = 7.500 readings per second (index = 3)
     current mode index = 0
     continuous read mode
    


    (The above output was active after selecting menu item "D".)

    Theoretically the "normalized field strength should always be 1000. This value gets adjusted to compensate for the gain setting used.

    The program will record the initial heading and tilt when the program starts up. To reset this "initial" state, use the "S" option.

    All the menu choices can be selected with either upper or lower case letters.

    Make sure and check out the "P" and "N" options. I believe these "biased" configurations can be used to calibrate the compass. For now I'm calibrating the compass by pointing each axis towards magnetic north and recording the magnitude of the field. I enter these readings into the constants "
    MAX_EXPECTED_RAW_X" through "MAX_EXPECTED_RAW_Z".

    Now that the gain can be adjusted, you want to make sure you using the same gain setting when recording the "MAX_EXPECTED" values as you assign to the constant "GAIN_USED_FOR_EXPECTED_RAW".

    I used the default gain index of 1 but the sensor is more sensitive with a gain index of zero. If any of you use a gain index of zero, make sure you don't get any overflow errors.

    When the program starts up, it uses the default gain index of one but if there's overflow errors, the program will keep adjusting the gain until the overflow errors go away or maximum gain index is reached. The higher the gain index, the lower the sensitivity of the sensor.

    I think this latest version is worthy of the OBEX. I'll likely submit it to the OBEX in the next few days unless someone finds a problem.

    If any of you have a Propeller and a HMC5883L sensor, I hope you give this program a try. I don't claim the user interface is intuitive but if some aspect of the menu is unclear, I hope you let me know.

    For those without a HMC5883L sensor, what are you waiting for? You can buy them for about $3 shipped from ebay. Get a couple.

    Edit: Be warned. Setting the mode to 1 (single read) will pretty much stop the readings from the sensor. Mode 0 (continuous read) is presently the only useful mode setting used by the program.
  • BasilBasil Posts: 380
    edited 2014-05-24 04:24
    Thank you for your efforts!
  • Duane DegnDuane Degn Posts: 10,484
    edited 2014-06-02 23:00
    Somehow I missed the demo code available on the Parallax HMC5883L product page. It looks has a lot of similarities to the code on the Learn site. The code on the product page includes a method to compute the heading.

    It uses Kye's integer math object. While there's things about the demo code I don't like, I do like it doesn't require a cog to run the math co-processor (F32).

    The demo code neglects to use the method "polarAngle" (aka ATAN2 or arctangent2) which will return an angle from 0 to 360. IMO, the demo code goes about computing this angle the hard way.

    I'm hoping to combine the best of my object and Parallax's object to come up with an improvement on both.

    One concern I have about using the integer math object is the heading is limited in precision to a single degree. I find with my current version I can get sub-degree precision on the heading and tilt values. I'm pretty sure I'll end up making a version using both types of math objects so the one best matching the needs of a particular application can be used.
Sign In or Register to comment.