domingo, 21 de octubre de 2012

HMC5883L

Hi! In this post I am writing about the 3 axis digital compass IC HMC5883L. This is a cheap IC which gives the magnetic field direction and, as its name suggests, can be used to build an electronic compass. All information about this IC can be found in the datasheet.

If you plan on using this device I strongly suggest you to buy a board that includes all the necessary electronics, that is capacitors, resistors, maybe a LDO, ... that way you will save lot of time and even some money. Here is a photograph of mine:





To connect the device to our controller the key aspect of this device is the interface used, which is the I2C serial bus. That means you need 2 dedicated writes, one for SDA and another for SCL and don´t forget the pull-up resistors!


When the device is properly connected to the controller you can start communicating with it, which as you know is done writing and reading to and from internal registers. The simplest set of commands to configure and start reading the sensor are the following:

  1. Write 0x78 to resgister 0x00. This sets 8 samples averaged per measurement, output rate of 75Hz and normal measurement configuration.
  2. Write 0xA0 to register 0x01. This sets the gain to 4.7
  3. Write 0x00 to register 0x02. This sets the device operating mode to continuous-Measurement mode.
One usefull thing you can do to ensure the conmmunication is properly initialized and everything is going fine is to read the identifications registers. In this IC there are three of them, so you can choose which one to read, or read them all!
To do that just issue a start command, after that a write operation to address 0x0A, then a stop command. Up to now we have changed the address register to Identification Register A. To read the content, send a start command, then read 8 bits with NO-ACK and send a stop command. The data just read should be 0x48 or character 'H', if it is not (it will likely be 0x00), there is a problem with either the software or hardware.

It´s worth noting something from the previous example, To set the address register, which is the register that contains the address of the current register to be read from or written to, just issue a write opetation to the register address you want to access with no data following the address.

Once you have configured the device and you have checked it is properly working, it´s time to read the output. To do that we have to bear in mind a few aspects, first each axis has two output registers, second the address register is self incremented when you read one such register,third you have to read al output registers to perform a new measurement (if not the output registers will no longer update).

To summarize it all and provide a basic piece of code for this device, let´s write both the initialization and read routines. I have chosen CSS PICC compliler becasuse it is extremely easy to use and to understand so it is easily portable to any other C compiler (and even to assembler):



char Configure()
{
   unsigned char byte1;

   i2c_start();
   i2c_write(ADDR | WRITE);
   i2c_write(0x00); //set Configuration Register A
   i2c_write(0x78);
   i2c_stop();
   
   i2c_start();
   i2c_write(ADDR | WRITE); 
   i2c_write(0x01); //set Configuration Register B
   i2c_write(0xA0);
   i2c_stop();
   
   i2c_start();
   i2c_write(ADDR | WRITE); 
   i2c_write(0x02); //set Mode Register
   i2c_write(0x00);
   i2c_stop();
   
   i2c_start();
   i2c_write(ADDR | WRITE);
   i2c_write(0x0A); //set address register to register 0xA
   i2c_stop();
   delay_ms(10);
   i2c_start();
   i2c_write(ADDR | READ);
   byte1=i2c_read(0); //read Identification Register
   i2c_stop();
  
   if(0x48 == byte1)
      return 1;
   else
      return 0;
}

And the routine to actually read the output:

typedef struct
{

    unsigned int8 x;
    unsigned int8 y;
    unsigned int8 z;
}point;


signed int16 Read()
{
   point pnt;
   i2c_start();
   i2c_write(ADDR | WRITE);
   i2c_write(0x03); //Data output X. Remember address register is self incremented
   i2c_stop();
   i2c_start();
   i2c_write(ADDR | READ);

   pnt.x=i2c_read(); 
   (pnt.x)<<=8;
   (pnt.x)|=i2c_read(); //read X component

   pnt.z=i2c_read();
   (pnt.z)<<=8;
   (pnt.z)|=i2c_read(); //read Z component

   pnt.y=i2c_read();
   (pnt.y)<<=8;
   (pnt.y)|=i2c_read(0); //read Y component

   i2c_stop();

   //convert pnt to direction TO BE DONE
    return PointToDirection(&pnt);
}


I have tried to keep this code simple and nice, no error or collition checkings are performed so if you connect more than one device to the same I2C bus, I'd reccomend you to do that.

So far I have shown how to to some basic operations with this device, but I have not shown how to obtain the real direction. I hope I'll have some time to draw some pictures to explain that, in the meantime, if you have any question or find something wrong (I'm not much of an expert) just send a comment please!



8 comentarios:

  1. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  2. Hi, why is the last read command the only one with NO-ACK?

    ResponderEliminar
    Respuestas
    1. Hi! Actually, to indicate to the slave device that no more bytes are to be read. That is common for most I2C devices, you can see any example of this for the 24cxx memories for instance.
      Hope it helps!

      Eliminar
    2. I see, makes sense.

      Thanks for the Help :)

      Eliminar
  3. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  4. Hello, me again.

    You declared the variables of the structure as unsigned int8.

    Later on in the code you shift the bits 8 positions to the left in (because the first register read is the one that contains the MSB of the measurement, and the second one is the LSB). But, since the variables are only 8 bits, aren't you throwing away the MSB?

    Shouldn't you declare them as int16?

    Thanks,

    ResponderEliminar
  5. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  6. /* Calculates the adjustment angle with respect to the MAGNETIC north pole */

    float compass_angle()

    heading = atan2(pnt.y,pnt.x);

    if(heading < 0) heading += 2*PI;
    if(heading > 2*PI) heading -= 2*PI;

    return heading;
    }

    You don't have to use the Z direction, because only the X and Y vectores point to the north MAGNETIC pole.

    ResponderEliminar