Reading SPI accelerometer

Hi everyone,
I'm having some troubles in reading the output from a IIS3DWB accelerometer; I tried using an Arduino Uno and a STM32L476RE (with an official arduino core) and got the same result; this is the output from one axis:


the signal randomly drops (or rises) for one sample to a meaningless value.
I'm using this library and adapted it to my needs; the code below is for the STM32 but I used a similar one for the Arduino Uno. I'm using a timer in order to raise a flag and do the reading;

#include "IIS3DWB.h"
#include "SPI.h"

#define CSPIN    D2

/* Specify sensor parameters (sample rate is same as the bandwidth 6.3 kHz by default)
   choices are:  AFS_2G, AFS_4G, AFS_8G, AFS_16G
*/
uint8_t Ascale = AFS_4G;

int16_t IIS3DWBDataZ[1] = {0};                  // Stores the 16-bit signed sensor output
int16_t data = 0;
float ax, ay, az, accelTemp;             // variables to hold latest accel data values
uint8_t IIS3DWBstatus;

volatile bool timetoread = false;

IIS3DWB IIS3DWB(CSPIN); // instantiate IIS3DWB class

void setup()
{
  Serial.begin(2000000);

  delay(4000);

  SPI.begin(); // Start SPI serial peripheral
  SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE3));

  // Configure SPI ship select for sensor breakout
  pinMode(CSPIN, OUTPUT);
  pinMode(CSPIN, HIGH); // disable SPI at start

  // Read the IIS3DWB Chip ID register, this is a good test of communication
  Serial.println("IIS3DWB accel...");
  uint8_t c = IIS3DWB.getChipID();  // Read CHIP_ID register for IIS3DWB
  Serial.print("IIS3DWB "); Serial.print("I am "); Serial.print(c, HEX); Serial.print(" I should be "); Serial.println(0x7B, HEX);
  Serial.println(" ");

  if (c == 0x7B) // check if all SPI sensors have acknowledged
  {
    Serial.println("IIS3DWB is online...");
    Serial.println(" ");

    // reset IIS3DWB to start fresh
    IIS3DWB.reset();

    IIS3DWB.selfTest();
    IIS3DWB.init(Ascale); // configure IIS3DWB

    IIS3DWB.enableSingleAxisMode('z');
    Serial.println("single axis mode activated for z axis");
    delay(1000);
  }
  else
  {
    if (c != 0x6A) Serial.println(" IIS3DWB not functioning!");
    while (1) {};
  }

  TIM_TypeDef *Instance1 = TIM1;

  // Instantiate HardwareTimer object. Thanks to 'new' instanciation, HardwareTimer is not destructed when setup() function is finished.
  HardwareTimer *MyTim1 = new HardwareTimer(Instance1);

  MyTim1->setOverflow(400, HERTZ_FORMAT);
  MyTim1->attachInterrupt(update_callback1);
  MyTim1->resume();

}
/* End of setup */

void loop() {

  if (timetoread) // Handle data ready condition
  {
    timetoread = false;
    IIS3DWB.readAccelDataAxis(IIS3DWBDataZ, 'z');
    Serial.println ( IIS3DWBDataZ[0]);
  } // end activity change interrupt handling

}


void update_callback1(void)
{
  timetoread = true;
}

and this is the readAccelDataAxis (and readBytes) function I'm using; each axis output is taken from two registers and combined in a 16 bit signed number; no need to call a readbyte also for the second register as the sensor shifts automatically registers in case of a multi-byte transaction:

void IIS3DWB::readAccelDataAxis(int16_t * destination,char axis)
{
  uint8_t rawData[2];  // x/y/z accel register data stored here
  switch (axis)
  {
    case 'x':
    readBytes(IIS3DWB_OUTX_L_XL, 2, &rawData[0]);  // Read the 2 raw accel data registers into data array
    break;
    case 'y':
    readBytes(IIS3DWB_OUTY_L_XL, 2, &rawData[0]);  // Read the 2 raw accel data registers into data array
    break;
    case 'z':
    readBytes(IIS3DWB_OUTZ_L_XL, 2, &rawData[0]);  // Read the 2 raw accel data registers into data array
    break;
  }
  *destination = (int16_t)((int16_t)rawData[1] << 8)  | rawData[0] ;
}

void IIS3DWB::readBytes(uint8_t reg, uint8_t count, uint8_t * dest) 
{
  digitalWrite(_cs, LOW);
  SPI.transfer((reg & 0x7F) | 0x80);
  for (uint8_t ii = 0; ii < count; ii++)
    dest[ii] = SPI.transfer(0);
  digitalWrite(_cs, HIGH);
}

Just to make things clear I attach an image showing a multi-byte transaction fron the accelerometer datasheet;


Experiments that I tried but didn't work: lowering SPI speed transaction (the sensor is rated for up to 10MHz speed), changing SPI mode (the sensor accepts mode 0 and 3), lowering timer frequency, doing the reading in the update_callback1 function (I was thinking that maybe the interrupt was disturbing the SPI transaction somehow).
Do you have any idea of why this is happening?
Thanks in advance

So far the only time SPI mode 3 worked for me is with an ESP32 using the SPI API. The ESP32 even does background SPI transfers using the ESP32 API.

You might want to crank this down a bit.
SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE3));
to find the spot where things work the best. Try 7 instead of 10.

Next run your scaled data through a filter of some sort.

There is rolling average filter

In this case a SimpleKalmanFilter would work real well.

And if you were to normalize the data you can use LinearRegression to determine the operational drift rates and to determine future drift rate values. Linear Regression (machine learning) requires a 32 bit MCU.

The scale of the graph is unreadable. Some of the libraries have issue with roll over. Where the graph is sitting at zero, the range is 0 to 180, and the graph goes from 0 to 179 and back to 3.

+1 for proper posting.

The SPI bus is not the issue. I like to buy 3 to 5 IMU's and test each one for best operation. So far, the best hobby grade IMU I have found if the LSM9DS1.

Thank you for your suggestions! The ESP32 seems fast and cheap, I might consider switching to it... As for the SPI speed, I tried lowering it to 1 Mhz and still got the same issue. Tried also changing to mode 0; nothing new...
Can't simply filter out those samples as I need a reliable signal to do some precise frequency calculation, and that's also the reason why I chose that specific sensor, it has a super-wide bandwidth (the -3dB line is at 6.5kHz but I've done some tests and it's reliable for frequencies up to 9kHz) and super low-noise.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.