Until now, results were only observed via LEDs and pins, but now the chip communicates with a person or PC in text. UART (Universal Asynchronous Receiver/Transmitter) is the oldest and most widely used communication method, used everywhere from debug message output, sensor module communication, to bootloaders. STM32's USART includes UART functionality and performs two-way communication with only two wires (TX/RX).
In this chapter, we set USART1 to 9600bps to send a greeting string and implement an echo, returning any received characters as they are. You can directly check the transmitted and received text in the µVision simulator’s Serial window, making UART the easiest peripheral to verify with the simulator.
Learning Objectives
- Understand the principles of UART asynchronous communication and the frame (8N1).
- Set the pins (PA9/PA10) and clock for USART1.
- Calculate and set the baud rate with
BRR. - Implement polling-based transmission and reception with the TXE/RXNE flags.
- Verify transmission and reception in the µVision Serial window.
Target Environment
- Compiler: Keil MDK-ARM (Arm Compiler 6).
- Device: STM32F103C8 (μVision simulator).
- Verification: µVision simulator (
View → Serial Windows → UART #1). BRR calculation on PCgcc(pc_test.c).
Key Concepts
1) UART Asynchronous Communication
UART transmits and receives data at a pre-agreed speed (baud rate) without a clock line. The transmitter TX connects to the receiver RX (crossed), and they share GND. If the speeds don’t match, characters get corrupted.
2) Frame — 8N1
The most common setting is 8N1: 8 data bits, no parity (None), 1 stop bit. One character is transmitted as start bit (1) + data (8) + stop bit (1) = 10 bits.
3) USART1 Pins and Clock
| Signal | Pin | Mode |
|---|---|---|
| TX | PA9 | AF Push-Pull (0xB) |
| RX | PA10 | Floating Input (0x4) |
USART1 is on the APB2 bus, so turn it on via RCC_APB2ENR_USART1EN. Also, enable the AFIO clock for pin remapping.
4) Baud Rate Setting — BRR
BRR = fCK / baud
The fCK of USART1 is PCLK2 (=72MHz). For 9600bps, write 72000000 / 9600 = 7500 to BRR.
5) Transmission and Reception — Polling
- Transmit: Wait for
SRTXE (transmit buffer empty) and then write toDR. - Receive: Check
SRRXNE (receive buffer full) and read fromDR(reading automatically clears RXNE).
while (!(USART1->SR & USART_SR_TXE)) { } /*Standby to send*/
USART1->DR = c; /*Send one character*/
Creating a μVision Project
Project → New µVision Project…→ name itstm32_usart.- Device: STM32F103C8.
Manage Run-Time Environment→ CMSIS:CORE, Device:Startup.- After adding
main.c, copy and paste. - Apply
STM32F10X_MDtoC/C++ → Define. Debug → Use Simulator,-pSTM32F103C8.- Rebuild(F7) → Debug(Ctrl+F5).
Open
View → Serial Windows → UART #1.
Examples
Example 1 — main.c: Greeting + Echo
#include "stm32f10x.h"
/*
*Lesson 19 Example — USART1 Transmission + Echo.
*Target: STM32F103C8, Compiler: Keil MDK-ARM.
*
*USART1: TX=PA9, RX=PA10 (APB2 Bus). 9600 8N1.
*Send a greeting string at startup, then echo back received characters.
*Baud rate divider BRR = fCK / baud, fCK of USART1 is PCLK2(=72MHz).
* BRR = 72000000 / 9600 = 7500
*/
#define BAUD_BRR 7500u /* 72MHz / 9600 */
static void usart1_init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_AFIOEN | RCC_APB2ENR_USART1EN;
/*PA9 = TX: AF push-pull (0xB), PA10 = RX: floating input (0x4). Both CRH.*/
GPIOA->CRH &= ~((0xFu << ((9u - 8u) * 4u)) | (0xFu << ((10u - 8u) * 4u)));
GPIOA->CRH |= (0xBu << ((9u - 8u) * 4u)); /*PA9 AF push-pull*/
GPIOA->CRH |= (0x4u << ((10u - 8u) * 4u)); /*PA10 floating input*/
USART1->BRR = BAUD_BRR; /* 9600 baud */
USART1->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE; /*Transmit/Receive + Enable*/
}
static void usart1_send_char(char c)
{
while (!(USART1->SR & USART_SR_TXE)) { } /*Wait until the transmit buffer is empty*/
USART1->DR = (uint16_t)c;
}
static void usart1_send_str(const char *s)
{
while (*s) {
usart1_send_char(*s++);
}
}
int main(void)
{
usart1_init();
usart1_send_str("Hello, STM32 UART!\r\n");
while (1) {
if (USART1->SR & USART_SR_RXNE) { /*Is there received data?*/
char c = (char)USART1->DR; /*Reading automatically clears RXNE*/
usart1_send_char(c); /*Echo as is*/
}
}
}
Example 2 — pc_test.c: BRR Calculation
#include <stdio.h>
#include <stdint.h>
/*
*Lecture 19 PC Check — USART BRR (Baud Rate Register) Calculation.
*BRR = fCK / baud (for USART1, PCLK2 = 72MHz).
*If you truncate to an integer, the actual baud rate becomes fCK/BRR, so a slight error can occur.
*(72MHz divides well into standard baud rates, so the error is 0.)
*/
static void report(uint32_t fck, uint32_t baud)
{
uint32_t brr = fck / baud;
uint32_t actual = fck / brr;
printf("baud %lu -> BRR=%lu (actual %lu)\n",
(unsigned long)baud, (unsigned long)brr, (unsigned long)actual);
}
int main(void)
{
uint32_t fck = 72000000u;
report(fck, 9600u);
report(fck, 19200u);
report(fck, 38400u);
report(fck, 57600u);
report(fck, 115200u);
return 0;
}
Checking with Simulator
- Open
View → Serial Windows → UART #1after starting debug. - When you run (F5),
Hello, STM32 UART!will appear in the UART #1 window. - If you type characters in the UART #1 window (delivered to RX), the same characters echo back.
- Check via register whether
USART1->BRRis7500(0x1D4C) or whether CR1's UE/TE/RE-is is 1. - Expected behavior: After printing the greeting string, the input character is returned as it was.
Verification on a PC
gcc pc_test.c -o pc_test && ./pc_test
Expected output (stdout):
baud 9600 -> BRR=7500 (actual 9600)
baud 19200 -> BRR=3750 (actual 19200)
baud 38400 -> BRR=1875 (actual 38400)
baud 57600 -> BRR=1250 (actual 57600)
baud 115200 -> BRR=625 (actual 115200)
Common Mistakes
Q. The characters are garbled (□□□).
A. Baud rate mismatch is the most common. Check if the fCK used in BRR calculation matches the actual clock (USART1 has PCLK2=72MHz) and if the terminal baud rate is the same.
Q. Only the first character appears and then it stops.
A. If you write continuously to DR without waiting for TXE, it overwrites the previous character. Wait with while(!(SR & TXE)) before each character.
Q. Echo is not working.
A. Check whether RE (Receiver Enable) is turned on in CR1, whether RXNE is checked, and whether DR is read. RXNE is cleared and the next reception is possible only after reading DR.
Q. I set PA9/PA10 to general input/output.
A. TX (PA9) must be AF push-pull (0xB), RX (PA10) must be input for USART signals to be connected to the pins. If set as general output, USART cannot use the pins.
Summary
- UART communicates asynchronously with two lines (TX/RX) that match the baud rate (8N1 is common).
- USART1 is PA9 (TX)/PA10 (RX), APB2 bus, BRR=fCK/baud.
- Transmission: wait for TXE then write DR; Reception: check RXNE then read DR.
- Check transmitted and received text directly using the simulator UART #1 window.
- TX must be set to AF push-pull.
Try it yourself
Solve the assignment below. The reference solution is shown below.