I²C · Module 1
I²C vs SPI vs UART — Choosing a Board-Level Bus
Three interfaces that solve three different problems. Work the dimensions that actually decide — where identity lives, whether timing is sent or agreed, how wire count grows with device count, and whether the system is waiting — then apply them to four concrete board decisions where the right answer is not always I²C.
A comparison table is the least useful part of a comparison. It is a summary of reasoning that has already happened, and read on its own it produces engineers who can recite that I²C uses fewer wires without being able to say when that matters. This chapter therefore does the reasoning first and the table last, and the table at the end is deliberately short.
The question is not which of these is best. All three are current, all three ship in volume, and a single board frequently carries all three at once. The question an engineer is actually asked is narrower and answerable: given this traffic, between these parts, under these constraints, which interface is the right price? Chapters 1.1 to 1.3 built everything needed to answer it.
1. The Dimensions That Actually Decide
Four questions separate these three interfaces. Everything else — including most of what a feature table lists — is downstream of them.
How is a participant identified? Chapter 1.2 established that sharing a medium destroys the implicit addressing a dedicated wire provides, so identity must be reintroduced somehow. There are two places to put it: in the transmitted information, or in additional conductors. That single choice is the largest structural difference in this chapter, and almost every wire-count argument is a consequence of it.
Is the timing sent, or agreed in advance? Also from Chapter 1.2: a receiver must know when to sample. Either a conductor carries that timing, or both ends are configured with a matching rate and tolerate the drift between their independent references.
How does wire count grow with device count? Not how many wires the interface uses for one device — how the total changes when the fifth device is added. This is the Chapter 1.1 question, and it is the one that decides whether a design scales or has to be re-architected.
Is the system waiting? The Chapter 1.3 control-plane / data-plane distinction. Traffic nobody is blocked on can afford to share a medium and take turns. Traffic the system is stalled on cannot.
Read the columns downward. Each interface's growth behaviour in the bottom row is a consequence of its identity mechanism in the middle row, not an independent property — which is why "SPI uses more wires" is a shallow way to state the difference and "SPI pays for identity in conductors rather than in transmitted bits" is a useful one.
2. What Each Interface Is Structurally
Now describe the three in the terms the dimensions established, staying carefully on the right side of what can be claimed.
I²C is a shared bus. Two conductors — one carrying serialised information, one carrying the timing — run past every device, and each device answers to an address transmitted at the start of a transfer. Because identity is in the information, attaching another device costs no conductors. Because the medium is shared, transfers take turns, and participants need rules for whose turn it is. It is specified precisely: NXP UM10204 defines the bus, and this curriculum cites it for every behavioural claim.
SPI is a synchronous link with explicit selection. A clock conductor and data conductors are shared among the attached devices, and the host asserts an additional select conductor to choose which one is being addressed. Identity therefore lives in a conductor rather than in transmitted bits: no address is sent, and the selected device simply knows it has been chosen. Separate conductors in each direction mean both directions can carry information simultaneously. The trade is visible immediately — a fifth device needs a fifth select conductor, so wire count grows with device count, and that growth lands on the host exactly as Chapter 1.1 described.
A caution that matters for any comparison: SPI has no single governing specification in the way I²C does. It originated as a Motorola interface and is followed by convention, and real devices differ in clock polarity and phase expectations, in word length, in whether a select must be de-asserted between transfers, and in what the idle state means. Statements about "what SPI does" are therefore statements about common practice, and the device's datasheet is the authority. This curriculum's Why SPI Exists develops the interface on its own terms.
UART is point-to-point and asynchronous. Two endpoints, one conductor per direction, and — the defining property — no clock conductor at all. Both ends are configured with a matching rate; the receiver finds the start of a character from the data itself and times the remaining bits from its own local reference. There is no addressing, because there is no one else on the link to distinguish. There is no shared medium, so there is no turn-taking.
The same caution applies with more force. Asynchronous serial framing has no governing specification either; the standards engineers associate with it — TIA/EIA-232, TIA/EIA-485 — govern the electrical layer beneath the framing, which is a different layer, and conflating them is a common error. The repository's UART vs RS-232 and RS-485 exists to correct exactly that. Because the two ends run independent references, rate accuracy is a real design constraint rather than a formality: see clock drift and accumulated error.
3. Scenario A — Configure a Regulator and Read a Temperature Sensor
A board has a PMIC whose rails must be programmed and sequenced at start-up, and a temperature sensor read every few seconds thereafter.
Apply the four dimensions. Waiting? No — this is the control-plane profile from Chapter 1.3 in its purest form. Device count? Two now, and boards of this kind reliably acquire more supporting devices as a design matures. Identity? Two devices must be distinguished, so identity has to come from somewhere. Timing? Two unrelated parts with unrelated internal references, from possibly different vendors, must interoperate without anyone configuring a matching rate.
The shared addressed bus answers all four well, and the last two are the reasons worth noticing. Paying for identity in transmitted bits means the third and fourth devices cost no conductors — and on a board that will grow, a wire count that stays flat is worth more than any per-transfer efficiency. Sending the timing means the sensor and the regulator need not agree on anything in advance.
I²C is the right price here, and this is the scenario the bus was designed for.
4. Scenario B — Stream Samples From a High-Rate Converter
The same board has an analogue-to-digital converter producing a continuous sample stream that the system processes.
Waiting? Yes — emphatically. The samples are the work. That single answer settles the question before any other dimension is consulted: traffic the system is stalled on cannot share a medium with a regulator's status poll and wait for its turn. Sharing was affordable in Chapter 1.3 precisely because nothing was blocked, and here something is.
A dedicated interface is correct, and a synchronous one with separate conductors per direction fits a continuous stream well — the host clocks data out at a rate it controls, with no per-transfer addressing overhead because the device is selected by a conductor rather than named in the stream.
But notice the more interesting half of the answer, because it is where a real schematic differs from a naive one. The converter is very likely on both interfaces. Its samples leave on the dedicated link; its mode, gain and calibration are programmed over the shared control bus, because that exchange is small, happens at start-up and blocks nothing. A part is not assigned to a plane — its traffic is.
5. Scenario C — A Debug Console From an FPGA to a Host Computer
An FPGA design needs to emit status text during development, read by an engineer on a host computer through a USB-to-serial adapter.
Device count? Exactly two, permanently. Identity? Not needed — there is nobody else to distinguish, so an addressing mechanism would be machinery with no job. Waiting? No. Timing? Here the dimension inverts. Sending a clock would require the host adapter to accept one, and the near-universal convention at this boundary is asynchronous serial at an agreed rate. The ecosystem, not the electrical argument, decides.
UART is the right choice, and the reason is worth stating plainly because it is not a technical superiority: point-to-point asynchronous serial is what host-side tooling, adapters and terminal software expect. Choosing something structurally more capable here would mean building support for it at the far end for no benefit.
This scenario also illustrates why "fewer wires is better" fails as a rule. UART's two conductors carry no clock and no address, which is minimal — and that minimalism is exactly why it does not extend to a third participant. It is not a cheaper version of the other two; it is a different topology with a different job.
6. Scenario D — External Flash Holding a Configuration Image
An FPGA loads its configuration image from an external non-volatile memory at power-up.
Waiting? Yes — the device cannot start until the image has been read. How much? A configuration image is large compared with everything in Chapter 1.3: this is a block transfer, not a register access. Identity? One memory, selected by a conductor, no addressing needed. Timing? Both parts are on the same board and the host supplies the clock.
A synchronous dedicated link is correct, and the reason is throughput on a blocking transfer rather than any deficiency in addressing. Putting a configuration image on a shared control bus would make start-up time a function of how fast a bus designed for register writes can move a block, while occupying the medium that other devices also need.
Contrast this deliberately with the small identity EEPROM in Chapter 1.3. Both are non-volatile memories; they sit on different interfaces because one holds a few bytes read once and the other holds a block the system is blocked on. The category of the part predicted nothing. The profile of the traffic predicted everything — which, four scenarios in, is the actual lesson of this chapter.
7. Scenario E — Six Configuration Devices and Not Enough Pins
An FPGA design must configure six low-rate devices at boot. The package is already pin-constrained by a memory interface and a high-speed link, and the six devices are exactly the profile from Chapter 1.3: regulators, a clock generator, sensors, an identity memory.
This scenario is included because it is the one where the dimensions actively disagree, and the disagreement is the lesson.
Waiting? No. All six are configuration and status. Device count? Six, which is where growth behaviour stops being theoretical. Pins? The binding constraint, stated up front.
Dedicated selected links would need shared clock and data plus six select conductors, and every one of them lands on a package that has already run out. That is Chapter 1.1's worked table with the pin budget already spent — the design does not merely prefer fewer pins, it does not have the pins.
A shared addressed bus needs its bundle and nothing more, whether there are six devices or nine. The shared bus wins here, and it wins on the constraint that was declared binding rather than on any general claim of superiority.
Now the twist that makes this scenario worth its own section. Suppose one of those six occasionally transfers a large payload — a firmware image, a calibration table. The naive readings are both wrong: "it moves a lot of data, so give it its own interface" ignores that it does so occasionally and that there are no pins; "it is a configuration device, so put it on the shared bus" ignores that the payload may take long enough to matter.
The questions that actually decide it:
- How large is the payload, and how often? Once at manufacturing is a different problem from once per power-on, which is different again from once per hour.
- What is waiting while it transfers? If boot time is not a product requirement, a slow transfer is merely slow. If the device must be usable within a deadline, the transfer is on the critical path.
- What else needs the bus during it? A long transfer on a shared medium delays every other participant's turn. If a regulator's telemetry must be read on a schedule, a multi-second occupancy is a functional problem, not just a slow one.
- Can it be split? A large transfer broken into chunks, with other traffic interleaved between them, often removes the objection entirely — and that is a protocol-level property worth knowing the bus supports.
- Is there a pin left at all? If not, the question is not which interface is better but which compromise is survivable.
Only after answering those is there a decision. The honest outcome is frequently "put all six on the shared bus and accept a slow one-off transfer", and just as frequently "find pins for the one outlier" — and an engineer who can say which, and why, has understood this chapter. One who reaches for a comparison table has not.
8. How the Choice Changes the Interface Shape
The three topologies produce visibly different module boundaries, and seeing them side by side makes §1's identity argument concrete in a way prose cannot.
// SHARED ADDRESSED BUS — two bidirectional conductors, any number of devices.
// Identity travels in the data, so the port list does not grow with device count.
module host_i2c (
input logic clk,
inout logic sda, // information — driven by whichever device is speaking
inout logic scl // timing — shared, and not simply an output
);
endmodule
// SELECTED SYNCHRONOUS LINK — identity lives in a conductor, so it grows.
// Separate directions mean both can carry information at once.
module host_spi #(
parameter int N_DEVICES = 4
)(
input logic clk,
output logic sclk,
output logic mosi, // host to device
input logic miso, // device to host
output logic [N_DEVICES-1:0] cs_n // ONE MORE per device added
);
endmodule
// POINT-TO-POINT ASYNCHRONOUS LINK — two endpoints, no timing conductor,
// no identity because there is nobody else to distinguish.
module host_uart (
input logic clk,
output logic tx,
input logic rx
);
endmoduleThree observations, each a restatement of something already argued:
cs_n is a vector and sda is not. That single difference is the whole wire-count argument. The parameter N_DEVICES appears in one port list and is absent from the others, because only one of the three pays for identity in conductors.
The shared bus uses inout; the others use output and input. A dedicated conductor has exactly one driver, so its direction is a property of the design. A shared conductor does not — and that is not a keyword preference but a hardware problem with a specific solution, which is Module 2's entire subject.
host_uart has no clock conductor and no parameter. It cannot address a second participant and it cannot be extended to one. Minimal and non-extensible are the same property seen twice.
These are interface sketches with no bodies, and no executable RTL or testbenches appear in this chapter — a deliberate judgement. This chapter compares topologies, and a topology is expressed by a port list and a wiring diagram, not by behaviour. Implementing three protocol controllers here would produce hundreds of lines that teach protocol mechanics none of this chapter owns, and would bury the comparison. Executable, testbench-backed RTL belongs where a chapter owns a genuine hardware abstraction: the serialiser in Chapter 1.2, the register block in Chapter 1.3, and the full controller and target designs in Modules 17 and 18.
9. Verification Connection — Topology Decides the Monitor's Job
Interface choice changes the verification environment more than it changes the RTL, and the reason is worth understanding before any of it is built.
Ask what a monitor must do to report "a transfer happened" on each of the three.
On the selected synchronous link, the select conductor tells the monitor almost everything. Identity is a pin: when a device's select is asserted, that device is being addressed, and the monitor knows it without decoding anything. Transfer boundaries are similarly explicit — selection begins the transfer and de-selection ends it. A monitor here is close to a decoder.
On the shared addressed bus, identity is information, so the monitor has to reconstruct it. It must recognise where a transfer begins, accumulate the early bits, decode which device was named and in which direction, and only then attribute the following bytes to anything. Nothing on any pin tells it directly. This is why reconstruction is the hardest component in an I²C environment, and why Chapter 1.2 drew the pin-level versus transaction-level boundary so sharply.
On the point-to-point asynchronous link, there is no identity to recover and no shared clock to sample against — but the monitor must recover timing, finding the start of each character and sampling at the right instants from its own reference. The difficulty has moved from decoding to synchronisation.
The consequences are concrete:
| Concern | Selected link | Shared addressed bus | Async point-to-point |
|---|---|---|---|
| Who was addressed | read the select pin | decode from the data | only one possible party |
| Transfer boundaries | selection and de-selection | decoded framing | recovered from the data |
| Hardest monitor problem | word alignment | reconstruction and attribution | timing recovery |
| Other participants to model | the selected device | every device on the bus | the one far end |
| Failure modes unique to it | select timing conventions | one participant disturbing others | rate mismatch and drift |
The last row is the one that most changes the size of the job. On a shared bus, correctness claims involve the interaction of several participants — an environment must model well-behaved devices and misbehaving ones, because a controller exercised only against cooperative peripherals has not been verified against what boards actually do. The selected link has no equivalent class of problem, and that is a genuine argument in its favour that has nothing to do with speed.
Sequences change too. A sequence for a selected link can reasonably talk about the selected device. A sequence for a shared bus should express register intent — as Chapter 1.3 argued — and let a driver turn that into addressed traffic, because the intent is what survives when the bus underneath it changes. Modules 19 to 21 build all of this for I²C.
10. The Comparison, After the Reasoning
Only now is a table useful, and only as a reminder of conclusions already argued.
| I²C | SPI | UART | |
|---|---|---|---|
| Topology | shared bus, many devices | selected link, many devices | point-to-point, two endpoints |
| Identity carried by | transmitted address | a select conductor per device | not needed |
| Timing | sent on a dedicated conductor | sent on a dedicated conductor | agreed in advance, not sent |
| Wire count as devices grow | flat | grows by one per device | does not extend |
| Direction | one shared information conductor | separate conductors per direction | separate conductors per direction |
| Specified by | NXP UM10204 | convention; the datasheet is the authority | convention; framing has no governing spec |
| Natural fit | control plane, many small devices | blocking or continuous transfers | a permanent two-party link |
Two cells deserve emphasis rather than a glance. The specification row is a genuine engineering difference, not a footnote: designing against I²C means designing against a document, while designing against SPI or asynchronous serial means designing against a particular device's datasheet and being careful about what you assume is universal. And the wire count row is the only one whose behaviour changes with the size of the design, which is why it so often decides an architecture that any single-device comparison would call a tie.
The table has no throughput row on purpose. Each interface spans a wide range of signalling rates that depend on the devices, the board and the configuration chosen, and a single number would be false precision of exactly the kind this curriculum avoids. The useful question was never "how fast is it" but "is the system waiting" — and that is answered by the traffic, not the interface.
11. Where the Honest Answer Is "It Depends"
Three situations resist the clean reasoning above, and an engineer should recognise them rather than force a verdict.
A device that exists in only one form. Sometimes the part that meets a requirement offers one interface and the decision is made for you. This is a real and legitimate constraint, and the reasoning above still earns its keep — it tells you what the choice will cost elsewhere in the design.
Traffic near the boundary. A sensor read often enough to matter, or a memory just large enough to notice, can sit genuinely between the planes. The productive move is to estimate rather than argue: how much does the transfer actually move, how often, and what is blocked while it happens. If the answer is close, the risk of being wrong is usually smaller on the dedicated side.
A board already carrying one of them. If a shared control bus exists and a new device fits its profile, attaching it costs almost nothing — that flat wire count is the whole point. A new interface for one device is a larger decision than its wire count suggests, because it also adds a controller block, pins, routing and something else to bring up and debug.
12. Debugging — When "It Works" Is Still the Wrong Architecture
Most of this curriculum's debugging teaches you to find a fault. This chapter teaches a harder diagnosis: a system in which nothing is faulty and the design still does not meet its requirement.
The signature is specific. Every transfer completes. No device refuses, no line is stuck, no timing parameter is violated, the testbenches pass and a capture looks textbook. And the product misses a deadline — boot takes too long, a control loop samples too slowly, telemetry arrives late under load.
That is not a bug in anything. It is an interface-selection failure, and it presents late because it only appears once the bus carries its full traffic. Three symptoms distinguish it from an RTL defect:
It scales with load rather than occurring randomly. An RTL defect fires on a particular sequence. An architectural shortfall gets steadily worse as devices and traffic are added, because the resource is simply shared more ways.
Nothing is out of specification. The reason it is so often mis-triaged is that every layer reports success. There is no violation to find, which sends engineers looking harder at a layer that is behaving correctly.
Making the implementation better barely helps. Optimising the controller, raising the bus speed a notch, or tightening the driver buys a small fraction — because the shortfall is in how much work was put on one shared path, not in how efficiently that path is used. A fix that yields ten percent when a design needs three times more is evidence of an architecture problem, not encouragement.
The remedy is the reasoning in this chapter applied retrospectively: identify which traffic is actually waiting, and move that traffic off the shared path. Scenario B and Scenario E are both worked examples of the decision that, made late, produces exactly this failure.
13. Common Misconceptions
14. Reason It Through
Work this through before reading the answers.
A design review presents a board with six low-rate configuration devices on a shared control bus. A reviewer proposes moving them all to dedicated synchronous links "because it is faster and simpler to verify."
Is "faster" a relevant argument here? Not on its own. Nothing is waiting on these transfers, so a shorter transfer time converts into idle time rather than into system performance. Speed is only an argument when something is blocked, and the reviewer has not shown that anything is.
What would the change actually cost? Six select conductors plus shared clock and data, all landing on the host — the growth behaviour from §1 — spending pins and routing in the escape region, which Chapter 1.1 identified as the budgets that fail first and most expensively. It also loses the property that a seventh device is an attachment rather than an interface.
Is "simpler to verify" defensible? Partly, and it deserves a fair hearing rather than dismissal. A selected link genuinely removes shared-medium concerns: no addressing to get wrong, no turn-taking, no participant able to disturb transfers that do not involve it. That is a real reduction in verification scope. But it multiplies the number of interfaces to verify and integrate by six, and the datasheet-level variation in SPI conventions means six devices may not agree on clock phase or select behaviour. Simpler per link, more links, and less uniformity across them.
What is the strongest version of the reviewer's position? That one of the six does not belong on the shared bus — a device with a blocking or continuous transfer hiding among five genuinely sparse ones. That is worth checking properly, and it is the argument the reviewer should have made. The answer is then a mixed design rather than a wholesale move, which is what Scenario B produced and what real boards look like.
15. Understanding Check
16. Summary
Three interfaces, three different problems, and four dimensions that decide between them: where identity lives, whether timing is sent or agreed, how wire count grows with device count, and whether the system is waiting.
I²C puts identity in the transmitted information, sends the timing, and keeps wire count flat as devices are added — bought with turn-taking rules and paid for in a currency that sparse, non-blocking control traffic has in abundance. SPI puts identity in a conductor, which removes addressing and shared-medium concerns at the cost of a wire per device, and suits blocking or continuous transfers. UART sends no timing and needs no identity because it connects exactly two endpoints, which makes it minimal and non-extensible in the same breath.
The scenarios converged on one idea worth keeping: the category of the part predicts nothing; the profile of the traffic predicts almost everything. Two memories sit on different interfaces because one holds a few bytes read at boot and the other holds a block the system is blocked on. A converter sits on both because its samples and its configuration are different traffic.
And the confidence you can place in a comparison differs by interface. I²C is specified in a document; SPI and asynchronous serial framing are conventions, where the datasheet in front of you is the authority. Knowing which kind of claim you are making is part of making it well.
17. What Comes Next
Module 1 is complete, and it answered four questions in sequence: why chips on a board need a bus at all, why reducing and sharing wires leads to a serial addressed bus, where that bus sits in a real system, and — here — when to choose it over the alternatives.
Every one of those answers rested on an assumption that was used freely and never justified: that several devices can be attached to the same conductor, drive it at different times, and disagree about its level without damaging one another. That is not safe by default. Two devices imposing different levels on one wire is an electrical conflict, not a confused message, and nothing in Module 1 explained how a shared bus avoids it.
Module 2 — Electrical Bus & Open-Drain Design pays that debt, and it is not a detour before the protocol: the electrical arrangement that makes sharing safe turns out to determine how devices signal, how disagreements resolve, and what happens when one participant stops cooperating. A surprising amount of the protocol in later modules is a consequence of that single decision. Browse the full path on the I²C tutorials index.
For these interfaces developed on their own terms rather than comparatively, see Why SPI Exists and UART vs RS-232 and RS-485. For the same control-plane-versus-data-plane judgement applied inside a chip, see Why APB Exists and Why AXI Exists.
Continue learning
Related tutorials
- Related topic
UART vs Other Interfaces: Choosing the Right Link
Serial interfaces differ first in where the receiver's timing comes from, then in what organises a shared medium — and capability is paid for in what the system must already provide. A question order for choosing between UART, SPI, I2C, CAN, USB and Ethernet.
- Related topic
Why Chips on a Board Need a Bus
A connection between two chips is not a wire. It is a pin on each package, a routed trace, the board area and layers that trace consumes, and an I/O cell driving it — and all of that is paid for again for every device added. This is the cost structure that makes dedicating an interface per peripheral stop scaling, and that forces a board to share one set of wires instead.
- Related topic
From Parallel Buses to Two Wires
Derive the bus rather than meet it. Trading wires for time gives serialisation; trading exclusivity for coordination gives a shared medium; losing the wire as an implicit address forces a logical one. Each step is a deliberate exchange, and what falls out is a two-wire addressed bus — which is what Philips specified as I²C.
- Related topic
SDA and SCL — The Two-Wire Contract
Name the two conductors, establish that both are bidirectional shared nodes rather than any device's output, and define what a free bus looks like. Then state precisely the question the rest of the module exists to answer: what happens when two devices attached to the same wire disagree about its level?
