Skip to main content
Visitor II
December 1, 2023
Solved

STM32G030 SPI master read issue

  • December 1, 2023
  • 6 replies
  • 5056 views

I use in my project stm32g030f6p6 to communicate with ADXL345 by 4-wire SPI. I have no problem with transmition, yet with reading I run into serious problem, that doesn't mentioned in Errata.

My SPI configuraton is CPOL 1, CPHA 1, speed 1MHz. 

I use Logic-U analizer and ADXL345 for sure respond right, but I receive wrong data, usualy from 1 to 2 bytes.
Only one solution is while reading procedure before togling NSS pin twice read SPI_DR in tthat case RX FiFo is cleared and I receive right data.
I can't understand why I should have done it even once, why it isn't mentioned in Errata.

I also tried to set RXONLY bit right after sending register address and clear it after receiving last byte, but permantely SPI send extra byte it's register address with cleared 7th bit, nor clearing SPE bit neither reading DR doesn't help.

Thus, I wonder what I did wrang If I did of course, and what the solution ST offer.

 

Here it's part of my code for reading data from register

 

#define CAST_IO8(x) *((__IO uint8_t *)&x)
#define CAST_IO16(x) *((__IO uint16_t *)&x)

typedef struct {
 pinDef_TypeDef nss;
 uint32_t cr1;
 uint32_t cr2;
 bool halfDuplex;
 bool hardwareNSS;
} spi_t;
// Array to store configuration of 'n' devices for both SPI interfaces
static spi_t spi_unit[2][4];
// Array of the pointers to current device for both SPI interfaces
static spi_t* spi[2];

/****************************************************************/
/****************************************************************/
static uint8_t getBusID (SPI_TypeDef* pBus)
{
 switch ((uint32_t)pBus) {
 case (uint32_t)SPI1: return 0;
 case (uint32_t)SPI2: return 1;
 }
 return 0xff; // Error
}

/****************************************************************/
/****************************************************************/
__attribute__((always_inline))
static inline void SPI_ClearDR(SPI_TypeDef* pBus)
{
 limited_while (0xffff, !(pBus->SR & SPI_SR_RXNE));
 (void)CAST_IO8(pBus->DR);
}

/****************************************************************/
/****************************************************************/
__attribute__((always_inline))
static inline void SPI_WriteDR(SPI_TypeDef* pBus, uint8_t data)
{
 limited_while (0xffff, !(pBus->SR & SPI_SR_TXE));
 CAST_IO8(pBus->DR) = data;
}

/****************************************************************/
/****************************************************************/
__attribute__((always_inline))
static inline uint8_t SPI_ReadDR(SPI_TypeDef* pBus, uint8_t busId)
{
 if (!spi[busId]->halfDuplex) {
 limited_while (0xffff, !(pBus->SR & SPI_SR_TXE));
 CAST_IO8(pBus->DR) = 0;
 }
 limited_while (0xffff, !(pBus->SR & SPI_SR_RXNE));
 return CAST_IO8(pBus->DR);
}

/****************************************************************/
/****************************************************************/
__attribute__((always_inline))
static inline void SPI_SwitchToRX(SPI_TypeDef* pBus, uint8_t busId)
{
 if (spi[busId]->halfDuplex) pBus->CR1 &=~SPI_CR1_BIDIOE;
}

/****************************************************************/
/****************************************************************/
__attribute__((always_inline))
static inline void SPI_SwitchToTX(SPI_TypeDef* pBus, uint8_t busId)
{
 if (spi[busId]->halfDuplex) pBus->CR1 |= SPI_CR1_BIDIOE;
}

/****************************************************************/
/****************************************************************/
__attribute__((always_inline))
static inline void SPI_NSS_set(SPI_TypeDef* pBus, uint8_t state)
{
 uint8_t bus = getBusID(pBus);

 // Check state for the part of the transaction
 switch (state) {
 // It's end of the transaction - Wait till it's done
 case true: limited_while (0xffff, pBus->SR & SPI_SR_BSY); break;
 // It's start of the transaction - Clear DR.
 // I don't know why I has to do it twice, yet it's only one way to read right data!
 case false: (void)CAST_IO16(pBus->DR); (void)CAST_IO16(pBus->DR); break;
 }

 // It's hardware NSS mode there's nothing to do
 if (spi[bus]->hardwareNSS) return;

 GPIOx_Write(spi[bus]->nss.GPIO, spi[bus]->nss.pin, state);
}
/****************************************************************/
/* The function for reading data from register in Master mode */
/* ============================================================ */
/* pBus - SPI bus (SPI1 / SPI2) */
/* addr - register address to read data */
/* data - pointer to array to write read data */
/* len - bytes to read */
/****************************************************************/
void SPI_MasterReadDataFromReg (SPI_TypeDef* pBus, uint8_t addr, uint8_t* data, size_t len)
{
 uint8_t bus = getBusID(pBus);

 SPI_NSS_set(pBus, 0);

 SPI_WriteDR(pBus, addr);
 SPI_ClearDR(pBus);

 // Switch to read mode
 SPI_SwitchToRX(pBus, bus);

 // Read received data
 while(len--) *(data++) = SPI_ReadDR(pBus, bus);

 // Switch to transmit mode
 SPI_SwitchToTX(pBus, bus);

 SPI_NSS_set(pBus, 1);
}

 

 

    This topic has been closed for replies.
    Best answer by KhPaul

    I did presented CAST_IO8. Considering  limited_while, here it's 

     

    extern uint32_t wdt_while_cnt;
    
    /* ================================================================
     * Helper macros
     * ================================================================ */
    #define limited_while(itr, cndt) {wdt_while_cnt=itr; while(wdt_while_cnt-- && (cndt));}

     

    As I said, before I deeply checked everything before starting the topic, the issue is clear: RXFIFO is filled with extra data. But, anyway I missed some important information from the refernce manual, there is next note:  Read data until FRLVL[1:0] = 00 (read all the received data) 

    So, extra bytes in RXFIFO it is a normal behaviour, and reading DR is a right way to clear it.

    I changed limited_while to be able to pass an extra variable argument - expression

     

    #define limited_while(itr, cnd, ...) for(uint32_t __i = (itr); __i && (cnd); __i--){__VA_ARGS__;}

     

    And added RXFIFO clearing procedure after data are received

     

    limited_while (0xffff, pBus->SR & (SPI_SR_RXNE | SPI_SR_FRLVL), (void)CAST_IO8(pBus->DR));

     

    Conclusion: It's necessary to read the documentation more carefully.

    Topic is closed.

    Thank you everyone, I wish you good luck.

     

    6 replies

    Super User
    December 1, 2023

    > Only one solution is while reading procedure before togling NSS pin twice read SPI_DR in tthat case RX FiFo is cleared and I receive right data.

    I don't understand your code, but if you use BIDIR in Rx, or RXONLY, SPI will generate clocks autonomously and thus fill the Rx FIFO, so if you don't read it out continuously all the time (regardless of NSS or whatnot), it's full.

    This behaviour is described in RM.

    JW

    KhPaulAuthor
    Visitor II
    December 1, 2023

    KhPaul_0-1701417419536.png

    That what I see with analizer, ADXL345 responds correctly, yet without the extra code of the reading DR In the start of transaction, before toggling NSS pin, I get one or two trash bytes.

    (void)CAST_IO16(pBus->DR); (void)CAST_IO16(pBus->DR);

     

    I don't use either BIDIR or RXONLY, as I wrote I also tried it, but in this mode after switching to normal mode SPI transmits extra byte with cleared 7th bit (for ADXL345 it's register address), and it's a problem cause ADXL345 determine it like a writing procedure to register, so I got my solution broken.

    Super User
    December 1, 2023

    So, in you waveform above, you've read from SPI_DR 7 bytes in the first transaction during NSS being low, yet you've found one or two more bytes in it before NSS went down the next time? Or how should that waveform be interpreted? It would be helpful if you'd toggle a GPIO pin at the moment of reading from SPI_DR, and observe that on the LA, too.

    Meantime, read out and check/post content of SPI registers, say just before you find those extra bytes.

    JW

    KhPaulAuthor
    Visitor II
    December 1, 2023

    If I not read SPI_DR twice  in the start of the transaction during NSS being high, I'll get 1-2 trash bytes.

    If you see in my code, I read DR before toggle NSS to low

     Normal procedure should be next, if I'm correct

    NSS_LOW

    Wait for TXE

    Write DR = ADDR

    Wait RXNE

    Read (void)DR

    Wait for TXE

    Loop n = bytes

    Write DR = 0

    Wait RXNE

    Read data[n] = DR

    End of the loop

    Wait for BSY == false

    NSS_HIGH

    But it doesn't work, I'll get trash data, it'll be the one from previous transaction, although I waited for RXNE and read last received byte

     

    To make it work I have to do next, read DR twice:

    Read (void)DR

    Read (void)DR

    NSS_LOW

    Wait for TXE

    Write DR = ADDR

    Wait RXNE

    Read (void)DR

    Loop n = bytes

    Write DR = 0

    Wait RXNE

    Read data[n] = DR

    End of the loop

    Wait for BSY == false

    NSS_HIGH

    In this case I get right data.

     

    P.S. I switched to g0 from f0 which I used for years, and SPI library worked correctly, besides I use SPI2, and may be it's a SPI2 issue only, unfortunatelly in my project I can't use SPI1.

    So, now I made it work with double reading DR, but it's the crutches, and I want to get is it an g0 issue, or I did something wrong, but what?

    Super User
    December 1, 2023

    Devil is in the details.

    Read out and post content of SPI registers just *before* you'd read the dummy bytes.

    JW

    KhPaulAuthor
    Visitor II
    December 1, 2023

    Before reading DR while NSS is High

    KhPaul_2-1701425790033.png

    After (NSS is LOW)

    KhPaul_3-1701425867959.png

    Totaly the same, but if I don't read DR twice I'll get dummy bytes. And as I said before I used the SPI librarry for years for f0 mcu, and had no issues.

     

     

    Super User
    December 1, 2023

    At this point, I see no problem with SPI as such, and accuse the rest of the code, upon which you base your observation.

    Yes, I do hear you, this is a proven good library. However, it's a different program, isn't it. Details do matter, and the nature of most bugs is, that you don't know *which* detail does matter beforehand (with hindsight, its always a different story).

    One approach to tackle this would be to take the non-functioning code without the dummy reads, and single-step through the offending portion, maybe even in disasm, observing individual transactions (with this caveat in mind). Or just simply read the disasm, if you are into that.

    JW

    PS. Does CAST_IO8 macro retain volatility?

    KhPaulAuthor
    Visitor II
    December 1, 2023

    Of course __IO is from core_cm0plus.h ST use this macros in HAL and LL libraries ,don't you know?

     

     

    #define __IO volatile /*!< Defines 'read / write' permissions */

     

     

    CAST_IO8 is in part of my code I wrote in the the fbegining of the conversation.

     

     

    #define CAST_IO8(x) *((__IO uint8_t *)&x)

     

     

     I also provided, all code for my reading procedure. 

    waclawek.jan you said you "I don't understand your code" what exactly you can't get? 
     
     I explained the issue, and it's clear, that RX FIFO of the SPI interface has in dummy bytes from previous transaction, and the other is if I switch to RXONLY mode, after switch back SPI send a byte, yet I don't write anything to DR, and it isn't a clocking issue as you said. I well aware how SPI works, and it has to send a byte only when I write something to DR. IThere is no any notes about kind of SPI behavior I see. Even then when I disable SPI by clearing SPE bit, right after I enable it, the byte is being send, if RXONLY was set before. 
     
     
    Super User
    December 1, 2023

    > I explained the issue, and it's clear, that RX FIFO of the SPI interface has in dummy bytes from previous transaction, and the other is if I switch to RXONLY mode, after switch back SPI send a byte

    I'm not going to dig into the code either, but it sounds like you haven't read out old bytes in the RXFIFO that you may not care about, but that came from previous TX transactions.

    You're using 4-wire SPI. Using two-wire mode (never using RXONLY) is conceptually much simpler and I'd stick with that.

     

    > // I don't know why I has to do it twice, yet it's only one way to read right data!

    Seeing stuff like this in a "known good working code" is a bad sign.

     

     

    KhPaulAuthor
    Visitor II
    December 1, 2023

    Before starting the topic, I deeply read reference manual, erratas, and similar topics I found.

    No offence, but if you aren't going  to dig into the code either, how could you help me? If you read my code you'll see that I read DR each time I send a byte to DR, besides I wait for TXE before sending, and for RXNE before reading, and for BSY clear befor ending of the transaction. So, what a reason in this kind of help, or you do it just to grow your rate GURU?  

    Besides, I gave a simplified alghorithm my reading procedure, step by step, I read all bytes Slave send me, but while next transaction there are some data from previous one in RX FiFo of the SPI.

    Thus, just leave it, I'm ready to wait for reasonable response from the comunity.

    Super User
    December 1, 2023

     

    Good luck.