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@0:a44429321af8, 2009-12-02 (annotated)
- Committer:
- igorsk
- Date:
- Wed Dec 02 23:03:25 2009 +0000
- Revision:
- 0:a44429321af8
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
igorsk | 0:a44429321af8 | 1 | #include "MMCx12xM.h" |
igorsk | 0:a44429321af8 | 2 | #include <stdarg.h> |
igorsk | 0:a44429321af8 | 3 | #include <limits.h> |
igorsk | 0:a44429321af8 | 4 | |
igorsk | 0:a44429321af8 | 5 | /* |
igorsk | 0:a44429321af8 | 6 | |
igorsk | 0:a44429321af8 | 7 | Memsic datasheets use very obtuse language, here I tried to summarize it in plain English. |
igorsk | 0:a44429321af8 | 8 | |
igorsk | 0:a44429321af8 | 9 | The device works over I2C ("fast" mode, i.e. 400 kHz max) |
igorsk | 0:a44429321af8 | 10 | the slave address is determined by the last digit (x in MMC212x) : |
igorsk | 0:a44429321af8 | 11 | 0: 0x60, 1: 0x64, 2: 0x68, 3: 0x6C |
igorsk | 0:a44429321af8 | 12 | |
igorsk | 0:a44429321af8 | 13 | Register map: |
igorsk | 0:a44429321af8 | 14 | 00 control register |
igorsk | 0:a44429321af8 | 15 | 01 most significant byte x axis |
igorsk | 0:a44429321af8 | 16 | 02 least significant byte x axis |
igorsk | 0:a44429321af8 | 17 | 03 most significant byte y axis |
igorsk | 0:a44429321af8 | 18 | 04 least significant byte y axis |
igorsk | 0:a44429321af8 | 19 | 05 most significant byte z axis (MMC312xM only) |
igorsk | 0:a44429321af8 | 20 | 06 least significant byte z axis (MMC312xM only) |
igorsk | 0:a44429321af8 | 21 | |
igorsk | 0:a44429321af8 | 22 | Operation is initiated by sending the register address (must be 0, since |
igorsk | 0:a44429321af8 | 23 | only control register is writable) and then the command code, which is one |
igorsk | 0:a44429321af8 | 24 | of the bits from the control register. |
igorsk | 0:a44429321af8 | 25 | |
igorsk | 0:a44429321af8 | 26 | Control register layout: |
igorsk | 0:a44429321af8 | 27 | bit 0: TM (take measurements) |
igorsk | 0:a44429321af8 | 28 | set to get measurements (i.e. send 0x01) |
igorsk | 0:a44429321af8 | 29 | bit is reset when measurement is done, so you can use it |
igorsk | 0:a44429321af8 | 30 | to check if the operation is finished |
igorsk | 0:a44429321af8 | 31 | bit 1: SET (set coil) |
igorsk | 0:a44429321af8 | 32 | set to send a large current through the set/reset coil |
igorsk | 0:a44429321af8 | 33 | should be done if sensor was affected by a large magnetic field (>5.5 gauss) |
igorsk | 0:a44429321af8 | 34 | also after power on |
igorsk | 0:a44429321af8 | 35 | result can be checked same as above |
igorsk | 0:a44429321af8 | 36 | bit 2: RESET (reset coil) |
igorsk | 0:a44429321af8 | 37 | same as above but sends current in the opposite direction |
igorsk | 0:a44429321af8 | 38 | set/reset should be interleaved in "low-power mode", whatever that is |
igorsk | 0:a44429321af8 | 39 | bits 3-6: reserved |
igorsk | 0:a44429321af8 | 40 | bit 7: not described |
igorsk | 0:a44429321af8 | 41 | |
igorsk | 0:a44429321af8 | 42 | To read a register, write its address and then read the value. The address auto-increments |
igorsk | 0:a44429321af8 | 43 | after reading, so it's not necessary to send it again if reading sequentially. The address |
igorsk | 0:a44429321af8 | 44 | is reset to 0 on power-on. |
igorsk | 0:a44429321af8 | 45 | |
igorsk | 0:a44429321af8 | 46 | Taking measurements works like following: |
igorsk | 0:a44429321af8 | 47 | 1. write 0 (control register address), then 0x01 (take measurements) |
igorsk | 0:a44429321af8 | 48 | 2. wait 5ms for the measurement to complete |
igorsk | 0:a44429321af8 | 49 | 3. write 0 again (control register address), then read the register value |
igorsk | 0:a44429321af8 | 50 | 4. check if the bit 0 is cleared. If not, repeat from 3. |
igorsk | 0:a44429321af8 | 51 | 5. read x msb |
igorsk | 0:a44429321af8 | 52 | 6. read x lsb |
igorsk | 0:a44429321af8 | 53 | 7. read y msb |
igorsk | 0:a44429321af8 | 54 | 8. read y lsb |
igorsk | 0:a44429321af8 | 55 | 9. (if MMC312xM) read z msb |
igorsk | 0:a44429321af8 | 56 | 10. (if MMC312xM) read z lsb |
igorsk | 0:a44429321af8 | 57 | |
igorsk | 0:a44429321af8 | 58 | N.B.: ADC resolution is only 12 bits, so four high bits of values will be 0 |
igorsk | 0:a44429321af8 | 59 | |
igorsk | 0:a44429321af8 | 60 | You can also skip some values and read directly the value wanted |
igorsk | 0:a44429321af8 | 61 | by sending its address before reading. |
igorsk | 0:a44429321af8 | 62 | */ |
igorsk | 0:a44429321af8 | 63 | |
igorsk | 0:a44429321af8 | 64 | // uncomment to show protocol debug tracing |
igorsk | 0:a44429321af8 | 65 | //#define DEBUG |
igorsk | 0:a44429321af8 | 66 | |
igorsk | 0:a44429321af8 | 67 | int dprintf(const char *fmt, ...) |
igorsk | 0:a44429321af8 | 68 | { |
igorsk | 0:a44429321af8 | 69 | #ifdef DEBUG |
igorsk | 0:a44429321af8 | 70 | va_list args; |
igorsk | 0:a44429321af8 | 71 | va_start (args, fmt); |
igorsk | 0:a44429321af8 | 72 | int ret = vprintf(fmt, args); |
igorsk | 0:a44429321af8 | 73 | va_end(args); |
igorsk | 0:a44429321af8 | 74 | return ret; |
igorsk | 0:a44429321af8 | 75 | #else |
igorsk | 0:a44429321af8 | 76 | return 0; |
igorsk | 0:a44429321af8 | 77 | #endif |
igorsk | 0:a44429321af8 | 78 | } |
igorsk | 0:a44429321af8 | 79 | |
igorsk | 0:a44429321af8 | 80 | enum command { |
igorsk | 0:a44429321af8 | 81 | TM = 1, // take measurements |
igorsk | 0:a44429321af8 | 82 | SET = 2, // set |
igorsk | 0:a44429321af8 | 83 | RESET = 4 // reset |
igorsk | 0:a44429321af8 | 84 | }; |
igorsk | 0:a44429321af8 | 85 | |
igorsk | 0:a44429321af8 | 86 | MMCx12xM::MMCx12xM(I2C &i2c, int address, const char *name) : Base(name), |
igorsk | 0:a44429321af8 | 87 | _I2C(&i2c), _addr(address), _own_i2c(false) |
igorsk | 0:a44429321af8 | 88 | { |
igorsk | 0:a44429321af8 | 89 | } |
igorsk | 0:a44429321af8 | 90 | |
igorsk | 0:a44429321af8 | 91 | MMCx12xM::MMCx12xM(PinName sda, PinName scl, int address, const char *name) : Base(name), |
igorsk | 0:a44429321af8 | 92 | _addr(address) |
igorsk | 0:a44429321af8 | 93 | { |
igorsk | 0:a44429321af8 | 94 | _I2C = new I2C(sda, scl); |
igorsk | 0:a44429321af8 | 95 | // we own the bus, so we can set frequency |
igorsk | 0:a44429321af8 | 96 | // MMCx12x claims to handle 400 kHz |
igorsk | 0:a44429321af8 | 97 | _I2C->frequency(400000); |
igorsk | 0:a44429321af8 | 98 | _own_i2c = true; |
igorsk | 0:a44429321af8 | 99 | } |
igorsk | 0:a44429321af8 | 100 | |
igorsk | 0:a44429321af8 | 101 | MMCx12xM::~MMCx12xM() |
igorsk | 0:a44429321af8 | 102 | { |
igorsk | 0:a44429321af8 | 103 | if (_own_i2c) |
igorsk | 0:a44429321af8 | 104 | delete _I2C; |
igorsk | 0:a44429321af8 | 105 | } |
igorsk | 0:a44429321af8 | 106 | |
igorsk | 0:a44429321af8 | 107 | // send the specified command: write it into the control register |
igorsk | 0:a44429321af8 | 108 | bool MMCx12xM::_send_command(int command) |
igorsk | 0:a44429321af8 | 109 | { |
igorsk | 0:a44429321af8 | 110 | dprintf("* send command %d\n", command); |
igorsk | 0:a44429321af8 | 111 | const char writecmd[] = {0, command}; // address: 0 (control reg) |
igorsk | 0:a44429321af8 | 112 | int res = _I2C->write(_addr, writecmd, 2); |
igorsk | 0:a44429321af8 | 113 | dprintf("write: %d\n", res); |
igorsk | 0:a44429321af8 | 114 | return res == 0; |
igorsk | 0:a44429321af8 | 115 | } |
igorsk | 0:a44429321af8 | 116 | |
igorsk | 0:a44429321af8 | 117 | // wait until the command is done |
igorsk | 0:a44429321af8 | 118 | // this is signalled by clearing of the |
igorsk | 0:a44429321af8 | 119 | // corresponding bit in the control register |
igorsk | 0:a44429321af8 | 120 | bool MMCx12xM::_wait_ready(int command) |
igorsk | 0:a44429321af8 | 121 | { |
igorsk | 0:a44429321af8 | 122 | dprintf("* wait ready %d\n", command); |
igorsk | 0:a44429321af8 | 123 | const char writecmd = 0; // set read address to 0 (control reg) |
igorsk | 0:a44429321af8 | 124 | int res = _I2C->write(_addr, &writecmd, 1); |
igorsk | 0:a44429321af8 | 125 | dprintf(" write: %d\n", res); |
igorsk | 0:a44429321af8 | 126 | if ( res != 0 ) |
igorsk | 0:a44429321af8 | 127 | return false; |
igorsk | 0:a44429321af8 | 128 | char reg; |
igorsk | 0:a44429321af8 | 129 | while ( 1 ) |
igorsk | 0:a44429321af8 | 130 | { |
igorsk | 0:a44429321af8 | 131 | // read control register value |
igorsk | 0:a44429321af8 | 132 | res = _I2C->read(_addr, ®, 1); |
igorsk | 0:a44429321af8 | 133 | dprintf(" read: %d, reg=%08X\n", res, reg); |
igorsk | 0:a44429321af8 | 134 | if ( res != 0 ) |
igorsk | 0:a44429321af8 | 135 | return false; |
igorsk | 0:a44429321af8 | 136 | // check if the command bit is cleared |
igorsk | 0:a44429321af8 | 137 | if ( (reg & command) == 0 ) |
igorsk | 0:a44429321af8 | 138 | break; // data ready |
igorsk | 0:a44429321af8 | 139 | dprintf("* Not ready, try again\n"); |
igorsk | 0:a44429321af8 | 140 | // otherwise tell the device that we want to read the register (address 0) again |
igorsk | 0:a44429321af8 | 141 | res = _I2C->write(_addr, &writecmd, 1); |
igorsk | 0:a44429321af8 | 142 | dprintf(" write: %d\n", res); |
igorsk | 0:a44429321af8 | 143 | if ( res != 0 ) |
igorsk | 0:a44429321af8 | 144 | return false; |
igorsk | 0:a44429321af8 | 145 | } |
igorsk | 0:a44429321af8 | 146 | return true; |
igorsk | 0:a44429321af8 | 147 | } |
igorsk | 0:a44429321af8 | 148 | |
igorsk | 0:a44429321af8 | 149 | // read one axis value (two bytes) |
igorsk | 0:a44429321af8 | 150 | // set read address if index specified explicitly |
igorsk | 0:a44429321af8 | 151 | bool MMCx12xM::_read_axis(int *axis, int index) |
igorsk | 0:a44429321af8 | 152 | { |
igorsk | 0:a44429321af8 | 153 | dprintf("* read axis %d\n", index); |
igorsk | 0:a44429321af8 | 154 | // accept only x, y or z (0, 1, 2) |
igorsk | 0:a44429321af8 | 155 | if ( index > 2 ) |
igorsk | 0:a44429321af8 | 156 | return false; |
igorsk | 0:a44429321af8 | 157 | int res; |
igorsk | 0:a44429321af8 | 158 | if ( index != - 1 ) |
igorsk | 0:a44429321af8 | 159 | { |
igorsk | 0:a44429321af8 | 160 | const char writecmd = index*2 + 1; // set read address for the axis value |
igorsk | 0:a44429321af8 | 161 | res = _I2C->write(_addr, &writecmd, 1); |
igorsk | 0:a44429321af8 | 162 | dprintf(" write: %d\n", res); |
igorsk | 0:a44429321af8 | 163 | if ( res != 0 ) |
igorsk | 0:a44429321af8 | 164 | return false; |
igorsk | 0:a44429321af8 | 165 | } |
igorsk | 0:a44429321af8 | 166 | uint8_t pair[2]; // msb, lsb |
igorsk | 0:a44429321af8 | 167 | res = _I2C->read(_addr, (char*)&pair, 2); |
igorsk | 0:a44429321af8 | 168 | dprintf(" read: %d, msb=%02X, lsb=%02X\n", res, pair[0], pair[1]); |
igorsk | 0:a44429321af8 | 169 | if ( res != 0 ) |
igorsk | 0:a44429321af8 | 170 | return false; |
igorsk | 0:a44429321af8 | 171 | // make an integer from msb and lsb |
igorsk | 0:a44429321af8 | 172 | *axis = (pair[0] << 8) | pair[1]; |
igorsk | 0:a44429321af8 | 173 | return true; |
igorsk | 0:a44429321af8 | 174 | } |
igorsk | 0:a44429321af8 | 175 | |
igorsk | 0:a44429321af8 | 176 | // ask chip to take measurements and |
igorsk | 0:a44429321af8 | 177 | // read specified number of raw axis values |
igorsk | 0:a44429321af8 | 178 | bool MMCx12xM::read_raw_values(int *values, int count) |
igorsk | 0:a44429321af8 | 179 | { |
igorsk | 0:a44429321af8 | 180 | dprintf("* read_raw_values\n"); |
igorsk | 0:a44429321af8 | 181 | if ( !_send_command(TM) ) |
igorsk | 0:a44429321af8 | 182 | { |
igorsk | 0:a44429321af8 | 183 | dprintf(" send_command(TM) failed\n"); |
igorsk | 0:a44429321af8 | 184 | return false; |
igorsk | 0:a44429321af8 | 185 | } |
igorsk | 0:a44429321af8 | 186 | wait_ms(5); |
igorsk | 0:a44429321af8 | 187 | if ( !_wait_ready(TM) ) |
igorsk | 0:a44429321af8 | 188 | { |
igorsk | 0:a44429321af8 | 189 | dprintf(" wait_ready(TM) failed\n"); |
igorsk | 0:a44429321af8 | 190 | return false; |
igorsk | 0:a44429321af8 | 191 | } |
igorsk | 0:a44429321af8 | 192 | // we have read the control register, so continue reading the data |
igorsk | 0:a44429321af8 | 193 | // which will be the axis values |
igorsk | 0:a44429321af8 | 194 | for ( int i=0; i < count; i++ ) |
igorsk | 0:a44429321af8 | 195 | { |
igorsk | 0:a44429321af8 | 196 | // we're reading values sequentially, so no need to set the index |
igorsk | 0:a44429321af8 | 197 | if ( !_read_axis(&values[i]) ) |
igorsk | 0:a44429321af8 | 198 | { |
igorsk | 0:a44429321af8 | 199 | dprintf(" _read_axis() failed\n"); |
igorsk | 0:a44429321af8 | 200 | return false; |
igorsk | 0:a44429321af8 | 201 | } |
igorsk | 0:a44429321af8 | 202 | } |
igorsk | 0:a44429321af8 | 203 | return true; |
igorsk | 0:a44429321af8 | 204 | } |
igorsk | 0:a44429321af8 | 205 | |
igorsk | 0:a44429321af8 | 206 | bool MMCx12xM::coil_set() |
igorsk | 0:a44429321af8 | 207 | { |
igorsk | 0:a44429321af8 | 208 | return _send_command(SET) && _wait_ready(SET); |
igorsk | 0:a44429321af8 | 209 | } |
igorsk | 0:a44429321af8 | 210 | |
igorsk | 0:a44429321af8 | 211 | bool MMCx12xM::coil_reset() |
igorsk | 0:a44429321af8 | 212 | { |
igorsk | 0:a44429321af8 | 213 | return _send_command(SET) && _wait_ready(SET); |
igorsk | 0:a44429321af8 | 214 | } |
igorsk | 0:a44429321af8 | 215 | |
igorsk | 0:a44429321af8 | 216 | void MMCx12xM::calibrate_begin() |
igorsk | 0:a44429321af8 | 217 | { |
igorsk | 0:a44429321af8 | 218 | // begin calibration: init the values arrays |
igorsk | 0:a44429321af8 | 219 | for ( int i=0; i < 3; i++ ) |
igorsk | 0:a44429321af8 | 220 | { |
igorsk | 0:a44429321af8 | 221 | _maxvals[i] = 0; |
igorsk | 0:a44429321af8 | 222 | _minvals[i] = INT_MAX; |
igorsk | 0:a44429321af8 | 223 | } |
igorsk | 0:a44429321af8 | 224 | } |
igorsk | 0:a44429321af8 | 225 | |
igorsk | 0:a44429321af8 | 226 | void MMCx12xM::calibrate_step(int count) |
igorsk | 0:a44429321af8 | 227 | { |
igorsk | 0:a44429321af8 | 228 | // take a measurement and update min-max values |
igorsk | 0:a44429321af8 | 229 | int values[3]; |
igorsk | 0:a44429321af8 | 230 | if ( read_raw_values(values, count) ) |
igorsk | 0:a44429321af8 | 231 | { |
igorsk | 0:a44429321af8 | 232 | for ( int i=0; i < count; i++ ) |
igorsk | 0:a44429321af8 | 233 | { |
igorsk | 0:a44429321af8 | 234 | if ( _maxvals[i] < values[i] ) |
igorsk | 0:a44429321af8 | 235 | _maxvals[i] = values[i]; |
igorsk | 0:a44429321af8 | 236 | if ( _minvals[i] > values[i] ) |
igorsk | 0:a44429321af8 | 237 | _minvals[i] = values[i]; |
igorsk | 0:a44429321af8 | 238 | } |
igorsk | 0:a44429321af8 | 239 | } |
igorsk | 0:a44429321af8 | 240 | } |
igorsk | 0:a44429321af8 | 241 | |
igorsk | 0:a44429321af8 | 242 | void MMCx12xM::calibrate_end() |
igorsk | 0:a44429321af8 | 243 | { |
igorsk | 0:a44429321af8 | 244 | // calculate sensitivity and offset for each axis |
igorsk | 0:a44429321af8 | 245 | // see Memsic app note AN-00MM-003 |
igorsk | 0:a44429321af8 | 246 | dprintf("* calibration end\n"); |
igorsk | 0:a44429321af8 | 247 | for ( int i=0; i < 3; i++ ) |
igorsk | 0:a44429321af8 | 248 | { |
igorsk | 0:a44429321af8 | 249 | _sensitivity[i] = (_maxvals[i] - _minvals[i]) / 2; |
igorsk | 0:a44429321af8 | 250 | _offset[i] = (_maxvals[i] + _minvals[i]) / 2; |
igorsk | 0:a44429321af8 | 251 | dprintf(" %i: min = %d, max = %d, s = %d, o = %d\n", i, _minvals[i], _maxvals[i], _sensitivity[i], _offset[i]); |
igorsk | 0:a44429321af8 | 252 | } |
igorsk | 0:a44429321af8 | 253 | } |
igorsk | 0:a44429321af8 | 254 | |
igorsk | 0:a44429321af8 | 255 | bool MMCx12xM::read_values(float *values, int count) |
igorsk | 0:a44429321af8 | 256 | { |
igorsk | 0:a44429321af8 | 257 | int rvalues[3]; |
igorsk | 0:a44429321af8 | 258 | if ( read_raw_values(rvalues, count) ) |
igorsk | 0:a44429321af8 | 259 | { |
igorsk | 0:a44429321af8 | 260 | // transform into calibrated values in the range -1.0 .. +1.0 |
igorsk | 0:a44429321af8 | 261 | for ( int i=0; i < count; i++ ) |
igorsk | 0:a44429321af8 | 262 | { |
igorsk | 0:a44429321af8 | 263 | // NB: we use a temp float so that the division is not integer |
igorsk | 0:a44429321af8 | 264 | float d = (rvalues[i] - _offset[i]); |
igorsk | 0:a44429321af8 | 265 | values[i] = d / _sensitivity[i]; |
igorsk | 0:a44429321af8 | 266 | } |
igorsk | 0:a44429321af8 | 267 | return true; |
igorsk | 0:a44429321af8 | 268 | } |
igorsk | 0:a44429321af8 | 269 | return false; |
igorsk | 0:a44429321af8 | 270 | } |