cancel
Showing results for 
Search instead for 
Did you mean: 

SPI-DMA slave HAL_SPI_Transmit_DMA: How to flush rx afterwards?

gfuchs
Associate II
Posted on October 17, 2016 at 22:28

My code configures SPI as slave and then enters an infinite loop where it receives a message, sets a GPIO line indicating to the host that the processor is busy, and readies to transmit a response. After one round, calling HAL_SPI_Receive_DMA a second time immediately triggers a DMA interrupt, probably for a byte having been shifted in with every byte out by HAL_SPI_Transmit_DMA. Such, I guess, DMA sees SPI DR as not being empty as soon as it is enabled. (Although TXE is 0, the underlying shift register might not be.) I ''solved'' the problem of having an unwanted leading byte in the receive buffer by calling HAL_SPI_Receive_DMA for one byte after the transmit DMA has completed.

Is there a simpler way like the for example SHARC DSP provides with a flush tx / rx control bit?

while
(
true
) {
// Receive message.
// todo How to flush receive buffer?
memset
(bufferRx, 0, 
sizeof
bufferRx);
spiRxComplete = 
false
;
spiStatus = HAL_SPI_Receive_DMA(&hspi1, bufferRx, COMM_RX_MESSAGE_SIZE);
CHECK_SPI_STATUS(spiStatus);
HAL_GPIO_WritePin(busy_GPIO_Port, busy_Pin, GPIO_PIN_RESET);
while
(!spiRxComplete);
HAL_GPIO_WritePin(busy_GPIO_Port, busy_Pin, GPIO_PIN_SET);
if
(spiStatus != HAL_OK) {
PRINTF(
''spiStatus = %d\n''
, spiStatus);
// We are dead in the water. Just sit here until the host resets us.
while
(
true
);
}
uint8_t command = bufferRx[COMM_INDEX_COMMAND];
if
(command != COMM_CMD_DATA)
PRINTF(
''rx: %08X\n''
, *((unsigned *) bufferRx));
if
(command == COMM_CMD_DATA) {
if
(noConfig == 
false
) {
// Align buffer content to 32 bit (type float).
memmove
(bufferRx + 
sizeof
(
float
), bufferRx + COMM_INDEX_COMMAND + 1, COMM_RX_DATA_SIZE);
poStatus = VerifyInput((
float
*) bufferRx);
if
(poStatus == STATUS_SUCCESS) {
// Process command.
}
}
else
poStatus = STATUS_CONFIG;
}
else
{
poStatus = STATUS_INVALID_COMMAND;
}
if
(poStatus < STATUS_SUCCESS)
memset
((
void
*) bufferTx, 0, 
sizeof
bufferTx);
// Put the status in the tx buffer. Make it non-zero so that the host can distinguish it from
// just clocking in empty data because it started receiving (clocking as SPI master) too early.
bufferTx[0] = poStatus == STATUS_SUCCESS ? STATUS_HOST_ACK : poStatus;
// Transmit result.
spiTxComplete = 
false
;
spiStatus = HAL_SPI_Transmit_DMA(&hspi1, (uint8_t *) bufferTx, COMM_TX_SIZE);
CHECK_SPI_STATUS(spiStatus);
HAL_GPIO_WritePin(busy_GPIO_Port, busy_Pin, GPIO_PIN_RESET);
while
(!spiTxComplete);
// The lines below (mutual exclusively used) don't fix the misbehavior of HAL_SPI_Transmit_DMA, see below.
// hspi1.Instance->CR1 &= ~SPI_CR1_SPE;
// uint16_t dummy = hspi1.Instance->DR;
// HAL_SPI_DMAStop(&hspi1);
// Set busy flag for getting ready to receive.
HAL_GPIO_WritePin(busy_GPIO_Port, busy_Pin, GPIO_PIN_SET);
// Misbehavior of HAL_SPI_Transmit_DMA. Switching to transmit immediately triggers a DMA interrupt,
// probably for a byte having been shifted in with every byte out. Such, I guess, DMA sees SPI DR as
// not being empty as soon as it is enabled. (Although TXE is 0, the underlying shift register might not be.)
spiRxComplete = 
false
;
spiStatus = HAL_SPI_Receive_DMA(&hspi1, bufferRx, 1);
CHECK_SPI_STATUS(spiStatus);
while
(!spiRxComplete);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */

#spi-dma-slave-hal
8 REPLIES 8
gfuchs
Associate II
Posted on October 19, 2016 at 18:32

Turns out that no additional byte is triggered when I reduce HCLK (prescaler / divisor = 2) and such no flushing of tx is needed. Maybe I am not interpreting the data sheet for the STM32F446 correctly, but it and CubeMX states that HCLK max is 180 MHz (168 MHz at 1.8 V, I assume).

gfuchs
Associate II
Posted on October 25, 2016 at 14:42

Now I ran the same code (almost, now using SPI2) on a NucleoF446RE evaluation board, and the additional byte is back. Reducing peripheral and DMA clock speeds did not fix it this time. The system configuration is the CubeMX default for the Nucleo board. I just added SPI2.

Posted on December 13, 2016 at 12:56

Hi Gunter,

Scanning forums it sounds like we may ahve a simolar issue to yourself.

Did you get a fix?

Regards,

Owain

We are using a STM32F205 as an SPI slave in DMA mode to transfer commands/data/status to/from a SPI master.

Commands sent from the master include a Start of frame marker and a checksum so we can determine if we loose synchronisation between the master and the slave.

Looking to our SPI traces...

Several commands/status have been exchenaged successfuly with the slave and a 2096 byte data block has been sent sucessfully.

The sequence now continues as follows....

CS MOSI MISO

1 -- -- Command 80 50 f2 00 04 00 c6 01 received OK in slave memory

0 80 00

0 50 00

0 f2 00

0 00 00

0 04 00

0 00 00

0 c6 00

0 01 00

1 -- --

1 -- --

0 00 01 Status 01 00 00 00 sent by slave and received OK at master

0 00 00

0 00 00

0 00 00

1 -- --

1 -- --

0 80 00 Slave see's checksum error; actual DMA buffer contains

0 50 00 00 00 00 00 80 50 f2 00

0 00 00

0 e0 00

0 00 00

0 08 00

0 b8 00

0 01 00

1 -- --

1 -- --

We are using the latest STM32 HAL and only using the DMA interrupts.

Command is verified in HAL_SPI_RxCpltCallback(); where depending on cmd either a HAL_SPI_Receive_DMA() or HAL_SPI_Transmit_DMA() os called to continue sequence.

SPI configuration is...

/* Set the SPI parameters */

spi_co_cpu_hspi.Instance = SPIx;

spi_co_cpu_hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;

spi_co_cpu_hspi.Init.Direction = SPI_DIRECTION_2LINES;

spi_co_cpu_hspi.Init.CLKPhase = SPI_PHASE_1EDGE;

spi_co_cpu_hspi.Init.CLKPolarity = SPI_POLARITY_LOW;

spi_co_cpu_hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLED;

spi_co_cpu_hspi.Init.CRCPolynomial = 7;

spi_co_cpu_hspi.Init.DataSize = SPI_DATASIZE_8BIT;

spi_co_cpu_hspi.Init.FirstBit = SPI_FIRSTBIT_MSB;

spi_co_cpu_hspi.Init.NSS = SPI_NSS_HARD_INPUT;

spi_co_cpu_hspi.Init.TIMode = SPI_TIMODE_DISABLED;

spi_co_cpu_hspi.Init.Mode = SPI_MODE_SLAVE;

HAL_SPI_Init(&spi_co_cpu_hspi);

DMA configuration is...

hdma_tx.Instance = SPI_CO_CPU_TX_DMA_STREAM;

hdma_tx.Init.Channel = SPI_CO_CPU_TX_DMA_CHANNEL;

hdma_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;

hdma_tx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;

hdma_tx.Init.MemBurst = DMA_MBURST_INC4;

hdma_tx.Init.PeriphBurst = DMA_PBURST_INC4;

hdma_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;

hdma_tx.Init.PeriphInc = DMA_PINC_DISABLE;

hdma_tx.Init.MemInc = DMA_MINC_ENABLE;

hdma_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;

hdma_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;

hdma_tx.Init.Mode = DMA_NORMAL;

hdma_tx.Init.Priority = DMA_PRIORITY_HIGH;

HAL_DMA_Init(&hdma_tx);

/* Associate the initialized DMA handle to the the SPI handle */

__HAL_LINKDMA(hspi, hdmatx, hdma_tx);

hdma_rx.Instance = SPI_CO_CPU_RX_DMA_STREAM;

hdma_rx.Init.Channel = SPI_CO_CPU_RX_DMA_CHANNEL;

hdma_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;

hdma_rx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;

hdma_rx.Init.MemBurst = DMA_MBURST_INC4;

hdma_rx.Init.PeriphBurst = DMA_PBURST_INC4;

hdma_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;

hdma_rx.Init.PeriphInc = DMA_PINC_DISABLE;

hdma_rx.Init.MemInc = DMA_MINC_ENABLE;

hdma_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;

hdma_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;

hdma_rx.Init.Mode = DMA_NORMAL;

hdma_rx.Init.Priority = DMA_PRIORITY_HIGH;

HAL_DMA_Init(&hdma_rx);

/* Associate the initialized DMA handle to the the SPI handle */

__HAL_LINKDMA(hspi, hdmarx, hdma_rx);

I assume DMA interrupts are purely based on DMA operation and terminal count.

How does this synchronise with the SPI controller and the chip select?

What am I doing wrong here; what part of the jigsaw is missing?

Regards,

Owain
gfuchs
Associate II
Posted on December 14, 2016 at 18:44

Hi Owain,

simple answers first:

I think the synchronization between SPI DMA is done in these lines:

__HAL_LINKDMA(hspi, hdmatx, hdma_tx);
hdma_rx.Instance = SPI_CO_CPU_RX_DMA_STREAM; 
hdma_rx.Init.Channel = SPI_CO_CPU_RX_DMA_CHANNEL;
�?�?�?�?

I think, the linkage between a peripheral and DMA is done in very similar ways in most devices. The peripheral interrupt is linked to the DMA interrupt. When the peripheral interrupt fires, it triggers the DMA interrupt. The DMA engine reads the peripheral transfer register and moves it into or out of the DMA buffer and decrements the transfer count. When the transfer count reaches 0, the DMA engine triggers the DMA complete interrupt. That's it, I think.

That you get a checksum error might have a code / logical and not a hardware cause, because SPI worked fine on both sides for quite a number of bytes and transfers. In my case, the error appeared from the start (as soon as the slave transitioned from transmit to receive, starting with receive). Reducing the peripheral clock solved the issue on our own hardware, but not on ST's Nucleo evaluation board.

I only found out what's happening by using a storage oscilloscope. (A logic analyzer would also do.) Only then I saw that the byte in the DMA receive buffer was never on the bus / sent by the master. So, if in your shoes, I would first look at what is really on the bus and not take for granted that what is in the DMA buffer was actually sent.

A hardware gotcha could be if the time between the chip-select and clock edge is too short / borderline. In such a case, I would not expect to see an error always at the same time / occasion. On the other hand, if the error happens always at the same occasion, I would rather suspect a logical / code error.

By the way, the best way to enter code into a post is, I think, to choose More/Syntax highlighter from the editor toolbar.

In the hope I could help,

Günter

Seb
ST Employee
Posted on December 16, 2016 at 21:20

Depending on the STM32 there are fast SPI and slow SPI peripheral.

For example, on STM32F437 say running at SYSCLK = 100MHz , SPI1,4,5,6 are fast ones, SPI2,3 are slower ones (twice slower). The max speed of an SPI in slave mode is half of the one in master mode.

Then, it is important to understand the 2 interrupts: RXNE = a byte is waiting to be read, TXE = a byte is waiting to be written, they do not come at the same time. It is important to look at the timings and corresponding events.

An alternate way if possible, could be to use NSS as GPIO edge interrupt (EXTI) and program the DMA to point to the transmit buffer in cyclical mode, using NSS edge as 'done' event?

Addi R.
Associate
Posted on April 26, 2017 at 16:07

Hello,

i think we had the same problem and solved it today.

We found out, that in the SPI_DMATransmitReceiveCplt(DMA_HandleTypeDef *hdma) is a CheckUp for the BSY-FLAG:    

if(SPI_CheckFlag_BSY(hspi, SPI_DEFAULT_TIMEOUT, tickstart) != HAL_OK)

    {

      SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_FLAG);

 

}

The CheckUp as a Slave is wrong, because the manual says, that it is not recommened to use the BSY Flag when use SPI as a Slave (as a Notice in the SPI Chapter in the Reference Manuals).

Coment out these 3 Lines (or check RXE and TXE instead) and it should work!

Greetings

Posted on September 08, 2017 at 20:48

Dear Addi, we faced the same problem but we couldn't find the place in the Reference Manual where the notes you've mentioned are written.

Could you please specify the version of the Reference Manual you're referring to and the page number?

Thank you.

tbulsink9
Associate II
Posted on September 27, 2017 at 16:41

I have no idea if it is the same problem (and i don't use the HAL if i van avoid it) but we had a problem like this using the SPL, especially when switching between transmit/receive mode.  Today i ported that stuff to the LL library and i stumbled upon a comment which may have has some bearing on your problem, it is this piece of code wich is called between switching SPI direction:

[code]

/*!

 * @remarks    From

https://community.st.com/external-link.jspa?url=http%3A%2F%2Fwww.st.com%2Fst-web-ui%2Fstatic%2Factive%2Fcn%2Fresource%2Ftechnical%2Fdocument%2Freference_manual%2FDM00031020.pdf

 *             Chapter 28.3.9

 *             Note: During discontinuous communications, there is a 2 APB clock period delay between the

 *             write operation to SPI_DR and the BSY bit setting. As a consequence, it is mandatory to

 *             wait first until TXE=1 and then until BSY=0 after writing the last data.

 */

void nvram_wait_idle( void )

{

    while( !LL_SPI_IsActiveFlag_TXE( sFRAM_SPI ) )

        ;

    while( LL_SPI_IsActiveFlag_BSY( sFRAM_SPI ) )

        ;

}

[/code]