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

Files at this revision

API Documentation at this revision

Comitter:
igorsk
Date:
Wed Dec 02 23:03:25 2009 +0000
Commit message:

Changed in this revision

MMCx12xM.cpp Show annotated file Show diff for this revision Revisions of this file
MMCx12xM.h Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
mbed.bld Show annotated file Show diff for this revision Revisions of this file
--- /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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MMCx12xM.h	Wed Dec 02 23:03:25 2009 +0000
@@ -0,0 +1,73 @@
+#include <mbed.h>
+#ifndef __MMCx12xM__
+#define __MMCx12xM__
+
+// possible I2C addresses, depending on the chip number
+enum
+{
+  MMCx120M = 0x60,
+  MMCx121M = 0x64,
+  MMCx122M = 0x68,
+  MMCx123M = 0x6C,
+};
+
+/* Class: MMCx12xM
+ *  Control a Memsic MMC212xM magnetometer over I2C
+ *
+ * Example:
+ * > // MMC2120M at address 0x60
+ * >
+ * > #include "mbed.h"
+ * >
+ * > I2C i2c(p28, p27);
+ * > MMC212xM memsic1(i2c);
+ * >
+ * > int main() {
+ * >     int data[2];
+ * >     memsic1.read_raw_values(data, 2);
+ * > }
+ */
+
+class MMCx12xM : public Base
+{
+public:
+    // constructor in case you already have an I2C bus instance
+    MMCx12xM(I2C &i2c, int address = MMCx120M, const char *name = NULL);
+    // use this constructor if the sensor is the only device on the bus 
+    MMCx12xM(PinName sda, PinName scl, int address = MMCx120M, const char *name = NULL);
+    // send a SET coil command
+    bool coil_set();
+    // send a RESET coil command
+    bool coil_reset();
+    // read raw (12-bit) axis values
+    bool read_raw_values(int *values, int count = 2);
+    // start calibration
+    void calibrate_begin();
+    // take a single measurement for calibration
+    void calibrate_step(int count = 2);
+    // finish calibration and calculate offset and sensitivity values
+    void calibrate_end();
+    // read calibrated (-1.0 .. +1.0) axis values
+    bool read_values(float *values, int count = 2);
+    virtual ~MMCx12xM();
+  
+private:
+    bool _send_command(int command);
+    bool _wait_ready(int command);
+    bool _read_axis(int *value, int index = -1);
+
+    // reference to the I2C bus
+    I2C *_I2C;
+    // sensor slave address
+    int _addr;
+    // did we create the bus instance? (i.e. we should delete it on destruct)
+    bool _own_i2c;
+    // calibration values
+    int _sensitivity[3];
+    int _offset[3];
+    // temporaries for calibration
+    int _maxvals[3];
+    int _minvals[3];
+};
+
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Wed Dec 02 23:03:25 2009 +0000
@@ -0,0 +1,53 @@
+#include "mbed.h"
+#include "MMCx12xM.h"
+
+I2C i2c(p9, p10); // sda, scl
+DigitalOut memsic_power(p8);
+
+MMCx12xM memsic(i2c);
+
+PwmOut led1(LED1);
+PwmOut led2(LED2);
+PwmOut led3(LED3);
+PwmOut led4(LED4);
+
+int main()
+{
+    printf("MMC2120M demo\n");
+    memsic_power = 1;
+    bool ok = memsic.coil_set();
+    printf("Set: %d\n", ok);
+    
+    /*int values[2];
+    for (;;)
+    {    
+        ok = memsic.read_raw_values(values);
+        printf("ok: %d, x: %d, y: %d\n", ok, values[0], values[1]);
+        wait(2);
+    }*/
+    
+    printf("Starting calibration. Turn the sensor in all possible directions for 10 seconds.\n");    
+    memsic.calibrate_begin();
+    int cal_count = 0;
+    while (1)
+    {
+        memsic.calibrate_step();
+        cal_count++;
+        wait_ms(100);
+        if ( cal_count > 100 )
+            break;
+    }    
+    memsic.calibrate_end();
+    //printf("%d samples were used for calibration\n", cal_count);
+    float fvalues[2];
+    for (;;)
+    {    
+        ok = memsic.read_values(fvalues);
+        printf("ok: %d, x: %f, y: %f\n", ok, fvalues[0], fvalues[1]);
+        led1 = fvalues[0] > 0 ?  fvalues[0] : 0;
+        led2 = fvalues[1] > 0 ?  fvalues[1] : 0;
+        led3 = fvalues[0] < 0 ? -fvalues[0] : 0;
+        led4 = fvalues[1] < 0 ? -fvalues[1] : 0;
+        wait_ms(100);
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed.bld	Wed Dec 02 23:03:25 2009 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/mbed_official/code/mbed/builds/32af5db564d4