Source: https://mokxi.com/learn/uart-serial-explained
Updated: 2026-09-27

Learn

# UART serial, one bit at a time

Written by the Mokxi team, updated September 27, 2026

Sign up free or see every lesson

- 192 parts on the bench
- 25 boards running now
- 1.00x real time, on every board

Click to open it in the editor

Serial1 at 9600 baud, looped back from TX1 to RX1, with the analyzer decoding pin 18.

Every Arduino tutorial starts with Serial.begin(9600) and most never say what the 9600 is for. It is the whole of the protocol. A UART has no clock wire, so the sender and the receiver agree on a speed in advance, and the receiver times every bit from the moment the line first drops. Get the number wrong on one end and the bytes still arrive, just as the wrong bytes.

The circuit above is an Arduino Mega 2560 running its built-in ports example. A Mega has four hardware UARTs; the sketch opens Serial1 at 9600 baud, Serial2 at 19200 and Serial3 at 38400, and jumpers each TX pin back to the RX pin beside it, so every port hears itself. A logic analyzer is clipped to TX1 (pin 18) with its decoder set to UART at 9600. Press Run and the analyzer writes p, i, n, g, a space, a number, and then \r and \n over the trace, one label per frame, while the serial monitor prints "Serial1 heard: ping 3".

We used a Mega rather than an Uno for one honest reason: the Uno has a single UART and it is spoken for by the USB link to your computer. In Mokxi the Uno’s USART talks straight to the serial monitor and its pins 0 and 1 stay ordinary GPIO, so there would be nothing on the wire to decode.

## What one byte looks like on the wire

An idle UART line sits high. To send a byte the transmitter pulls it low for one bit time, the start bit, then sends the eight data bits least significant bit first, then lets the line go high for at least one bit time, the stop bit. That is the "8N1" you see in settings panels: eight data bits, no parity, one stop bit. Ten bit times per byte.

At 9600 baud a bit lasts 1/9600 of a second, 104 microseconds, so one byte takes 1.04 milliseconds and the line can carry at most 960 bytes a second. At 115200 a bit is 8.7 microseconds and a byte is 87 microseconds. Those two numbers are worth remembering, because they are also how long Serial.print can hold up your sketch once its buffer is full.

Zoom into the first frame on the analyzer and you can read it by hand. The letter p is 0x70, binary 01110000. Sent least significant bit first, that is four zeros, three ones and a zero, so after the start bit the line stays low for four more bit times, goes high for three, drops for one, and comes back high for the stop bit. The analyzer samples each bit in its middle, which is why a little timing error between the two ends does not matter.

## Why both ends must agree on the baud rate

Nothing on the line says how fast it is. The receiver sees the falling edge of the start bit, waits one and a half bit times, and samples. If it thinks a bit is half as long as it really is, it samples the same bit twice, reads garbage, and usually finds the line low where it expected the stop bit. That is a framing error, and it is the first thing a real logic analyzer or oscilloscope decoder will report.

Try it. Open the circuit in the editor, click the analyzer, and change its baud property from 9600 to 19200. The same line, read at the wrong speed, turns into a scatter of wrong characters and the strip under the trace counts the bad frames. When we tried it, the strip reported 14 bytes and 11 bad frames where the same line at 9600 had decoded without a single error. The serial monitor in the Arduino IDE does exactly the same thing when its drop-down does not match Serial.begin, which is where most people first meet mojibake.

Real clocks are never exact, and they do not need to be. An ATmega at 16 MHz asked for 9600 baud actually runs at 9615, 0.16 percent fast, because the divider has to be a whole number. The receiver re-synchronizes on every start bit, so an error of a couple of percent is tolerated across one ten-bit frame. The Mega above is sending at 9615 and the analyzer, set to 9600, decodes every byte.

## Wiring two boards together

TX goes to RX and RX goes to TX: one board’s transmit is the other’s receive. The grounds must be joined too, because a UART level is only a voltage relative to ground, and without a shared ground the receiver has no idea what "low" means.

Mind the voltage. An Uno or a Mega drives its TX pin to 5 V. An ESP32, a Raspberry Pi Pico or an STM32 runs at 3.3 V and its pins are not rated for 5 V. Wiring a 5 V TX straight into a 3.3 V board’s RX can damage that pin on real hardware. A two-resistor divider (for example 1 k in series and 2 k to ground, which turns 5 V into about 3.3 V) or a level shifter fixes it. The other direction usually works as it is, since 3.3 V is enough for most 5 V inputs to read as high.

And never connect a board’s TX and RX pins to an RS-232 port, the nine-pin D-connector on old PCs and lab equipment. RS-232 uses the same framing but swings roughly plus and minus 12 volts, with the logic inverted, and needs a converter chip such as a MAX232 between it and a microcontroller.

## Common mistakes on the bench

TX wired to TX. Nothing is ever received, both ends look healthy, and it is the first thing to check when two boards will not talk.

Using pins 0 and 1 on an Uno for something else. On a real Uno those pins are wired to the USB serial chip, so a sensor on them can stop uploads working and anything you print also appears on that sensor’s input. Software serial on two other pins, or a board with a second UART like the Mega, avoids the clash.

Printing too much. A line of 40 characters at 9600 baud takes about 42 milliseconds to leave. Once the transmit buffer fills, Serial.print waits, and a sketch that prints every pass through loop() quietly becomes a sketch that runs at the speed of its serial port. Raising the baud rate or printing less often both help.

## Questions

What baud rate should I use?

9600 is the traditional default and works everywhere. 115200 is twelve times faster and is what most modern examples, including Mokxi’s own, use for the serial monitor. Pick one and set the same value on both ends.

Is UART the same as USB?

No. On an Uno a separate chip converts between the ATmega’s UART and USB, which is why Serial looks like a USB port on your computer. Boards like the Leonardo or the Pico do USB inside the microcontroller itself.

Why does my serial monitor show strange characters?

Almost always a baud mismatch between Serial.begin and the monitor’s setting. Less often it is a missing ground between two boards, or a board that resets and prints boot messages at a different speed.

How many devices can share one UART?

Two, one at each end. UART is point to point. If you need several devices on the same pair of wires, I2C or SPI are built for that.

Related

## Keep going

I2C Explained: Addresses, Pull-Ups and a Scanner SPI Explained: Clock, Data and Chip Select Measure It: Read a Circuit With a Scope and Plotter How to Debug Arduino Code: Prints to Breakpoints The Arduino Mega 2560 simulator The logic analyzer and the other bench instruments Baud rate, in the glossary

## Build this for real

Open the editor, change a value and watch the number move with it. Nothing to install, and no account needed.

Start building Open the editor
