Low Level Control of SPI for LIS3DH (LIS2DH) with SCB Module

Tip / Sign in to post questions, reply, level up, and achieve exciting badges. Know more

cross mob
TeMa_1467596
Level 5
Level 5
5 sign-ins 5 likes given First like received

I'm using a CYBLE-222014-01 module and I downloaded a driver from ST Micro for their LIS3DH accelerometer (which according to ST is the same register set as LIS2DH which I am using).

   

In their code, ST provide 2 places where you have to build your own functions (they give 2 comment lines giving very basic suggestions) and I've written code that compiles but I'm looking for a check of understanding - note I'm hard-controlling the chip select (CS) line. Here's teh code ...

   

const uint8 READ  = 0b10000000;     // LIS2DH's read mask
const uint8 WRITE = 0b00111111;     // LIS2DH's write mask
      uint8 dataToSend;

   


u8_t LIS3DH_ReadReg(u8_t Reg, u8_t* Data) {
  
  //To be completed with either I2c or SPI reading function
  //i.e. *Data = SPI_Mems_Read_Reg( Reg ); 

   

// my code starts here
    // combine the R/W register address and the command into one uint8
    dataToSend = (Reg | READ);
  ACC_CS_Write(0);  // clear ACC_CS for access
    SPI_SpiUartWriteTxData(dataToSend);     // TM
    *Data = SPI_SpiUartReadRxData();        // TM read data from the received buffer
  ACC_CS_Write(1);  // set ACC_CS
    return 1;
}

/*******************************************************************************
* Function Name : LIS3DH_WriteReg
* Description : Generic Writing function. It must be fullfilled with either
* : I2C or SPI writing function
* Input : Register Address, Data to be written
* Output : None
* Return : None
*******************************************************************************/
u8_t LIS3DH_WriteReg(u8_t WriteAddr, u8_t Data) {
  
  //To be completed with either I2c or SPI writing function
  //i.e. SPI_Mems_Write_Reg(WriteAddr, Data);  
    dataToSend = (WriteAddr & WRITE);       // TM set up write mask (NB the M/S bit is also 0 for single address
  ACC_CS_Write(0);  // clear ACC_CS for access TM ???? do we want to clear other CS or use a SPI busy flag ???
    SPI_SpiUartWriteTxData(dataToSend);     // TM send mask+address 
    SPI_SpiUartWriteTxData(Data);           // TM send actual data to write
  ACC_CS_Write(1);  // set ACC_CS
    
    return 1;
}

   

The bit that's confusing me is that the data sheet for the LIS2DH device indicates that for an SPI read, the register address is sent to the device and then the contents are returns in the next 8 bits, so for my LIS3DH_ReadReg() function, won't I need to throw away the first Bte I get back with the SPI_SpiUartReadRxData() function?

   

or maybe someone's trodden this path before and already has code they are willing to share?

   

Thanks in advance for any help.

0 Likes
1 Solution
TeMa_1467596
Level 5
Level 5
5 sign-ins 5 likes given First like received

Good news, I have the SPI system sending and receiveing data to/from the LIS2DH.  There are 2 simple things to note:

   

1. Every byte that's TX'd over SPI causes a byte to be read back into the PSOC's SPI RX buffer so even when you're sending data to it, you have to wait for the same number of bytes to be received and throw them away or the buffer fills up and stops working.

   

2. the SPI_SpiUartWriteTxData(dataToSend) only puts data into the SPI TX buffer so you have to wait for that data to be clocked out before the returned data can be read.

   

The following is my read an write routines that I added to the LIS3DH driver code supplied by Cypress

   


/*******************************************************************************
* Function Name : LIS3DH_ReadReg
* Description : Generic Reading function. It must be fullfilled with either
* : I2C or SPI reading functions
* Input : Register Address
* Output : Data REad
* Return : None
*******************************************************************************/
u8_t LIS3DH_ReadReg(u8_t Reg, u8_t* Data) {
  uint8 junkByte = 0xFF;
  //To be completed with either I2c or SPI reading function
  //i.e. *Data = SPI_Mems_Read_Reg( Reg ); 
    // combine the R/W register address and the command into one uint8
  ACC_CS_Write(0);  // clear ACC_CS for access TM ???? do we want to clear other CS or use a SPI busy flag ???
    dataToSend = (Reg | READ);
    SPI_SpiUartWriteTxData(dataToSend);     // TM the real byte to send
    SPI_SpiUartWriteTxData(junkByte);     // TM a second byte that's just there to send another 8 bits out to clock the response back in 
    // the above Write TX data commands hav just put 2 bytes in the output buffer    
    // now we have to wait for data to come back
    // while (SPI_SpiUartGetRxBufferSize() >= 2); // loop until 2 bytes are received
    CyDelayUs(6); // 6 uS delay
    junkByte = SPI_SpiUartReadRxData();        // TM read junk data from the first received buffer
    *Data = SPI_SpiUartReadRxData();        // TM read data from the next received buffer
  ACC_CS_Write(1);  // set ACC_CS
    return 1;
}

/*******************************************************************************
* Function Name : LIS3DH_WriteReg
* Description : Generic Writing function. It must be fullfilled with either
* : I2C or SPI writing function
* Input : Register Address, Data to be written
* Output : None
* Return : None
*******************************************************************************/
u8_t LIS3DH_WriteReg(u8_t WriteAddr, u8_t Data) {
  uint8 junkByte;
  
  //To be completed with either I2c or SPI writing function
  //i.e. SPI_Mems_Write_Reg(WriteAddr, Data);  
    dataToSend = (WriteAddr & WRITE);       // TM set up write mask (NB the M/S bit is also 0 for single address
  ACC_CS_Write(0);  // clear ACC_CS for access TM ???? do we want to clear other CS or use a SPI busy flag ???
    SPI_SpiUartWriteTxData(dataToSend);     // TM send mask+address 
    SPI_SpiUartWriteTxData(Data);           // TM send actual data to write
    // we've just written out 2 bytes but the PSoC SPI system will automatically read 2 bytes back, we're not interested in those bytes
    CyDelayUs(6); // 6 uS delay - at 6 MHz it takes < 4 US to clock out 2 bytes (16 bits)
    junkByte = SPI_SpiUartReadRxData();        // TM read junk data from the first received buffer
    junkByte = SPI_SpiUartReadRxData();        // TM read junk data from the next received buffer
  ACC_CS_Write(1);  // set ACC_CS
    
    return 1;
}
 

View solution in original post

0 Likes
11 Replies
Anonymous
Not applicable

As far as SPI bus communication is concerned: Yes, when you transmit a byte of data, the returned byte will be transmitted syncrhonously as the register address byte you sent, and thus will be 0xFF or 0x00 based on the default output of the other side of the SPI bus. You would then throw away this byte, and read the next byte to get the response from the other chip. However, this applies for every byte sent; The SPI bus is more like a byte-trade between the two chips, than a send/receive bus. See here for more details: https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus

   

My advice: Write the entire command/address/data to the LIS3DH, clear out your current SPI receive buffer, then read the next x-bytes based on the response you expect from the LIS3DH to your device. Keep in mind that clock timing and communication timing overall being too fast might cause your buffer to read null values before the LIS3DH can respond.

0 Likes
TeMa_1467596
Level 5
Level 5
5 sign-ins 5 likes given First like received

Thanks, you confirmed my understanding, I'll try it like you suggest and report back.

   

Ted

0 Likes
TeMa_1467596
Level 5
Level 5
5 sign-ins 5 likes given First like received

OK, I tried to get this to work and I'm not getting very far.  I can see that there are SPI signals with my scope but all I seem to read are zeros.  I'm pretty sure that the issue is to do with timing in that, so far, I haven't found a way to test that the byte that's been dropped into the write buffer so let me ask a few check of understanding questions if I may...

   

1. If I issue a SPI_SpiUartWriteTxData(dataToSend); instruction, all that does is put the dataToSend byte into the hardware buffer - right?  

   

2. If 1 is true then, if I want to write out multiple bytes, I can simply put more bytes in and these will be written out under control of the SPI hardware - right?

   

3. If I want to issue a command to read a register like this...

   

        response = LIS3DH_GetWHO_AM_I(&valueX);

   

and the function is as follows...

   

/*******************************************************************************
* Function Name  : LIS3DH_GetWHO_AM_I
* Description    : Read identification code by WHO_AM_I register
* Input          : Char to empty by Device identification Value
* Output         : None
* Return         : Status [value of FSS]
*******************************************************************************/
status_t LIS3DH_GetWHO_AM_I(u8_t* val){
  
  if( !LIS3DH_ReadReg(LIS3DH_WHO_AM_I, val) )
    return MEMS_ERROR;
  
  return MEMS_SUCCESS;
}

   

then the ReadReg command needs to request the specific register and then wait for that to be requested before the result is passed back...

   

u8_t LIS3DH_ReadReg(u8_t Reg, u8_t* Data) {
  uint8 junkByte;
  //To be completed with either I2c or SPI reading function
  //i.e. *Data = SPI_Mems_Read_Reg( Reg ); 

   

    // combine the R/W register address and the command into one uint8
    dataToSend = (Reg | READ);
  ACC_CS_Write(0);  // clear ACC_CS for access TM ???? do we want to clear other CS or use a SPI busy flag ???
    SPI_SpiUartWriteTxData(dataToSend);     // TM
    // now we have to wait for data to come back
    // while (SPI_SCB_IRQ_GetState());
    CyDelayUs(10); // 10 uS delay
    junkByte = SPI_SpiUartReadRxData();        // TM read junk data from the first received buffer
    *Data = SPI_SpiUartReadRxData();        // TM read data from the next received buffer
  ACC_CS_Write(1);  // set ACC_CS
    return 1;
}

   

but it doesn't work, I get all zeros.

   

I'll post the entire project in a few minutes...

0 Likes
lock attach
Attachments are accessible only for community members.
TeMa_1467596
Level 5
Level 5
5 sign-ins 5 likes given First like received

project upload

0 Likes
TeMa_1467596
Level 5
Level 5
5 sign-ins 5 likes given First like received

In looking at this, I wanted to create a way to know that the stuff that was in the TX buffer had gone out before issuing the read command.

   

I read the datasheets and tried to add an internal interrupt using SPI done but when I did that, my program would lock up - presumably stuck in the ISR.  I'm at a disadvantage in debugging because I've used the SWDIO and SWDCLK lines for SPI and therefore can only program and not debug my module.  I have got a UART running that I can send messages out on though.

0 Likes
lock attach
Attachments are accessible only for community members.
Anonymous
Not applicable

I've attached the project(s) with changes to have the chip write a 'T' every half a second, and to echo the receive buffer to the UART every half a second too. As a simple example 🙂

0 Likes
Anonymous
Not applicable

I forgot to add the start function calls to the beginning of the main() code, but you can quickly add that in before you try running it;
    SCB_1_Start();
    SCB_2_Start();

0 Likes
lock attach
Attachments are accessible only for community members.
TeMa_1467596
Level 5
Level 5
5 sign-ins 5 likes given First like received

Thanks for the suggestion, I'm not getting any data at all from the accelerometer.  Here are some pictures describing my issue.  The first is a snip from the data sheet, you can see that the accelerometer expects to send the data after the R/W mask + register is sent, it's followed by the acellerometer sending back data to the PROC.  The second picture is an actual screen capture (with the signals in the same horizontal order) from my scope and you can see that:

   

A) There are only 8 clock bits going out with data that is aligned on the MOSI line

   

B) The second 8 clock bits are not there and

   

C) (Unsurprisingly) no data is coming back on the MISO line which would explain why I'm getting nothing back.

   

This makes me think there's something not set up right with my PROC configuration. Plus, as I say above, it's not clear to me what the sequence of commands should be for the SCB to perform the write followed by read.

0 Likes
Anonymous
Not applicable

For the SPI bus, the hardware will perform reads and writes for every single byte written simultaneously; Thus, writing two bytes output should have the hardware read 2 bytes into the input/read buffer. This would also explain why the accelerometer isn't responding, since you need to send a second byte to clock the read-byte from the accelerometer into the PRoC.

   

The PRoC is the master of the spi, and thus the SPI bus clock will only fire when you write bytes into the SPI bus from the PRoC. If you are just wanting to read data, then you will probably want to send a 0x00 or a 0xFF to the accelerometer to get it to clock out the data byte you want to read. Thus:

   

If you write <command byte><DI-byte>, then you should get a <don't-care byte><DO-byte>

   

A) The MOSI data will be matching to the clock signaling because the PRoC is the master of the SPI bus.

   

B) You need to write a byte for every 8 clock signals you want, and thus in order to write 1 byte then read 1 byte, you will need to write 2 bytes. The second written byte should clock a read byte into the PRoC hardware from the bus line, and you would probably want it to be a NULL byte (0x00 or 0xFF depending on the MOSI's idle state).

   

C) When you write the second byte, you should see data bits coming back from the accelerometer. One thing to test, is if you are still having trouble getting data back, try writing your command/data bytes to the accelerometer, then write say 10-20 0x00s or 0xFFs; This will ensure any pending response data from the accelerometer will be clocked to the PRoC for you to see/read in debugging how things work.

0 Likes
TeMa_1467596
Level 5
Level 5
5 sign-ins 5 likes given First like received

Thanks, I'll try that today.

0 Likes
TeMa_1467596
Level 5
Level 5
5 sign-ins 5 likes given First like received

Good news, I have the SPI system sending and receiveing data to/from the LIS2DH.  There are 2 simple things to note:

   

1. Every byte that's TX'd over SPI causes a byte to be read back into the PSOC's SPI RX buffer so even when you're sending data to it, you have to wait for the same number of bytes to be received and throw them away or the buffer fills up and stops working.

   

2. the SPI_SpiUartWriteTxData(dataToSend) only puts data into the SPI TX buffer so you have to wait for that data to be clocked out before the returned data can be read.

   

The following is my read an write routines that I added to the LIS3DH driver code supplied by Cypress

   


/*******************************************************************************
* Function Name : LIS3DH_ReadReg
* Description : Generic Reading function. It must be fullfilled with either
* : I2C or SPI reading functions
* Input : Register Address
* Output : Data REad
* Return : None
*******************************************************************************/
u8_t LIS3DH_ReadReg(u8_t Reg, u8_t* Data) {
  uint8 junkByte = 0xFF;
  //To be completed with either I2c or SPI reading function
  //i.e. *Data = SPI_Mems_Read_Reg( Reg ); 
    // combine the R/W register address and the command into one uint8
  ACC_CS_Write(0);  // clear ACC_CS for access TM ???? do we want to clear other CS or use a SPI busy flag ???
    dataToSend = (Reg | READ);
    SPI_SpiUartWriteTxData(dataToSend);     // TM the real byte to send
    SPI_SpiUartWriteTxData(junkByte);     // TM a second byte that's just there to send another 8 bits out to clock the response back in 
    // the above Write TX data commands hav just put 2 bytes in the output buffer    
    // now we have to wait for data to come back
    // while (SPI_SpiUartGetRxBufferSize() >= 2); // loop until 2 bytes are received
    CyDelayUs(6); // 6 uS delay
    junkByte = SPI_SpiUartReadRxData();        // TM read junk data from the first received buffer
    *Data = SPI_SpiUartReadRxData();        // TM read data from the next received buffer
  ACC_CS_Write(1);  // set ACC_CS
    return 1;
}

/*******************************************************************************
* Function Name : LIS3DH_WriteReg
* Description : Generic Writing function. It must be fullfilled with either
* : I2C or SPI writing function
* Input : Register Address, Data to be written
* Output : None
* Return : None
*******************************************************************************/
u8_t LIS3DH_WriteReg(u8_t WriteAddr, u8_t Data) {
  uint8 junkByte;
  
  //To be completed with either I2c or SPI writing function
  //i.e. SPI_Mems_Write_Reg(WriteAddr, Data);  
    dataToSend = (WriteAddr & WRITE);       // TM set up write mask (NB the M/S bit is also 0 for single address
  ACC_CS_Write(0);  // clear ACC_CS for access TM ???? do we want to clear other CS or use a SPI busy flag ???
    SPI_SpiUartWriteTxData(dataToSend);     // TM send mask+address 
    SPI_SpiUartWriteTxData(Data);           // TM send actual data to write
    // we've just written out 2 bytes but the PSoC SPI system will automatically read 2 bytes back, we're not interested in those bytes
    CyDelayUs(6); // 6 uS delay - at 6 MHz it takes < 4 US to clock out 2 bytes (16 bits)
    junkByte = SPI_SpiUartReadRxData();        // TM read junk data from the first received buffer
    junkByte = SPI_SpiUartReadRxData();        // TM read junk data from the next received buffer
  ACC_CS_Write(1);  // set ACC_CS
    
    return 1;
}
 

0 Likes