![](/media/cache/img/default_profile.jpg.50x50_q85.jpg)
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.
Revision 0:a44429321af8, committed 2009-12-02
- Comitter:
- igorsk
- Date:
- Wed Dec 02 23:03:25 2009 +0000
- Commit message:
Changed in this revision
diff -r 000000000000 -r a44429321af8 MMCx12xM.cpp --- /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, ®, 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
diff -r 000000000000 -r a44429321af8 MMCx12xM.h --- /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
diff -r 000000000000 -r a44429321af8 main.cpp --- /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
diff -r 000000000000 -r a44429321af8 mbed.bld --- /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