2017-08-01 07:03 AM
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:
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 #dmaSolved! Go to Solution.
2017-08-02 09:39 AM
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
2017-08-01 08:07 AM
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
2017-08-01 08:43 AM
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.
2017-08-02 06:12 AM
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);
}�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?
2017-08-02 07:43 AM
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.
2017-08-02 08:14 AM
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.
2017-08-02 08:26 AM
Code posted below.
How far below? ;)
JW
2017-08-02 08:44 AM
Sorry, I posted it as main comment, not a reply. Looks like the comment is being moderated right now
2017-08-02 08:55 AM
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
2017-08-02 09:00 AM
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