Microcontrollers · #12 of 52

UART Logging + Debugging

Serial Frames, Baud Rate, USB Bridges, and Logs That Survive Bring-Up

An ESP32-C3 development board with a USB connector and USB-to-serial bridge circuitry.
The USB connector is not magic. On many boards it ends at a small USB-to-serial bridge that talks UART to the microcontroller. · Popolon, CC BY-SA 4.0

The robot hand is on the bench. A new firmware image boots, the fingers twitch once, and then nothing visible happens. No Wi-Fi packet arrives. The display is blank. The motor driver enable pin might be low, or the boot mode might be wrong, or the code might be trapped before setup finishes.

UART is how the board speaks while the rest of the system is still unreliable. Before the app, before the network, before the polished dashboard, there is usually a TX pin, an RX pin, ground, a baud rate and a terminal window.

UART is not glamorous, but it is one of the first interfaces a working engineer reaches for. It is simple enough to bring up early, precise enough to debug timing, and humble enough to stay in the product as a service port or manufacturing log.

By the end, you can

  1. Wire a UART debug link without confusing TX, RX, ground, USB, TTL and RS-232
  2. Read an 8N1 UART frame: idle level, start bit, LSB-first data bits and stop bit
  3. Compute bit time, frame time and practical byte throughput from baud rate
  4. Recognize baud mismatch, voltage-level mismatch and missing-ground failures
  5. Design firmware logs that help during bring-up without breaking timing
  6. Use UART as a repeatable debug workflow, not just as scattered print statements

UART starts with three wires

A plain UART link has transmit, receive and a shared reference. One device's TX goes to the other device's RX. The return path is ground. That ground wire is not optional: without it, the receiver does not know what voltage "high" and "low" mean.

A compact red USB-to-UART adapter with header pins.
A USB-to-UART adapter is a level-shifting translator between your computer and the board's serial pins. · Sunmist, CC0

The confusing part is naming. A header labeled TX on the adapter is the adapter's transmitter, so it usually connects to RX on the target. A header labeled TX on the target connects to RX on the adapter. If both sides talk into TX, nobody is listening. If both sides listen on RX, nobody is talking.

A black FTDI USB-to-TTL serial cable with a six-pin header.
The cable carries more than a byte stream. Many adapters also expose power, ground and flow-control pins, so read the pinout before plugging it into a board. · oomlout, CC BY-SA 2.0

Voltage levels are just as important. A 3.3 V microcontroller UART is usually idle high near 3.3 V and active low near ground. A 5 V adapter can damage a 3.3 V-only input. A classic RS-232 port is a different electrical standard entirely, with inverted signaling and voltage swings that can go negative. A DB9 connector and a UART header are not the same thing.

A USB to RS-232 adapter with a DB9 connector.
This kind of adapter produces RS-232 levels at a DB9 connector. It is for RS-232 equipment, not for a bare 3.3 V microcontroller RX pin. · Kilowattradio, Public domain

You connect a USB-to-UART adapter to an ESP32 debug header. Which connection is most likely correct?

The frame is self-clocking enough

UART stands for Universal Asynchronous Receiver/Transmitter. The important word is asynchronous: there is no separate clock wire. The receiver watches for a falling edge, waits to the middle of each bit time, and samples.

In the common 8N1 format, one byte travels as ten bit times:

  1. One start bit, held low.
  2. Eight data bits, least significant bit first.
  3. No parity bit.
  4. One stop bit, held high.
Oscilloscope screenshot of an RS-232 UART character waveform.
A serial byte is a time pattern. Once you can see the start edge and bit cells, the terminal output stops feeling mysterious. · Haji akhundov, CC BY-SA 3.0

For 0x55, the binary pattern is alternating ones and zeros. Because UART sends the least significant bit first, 0x55 is a useful scope pattern: it makes transitions on nearly every bit and reveals baud-rate problems quickly.

TX: 0x55 'U' RX: 0x55 'U' bit time: 8.68 us frame: 86.81 us max: 11520 B/s stop drift: 0.00 bit

Try these moves:

  1. Set the byte to 85, which is 0x55, and notice the alternating data bits.
  2. Change the baud rate. The waveform shape is the same, but bit time and byte time shrink.
  3. Move receiver baud error toward plus or minus 8 percent. The orange sample points drift until the decoded byte can change.
  4. Try byte 65, which is 0x41, and watch how a printable character is only a pattern of bits.

Baud rate is a timing contract

Baud rate is the symbol rate. For ordinary UART, one symbol is one bit, so 115200 baud means 115200 bits per second.

The bit time is:

Tbit=1baudT_\text{bit} = \frac{1}{\text{baud}}

At 115200 baud:

Tbit=11152008.68 μsT_\text{bit} = \frac{1}{115200} \approx 8.68\ \mu\text{s}

With 8N1, each payload byte takes ten bit times:

Tbyte=10TbitT_\text{byte} = 10T_\text{bit}

The ideal byte rate is therefore:

bytes per second=baud10\text{bytes per second} = \frac{\text{baud}}{10}

At 115200 baud, that is 11520 bytes per second before terminal overhead, formatting, driver buffering and firmware costs.

A UART uses 115200 baud and 8N1 framing. Roughly how many payload bytes per second can the wire carry?

Practice 1 warm-up

At 9600 baud with 8N1 framing, estimate the time to transmit a 40-byte status line.

Show worked solution

Each payload byte uses 10 line bits, so 40 bytes uses 400 bit times. At 9600 baud, one bit is about 104.17 microseconds. The message takes about 41.7 milliseconds on the wire, before terminal and firmware overhead.

USB is often just the way into UART

Your laptop does not usually expose a 3.3 V UART pin. Development boards hide the conversion with a USB connector and a bridge chip. The bridge enumerates as a serial port on the host and drives TX/RX pins on the microcontroller side.

Close-up photo of a Silicon Labs CP2102 USB-to-UART bridge integrated circuit.
A USB-to-UART bridge chip is the small translator that lets a USB cable behave like a serial console. · SparkFun Electronics, CC BY-SA 4.0
A small USB-to-serial bridge module with header pins.
Breakout boards make the bridge explicit: USB on one side, logic-level serial pins on the other. · SparkFun Electronics, CC BY 2.0
An Arduino USB-to-serial converter board.
Arduino-style USB-to-serial boards are common bench tools because they turn a computer terminal into a microcontroller debug channel. · oomlout, CC BY-SA 2.0

On many ESP32 development boards, the same bridge also helps with flashing. Control lines such as DTR and RTS may tug reset and boot-mode pins through a small transistor network. That is why a board can enter the ROM bootloader automatically when you flash it from a tool.

Some newer microcontrollers expose a native USB device and implement a USB CDC serial port without a separate bridge chip. The workflow looks similar in your terminal, but the electrical path is different: USB packets reach firmware directly instead of arriving as a UART waveform on TX/RX pins. During early bring-up, knowing which path your board uses matters.

Logs are an instrument

The lazy version of UART debugging is to scatter prints until the bug moves. The useful version is to design logs like a measurement channel.

Good bring-up logs have structure:

  1. A boot banner with firmware version, build time and reset reason.
  2. One line when each rail, peripheral and task changes state.
  3. Severity levels: error, warning, info and debug.
  4. A timestamp or monotonically increasing tick.
  5. Values with units.
  6. Rate limits for noisy paths.
  7. A way to compile out verbose logs in timing-sensitive builds.
A serial terminal application screenshot.
The terminal window should become a lab notebook written by the firmware, not a random stream of half-useful strings. · VijayGES2, CC BY-SA 4.0

For the robot hand, a bad log is failed. A useful log is closer to: gripper current limit hit, phase A sense 1.8 A, rail 7.4 V, command 40 percent, tick 184223. The second line points you toward a supply, current-sense or motor-control question. The first line points nowhere.

Logs can also break the thing they are measuring. Printing inside a fast interrupt, inside a tight control loop or during a timing-sensitive bus transaction can change the timing enough to hide the original bug. Buffer logs, lower their rate, or mark events with counters and print them later.

Practice 2 core

A motor board sometimes resets when Wi-Fi starts. Write the fields you would want in a UART log line before and after the Wi-Fi transmit attempt.

Show worked solution

Useful fields include tick time, reset reason from the previous boot, supply voltage, regulator status if available, Wi-Fi state, radio transmit start/end markers, heap or task state if relevant, and a sequence number. For this failure, voltage and reset reason matter more than a generic "starting Wi-Fi" string.

When UART output is missing, prove the path before changing the application. The fault is often outside the code.

Start with this order:

  1. Confirm the serial port name and permission on the host.
  2. Confirm baud rate, data bits, parity and stop bits. For ESP32 logs, 115200 8N1 is common.
  3. Tie grounds.
  4. Cross TX and RX.
  5. Check the adapter voltage setting.
  6. Press reset and watch for boot ROM text.
  7. Try a known-good loopback on the adapter: TX connected to RX should echo characters.
  8. If a DB9 connector is involved, verify whether it is RS-232 and use the right transceiver.
Rear panel of an electronic device with a DB9 RS-232 serial port.
DB9 serial ports still appear on instruments and industrial equipment. They are useful, but their electrical layer is not a bare microcontroller UART. · Zuzu, CC BY-SA 3.0

A terminal shows nonsense characters immediately after reset. The board is otherwise alive. What should you check first?

Production still needs a serial story

UART is not only a beginner debugging tool. It often becomes a manufacturing fixture, service console, calibration channel or emergency recovery path. That means it needs a policy.

Decide what happens after the product leaves the bench:

  1. Is the UART connector populated, hidden as pads, or removed?
  2. Are debug logs compiled out, rate-limited or protected?
  3. Can the service console change calibration or only report state?
  4. Are secrets ever printed? They should not be.
  5. Can a manufacturing jig read serial numbers, test results and firmware version?
  6. What is the recovery path if the main network stack fails?

Key takeaways

  • UART needs TX, RX and a shared ground; signal names are local, so TX usually crosses to RX.
  • 8N1 sends ten line bits per byte: start, eight data bits LSB first and stop.
  • Tbit=1/baudT_\text{bit}=1/\text{baud} and ideal 8N1 throughput is baud divided by 10.
  • USB-to-UART, TTL UART and RS-232 are different layers. Do not connect them by connector shape alone.
  • Good logs carry time, state, units and context; bad logs merely announce that something failed.
  • A repeatable UART debug workflow starts by proving the link before rewriting firmware.

UART is the board's quietest voice. It will not move a motor or sample a sensor by itself, but it lets the system explain what it is doing while every other interface is still suspect. Learn to read the waveform, respect the voltage levels, and write logs that behave like measurements. Then a blank bench becomes a conversation.

full glossary →