STM32 Nucleo UART Communication Tutorial with CubeIDE and HAL Libraries

In this tutorial, we will learn to use STM32 Nucleo’s UART communication channels. We will guide you in detail on how to transmit and receive data using the STM32CubeIDE and HAL libraries. First, we’ll illustrate how to send serial data to your system’s serial COM port employing a micro USB cable along with a USB-TTL converter. Then, we’ll demonstrate how to send data to STM32 Nucleo from any serial terminal and simultaneously transmit the same data to the serial terminal. By the end of this tutorial, you’ll have a firm understanding and the necessary hands-on experience to utilize UART communication effectively in your STM32 Nucleo projects.

  • We will be covering two projects in this guide. In the first project, we will use the UART feature of STM32 Nucleo F103RB to send serial data to our system’s serial COM port. We will do that by using a micro USB cable and by using a USB-TTL converter and display it on our terminal.
  • In the second example, we will send data to STM32 Nucleo from any serial terminal and transmit the same data to the serial terminal. In other words, we will see example data transmit and receive in this example

As discussed earlier, we will be using STM32CubeIDE, therefore, you should have the latest version installed on your system. You can follow this guide to install it:

Why use the UART Port of the STM32 Nucleo F103RB?

Whenever we work on embedded system applications development sooner or later we need to use a serial communication protocol. We often use UART/USART to transfer data between microcontroller and computer for various purposes. One of the most important applications is to display data on the serial console of the computer for debugging or logging important events during program execution on a microcontroller. Furthermore, many wireless devices such as GSM, GPS, Bluetooth, Xbee, LoRA, and many others provide a serial interface to transfer data between these devices and a microcontroller. 

STM32 Nucleo F103RB pinout

In the last tutorials, we have seen how to use STM32 Nucleo F103RB GPIO pins as digital output or digital input pins. You can read these articles on these links: 

STM32 Nucleo F103RB UART Ports

STM32F103 consists of three UART modules: UART1, UART2, and UART3.

Nucleo-F103RB UART Pins

We can use any UART module for serial data transmission. However, unlike Blue Pill STM32 microcontroller board, Nucleo-F103RB has an onboard ST-Link debugger. Therefore, if we do not want to use a USB-TTL converter, we have to use UART2 on PA2 and PA3.

In this tutorial, we will implement using both methods.

Connect FTDI USB to Serial with STM32 Nucleo F103RB

Let us show you how to connect the FTDI programmer with Nucleo-F103RB STM32. As we will be transmitting serial data to the system, we will only connect the transmission pin TX with the RX pin of the programmer.

For this guide, we will use the UART1 module pins.

STM32 Nucleo F103RB UART Data Transmit Example

We will use STM32Cube IDE to program our STM32 board. Open the IDE and head over to a new project.

Then for the target selection, specify the STM32 Nucleo F103RB board. After that click on any column as shown in the picture below. Then click the ‘Next’ button.

select STM32 Nucleo from board selector

The following window will open. Here specify the name of your project then click ‘Finish’ to complete the setup of your project.

Now head over to Connectivity > USART1 and set the mode as ‘Asynchronous.’
You can view the parameter settings of the UART including the baud rate, word length, parity, etc.

Nucleo UART2 enable

Now go to System Core > RCC then select ‘Crystal/Ceramic Resonator’ in from the High Speed Clock feature.

Nucleo HSE Enable

Now we have enabled the RCC external clock source.

Clock Configuration

Next go to the Clock Configuration found at the top. This will open the following window. Here we will select the clock frequency.

Blue Pill STM32 Creating project Digital Input picture 8

You can specify your system clock. We will set it as 72 MHz. These are the configurations we have set:

Blue Pill STM32 Creating project Digital Input picture 9

Now we will save our file. Press Ctrl + S. The following window will appear. Click ‘Yes.’ This will generate a template code for you.

Blue Pill STM32 using STM32Cube creating project pic 11

Another window will appear that will ask if you want to open the perspective. Click ‘Yes.’

Blue Pill STM32 Creating project Digital Input picture 11

Serial Data Print with STM32 Nucleo F103RB Code

Now the following page opens. On the right side, you will be able to view the Outline of the code. This happened because we opened our code with perspective. In the center, you can view the main.c file and on the left you can view Project Explorer. If you want to go to the Device Configuration Tool, then click the .ioc tab.

Now let us look at our main.c file that was generated.
Inside the main.c file, specific locations are provided to write your code. This is because if you go to .ioc file again to edit any peripheral setting and generate code again, any code outside those areas will be deleted. So, it is best to keep your code in those placeholders so that you do not lose your code.

Code File

Modifying Code

Now, STM32CubeIDE takes care of all the declarations and initializations which are required for peripheral initialization as per your .ioc files. You just need to write the code which fulfills your application requirements. So, keep all the code generated and in addition to that, add the lines in bold at the locations defined below.

int main(void)
{
    /* USER CODE BEGIN 1 */
	uint8_t message[35] = {'\0'};
	uint8_t num = 0;
    /* USER CODE END 1 */


    /* USER CODE BEGIN WHILE */
  while (1)
  {
	  sprintf(message, "Welcome to lab! Counting = %d\r\n", num);
	  HAL_UART_Transmit(&huart2, message, sizeof(message), 100);
	  HAL_Delay(500);
	  num++;
    /* USER CODE END WHILE */
 

HAL UART Data Transmit Function

In Polling mode IO operation of UART module of STM32 Nucleo F103RB, HAL_UART_Transmit() is used to send an amount of data in blocking mode HAL_UART_Receive() is used to receive an amount of data in blocking mode. These APIs are :

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
  • First parameter huart is a pointer to a UART_HandleTypeDef structure that contains the configuration information for the specified UART module.
  • Second parameter pData is a Pointer to data buffer that we want to transmit
  • Third parameter is the number of data elements (u8 or u16) to be sent
  • Last parameter is a timeout duration for this function to retry.
  • The return type of this function is a HAL status enum which contains status.
typedef enum 
{
  HAL_OK       = 0x00U,
  HAL_ERROR    = 0x01U,
  HAL_BUSY     = 0x02U,
  HAL_TIMEOUT  = 0x03U
} HAL_StatusTypeDef;

In short, the above code will print messages with counter values on the system console after every 500ms.

main.c file

This is how a complete main.c file will be after modification. Save the main.c file after modifying it. Now we are ready to build our project.

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

UART_HandleTypeDef huart2;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
	uint8_t message[35] = {'\0'};
	uint8_t num = 0;
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {

	  sprintf(message, "Welcome to lab! Counting = %d\r\n", num);
	  HAL_UART_Transmit(&huart2, message, sizeof(message), 100);
	  HAL_Delay(500);
	  num++;

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief USART2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART2_UART_Init(void)
{

  /* USER CODE BEGIN USART2_Init 0 */

  /* USER CODE END USART2_Init 0 */

  /* USER CODE BEGIN USART2_Init 1 */

  /* USER CODE END USART2_Init 1 */
  huart2.Instance = USART2;
  huart2.Init.BaudRate = 115200;
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART2_Init 2 */

  /* USER CODE END USART2_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin : B1_Pin */
  GPIO_InitStruct.Pin = B1_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : LD2_Pin */
  GPIO_InitStruct.Pin = LD2_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(LD2_GPIO_Port, &GPIO_InitStruct);

  /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);

/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

Building the Project

To build our project press Ctrl + B or go to Project > Build All.

Your project will start building. After a few moments, your project will be successfully built if there are no errors.

Running the Project

Next press the RUN button in the IDE. The ‘Edit configuration’ window will open up. Click ‘OK’.

After a few moments, the code will be successfully sent to the STM32 board.

STM32 Serial Data Print Run Project 2

Otherwise, press the RESET button on your STM32 board.

Open Device Manager and head over to Ports to check the COM port through which the STM32-Nucleo103RB board is connected. It is COM6 in our case.

Open a terminal in your system to view the messages being transmitted by the board. Lets show you how to open a terminal from Cube IDE. First click the Open Console icon as shown below. Then click ‘Command Shell Console.’

STM32 Serial Data Print Project Command Shell Console 1

Select the remote connection by first creating a New connection.

STM32 Serial Data Print Project Command Shell Console 2

Specify the name of the connection, serial port and baud rate. Then click Finish to create a new connection.

The console will open for the connection we set up. Press the Reset button of STM32 and the messages will start appearing on the console after every 0.5 seconds.

STM32 Serial Data Print Project Command Shell Console 4

Additionally, we can use other terminals on our system as well for example Putty. We will discuss this in following example.

Connecting USB-TTL converter with Nucleo-F103RB board

Now if we don’t want to use UART2 or if we want to connect USB-TTL converter with Nucleo board, we can also do that. In this example we will use UART1 in addition to all the settings done above and display the output of both UARTs, one using micro-USB of Nucleo board (as before) and the other using USB_TTL converter.

For this we will go back to .ioc file and also enable UART1 in asynchronous mode at a baud rate of 115200.

After that we will save the file and generate code. We will see that it will generate and initialize code for UART1 in main.c while our old code is also intact (if placed in proper placeholders).

Now we will transmit the same messege on UART1.

Build and Run the project.

Below you can find how can we connect FTDI USB-TTL converter with STM32F103RB Nucleo board.

Now open serial terminal on STM32CubeIDE and other on Putty. To open a serial terminal on Putty, set the correct speed and COM port (COM10 in our case) and then press Open to establish a connection.

Press the Reset button of STM32 and the messages will start appearing on the terminal after a short delay.

Below you can see that we are receiving data from both UARTs. on black screen putty display is showing output of UART1 on COM10 while on white display from STM32CubeIDE, data from UART@ is displayed at COM6.

STM32 Nucleo F103RB UART Data Receive Example

In the last section, we have seen how to send data with STM32 over UART. Similarly, we can also receive data from other devices over UART with STM32.

HAL UART Data Receive Function

HAL_UART_Receive function receives an amount of data in blocking mode or in polling method on the selected UART channel receive pin.

HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
  • First parameter huart is a pointer to a UART_HandleTypeDef structure that contains the configuration information for the specified UART module.
  • Second parameter pData is a pointer to a data buffer in which we want to store received data
  • Third parameter is the number of data elements (u8 or u16) to be received
  • Last parameter is a timeout duration for this function to retry.
  • The return type of this function is a HAL status enum which contains status

STM32 UART Data Receive Code

Now modify the main.c file with the following code:

  /* USER CODE BEGIN WHILE */  
while (1)
  {
          uint8_t buffer[10]= {'\0'};
	  HAL_UART_Receive(&huart2, buffer, sizeof(buffer), HAL_MAX_DELAY);
	  HAL_UART_Transmit(&huart2, buffer, sizeof(buffer), HAL_MAX_DELAY);

     /* USER CODE END WHILE */

The above will code echo back whatever it will receive on the Rx pin of UART2 to the Tx pin of UART1. In other words. Whatever you type on UART1 (COM10), it will be sent to the STM32 and STM32 will transmit it back to the computer through UART2 (COM6) transmit function which is HAL_UART_Transmit().

Follow the same steps as mentioned above to open the serial console after uploading code to the STM32 Nucleo F103RB and you will get the following output:

In summary, we learned the following in this tutorial:

In conclusion, this comprehensive tutorial provides a guide on how to use the UART communication channels of the STM32 Nucleo, particularly utilizing STM32CubeIDE and HAL libraries. We discussed the significance of UART Port within embedded system applications, walked through step-by-step processes for STM32 Nucleo F103RB UART data transmit and receive examples, and delved into how to establish connections with an FTDI programmer. Lastly, we explored the essentials of the HAL UART data transmit and receive functions. Using this knowledge, readers should be well-equipped to effectively implement UART communication in their embedded system projects using the STM32 Nucleo platform.

  • STM32 Nucleo F103RB UART Ports and channels
  • HAL USART/UART Driver 
  • How to use STM32F1 UART in polling mode
  • How to transmit data with STM32 UART
  • How to receive data with STM32 UART

You may also like to read:

Leave a Comment