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.

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

Check out the Video Below










Info

You can help with the development by DONATING
To download the code, click DOWNLOAD button and view the Ad. The project will download after the Ad is finished.

22 Comments. Leave new

  • I found a small bug which makes it drop characters under certain conditions. The line
    if (oldPos+Size > MainBuf_SIZE)
    should be
    if (oldPos+Size >= MainBuf_SIZE)

    Reply
  • Thank you very much for this useful tutorial
    When will you make file transfer using the UART where we can save the file in the SD card or USB or FLASH stm32 video?

    Reply
  • Have you tried using IDLE DMA transfers with half duplex UART?

    Reply
  • With FreeRtos it fires only once, how to debug this?

    Reply
    • Allocated memory for task was too low. Increased it and now it works.

      Reply
      • Hi Bob, Can you please guide me how, I allocated 25000 Word as stack size but still triggered only once when sending the AT+RST, I have some wrong caracters sent at different baudrate at first but the rest are good, I am checking my serial port.

        Reply
  • 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!

    Reply
  • 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?

    Reply
    • 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();

      Reply
  • 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.

    Reply
  • didn’t work with me in stm32f407ve

    Reply
  • Is it possible to set the IDLE time to a custom value?

    Reply
  • Roger Sacchelli
    December 29, 2021 10:27 AM

    Great Post!

    Reply
  • 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

    Reply
    • 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.

      Reply
  • thx very much

    Reply
  • v helpful thx brother

    Reply
  • you get a hardfault in stm32h7a3 with mpu

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

keyboard_arrow_up

Adblocker detected! Please consider reading this notice.

We've detected that you are using AdBlock Plus or some other adblocking software which is preventing the page from fully loading.

We don't have any banner, Flash, animation, obnoxious sound, or popup ad. We do not implement these annoying types of ads!

We need money to operate the site, and almost all of it comes from our online advertising.

Please add controllerstech.com to your ad blocking whitelist or disable your adblocking software.

×