Worked examples — Communication interfaces — UART, SPI, I2C (master - slave), CAN bus
The scenario matrix
Each row is a case class — a kind of situation the topic can throw. Each is covered by at least one worked example (WE#) below.
| Cell | Case class | Which protocol | Covered by |
|---|---|---|---|
| A | Normal timing — send a byte, find duration | UART | WE1 |
| B | Degenerate input — clock mismatch at the limit of tolerance | UART | WE2 |
| C | Full-duplex swap — read while writing (ring of shift registers) | SPI | WE3 |
| D | Wire-count scaling — N slaves, many-slave limit | SPI | WE4 |
| E | Resistor sizing between two bounds (upper & lower) | I2C | WE5 |
| F | Zero / no-device case — NACK, nobody answers | I2C | WE6 |
| G | Wired-AND arbitration — two IDs collide, who wins | CAN | WE7 |
| H | Limiting value — longest bus at full speed | CAN | WE8 |
| I | Real-world word problem — sensor budget across a system | mixed | WE9 |
| J | Exam-style twist — the "obvious" answer is wrong | I2C/SPI | WE10 |
WE1 — UART: how long to send one byte? (Cell A)
- Count the bits. 8N1 frame = 1 start + 8 data + 1 stop = 10 bits total, of which 8 are payload. Why this step? The line only carries voltage-vs-time; start and stop bits are real time on the wire, so they count toward duration.
- Find one bit's duration. Baud = bits/second, so . Why this step? Everything in UART is measured in bit-times; you need the ruler before you measure.
- Total frame time. . Why this step? Bits are sent back-to-back, so just multiply count by the ruler.
- Wasted fraction. Framing bits = 2 of 10 = . Why this step? Effective throughput is always less than the baud number — this is the reason.
Verify: bit-slots ✓. The value
'K'doesn't matter for timing (a0x00byte takes the same time) — timing depends only on bit count, not bit values. Units: ✓.
See Baud rate & clock generation (timers/PLL) for how a timer/PLL actually produces 19200 edges per second.
WE2 — UART: clock mismatch at the tolerance edge (Cell B — degenerate/limit)

- Locate the worst sample. The receiver samples mid-bit, so the STOP bit (the 10th bit) is sampled after 9.5 bit-times from the start edge. In the figure, the amber square drifts further from its white circle on every bit — the last one is the worst. Why this step? Error accumulates; the last sample has the most drift, so it is the one that fails first.
- Accumulate the drift. With drift fraction , timing error at that sample is bit-times. Why this step? Each bit adds of slip; after bits it's .
- Compare to the margin. The sample point may slip up to half a bit (0.5) before landing in the wrong window. Since , the sample still lands inside the correct bit → byte read correctly (this is the shaded margin in the figure). Why this step? Mid-bit sampling gives a bit safety window; that is the whole point of sampling at the middle.
- Find the breaking point. Solve . Why this step? This is the limiting value — the degenerate case where 4% would have become fatal.
Verify: Margin left = bit-times ✓. At exactly the drift equals (on the edge) ✓. Sanity: bigger (e.g. 2 stop bits, more data bits) → smaller tolerance, which matches intuition that longer frames drift more.
WE3 — SPI: full-duplex byte swap (Cell C)

- See the ring. MOSI feeds master→slave, MISO feeds slave→master. The two 8-bit Shift registers form one 16-bit loop (top of the figure). Why this step? SPI has no memory-copy; it only rotates a physical ring of flip-flops.
- Each clock = one shift. On each of 8 rising edges (Mode 0), the master pushes its current MSB out MOSI and latches the slave's MSB in from MISO, simultaneously. Why this step? This simultaneity is exactly what "full-duplex" means — one edge moves data both ways. MSB-first means the leftmost bit leads.
- After 8 edges. All 8 of the master's original bits have entered the slave, and vice-versa. Master now holds
0x3C, slave holds0xA5(bottom of the figure). Why this step? 8 shifts = one full byte swap; the registers have exchanged contents.
Verify: , . XOR-swap identity: swapping is its own inverse, so 8 more clocks would restore
0xA5/0x3C✓. No bits created or destroyed: before = after (invariant of a pure permutation) ✓.
WE4 — SPI: how many wires for N slaves? (Cell D — scaling / limit)
- Fixed bus wires. MOSI, MISO, SCLK are shared by all slaves → 3 pins, regardless of N. Why this step? These are broadcast lines; only the selected slave listens/drives MISO.
- Per-slave selects. SPI has no addressing, so each slave needs its own SS line → pins.
Why this step? The only way the master picks a device is by pulling that device's own SS low (see the parent's
[!mistake]). - Total. pins. Why this step? Wires = shared bus + one select each.
- I2C comparison. I2C uses SDA + SCL = 2 wires for all 6 devices, because it addresses by a 7-bit number, not a pin. Why this step? This is the fundamental SPI-vs-I2C trade: SPI spends pins to gain speed; I2C spends protocol overhead to save pins.
Verify: SPI ; I2C (constant in N). Limiting behaviour: as , SPI pins but I2C stays 2 ✓ — exactly why I2C wins on device count. See Serial vs Parallel communication for why even 9 serial pins beats a parallel bus.
WE5 — I2C: pull-up resistor between two bounds (Cell E)
- Upper bound (speed). The line rises as an RC toward the HIGH threshold; rise time . Solve for : Why this step? Too big a resistor → sluggish edge → the line never reaches HIGH before the next clock → bus fails at 400 kHz.
- Lower bound (sink current). When a device pulls LOW, current must not exceed what it can sink: Why this step? Too small a resistor → too much current → the open-drain transistor can't hold the line below → a "0" reads as a weak "1".
- The window. . A common choice like sits comfortably inside. Why this step? You live between the two bounds — that is the whole design rule.
Verify: At : rise ✓; sink current ✓. Both bounds satisfied. This wired-AND behaviour is Wired-AND logic in action.
WE6 — I2C: nobody answers (the NACK / zero-device case) (Cell F — degenerate)
- Build the address byte. 7-bit address
0x22in the top 7 bits, R/W = 0 (write) in bit0: Why this step? The first byte is always[7-bit addr][R/W]; the shift moves the address above the R/W bit. - The ACK slot. After 8 clocks, the master releases SDA (open-drain → floats HIGH via pull-up) and gives a 9th clock. A present slave would pull SDA LOW = ACK. Why this step? HIGH is the "released/nobody" state; only a real device can create the LOW that means "I'm here."
- No device → SDA stays HIGH during the 9th clock = NACK. Master reads HIGH. Why this step? With no one to pull it down, the pull-up wins → the master literally sees the absence of a device.
- Master aborts. It issues STOP and reports "no ACK / device not found." Why this step? NACK is the protocol's built-in "are you there?" — no timeout guessing needed.
Verify: ✓. If the same device were read, byte ✓. The NACK on an empty address is why bus scanners work: they probe every address 0x00–0x7F and record which ones ACK.
WE7 — CAN: two nodes collide, who wins? (Cell G — wired-AND arbitration)

- Write both IDs in binary (MSB first), aligned to 11 bits. Padding to 11 bits and grouping as 3-4-4 for readability: Label the columns b10 (leftmost) down to b0 (rightmost), matching the figure. Why this step? Arbitration compares bits one column at a time from the MSB (b10), so both IDs must be padded to the same 11-bit width and lined up in the same columns before we can compare them fairly.
- Recall the rule. Bus is wired-AND: dominant 0 beats recessive 1 (defined in the callout above). Each node transmits and listens; if it sends 1 but reads 0, it lost. Why this step? This is exactly the parent's non-destructive arbitration — no bit is corrupted, the loser just steps aside.
- Compare column by column (the aligned rows in the figure). Reading from b10:
- b10: P=0, Q=0 → same.
- b9: P=1, Q=1 → same.
- b8: P=0, Q=0 → same.
- b7: P=1, Q=1 → same.
- b6: P=1, Q=0 → differ. Bus reads 0 (dominant). P sent 1 but reads 0 → P loses; Q wins at bit b6. Why this step? The first differing column decides everything; the node with 0 there (lower ID) keeps going. Note the differing bit is b6, the 5th column from the left.
- Confirm with arithmetic. , so the lower ID (Q) wins. ✓ Why this step? "Lowest ID wins" and "wired-AND on MSB-first bits" are the same statement — this cross-check proves it.
Verify: , , and so Q wins ✓. First differing bit: , highest set bit is bit 6 (b6), where P has a 1 and Q has a 0 ✓. Q's message continues uncorrupted — that is why CAN never retransmits the winner. See Bit stuffing & CRC in CAN frames and Differential signaling & noise immunity.
WE8 — CAN: longest bus at full speed (Cell H — limiting value)
- The timing rule. For arbitration a dominant bit from the far node must reach the near node and the loss-detect happen within one bit time — so the round-trip must fit in one bit: Why this step? If the far node's bit hasn't arrived before this bit ends, the loser can't detect it lost — arbitration breaks (this is why WE7's "listen while you talk" needs the whole bus to settle within one bit).
- Bit time at 1 Mbps. . Why this step? The bit rate fixes how long the "listening window" is; we need this number before we can convert the timing rule into a distance.
- Solve for L. Rearrange the timing rule for the maximum length: Why this step? This isolates — the limiting length. So the maximum ideal bus is 100 m; real buses use ~40 m at 1 Mbps once transceiver and oscillator delays eat into the budget.
Verify: ✓. Check the inverse trade-off: drop to 500 kbps () → ✓ — halving speed doubles reach, matching the relationship.
WE9 — Real-world word problem: mixed-protocol system budget (Cell I)
- I2C bit count. START + 3 bytes, each byte = 8 bits + 1 ACK = 9 bits, + STOP. Approx bits. Why this step? I2C spends a whole clock on each ACK — overhead is real, so count it.
- I2C time. At 400 kHz, . Total . Why this step? Same "count × bit-time" ruler as UART.
- SPI bit count & time. 256 bytes × 8 bits = 2048 bits, no start/stop/ACK overhead. At 8 MHz, . Total . Why this step? SPI's zero-overhead + high clock is exactly why it's chosen for bulk transfer.
- Compare. SPI dump () is the bottleneck, ~3.5× the I2C read (). Why this step? Design lesson: pick I2C for slow low-pin sensors, SPI for high-volume data — the numbers justify the parent's "killer feature" column.
Verify: I2C ✓; SPI ✓; ratio ✓. Uses Interrupts vs Polling thinking: during that SPI burst you'd typically DMA rather than poll.
WE10 — Exam-style twist: the "obvious" answer is a trap (Cell J)
- SPI select count. SPI needs one SS per slave = 3 selects, plus shared MOSI/MISO/SCLK. There is no such thing as an SPI address — the "3" for SPI is 3 pins, not addresses. Why this step? The trap conflates SPI's pin-based selection with I2C's address-based selection; they are different resources.
- I2C address count. 3 I2C slaves = 0 extra select pins (all share SDA/SCL), just 3 distinct 7-bit addresses on the same 2 wires. So "6 resources" is meaningless — SPI costs pins, I2C costs address values. Why this step? Adding pins and addresses is apples + oranges; the exam's sum is a category error.
- The address-byte trap. I2C address
0x50on the wire is not0x50. It occupies the top 7 bits with the R/W bit appended: Why this step? The classic off-by-one-bit error — the datasheet's "7-bit address" must be shifted left before the R/W bit is OR'd in. - Correct read byte too. . Why this step? Read vs write differ only in bit0, which sits below the shifted address.
Verify: ✓; ✓. SPI selects (pins), I2C selects (pins) but 3 addresses — so the " resources" claim is false on both counts ✓.
Recall Self-test — cover the answers
UART 8N1 frame bit count ::: 10 bits (1 start + 8 data + 1 stop) Worst-case UART clock tolerance for 8N1 ::: about 5.26% () Why SPI needs one SS per slave ::: SPI has no addressing; the master selects only by pulling that slave's own SS low I2C address 0x68 as a write byte ::: What "NACK" tells the master ::: no device pulled SDA low → nobody at that address CAN arbitration winner rule ::: lowest numeric ID (most leading dominant 0s) wins, non-destructively Max classic-CAN length at 1 Mbps (ideal) ::: ~100 m from