added lsm303d library for arduino due
authorkremlin <kremlin@uglyman.kremlin.cc>
Mon, 16 Feb 2015 07:55:08 +0000 (01:55 -0600)
committerkremlin <kremlin@uglyman.kremlin.cc>
Mon, 16 Feb 2015 07:55:08 +0000 (01:55 -0600)
lsm303d_arm_arduino.c [new file with mode: 0644]

diff --git a/lsm303d_arm_arduino.c b/lsm303d_arm_arduino.c
new file mode 100644 (file)
index 0000000..decd6ad
--- /dev/null
@@ -0,0 +1,969 @@
+/* Copyright (c) 2015, Ian Sutton <ian@kremlin.cc>
+
+ * 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 <SPI.h>
+
+/* 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 <ian@kremlin.cc>
+
+ * 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 <SPI.h>
+
+/* 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();
+}
+