cancel
Showing results for 
Search instead for 
Did you mean: 

USART Rx with DMA: IDLE interrupt vs timer?

Sean Kelly
Associate II
Posted on August 01, 2017 at 16:03

Hi Forum

I'm trying to implement USART Rx of variable, unknown length messages at a 1Mbps baud rate through DMA. I would like to minimize the latency from receiving to consuming the data, so I'm trying to do this with some sort of idle detection as opposed to having my consumer code periodically drain a buffer.

I can attach my code if necessary, but for right now I'm mostly looking for high-level answers on how this is best accomplished to make sure I'm on the right track.

My first attempt was to configure a buffer in DMA and enable the USART IDLE interrupt. In the USART interrupt handler, I first disable DMA (and wait for the enable bit to clear on the DMA stream) and then clear the IDLE flag (by reading the bit and then reading the data register). I copy the data out of the DMA buffer and then re-enable DMA in the interrupt to be ready for the next message.

Doing this gets mixed results -- I do get valid packets occasionally, but most often my received data falls into one or more of these failure categories:

  1. The valid data starts at byte 1 instead of byte 0 of the DMA buffer. The extra byte is very often a duplicate start byte. Where's the extra byte coming from? Should I be disabling the UART peripheral as well?
  2. Truncated message - A valid message is in the buffer, but it abruptly ends. It appears the IDLE interrupt might have come early, or the sender stalled (thus triggering IDLE)?
  3. Data underrun - NDTR says N bytes were received but memory inspection shows fewer than N bytes were actually touched. Do I need to wait a short while after the IDLE flag for DMA to complete before disabling the stream?

I've spent a lot of time searching for this online, and it seems the consensus is that using the IDLE interrupt is not the way to go, and I should be using a timer on the Rx pin (as described in AN3109). The reasons I have found are unclear, though. I'd like to understand more about why IDLE doesn't work for this, and what's the point of the IDLE interrupt if not for this sort of application?

Thanks

Sean

#uart-dma #usart #usart-idle #dma
1 ACCEPTED SOLUTION

Accepted Solutions
Posted on August 02, 2017 at 16:39

Oh, so the code (in the flat view I am using) appeared *above* rather than *below*... with a timestamp breaking the timeline... jive's code behind the moderation feature is ehm well how to say that politely... :|

In

void __attribute__((used)) USART6_IRQHandler(void)

Can we be sure that

Only have USART_IT_IDLE enabled

?

Also, does your scenario account for more than

RX_BUFFER_SIZE

bytes between IDLEs? Is your testing transmitter well-behaved, i.e. transmits only up to

RX_BUFFER_SIZE 

bytes and then long enough IDLEs?

What are the interrupt priorities? May these interrupt nest and if yes, how?

Other than that I see nothing suspicious. I don't use 'libraries' so can't say if there's nothing related to that.

JW

View solution in original post

15 REPLIES 15
Posted on August 01, 2017 at 17:07

Which STM32? How do you set the DMA?

The valid data starts at byte 1 instead of byte 0 of the DMA buffer.

RXNE set before DMA enabled, maybe from a previous communication? Read UART_DR before enabling DMA.

JW

Posted on August 01, 2017 at 17:43

I use DMA for a USART RX FIFO, I use 16-bit mode for the read/write, and tag the unused words as 0xFFFF, and periodically sweep/advance the the flushing from a singular thread (ie not creating race conditions between multiple interrupts to contend over the resource). The buffer is circular and of sufficient depth to guarantee service in the worker task/thread consuming the data. The data words are truncated to 8-bit at this point, and consumed words written as 0xFFFF in the buffer.

The 16-bit DMA from USART->RDR or DR has the top order 7-bits as zero, as the USART supports up to 9-bit data words.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
Sean Kelly
Associate II
Posted on August 02, 2017 at 15:12

Here's some code. Please let me know if more details are needed, but I think the below should be sufficient. DMA setup code followed by my interrupt handlers.

Sean

DMA setup:

#define UART6_RX_DMA_ALL_FLAGS (DMA_FLAG_FEIF1 | DMA_FLAG_DMEIF1 | \
 DMA_FLAG_TEIF1 | DMA_FLAG_HTIF1 | DMA_FLAG_TCIF1 )
// USART RX DMA Channel 
ConfigDMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART6->DR; 
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; 
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; 
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; 
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; 
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; 
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; 
DMA_InitStructure.DMA_Priority = DMA_Priority_High; 
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; 
DMA_InitStructure.DMA_Channel = DMA_Channel_5; 
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; 
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)RxBuffer;
DMA_InitStructure.DMA_BufferSize = RX_BUFFER_SIZE; 
DMA_Cmd(DMA2_Stream1, DISABLE); 
DMA_Init(DMA2_Stream1, &DMA_InitStructure);�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?
// Enable Rx DMA 
DMA_ITConfig(DMA2_Stream1, DMA_IT_TC, ENABLE); 
DMA_ClearFlag(DMA2_Stream1, UART6_RX_DMA_ALL_FLAGS); 
USART_DMACmd(USART6, USART_DMAReq_Rx, ENABLE); 
DMA_Cmd(DMA2_Stream1, ENABLE);�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

Interrupt processing:

// Global to store the count of the data in the buffer
static volatile uint32_t rxBufferContentsSize = 0;
// RxBuffer for DMA
#define RX_BUFFER_SIZE 64
static uint8_t RxBuffer[RX_BUFFER_SIZE];
void __attribute__((used)) USART6_IRQHandler(void)
{
 // Only have USART_IT_IDLE enabled
 if (USART_GetITStatus(UARTSLK_TYPE, USART_IT_IDLE) == SET)
 {
 // Disable the DMA - this ought to trigger the TC interrupt
 DMA_Cmd(UARTSLK_RX_DMA_STREAM, DISABLE);
 
 // Block on the EN bit to wait for the DMA to be disabled
 // (Yes, I plan to do a timeout here here eventually)
 while((UARTSLK_RX_DMA_STREAM->CR & CCR_ENABLE_SET) != 0);
 
 // Record the number of bytes the DMA has read out at the time of IDLE
 // Is this the appropriate way to determine bytes read? Could be related
 // to my underrun errors if the DMA isn't actually finished reading data
 // to memory before I disable it, right?
 rxBufferContentsSize = RX_BUFFER_SIZE - UARTSLK_RX_DMA_STREAM->NDTR;
 
 // This step is necessary to clear the IDLE interrupt
 USART_ReceiveData(UARTSLK_TYPE);
 // Would it be a good practice to disable the UART peripheral here as well,
 // until the DMA is re-enabled??? Perhaps this is related to the extra byte
 // of received data I often see, described by problem number 1 in my post?
 }
}
// DMA Rx handler
void __attribute__((used)) DMA2_Stream1_IRQHandler(void)
{ 
 // DMA Rx handler has been entered. This should only happen when the IDLE
 // interrupt has been processed, so the data from 0 through rxBufferContentSize
 // is supposed to contain one complete, valid packet.
 // This is my test function that quickly runs through the buffer starting
 // from index 0 through rxBufferContentsSize (see the UART ISR for where that's set)
 // In my actual application, this is where the upstream code would be signaled that
 // data is ready. This would not be a long, blocking call - we need to get the DMA
 // enabled again ASAP to be ready for the next message.
 if(!isValidPacket())
 {
 DEBUG_PRINT('RECEIVED INVALID PACKET');
 }
 // Clear the Rx Buffer on each iteration so it's easier to inspect for errors
 // Won't need to do this in product code.
 memset(RxBuffer, 0, RX_BUFFER_SIZE);
 // Restart the DMA
 // I suppose this is where JW has suggested I do an additional read of the UART first
 // Would it be better to have disabled the UART perihperal and only re-enable it when
 // I am ready to enable DMA again?
 DMA_ClearFlag(DMA2_Stream1, UART6_RX_DMA_ALL_FLAGS);
 DMA2_Stream1->NDTR = RX_BUFFER_SIZE;
 DMA2_Stream1->M0AR = (uint32_t)RxBuffer;
 /* Enable DMA USART TX Stream */
 DMA_Cmd(DMA2_Stream1, ENABLE);
}�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

Posted on August 02, 2017 at 14:43

Thanks for the suggestion; this is a clever scheme that avoids needing to manage several head/tail pointers! I may end up doing something like this.

However, for the purpose of this post I'm more interested in understanding what I'm doing wrong than trying to find workarounds or alternate solutions.

Posted on August 02, 2017 at 15:14

I read UART_DR in the IDLE interrupt (it's required to clear the flag) and then re-enable DMA in the DMA TC interrupt that results. I'd hope that's not enough time for a new transaction to come in, but I guess I ought to be safe. 

Is it actually better to just disable the UART peripheral in the IDLE interrupt and only re-enable the peripheral when I'm ready for the DMA to get re-enabled too?

Code posted below.

Posted on August 02, 2017 at 15:26

Code posted below.

How far below? 😉

JW

Posted on August 02, 2017 at 15:44

Sorry, I posted it as main comment, not a reply. Looks like the comment is being moderated right now

Posted on August 02, 2017 at 15:55

I see, sorry for the joke, I couldn't resist... Meantime, you might post the read out values from the relevant DMA registers; that's often more useful than code.

And you haven't told us what's your chip.

JW

Posted on August 02, 2017 at 16:00

No worries, I can't resist a good joke either

🙂

I'm using the STM32F405RG.

Which DMA registers would you consider relevant to read out for debug? I'm pretty much only looking at the resulting memory in RAM and the NDTR register. I've got to go for now, but I'll post some examples when I can get back to my desk with a debugger.

In the meantime, the fact that you're asking all of these questions leads me to believe that I'm not necessarily off in the woods, and I should be able to use the IDLE interrupt in this way - is that true?

Sean