STM32 UART Example

In this post, I will show the examples of using HAL library to manipulate the UART on my NUCLEO-F446RE board. The UART settings are: 115200-8-N-1.

The example can:

UART using polling method

Use HAL_UART_Transmit and HAL_UART_Receive for the UART transmit and receive functionailities, these are blocking functions. This method is normally not used because it blocks other processes, while waiting to finish data reception.

Here is an exmaple:

// main.c

#include <stdio.h>
#include <string.h>

uint32_t count = 0;
uint8_t uartRxBuffer[2] = {0};
uint8_t uartTxBuffer[20] = {0};

...

 while (1)
  {
    count++;
    
    /* UART TX */
    sprintf((char *)uartTxBuffer, "✅Count: %lu\r\n", count);
    HAL_UART_Transmit(&huart2, uartTxBuffer, strlen((char *)uartTxBuffer), 1000); // Transmit the count value

    /* UART RX */ 
    HAL_UART_Receive(&huart2, uartRxBuffer, sizeof(uartRxBuffer), 1000); // Receive data from UART
    HAL_UART_Transmit(&huart2, uartRxBuffer, sizeof(uartRxBuffer), 1000); // Echo back the received data 

    memset(uartRxBuffer, 0, sizeof(uartRxBuffer)); // Clear the receive buffer   

    HAL_Delay(1000); // Delay for 1 second 
  }

In serial monitor it looks like:

UART Polling Method
UART Polling Method

From the output, we can see the problem of using the polling method

If no data is received, it will wait until the time out specified in the HAL_UART_Receive function. Worst case is, if the time out specified is HAL_MAX_DELAY, then the program will not proceed until data has been received via UART.


UART using interrupt method

Use HAL_UART_Receive_IT and HAL_UART_Transmit_IT for the UART transfer using interrupt method. When receiving data using interrupt mode, only when the data is received, it will cause the interrupt and inside the ISR, the received data is echoed back to the serial monitor.

Here is the code implementation:

// main.c

...

/**
 * @brief callback function for UART RX interrupt
 * 
 * @param huart 
 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == USART2)
  {
    HAL_UART_Transmit_IT(&huart2, uartRxBuffer, sizeof(uartRxBuffer)); // Echo back the received data

    HAL_UART_Receive_IT(&huart2, uartRxBuffer, sizeof(uartRxBuffer)); // Re-enable the interrupt
  }
}

...

int main()
{
    ...
    HAL_UART_Receive_IT(&huart2, uartRxBuffer, sizeof(uartRxBuffer));

    while (1)
    {
        sprintf((char *)uartTxBuffer, "✅Count: %lu\r\n", count++);

        HAL_UART_Transmit_IT(&huart2, uartTxBuffer, sizeof(uartTxBuffer));

        HAL_Delay(1000); // Delay for 1 second
    }
}

The performance in the serial monitor:

UART Interrupt Method
UART Interrupt Method

UART using DMA method

HAL_UART_Transmit_DMA and HAL_UART_Receive_DMA can be used for UART transfer using DMA mode. Using DMA is particularly useful when transferring large amounts of data.

Using UART DMA mode also allows to receive unknown length data buffer. In the previous example, the received data has a fixed length, and only when the amount of data has been received, the data is echoed. To allow receiving unknown length buffer, the function HAL_UARTEx_ReceiveToIdle_DMA can be used, it allows to receive an amount of data till either the expected number of data is received or an IDLE event occurs. Note that the ISR callback function is HAL_UARTEx_RxEventCallback.

Here’s how to use DMA mode to receive unknown length of data:

// main.c
uint32_t count = 0;
uint8_t uartRxBuffer[20] = {0};
uint8_t uartTxBuffer[20] = {0};

/**
 * @brief callback function for UART RX idle interrupt
 * 
 * @param huart 
 * @param Size 
 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
  if (huart->Instance == USART2)
  {
    HAL_UART_Transmit_IT(&huart2, uartRxBuffer, Size); // Echo back the received data

    HAL_UARTEx_ReceiveToIdle_DMA(&huart2, uartRxBuffer, sizeof(uartRxBuffer)); // Re-enable the interrupt
    __HAL_DMA_DISABLE_IT(huart->hdmarx, DMA_IT_HT); // Disable half transfer interrupt
  }
}

int main(void)
{
  ...
  
  HAL_UARTEx_ReceiveToIdle_DMA(&huart2, uartRxBuffer, sizeof(uartRxBuffer));
  __HAL_DMA_DISABLE_IT(huart2.hdmarx, DMA_IT_HT); // Disable half transfer interrupt 

  while (1)
  {
    sprintf((char *)uartTxBuffer, "✅Count: %lu\r\n", count++);

    HAL_UART_Transmit_IT(&huart2, uartTxBuffer, sizeof(uartTxBuffer));

    HAL_Delay(1000); // Delay for 1 second
  }
}

The performance in the serial monitor:

UART DMA Method
UART DMA Method

Note:

Using the function HAL_UARTEx_ReceiveToIdle_IT can also receive unknown length of data. Attention that, ONLY when using HAL_UARTEx_ReceiveToIdle_DMA, it’s better to disable the half transfer interrupt. If this is not disabled, the RxEvent will be triggered as well once the received data amount reaches half of the size we set. Call the function __HAL_DMA_DISABLE_IT to disable the half transfer interrupt, see example above.

Reference

  1. Implementing UART receive and transmit functions on an STM32
  2. 串口DMA模式与收发不定长数据