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.
Diff: MMCx12xM.cpp
- Revision:
- 0:a44429321af8
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