How to use variable length UART protocol efficiently with DMA + Idle Line Interrupt + FreeRTOS + STM32

In this article, you will find the usage of UART with DMA mode. This example is implemented for STM32 microprocessors but can easily be adapted other MCUs.

UART has a useful interrupt handler, called idle line detection, which is triggered if receive line is inactive 1 character frame. This duration is inversely proportional with the baud rate.

Normally, when we use UART with DMA method, we need to know the size of the transmission. After the reception of given sized character, UART will rise a Transfer Complete interrupt. But in the case of variable length reception situation, idle line interrupt comes into play.

We will use DMA in circular buffer mode. In circular mode, it is essential to choose the buffer size large enough to capture all possible data reception. For the cases where received data size is bigger than our DMA buffer size, it is still possible to capture the all data using Half Transfer complete interrupt of DMA. Using this Half Transfer complete or Transfer Complete interrupts, we can capture and process the data. In this example, we will only use Idle Line detection and capture the data in its handler.

We need to think about the circular buffer for normal and overflow cases. We will process the data in a task which waits for a semaphore. This semaphore is released in the Idle Line interrupt handler. In below code, we can see a RTOS thread processing only the UART received data.


void UartIRQ(void const * argument)
{ 
  static uint32_t rx_buffer_old_index;
  uint32_t rx_buffer_new_index;
  uint8_t text[100]; 
  for(;;)
  {
	  osSemaphoreWait(uart_irq_smphrHandle, osWaitForever);
	  rx_buffer_new_index = RX_DMA_BUFFER_LEN - __HAL_DMA_GET_COUNTER(&hdma_uart4_rx);
	  if(rx_buffer_new_index != rx_buffer_old_index) {	//Simple check in receiving data
		  if (rx_buffer_new_index > rx_buffer_old_index) {
			  HAL_UART_Transmit(&huart4, (uint8_t *) (usart_rx_dma_buffer + rx_buffer_old_index ), rx_buffer_new_index - rx_buffer_old_index,  0xFF);
		  }
		  else {	//Overflow mode
			  /* First process the data end of the buffer */
			  HAL_UART_Transmit(&huart4, (uint8_t *) (usart_rx_dma_buffer + rx_buffer_old_index ), RX_DMA_BUFFER_LEN - rx_buffer_old_index,  0xFF);
			  if (rx_buffer_new_index > 0) {/* If remains, process the data beginning of the buffer */
				  HAL_UART_Transmit(&huart4, (uint8_t *) usart_rx_dma_buffer , rx_buffer_new_index,  0xFF);
			  }
		  }
	  }

	  rx_buffer_old_index = rx_buffer_new_index;

	  /* If DMA counter is at the end of the buffer, set it to 0*/
	  if (rx_buffer_old_index == RX_DMA_BUFFER_LEN) {
		  rx_buffer_old_index = 0;
	  }

  } 
}

We only transmit the same data in this task. You can adjust for your system architecture, e.g. using queue to process the incoming data.


You should enable the UART Idle Line interrupt in your initial thread and start the DMA reception.

__HAL_UART_ENABLE_IT(&huart4, UART_IT_IDLE);
HAL_UART_Receive_DMA(&huart4, usart_rx_dma_buffer, RX_DMA_BUFFER_LEN);

And in your interrupt handler, check the idle flag and then clear the idle flag and release the semaphore to process the data in the RTOS thread.

if (__HAL_UART_GET_IT_SOURCE(&huart4, UART_IT_IDLE) && __HAL_UART_GET_FLAG(&huart4, UART_FLAG_IDLE)){
	__HAL_UART_CLEAR_IDLEFLAG(&huart4);
	osSemaphoreRelease(uart_irq_smphrHandle);

Leave a Reply

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