UART
Why this matters
UART is the first interface you look for on any embedded device, and the reason is economic rather than technical.
Bringing up a board requires a console. That console is how the firmware engineer found out the DDR timings were wrong at 3 a.m., six weeks before shipping. Removing it before production requires someone to decide to remove it, test that nothing depends on it, and absorb the risk of losing field-debug capability. That decision frequently does not get made — and when it is made, it is often made badly, leaving the pins connected and only the login disabled.
Cheap to attempt, high payoff. Always try it first.
What UART actually is
Universal Asynchronous Receiver/Transmitter. Two signal wires, each unidirectional, plus a common ground:
| Line | Direction | Idle state |
|---|---|---|
| TX | Device → you | High |
| RX | You → device | High |
| GND | — | — |
Asynchronous is the important word. There is no clock line. Both ends are configured with the same bit period and each recovers timing independently from the falling edge that starts a frame. That is why the baud rate has to be exactly right, and why getting it wrong produces plausible-looking garbage rather than nothing at all.
Frame format
One frame carries one character:
start data bits (LSB first) parity stop
idle ‾‾‾\__ D0 D1 D2 D3 D4 D5 D6 D7 ‾‾‾‾‾‾ (opt) ‾‾‾‾‾ idle
- Start bit — a single low bit. The falling edge is what the receiver syncs on.
- Data bits — usually 8, sometimes 7 on older industrial gear. LSB first, which trips people reading a capture by eye.
- Parity — usually none. Even and odd still appear on industrial equipment.
- Stop bit(s) — one or two high bits, returning the line to idle.
Written as 8N1: eight data bits, no parity, one stop bit. That is the default
almost everywhere, and the right first guess.
Finding the pins
Four pads, and often only three are populated. To find them:
- Ground first. Multimeter in continuity mode, referenced against a shield can, a USB shell, or the negative terminal of a large electrolytic capacitor. Getting ground wrong is how probes die.
- VCC next. It sits at a steady 3.3 V (increasingly 1.8 V) relative to ground and never moves. You do not connect to it — you identify it so you do not connect to it by mistake.
- TX is the one that talks at boot. Power-cycle with a logic analyzer on the candidates. TX shows a burst of activity in the first second or two as the bootloader prints. RX idles high and does nothing.
- RX is what is left, and it is the one you verify last, by typing.
⚠️ Check the voltage domain before connecting anything. A 5 V USB-serial adapter into a 1.8 V pin destroys it. Measure the idle voltage on TX first. See Protocols.
Header shapes worth recognising
A row of four 0.1” pads near the SoC, or a 4-pin JST connector, or four vias in
a line with a silkscreen outline of a header that was never fitted. Test points
labelled TP with numbers. On consumer routers, frequently a 4-pin header
right beside the flash chip.
Labels, when present, are worth exactly what they cost the vendor: nothing. RX
on a silkscreen may mean “this device’s RX” or “connect your RX here”, which
are opposite. Verify by observation.
Recovering the baud rate
You will not be told the rate, and guessing wastes time. Measure it.
UART has no clock, so the receiver assumes a fixed bit period — and the bit
period is the only thing you need to determine. Measure the narrowest pulse
in the capture. Somewhere in the data there is an isolated 1 between two
0s or vice versa, and that pulse is exactly one bit time.
Worked example
A capture that decodes to garbage at every rate you tried. Zoom in, find the shortest interval between two edges, and measure it:
narrowest pulse measured : 8.68 µs
1 / 8.68e-6 = 115,207 baud
nearest standard rate = 115200
error = 0.006%
Standard rates, for matching against your measurement:
| Rate | Bit period |
|---|---|
| 9600 | 104.2 µs |
| 19200 | 52.1 µs |
| 38400 | 26.0 µs |
| 57600 | 17.4 µs |
| 115200 | 8.68 µs |
| 230400 | 4.34 µs |
| 921600 | 1.09 µs |
If your measurement is not close to any of these, you may be looking at a non-standard rate — U-Boot on some SoCs derives its rate from an odd crystal and lands a percent or two off nominal. Decoders that accept an arbitrary rate will still lock on.
If the rate is right but the output is still wrong, the problem is framing:
try 8N1, then 8E1, then 7E1.
⚠️ A UART tolerates roughly 2% total clock error before frames start corrupting. That is why 0.08% error is fine and why a 5% guess is not.
What you get when it works
The range is wide, and even the worst case is worth having:
| What you get | How common | Worth |
|---|---|---|
| Silent pins | Common on newer gear | Nothing directly; try JTAG |
| Boot log only, no input | Very common | Kernel version, partition layout, hardware inventory |
| Bootloader prompt (U-Boot) | Common | Usually total compromise |
| Root shell, no password | Still distressingly common | Total compromise |
| Login prompt | Common | A password target, and a version banner |
The “worthless” boot-log-only case hands you the kernel version, the partition table, and often the exact build string — everything needed to go find the vendor’s GPL source drop and skip reverse engineering entirely.
Why a bootloader prompt is game over
U-Boot with an unrestricted console lets you change bootargs to append
init=/bin/sh, giving a root shell before any of the system’s own
authentication runs. Or tftpboot an entirely different kernel. A signed
kernel does not help if you can instruct the bootloader to load a different
one — which is why the chain of trust
has to start earlier than most vendors start it.
Connecting
A USB-serial adapter (FTDI, CP2102, CH340) or a multi-protocol tool like the Tigard. Cross the lines: your RX to the device’s TX, your TX to its RX, grounds common. Do not connect VCC unless you know you need to.
$ dmesg | tail -3
[12345.678] usb 1-2: cp210x converter now attached to ttyUSB0
$ picocom -b 115200 /dev/ttyUSB0
screen /dev/ttyUSB0 115200 and minicom -D /dev/ttyUSB0 -b 115200 do the same
job. In picocom, quit with Ctrl-A Ctrl-Q; in screen, Ctrl-A k.
If you see nothing, in order of likelihood: wrong baud, TX and RX swapped, no common ground, wrong pins, or the console is genuinely disabled.
If you see the boot log but typing does nothing, RX is either not connected or not enabled in firmware — the latter being a deliberate, and reasonable, hardening step.
Key takeaways
- UART persists because removing it costs money and provides no customer-visible benefit.
- There is no clock line, so the baud rate must be measured — from the narrowest pulse in a capture — rather than guessed.
- Data is transmitted LSB first, which matters when reading a capture by eye.
- TX is identifiable as the line that talks during the first second after reset.
- Even a read-only boot log usually gives you the kernel version, the flash layout, and the vendor’s build string.
- An interactive bootloader prompt defeats a signed kernel, because you can tell it to load a different one.
References
- SparkFun, Serial Communication tutorial — https://learn.sparkfun.com/tutorials/serial-communication
- sigrok UART protocol decoder — https://sigrok.org/wiki/Protocol_decoder:Uart
- U-Boot documentation, environment and
bootargs— https://docs.u-boot.org/ - picocom — https://github.com/npat-efault/picocom
- Tigard multi-protocol interface — https://github.com/tigard-tools/tigard
- FTDI FT232R datasheet — https://ftdichip.com/products/ft232rl/
Related course pages: Protocols · Schedule · Tools of the Trade · I2C · SPI · JTAG and SWD · Software Reverse Engineering
🛠️ Maintenance note: the protocol is ancient and will not change. What ages here is the adapter chipset list — CH340 clones and their driver situation in particular — and terminal-emulator key bindings. Re-check the
picocom/screenescape sequences before demonstrating them, because students who cannot exit a terminal emulator will not tell you.