From: kremlin Date: Mon, 16 Feb 2015 07:55:08 +0000 (-0600) Subject: added lsm303d library for arduino due X-Git-Url: https://uglyman.kremlin.cc/gitweb/gitweb.cgi?p=grab_bag.git;a=commitdiff_plain;h=e4a4f539714b6514e03d7c706a67228b981042a1 added lsm303d library for arduino due --- diff --git a/lsm303d_arm_arduino.c b/lsm303d_arm_arduino.c new file mode 100644 index 0000000..decd6ad --- /dev/null +++ b/lsm303d_arm_arduino.c @@ -0,0 +1,969 @@ +/* Copyright (c) 2015, Ian Sutton + + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + + /* this is a library for the arduino due that controls the LSM303D accelerometer + * & magnometer. it will not work on other arduino models, it uses API calls + * exclusive to newer ARM-based arduinos. + * + * steps to recreate: + * + * !! be especially careful of the 5V output in on the due's SPI header. it is a high !! + * !! current output & will cook anything nearby. refer to this diagram before making !! + * !! any connections: http://uglyman.kremlin.cc/quick/due-pinout-web.png !! + * + * - wire SPI ports on the LSM to their respective pins on the due's SPI header (not + * ICSP!) + * + * - wire INT2 to digital pin 53 and the LSM's chip select/slave select port to + * pin 10 above the pwm module. + * + * - ground the SPI module, arduino, and LSM together, preferably alone. i had to use + * an audio isolation amplifier to provide a quiet enough ground to support the + * unusually high SPI baud rate i use here, discussed later. + * + * - wire LSM's Vin to the arduino's 3.3V supply. leave Vdd floating */ + +#include + +/* slave select pin */ +#define LSM_CS 10 + +/* wire to INT2 which latches to a magnometer-read-ready signal */ +#define MAGNO_RDY_PIN 53 + +/* SPI clock divider (84MHz divided by this equals SPI frequency) */ +#define SPI_CLK_DIV 3 + +/* frequency of temperature sensor in Hz, set in lsm_config() */ +#define TEMP_FREQ 100 + +/* struct representing magnometer values at shared instant of time */ +struct magno_point { + + signed short x; + signed short y; + signed short z; +}; + +/* hands off */ +const byte BAD_REGS[8] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x0E, 0x10, 0x11 }; + +/* these are set on successful completion of their respective funcs */ +bool SERIAL_CONFIGURED = 0; +bool SPI_CONFIGURED = 0; +bool SPI_OK = 0; +bool LSM_CONFIGURED = 0; + +/* don't brick the LSM */ +bool matches_badreg(byte addr) { + + short i; + i = 0; + + for (; i < 8; i++) + if (addr == BAD_REGS[i]) + return true; + + return false; +} + +/* fired when critical exception encountered, doesn't return */ +void killspin(String msg) { + + Serial.println(msg); + Serial.println("exception occurred or assertion failed. no-operation until reset"); + while (1); +} + +/* reads n sequential bytes starting @ addr returns byte array of size n + * returned array must be free()'d manually! */ +byte *spi_multiread(byte addr, short n) { + + byte *ret, first; + short cnt; + + if (matches_badreg(addr)) + killspin("tried to read from a reserved, internal register!"); + + if (!SPI_CONFIGURED || !SPI_OK) + killspin("tried to read over SPI before it was configured or tested!"); + + /* addresses are 6 bits wide */ + if (addr > 63) + killspin("invalid lsm register address"); + + /* set msb to read, 2nd msb for multibyte op, and append 6 bit address field */ + first = 128 + 64 + addr; + cnt = 0; + + /* free this! */ + ret = (byte *) calloc(n, 1); + + /* kickoff read operation */ + SPI.transfer(LSM_CS, first, SPI_CONTINUE); + + for (; cnt < n - 1; cnt++) + ret[cnt] = SPI.transfer(LSM_CS, first, SPI_CONTINUE); + + ret[n - 1] = SPI.transfer(LSM_CS, first, SPI_LAST); + + return ret; +} + +/* reads & returns 1 byte @ addr */ +byte spi_read(byte addr) { + + byte first, ret; + + if (matches_badreg(addr)) + killspin("tried to read from a reserved, internal register!"); + + if (!SPI_CONFIGURED || !SPI_OK) + killspin("tried to read over SPI before it was configured or tested!"); + + /* addresses are 6 bits wide */ + if (addr > 63) + killspin("invalid lsm register address"); + + /* set msb to read and append 6 bit address field */ + first = 128 + addr; + + /* perform 16 bit SPI exchange as per LSM datasheet's specification */ + SPI.transfer(LSM_CS, first, SPI_CONTINUE); + ret = SPI.transfer(LSM_CS, 0x00, SPI_LAST); + + return ret; +} + +/* write single byte @ addr */ +void spi_write(byte addr, byte data) { + + byte first; + + if (matches_badreg(addr)) + killspin("tried to write to a reserved, internal register!"); + + if (!SPI_CONFIGURED || !SPI_OK) + killspin("tried to write over SPI before it was configured or tested!"); + + /* addresses are 6 bits wide */ + if (addr > 63) + killspin("INVALID ADDRESS"); + + /* set 6 bit address field */ + first = addr; + + /* perform 16 bit SPI exchange as per LSM datasheet's specification */ + SPI.transfer(LSM_CS, first, SPI_CONTINUE); + SPI.transfer(LSM_CS, data, SPI_LAST); +} + +void spi_config() { + + SPI.begin(LSM_CS); + + /* my arduino due has a 84 MHz cpu which is divided here to provide the + * SPI baud rate. the LSM's data sheet purports the maximum SPI frequency + * is 10MHz, however i've found that it works fine up to 28MHz, which is + * the frequency set below (84 MHz / 3 = 28 MHz) */ + SPI.setClockDivider(LSM_CS, SPI_CLK_DIV); + + /* LSM is big endian */ + SPI.setBitOrder(LSM_CS, MSBFIRST); + + /* clock is active-low, exchange occurs on clock's first falling edge + * CPOL = 1, CKE = 0 */ + SPI.setDataMode(LSM_CS, SPI_MODE3); + + SPI_CONFIGURED = 1; +} + +/* read & check immutable device ID reg a number of times to guarantee LSM + * slave is responding and capable of handling master's SPI clock freq. + * then, write reg & read back to test writing */ +boolean spi_test() { + + bool read_ok, write_ok; + byte i; + + i = 0; + read_ok = true; + write_ok = true; + + if (!SPI_CONFIGURED) + killspin("tried to test SPI before it was configured!"); + + /* cheat a little here */ + SPI_OK = 1; + + /* test reading */ + Serial.print(" [READ: "); + + for (; i < 100; i++) + if (spi_read(0x0F) != 0x49) + read_ok = false; + + if (read_ok) + Serial.print("OK, WRITE: "); + else + Serial.print("FAIL, WRITE: "); + + /* test writing with */ + i = 0; + for (; i < 100; i++) { + + spi_write(0x17, i); + + /* uncomment for write debugging + Serial.print("WROTE "); + Serial.print(i); + Serial.print(" GOT "); + Serial.println(spi_read(0x17));*/ + + if (spi_read(0x17) != i) + write_ok = false; + } + + /* write back datasheet-defined default value to test + * register we used (OFFSET_X_L_M) */ + if (write_ok) + spi_write(0x17, 0x00); + + /* finish up */ + if (write_ok) + Serial.print("OK] "); + else + Serial.print("FAIL] "); + + if (read_ok && write_ok) { + + SPI_OK = 1; + return true; + + } else { + + SPI_OK = 0; + Serial.println(":: failed!"); + return false; + } +} + +void lsm_config() { + + if (!SPI_CONFIGURED || !SPI_OK) + killspin("tried to configure lsm before spi was configured & tested"); + + /* set 16 bit 2's comp. magnetic field offset values for x, y, z. + * these default to zero as correct offset values depend on your geographical + * location. you can find the current magnetic field strength at your coords + * using NOAA's database: http://www.ngdc.noaa.gov/geomag-web/#igrfwmm + * + * in the WMM model, the significant values are north comp (z offset), east comp + * (x offset), and vertical comp (y offset). you can ignore the 'change/year' and + * 'uncertainty' values + * + * you must translate the given tesla values into corresponding gauss equivalents + * and scale them according to the range specified later in this function. you will + * usually use the +2:-2 gauss scale. here is an example conversion for + * latitude 43° 2' 14" N, longitude 76° 7' 36" W (near syracuse university in + * syracuse, NY 13210), 0 meters above sea level, taken on february 16th, 2015 at 07:32 UTC: + * + * [x] :: -4,129.9 nanoteslas :: -0.041299 gauss + * [y] :: 49,817.8 nanoteslas :: 0.498178 gauss + * [z] :: 18,755.9 nanoteslas :: 0.187559 gauss + * + * next, we need to fit these values into our 4-gauss scale (spanning from +2 gauss + * to -2 gauss) in the context of a 16-bit signed number. to do this, take the gauss value + * and divide it by 2. take this number, and multiply it by 2^15 - 1. round to closest integer. + * finally, multiply this value by -1 as the offset value combined with the sensed value should + * result in zero. negatives should be expressed as two's complement. here are the offsets derived + * from the previous values: + * + * [x] :: 677 :: 0x02A5 + * [y] :: -16,324 :: 0xC03C + * [z] :: -6,146 :: 0xE7FE + */ + const byte x_lo_offset = 0xA5; + const byte x_hi_offset = 0x02; + + const byte y_lo_offset = 0x3C; + const byte y_hi_offset = 0xC0; + + const byte z_lo_offset = 0xFE; + const byte z_hi_offset = 0xE7; + + spi_write(0x16, x_lo_offset); + spi_write(0x17, x_hi_offset); + + spi_write(0x18, y_lo_offset); + spi_write(0x19, y_hi_offset); + + spi_write(0x1A, z_lo_offset); + spi_write(0x1B, z_hi_offset); + + /* latch magnometer-ready to INT2 output pin */ + spi_write(0x23, 0x04); + + /* enable temperature sensor, + * select high magnetic resolution, + * select 100Hz sensor rate */ + spi_write(0x24, 0xF4); + + /* set full scale of magnetometer to +2:-2 gauss as + * earth's field is usually between +0.65:-0.65 G */ + spi_write(0x25, 0x00); + + /* switch on magnometer, to continuous conversion mode */ + spi_write(0x26, 0x00); + + LSM_CONFIGURED = 1; +} + +/* returns a size-n array of readings from temperature sensor. function waits + * between reads for a time equal to the period length of the sensor as to avoid + * multiple reads of an un-updated value. this is a hack to get around the fact + * this chip does not have a TEMP_READY bit in a status register like the magnometer + * or accelerometer do. + * caller must free() returned pointer */ +signed short *pull_temp_values(int n) { + + signed short *ret; + int sensor_period, i; + byte *temp_pair, *sync_pair_i, *sync_pair_f; + + /* period length in milliseconds of temperature sensor refresh */ + sensor_period = 1000 / TEMP_FREQ; + + i = 0; + ret = (signed short *) calloc(n, 2); + + sync_pair_i = sync_pair_f = spi_multiread(0x05, 2); + + /* spin until sensor cranks */ + while (sync_pair_i == sync_pair_f) + sync_pair_f = spi_multiread(0x05, 2); + + /* wait until mid-period to read as to avoid edge-case duplicates */ + delay(sensor_period / 2); + + for (; i < n; i++) { + temp_pair = spi_multiread(0x05, 2); + ret[i] = word(temp_pair[1], temp_pair[0]); + free(temp_pair); + delay(sensor_period); + } + + free(sync_pair_i); + free(sync_pair_f); + + return ret; +} + +/* returns a magno_point struct from passed 48 bit input taken from magno sensors */ +struct magno_point parse_raw_magno_data(byte *in) { + + struct magno_point ret; + + ret.x = word(in[1], in[0]); + ret.y = word(in[3], in[2]); + ret.z = word(in[5], in[4]); + + return ret; +} + +/* returns a size-n array of readings from magnometer. result contains 3 words + * describing felt magnetic field strengths x, y, z directions. function polls INT2 + * (latched to magnometer-ready signal) until it goes high before reading from sensor. + * this guarantees each member in returned array is a genuine, non-repeat value from a + * single magnometer sensor cycle + * caller must free() returned pointer */ +struct magno_point *pull_magno_values(int n) { + + struct magno_point *ret; + int i; + + i = 0; + ret = (struct magno_point *) calloc(n, sizeof(struct magno_point)); + + for(; i < n; i++) { + + /* spin until sensors are fresh */ + while(digitalRead(MAGNO_RDY_PIN) != 1); + + ret[i] = parse_raw_magno_data(spi_multiread(0x08, 6)); + } + + return ret; +} + +void setup() { + + /* set up magno. read ready signal */ + pinMode(MAGNO_RDY_PIN, INPUT); + + /* this is about as fast as you can get on the serial console */ + Serial.begin(115200); + SERIAL_CONFIGURED = 1; + + /* spi init */ + Serial.print("configuring spi.."); + spi_config(); + Serial.println("done"); + + /* spi check */ + Serial.print("testing spi.."); + if (spi_test()) + Serial.println("done"); + else + killspin("SPI test failed. perhaps the device is not wired correctly or your frequency is too high"); + + /* lsm init */ + Serial.print("configuring lsm.."); + lsm_config(); + Serial.println("done"); +} + +/* demonstration of realtime temperature stream. does not return */ +void stream_temp() { + + for(;;) { + signed short *temp_vals; + temp_vals = pull_temp_values(100); + + for (int i = 0; i < 100; i++) { + if(!(i % 20) && i) + Serial.print('\n'); + else if(i) + Serial.print(" "); + + Serial.print(temp_vals[i], DEC); + } + + Serial.println("\n--------------------------------------------------------------------------------"); + + free(temp_vals); + } +} + +/* demonstration of realtime magnometer stream. does not return */ +void stream_magno() { + + struct magno_point *read_buf; + + read_buf = pull_magno_values(100); + + for(int i = 0; i < 100; i++) { + + Serial.print("X: "); + Serial.println(read_buf[i].x, DEC); + Serial.print("Y: "); + Serial.println(read_buf[i].y, DEC); + Serial.print("Z: "); + Serial.println(read_buf[i].z, DEC); + Serial.println("---------"); + } + + free(read_buf); +} + +void loop() { + + stream_temp(); + //stream_magno(); +} +/* Copyright (c) 2015, Ian Sutton + + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + + /* this is a library for the arduino due that controls the LSM303D accelerometer + * & magnometer. it will not work on other arduino models, it uses API calls + * exclusive to newer ARM-based arduinos. + * + * steps to recreate: + * + * !! be especially careful of the 5V output in on the due's SPI header. it is a high !! + * !! current output & will cook anything nearby. refer to this diagram before making !! + * !! any connections: http://uglyman.kremlin.cc/quick/due-pinout-web.png !! + * + * - wire SPI ports on the LSM to their respective pins on the due's SPI header (not + * ICSP!) + * + * - wire INT2 to digital pin 53 and the LSM's chip select/slave select port to + * pin 10 above the pwm module. + * + * - ground the SPI module, arduino, and LSM together, preferably alone. i had to use + * an audio isolation amplifier to provide a quiet enough ground to support the + * unusually high SPI baud rate i use here, discussed later. + * + * - wire LSM's Vin to the arduino's 3.3V supply. leave Vdd floating */ + +#include + +/* slave select pin */ +#define LSM_CS 10 + +/* wire to INT2 which latches to a magnometer-read-ready signal */ +#define MAGNO_RDY_PIN 53 + +/* SPI clock divider (84MHz divided by this equals SPI frequency) */ +#define SPI_CLK_DIV 3 + +/* frequency of temperature sensor in Hz, set in lsm_config() */ +#define TEMP_FREQ 100 + +/* struct representing magnometer values at shared instant of time */ +struct magno_point { + + signed short x; + signed short y; + signed short z; +}; + +/* hands off */ +const byte BAD_REGS[8] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x0E, 0x10, 0x11 }; + +/* these are set on successful completion of their respective funcs */ +bool SERIAL_CONFIGURED = 0; +bool SPI_CONFIGURED = 0; +bool SPI_OK = 0; +bool LSM_CONFIGURED = 0; + +/* don't brick the LSM */ +bool matches_badreg(byte addr) { + + short i; + i = 0; + + for (; i < 8; i++) + if (addr == BAD_REGS[i]) + return true; + + return false; +} + +/* fired when critical exception encountered, doesn't return */ +void killspin(String msg) { + + Serial.println(msg); + Serial.println("exception occurred or assertion failed. no-operation until reset"); + while (1); +} + +/* reads n sequential bytes starting @ addr returns byte array of size n + * returned array must be free()'d manually! */ +byte *spi_multiread(byte addr, short n) { + + byte *ret, first; + short cnt; + + if (matches_badreg(addr)) + killspin("tried to read from a reserved, internal register!"); + + if (!SPI_CONFIGURED || !SPI_OK) + killspin("tried to read over SPI before it was configured or tested!"); + + /* addresses are 6 bits wide */ + if (addr > 63) + killspin("invalid lsm register address"); + + /* set msb to read, 2nd msb for multibyte op, and append 6 bit address field */ + first = 128 + 64 + addr; + cnt = 0; + + /* free this! */ + ret = (byte *) calloc(n, 1); + + /* kickoff read operation */ + SPI.transfer(LSM_CS, first, SPI_CONTINUE); + + for (; cnt < n - 1; cnt++) + ret[cnt] = SPI.transfer(LSM_CS, first, SPI_CONTINUE); + + ret[n - 1] = SPI.transfer(LSM_CS, first, SPI_LAST); + + return ret; +} + +/* reads & returns 1 byte @ addr */ +byte spi_read(byte addr) { + + byte first, ret; + + if (matches_badreg(addr)) + killspin("tried to read from a reserved, internal register!"); + + if (!SPI_CONFIGURED || !SPI_OK) + killspin("tried to read over SPI before it was configured or tested!"); + + /* addresses are 6 bits wide */ + if (addr > 63) + killspin("invalid lsm register address"); + + /* set msb to read and append 6 bit address field */ + first = 128 + addr; + + /* perform 16 bit SPI exchange as per LSM datasheet's specification */ + SPI.transfer(LSM_CS, first, SPI_CONTINUE); + ret = SPI.transfer(LSM_CS, 0x00, SPI_LAST); + + return ret; +} + +/* write single byte @ addr */ +void spi_write(byte addr, byte data) { + + byte first; + + if (matches_badreg(addr)) + killspin("tried to write to a reserved, internal register!"); + + if (!SPI_CONFIGURED || !SPI_OK) + killspin("tried to write over SPI before it was configured or tested!"); + + /* addresses are 6 bits wide */ + if (addr > 63) + killspin("INVALID ADDRESS"); + + /* set 6 bit address field */ + first = addr; + + /* perform 16 bit SPI exchange as per LSM datasheet's specification */ + SPI.transfer(LSM_CS, first, SPI_CONTINUE); + SPI.transfer(LSM_CS, data, SPI_LAST); +} + +void spi_config() { + + SPI.begin(LSM_CS); + + /* my arduino due has a 84 MHz cpu which is divided here to provide the + * SPI baud rate. the LSM's data sheet purports the maximum SPI frequency + * is 10MHz, however i've found that it works fine up to 28MHz, which is + * the frequency set below (84 MHz / 3 = 28 MHz) */ + SPI.setClockDivider(LSM_CS, SPI_CLK_DIV); + + /* LSM is big endian */ + SPI.setBitOrder(LSM_CS, MSBFIRST); + + /* clock is active-low, exchange occurs on clock's first falling edge + * CPOL = 1, CKE = 0 */ + SPI.setDataMode(LSM_CS, SPI_MODE3); + + SPI_CONFIGURED = 1; +} + +/* read & check immutable device ID reg a number of times to guarantee LSM + * slave is responding and capable of handling master's SPI clock freq. + * then, write reg & read back to test writing */ +boolean spi_test() { + + bool read_ok, write_ok; + byte i; + + i = 0; + read_ok = true; + write_ok = true; + + if (!SPI_CONFIGURED) + killspin("tried to test SPI before it was configured!"); + + /* cheat a little here */ + SPI_OK = 1; + + /* test reading */ + Serial.print(" [READ: "); + + for (; i < 100; i++) + if (spi_read(0x0F) != 0x49) + read_ok = false; + + if (read_ok) + Serial.print("OK, WRITE: "); + else + Serial.print("FAIL, WRITE: "); + + /* test writing with */ + i = 0; + for (; i < 100; i++) { + + spi_write(0x17, i); + + /* uncomment for write debugging + Serial.print("WROTE "); + Serial.print(i); + Serial.print(" GOT "); + Serial.println(spi_read(0x17));*/ + + if (spi_read(0x17) != i) + write_ok = false; + } + + /* write back datasheet-defined default value to test + * register we used (OFFSET_X_L_M) */ + if (write_ok) + spi_write(0x17, 0x00); + + /* finish up */ + if (write_ok) + Serial.print("OK] "); + else + Serial.print("FAIL] "); + + if (read_ok && write_ok) { + + SPI_OK = 1; + return true; + + } else { + + SPI_OK = 0; + Serial.println(":: failed!"); + return false; + } +} + +void lsm_config() { + + if (!SPI_CONFIGURED || !SPI_OK) + killspin("tried to configure lsm before spi was configured & tested"); + + /* set 16 bit 2's comp. magnetic field offset values for x, y, z. + * these default to zero as correct offset values depend on your geographical + * location. you can find the current magnetic field strength at your coords + * using NOAA's database: http://www.ngdc.noaa.gov/geomag-web/#igrfwmm + * + * in the WMM model, the significant values are north comp (z offset), east comp + * (x offset), and vertical comp (y offset). you can ignore the 'change/year' and + * 'uncertainty' values + * + * you must translate the given tesla values into corresponding gauss equivalents + * and scale them according to the range specified later in this function. you will + * usually use the +2:-2 gauss scale. here is an example conversion for + * latitude 43° 2' 14" N, longitude 76° 7' 36" W (near syracuse university in + * syracuse, NY 13210), 0 meters above sea level, taken on february 16th, 2015 at 07:32 UTC: + * + * [x] :: -4,129.9 nanoteslas :: -0.041299 gauss + * [y] :: 49,817.8 nanoteslas :: 0.498178 gauss + * [z] :: 18,755.9 nanoteslas :: 0.187559 gauss + * + * next, we need to fit these values into our 4-gauss scale (spanning from +2 gauss + * to -2 gauss) in the context of a 16-bit signed number. to do this, take the gauss value + * and divide it by 2. take this number, and multiply it by 2^15 - 1. round to closest integer. + * finally, multiply this value by -1 as the offset value combined with the sensed value should + * result in zero. negatives should be expressed as two's complement. here are the offsets derived + * from the previous values: + * + * [x] :: 677 :: 0x02A5 + * [y] :: -16,324 :: 0xC03C + * [z] :: -6,146 :: 0xE7FE + */ + const byte x_lo_offset = 0xA5; + const byte x_hi_offset = 0x02; + + const byte y_lo_offset = 0x3C; + const byte y_hi_offset = 0xC0; + + const byte z_lo_offset = 0xFE; + const byte z_hi_offset = 0xE7; + + spi_write(0x16, x_lo_offset); + spi_write(0x17, x_hi_offset); + + spi_write(0x18, y_lo_offset); + spi_write(0x19, y_hi_offset); + + spi_write(0x1A, z_lo_offset); + spi_write(0x1B, z_hi_offset); + + /* latch magnometer-ready to INT2 output pin */ + spi_write(0x23, 0x04); + + /* enable temperature sensor, + * select high magnetic resolution, + * select 100Hz sensor rate */ + spi_write(0x24, 0xF4); + + /* set full scale of magnetometer to +2:-2 gauss as + * earth's field is usually between +0.65:-0.65 G */ + spi_write(0x25, 0x00); + + /* switch on magnometer, to continuous conversion mode */ + spi_write(0x26, 0x00); + + LSM_CONFIGURED = 1; +} + +/* returns a size-n array of readings from temperature sensor. function waits + * between reads for a time equal to the period length of the sensor as to avoid + * multiple reads of an un-updated value. this is a hack to get around the fact + * this chip does not have a TEMP_READY bit in a status register like the magnometer + * or accelerometer do. + * caller must free() returned pointer */ +signed short *pull_temp_values(int n) { + + signed short *ret; + int sensor_period, i; + byte *temp_pair, *sync_pair_i, *sync_pair_f; + + /* period length in milliseconds of temperature sensor refresh */ + sensor_period = 1000 / TEMP_FREQ; + + i = 0; + ret = (signed short *) calloc(n, 2); + + sync_pair_i = sync_pair_f = spi_multiread(0x05, 2); + + /* spin until sensor cranks */ + while (sync_pair_i == sync_pair_f) + sync_pair_f = spi_multiread(0x05, 2); + + /* wait until mid-period to read as to avoid edge-case duplicates */ + delay(sensor_period / 2); + + for (; i < n; i++) { + temp_pair = spi_multiread(0x05, 2); + ret[i] = word(temp_pair[1], temp_pair[0]); + free(temp_pair); + delay(sensor_period); + } + + free(sync_pair_i); + free(sync_pair_f); + + return ret; +} + +/* returns a magno_point struct from passed 48 bit input taken from magno sensors */ +struct magno_point parse_raw_magno_data(byte *in) { + + struct magno_point ret; + + ret.x = word(in[1], in[0]); + ret.y = word(in[3], in[2]); + ret.z = word(in[5], in[4]); + + return ret; +} + +/* returns a size-n array of readings from magnometer. result contains 3 words + * describing felt magnetic field strengths x, y, z directions. function polls INT2 + * (latched to magnometer-ready signal) until it goes high before reading from sensor. + * this guarantees each member in returned array is a genuine, non-repeat value from a + * single magnometer sensor cycle + * caller must free() returned pointer */ +struct magno_point *pull_magno_values(int n) { + + struct magno_point *ret; + int i; + + i = 0; + ret = (struct magno_point *) calloc(n, sizeof(struct magno_point)); + + for(; i < n; i++) { + + /* spin until sensors are fresh */ + while(digitalRead(MAGNO_RDY_PIN) != 1); + + ret[i] = parse_raw_magno_data(spi_multiread(0x08, 6)); + } + + return ret; +} + +void setup() { + + /* set up magno. read ready signal */ + pinMode(MAGNO_RDY_PIN, INPUT); + + /* this is about as fast as you can get on the serial console */ + Serial.begin(115200); + SERIAL_CONFIGURED = 1; + + /* spi init */ + Serial.print("configuring spi.."); + spi_config(); + Serial.println("done"); + + /* spi check */ + Serial.print("testing spi.."); + if (spi_test()) + Serial.println("done"); + else + killspin("SPI test failed. perhaps the device is not wired correctly or your frequency is too high"); + + /* lsm init */ + Serial.print("configuring lsm.."); + lsm_config(); + Serial.println("done"); +} + +/* demonstration of realtime temperature stream. does not return */ +void stream_temp() { + + for(;;) { + signed short *temp_vals; + temp_vals = pull_temp_values(100); + + for (int i = 0; i < 100; i++) { + if(!(i % 20) && i) + Serial.print('\n'); + else if(i) + Serial.print(" "); + + Serial.print(temp_vals[i], DEC); + } + + Serial.println("\n--------------------------------------------------------------------------------"); + + free(temp_vals); + } +} + +/* demonstration of realtime magnometer stream. does not return */ +void stream_magno() { + + struct magno_point *read_buf; + + read_buf = pull_magno_values(100); + + for(int i = 0; i < 100; i++) { + + Serial.print("X: "); + Serial.println(read_buf[i].x, DEC); + Serial.print("Y: "); + Serial.println(read_buf[i].y, DEC); + Serial.print("Z: "); + Serial.println(read_buf[i].z, DEC); + Serial.println("---------"); + } + + free(read_buf); +} + +void loop() { + + stream_temp(); + //stream_magno(); +} +