How UART Works on the STM32F4 (and How to Debug It)

UART is usually the first peripheral you bring up on a new board, because it is how the board talks back to you. It is also where a surprising number of embedded bugs hide, because "nothing prints" has a dozen different root causes. This guide walks the mental model and the failure modes on an STM32F4 (Cortex-M4).

What UART actually is

UART stands for Universal Asynchronous Receiver Transmitter. "Asynchronous" is the key word: there is no shared clock line between the two ends. Instead both sides agree ahead of time on a baud rate, and each samples the line on its own clock. If the two clocks disagree by more than a few percent, framing breaks and you get garbage.

A single UART frame is: one start bit (line pulled low), then the data bits (usually 8), an optional parity bit, then one or more stop bits (line held high). The common shorthand "115200 8N1" means 115200 baud, 8 data bits, No parity, 1 stop bit.

The three ways to move bytes

On the STM32F4 you have three escalating strategies, and choosing the wrong one is a classic mistake.

  • Polling. You spin on the status register waiting for the TXE (transmit empty) or RXNE (receive not empty) flag, then read or write the data register. Simple and fine for a boot banner. Fatal for anything real: while you spin, the CPU does nothing else, and at 115200 baud a single byte is ~87 microseconds of dead time.
  • Interrupt. The peripheral raises an interrupt when a byte arrives or the transmit register empties. You service it in a short ISR. This is the right default for command-response protocols and moderate data rates.
  • DMA. A Direct Memory Access controller moves bytes between the UART data register and RAM with no CPU involvement at all. This is what you use for high-rate or bursty streams, and it is the difference between a responsive system and one that drops bytes under load.

The bring-up failures that bite first

When "nothing prints," work down this list before you touch the code logic.

  1. Clock not enabled. On the STM32F4 every peripheral is gated by the RCC (Reset and Clock Control) block. If you forget to enable the clock to the USART and its GPIO port, writes to the peripheral registers silently do nothing. This is the single most common bring-up failure.
  2. Pins not in alternate-function mode. The TX and RX pins have to be switched from plain GPIO into the correct alternate function, or the UART signal never reaches the pad.
  3. Baud rate mismatch. If your BRR (baud rate register) calculation used the wrong peripheral clock frequency, everything transmits at the wrong speed and the receiver sees framing errors. Garbled but periodic output is the tell.
  4. TX and RX swapped. If your board sees your bytes but you see nothing back, the wires are crossed. TX on one side goes to RX on the other.

Why this is a debugging skill, not a trivia question

Notice that none of the failures above show up as a compiler error or a crash. The code runs, the peripheral is "configured," and yet no bytes move. Bringing UART up is an exercise in state inspection: reading the RCC enable bits, the GPIO mode registers, and the USART status flags to find the one that is wrong. That is exactly the kind of judgment OhmWork's UART challenges are built to train.