ROBOTECA FREE SAMPLE
Dashboard

A free lesson from Embedded Systems & Electronics: the whole module, nothing cut short.

LESSON · Embedded Systems

Serial protocols: UART, SPI, I2C

Turn 1 35 min LESSON

ALearning Material

Microcontrollers rarely work alone: they talk to sensors, displays, memory, and other chips. They do so over serial communication protocols, where data is sent one bit at a time over a few wires. The three you'll use constantly (‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍UART, SPI, and I2C) each suit different situations, and knowing which to use (and how each works) is essential to interfacing any peripheral.

UART (Universal Asynchronous Receiver/Transmitter): asynchronous point-to-point serial: two wires (TX, RX), no shared clock (both sides agree on a baud rate). Simple, common for device-to-device or to a PC (e.g. debug output, a GPS module). Two devices only.

SPI (Serial Peripheral Interface): synchronous (a shared clock), fast, full-duplex, master/ slave with usually 4 wires (clock SCLK, MOSI, MISO, and a chip-select per slave). Fast and simple per-device, but needs a separate chip-select line for each slave. Good for high-speed peripherals (displays, SD cards, fast ADCs).

UART: TX <-> RX        (2 wires, async, baud rate, 2 devices)
SPI:  SCLK, MOSI, MISO, + CS per slave   (sync, fast, full-duplex, a wire per slave)
I2C:  SDA, SCL         (2 wires shared bus, addressed devices, many on one bus)

‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍I2C (Inter-Integrated Circuit): synchronous, only two wires (data SDA + clock SCL) shared by many devices, each with a unique address. Slower than SPI but very wire-efficient: many sensors on one two-wire bus. Great for connecting lots of low-speed sensors. The choice: UART for simple two-device async links; SPI for speed (few devices, but a wire per slave); I2C for many devices on few wires (addressed bus, slower). The disciplines: know the three protocols and their wires/speed/topology, and choose by the situation (simple link / speed / many-devices-few-wires). These are how an MCU connects to the world.

Syntax you need here. The three buses, in the form you actually type. Each has a setup call, a transfer call, and one parameter that must match at both ends:

// UART - asynchronous, point to point. THE BAUD RATE MUST MATCH ON BOTH SIDES.
Serial.begin(115200);                  // start the port at 115200 baud
Serial.print(value);                   // no newline
Serial.println("text");                // with a newline
Serial.printf("t=%lu v=%d\n", t, v);   // where the core provides it
if (Serial.available()) {              // bytes waiting in the receive buffer
    char c = Serial.read();            // one byte; -1 if nothing is there
}
Serial.readStringUntil('\n');          // a whole line, up to the delimiter

// I2C - two wires, many devices, each with a 7-bit ADDRESS
Wire.begin();
Wire.beginTransmission(0x68);          // the device address
Wire.write(REG_ADDR);                  // usually: which register you want
Wire.endTransmission(false);           // false = repeated START, keep the bus
Wire.requestFrom(0x68, 2);             // read 2 bytes back
uint8_t hi = Wire.read(), lo = Wire.read();

// SPI - four wires, fast, one chip select PER device
SPI.begin();
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
digitalWrite(CS, LOW);                 // select THIS device: active LOW
uint8_t in = SPI.transfer(out);        // full duplex: one byte out, one byte in
digitalWrite(CS, HIGH);                // deselect
SPI.endTransaction();

‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍The parameter that must match is different for each bus, and getting it wrong fails differently. UART needs the baud rate agreed in advance, because there is no clock line: a mismatch gives consistent garbage rather than silence, which is the signature to recognise. I2C needs the address, and a device that is absent, unpowered or at a different address simply does not acknowledge, so endTransmission returns non-zero and you read zeros or 0xFF. SPI needs the mode, the clock polarity and phase pair, and a mode mismatch gives data shifted by one bit, which looks like a plausible but wrong number.

Two habits save time. Print at a fixed rate rather than every loop iteration, since a serial port at 115200 baud carries about 11 kilobytes per second and a chatty loop blocks on the transmit buffer, which quietly turns a fast control loop into a slow one. And pull the I2C bus up: SDA and SCL are open-drain and do not idle high without resistors, typically 4.7 kilohms, which many breakout boards already carry.

Why it exists. A microcontroller must communicate with many peripherals (sensors, displays, memory, other MCUs), and serial protocols send data over a few wires. UART, SPI, and I2C are the three standard choices, each with different speed, wiring, and topology trade-offs, so knowing how each works and when to use it is essential to interfacing any peripheral, the core of connecting an embedded system to the world.

‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍Mental model. The three protocols are like three ways to have a conversation. UART is two people talking directly with no clock, just an agreed pace (baud rate), simple but only two of them. SPI is a fast conversation led by a conductor's beat (the clock), but you need a separate 'attention' line to address each listener. I2C is a group on a party line (two shared wires) where you call each device by name (address) before talking. Many can share, but it's slower.

Common misunderstandings.

  • "All serial protocols are interchangeable." They differ in speed, wires, and topology: UART (2 devices, async), SPI (fast, a wire per slave), I2C (many devices, 2 shared wires, addressed, slower), choose by the situation.
  • "SPI vs I2C is just speed." It's also wiring/topology: SPI needs a chip-select per slave (more wires for many devices), while I2C shares two wires among many addressed devices. I2C wins on wire count, SPI on speed.
  • "UART needs a clock line." UART is asynchronous‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍, no shared clock; both sides just agree on the baud rate, which is why a mismatched baud rate garbles the data.

Connections. Serial protocols build on the GPIO/peripherals of the MCU (the GPIO lesson and the Turn-2 STM32 lesson: the MCU has UART/SPI/I2C peripherals), are how the projects read sensors and report data (the UART reporter project, the data loggers), use DMA for efficiency (the Turn-2 STM32-peripherals lesson), and connect to the C++/Python serial lessons (the host side). The standard way an embedded system talks to peripherals.

BImmediate Active Recall

QUERY

What is UART, and what are its key characteristics?

REVEAL
ANSWER

UART (Universal Asynchronous Receiver/Transmitter) is asynchronous, point-to-point serial: two wires (TX, RX), ‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍no shared clock. Both sides agree on a baud rate. It's simple and common for device-to-device or MCU-to-PC links (debug output, a GPS module), but connects only two devices. A mismatched baud rate garbles the data (since there's no clock to sync to).

Did you recall it?
QUERY

What is SPI, and when is it the right choice?

REVEAL
ANSWER

SPI (Serial Peripheral Interface) is synchronous (a shared clock SCLK), fast, full-duplex, master/slave, usually ‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍4 wires (SCLK, MOSI, MISO, + a chip-select per slave). It's the right choice for high-speed peripherals (displays, SD cards, fast ADCs) and few devices. It's fast and simple per device, but needs a separate chip-select line for each slave (more wires as devices grow).

Did you recall it?
QUERY

What is I2C, and what is its main advantage?

REVEAL
ANSWER

I2C (Inter-Integrated Circuit) is synchronous, using only two wires (data SDA + clock SCL) shared by many devices, each with a unique ‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍address. Its main advantage is wire efficiency (many devices on one two-wire bus (addressed)) so it's great for connecting lots of low-speed sensors. The trade-off is it's slower than SPI.

Did you recall it?
QUERY

How do you choose between UART, SPI, and I2C?

REVEAL
ANSWER

By the situation: UART for a simple two-device asynchronous link (e.g. to a PC/GPS); SPI for speed with few devices (accepting a chip-select wire per slave: displays, SD cards, fast ADCs); ‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍I2C for many devices on few wires (an addressed two-wire bus, slower, lots of sensors). Match the protocol to the speed, wire count, and number of devices you need.

Did you recall it?

CConceptual Questions

Answer each in your own words in the box, then reveal the model answer to compare. These ask why, not how, and your answers are saved.

PROMPT

Why do three different serial protocols (UART, SPI, I2C) coexist rather than one being universally best, and how do their trade-offs map to different situations?

‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍REVEAL MODEL ANSWER
MODEL ANSWER

Three protocols coexist because serial communication has competing demands (speed, wire count, number of devices, and simplicity) and no single design optimizes all of them, so each protocol makes a different trade-off that's best in a different situation. UART optimizes simplicity for a point-to-point link: just two wires and no shared clock (both sides agree on a baud rate), which is dead simple and perfect for connecting two devices (an MCU to a PC for debug, to a GPS module), but it fundamentally only links two devices and its asynchronous nature means a baud-rate mismatch corrupts data. SPI optimizes speed: a shared clock makes it synchronous and fast, full-duplex (simultaneous send and receive), and simple per device: ideal for high-speed peripherals like displays, SD cards, and fast ADCs; its cost is wiring, because each additional slave needs its own chip-select line, so wiring grows with the number of devices and it doesn't scale gracefully to many peripherals. I2C optimizes wire efficiency for many devices: only two shared wires (data and clock) carry a whole bus of devices, each addressed by a unique address, so you can hang many low-speed sensors on the same two wires. Its cost is speed (slower than SPI) and a bit more protocol complexity (addressing, acknowledgement). So the trade-offs map cleanly: need a simple link between two devices? UART. Need raw speed to a few peripherals and can afford a wire per device? SPI. Need to connect many devices over as few wires as possible and can tolerate lower speed? I2C. This is the recurring engineering theme of fit-to-purpose: rather than one universal protocol that's mediocre everywhere, you have a small toolkit where you pick the one whose trade-off matches your situation's priorities (speed vs wires vs device count vs simplicity). Knowing all three and their trade-offs is what lets an embedded developer interface any peripheral well, which is exactly why they all persist and why the skill is choosing among them, not memorizing one.

‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍Compared to the model answer - did you get it?
PROMPT

Why does UART being asynchronous (no shared clock, just an agreed baud rate) make it both simple and fragile, and what does this reveal about the role of a clock in serial communication?

REVEAL MODEL ANSWER
MODEL ANSWER

UART's asynchronicity is the source of both its simplicity and its fragility, and contrasting it with the clocked protocols reveals what a shared clock actually does. In any serial link, bits are sent one after another on a wire, and the receiver must know ‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍when to sample the wire to read each bit correctly. It has to align its sampling to the sender's bit boundaries. Synchronous protocols (SPI, I2C) solve this by sending an explicit clock line alongside the data: the clock signal tells the receiver exactly when each bit is valid, so the two sides are inherently synchronized and the data rate can be whatever the clock runs at, even varying, without confusion. UART omits that clock line (that's what 'asynchronous' means) which makes it simple (only two wires, TX and RX, no clock to route) and is why it's so convenient for basic point-to-point links. But without a shared clock, the receiver has no signal telling it when bits are valid; instead, both sides must independently agree in advance on the bit rate (the baud rate) and the receiver uses that agreed timing (plus start bits to mark the beginning of each byte) to guess when to sample. This is the fragility: if the two sides' baud rates don't match (or a side's clock drifts too far), the receiver samples at the wrong moments and reads garbage. The data is corrupted with no clock to fall back on. So UART trades the robustness and rate-flexibility a shared clock provides for the simplicity of fewer wires, at the cost of requiring exact pre-agreement on speed and being sensitive to timing mismatch. The deeper lesson is that a clock line is the mechanism that keeps sender and receiver synchronized bit-by-bit: synchronous protocols carry it explicitly (robust, rate-flexible, but an extra wire), while asynchronous UART replaces it with a pre-shared agreed rate (simpler wiring, but fragile to mismatch). Understanding this is why 'check the baud rate' is the first thing you do when a UART link garbles data, and why SPI/I2C don't have that particular failure mode.

‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍Compared to the model answer - did you get it?

DPractice Problems

P1 (easy). Compute the bus loading for a sensor node.

(a) A UART runs at 115200 baud, 8N1. Compute the bits per character frame, the byte rate, and the time to send one byte. How long does a 40-byte telemetry line take? (b) An I2C bus at 400 kHz polls six sensors at 50 Hz. Each read is: start, address byte, register byte, repeated start, address byte, six data bytes, stop, with every byte costing 9 bit-times (8 data plus the acknowledge). Compute the bits and the time per transaction, the total bus time per 20 ms cycle, and the bus utilisation.

P2 (medium). Choose the bus for four peripherals on one board, and justify each on a number: an ‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍SD card logging at 200 kB/s, a temperature sensor read once a second, a 9-axis IMU read at 500 Hz returning 18 bytes, and a debug link to a PC.

Give the bus, the clock rate needed, the pin count, and the one attribute that decides it. Then state what goes wrong if the IMU is put on the same I2C bus as the temperature sensor at 100 kHz.

P3 (harder). Two boards exchange data over UART and receive garbage: mostly plausible-looking bytes with occasional framing errors.

Work through the diagnosis: list the candidate causes in the order you would check them, with the specific symptom each produces and the measurement that confirms it. Compute the worst-case clock tolerance a UART link can survive and say where that number comes from. Finish with the design change that removes the whole class of problem.

‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍Solutionsclick to reveal

P1. (a) UART at 115200, 8N1.

  • 8N1 means 8 data bits, no parity, 1 stop bit, plus 1 start bit: 10 bits per character. That 10 rather than 8 is the number people forget, and it costs 25 percent
  • 11 520 bytes per second
  • 86.8 microseconds
  • A 3.47 milliseconds

Worth noticing: 3.47 ms is a long time on a microcontroller. If that line is sent from a blocking printf in the main loop, the loop stalls for 3.5 ms every time it logs, which on a 1 kHz control loop means three missed cycles. This is why telemetry goes out through a ring buffer and a transmit interrupt, or through DMA, rather than through a blocking call, and it is one of the commonest causes of a control loop that jitters whenever logging is enabled.

‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍(b) I2C at 400 kHz, six sensors at 50 Hz.

Bits per transaction:

  • Start: 1
  • Address plus write bit, 9 bits (8 plus acknowledge): 9
  • Register address byte: 9
  • Repeated start: 1
  • Address plus read bit: 9
  • Six data bytes at 9 bits each: 54
  • Stop: 1
  • Total: 84 bits

  • Time per transaction = 84 / 400 000 = 210 microseconds

  • 1260 microseconds = 1.26 ms
  • Cycle period at 50 Hz = 20 ms

Comfortable, with room for more sensors or a faster poll rate. Two caveats the arithmetic hides.

Clock stretching. An I2C slave may hold SCL low while it prepares data, and some sensors stretch for hundreds of microseconds after a conversion command. The 210 microseconds is the best case; a sensor that stretches for 500 microseconds triples its own transaction time and the bus utilisation with it. Check the datasheet for stretching before believing the calculation.

‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍The real cost is not bus time, it is CPU time. If those transactions are done by polling the peripheral's status register, the CPU is occupied for the full 1.26 ms, which is 6.3 percent of the CPU as well as of the bus. Done with interrupts, the CPU is occupied only at byte boundaries. Done with DMA, it is occupied twice per transaction. On a constrained device the question "how busy is the bus" and "how busy is the processor" have very different answers, and the second one is usually what limits you.

P1Compared to this solution - did you get it right?

P2.

Peripheral ‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍Bus Clock Pins The deciding attribute
SD card, 200 kB/s SPI 8 to 25 MHz 4 plus chip select Throughput. 200 kB/s is 1.6 Mbit/s of payload, and I2C's ceiling is 400 kHz (about 36 kB/s after overhead). SPI is the only option in this list that reaches it, and it is full duplex with no per-byte acknowledge overhead
Temperature sensor, 1 Hz I2C 100 kHz 2, shared Pin count. One reading a second needs no speed at all. What it needs is not to consume two more pins, and I2C's addressing lets it share the bus with everything else slow
9-axis IMU, 500 Hz, 18 bytes SPI, or I2C at 400 kHz SPI at 1 to 10 MHz 4 plus chip select Latency and jitter, discussed below
Debug to PC UART 115200 2 What the other end is. A PC has a serial port (through USB). It does not have an I2C or SPI master, and the debug link's job is to be trivially connectable

‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍What goes wrong if the IMU shares a 100 kHz I2C bus with the temperature sensor.

The bandwidth arithmetic first. An 18-byte IMU read on I2C costs roughly 192 bits. At 100 kHz that is 1.92 ms, and at 500 Hz the period is 2 ms.

Bus utilisation: 96 percent. The IMU alone nearly saturates the bus, leaving 80 microseconds per cycle for everything else. Add the temperature sensor's occasional transaction, at 210 microseconds, and the bus is over-subscribed: some IMU reads will not complete before the next one is due.

And the consequence is worse than a missed sample. Three specific failures:

  • Jitter in the sample interval. The IMU is being read for an attitude estimate, and the estimator integrates gyro rate over the interval between samples. If that interval varies between 2.0 and 2.4 ms depending on whether the temperature sensor got in first, the integration is wrong by the variation, and the attitude estimate drifts in a way that looks like sensor bias and is actually a scheduling artefact
  • ‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍Blocking the CPU. A 1.92 ms transaction, polled, occupies 96 percent of the processor as well as the bus
  • One misbehaving device stops everything. I2C is a shared bus with no arbitration timeout in the basic protocol. A slave that stretches the clock, or one that is left mid-transaction by a reset, holds SDA low and locks the bus for every device on it. The temperature sensor can take down the IMU

The fix, in order of preference:

  1. Put the IMU on SPI. At 4 MHz, 18 bytes take 36 microseconds against I2C's 1920: 53 times faster, no addressing overhead, no clock stretching, no shared-bus lockup, and a dedicated chip select so its timing is independent of anything else. The cost is three pins
  2. If SPI is unavailable, raise I2C to 400 kHz, which brings the transaction to 480 microseconds and utilisation to 24 percent. Acceptable, and it still shares a bus with a device that can lock it
  3. Read the IMU with DMA regardless of bus, so the CPU cost falls to two interrupts per sample

The general rule this illustrates: I2C is for slow, many, pin-limited. SPI is for fast, few, latency-sensitive. The mistake is not choosing I2C for the IMU, it is choosing it without computing the transaction time against the sample period. That is one line of arithmetic and it decides the architecture.

‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍P2Compared to this solution - did you get it right?

P3. The candidates, in order.

# Cause Symptom The measurement that confirms it
1 Baud rate mismatch Consistently wrong bytes with a repeatable pattern; framing errors on most bytes Scope on TX: measure the width of one bit. At 115200 it must be 8.68 microseconds. Compare against the other end's configured rate
2 Clock source error. The MCU is running from its internal RC oscillator rather than the crystal, so its baud rate is off by a few percent Same as above, but the configured rates agree. The error varies with temperature Measure the bit width, and separately measure a known timer output. If both are off by the same percentage, the system clock is the problem, not the UART divisor
‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍3 Missing common ground between the two boards Intermittent, worse when other loads switch, and completely fixed by adding a ground wire Meter between the two grounds: any DC difference at all, or a scope showing the receiver's idle level not sitting at the supply rail
4 Framing mismatch: one end 8N1, the other 8E1 or 8N2 Every byte's value is right but framing errors appear, or every byte is shifted Compare configurations. On the scope, count the bits between start edges
5 Receive overrun. The receiver's ISR is not servicing the UART fast enough and bytes are lost Bytes missing rather than wrong, and it correlates with the receiver being busy Check the UART's overrun error flag. It is almost always there and almost never read
6 Signal integrity: long unterminated wires, reflections, or interference Errors that get worse with cable length or near a motor Scope at the receiver's pin, not the transmitter's, looking at edge quality and the level at the sampling instant

‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍Note that 1 and 2 produce nearly identical symptoms and have different fixes, which is why the measurement for 2 separates them.

The worst-case clock tolerance.

A UART receiver synchronises on the falling edge of the start bit and then samples each subsequent bit at its centre, timing purely from its own clock. There is no clock line and no resynchronisation during the frame.

The last bit sampled is the stop bit, which is 9.5 bit times after the start edge for 8N1 (0.5 to reach the centre of bit 0, plus 9 more bit periods). For the sample to still land within the stop bit, the accumulated timing error must be less than half a bit period:

error per bit times 9.5 must be less than 0.5, so error must be less than 5.3 percent

That is the total between the two ends. Split evenly, each end must be within about 2.6 percent, and in practice designers target 2 percent or better to leave margin for temperature and for the receiver's own sampling granularity.

Where the number matters: a typical internal RC oscillator is specified at to 2 percent over temperature, which is ‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍right at the limit, and two devices at opposite extremes of their tolerance exceed it. This is precisely why a board that communicates reliably on the bench fails in a cold enclosure, and why UART-bearing designs use a crystal (tens of parts per million) rather than the internal oscillator.

The design change that removes the class of problem: use a synchronous or self-clocking link.

SPI carries its clock on a wire, so there is no tolerance requirement at all: the receiver samples when told to, and the two ends can differ by any amount.

CAN self-synchronises on every recedessive-to-dominant edge within the frame, so it re-aligns continuously rather than once per byte, which is why it tolerates worse clocks over longer cables, and why it is what a vehicle uses.

USB recovers the clock from the data stream.

If UART must be kept, three changes reduce the exposure substantially: a crystal rather than the internal oscillator; RS-485 or RS-422 differential signalling instead of single-ended, which removes the ground-difference and noise categories entirely; and a ‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍framing protocol with a start delimiter and a checksum, so that a corrupted byte is detected and the receiver can resynchronise rather than silently passing garbage to the application. The last one is worth doing regardless: a link that cannot tell you it failed is worse than one that fails.

P3Compared to this solution - did you get it right?

EFeynman Exercise

Explain to a beginner, using three ways to have a conversation: (1) two people talking directly at an agreed pace with no conductor (UART): simple but only two of them, (2) a fast conversation led by a conductor's beat where you need a separate 'attention' signal for each listener (SPI), and (3) a group on a shared party line where you call each person by name before talking (I2C). Many can share but it's slower.

‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍REVEAL MODEL ANSWER
MODEL ANSWER

The three serial protocols an MCU uses are like three ways to have a conversation. UART is like two people talking directly at an agreed pace, with no conductor: they just both agree beforehand how fast they'll speak (the baud rate) and then talk back and forth on two lines: dead simple, but it only works between two people, and if they disagree on the pace, it comes out as gibberish (a baud-rate mismatch garbles the data). SPI is like a fast conversation led by a conductor's beat: there's a shared clock (the conductor) keeping everyone perfectly in time, so it's quick and you can talk and listen at once, but to address each listener you need a separate 'attention' signal for each one (a chip-select per device), so the more listeners, the more 'attention' wires you string up. Great when you need speed with just a few devices (a fast display, an SD card). I2C is like a group on a shared party line: everyone shares just ‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍two wires, and to talk to someone you call them by name first (each device has an address) so the right one responds: many people can share the same two wires, which is wonderfully wire-efficient for hooking up lots of sensors, but the shared, take-turns nature makes it slower than SPI. So you pick the conversation style to fit: two people, simple, agreed pace -> UART; fast, few listeners, with attention signals -> SPI; many sharing two wires, called by name -> I2C. Knowing all three and which fits is how an MCU talks to everything around it.

Compared to the model answer - did you get it?

FError Analysis Framework

  • Treating UART, SPI, and I2C as interchangeable. Why: they're all serial. Recognise: they differ in speed/wires/topology; the wrong one fits poorly. ‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍Avoid: choose by the situation (simple link / speed / many devices on few wires).
  • Thinking SPI vs I2C is only about speed. Why: SPI is faster, so use it. Recognise: you ignore wiring. SPI needs a chip-select per device; I2C shares 2 wires. Avoid: weigh speed (SPI) vs wire-efficient many-device bus (I2C).
  • Expecting UART to have a clock line. Why: synchronous serial has a clock. Recognise: UART is async, no clock; a baud mismatch garbles data. Avoid: set both sides to the same baud rate (no shared clock to resync).
  • Hanging many SPI devices off one chip-select. Why: fewer wires is simpler. Recognise: SPI addresses slaves by separate chip-selects; sharing one breaks it. Avoid: give each SPI slave its own chip-select (or use I2C for many devices).

GMini Challenge

Design the peripheral communication for an embedded sensor node: it must connect to a PC for debug, a fast display, and several low-speed environmental sensors. Choose the protocol for each, specify the wiring, and explain how matching each protocol to the situation interfaces everything efficiently.

‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍REVEAL MODEL ANSWER
MODEL ANSWER

Protocol choices (match to situation):

Connection Protocol Wiring Why
MCU <-> PC (debug output) UART TX, RX (2 wires) Simple asynchronous point-to-point link to a PC (agreed baud rate)
MCU <-> fast display SPI SCLK, MOSI, MISO, + CS (chip-select) High speed, full-duplex, ideal for a fast display; one CS for the one device
MCU <-> several low-speed sensors I2C SDA, SCL (2 shared wires) Many addressed devices on two wires. Wire-efficient for several low-speed sensors

Wiring summary: UART uses 2 wires to the PC; SPI uses ~4 wires to the display (clock, two data, chip-select); all the I2C sensors share the same 2 wires (SDA+SCL), each responding to its unique address, so adding more sensors adds ‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍no extra wires, just more addresses on the bus.

How matching each protocol interfaces everything efficiently:

  • UART for the PC link: the situation is a simple two-device async link: UART is exactly that (TX/RX, baud rate), minimal and standard for debug.
  • SPI for the display: the situation needs speed. SPI's shared clock gives fast, full-duplex transfer ideal for a display; the single chip-select cost is trivial for one device.
  • I2C for the many sensors: the situation is many low-speed devices, few wires: I2C shares two wires among all the sensors (addressed), so you connect many without a forest of wires (accepting lower speed, fine for environmental sensors).

By choosing each protocol to fit its situation (simple link / speed / many-devices-few-wires), the node interfaces the PC, display, and all the sensors efficiently. The right speed and minimal wiring for each. This fit-to-purpose selection is the core skill of interfacing peripherals, and these protocols (using the MCU's UART/SPI/I2C peripherals, often with DMA) are how the embedded system talks to everything around it.

‍​‌‌​​‌‌​​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌​‌​​‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​​‌​‌‌​‌‌​‌​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌‍Compared to the model answer - did you get it?

Quiz Check

A quick auto-graded check, separate from the recall cards above. Your score is pooled with the recall cards into this module's Mastery score, and completing this lesson requires the quiz submitted with pooled mastery at 80% or above.

QUIZAuto-graded check · feeds your mastery score
  1. UART is:

  2. SPI's main cost when connecting many devices is:

  3. I2C's main advantage is:

  4. You choose the serial protocol by:

This is a free sample

Progress and the spaced-repetition reviews are part of the course. The full track continues from here.