UART DMA with IDLE Line Detection
Long ago I covered the Uart Ring Buffer, where the data of unknown length could be received efficiently. Though the method was fine, but there were lot of changes needed to be made with different series of MCUs.
So Today we will see another efficient method, which is completely based on HAL, so the changes are not needed for different series of MCUs. This method uses DMA with the IDLE Line to trigger the interrupt.
Circular Buffer
Check out the UART Circular buffer (Ring Buffer) based on IDLE LINE STM32/UART CIRCULAR BUFFER at master · controllerstech/STM32 (github.com)
What is IDLE Line
- Let’s say we are receiving some large data using the UART.
- This data is sent in chunks.
- There is some small delay between two chunks, and for this delay the line remains IDLE.
- That’s it, whenever the MCU detects this idle line, an interrupt will be triggered, and we can process the data, and prepare for the next chunk.
Since we are going to use DMA for this purpose, the CPU will be free for other operations.
CubeMX Setup
The setup is pretty simple. We will select the UART, enable the DMA for Receiving Data, and turn on the UART Interrupt.
- Make sure the DMA is select is NORMAL mode.
- If there is any overrun bit turned on by default, Disable it.
- Data width should be Bytes, and Direction is Peripheral to Memory
Update for CORTEX M7
Cortex M7 Users might need to Disable the cache of the Respective SRAM, or else the DMA won’t be able to copy the data. Watch the video, I have explained it in the end
Some Insight into the CODE
Let’s see some of the functions that we will use in this tutorial.
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, RxBuf, RxBuf_SIZE);
__HAL_DMA_DISABLE_IT(&hdma_usart2_rx, DMA_IT_HT);
- HAL_UARTEx_ReceiveToIdle_DMA is used to receive the data using the DMA until, an IDLE event occurs, or all the data has been received.
- Here we will save the incoming data into the RxBuf, which will be processed later.
- When we enable DMA transfer using HAL, all the interrupts associated with it are also enabled.
- As we don’t need the Half Transfer interrupt, we will disable it.
Once the IDLE event occurs, an interrupt will be triggered, and the Rx event callback will be called. Now we will process the data inside this callback.
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if (huart->Instance == USART2)
{
oldPos = newPos; // Update the last position before copying new data
/* If the data in large and it is about to exceed the buffer size, we have to route it to the start of the buffer
* This is to maintain the circular buffer
* The old data in the main buffer will be overlapped
*/
if (oldPos+Size > MainBuf_SIZE) // If the current position + new data size is greater than the main buffer
{
uint16_t datatocopy = MainBuf_SIZE-oldPos; // find out how much space is left in the main buffer
memcpy ((uint8_t *)MainBuf+oldPos, RxBuf, datatocopy); // copy data in that remaining space
oldPos = 0; // point to the start of the buffer
memcpy ((uint8_t *)MainBuf, (uint8_t *)RxBuf+datatocopy, (Size-datatocopy)); // copy the remaining data
newPos = (Size-datatocopy); // update the position
}
/* if the current position + new data size is less than the main buffer
* we will simply copy the data into the buffer and update the position
*/
else
{
memcpy ((uint8_t *)MainBuf+oldPos, RxBuf, Size);
newPos = Size+oldPos;
}
/* start the DMA again */
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, (uint8_t *) RxBuf, RxBuf_SIZE);
__HAL_DMA_DISABLE_IT(&hdma_usart2_rx, DMA_IT_HT);
}
}
- Since RxBuf could be modified at any point due to incoming data, we will save the data into some other buffer (mainBuf).
- Here we will check if the current position + incoming data is less than the size of main buffer
- If it is, then we will save the data normally, and update the current position in the main buffer.
- But if the current position + incoming data exceeds the buffer length, then we need to start saving data from the beginning.
- To do this, we will first find out how much space is left in the buffer, and copy the data into that space.
- now shift the current position to 0, and write the rest of the data.
- And finally update the current position to keep track of it for the next case.
In the end we will start the DMA again, and get ready for the next transfer. We can do the processing of the data in the while loop, or in a RTOS task.
Result
This is more like a working proof, so I would suggest that you watch the video below
16 Comments. Leave new
Something that I have a hard time wrapping my head around is how to implement this is FreeRTOS. is it just a matter of making a task that reads and writes the Ring buffer? All FreeRTOS examples do the usual LED stuff, but communication ? I haven’t seen one yet.
Do you have any tips? Thanks in advance!
This whole idea works on the interrupt basis. So i don’t think there should be a problem with RTOS.
Hello, Thanks for the tutorial, but I’m working with stm32l072cb with an internal osc. HAL_UARTEx_RxEventCallback is never called, could you give a clue to I’m looking for?
Hi,
my first program generated and worked. Then I did the second one and the HAL_UARTEx_RxEventCallback function was not called. I searched and the only thing I found was swapping two lines.
Must be done first during initialization
MX_DMA_Init();
and then only
MX_USART1_UART_Init();
I was trying running it on the blue-pill (f103C8) and every time I start the debugger, it disconnects.
Had to load again another project (with BOOT0 =1) to restore the MCU and it repeat again and again.
The “Ring buffer using head and tail in STM32” works fine, as also other projects including DMA.
Are there any modifications I have to do so it will run on M3 MCU? I know the above-mentioned project had an issue with the F7 series and the uart_isr function needed to be replaced.
I’m using the optional ST-LINK V2 programmer, and I have a log file from the debugger.
I’ll be glad to get some guidance, please.
no modifications are needed. I run this code in bluepill all the time and it works fine
didn’t work with me in stm32f407ve
use UART3
Is it possible to set the IDLE time to a custom value?
nope
Great Post!
Can i ask you please,
why i can not find this function in HAL library HAL_UARTEx_ReceiveToIdle_DMA
even i tried to check it by Stm32f4 and Stm32H7
It is a new function please update your STM32 HAL driver. I checked my driver and found STM32Cube_FW_F4_v1.26.0 has this function.
thx very much
v helpful thx brother
you get a hardfault in stm32h7a3 with mpu