cancel
Showing results for 
Search instead for 
Did you mean: 

STM32G030 SPI master read issue

KhPaul
Associate II

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);
}

 

 

1 ACCEPTED SOLUTION

Accepted Solutions
KhPaul
Associate II

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.

 

View solution in original post

12 REPLIES 12

> 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

KhPaul
Associate II

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.

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

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?

Devil is in the details.

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

JW

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.

 

 

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?

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. 
 
 
TDK
Guru

> 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.

 

 

If you feel a post has answered your question, please click "Accept as Solution".