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.
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, ®, 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; }