Skip to main content
Visitor II
June 20, 2018
Question

LIS3DSH SPI interface issue (bug??)

  • June 20, 2018
  • 7 replies
  • 2948 views
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.

    This topic has been closed for replies.

    7 replies

    Graduate II
    June 20, 2018
    Posted on June 20, 2018 at 17:52

    Are you sure the CS pin stays high long enough? Looks awfully short

    Visitor II
    June 20, 2018
    Posted on June 20, 2018 at 18:40

    I'm bit-banging the SPI lines, so it's hard to violate any set-up or hold times.  In this case, the CS hold time relative to SCL going high is about 440 nsec.  Data sheet spec is 8 nsec.

    I'm not familiar with any minimum CS high spec.  There's none in the data sheet.  It would be dictated by the minimum SCL period and the set-up and hold times relative to SCK.

    In the example shown, CS is high for about 700 nsec.

    Visitor II
    September 14, 2018
    Visitor II
    December 7, 2020

    I am facing similar issue with I2C. can anyone guide me on how to proceed?

    Visitor II
    December 7, 2020

    When I switched the interface to I2C, everything worked fine (see my original post). The problem I noticed was only with the SPI interface.

    What issues are you seeing with the I2C interface?

    Rick

    Visitor II
    December 11, 2020

    I am implementing the wake up state machine. All the registers I am configuring according to whats mentioned in the application note. I am unable to write into register 1. after writing 0x01 to it, when I read it, its 0x00. all other registers work fine. Is there any sequence to write into the registers or something that I might be missing?

    Visitor II
    December 11, 2020

    I've not used with any of the state machine features of the LIS3DSH and I've not looked at the Ap Note in detail.

    The application note (AP3393) "Wake Up" example makes reference to CTRL_REG1 (address 0x21)? Is this what you mean by "register 1"?

    If so, you should be able to write and read it.

    Some more questions:

    1. With regard to CTRL_REG1, if you write another bit pattern into to it and read it back, do the other bits function? Is it just a problem bit bit 0 in the register, or does any value written always read back as 0x00?
    2. When you say "all the other registers work fine", does that include the other registers in this vicinity - like CTRL_REG4 (address 0x20) and CTRL_REG3 (address 0x23)? Can you write and read back random bit patterns in those registers?
    3. Have you tried any of the other simple examples in the Ap Note - like "Toggle"?

    Rick

    Visitor II
    December 14, 2020

    Yes I meant CTRL_REG1(0x21).

    1. I have tried writing other bits too, it is read back always as 0x00.
    2. Yes, it includes CTRL_REG3 and REG4.
    3. I have tried the freefall state machine, but since I am unable to write into CTL_REG1, the state machines are not enabled.
    Visitor II
    December 14, 2020

    Sorry you're having problems. I hooked up an LIS3DSH to my Bluefruit Feather board this morning and typed in the Wake-up example in AP3393 and it appears to work fine. I can load all the registers with the values shown and read them back and get the same results, including CTRL_REG1. I can shake the board and the INT1 line goes high (although it does not return low after the motion stops).

    My code uses direct pin I/O to implement the I2C interface - I don't use the Arduino "Wire" library, as I've never trusted it to implement the repeated start condition.

    At this point, it's either a problem in your code, or you've got a bad device. Is there anything different you're doing when you write CTRL_REG1 that you are doing differently with the other registers? For example, are you programming the I2C transfers in "straight line code" or are you calling a function and passing the register number and data value as parameters?

    Rick

    Visitor II
    January 7, 2021

    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.

    Visitor II
    January 7, 2021

    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
    }