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

Revision:
0:a44429321af8
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MMCx12xM.cpp	Wed Dec 02 23:03:25 2009 +0000
@@ -0,0 +1,270 @@
+#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;        
+}
\ No newline at end of file