STM32 HAL Tutorial: UART Input Handling for Embedded Projects
Abstract
Learn how to implement scanf in STM32 projects using HAL. Handle UART input in blocking and non-blocking modes for real-time applications.
1. Introduction
scanf allows STM32 programs to receive input from UART, enabling:
- Interactive debugging
- Command-based control of embedded systems
- Dynamic configuration during runtime
Two main modes:
- Blocking mode: MCU waits until input is fully received
- Non-blocking mode: MCU continues executing while receiving data using interrupts
2. Prerequisites
- STM32 board with UART and a serial2USB converter
- STM32CubeIDE and/or VS Code installed
- Knowledge of HAL and UART
- Serial terminal (e.g., CubeIDE Serial Monitor, PuTTY, minicom)
3. Blocking scanf via UART
Implementation Steps:
- Redirect _read() function to UART
- Use scanf to read formatted input
Example:
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */
/* USER CODE BEGIN 0 */
int __io_getchar(void)
{
uint8_t ch = 0;
__HAL_UART_CLEAR_OREFLAG(&huart2);
HAL_UART_Receive(&huart2, (uint8_t *)&ch, 1, 10000);
return ch;
}
int __io_putchar(int ch)
{
HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 100);
return ch;
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
setvbuf(stdin, NULL, _IONBF, 0);
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
printf("type something! \r\n");
scanf("%5s", data);
printf("typed: %5s \n", data);
}
/* USER CODE END 3 */
- HAL_UART_Receive() blocks until each byte is received
- Simple and works for interactive debugging
4. Non-Blocking scanf via Interrupt
Implementation Steps:
- Use HAL_UART_Receive_IT() to receive bytes asynchronously
- Store bytes in a buffer until Enter (\n) is pressed
- Parse buffer when full
Example:
/* USER CODE BEGIN PD */
#ifdef USE_NON_BLOCKING_IT
#define RX_BUFFER_SIZE 20
uint8_t rxBuffer[RX_BUFFER_SIZE];
uint8_t rxIndex = 0;
#endif
/* USER CODE END PD */
/* USER CODE BEGIN 2 */
setvbuf(stdin, NULL, _IONBF, 0);
#ifdef USE_NON_BLOCKING_IT
printf("type something! \r\n");
HAL_UART_Receive_IT(&huart2, &rxBuffer[rxIndex], 1);
#endif
/* USER CODE END 2 */
/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART2)
{
if(rxBuffer[rxIndex] == '\n')
{
rxBuffer[rxIndex] = 0; // Null-terminate
printf("You entered (non-blocking): %s\n", rxBuffer);
rxIndex = 0; // Reset buffer
}
else
{
rxIndex++;
}
HAL_UART_Receive_IT(&huart2, &rxBuffer[rxIndex], 1);
}
}
/* USER CODE END 4 */
- MCU continues running while waiting for input
- HAL_UART_RxCpltCallback handles each byte
- Buffer is processed once Enter key is detected
5. Hands-On Lab Example
1. Configure the UART with IT
2. Implement either blocking or interrupt
3. Type the message and/or monitor the buffer
4. Test non-blocking mode by performing other tasks (e.g., toggling LED) while receiving input
Tip: For non-blocking modes, always ensure buffer overflow protection
6. Advantages
- Blocking scanf: simple and easy for interactive debugging
- Interrupt/DMA: non-blocking, suitable for real-time embedded systems
- Works with STM32CubeIDE, VS Code, or PlatformIO
- Compatible with HAL UART API


