A class and a demo program to use with the DC-SS504 board from SureElectronics which uses MMC2120MG magnetometer from Memsic. The program glows leds depending on the direction it is turned to.

Dependencies:   mbed

MMCx12xM.cpp

Committer:
igorsk
Date:
2009-12-02
Revision:
0:a44429321af8

File content as of revision 0:a44429321af8:

#include "MMCx12xM.h"
#include <stdarg.h>
#include <limits.h>

/*
 
 Memsic datasheets use very obtuse language, here I tried to summarize it in plain English.

 The device works over I2C ("fast" mode, i.e. 400 kHz max)
 the slave address is determined by the last digit (x in MMC212x) :
 0: 0x60, 1: 0x64, 2: 0x68, 3: 0x6C
 
 Register map:
   00 control register 
   01  most significant byte x axis
   02 least significant byte x axis
   03  most significant byte y axis
   04 least significant byte y axis
   05  most significant byte z axis (MMC312xM only)
   06 least significant byte z axis (MMC312xM only)

 Operation is initiated by sending the register address (must be 0, since
 only control register is writable) and then the command code, which is one
 of the bits from the control register.

 Control register layout:
   bit 0: TM (take measurements)
     set to get measurements (i.e. send 0x01)
     bit is reset when measurement is done, so you can use it 
     to check if the operation is finished
   bit 1: SET (set coil)
     set to send a large current through the set/reset coil
     should be done if sensor was affected by a large magnetic field (>5.5 gauss)
     also after power on
     result can be checked same as above
   bit 2: RESET (reset coil)
     same as above but sends current in the opposite direction
     set/reset should be interleaved in "low-power mode", whatever that is
   bits 3-6: reserved
   bit 7: not described
   
 To read a register, write its address and then read the value. The address auto-increments
 after reading, so it's not necessary to send it again if reading sequentially. The address
 is reset to 0 on power-on.
 
 Taking measurements works like following:
   1. write 0 (control register address), then 0x01 (take measurements)
   2. wait 5ms for the measurement to complete
   3. write 0 again (control register address), then read the register value
   4. check if the bit 0 is cleared. If not, repeat from 3.
   5. read x msb
   6. read x lsb
   7. read y msb
   8. read y lsb
   9. (if MMC312xM) read z msb
  10. (if MMC312xM) read z lsb
   
   N.B.: ADC resolution is only 12 bits, so four high bits of values will be 0
   
   You can also skip some values and read directly the value wanted
   by sending its address before reading.
*/

// uncomment to show protocol debug tracing
//#define DEBUG

int dprintf(const char *fmt, ...)
{
#ifdef DEBUG
    va_list args;
    va_start (args, fmt);
    int ret = vprintf(fmt, args);
    va_end(args);
    return ret;
#else
    return 0;  
#endif
}

enum command { 
  TM = 1,     // take measurements
  SET = 2,    // set
  RESET = 4   // reset
};

MMCx12xM::MMCx12xM(I2C &i2c, int address, const char *name) : Base(name),
  _I2C(&i2c), _addr(address), _own_i2c(false)
{
}

MMCx12xM::MMCx12xM(PinName sda, PinName scl, int address, const char *name) : Base(name),
  _addr(address)
{ 
    _I2C = new I2C(sda, scl);
    // we own the bus, so we can set frequency
    // MMCx12x claims to handle 400 kHz
    _I2C->frequency(400000);
    _own_i2c = true;
}

MMCx12xM::~MMCx12xM()
{
    if (_own_i2c)
        delete _I2C;
}

// send the specified command: write it into the control register
bool MMCx12xM::_send_command(int command)
{
    dprintf("* send command %d\n", command);
    const char writecmd[] = {0, command}; // address: 0 (control reg)
    int res = _I2C->write(_addr, writecmd, 2);
    dprintf("write: %d\n", res);
    return res == 0;
}

// wait until the command is done
// this is signalled by clearing of the 
// corresponding bit in the control register
bool MMCx12xM::_wait_ready(int command)
{
    dprintf("* wait ready %d\n", command);
    const char writecmd = 0; // set read address to 0 (control reg)
    int res = _I2C->write(_addr, &writecmd, 1);
    dprintf("  write: %d\n", res);
    if ( res != 0 ) 
        return false;
    char reg;
    while ( 1 ) 
    {
        // read control register value
        res = _I2C->read(_addr, &reg, 1);
        dprintf("  read: %d, reg=%08X\n", res, reg);
        if ( res != 0 ) 
            return false;
        // check if the command bit is cleared
        if ( (reg & command) == 0 )
            break; // data ready        
        dprintf("* Not ready, try again\n");
        // otherwise tell the device that we want to read the register (address 0) again
        res = _I2C->write(_addr, &writecmd, 1);
        dprintf("  write: %d\n", res);
        if ( res != 0 ) 
            return false;
    }
    return true;
}

// read one axis value (two bytes)
// set read address if index specified explicitly
bool MMCx12xM::_read_axis(int *axis, int index)
{
    dprintf("* read axis %d\n", index);
    // accept only x, y or z (0, 1, 2)
    if ( index > 2 )
        return false;
    int res;    
    if ( index != - 1 )
    {
        const char writecmd = index*2 + 1; // set read address for the axis value
        res = _I2C->write(_addr, &writecmd, 1);
        dprintf("  write: %d\n", res);
        if ( res != 0 ) 
            return false;
    }
    uint8_t pair[2]; // msb, lsb
    res = _I2C->read(_addr, (char*)&pair, 2);
    dprintf("  read: %d, msb=%02X, lsb=%02X\n", res, pair[0], pair[1]);
    if ( res != 0 )
        return false;
    // make an integer from msb and lsb
    *axis = (pair[0] << 8) | pair[1];
    return true;
}

// ask chip to take measurements and 
// read specified number of raw axis values
bool MMCx12xM::read_raw_values(int *values, int count)
{
    dprintf("* read_raw_values\n");
    if ( !_send_command(TM) )
    {
        dprintf("  send_command(TM) failed\n");
        return false;
    }
    wait_ms(5);
    if ( !_wait_ready(TM) )
    {
        dprintf("  wait_ready(TM) failed\n");
        return false;
    }
    // we have read the control register, so continue reading the data
    // which will be the axis values
    for ( int i=0; i < count; i++ )
    {
        // we're reading values sequentially, so no need to set the index
        if ( !_read_axis(&values[i]) )
        {
            dprintf("  _read_axis() failed\n");
            return false;
        }
    }
    return true;
}

bool MMCx12xM::coil_set()
{
    return _send_command(SET) && _wait_ready(SET);    
}

bool MMCx12xM::coil_reset()
{
    return _send_command(SET) && _wait_ready(SET);    
}

void MMCx12xM::calibrate_begin()
{
    // begin calibration: init the values arrays
    for ( int i=0; i < 3; i++ )
    {
        _maxvals[i] = 0;
        _minvals[i] = INT_MAX;
    }
}

void MMCx12xM::calibrate_step(int count)
{
    // take a measurement and update min-max values
    int values[3];
    if ( read_raw_values(values, count) )
    {
        for ( int i=0; i < count; i++ )
        {
            if ( _maxvals[i] < values[i] )
                _maxvals[i] = values[i];
            if ( _minvals[i] > values[i] )
                _minvals[i] = values[i];
        }
    }
}

void MMCx12xM::calibrate_end()
{
    // calculate sensitivity and offset for each axis
    // see Memsic app note AN-00MM-003
    dprintf("* calibration end\n");
    for ( int i=0; i < 3; i++ )
    {
        _sensitivity[i] = (_maxvals[i] - _minvals[i]) / 2;
        _offset[i]      = (_maxvals[i] + _minvals[i]) / 2;
        dprintf("  %i: min = %d, max = %d, s = %d, o = %d\n", i, _minvals[i], _maxvals[i], _sensitivity[i], _offset[i]);
    }
}

bool MMCx12xM::read_values(float *values, int count)
{    
    int rvalues[3];
    if ( read_raw_values(rvalues, count) )
    {
        // transform into calibrated values in the range -1.0 .. +1.0
        for ( int i=0; i < count; i++ )
        {
            // NB: we use a temp float so that the division is not integer
            float d = (rvalues[i] - _offset[i]);
            values[i] =  d / _sensitivity[i];
        }
        return true;        
    }
    return false;        
}