STM32 UART – Receive unknown size data using DMA and FreeRTOS

S

There are several situations where we need to use a UART/Serial interface to connect our microcontroller with an external device. However, a common issue is that in most of those cases we do not know in advance the size of the messages thus, our final application needs to use either per character interrupt or a more advanced way like DMAs. Each method has it’s own pros/cons. As you can imagine interrupts per character can cause several issues depending on the frequency of the transmission, however it is a lot easier to implement than a DMA approach. In this example we are going to use DMAs and FreeRTOS in a STM32L4XX microcontroller. Beware that the following example should not be used as it is. It is really slow and the purpose is to give you the possibility to check the buffer arrays during the debug to let you understand how you could play with the DMA

In the end of this page you can find the github links (soon).

Step 1: Create your project using the CubeMX and place both RX/TX DMAs on the serial interface. The Tx DMA should be in Normal Mode and the Rx should be in Circular Mode.

Step 2: Activate the NVIC Interrupt for the serial. (it is needed by the DMA)

Step 3: Basically, what we are going to do is to leave the DMA reading the UART and put them in an array. By changing the Mode to circular the DMA will indefinitely continue this operation without interrupting (circular buffering). On top of this, a thread will collect the data and parse them. For that reason we need the following definitions

#define UART_DMA_BUFFER_SIZE 2048
#define PARSER_MESSAGE_LIST_SIZE 8
#define PARSER_MESSAGE_SIZE 1024

Step 4: Assign and activate the UART reception using the DMA and give a (large enough) buffer. Please note that this approach will never fire an interrupt on RX and the array will be used as a circular buffer.

static uint8_t buffer[UART_DMA_BUFFER_SIZE];
...
...
{
    // This should be done before the beginning of our parser, 
    // you can place it in the main or inside the parser
    HAL_UART_Receive_DMA(&huart2, buffer, UART_DMA_BUFFER_SIZE);
}

Step 5: Create a FreeRTOS thread which will monitor the buffer and copy any new data to an intermediate buffer for further processing.

static osThreadId_t uartParserTaskHandle;
...
...
void UARTParser(void* arguments);
...
...
{
  const osThreadAttr_t uartParserTask_attributes2 = {
    .name = "UARTParserTask",
    .priority = (osPriority_t) osPriorityLow,
    .stack_size = 128};
  uartParserTaskHandle = osThreadNew(UARTParser, NULL, &uartParserTask_attributes2);
}

Step 6: Now we will fill the Parser of the UART. As we said before the main purpose is to get all the new available data from the DMA buffer and put them in a list of messages (each complete message should be ended with the ‘\r\n’ characters).

static uint8_t msg_list[PARSER_MESSAGE_LIST_SIZE][PARSER_MESSAGE_SIZE];
...
...
void UARTParser(void* arguments)
{
    size_t dma_head = 0, dma_tail = 0;
    size_t cur_msg_sz = 0;
    size_t cur_msg = 0;
    uint8_t found = 0;
    
    for(;;)
    {
    	do
    	{
    	    __disable_irq();
    	    dma_tail = UART_DMA_BUFFER_SIZE - huart2.hdmarx->Instance->CNDTR;
            __enable_irq();

            if(dma_tail!=dma_head)
            {
            	if(dma_head < dma_tail)
            	{
       		    for(register size_t i=dma_head; i<dma_tail; i++)
            	    {
            	        found = (found == 0 && buffer[i] == '\r') ? 1
            		      : (found == 1 && buffer[i] == '\n') ? 2
            		      : 0;
       			msg_list[cur_msg][cur_msg_sz++]= buffer[i];

       			if(found==2)
            		{
            		    cur_msg = cur_msg == PARSER_MESSAGE_LIST_SIZE-1 ? 0 : cur_msg + 1;
            		    memset(msg_list[cur_msg],0,PARSER_MESSAGE_SIZE);
            		    cur_msg_sz=0;
            		}
            	    }
            	}
            	else
            	{
            	    for(register size_t i=dma_head; i<UART_DMA_BUFFER_SIZE; i++)
		    {
		        found = (found == 0 && buffer[i] == '\r') ? 1
			      : (found == 1 && buffer[i] == '\n') ? 2
			      : 0;
			msg_list[cur_msg][cur_msg_sz++]= buffer[i];

			if(found==2)
			{
 			    cur_msg = cur_msg == PARSER_MESSAGE_LIST_SIZE-1 ? 0 : cur_msg + 1;
			    memset(msg_list[cur_msg],0,PARSER_MESSAGE_SIZE);
            		    cur_msg_sz=0;
			}
		    }
		    for(register size_t i=0; i<dma_tail; i++)
		    {
		        found = (found == 0 && buffer[i] == '\r') ? 1
			      : (found == 1 && buffer[i] == '\n') ? 2
			      : 0;

			msg_list[cur_msg][cur_msg_sz++]= buffer[i];

     		        if(found==2)
			{
			    cur_msg = cur_msg == PARSER_MESSAGE_LIST_SIZE-1 ? 0 : cur_msg + 1;
			    memset(msg_list[cur_msg],0,PARSER_MESSAGE_SIZE);
            		    cur_msg_sz=0;
			}
		    }
            	}
            	dma_head=dma_tail;
            }
        }while(dma_head!=(UART_DMA_BUFFER_SIZE- huart2.hdmarx->Instance->CNDTR));
        osDelay(25); // this should be the minimum time difference between each frame
    }
}

Now, if you run this application and send data through UART, you will see them in the messages list separated by ‘\r\n’.

You can download the example here and if you find it useful please reference this blog in your project. STM32L4-dma-uart-example

Disclaimer: The present content may not be used for training artificial intelligence or machine learning algorithms. All other uses, including search, entertainment, and commercial use, are permitted.

  • Hi i am trying your code to read messages through uart3 from my wifi embedded card on the B-L475E-IOT01A2 board. All messages from the cards ends with \r\n>sp where sp is a space. I have modified the code to reflect this but something is going wrong after the last message is reaceved. The code gets to the while and try to osDelay but crash going in HardFault Handler.
    This is what debugger shows:
    bt
    #0 HardFault_Handler () at ../Core/Src/stm32l4xx_it.c:88
    #1
    #2 0x080042e4 in HAL_TIM_IRQHandler (htim=0x20004794 ) at ../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_tim.c:3181
    #3 0x0800164a in TIM6_DAC_IRQHandler () at ../Core/Src/stm32l4xx_it.c:235
    #4
    #5 0x08008078 in prvPortStartFirstTask () at ../Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/port.c:267
    #6 0x08008186 in xPortStartScheduler () at ../Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/port.c:379
    Backtrace stopped: previous frame inner to this frame (corrupt stack?)

    This is my code:
    https://pastebin.com/CHDMzn8x

  • Hi,

    I have a question here. what happens when dma_head==dma_tail (for example 2048 = 2048), It does not go inside the loop at all.

    • Hi, that is normal because actually when head==tail your buffer is actually empty.. so no pending characters to process

Categories

Tags