cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F103C8 SPI DMA hangs at 1 byte remaining in RX channel

moschendrohtzaun
Associate II

Hi there,

I'm using an STM32F103C8 and using the SPI1 interface with software CS together with DMA1 Ch2 and 3 (RX and TX, respectively) in master mode. Here is how I init my transactions:

DMA_ClearFlag(DMA1_FLAG_TC3 | DMA1_FLAG_TE3 | DMA1_FLAG_HT3 | DMA1_FLAG_TC2 | DMA1_FLAG_TE2 | DMA1_FLAG_HT2);
 
DMA_Channel_TypeDef *dma_channel_rx = DMA1_Channel2;
DMA_Channel_TypeDef *dma_channel_tx = DMA1_Channel3;
 
DMA_Init(dma_channel_rx, &(DMA_InitTypeDef){
		.DMA_PeripheralBaseAddr = (uint32_t)(&SPI1->DR),
		.DMA_MemoryBaseAddr = (uint32_t)(vdata),
		.DMA_DIR = DMA_DIR_PeripheralSRC,
		.DMA_BufferSize = length,
		.DMA_PeripheralInc = DMA_PeripheralInc_Disable,
		.DMA_MemoryInc = DMA_MemoryInc_Enable,
		.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte,
		.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte,
		.DMA_Mode = DMA_Mode_Normal,
		.DMA_Priority = DMA_Priority_Medium,
		.DMA_M2M = DMA_M2M_Disable,
	});
 
DMA_Init(dma_channel_tx, &(DMA_InitTypeDef){
		.DMA_PeripheralBaseAddr = (uint32_t)(&SPI1->DR),
		.DMA_MemoryBaseAddr = (uint32_t)(vdata),
		.DMA_DIR = DMA_DIR_PeripheralDST,
		.DMA_BufferSize = length,
		.DMA_PeripheralInc = DMA_PeripheralInc_Disable,
		.DMA_MemoryInc = DMA_MemoryInc_Enable,
		.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte,
		.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte,
		.DMA_Mode = DMA_Mode_Normal,
		.DMA_Priority = DMA_Priority_Medium,
		.DMA_M2M = DMA_M2M_Disable,
	});
 
DMA_ITConfig(DMA1_Channel2, DMA_IT_TC, ENABLE);
DMA_ITConfig(DMA1_Channel3, DMA_IT_TC, ENABLE);
 
DMA_Cmd(dma_channel_tx, ENABLE);
DMA_Cmd(dma_channel_rx, ENABLE);
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx | SPI_I2S_DMAReq_Rx, ENABLE);

Then, I have two ISRs:

void DMA1_Channel2_Handler(void) {
	DMA_Channel_TypeDef *dma_channel_rx = DMA1_Channel2;
	if (DMA_GetITStatus(DMA1_IT_TC2)) {
		DMA_ClearITPendingBit(DMA1_IT_TC2);
		DMA_Cmd(dma_channel_rx, DISABLE);
		w25qxx_cs_set_inactive();
	}
}
 
void DMA1_Channel3_Handler(void) {
	DMA_Channel_TypeDef *dma_channel_tx = DMA1_Channel3;
	if (DMA_GetITStatus(DMA1_IT_TC3)) {
		DMA_ClearITPendingBit(DMA1_IT_TC3);
		DMA_Cmd(dma_channel_tx, DISABLE);
	}
}

This works *most* of the time. But at seemingly random intervals, the DMA just "hangs" with the TX interrupt fired and completed, but the RX interrupt never fired and 1 byte of read "hanging" in the queue.

I'm suspecting this happens when TX is complete (last byte has been transferred to the SPI data register, but not yet shifted out), and somehow the peripheral is then therefore stopped (causing the last byte of RX never to cocur). When I debug in the "hanged" state, these are some interesting register values:

DMA1 Interrupt Status (ISR): 00000550
  DMA1_Channel2 (SPI1 RX): CCR 00001083 enabled 1 bytes remain
  DMA1_Channel3 (SPI1 TX): CCR 00001092 disabled 0 bytes remain

I've also noticed the DMA error flag gets set. However, neither is any access non-aligned (bytewise transfers) nor is the memory ever invalid. The reference manual does not give more indication as to what could lead to the error flag in this case.

What is the correct way to instrument the DMA for SPI as I want it without this race condition?

Thanks, Joe

5 REPLIES 5

IMO what you see is simply Rx overrun, due to DMA not being able to retrieve Rx data fast enough, for whatever reason.

You did not care to tell us the baudrate and clock. Try one step lower baudrate.

If there is any other load on the DMA, or RAM, remove it (empty loop with processor).

Loop back and use a recognizable data sequence (0, 1, 2, 3...).

JW

moschendrohtzaun
Associate II

Indeed, lowering the clock seems to fix the issue. I'm running the F103 at 72e6 with a DIV8 prescaler, i.e., 9 MHz SCK. with DIV16 the error seems to disappear (but I'll leave it running all night and see how it goes).

I find that solution a little bit shocking to be honest, because it makes DMA in this application really unreliable. I.e., any load conditions may cause the DMA to fail catastrophically by locking up? What is the typical solution to get reliable transmission and fast speed? Or must the DMA only ever be used when it's certain that there is no load on the memory?

My assumption would have been that when memory is not fast enough to serve the DMA, that the DMA unit just stalls and sends one byte a little bit delayed. Hmm.

Interesting fun fact, even with a ridiculously slow transmission (DIV256, i.e., 280 kHz SCK) I still get the error flags set in the DMA register. Any idea why this would happen?

Thanks for your suggestion, Joe

> DIV8 prescaler, i.e., 9 MHz SCK.

With 8-bit SPI frames, that's 64 system clocks per two DMA transfers (one Rx, one Tx). That should leave ample reserve, unless there's some other load on DMA and/or RAM and/or the APB bus where SPI sits. You did not tell us about any of these.

You may want to read AN2548.

> Interesting fun fact, even with a ridiculously slow transmission (DIV256, i.e., 280 kHz SCK) I still get the error flags set in the DMA register. Any idea why this would happen?

I don't know. This may be worth further investigation, but again, provide more details - what error flag exactly, when does it happen (enable interrupt, in ISR handle perhaps by toggling a pin, observing together with the SPI stream).

JW

Sorry, forgot to write this -- this is the only DMA used in the system at all. And there's just one high frequency interrupt configured (which does almost nothing, however, but fires at around 10 kHz). The main thing it does is main() calling a short "read ID" from the SPI slave over and over again. Which, by the way, ran in an infinite loop all night long with DIV16 without crashing, but which also almost immediately crashes in DIV8.

AN2548 however mentions the behavior that I had expected: "Pauses in transmission – if T x data are delayed, the communication interface is stalled for a short time. This is a typical problem of high speed communication interface such as SPI." -- i.e., in cases of bus contention, the byte should be delayed, not just the transmission aborted, right?

The STM32F103 as I understand only has one error flag for each channel, inside DMA1->ISR. However, I misread. ISR in my case is 0x550, i.e. HT and G are set. The error flag never gets set, I only noticed when trying our your idea, which would have been awesome to find out more.

So I have to correct myself: the error flag never gets set and the DMA just hangs there. Right now the only way I see would be to wire a timer up to "time out" the DMA transfer and then retry, which I guess would work but boy would that be an awful solution.

I'm also always wondering, if it were really buffer underrun, would that then not randomly happen at arbitrary intervals? Why always at the last byte? That doesn't seem to add up. I've also checked silicon errata for the chip, none exist for that particular unit so I'm guessing it must be me.

Thank you for the support, Joe

What you see is IMO not missing data on Tx, but Rx overrun, ie. DMA not able to pick the Rx data fast enough.

Write a minimal code with just the SPI and DMA. You should be able to use the /8 clock without problems. Then gradually add whatever you have now in your code to see the effect.

Loop back and use a recognizable data sequence (0, 1, 2, 3...), then observe what you have in the Rx buffer to see where did the problem occur.

JW