cancel
Showing results for 
Search instead for 
Did you mean: 

Implementing UART receive and transmit functions on an STM32

B_Subramanian
ST Employee

Introduction

UART is a communication protocol that enables the user to send data asynchronously through transmit (Tx) and receive (Rx) lines. It involves a shared baud rate between the transmitter and receiver. This article shows you how to set up an STM32 UART project and implement different UART receive and transmit HAL functions.

1. Prerequisites

Software

Hardware

2. Project setup

1. Open STM32CubeIDE and create a new project by going to [File] [New][STM32 Project]

2. Select the board that you’ll be using. For this project, we use the NUCLEO-G071RB.

B_Subramanian_0-1719502105429.png

3. To start this project, we’ll use the default configurations set up by STM32CubeIDE. Note that the UART pins are on PA2 and PA3. 

B_Subramanian_1-1719502162470.png

3. HAL_UART_TRANSMIT & HAL_UART_RECEIVE

HAL_UART_TRANSMIT and HAL_UART_RECEIVE are blocking functions that can be used for the UART transmit and receive functionalities. While this function works for receiving data through UART, it isn’t ideal for most applications. This is because it blocks other processes, while waiting to finish data reception. The below code example highlights this drawback of the function. 

1. Add the UART buffer initialization under /* USER CODE BEGIN PV */

/* USER CODE BEGIN PV */
uint8_t buffer[1];
/* USER CODE END PV */

2. Add the HAL_UART_Receive and HAL_UART_Transmit call under /* USER CODE BEGIN 2 */. The HAL_UART_Transmit call echoes the data back to the terminal.

  /* USER CODE BEGIN 2 */
  HAL_UART_Receive(&huart2, buffer, 1, 0xFFFF);
  HAL_UART_Transmit(&huart2, buffer, 1, 0xFFFF);
  /* USER CODE END 2 */

3. Add the HAL_GPIO_TogglePin command and HAL_Delay under /* USER CODE BEGIN WHILE*/

/* USER CODE BEGIN WHILE */
  while (1)
  {
	HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
	HAL_Delay(100);
/* USER CODE END WHILE */

4. Build and run your program.

5. Open your serial monitoring console within STM32CubeIDE and connect your Nucleo board. Make sure you set the correct baud rate. If you initialized your STM32CubeIDE project with the default settings, it will be 115200. Here you can enter a character from your keyboard to interface with UART.

B_Subramanian_0-1724353001129.png

You will notice that the LED doesn’t start to toggle until the device receives a character, which is echoed back through the terminal. This demonstrates the inefficiency of using the HAL_UART_Receive function for UART communication. 

4. HAL_UART_RECEIVE_IT

HAL_UART_RECEIVE_IT is a non-blocking function that can be used for the UART transmit and receive functionalities. Using this function allows you to increase your program's efficiency by only calling the interrupt when there’s data to be received. This is generally a better approach than the polling method mentioned earlier in this article.  The below code demonstrates an example of using this interrupt. 

1. Enable the UART interrupt in the [Pinout & Configuration] tab of your .ioc file

B_Subramanian_0-1720389503876.png

2. Add the UART buffer initialization under /* USER CODE BEGIN PV */

/* USER CODE BEGIN PV */
uint8_t buffer[1];
/* USER CODE END PV */

3.  Add the initial HAL_UART_Receive_IT call under /* USER CODE BEGIN 2 */

  /* USER CODE BEGIN 2 */
  HAL_UART_Receive_IT(&huart2, buffer, 1);
  /* USER CODE END 2 */

4. Add the HAL_GPIO_TogglePin command and HAL_Delay under /* USER CODE BEGIN WHILE*/

/* USER CODE BEGIN WHILE */
  while (1)
  {
	HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
	HAL_Delay(100);
/* USER CODE END WHILE */

5. Add the interrupt callback under /* USER CODE BEGIN 4*/

/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
	HAL_UART_Receive_IT(&huart2, buffer, 1);
	HAL_UART_Transmit(&huart2, buffer, 1, 0xFFFF);
}
/* USER CODE END 4 */

6. Build and run your program.

7. Open your serial monitoring console within STM32CubeIDE and connect your Nucleo board. Make sure you set the correct baud rate. If you initialized your STM32CubeIDE project with the default settings, it will be 115200. Here you can enter a character from your keyboard to interface with UART.

B_Subramanian_0-1724353001129.png


With this example, you can see that the LED continues to toggle and isn’t blocked by the UART function. Using an interrupt for UART is more efficient for many applications, because the MCU can execute other functions without waiting for UART commands. 

We can also try using an interrupt with the buffer size being 5. 

1. Change the UART buffer initialization to be of size 5 under /* USER CODE BEGIN PV */

/* USER CODE BEGIN PV */
uint8_t buffer[5];
/* USER CODE END PV */

2.  Modify the initial HAL_UART_Receive_IT call under /* USER CODE BEGIN 2 */

  /* USER CODE BEGIN 2 */
  HAL_UART_Receive_IT(&huart2, buffer, 5);
  /* USER CODE END 2 */

3. Edit the interrupt callback under /* USER CODE BEGIN 4*/

/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
	HAL_UART_Receive_IT(&huart2, buffer, 5);
	HAL_UART_Transmit(&huart2, buffer, 5, 0xFFFF);
}
/* USER CODE END 4 */

4. Build and run your program.

5. Open your serial monitoring console within STM32CubeIDE and connect your Nucleo board. Make sure you set the correct baud rate. If you initialized your STM32CubeIDE project with the default settings, it will be 115200. Here you can enter a character from your keyboard to interface with UART.

B_Subramanian_0-1724353001129.png

You can see that when you increase the size to 5, the interrupt callback isn’t reached until you enter 5 characters. At that point, the 5 characters are transmitted from the device. 

5. HAL_UART_RECEIVE_DMA

HAL_UART_RECEIVE_DMA is a non-blocking function that can be used for the UART receive functionality. This function works similarly to the HAL_UART_RECEIVE_IT function above, but it uses DMA to transfer the data instead of utilizing the CPU. This is particularly useful when you’re transferring large amounts of data.

1. Enable the UART interrupt in the [Pinout & Configuration] tab of your .ioc file

B_Subramanian_0-1720389503876.png

2. Add a DMA Channel for UART Rx under the [Pinout & Configuration] tab of the .ioc file

B_Subramanian_1-1720390261284.png

3. Add the UART buffer initialization under /* USER CODE BEGIN PV */

/* USER CODE BEGIN PV */
uint8_t buffer[1];
/* USER CODE END PV */

4. Add the initial HAL_UART_Receive_DMA call under /* USER CODE BEGIN 2 */

  /* USER CODE BEGIN 2 */
  HAL_UART_Receive_DMA(&huart2, buffer, 1);
  /* USER CODE END 2 */

5. Add the HAL_GPIO_TogglePin command and HAL_Delay under /* USER CODE BEGIN WHILE*/

/* USER CODE BEGIN WHILE */
  while (1)
  {
	HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
	HAL_Delay(100);
/* USER CODE END WHILE */

6. Add the interrupt callback under /* USER CODE BEGIN 4*/

/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
	  HAL_UART_Transmit(&huart2, buffer, 1, 0xFFFF);
	  HAL_UART_Receive_DMA(&huart2, buffer, 1);
}
/* USER CODE END 4 */

7. Build and run your program.

8. Open your serial monitoring console within STM32CubeIDE and connect your Nucleo board. Make sure you set the correct baud rate. If you initialized your STM32CubeIDE project with the default settings, it will be 115200. Here you can enter a character from your keyboard to interface with UART.

B_Subramanian_0-1724353001129.png

As you can see through this example, using a DMA channel functions similarly to using an interrupt. The LED will toggle the entire time and isn’t blocked by the UART receive being incomplete. 

Similar to the above, we can change the size to 5 to see how the behavior changes.

1. Change the UART buffer initialization to size 5 under /* USER CODE BEGIN PV */

/* USER CODE BEGIN PV */
uint8_t buffer[1];
/* USER CODE END PV */

2. Change the HAL_UART_Receive_DMA call to size 5 under /* USER CODE BEGIN 2 */

  /* USER CODE BEGIN 2 */
  HAL_UART_Receive_DMA(&huart2, buffer, 1);
  /* USER CODE END 2 */

3. Change the interrupt callback to size 5 under /* USER CODE BEGIN 4*/

/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
	  HAL_UART_Transmit(&huart2, buffer, 5, 0xFFFF);
	  HAL_UART_Receive_DMA(&huart2, buffer, 5);
}
/* USER CODE END 4 */

Similar to the above, the callback is only triggered when all 5 bytes of data are received.

Conclusion

From this article, you can see that there are a few different ways to approach UART when creating your application. The interrupt/DMA methods are ideal since they allow other processing to occur while waiting for UART data. In this article, we also used the STM32CubeIDE embedded terminal. I hope you enjoyed this article and that it has helped you in understanding these essential functions.

Related links

Comments
Andrew Neil
Evangelist III

Having got the basic UART transmission working, see this article for how to link it to the standard C printf:

https://community.st.com/t5/stm32-mcus/how-to-redirect-the-printf-function-to-a-uart-for-debug-messages/ta-p/49865

 

And here's a 3rd-party article on the same:

https://shawnhymel.com/1873/ 

Version history
Last update:
‎2024-10-21 07:13 AM
Updated by: