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’.

Author

Write A Comment