Learning Goals 5 min
You've used Serial.println() since L01-24. Today you find out what's actually happening on the wires. The protocol is called UART — Universal Asynchronous Receiver/Transmitter — and almost every microcontroller on Earth speaks it. By the end of this lesson you will:
- Describe the structure of a UART byte on the wire: start bit, 8 data bits, stop bit — 10 bits per byte total.
- Explain what baud rate means and why both ends of the link must agree on it down to ~3% accuracy.
- Wire two Arduinos UART-to-UART (or one Arduino in loopback) and demonstrate "Hello" being sent from one and received on the other.
Warm-Up 10 min
Open any sketch you've written that uses Serial.begin(9600) and Serial.println("Hello"). Upload it. Open the Serial Monitor at 9600 baud. See "Hello" appear.
The question we've been ignoring
How does "Hello" get from the Arduino to your computer? Two wires (TX from Arduino, RX into the USB-serial chip) and a shared agreement called the UART protocol.
The numbers that matter
A serial message is a stream of bytes. Each byte is 8 bits. The wire flips between "HIGH" (a 1) and "LOW" (a 0) at a fixed rate. The rate of bit-flipping is the baud rate — 9600 bits per second is the classic default. At 9600 baud, one bit lasts ~104 µs and one byte (10 bits with overhead) takes about 1 ms to send.
New Concept · The UART frame 25 min
Idle, start, data, stop
Both UART wires sit at HIGH when idle (no data). To send a byte:
- Start bit: the wire drops to LOW for one bit time. The receiver sees the falling edge and starts its clock.
- 8 data bits: sent LSB-first (least significant bit first). HIGH = 1, LOW = 0.
- Stop bit: the wire returns to HIGH for one bit time. Marks "byte complete".
- Idle: the wire stays HIGH until the next start bit.
Total: 10 bits on the wire per byte. At 9600 baud → 960 bytes per second of throughput, well under the headline "9600".
Example — sending the letter "A"
ASCII "A" = 65 decimal = 0100 0001 binary. LSB first, framed:
time ---> HIGH (idle) | LOW (start) | 1 0 0 0 0 0 1 0 (data, LSB first) | HIGH (stop) | HIGH (idle...)
Trace: idle HIGH → drop to LOW for one bit (start) → 1, 0, 0, 0, 0, 0, 1, 0 = bits 0..7 of 65 = 0b01000001 read LSB-first → up to HIGH for one bit (stop) → stay HIGH until next byte.
Baud rate must match within ~3%
The receiver doesn't know exactly when each data bit will appear — it just knows the bit time from the agreed baud rate. It samples each bit in the middle of its expected time slot. If the baud rates differ by more than ~3%, the sampling drifts and bytes come out garbled.
| Baud | Bit time | Bytes / sec | Notes |
|---|---|---|---|
| 9600 | 104 µs | 960 | The 1990s default. Forgiving timing; works everywhere. |
| 57600 | 17.4 µs | 5760 | Common for "more data, still safe". ESP8266 default at boot. |
| 115200 | 8.7 µs | 11520 | Modern default. Most boards manage this reliably. |
| 921600 | 1.1 µs | 92160 | For bulk transfers. Needs short, clean wires. |
Doubling the baud halves the per-byte time but doesn't need any extra hardware on a UNO — the USART can clock either rate.
TX and RX — who's talking to whom
UART is asynchronous full-duplex: two separate wires, one in each direction. Wire connection is "crossover":
| Board A | Board B |
|---|---|
| TX (pin 1 on UNO) | RX (pin 0 on UNO) |
| RX (pin 0 on UNO) | TX (pin 1 on UNO) |
| GND | GND |
If both boards have their TX wired together, neither receives anything (both are talking, nobody listening). Crossover is mandatory.
5 V vs 3.3 V — the gotcha that fries pins
The UNO's UART is 5 V logic. Many newer boards (ESP8266, ESP32, Raspberry Pi GPIO, Nano 33 BLE) are 3.3 V logic. Wire 5 V TX straight into a 3.3 V RX and you can damage the 3.3 V chip's input pin (it's rated for max ~3.6 V).
Fix: a logic-level shifter — a tiny breakout board with 5 V and 3.3 V pins on each side, MOSFETs in between. Or a resistor divider (1 kΩ + 2 kΩ) on the 5 V → 3.3 V line for low-speed UART. We'll wire one explicitly for the ESP8266 in L03-30.
UART pin reservations on UNO
Pins 0 (RX) and 1 (TX) on the UNO are wired to the on-board USB-serial chip. Anything you connect to those pins also sees data flowing to/from your computer during sketch uploads. Disconnect anything on D0/D1 before uploading — otherwise upload may fail. For permanent connections to other devices, use SoftwareSerial on different pins (tomorrow's lesson, L03-16).
Worked Example · Two UNOs talking 25 min
Setup A — two Arduinos
If you have two UNOs, use the cross-wire setup:
| UNO A | UNO B | Wire |
|---|---|---|
| D1 (TX) | D0 (RX) | Cross 1 |
| D0 (RX) | D1 (TX) | Cross 2 |
| GND | GND | Common ground |
You will not be able to upload sketches while the wires are connected — pins 0/1 collide with the USB chip. Upload first with the wires disconnected, then wire and power both boards.
Setup B — one Arduino in loopback
If you only have one UNO: short pin 0 to pin 1 with a single wire. Anything the Arduino sends out TX comes straight back into RX. You can't use the Serial Monitor at the same time (the USB chip is also talking on those pins), but the Arduino itself can verify the loop.
Sketch — sender (upload to UNO A)
// UNO A — sender
void setup() {
Serial.begin(9600);
delay(2000);
Serial.println("Hello from A");
}
void loop() {
Serial.print("Tick ");
Serial.println(millis());
delay(1000);
}Sketch — receiver (upload to UNO B)
// UNO B — receiver
void setup() {
Serial.begin(9600);
}
void loop() {
while (Serial.available()) {
char c = Serial.read();
// forward what we receive back to USB (if we re-attached USB temporarily)
// but in this rig, light the LED on each byte to confirm we're hearing
digitalWrite(LED_BUILTIN, HIGH);
delay(10);
digitalWrite(LED_BUILTIN, LOW);
}
}Verify
With both boards powered (one over USB, one over a barrel jack or USB to a different port), the receiver's built-in LED should blink each time a tick arrives. The LED flashes ~10 times in quick succession every second — one per character of "Tick 1234\n".
Loopback variant
For the loopback rig with one UNO: jumper pin 0 to pin 1. Upload this sketch:
// Loopback test — TX and RX shorted together
void setup() {
Serial.begin(9600);
}
void loop() {
Serial.print("ping ");
Serial.println(millis());
delay(500);
// we just sent bytes; if loopback is working they're now in the RX buffer
while (Serial.available()) {
Serial.read(); // consume so the buffer doesn't overflow
}
}Open Serial Monitor (briefly remove the jumper first, then re-add it) — you should see the echoes coming back. The bytes the Arduino sent went out TX, looped to RX, were read by Serial.read(), and the next iteration kept going. Not very useful as a demo, but it proves the round trip works.
Step 6 — change the baud and see the garbage
Change UNO A's baud to Serial.begin(57600) but leave UNO B at 9600. Re-upload only A. The receiver's LED will still blink (any signal triggers something), but if you read its Serial back to USB on a third monitor it will show garbage characters. This is the "baud rate mismatch" symptom. Set them both back to 9600.
Try It Yourself · Paper + light wiring 15 min
The wire shows: LOW, then bits 1 1 0 0 0 0 0 1, then HIGH. The bits are sent LSB-first. What ASCII character was sent? (Hint: 0x80 is the highest bit.)
Reveal
Bits LSB to MSB: 1 1 0 0 0 0 0 1 → reverse to read MSB first: 1 0 0 0 0 0 1 1 = 0x83 = 131 decimal. That's outside standard ASCII (which ends at 127), so it's an "extended ASCII" or "Latin-1" character — but you might see this on a real serial line if the receiver and sender disagree on character set. Honestly: most of the time, get a small letter from the table and decode that instead.
At 115200 baud, with 10 bits per byte (start + 8 data + stop), how many bytes per second can you send?
Reveal
115200 ÷ 10 = 11520 bytes per second. About 12 KB/s. Plenty for sensor data or text logs; nowhere near enough for raw video.
Your sender ticks at exactly 9600 baud. The receiver's clock runs 4% slow, so it samples at an effective 9216 baud. Predict what happens to received bytes.
Reveal
4% drift means the receiver's sample point drifts ~0.4 bit-times by the end of a 10-bit byte. That puts the stop bit's sample right at the edge of the next byte's start bit — usually fine for a single byte, but accumulated framing errors cause garbled bytes every few characters. The 3%-tolerance rule of thumb says you need ≤ ~0.3 bit-time drift over 10 bits, and 4% is just above that. Expect intermittent garbage.
RS-485, a popular industrial UART variant, uses one wire pair shared between sender and receiver (half-duplex), not two pairs (full-duplex). What's the trade-off?
Reveal
One pair is simpler and cheaper to wire long distances, especially in noisy industrial environments where differential signalling (twisted-pair) shines. The trade-off: only one device can transmit at a time, and you need a protocol layer to decide whose turn it is — usually a master/slave pattern with addresses. Modbus (L04-20) is the most common protocol on RS-485 hardware.
The UNO has both a USB port and pins 0/1 for UART. Why is there a separate chip (the ATmega16U2 or CH340) on the UNO between the USB port and the main microcontroller?
Reveal
The main ATmega328P speaks UART natively but not USB. The 16U2 (or CH340 on cheap clones) sits between USB on one side and UART on the other, translating USB packets ↔ UART bits. That's why the main chip's pins 0 and 1 collide with USB — they're wired to the 16U2's UART side. Disconnect external things from 0/1 before uploading, and the USB-serial flow works untouched.
Mini-Challenge · Build a 2-UNO command pipe 10 min
Two UNOs cross-wired via UART. UNO A is a "keyboard" (you type into its Serial Monitor over USB), UNO B has an LED on D6. Single-character commands on UNO A travel over the UART link and turn UNO B's LED on/off.
- UNO A: read from
Serial(the USB Serial Monitor). On each character, also send it out viaSerial.write()— which sends to the same TX wire, which is now cross-wired to UNO B's RX. - UNO B: read characters;
1turns LED on,0turns LED off. - To upload: disconnect the cross-wires; upload A and B separately; reconnect.
The architectural insight: UART is just bytes-in / bytes-out. You can put any protocol on top of it — single-character commands today, structured messages tomorrow with a Bluetooth module (L03-25) or a WiFi connection (L03-32). The motor controllers, the sensors, the radios — all of L3 from here is bytes on a wire, with progressively fancier protocols on top.
Recap 5 min
UART is two wires (TX out, RX in) plus a shared ground, sending bytes as 10-bit frames: start bit, 8 data bits LSB-first, stop bit. The baud rate sets the bit duration; both ends must agree within ~3%. Modern defaults are 9600 (forgiving) and 115200 (modern fast). 5 V and 3.3 V boards can't share UART without a level shifter. The UNO's pins 0 and 1 share with USB upload — disconnect external devices before uploading. Tomorrow we get those pins back by moving UART to any pair with SoftwareSerial.
- UART
- Universal Asynchronous Receiver/Transmitter. The hardware peripheral inside the AVR that sends and receives serial frames. Also commonly used to mean the protocol itself.
- Asynchronous
- No shared clock between sender and receiver. Each end has its own clock; the protocol's structure (start bit, fixed baud) lets the receiver re-sync each byte.
- Full duplex
- Both sides can talk at the same time on separate wires. Opposite of half-duplex (one wire pair, one talker at a time).
- Baud rate
- Bits per second on the wire (in basic UART, equal to symbol rate). Typical: 9600, 57600, 115200. Both ends must match within ~3%.
- Frame
- One byte's worth of bits on the wire: start + data + stop. Total 10 bits per data byte at the standard 8-N-1 settings.
- TX / RX
- Transmit / Receive. The two data wires of a UART link. Connect crossover between two boards.
- Logic level
- The voltage that represents "1". 5 V on a UNO, 3.3 V on ESPs and most modern boards.
- Logic-level shifter
- A small adapter that translates signals between two voltage domains (typically 5 V ↔ 3.3 V). Required when crossing voltage domains.
- USB-serial adapter
- A chip (ATmega16U2 or CH340 on UNO) that bridges USB on one side to UART on the other. Lets your computer talk to the UART pins of the main chip.
Homework 5 min
- Build the 2-UNO command pipe or the loopback rig from §4. Verify it works.
- Try uploading a sketch without disconnecting the cross-wires. Observe the failure mode (often: "avrdude: stk500_recv(): programmer is not responding"). This is the "something on pins 0/1 blocking upload" symptom — useful to recognise.
- Bring tomorrow: a few extra jumper wires. We'll move serial to pins 2/3 with
SoftwareSerialso we can keep pin 0/1 for USB uploads.
Bring back next class:
- Your command pipe (or loopback) sketch and wiring.
- A note in your engineering notebook describing what the "upload while wires are connected" failure looked like.
- Jumper wires for L03-16.