views
Getting UART working on an STM32 can feel slow if you are new to the HAL library. Sometimes the data just does not send, or it arrives as strange characters. It wastes time and makes you wonder if the hardware is fine. This guide makes it easy.
You will learn how to set up STM32 UART using HAL for both transmit and receive, with clear steps that work on STM32F103, STM32F4, and STM32 Nucleo boards.
What You Need Before You Start
Before sending or reading data, make sure you have:
-
STM32 board (F103, F4, or Nucleo)
-
USB-to-serial adapter or another UART device
-
STM32CubeMX installed
-
HAL library code generation enabled
-
A terminal app like PuTTY, Tera Term, or CoolTerm
Setting Up UART in STM32CubeMX
CubeMX helps you configure pins and HAL drivers without writing all the register code yourself.
-
Select your MCU or board – Example: STM32F103C8T6 or Nucleo-F446RE.
-
Enable UART – Choose USART1, USART2, or another available port.
-
Set baud rate – Common value is 115200 for PC terminals.
-
Set word length, stop bits, parity – Keep default for basic tests (8-N-1).
-
Enable NVIC for UART interrupts if you plan to use interrupt mode.
-
Generate code – CubeMX creates the
MX_USARTx_UART_Init
function for you.
Once done, open your project in STM32CubeIDE or your preferred tool.
UART Transmit with STM32 HAL
The simplest way to send data is the blocking transmit function:
char msg[] = "Hello from STM32\r\n";
HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);
-
&huart2
is the UART handle created by CubeMX. -
The second parameter is the pointer to the data.
-
The third is the number of bytes.
-
The last is the timeout in milliseconds.
For short messages, blocking mode works well. For long transfers or background tasks, consider interrupt or DMA modes.
Sending Data in Interrupt Mode
Interrupt mode sends data in the background while your main code runs:
HAL_UART_Transmit_IT(&huart2, (uint8_t*)msg, strlen(msg));
In this mode, HAL_UART_TxCpltCallback()
runs when the transfer is done.
Sending Data with DMA
DMA offloads the transfer to the DMA controller, which is fast and CPU-friendly:
HAL_UART_Transmit_DMA(&huart2, (uint8_t*)msg, strlen(msg));
Remember to enable DMA in CubeMX for the selected UART.
UART Receive with STM32 HAL
Receiving is similar to sending but has a few more steps.
Blocking Receive
uint8_t rxData[10];
HAL_UART_Receive(&huart2, rxData, sizeof(rxData), HAL_MAX_DELAY);
The function waits until all bytes are received or the timeout expires.
Interrupt Receive
HAL_UART_Receive_IT(&huart2, rxData, sizeof(rxData));
When the buffer fills, HAL_UART_RxCpltCallback()
is called:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART2) {
// Process rxData
HAL_UART_Receive_IT(&huart2, rxData, sizeof(rxData)); // restart receive
}
}
This is more efficient than blocking, especially for event-driven programs.
Continuous Reception with Idle Line Detection
Sometimes you do not know how many bytes will arrive. Use idle line detection:
-
Enable the idle line interrupt in CubeMX or manually:
__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
-
In your IRQ handler, check for the idle flag:
if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(&huart2); // Process data received so far }
This helps with variable-length packets in protocols.
Common Problems and Fixes
-
Garbled data – Check baud rate matches on both ends.
-
No data received – Verify pin mapping in CubeMX and wiring.
-
HAL function hangs – Make sure timeout is set correctly and the other side is sending data.
-
Interrupt never fires – Confirm NVIC interrupt is enabled for that UART.
Example: Echo Program
Here’s a simple example that receives a string and sends it back:
uint8_t rxData[20];
int main(void) {
HAL_Init();
SystemClock_Config();
MX_USART2_UART_Init();
HAL_UART_Receive_IT(&huart2, rxData, sizeof(rxData));
while (1) {
// Main loop can do other work
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART2) {
HAL_UART_Transmit(&huart2, rxData, sizeof(rxData), HAL_MAX_DELAY);
HAL_UART_Receive_IT(&huart2, rxData, sizeof(rxData)); // restart
}
}
Pro Tips for Reliable UART Communication
-
Keep cables short to avoid noise.
-
Use ground connection between devices.
-
For long cables, consider RS-485 or differential drivers.
-
Test with known-good USB-to-UART adapters.
-
Log both transmit and receive data during testing.
FAQ
Who can use this guide?
Anyone with an STM32 board and CubeMX.
What is the best baud rate?
115200 is fast and reliable for short cables.
Where is the data stored after receive?
In the buffer you pass to HAL_UART_Receive
or its variants.
Why use interrupt mode?
It frees the CPU while waiting for data.
How to restart receive after data comes in?
Call HAL_UART_Receive_IT
again in the receive callback.
Will DMA make it faster?
Yes, especially for large data transfers.
How to send strings without strlen?
Pass the fixed length of your buffer instead.
Can I use two UARTs at once?
Yes, each UART has its own HAL handle.
Does HAL work on all STM32 families?
Yes, with the same function names.
How to debug UART?
Check pin config, baud match, and test with a loopback.
Final Words
You now know how to set up STM32 HAL for UART transmit and receive in blocking, interrupt, and DMA modes. You also learned how to handle idle line detection for variable-length data. Start simple, verify each step, and your STM32 board will send and read data reliably in no time.

Comments
0 comment