UART, I²C, SPI and CAN in Firmware
Frames and timing, baud-rate error, addresses, modes, reading registers, DMA, and the faults that stop a bus working.
Which bus for which job
| UART | I²C | SPI | CAN | |
|---|---|---|---|---|
| Wires | TX, RX (+ ground) | SDA, SCL (+ ground) | SCK, MOSI, MISO, one CS per device | CAN-H, CAN-L (differential) |
| Devices | 2 (point to point) | Many on 2 wires, each with a 7-bit address | Many, each needs its own CS line | Many, with message IDs |
| Speed (typical) | 9 600 to 115 200 baud, up to Mbaud | 100 kHz, 400 kHz, 1 MHz | Several MHz to tens of MHz | Up to 1 Mbit/s (CAN FD 5 Mbit/s) |
| Distance | Short (or RS-485/RS-232 for long) | Short, on one board or a short cable | Short, same board | Long (40 m at 1 Mbit/s, 1 km at 50 kbit/s) |
| Typical devices | GPS, Bluetooth and Wi-Fi modules, debug console, RS-485 networks | Sensors, EEPROMs, real-time clocks, small displays, I/O expanders | Flash, SD cards, ADCs/DACs, displays, radios | Vehicles, machines, motor drives |
| Notes | Both ends must agree on the baud rate | Open-drain; needs pull-ups; acknowledges each byte | Full duplex; no addressing; four modes (clock polarity and phase) | Priority arbitration; built-in error detection |
Rule of thumb: I²C for slow sensors and configuration (few wires, many chips), SPI for speed (memory, displays, ADCs), UART for modules and consoles, CAN or RS-485 (see Industrial Communication) for distance and noise. The electrical side (pull-ups, levels, wiring) is in Microcontroller Electronics.
UART: asynchronous serial
A UART sends bytes one bit at a time with no shared clock. The line idles high. The common frame is 8N1: one start bit (low), eight data bits (least significant first), no parity, one stop bit (high): 10 bit times per byte. Both ends must use the same baud rate, and the receiver synchronizes on each start bit, so the total error of the two clocks must stay within about ±2 % (it must not drift by more than half a bit over 10 bits, which allows ±5 % in theory and about ±2 % in practice).
Worked example: baud-rate error on a 16 MHz AVR
The UART divides the clock: UBRR = f ÷ (16 × baud) − 1 in normal mode (or f ÷ (8 × baud) − 1 with the double-speed bit). The divider is rounded to an integer, so the real baud rate differs from the one you asked for.
| Wanted baud | Normal: UBRR → actual | Error | Double speed: UBRR → actual | Error |
|---|---|---|---|---|
| 9 600 | 103 → 9 615 | +0.16 % | 207 → 9 615 | +0.16 % |
| 57 600 | 16 → 58 824 | +2.12 % | 34 → 57 143 | -0.79 % |
| 115 200 | 8 → 111 111 | -3.55 % | 16 → 117 647 | +2.12 % |
| 230 400 | 3 → 250 000 | +8.51 % | 8 → 222 222 | -3.55 % |
| 500 000 | 1 → 500 000 | +0.00 % | 3 → 500 000 | +0.00 % |
| 1 000 000 | 0 → 1 000 000 | +0.00 % | 1 → 1 000 000 | +0.00 % |
At 115 200 baud the normal divider (UBRR = 8) gives 111 111 baud, an error of −3.55 %: marginal, and on a long run of bytes it can fail. Double speed (UBRR = 16) gives 117 647 baud, +2.12 %, better but not perfect; a crystal of 18.432 MHz (a multiple of 16 × 115 200) gives 0 %. 1 Mbaud is exact on 16 MHz. Choose the clock for the baud rates you need, or pick baud rates the clock divides.
A byte takes 10 ÷ 115 200 = 86.8 µs so the line moves 11 520 bytes per second; a 32-byte message takes 2.8 ms.
Framing, buffers and flow control
The UART hardware raises an interrupt per byte (or fills a small FIFO). Firmware must collect the bytes into a ring buffer in the ISR and process them in the main loop, or the next byte overwrites the last (an overrun).
Worked example: how long can the main loop be busy?
At 115 200 baud a new byte arrives every 86.8 µs. A 64-byte buffer fills in 64 × 86.8 µs = 5.6 ms: if the main loop is blocked for longer (a slow flash write, a delay, a long calculation), data is lost. A 5 ms stall loses 58 bytes if there is no buffer at all (more than a whole message), and is harmless with a 64-byte buffer. Size the buffer for the longest time the loop can stall and the highest data rate, with margin.
// Interrupt-driven receive with a ring buffer (head: written by ISR, tail: read by main loop)
#define RX_SIZE 64 // power of two so the wrap is a mask
volatile uint8_t rx_buf[RX_SIZE];
volatile uint8_t rx_head = 0, rx_tail = 0;
ISR(USART_RX_vect) {
uint8_t next = (rx_head + 1) & (RX_SIZE - 1);
uint8_t b = UDR0; // always read the byte to clear the flag
if (next != rx_tail) { rx_buf[rx_head] = b; rx_head = next; } // drop it if full
}
int rx_read(void) { // returns -1 when empty
if (rx_tail == rx_head) return -1;
uint8_t b = rx_buf[rx_tail];
rx_tail = (rx_tail + 1) & (RX_SIZE - 1);
return b;
}
- Message framing: a stream of bytes has no message boundaries. Mark them with a start byte and a length, a terminator (a newline for text), or a time gap (as Modbus RTU does), and check a checksum or CRC (see Memory, Number Formats and Reliability).
- Flow control: RTS/CTS hardware lines or XON/XOFF characters stop the sender when your buffer is nearly full; high-rate links without it lose data.
- Errors: the UART flags framing errors (wrong baud or noise), parity errors and overruns; count them: a rising count is an early warning of a bad cable or clock.
- Voltage levels: a microcontroller UART is TTL (0 to 3.3 V or 5 V). RS-232 uses ±3 to ±15 V and is inverted; RS-485 is differential. Never connect an RS-232 port straight to a TTL pin; use a level converter.
- Printing from an ISR or while sending:
Serial.printblocks when the transmit buffer is full, so large debug output slows the program.
I²C: two wires, many devices
I²C has a clock (SCL) and a data line (SDA), both open-drain: devices only pull them low, and pull-up resistors pull them high. One master generates the clock and starts each transfer with a START condition (SDA falls while SCL is high), sends the 7-bit address and a read/write bit, and the addressed device answers with an ACK (it pulls SDA low for the ninth clock). Data bytes follow, each acknowledged, until the master sends a STOP. A repeated START lets the master switch from a write (to set a register pointer) to a read without releasing the bus.
Worked example: how long does a read take?
- Write one register (address byte, register byte, data byte): 3 bytes × 9 clocks = 27 clocks plus the START and STOP, about 29 clock periods: 290 µs at 100 kHz and 73 µs at 400 kHz.
- Read a 16-bit register: address+W, register pointer, repeated START, address+R, then two data bytes: 5 bytes × 9 clocks plus START, repeated START and STOP, about 48 clocks: 480 µs at 100 kHz, 120 µs at 400 kHz. Reading ten such sensors every millisecond would take 1 200 µs at 400 kHz, more than the 1 ms available, so read them less often, use interrupts or DMA, or a faster bus.
- 7-bit addresses run 0x08 to 0x77 (128 values with some reserved). Address clashes are common: many sensors have one or two address pins; otherwise use an I²C multiplexer (such as the TCA9548A) or a second bus.
Pull-up resistors
Worked example: choosing the pull-up
- The rise time from 30 % to 70 % of VCC is tr = 0.8473 R C, so Rmax = tr ÷ (0.8473 C). With a bus capacitance of 150 pF (a few devices and a short cable) the standard limit of 1 000 ns at 100 kHz gives Rmax = 1 000 ns ÷ (0.8473 × 150 pF) = 7.9 kΩ, and the 300 ns limit at 400 kHz gives 2.4 kΩ.
- The minimum comes from the sink current: a device must pull the line below 0.4 V with at most 3 mA: Rmin = (3.3 − 0.4) ÷ 3 mA = 0.97 kΩ.
- The choice: 2.2 kΩ to 4.7 kΩ is right for 400 kHz and 3.3 V; 4.7 kΩ to 10 kΩ for 100 kHz on a short bus. The internal pull-ups of a microcontroller (20 to 50 kΩ) are usually too weak except for very short, slow buses.
- Total capacitance is limited to 400 pF by the standard, which restricts bus length and the number of devices. Long wires need lower speeds, stronger pull-ups, or a bus extender/buffer.
- Level shifting: mixed 3.3 V and 5 V devices need a bidirectional shifter (a MOSFET per line or a dedicated chip).
- Clock stretching: a slave can hold SCL low to ask for time; make sure your driver and timeouts allow for it.
- Stuck bus: if a slave holds SDA low after an interrupted transfer, clock SCL up to nine times to release it, then send a STOP.
- Scan the bus when you first connect a device: try every address from 0x08 to 0x77 and print the ones that ACK.
// I2C read of a 16-bit register (Arduino Wire library)
uint16_t read_reg16(uint8_t addr, uint8_t reg) {
Wire.beginTransmission(addr);
Wire.write(reg); // set the register pointer
if (Wire.endTransmission(false) != 0) // false = repeated START; non-zero means no ACK
return 0xFFFF;
if (Wire.requestFrom(addr, (uint8_t)2) != 2) return 0xFFFF;
uint16_t hi = Wire.read(), lo = Wire.read();
return (hi << 8) | lo; // check the datasheet for the byte order
}
SPI: fast and simple
SPI uses a clock (SCK), two data lines (MOSI out of the master, MISO in) and a chip-select (CS) per device. Data moves in both directions at the same time: every byte sent also receives a byte. The master sets the clock speed, so SPI is limited only by the slave and the wiring: 1 to 50 MHz or more.
| Mode | Clock idle (CPOL) | Data sampled on (CPHA) | Typical devices |
|---|---|---|---|
| 0 | Low | Rising edge | Most devices: SD cards, many sensors, flash memory |
| 1 | Low | Falling edge | Some ADCs and displays |
| 2 | High | Falling edge | Rare |
| 3 | High | Rising edge | Many sensors (also accept mode 0), some radios |
Worked example: SPI speed and SD cards
- At 4 MHz one byte takes 8 ÷ 4 MHz = 2 µs, so a 4-byte transfer takes 8 µs plus the time to assert CS and respond.
- An SD card at 25 MHz moves 25 000 000 ÷ 8 = 3.13 MB/s at most, in practice 1 to 2 MB/s after protocol overhead, still far faster than any logging rate of a sensor node.
- A slow slave limits the clock: read the datasheet for the maximum SCK and the setup time for CS. Long wires add ringing and delay, so lower the clock and add series resistors (22 to 100 Ω) at the source.
- Pick the mode from the device’s datasheet and set the same mode, the bit order (usually MSB first) and the clock speed in the driver.
- Hold CS low for the whole transaction and release it after: the slave uses the CS rising edge to end a command. Give each slave its own CS pin, keep the others high, and put unused MISO lines in high impedance.
- To read a register send the command byte, then clock out dummy bytes (0x00 or 0xFF) to receive the reply.
- Share the bus carefully when several devices need different modes or speeds: reconfigure between transactions, and never talk to two at once.
- Daisy chains (shift registers, LED drivers) pass data through each device: one CS and a long shift.
CAN: robust multi-node messaging
CAN is a differential multi-master bus for vehicles and machines. A message carries an identifier (11 or 29 bits), not a destination address: every node sees every message and decides whether it cares. When two nodes transmit at once, the one with the lower identifier wins arbitration without losing the message, so the identifier is also the priority. The hardware handles bit timing, arbitration, CRC and automatic retransmission, and it flags errors; a node that causes too many errors takes itself off the bus. The bus needs a 120 Ω terminator at each end, and a transceiver chip between the MCU and the wires (see Industrial Communication).
Worked example: frame time and bus load
- A standard frame with 8 data bytes is 47 bits of overhead (start, identifier, control, CRC, acknowledge, end, inter-frame space) plus 64 data bits = 111 bits, and up to 135 bits with worst-case bit stuffing. At 500 kbit/s a bit lasts 2 µs, so a frame takes 222 µs, up to 270 µs.
- Four periodic messages: 8 bytes every 10 ms, 8 bytes every 20 ms, 4 bytes every 100 ms and 8 bytes every 50 ms load the bus by Σ (bits ÷ rate ÷ period) = 4.8 % (worst-case stuffing). Keep a CAN bus below about 30 to 50 % to leave room for bursts and retransmissions.
- Choosing identifiers: lower numbers for urgent messages (emergency stop, control commands), higher numbers for slow status data. Standard protocols (CANopen, J1939, NMEA 2000) assign them for you.
- Acceptance filters in the controller let the hardware ignore messages you do not need.
- Set the bit timing (prescaler, time segments, sample point near 80 %) for the speed and the bus length; all nodes must agree. A common mistake is a mismatched bit rate, which floods the bus with error frames.
- CAN FD raises the data rate (up to 5 Mbit/s) and the payload (64 bytes) after arbitration.
Interrupts, DMA and bit-banging
| Method | CPU load | Use |
|---|---|---|
| Polling (blocking) | High: the CPU waits for every byte | Quick tests, tiny programs, very short transfers |
| Interrupt-driven | One interrupt per byte (a few µs each) | UART receive, I²C and SPI at modest rates |
| DMA (direct memory access) | Almost none: the DMA engine moves a whole block, then interrupts once | High-rate SPI (displays, SD cards), UART blocks, ADC streams |
| Bit-banging | Whole CPU during the transfer | A protocol the chip has no hardware for, at low speeds; or the RP2040’s PIO to do it in hardware |
The load of an interrupt-driven UART at 115 200 baud is one interrupt per 86.8 µs; if the ISR takes 3 µs that is 3 % of the CPU, fine on its own but significant at 1 Mbaud (30 %), where DMA or a FIFO is the answer (see GPIO, Interrupts and Debouncing for the interrupt budget).
Designing a robust protocol
- Frame: start marker, length, payload, checksum or CRC. Example:
0x7E | len | cmd | data… | CRC16. - Timeouts everywhere: never wait forever for a reply; time out, drop the partial frame and re-synchronize on the next start marker.
- Validate before acting: check the length, the checksum and the range of every value before using it.
- Retry with a limit, and report the failure instead of hanging.
- Version the protocol and keep byte order and units fixed in a document. Use fixed-width integers and a defined byte order, not C structs copied to the wire (padding and endianness differ).
- Make errors visible: count CRC errors, timeouts and overruns, and expose them for debugging.
When a bus does not work
| Symptom | Likely cause | Try |
|---|---|---|
| Garbage on the serial port | Baud rate mismatch, wrong voltage level, TX and RX not crossed, bad ground | Check the baud rate and 8N1 framing; cross TX to RX; join grounds; check levels; use a logic analyzer with a UART decoder |
| Characters lost | Buffer overrun, a blocked loop, no flow control | Bigger ring buffer, interrupts or DMA, flow control; find what blocks the loop |
| I²C device not found | Wrong address, missing pull-ups, swapped SDA and SCL, 3.3 V versus 5 V | Run an I²C scanner; check pull-ups on a scope; check the voltage; confirm the 7-bit address (some datasheets give the 8-bit one) |
| I²C works then hangs | Stuck SDA line after a reset in mid-transfer, too much capacitance, noise | Clock SCL nine times to free the line; add a timeout and a recovery routine; lower the speed; shorten the wires |
| SPI returns all 0s or all 1s | Wrong mode, CS not asserted, MISO not connected, slave not powered or in reset | Check the mode and CS on a logic analyzer; check wiring and power; read an ID register first |
| SPI data shifted by one bit | Wrong clock phase (mode), or too high a clock for the wires | Try the neighboring mode; lower the clock; shorten the wires |
| CAN: bus-off or error frames | Bit rate mismatch, missing terminator, no other node to acknowledge, wiring | Same rate on all nodes; 120 Ω at both ends (about 60 Ω across the pair); at least two nodes; check CAN-H and CAN-L polarity |
| Works on the bench, fails in the machine | Noise, ground shifts, long cables | Shielded twisted pair, differential bus (RS-485 or CAN), lower speed, proper terminations (see Industrial Communication) |
Related
- Embedded Systems: the overview of this guide.
- GPIO, Interrupts and Debouncing and Firmware Architecture, State Machines and RTOS: interrupts, buffers and queues.
- Debugging, Testing and Troubleshooting: logic analyzers decode these buses.
- Industrial Communication: RS-485, Modbus, CAN and Ethernet in machines.
- Microcontroller Electronics: pull-ups, level shifting and wiring.
FAQ
Should I use I²C or SPI?
I²C needs two wires and handles many devices, but it is slower and more fussy about pull-ups and wiring. SPI needs more wires but is simple, fast and robust on a board. Use I²C for slow sensors, SPI for speed.
How do I find the I²C address of my device?
Run an I²C scanner sketch that tries every address from 0x08 to 0x77 and prints the ones that answer. Datasheets sometimes list the 8-bit address (7-bit address shifted left, plus the R/W bit); divide by two.
What baud rate should I use?
The highest that both ends handle cleanly with an error under 2 %: 115 200 is the usual default; 1 Mbaud or more works on short wires with a clock that divides well. Prefer a crystal that gives low error.
Why do I need a termination resistor on CAN?
Without 120 Ω at both ends the signal reflects along the cable and corrupts the bits. A healthy bus measures about 60 Ω between CAN-H and CAN-L with the power off.
References
- NXP, UM10204 I²C-bus specification and user manual (pull-up and rise-time formulas).
- Motorola, SPI Block Guide; Bosch, CAN Specification 2.0; ISO 11898-1.
- Microcontroller datasheets (UART baud-rate registers and error tables; I²C and SPI peripherals).
Last reviewed: October 2026. Timings and counts use a 16 MHz AVR and common parts: check your datasheet.
