Trouble communicating with Analog Devices ADIS16355 IMU over SPI with Teensy 3.0

I am trying to make a simple sketch that just reads data from the ADIS16355 IMU. I am using the new Teensy 3.0. I am using USB for power. The IMU is using the same 5V power, and all the other connections to it should be 3.3V since that’s the operating voltage of the Teensy 3.0.

These are all the connections:

Teensy 3.0 pin 10 (CS0) to ADIS16355 pin 6 (CS)
Teensy 3.0 pin 11 (DOUT) to ADIS16355 pin 15 (DIN)
Teensy 3.0 pin 12 (DIN) to ADIS16355 pin 4 (DOUT)
Teensy 3.0 pin 13 (SCK) to ADIS16355 pin 3 (SCK)
Teensy 3.0 pin 25 (AGND) to ADIS16355 pin 13 (GND)
Teensy 3.0 pin 26 (Vin) to ADIS16355 pin 11 (VCC)

The loop function of my sketch just prints the XACCL_OUT value every 5 ms. Unfortunately the output is just:

Z Accel (g): 0.00
Z Accel (g): 0.00
…ad infinitum

Here is the code that handles the SPI stuff: (EDIT: Replaced with working code for posterity)

// Copyright 2012 Jacob Niehus
// jacob.niehus@gmail.com
// Do not distribute without permission.

#include "ADIS16355_Interface.h"
#include <Streaming.h>

// Pin definitions
const int SSpin = 10;

// SPI address definitions (upper; // lower)
const uint8_t SUPPLY_OUT = 0x03; // 0x02
const uint8_t XGYRO_OUT  = 0x05; // 0x04
const uint8_t YGYRO_OUT  = 0x07; // 0x06
const uint8_t ZGYRO_OUT  = 0x09; // 0x08
const uint8_t XACCL_OUT  = 0x0B; // 0x0A
const uint8_t YACCL_OUT  = 0x0D; // 0x0C
const uint8_t ZACCL_OUT  = 0x0F; // 0x0E
const uint8_t XTEMP_OUT  = 0x11; // 0x10
const uint8_t YTEMP_OUT  = 0x13; // 0x12
const uint8_t ZTEMP_OUT  = 0x15; // 0x14
const uint8_t AUX_ADC    = 0x17; // 0x16

// LSB definitions
const float SUPPLY_LSB  = 0.0018315;  // V per LSB
const float GYRO_LSB    = 0.07326;    // deg/sec per LSB (300 deg/sec dynamic range)
const float ACCL_LSB    = 0.002522;   // g per LSB
const float AUX_ADC_LSB = 0.0006105;  // V per LSB

// Other constants
const int T_DATA_STALL_MICROSEC = 15;

void ADIS16355::Initialize()
{
  pinMode(SSpin, OUTPUT);
  Deselect();
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
  SPI.setClockDivider(SPI_CLOCK_DIV32);
  SPI.setDataMode(SPI_MODE3);
}

float ADIS16355::getXaccel()
{
  uint16_t data = readRegister(XACCL_OUT);
  int16_t dataLSBs = twosComplementToInt(data, 14);
  return dataLSBs * ACCL_LSB;
}

float ADIS16355::getYaccel()
{
  uint16_t data = readRegister(YACCL_OUT);
  int16_t dataLSBs = twosComplementToInt(data, 14);
  return dataLSBs * ACCL_LSB;
}

float ADIS16355::getZaccel()
{
  uint16_t data = readRegister(ZACCL_OUT);
  int16_t dataLSBs = twosComplementToInt(data, 14);
  return dataLSBs * ACCL_LSB;
}

uint16_t ADIS16355::readRegister(uint8_t address)
{
  uint8_t upper, lower;

  // First data frame sends address in bits 8-13
  Select();
  SPI.transfer(address);
  SPI.transfer(0);
  Deselect();

  // Second data frame receives upper then lower bytes of data
  Select();
  upper = SPI.transfer(0);
  lower = SPI.transfer(0);
  Deselect();

  if ((upper & (1 << 6)) == 1)
  {
    Serial << "WARNING: Error/alarm accessing address " << address << endl;
  }
  if ((upper & (1 << 7)) == 1)
  {
    Serial << "WARNING: Using old data from address " << address << endl;
  }

  // Combine upper and lower bytes to form output
  return (upper << 8) | lower;
}

void ADIS16355::writeRegister(uint8_t address, uint8_t value)
{
  // Turn on left-most bit to give a write command
  uint8_t command = address | 1 << 7;

  Select();
  // Transfer R/W command and register first,
  // followed by the value to be written
  SPI.transfer(command);
  SPI.transfer(value);
  Deselect();
}

int16_t ADIS16355::twosComplementToInt(uint16_t data, uint8_t bits)
{
  if ((data & (1 << (bits - 1))) == 0)
  {
    for (uint8_t iter = bits; iter < 16; iter++)
    {
      data &= ~(1 << iter);
    }
    return (int16_t)data;
  }
  else
  {
    data = ~data + 1;
    for (uint8_t iter = bits; iter < 16; iter++)
    {
      data &= ~(1 << iter);
    }
    return -(int16_t)data;
  }
}

void ADIS16355::Select()
{
  digitalWrite(SSpin, LOW);
}

void ADIS16355::Deselect()
{
  digitalWrite(SSpin, HIGH);
  delayMicroseconds(T_DATA_STALL_MICROSEC);
}

You can download the full sketch here: http://dropcanvas.com/4ujy7

Datasheet for the IMU is here: http://www.analog.com/static/imported-files/data_sheets/ADIS16350_16355.pdf

The SCK pin is the pin connected to the LED pin built into the Teensy 3.0. The LED doesn’t seem to light up at all, and it seems like it should appear to be at half-brightness since it turns on and off quickly. I don’t have an oscilloscope or logic analyzer, though this may be what finally forces me to get one.

I tried putting some print statements in the readAddress function to print the values stored in “upper” and “lower” and they are always zero.

Can anyone take a look at the code and let me know if I’m doing something obviously wrong?

Made some progress, I think... I moved SPI.begin() to before the SPI.setBitOrder(), SPI.setClockDivider(), and SPI.setDataMode() commands. Now the LED does light up and I get different output, though still not correct:

Z Accel (g): 0.64
Z Accel (g): 0.64
Z Accel (g): 0.48
Z Accel (g): 0.48

It seems to only give one of those two values at a time depending on how I tilt the IMU. Better than nothing...

Now I put a delay(1) between the two data frames in the readAddress function, and it’s giving me the correct data. I assume that delay doesn’t need to be nearly so long, but I can’t find a minimum value for it in the datasheet. How do I know how long that delay needs to be?

The code is working exactly as intended after I put 1 millisecond delays between data frames, and the link I provided in the OP has the corrected code. I do need to find out how small to make that delay, but other than that, I have to say I'm rather proud of doing this in one day without using SPI before.

I’m totally having a conversation with myself here, but there is a tDATASTALL called on in the datasheet which is exactly what I need. It says 9 microseconds minimum. Does that already include some kind of safety factor, or should I add my own, say by using a 15 microsecond stall time?

wilywampa: I'm totally having a conversation with myself here ...

Yes, in the last 58 minutes since you started posting. I make it close to midnight in parts of the USA.

However congratulations on working it out on your own. :)

Some sort of delay sounds reasonable as SPI is pretty fast and the device needs time to do things. Possibly a small safety margin would be wise. Try and see.