cancel
Showing results for 
Search instead for 
Did you mean: 

stm32 as SPI slave, full-duplex transmission via circular DMA buffer: changes to MISO buffer not (completely) reflected in transfer

FBar.2
Associate II

Created a project template via CubeMX: STM32G030 is SPI slave and uses circular DMA bufs for transfers.

As SPI master I'm using an ESP32 running micropython. This, though, should be irrelevant, as I don't rely on its output but what the Logic Analyzer says. And what the ESP32 = SPI Master = micropython says equals the output of the LA.

Back to the STM32:

Everything appears to be working, except changes to the MISO-buffer are not /fully/ reflected in the final transfer.

I'm using a 4 byte array as MISO-buffer (called `spi_tx_tst_buf` in the project's source posted below), which I set initially to `XXXX`, modify right after boot to `!!!!` and then modify every 5 seconds in the mainloop - setting all 4 bytes to the same (random) character (e.g. 'AAAA', 5s later to 'BBBB' and so on).

After boot up of the stm32 I trigger a full-duplex SPI transfer every 2s from the SPI master.

When triggering the SPI transfer the first time after boot of the stm32 - let's say 1s after power-up - the stm32 always sends "XX!!" for the very first transfer after power-up - which is a mix of the old and the new values.

Every consecutive (>1) SPI read within the next ~4s (= time until the buffer is changed again) however, the stm32 provides the SPI master with the correct byte sequence (in this case: "!!!!"). Until the buffer is changed again, where the first read results in a mix of old and new values again.

What might appear as a race condition, I feel like is unlikely, as the passed time between changes to the buffer and querying it from the SPI master does not matter. Also, that reading twice after buffer change - and with definitely no change in between - results in different output.

When driving the SPI clock every 2s - starting right after the stm32 successfully powered up and it then changing its buffer every 5 seconds - the SPI master receives the following results:

XX!!
!!!!
!!!!
!!!L
LLLL
LLLV
VVVV
VVVV
VVVQ
QQQQ
QQQD
DDDD
DDDD
DDDY
YYYY

What sticks out is, that only one transfer per buffer change is wrong. That also doesn't change if I increase the SPI transfer frequency on the SPI master (e.g. every 2s -> every 0.5s).

What puzzles me most is, that I figured DMA means a direct mapping of the memory canonically storing the 4 bytes.

So, given I'm not completely off, either the changes I apply to the buffer do not (completely) make it into the memory, or the buffer which SPI uses to read the 4 bytes from for the transmission, is a shadowed one.

Either way: I'd really like to know how/why this is happening and - surprise - also how to do it correctly.

Full source code (incl. lot's of boilerplate added by CubeMX):

As I can neither post the full code (post too long) nor can I post a link (too new of a member), here are the essentials of the main.c:

​ 

#include "main.h"
#include <string.h>
 
SPI_HandleTypeDef hspi1;
DMA_HandleTypeDef hdma_spi1_rx;
DMA_HandleTypeDef hdma_spi1_tx;
 
volatile char spi_tx_tst_buf[4] = "XXXX";
volatile char spi_rx_tst_buf[4] = {0,0,0,0};
 
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_SPI1_Init(void);
 
int main(void)
{ 
 HAL_Init();
 SystemClock_Config();
 MX_GPIO_Init();
 MX_DMA_Init();
 MX_SPI1_Init();
 
 if(HAL_SPI_TransmitReceive_DMA(&hspi1, (uint8_t *)&spi_tx_tst_buf, (uint8_t *)&spi_rx_tst_buf, sizeof(spi_tx_tst_buf)) != HAL_OK)
 {
   Error_Handler();
 }
 
 strncpy(spi_tx_tst_buf, "!!!!", 4);
 
 while (1)
 {
 
   HAL_Delay(5000);
 
   int n = rand()%((90+1)-65) + 65;
   spi_tx_tst_buf[0] = n;
   spi_tx_tst_buf[1] = n;
   spi_tx_tst_buf[2] = n;
   spi_tx_tst_buf[3] = n;
 }
}
 
static void MX_SPI1_Init(void)
{
 hspi1.Instance = SPI1;
 hspi1.Init.Mode = SPI_MODE_SLAVE;
 hspi1.Init.Direction = SPI_DIRECTION_2LINES;
 hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
 hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
 hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
 hspi1.Init.NSS = SPI_NSS_SOFT;
 hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
 hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
 hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
 hspi1.Init.CRCPolynomial = 7;
 hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
 hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
 if (HAL_SPI_Init(&hspi1) != HAL_OK)
 {
   Error_Handler();
 }
}
 
static void MX_DMA_Init(void)
{
 __HAL_RCC_DMA1_CLK_ENABLE();
 HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
 HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
 HAL_NVIC_SetPriority(DMA1_Channel2_3_IRQn, 0, 0);
 HAL_NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
}

6 REPLIES 6
S.Ma
Principal

If the SPI has 32 bit HW FIFO, they will be always made full by the DMA for transmission in slave mode.

That means when NSS goes high, the TX fifo is full, and SCK stops, freezing data transfer. Next transaction will flush the FIFO which is not desired.

One fix is to EXTI on NSS rising edge to reset the SPI (this flush the fifo), and reset the DMA. Until someone finds a better way, this one works.

Also you should make sure that the buffer update must be done only when NSS is high to avoid sending data by SPI while it's being updated by the core....

Thank you, not saying your solution doesn't suit, but I'm not using NSS (as in: NSS_SOFT, as this slave is my only slave on the SPI bus).

Can I still use this approach without having the NSS pin I can attach the INT to?

FBar.2
Associate II

Wrong thread, please delete (can't do so myself)

Thinking about it: what are the actual implications of what you're saying?

That my buffer is misaligned? The way I see it it's 8*4 bits - which is exactly 32 bits and would fulfill this condition of being (a multiple of) 32bits - doesn't it?

S.Ma
Principal

If there is a glitch on the bus, there is no way to recover.

What if the power up sequence of the master and slave are time shifted (multiple scenario). If you don't have any data sync mecanism, your application will glitch overnight.

So what you're saying is I will need some kind of starting/reset sequence - either way, correct?

But then again, even if I listen for that on the slave and prepare, apparently I can't be sure what I put into the buffer will end up on the wire.

Or I'M (still(?)) misunderstanding.