cancel
Showing results for 
Search instead for 
Did you mean: 

LIS3DSH SPI interface issue (bug??)

Rick Schue
Associate II
Posted on June 20, 2018 at 17:39

I've been beating my head against the wall for the last couple of days trying to figure out why I cannot read registers at odd addresses through the LIS3DSH SPI interface with the nRF52832.  Byte accesses to even addresses work fine, but byte accesses to odd addresses always return the value of the register at the even address one location lower.  16-bit accesses only work when the address is even.  It seems like the LSB of the register address is always read by the LIS3DSH as a zero.

Communicating via the I2C bus works fine - bytes, words, even and odd alignment are all OK.

So, it looks to me like the LIS3DSH has a bug in the SPI interface.  My best guess is that the last bit of the address is not correctly latched internally by the rising edge of SCL, and must be held static until the next falling edge of SCL (at which time the LIS3DSH starts to drive the data bus).

Here's my proof:

Shown below is a logic analyzer capture with showing a single byte read of the WHO_AM_I register (address 0x0F).   The bottom 4 traces are a delayed (expanded) version of the top four. Here, I drive MOSI (SDI) to zero after the rising edge of the 8th clock in the ''address'' phase of the transfer.  The hold time is shown to be about 460nsec.  You can see that the data (MISO) returned during the next 8 clocks is 0x00 (corresponding to register 0x0E, INFO2).

0690X00000604csQAA.jpg

In the second capture, I hold the last bit of the address until the falling edge of that same clock.  Here, the data returned is correct for WHO_AM_I (0x3F).

0690X00000604RCQAY.jpg

The data sheet says the hold time is only 15 nsec, relative to the rising edge of SCL.  It looks like you have to hold the 8th bit of the address (and only the 8th bit) until the falling edge of SCL.

So, is this a bug in the chip?  I didn't see any mention of this in the errata sheet.

This is not a problem if the last bit of the address is held static through the data transfer.  In my case, I'm now bit-banging the I/O ports, so I merely leave the MOSI (SDI) pin in its last state. 

Unfortunately the built-in SPI interface of some micro's (like the nRF52832) drive the MOSI line low immediately after the last bit of the address is clocked out during read operations. This means you can't do individual byte reads of registers at odd addresses.  The work-around would be to do a word-aligned 16-bit read and select the proper byte. Fortunately the 16-bit X, Y, and Z data values in the LIS3DSH are word aligned and 16-bit SPI reads with chips like the nRF52832 work fine.

11 REPLIES 11

Hi,

I am still stuck with the same problem. I tried with different sensor too. I am writing CTRL_REG1 exactlt same way as the other registers. I am passing the register number and data value as parameters. That works fine for all the other registers.

Since you successfully enabled the wake up state machine, can u share the sequence in which you are writing the registers, may be that will help me.

Thank you for your help.

Rick Schue
Associate II

Here's a simple sketch for the Bluefruit Feather that I wrote to implement the Wake-Up example from AP3393. Give it a try to see what happens.

The LIS3DSH SDA and SCL are connected to the SDA and SCL pins of the Feather board, and I used a pull up resistor on SDA. The LIS3DSH CS pin (to enable I2C) and SDO pin (to select the address) on the LIS3DSH are tied high (+3.3V).

#include <Wire.h>
 
#define TRUE      1
#define FALSE     0
 
// Software Timer for blinking RED LED
SoftwareTimer blinkTimer;
 
// LIS3DSH register addresses
#define LIS_BASE_RD   0x3B         // I2C base address with pin 7 pulled up
#define LIS_BASE_WR   0x3A         // I2C base address with pin 7 pulled up
#define LIS_BASE      0x1D         // I2C base address with pin 7 pulled up
#define INFO1         0x0D
#define INFO2         0x0E
#define WHO_AM_I      0x0F
#define STAT          0x18
#define CTRL_REG1     0x21
#define CTRL_REG3     0x23
#define CTRL_REG4     0x20
#define CTRL_REG5     0x24
#define CTRL_REG6     0x25
#define STATUS        0x27
#define THRS1_1       0x57
#define ST1_1         0x40
#define ST1_2         0x41
#define MASK1_B       0x59
#define MASK1_A       0x5A
#define SETT1         0x5B
#define OUT_X_L       0x28
#define OUT_X_H       0x29
#define OUT_Y_L       0x2A
#define OUT_Y_H       0x2B
#define OUT_Z_L       0x2C
#define OUT_Z_H       0x2D
 
#define INT_SM1       0x08    // INT1 bit position in STAT register
 
// Library I2C Routines
 
void I2C_Write_LIS(uint8_t address, uint8_t value) {
 
  Wire.beginTransmission(LIS_BASE); 
  Wire.write(address); 
  Wire.write(value); 
  Wire.endTransmission();
}
 
uint8_t I2C8_Read_LIS(uint8_t address) {
 
  uint8_t  temp;
 
  // send the register address
  Wire.beginTransmission(LIS_BASE); 
  Wire.write(address); 
  Wire.endTransmission();
 
  // read back the value (1 byte)
  Wire.requestFrom(LIS_BASE, 1);
  if (Wire.available()) temp = Wire.read();
  return (temp);
}
 
uint16_t I2C16_Read_LIS(uint8_t address) {
 
  uint16_t  temp;
  uint8_t   lo_byte;
  
  // send the register address
  Wire.beginTransmission(LIS_BASE); 
  Wire.write(address); 
  Wire.endTransmission();
 
  // read back the value (2 bytes)
  Wire.requestFrom(LIS_BASE, 2);
  if (Wire.available()) lo_byte = Wire.read();
  if (Wire.available()) temp = Wire.read();
  temp <<= 8;
  temp |= lo_byte;
  return (temp);
}
 
 
void config_LIS3DSH(void){
 
  I2C_Write_LIS(CTRL_REG4,0x67);              // 100Hz update, all axis enabled
  I2C_Write_LIS(CTRL_REG6,0x10);              // ADD_INC = 1
 
  // read back some register to verify operation
  Serial.print("INFO1=");
  Serial.println(I2C8_Read_LIS(INFO1),HEX);
  Serial.print("INFO2=");
  Serial.println(I2C8_Read_LIS(INFO2),HEX);
  Serial.print("WHOAMI=");
  Serial.println(I2C8_Read_LIS(WHO_AM_I),HEX);
  Serial.print("CTRL_REG6=");
  Serial.println(I2C8_Read_LIS(CTRL_REG6),HEX);
}
 
void setup(){
 
  // initialize the I2C interface
  Wire.begin();
  Wire.setClock(400000);
  
  // configure serial debug port
  Serial.begin(115200);
  Serial.println("\n\n********* LIS3DSH I2C Demo 1 *********");
 
  config_LIS3DSH();
  Serial.println("\nLIS3DSH configured");
 
  Serial.println("\nSetting up Wake-up demo");
  I2C_Write_LIS(CTRL_REG1,0x01);
  I2C_Write_LIS(CTRL_REG3,0x48);
  I2C_Write_LIS(CTRL_REG4,0x67);
  I2C_Write_LIS(CTRL_REG5,0x00);
  I2C_Write_LIS(THRS1_1,0x55);
  I2C_Write_LIS(ST1_1,0x05);
  I2C_Write_LIS(ST1_2,0x11);
  I2C_Write_LIS(MASK1_B,0xFC);
  I2C_Write_LIS(MASK1_A,0xFC);
  I2C_Write_LIS(SETT1,0x01);
 
  Serial.println("\nReading back Wake-up demo registers");
  Serial.print("CTRL_REG1=");
  Serial.println(I2C8_Read_LIS(CTRL_REG1),HEX);
  Serial.print("CTRL_REG3=");
  Serial.println(I2C8_Read_LIS(CTRL_REG3),HEX);
  Serial.print("CTRL_REG4=");
  Serial.println(I2C8_Read_LIS(CTRL_REG4),HEX);
  Serial.print("CTRL_REG5=");
  Serial.println(I2C8_Read_LIS(CTRL_REG5),HEX);
  Serial.print("THRS1_1=");
  Serial.println(I2C8_Read_LIS(THRS1_1),HEX);
  Serial.print("ST1_1=");
  Serial.println(I2C8_Read_LIS(ST1_1),HEX);
  Serial.print("ST1_2=");
  Serial.println(I2C8_Read_LIS(ST1_2),HEX);
  Serial.print("MASK1_B=");
  Serial.println(I2C8_Read_LIS(MASK1_B),HEX);
  Serial.print("MASK1_A=");
  Serial.println(I2C8_Read_LIS(MASK1_A),HEX);
  Serial.print("SETT1=");
  Serial.println(I2C8_Read_LIS(SETT1),HEX);
  Serial.print("STAT=");
  Serial.println(I2C8_Read_LIS(STAT),HEX);
  
  // Initialize blinkTimer for 1000 ms and start it
  blinkTimer.begin(500, blink_timer_callback);
  blinkTimer.start();
 
}
 
void loop(){
 
  int8_t status_reg;
 
  // read the X, Y, and Z registers and display on terminal
  Serial.print("\nX=");
  Serial.print(I2C16_Read_LIS(OUT_X_L),DEC);
  Serial.print("  Y=");
  Serial.print(I2C16_Read_LIS(OUT_Y_L),DEC);
  Serial.print("  Z=");
  Serial.print(I2C16_Read_LIS(OUT_Z_L),DEC);
 
  // read the status register to tell when an interrupt has occurred
  status_reg = I2C8_Read_LIS(STAT);
  Serial.print("  STAT=");
  Serial.println(status_reg,HEX);
 
  if (status_reg & INT_SM1) {
    // interrupt occurred
    Serial.println("**** State Machine 1 interrupt ****");
    
    // restart the state machine to clear the interrupt
    I2C_Write_LIS(CTRL_REG1,0x01);
  }
 
  delay (1000);
}
 
 
void blink_timer_callback(TimerHandle_t xTimerID){
  (void) xTimerID;
  digitalToggle(LED_RED);
}
 
void rtos_idle_callback(void){
  // Don't call any other FreeRTOS blocking API()
  // Perform background task(s) here
}