cancel
Showing results for 
Search instead for 
Did you mean: 

STM32H723 SPI DMA Low Level Drivers - sends only one frame

CBerg
Senior

Hi Folks,

I have a problem with STM32H723 SPI with DMA and Low Level drivers.

The issue is: it sends only one Frame, after that the DMA transfer sends constant Transfer-Error Interrupts.

The SPI should be triggered every 100 ms and should send a frame of 10 bytes. I got it running using HAL, so the Hardware is OK (i was able to Rx/Tx data), but I can not get it running with Low Level Drivers. My guess is: i forgot something in the SPI / DMA Interrupt handlers. It's working when the programm is (re-)started, but after the first frame the DMA remains blocked/locked and triggers a "transfer error" uppon the next request.

Hardware Setup

SPI: Full Duplex Master, 8 Bit, Motorola, MSB First, NSS Output Hardware, Fifo Threshold 1 Data

DMA: one channel for Rx and Tx each, normal mode, data width = 1 byte and memory increment.

Basic initialisation and Code generation is done by Cube MX

Code

Basic init, called once to configure the DMA

void myDMA_init(void) {
// DMA Basic Configuriation
LL_DMA_ConfigAddresses( DMA1, LL_DMA_STREAM_2,
LL_SPI_DMA_GetRxRegAddr(SPI2),
(uint32_t)RxBuffer,
LL_DMA_GetDataTransferDirection(DMA1, LL_DMA_STREAM_2));

LL_DMA_ConfigAddresses(DMA1, LL_DMA_STREAM_3,
(uint32_t)TxBuffer,
LL_SPI_DMA_GetTxRegAddr(SPI2),
LL_DMA_GetDataTransferDirection(DMA1, LL_DMA_STREAM_3));
}

Send function: called periodically in the main loop

void SendData(void) {
// configure RX DMA
uint32_t rxAddr = (uint32_t)RxBuffer;
LL_DMA_SetMemoryAddress(DMA1, LL_DMA_STREAM_2, rxAddr);
LL_DMA_SetDataLength(DMA1, LL_DMA_STREAM_2, DataSize);
LL_DMA_EnableStream(DMA1, LL_DMA_STREAM_2);
LL_SPI_EnableDMAReq_RX(SPI2);
// configure TX DMA
uint32_t txAddr = (uint32_t)TxBuffer;
LL_DMA_SetMemoryAddress(DMA1, LL_DMA_STREAM_3, txAddr);
LL_DMA_SetDataLength(DMA1, LL_DMA_STREAM_3, DataSize);
LL_DMA_EnableStream(DMA1, LL_DMA_STREAM_3);
LL_SPI_SetTransferSize(SPI2, DataSize);
LL_SPI_EnableDMAReq_TX(SPI2); 
// enable Transfer Complete and Transfer Error IT for RxDMA Stream
LL_DMA_EnableIT_TC(DMA1, LL_DMA_STREAM_2);
LL_DMA_EnableIT_TE(DMA1, LL_DMA_STREAM_2);
// enable and start SPI Master Transfer
LL_SPI_Enable(SPI2);
LL_SPI_StartMasterTransfer(SPI2);
}

Note: DataSize = 10, uint8_t TxBuffer[10] and uint8_t RxBuffer[10] are global variables. TxBuffer is filled before send, RxBuffer is cleared before send. The SendData function works once. I can see the transmission on the Scope.

Furthermore I have 3 Interrupt Handlers:

Handler for DMA1 Stream 2 (Rx DMA)

void DMA1S2_IRQHandler(void) {
// clear Transfer Complete and Transfer Error IRQ-Flag
if(LL_DMA_IsActiveFlag_TC2(DMA1)) LL_DMA_ClearFlag_TC2(DMA1);
if(LL_DMA_IsActiveFlag_TE2(DMA1)) LL_DMA_ClearFlag_TE2(DMA1);
LL_DMA_DisableIT_TC(DMA1, LL_DMA_STREAM_2);
LL_DMA_DisableIT_TE(DMA1, LL_DMA_STREAM_2);
// enable SPI End of Transfer Interrupt
LL_SPI_EnableIT_EOT(SPI2);
}

Handler for DMA1 Stream 3 (Tx DMA)

void DMA1S3_IRQHandler(void) {
// clear Transfer Complete and Transfer Error IRQ-Flag
if(LL_DMA_IsActiveFlag_TC3(DMA1)) LL_DMA_ClearFlag_TC3(DMA1);
if(LL_DMA_IsActiveFlag_TE3(DMA1)) LL_DMA_ClearFlag_TE3(DMA1);
LL_DMA_DisableIT_TC(DMA1, LL_DMA_STREAM_2);
LL_DMA_DisableIT_TE(DMA1, LL_DMA_STREAM_2);
}

and the Handler for the SPI Interrupt(s):

void SPI2_myIRQHandler(void) {
// clear the End of Transfer Interrupt Flag
LL_SPI_ClearFlag_EOT(SPI2);
LL_SPI_DisableIT_EOT(SPI2);
// Disable SPI2
LL_SPI_Disable(SPI2);
// Disable DMA1 Stream 2 and DMA Request
LL_DMA_DisableStream(DMA1, LL_DMA_STREAM_3);
LL_SPI_DisableDMAReq_RX(SPI2);
// Disable DMA1 Stream 3 and DMA Request
LL_DMA_DisableStream(DMA1, LL_DMA_STREAM_3);
LL_SPI_DisableDMAReq_TX(SPI2);
}

Behaviour:

on the first run, the DMA Rx Interrupt calls the Interrupt handler, the Transfer complete Flag is Set, but no transfer error. At the end of the Handler the SPI End of Transfer Interrupt is enabled, which is called without almost any delay. On the second and all following runs the Rx DMA Interrupt handler is called with TE and TC flags enabled. The SPI EoT Interrupt is never triggered again. In the Scope I can see that one frame has been sent out, but that's it.

The Question: has anyone an idea, why the DMA aborts immediately with an Transfer Error after the first run? Thanks!

2 REPLIES 2
CBerg
Senior

i have found a solution.

As my posting here was initially marked as spam, i crossposted this question on stack overflow. The solution can be found here:

https://stackoverflow.com/questions/77366784/stm32h7-spi-dma-low-level-sends-only-one-frame

TL/DR of my solution:

I had to enable the TC/TE Interrupts not only for the RX DMA stream, bot also for the TX DMA stream dnd  immediately stop the TX DMA Stream when the interrupt occurs.

In my initial version, where I only enabled the TC Complete Interrupt for the RX DMA Stream and closed both streams in the SPI End-of-Transmission Interrupt something got messed up in the TX/RX DMA or SPI.

This leads me to the following question:

RM0468 Rev 3, Page 619, Chapter 15.3.8 {Source, destination and transfer modes} states:

Memory-to-peripheral mode
Figure 81 describes this mode.
When this mode is enabled (by setting the EN bit in the DMA_SxCR register), the stream
immediately initiates transfers from the source to entirely fill the FIFO.
Each time a peripheral request occurs, the contents of the FIFO are drained and stored into
the destination. When the level of the FIFO is lower than or equal to the predefined
threshold level, the FIFO is fully reloaded with data from the memory.
The transfer stops once the DMA_SxNDTR register reaches zero, when the peripheral
requests the end of transfers (in case of a peripheral flow controller) or when the EN bit in
the DMA_SxCR register is cleared by software.

In my case the Tx DMA seems not to stop the transfers. Why is this? Did I miss something in the setup of the transmission / DMA streams?