HMC5843 Magnetometer Library for Arduino

HMC5843 and Arduino

I (finally) have a project taking up the idle cycles of my brain, the first step of which involves figuring out how to use a magnetometer.  The project will eventually use the digital compass, accelerometers, perhaps a gyro, and maybe absolute forms of positioning like an IR camera.  I’m being slightly vague about this project both because the idea is by far the coolest thing I’ve ever come up with and because it is still somewhat short of half baked.

Anyway, Honeywell recently released a rather reasonably priced three axis magnetometer, the HMC5843, which SparkFun carries a breakout board for.  It interfaces over i2c, which is conveniently supported in hardware by most AVR microcontrollers, including the ones used on Arduino.  Arduino is something of paradox.  On one hard, the hardware is so simple and easy to use, vastly cutting down on the amount of time I need to spend arranging parts on a breadboard.  On the other hand, computations that should take a few operations instead call long functions that get compiled into hundreds, and the IDE makes me want to stab a stick of RAM into my jugular.  Luckily, one can mitigate the downsides by using an external editor, communicating with cutecom/minicom, and using avr-libc instead of the Arduino libraries as much as possible.  Back to the project.

The actual circuit is fairly simple.  Analog pins 4 and 5 on the Arduino serve as i2c’s SDA and SDC lines, respectively.  I’m using a level shifter from SparkFun to get the Arduino’s 5v lines down to the 3.3v that the HMC5843 is looking for.  Note that one can skip this by using a 3.3v Arduino Pro.  The FTDI chip on the Arduino outputs 3.3v, which is brought out on the headers, allowing the level shifter and the magnetometer to be powered off the Arduino.

I tried using the Arduino Wire library for i2c communication, but had no luck.  Atmel made TWI, the i2c implementation on the AVR, fairly easy to use, so I read through the datasheet, looked at some examples, and wrote my own Arduino library specifically for the HMC5843.  The current implementation is absolutely alpha, but it seems to read the x, y, and z values at 10 Hz correctly.  Note that you probably can’t use this library at the same time as Wire or another i2c library, and that it also doesn’t support having multiple i2c devices connected.  Its sole purpose is interfacing the HMC5843.  Here is an example sketch using it:

#include <HMC.h>
 
void setup()
{
  Serial.begin(9600);
  delay(5); // The HMC5843 needs 5ms before it will communicate
  HMC.init();
}
 
void loop()
{
  int x,y,z;
  delay(100); // There will be new values every 100ms
  HMC.getValues(&x,&y,&z);
  Serial.print("x:");
  Serial.print(x);
  Serial.print(" y:");
  Serial.print(y);
  Serial.print(" z:");
  Serial.println(z);
}

Continuing the theme of a different license for each set of code I release, this library is under a two clause BSD style license.  Please feel free to try it out and give me feedback/suggestions/patches/pedantic advice/flames.

Download and extract into the hardware/libraries/ folder of your Arduino directory:
HMC.zip

Related Posts

44 Replies to “HMC5843 Magnetometer Library for Arduino”

    1. Wow. That is much less expensive than the hospital bills resulting from slamming my head against a desk repeatedly while trying to do it myself.

    2. Got a PNI SpacePoint Fusion today. You were right. This thing is insanely slick. An almost perfectly stable quaternion; accurate and no drift.

    1. Nah, it’s just a signed int, with each increment representing a number of milligauss that depends on what gain you set in the second configuration register. Check the datasheet for the specifics.

  1. I would like to use this is in a enviroment where the sensor will be tilting and swaying alot instead of a $100+ tilt compensated compass. Would this be possible here and if so, any one know how to get decrees out of this?

    Or how could I use this to know when I am “level” with the ground?

    Ken

    1. Lets just say there is a reason accurate tilt compensated compasses cost a lot. If you’re willing to calibrate it, add a 3 axis accelerometer, and do lots of exciting math, sure, you can use it as a compass. But unless you’re just doing it for fun or to learn, it’s easier to drop the $100 on a digital compass.

  2. Hi,
    I’m want to try the same thing, but could you give move clear picture of the connection with the sensor. What about the data that you get? can you give example. thanks

    1. Except for the level shifting, it is just connected like any i2c device: http://www.arduino.cc/playground/Learning/I2C

      The level shifting is on a board available from SparkFun, but it is basically just two MOSFETs. Here is a decent post about doing that: http://delphys.net/d.holmes/hardware/levelshift.html

      The data, as stated above, is just a set of raw, uncalibrated signed ints. You really want to read the datasheet for the specifics. Honestly though, for most use cases, you’re better off dropping the money on something like the SpacePoint Fusion. A raw magnetometer is more pain than it’s worth.

      1. It is not necessary to have the level shifting – I am running mine with pullups to +5V on the I2C lines.

      2. You’re right, level shifting isn’t necessary. You should probably have the pullups going to 3.3v though, not 5v. The datasheet for the ATmega168 shows that it’ll read anything above around 2.65v as high with a Vcc of 5v.

  3. Thanks very much for making this library available.

    I tried it with the sparkfun level shifter and HMC5843 breakout board as you did, but used them with a seeeduino mega arduino board.

    I get an output like this:

    x:4128 y:256 z:8192
    x:15677 y:15677 z:15677
    x:4128 y:256 z:8192
    x:4128 y:256 z:8192
    x:4128 y:256 z:8192
    x:4607 y:-1 z:-1
    x:4128 y:256 z:8192
    x:4128 y:256 z:8192
    x:4128 y:256 z:8192

    This output does appear to be coming from the HMC5843 (removing data lines or power to the HMC5843 sets all values to ~15600) but the values do not change in response to orientation changes or the presence of magnets (the value changes above seem to be artifacts).

    Do you think it is an incompatibility between the seeeduino mega and your library that is the problem? If so, what should I focus on changing in the library to make it work?

    I would be very grateful for any suggestions you might have.

    1. Apparently Sparkfun has been sending out defective HMC5843 boards for a while. It’s possible you’re hitting that problem. In the comments on the product description, people have found that adding a larger capacitor can fix it. Check those comments for details.

  4. Hi,
    First, thank you for this project, but I have a problem, my result as below;

    x:4128 y:256 z:8192
    x:5119 y:-1 z:-1
    x:4128 y:256 z:8192
    x:4128 y:256 z:8192
    x:4128 y:256 z:8192
    x:4128 y:256 z:8256
    x:4128 y:256 z:8192
    x:4129 y:-1 z:-1
    x:4128 y:256 z:8192
    x:4128 y:256 z:8192
    x:4128 y:256 z:8192

    but when I try to change the position of magnetic item around the sensor its not look as the results and its not change as real. Maybe I did it in wrong way. I don’t know ?. 🙁

  5. Hi,
    I do not know code where that error.

    /*
    11-11-09
    Copyright Spark Fun Electronics© 2009
    Aaron Weiss
    aaron at sparkfun.com

    HMC5843 3-axis magnetometer

    ATMega328 w/ external 16MHz resonator
    High Fuse DA
    Low Fuse FF

    raw output in continuous mode
    */

    #include
    #include
    #include
    #include
    #include
    #include “types.h”
    #include “defs.h”
    #include “i2c.h”

    #define FOSC 16000000
    #define BAUD 9600

    #define sbi(var, mask) ((var) |= (uint8_t)(1 << mask))
    #define cbi(var, mask) ((var) &= (uint8_t)~(1 << mask))

    #define WRITE_sda() DDRC = DDRC | 0b00010000 //SDA must be output when writing
    #define READ_sda() DDRC = DDRC & 0b11101111 //SDA must be input when reading – don't forget the resistor on SDA!!

    ///============Function Prototypes=========/////////////////
    void HMC5843(void);

    ///============I2C Prototypes=============//////////////////
    void i2cSendStart(void);
    void i2cSendStop(void);
    void i2cWaitForComplete(void);
    void i2cSendByte(unsigned char data);
    void i2cInit(void);
    void i2cHz(long uP_F, long scl_F);

    ///============Initialize Prototypes=====//////////////////
    void ioinit(void);
    void UART_Init(unsigned int ubrr);
    static int uart_putchar(char c, FILE *stream);
    void put_char(unsigned char byte);
    static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);
    void delay_ms(uint16_t x);

    /////===================================////////////////////

    int main(void)
    {
    ioinit();
    i2cInit();
    delay_ms(100);

    while(1)
    {
    HMC5843();
    delay_ms(400); //at least 100ms interval between measurements
    }
    }

    void HMC5843(void)
    {
    uint8_t xh, xl, yh, yl, zh, zl;
    long xo, yo, zo;

    i2cSendStart();
    i2cWaitForComplete();
    i2cSendByte(0x3C); //write to HMC
    i2cWaitForComplete();
    i2cSendByte(0x02); //mode register
    i2cWaitForComplete();
    i2cSendByte(0x00); //continuous measurement mode
    i2cWaitForComplete();
    i2cSendStop();

    //must read all six registers plus one to move the pointer back to 0x03
    i2cSendStart();
    i2cWaitForComplete();
    i2cSendByte(0x3D); //read from HMC
    i2cWaitForComplete();
    i2cReceiveByte(TRUE);
    i2cWaitForComplete();
    xh = i2cGetReceivedByte(); //x high byte
    i2cWaitForComplete();
    //printf(" %d", xh);

    i2cReceiveByte(TRUE);
    i2cWaitForComplete();
    xl = i2cGetReceivedByte(); //x low byte
    i2cWaitForComplete();
    xo = xl|(xh << 8);
    printf("x=%4ld, ", xo);

    i2cReceiveByte(TRUE);
    i2cWaitForComplete();
    yh = i2cGetReceivedByte(); //y high byte
    i2cWaitForComplete();
    //printf(" %d ", yh);

    i2cReceiveByte(TRUE);
    i2cWaitForComplete();
    yl = i2cGetReceivedByte(); //y low byte
    i2cWaitForComplete();
    yo = yl|(yh << 8);
    printf("y=%4ld, ", yo);

    i2cReceiveByte(TRUE);
    i2cWaitForComplete();
    zh = i2cGetReceivedByte();
    i2cWaitForComplete(); //z high byte
    //printf(" %d ", zh);

    i2cReceiveByte(TRUE);
    i2cWaitForComplete();
    zl = i2cGetReceivedByte(); //z low byte
    i2cWaitForComplete();
    zo = zl|(zh <>8;
    UBRR0L = ubrr;

    // Enable receiver and transmitter
    UCSR0A = (0<<U2X0);
    UCSR0B = (1<<RXEN0)|(1<<TXEN0);

    // Set frame format: 8 bit, no parity, 1 stop bit,
    UCSR0C = (1<<UCSZ00)|(1<<UCSZ01);

    stdout = &mystdout; //Required for printf init
    }

    static int uart_putchar(char c, FILE *stream)
    {
    if (c == '\n') uart_putchar('\r', stream);

    loop_until_bit_is_set(UCSR0A, UDRE0);
    UDR0 = c;

    return 0;
    }

    void put_char(unsigned char byte)
    {
    /* Wait for empty transmit buffer */
    while ( !( UCSR0A & (1< 0 ; x–){
    for ( y = 0 ; y < 90 ; y++){
    for ( z = 0 ; z = 16)
    bitrate_div = (bitrate_div-16)/2;
    outb(TWBR, bitrate_div);
    }

    void i2cSendStart(void)
    {
    WRITE_sda();
    // send start condition
    TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
    }

    void i2cSendStop(void)
    {
    // transmit stop condition
    TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
    }

    void i2cWaitForComplete(void)
    {
    int i = 0; //time out variable

    // wait for i2c interface to complete operation
    while ((!(TWCR & (1<<TWINT))) && (i < 90))
    i++;
    }

    void i2cSendByte(unsigned char data)
    {

    WRITE_sda();
    // save data to the TWDR
    TWDR = data;
    // begin send
    TWCR = (1<<TWINT)|(1<<TWEN);
    }

    void i2cReceiveByte(unsigned char ackFlag)
    {
    // begin receive over i2c
    if( ackFlag )
    {
    // ackFlag = TRUE: ACK the recevied data
    outb(TWCR, (inb(TWCR)&TWCR_CMD_MASK)|BV(TWINT)|BV(TWEA));
    }
    else
    {
    // ackFlag = FALSE: NACK the recevied data
    outb(TWCR, (inb(TWCR)&TWCR_CMD_MASK)|BV(TWINT));
    }
    }

    unsigned char i2cGetReceivedByte(void)
    {
    // retieve received data byte from i2c TWDR
    return( inb(TWDR) );
    }

    unsigned char i2cGetStatus(void)
    {
    // retieve current i2c status from i2c TWSR
    return( inb(TWSR) );
    }

  6. Does anyone knows what I have to do now? I used the HCM.zip files from above, but it still isn’t working..

    c:/documents and settings/……/bedrijfs docs/arduino/hardware/tools/avr/lib/gcc/../../avr/include/math.h:439: error: expected unqualified-id before ‘double’

    c:/documents and settings/……../bedrijfs docs/arduino/hardware/tools/avr/lib/gcc/../../avr/include/math.h:439: error: expected `)’ before ‘double’

    c:/documents and settings/………./bedrijfs docs/arduino/hardware/tools/avr/lib/gcc/../../avr/include/math.h:439: error: expected `)’ before ‘double’

  7. I know this isn’t really a reply to this thread but I have been trying to get my HMC5348 working for quite some time without much success. The data sheet for the compass doesn’t go into much detail about how to read from registers other than from the data registers. So my question is, what is the proper way to read the status register from the compass? Would the correct sequence be:

    start 0x3c 0x09 restart ‘receive enable’ 0x3d ‘read byte’ stop

    or

    start 0x3d 0x09 ‘receive enable’ ‘read byte’ stop

    The data sheet is not clear about how to read from registers other than the data registers. Given the auto incrementing of the register address for this chip, I’m not sure if you can send an address after issuing a read command (0x3d).

    Any help anyone can offer is greatly appreciated. Thanks in advance.

  8. Hej,

    Is there any one did this sensor work with Arduino, if the answer YES, please upload some videos on youtube so we can see what we have to do.
    Regards,

    1. Hi Asilloo,

      Still working on it. But we can’t figure out the date we get from the HMC, and how we have to put this in a picture of which way is north 😉
      Are you working on it too?

      1. Hi,
        I’m going to become crazy with this sensor 😉

        I send a email to sparkfun about this problem , this is the replay;

        Hello,I’m very sorry to hear about the trouble this sensor is giving you. The example code we’ve posted on the product page (http://www.sparkfun.com/commerce/product_info.php?products_id=9371) should work perfectly, and you can load it onto an Arduino with the need of an AVR programmer. This tutorial: http://www.sparkfun.com/commerce/tutorial_info.php?tutorials_id=142, in the ‘Serial bootloading an Arduino board’ section describes how you can load the code using WinAVR.

        There have been discussions of an incorrect capacitor on the board, but our tests show that the board does work with the current setup.

        Hope that helps. Let me know if you have any further questions or concerns.

  9. Hi,

    This output from HMC5843. If I want to change the settings below to HMC5843 output unit Tesla. How to approach.

    x:-374 y:183 z:-1006
    x:-1045 y:122 z:-1711
    x:-4096 y:-931 z:-4096
    x:-1264 y:57 z:-173
    x:819 y:-440 z:-4096
    x:3 y:357 z:-483

  10. I think it is important to notice that the Wire library may actually be used, but you have to use the 7bit address of the HMC5843 which is 0x1E (0x3C >> 1).

    void initHMC5843()
    {
    Wire.beginTransmission(0x1E);
    Wire.send(0x02);
    Wire.send(0x00); // continues reading
    Wire.endTransmission();
    }

    void readxyz()
    {
    Wire.beginTransmission(0x1E);
    Wire.send(0x03);
    Wire.endTransmission();

    Wire.requestFrom(0x1E, 6);
    if(6 <= Wire.available())
    {
    int x,y,z;
    x = Wire.receive() << 8;
    x |= Wire.receive();
    y = Wire.receive() << 8;
    y |= Wire.receive();
    z = Wire.receive() << 8;
    z |= Wire.receive();
    // […] your stuff here (lowpass filter values, transform)
    }
    }

    HTH

    1. I tried just using Wire library on HMC5883L. It works fine. So I believe that Wire library should also works fine for HMC5843. HMC5843 and HMC5883L are very similar.
      please see the complete code below:

      #include
      void setup()
      {
      initHMC5883L();
      }
      void loop()
      {
      readxyz();
      }
      void initHMC5883L()
      {
      Wire.beginTransmission(0x1E);// 7 bits address //0x3C //datasheet page 17
      Wire.send(0x02);
      Wire.send(0x00); // continues reading
      Wire.endTransmission();
      }

      void readxyz()
      {
      Wire.beginTransmission(0x1E);
      Wire.send(0x03);
      Wire.endTransmission();

      Wire.requestFrom(0x1E, 6);
      if(6 <= Wire.available())
      {
      int x,y,z;
      x = Wire.receive() << 8;
      x |= Wire.receive();
      y = Wire.receive() << 8;
      y |= Wire.receive();
      z = Wire.receive() << 8;
      z |= Wire.receive();
      }
      }

  11. I bought the HMC5843 in Germany and looks like it came with the old capacitor problem…
    It is possible to solve this with normal ceramic capacitors?

    I start the project with SuperCollider and Iphones, extracting the data from the internal Iphone compass and applying into a Synth in SC.

    http://www.andrewakko.com/all/unterwasser/

    Now I want to change the Iphone+SC to a more cheap solution = Arduino+HMC5843.
    But is already getting tricky in the first step with this badboys….

  12. Hey NRP…i followed your wiring diagram,loaded your library and sketch. Powered it up and it works beautifully! Many, many, many thanks for saving me the head-banging.

  13. Hi,
    sorry to bother you with my ignorance, but I keep failing with reading the HMC5843.
    the sketch (the one on top of this page, with the hmc.h lib) is compiling and running without errors, but constantly delivers all the same value:

    x:15677 y:15677 z:15677
    x:15677 y:15677 z:15677
    x:15677 y:15677 z:15677

    I am an absolute noob in i2c (actually, in Arduino stuff as well), and have no idea on how I can track down the problem.
    wiring is ok I guess (Arduino Uno, getting the 3.3V from board, Data pin on A4, clock on A5).

    as the hex value of 15677 is 3D3D, I have the suspicion that the sent data is somehow taken as read data (because the read address of the HMC is 3D as well… just a clueless wild guess…).
    I did not try the capacitor fix because I do not want to fry anything, but I think it is a general i2c communication issue that I am facing.

    most grateful for any hint,
    martin

    1. Those values are a possible symptom of the capacitor issue, but could be something else. I recommend trying the capacitor fix if at all possible.

      1. I have brought the sensors 2 month ago but still not yet get the correct readings…
        After I have read the comments I have brought a capacitor (100uF, 16V), but i dont know how to connect that to the circuit (in parallel). could anybody describe it in detail or show me a photo?

  14. hello
    i bought hmc5843 and i could get data from it but i dont know how i can change these data to gauss and the most important i dont know how i can change the output to the angels

Leave a Reply to Donnie Cancel reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.