UART Communication on STM32. Transmission to PC | STM32 on Registers #6
We have already learned the basic operations on GPIO, as well as ways to delay individual tasks. Today we will go beyond the microcontroller. We will communicate with our computer via the UART interface.
In this post we will focus on transmission, i.e. sending data from the STM32 somewhere outside. That external device will be our computer. We will receive the data in a terminal that emulates a serial port, which is essentially what the UART interface is.
👉 Do you want to learn STM32 programming on registers in full? I invite you to my complete course on this topic (the course is conducted in Polish): https://stm32narejestrach.pl
STM32 on Registers series on YouTube
These posts are created in parallel with a series on my YouTube channel on the same topic. If you prefer the video version, feel free to check it out there. These articles are a shortened version of what I show on YouTube.
Link to the full YouTube playlist
UART Interface
The UART interface is one of the simplest ways to communicate between devices. Even its name means something simple and universal 👇
U – Universal, meaning for all kinds of applications
A – Asynchronous, meaning without a clock signal maintaining the communication rhythm
R – Receiver, i.e. Rx
T – Transmitter, i.e. Tx
It is a bidirectional interface. Each direction has its own separate pin. Communication over UART takes place only between two devices. There are, of course, ways to increase the number of devices, but in its “normal” form it is 1:1 communication. The Tx and Rx lines are connected crosswise, i.e. the transmitter of one device is connected to the receiver of the other, and vice versa.
Communication is asynchronous, which means there is no clock signal driving the transmission. Both sides must “agree” on the transmission speed and the duration of individual bits. UART often uses standard speeds expressed in a funny unit – baud per second. These include 9600, 19200, 38400, 115200 baud.
In addition to data, there are also so-called start bits, stop bits, and parity bits, which are a primitive form of checking the correctness of transmitted data. The most popular frame format here is the so-called 8n1, which means 8 data bits, no parity bit, and one stop bit.
UART is used in the popular RS232 standard. In principle, you can say it’s the same thing, except that RS232 is an additional voltage standard. On the computer side, we must have the same interface. Unfortunately, RS232 has not been available for many years…
There is no need to worry, because there is a very simple solution. It is called a USB–UART converter. You plug such a small dongle into USB and you already have UART in the form of a virtual COM port. That’s it. You can find such converters in my shop. There are several types, so you will find something for yourself.

We, however, are using a Nucleo board, and it already has such a converter built in. Specifically, it is in the ST-Link, which is a programmer, debugger, and also such a converter. It is enough to install the ST-Link drivers for the converter to work.
Where is it connected to the STM32? Let’s check.
Connecting the USB–UART ST-Link from Nucleo to STM32
The most convenient way will be to take the schematic of the Nucleo board. In it, we look for the fragment responsible for the ST-Link programmer section. We look for pins marked as TX and RX. We have something like STLK_TX and STLK_RX, and these are our candidates. There are so-called labels here, so we should look for the same labels on the side of the microcontroller we are programming. We can click through these labels.

Following the STLK_TX trace takes us to the general block diagram. We see that this pin is connected to the VCP_RX label. Note that this is a cross connection! Let’s follow this label.

And we almost have it. The VCP_RX pin can be connected to PA3 or PB7. We see jumpers here called SBxx. DNF means that this jumper is not populated, so there is no connection. The other jumpers are soldered on the board, or at least they should be by default. Thus, we see that the UART going to the ST-Link is on pins PA2 and PA3.

Let’s take a look in the Datasheet to see if these pins are described as UART.

We see that there is USART2. This is very important information! In our C031 we have two UARTs, and larger STM32s have even more. Therefore, we will always refer to the UART together with its number.
Configuring UART Pins on STM32
The pins are identified – now they need to be configured. To “bring out” the UART interface to the microcontroller pins, the pins must be configured appropriately. These are the so-called Alternate Functions. We need to inform the GPIO port that we want to connect a given physical pin to an alternate function. We redirect this pin to the UART interface.
We will start with a bare, configured project. We can copy our initial project or any other and clear the code. We haven’t written much so far, so it’s manageable to clean up these projects 🙂
Since we are configuring an alternate function in GPIO, we first need to enable the appropriate GPIO clock. We are working on port A.
void UART2_Config(void)
{
RCC->IOPENR |= RCC_IOPENR_GPIOAEN;
}
Now we need to redirect this pin in the GPIO registers. First, we need to set the pin mode to AF, i.e. Alternate Function. We do this in the GPIOx_MODER register.
IMPORTANT! In this lesson, let’s try to set only those bits that are not in the state we require after reset. You will see that the code can be shorter thanks to this. Also remember that this is after reset. This does not mean that during reconfiguration while the microcontroller is running it will be the same.
void UART2_Config(void)
{
RCC->IOPENR |= RCC_IOPENR_GPIOAEN;
// Pin PA2 - TX AF Mode
GPIOA->MODER &= ~(GPIO_MODER_MODE2_0);
}
Now we need to select which alternate function we are setting. This is done using the GPIOx_AFRH and GPIOx_AFRL registers. Here it is described somewhat enigmatically. Each pin has as many as 4 bits, which gives 16 possibilities. We need to set PA2 and PA3, so we are interested in AFSEL2 and AFSEL3. But which exact alternate functions should we set them to? You can find this in the Datasheet below the table with the pinout.

For pin PA2, UART2_TX is under AF1. For PA3, UART_RX is also under AF1. So we need to set it like this.

There is one more “obstacle”. In the CMSIS headers there are no AFRH and AFRL registers. There is a two-element AFR array.

The letters H and L mean high and low registers. In CMSIS they are combined. AFR[0] is the low register (L), and AFR[1] is the high register (H). After reset, this register has a value of zero in its entirety. This time we will set only the bit that we need. This gives us the following code.
We will separate the RX and TX configuration to keep things organized.
void UART2_Config(void)
{
RCC->IOPENR |= RCC_IOPENR_GPIOAEN;
// Pin PA2 - TX AF Mode
GPIOA->MODER &= ~(GPIO_MODER_MODE2_0);
GPIOA->AFR[0] |= GPIO_AFRL_AFSEL2_0;
// Pin PA3 - RX AF Mode
GPIOA->MODER &= ~(GPIO_MODER_MODE3_0);
GPIOA->AFR[0] |= GPIO_AFRL_AFSEL3_0;
Do we need to set anything else? Let’s see what the Reference Manual says about Alternate Functions.


We can read there that for AF we also configure the remaining GPIO elements. The input blocks are the same, so there are also pull-up, pull-down, and output type selection (Push-Pull or Open Drain). This needs to be configured. How? Let’s think about it.
First, the RX pin, i.e. the receiver. Since it receives, it overlaps with the GPIO Input part. To ensure that the Output part does not interfere at all, we will set it to Open Drain just to be safe. After reset it is push-pull, so we need to change that. As for GPIO speed, the lowest setting is sufficient here, which is the default after reset. We also give up on PUPD resistors. They are not necessary for the UART interface. Let’s now set what needs to be set.
void UART2_Config(void)
{
RCC->IOPENR |= RCC_IOPENR_GPIOAEN;
// Pin PA2 - TX AF Mode
GPIOA->MODER &= ~(GPIO_MODER_MODE2_0);
GPIOA->AFR[0] |= GPIO_AFRL_AFSEL2_0;
// Pin PA3 - RX AF Mode
GPIOA->MODER &= ~(GPIO_MODER_MODE3_0);
GPIOA->AFR[0] |= GPIO_AFRL_AFSEL3_0;
GPIOA->OTYPER |= GPIO_OTYPER_OT3;
}
Now TX. It is an output, so what matters is that we correctly set high and low states on this output. We need to set (or rather, it is already set after reset) Push-Pull to achieve this. We also skip PUPD because it makes no sense with PP. Lowest speed.
So the code remains unchanged!
Configuring UART on STM32
We have the pins set, so we can configure the UART itself. It is a separate block, so first we need to provide it with a clock. We do this similarly to GPIO, but in the appropriate RCC register.
void UART2_Config(void)
{
RCC->IOPENR |= RCC_IOPENR_GPIOAEN;
// Pin PA2 - TX AF Mode
GPIOA->MODER &= ~(GPIO_MODER_MODE2_0);
GPIOA->AFR[0] |= GPIO_AFRL_AFSEL2_0;
// Pin PA3 - RX AF Mode
GPIOA->MODER &= ~(GPIO_MODER_MODE3_0);
GPIOA->AFR[0] |= GPIO_AFRL_AFSEL3_0;
GPIOA->OTYPER |= GPIO_OTYPER_OT3;
// Enable USART2 Clock
RCC->APBENR1 |= RCC_APBENR1_USART2EN;
}
Once the clock is supplied to the UART, we can configure it properly. The first thing we will set is the transmission speed. As mentioned earlier, devices must agree on a speed. We will operate at 115200 baud. This is one of the standard speeds – not too fast, not too slow. My favorite 🙂
This is done using the UARTx_BRR register. You need to enter an appropriate value there, which is obtained from the UART clock frequency and the desired transmission speed. The formula is presented in the Reference Manual.

As you can see, there are two possibilities. They depend on the oversampling setting. After reset it is set to 16 (a task for you – confirm this in the registers and write in the comments which bit in which register is responsible for this!). Therefore, the first formula applies.
Where do we get the clock that goes to the UART? We can use the diagram from the Reference Manual (page 108).

We see that UART1 is probably driven by the PCLK. But we have UART2, so where is it? If there is no dedicated multiplexer for a given peripheral, it will be clocked from the APB peripherals. In larger devices, you will encounter two APB buses that may have different clock speeds. Then, in the Datasheet, you must check which APB a specific UART is connected to. Let’s take a look at our DS (page 10).
It is clear as day that UART1 and UART2 communicate via the APB bus, so we need to know the PCLK frequency.

From lesson #3 we remember that HCLK is 12 MHz, because the internal clock is divided by 4. PCLK is derived from HCLK, so we need to check how the divider between HCLK and PCLK is set. The Reference Manual shows that it is a value of 1.


So 12 MHz goes to the UART, and we will use this value. We must enter it in the base unit. We divide it directly by the target value, i.e. 115200.
void UART2_Config(void)
{
RCC->IOPENR |= RCC_IOPENR_GPIOAEN;
// Pin PA2 - TX AF Mode
GPIOA->MODER &= ~(GPIO_MODER_MODE2_0);
GPIOA->AFR[0] |= GPIO_AFRL_AFSEL2_0;
// Pin PA3 - RX AF Mode
GPIOA->MODER &= ~(GPIO_MODER_MODE3_0);
GPIOA->AFR[0] |= GPIO_AFRL_AFSEL3_0;
GPIOA->OTYPER |= GPIO_OTYPER_OT3;
// Enable USART2 Clock
RCC->APBENR1 |= RCC_APBENR1_USART2EN;
// 115200 baudrate
USART2->BRR = 12000000/115200;
}
The result of the division is about 104. Unfortunately, it is not exact and there is a fractional remainder. At this point, I need to mention that there are so-called UART-friendly frequencies. These are clock frequencies for which this fractional part is minimal, and often zero. This affects the number of transmission errors. Fortunately for us, there will not be too many errors here, but it is better to use such friendly clock frequencies.
In STM32 documentation, you can find a formula for transmission error. STM32 has so many dividers that, in my opinion, there is nothing to worry about. You can easily achieve errors below 1%, which is almost ideal 🙂
Did you know that we still haven’t enabled the UART? 🙂 We have only supplied it with a clock. We need to enable:
- the UART itself
- the transmitter separately
- the receiver separately
These are 3 individual bits to be enabled in the UART registers. Let’s check which ones in the Reference Manual. You can enable each separately, or all at once in a single line.
void UART2_Config(void)
{
RCC->IOPENR |= RCC_IOPENR_GPIOAEN;
// Pin PA2 - TX AF Mode
GPIOA->MODER &= ~(GPIO_MODER_MODE2_0);
GPIOA->AFR[0] |= GPIO_AFRL_AFSEL2_0;
// Pin PA3 - RX AF Mode
GPIOA->MODER &= ~(GPIO_MODER_MODE3_0);
GPIOA->AFR[0] |= GPIO_AFRL_AFSEL3_0;
GPIOA->OTYPER |= GPIO_OTYPER_OT3;
// Enable USART2 Clock
RCC->APBENR1 |= RCC_APBENR1_USART2EN;
// 115200 baudrate
USART2->BRR = 12000000/115200;
// USART2->CR1 |= USART_CR1_UE; // UART Enable
// USART2->CR1 |= USART_CR1_TE; // Transmitter Enable`
// USART2->CR1 |= USART_CR1_RE; // Receiver Enable
USART2->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;
}
We still need to take care of the frame format. We need to set 8n1. This is done using the M0, M1, PCE bits in the CR1 register and STOP in CR2. Let’s check how to set them. First, CR1.

M0 and M1 are bits responsible for data length. Interestingly, they are not next to each other in the register. We need to set them to 0b00. The entire register is zero after reset, so no action is required.


PCE means parity control. 8n1 – the “n” means we don’t want this feature. It must be set to 0, which is already the case after reset.

The STOP bits in CR2 configure the length of the stop bits in the UART frame. We want one, so they should be set to 0b00, which is also the case after reset.


It turns out that the frame format is by default exactly what we need. I wonder where that came from, knowing that this is the most popular UART frame format? 🤔
And that’s all we need in the basic and simplest configuration. This is the code that configures UART2 for operation.
UART Transmission on STM32
First, we will send something to the computer. With the UART configured, we only need to:
- Enable the transmitter. We did this during configuration.
- Put a byte into the transmit register.
- Wait until the transmission is complete. This means the TXFNF bit = 1, i.e. the register is not full (it can accept data).
- Repeat steps 2–3 as many times as there are bytes to send.
In the C031 we have two data registers. One receive (RDR) and one transmit (TDR). More often, however, you will have a single common register (DR), under which there are actually two separate ones. Reading and writing still use different registers, even if it is hidden from the programmer behind a single “door”.
So let’s try to send something over UART. We will use a software timer to send cyclically. Putting data into the buffer is simple. Checking whether the transmission has finished is another matter.
// Software Timers variables
uint32_t Timer_UART2;
/* Loop forever */
while(1)
{
// UART2 transmit
if((GetSystemTick() - Timer_UART2) > UART2_TIMER)
{
Timer_UART2 = GetSystemTick();
// Put data to transmit register
USART2->TDR = 'A';
//
// ..wait for end of the transfer
//
}
}
The TXFNF bit is located in the ISR status register. Zero means that the FIFO queue leading to the transmit register is full and we cannot put anything in. One means we can.


Wait?! What FIFO queue? UART in the STM32C031 has a data queueing feature for transmission. By default, the FIFO queue is disabled (a task for you: check which bit this is). So we treat this status bit as can/cannot put data into TDR.
We wait until a one appears here. Therefore, when checking this bit in a while loop, we must invert the logical condition.
// Wait till end of transmission
while(!(USART2->ISR & USART_ISR_TXE_TXFNF))
{
// Wait for end of transmition
}
Thanks to this, as long as TDR is not free, we will wait. A busy TDR means that the UART transmission has not yet finished.
So the main code looks as follows:
int main(void)
{
// Configure SysTick Timer
SysTick_Config(12000000 / 1000);
// UART2 Initialization
UART2_Config();
// Timers
uint32_t Timer_UART = GetSystemTick();
/* Loop forever */
while(1)
{
// UART Task
if((GetSystemTick() - Timer_UART) > UART_TIMER)
{
// Reload Timer
Timer_UART = GetSystemTick();
USART2->TDR = 'A'; // Put byte 'A' to send
// Wait till end of transmission
while(!(USART2->ISR & USART_ISR_TXE_TXFNF))
{
// Wait for end of transmition
}
}
}
}
Let’s check if it works. On the PC I am using RealTerm. We set the appropriate COM port on which the ST-Link appeared, the speed to 115200, and wait for what comes from the Nucleo.
We receive the letter A regularly.

How to send, for example, an entire string? You will learn that in the next post. This one has already stretched quite a bit. In the next article, I will show you how to send longer strings, as well as how to receive something from the computer and react to what we receive in a very simple way.
Summary
UART communication is an absolute foundation in the embedded world. It allows data exchange between the STM32 and a computer, which opens up new possibilities – from debugging to controlling external devices.
In this post, we configured USART2 on the STM32, redirected its pins to the appropriate alternate functions, and set the correct transmission parameters. Thanks to this, we can already send data to the computer and receive it in a serial COM port terminal. We used a USB–UART converter, but in the case of Nucleo boards, we already have a built-in interface in the ST-Link.
We sent the first single character. In the next posts, we will deal with sending entire strings and receiving data from the computer. This will allow us to fully use UART in practical applications.
Let me know in the comments whether this post was useful for you! Do you have an idea for the next topics in the STM32 on Registers series? Share your suggestion!
I also invite you to my shop, where you will find the NUCLEO-C031C6 used in this series, USB–UART converters, and other accessories for STM32:
🔗 https://sklep.msalamon.pl
You can find the project code on GitHub:
📂 https://github.com/lamik/stm32narejestrach_6



0 Comments