Everything in digital electronics reduces to voltage on a wire. A wire connected between two chips carries a voltage relative to a shared ground (GND) reference. That voltage is interpreted as one of two states:
- High (1): voltage near the supply voltage (e.g., ~3.3V)
- Low (0): voltage near 0V (ground)
The exact thresholds are specified in the chip’s datasheet. For a 3.3V STM32, a voltage above ~2.0V is read as high, and below ~0.8V is read as low. Voltages between 0.8V and 2.0V are in the undefined zone — the chip might read either value unpredictably. A correctly functioning circuit never leaves a signal in this zone except during brief transitions.
Transistors as Switches
A FET (Field-Effect Transistor) is an electrically controlled switch — the fundamental building block of all digital logic. Every gate, flip-flop, and register inside an MCU is built from FETs. A FET has three terminals: a gate (the control input), a source, and a drain (the two ends of the switch). Applying a voltage to the gate closes the switch; removing it opens the switch.
Two flavors exist:
- N-FET (N-channel): closes when the gate voltage is high. Good at connecting things to ground (pulling a wire to 0V).
- P-FET (P-channel): closes when the gate voltage is low. Good at connecting things to the supply voltage (pulling a wire to 3.3V).
The letters N and P refer to the type of semiconductor doping
How a Single Pin Works
A GPIO pin on a chip can be in one of three states.
Output: Push-Pull
A push-pull output pin contains both a P-FET and an N-FET inside the chip, stacked between the supply voltage and ground:
3.3V (supply)
│
[P-FET] ← closes to connect pin to 3.3V (drive high)
│
├──── Pin ──── wire
│
[N-FET] ← closes to connect pin to 0V (drive low)
│
GND
To drive high: P-FET ON, N-FET OFF. To drive low: P-FET OFF, N-FET ON. The chip’s internal logic guarantees only one is on at a time, with a small dead time during transitions to prevent both conducting simultaneously (which would create a short circuit called shoot-through). This is handled by the silicon design; you never manage it as a firmware engineer.
Input: Just Reading
An input pin has both FETs off. The pin is not driving the wire in any direction — it’s passively sensing whatever voltage is present. Think of it as a voltmeter clipped to the wire. It cannot cause any electrical conflict because it’s not pushing or pulling anything.
High-Impedance (High-Z): Disconnected
A pin in high-impedance mode also has both FETs off, just like an input, but the distinction is conceptual: this is an output pin that has been temporarily disabled. The pin is electrically disconnected from the wire — as if the wire were cut at that point. This state matters when multiple devices share a wire.
Unidirectional Wires: One Driver, One or More Listeners
A wire is just copper — it has no inherent direction. Direction comes from which pins are configured as outputs and which as inputs. The simplest and safest arrangement is a unidirectional wire: exactly one pin is an output (driving the voltage), and one or more pins are inputs (reading the voltage).
Chip A Chip B
┌──────────┐ ┌──────────┐
│ Pin: OUT │──── wire ─────────────────▶│ Pin: IN │
│ (push- │ │ (just │
│ pull) │ │ reading) │
└──────────┘ └──────────┘
Drives the wire Reads the wire
to 0V or 3.3V Cannot cause conflict
This is inherently safe. The output pin controls the wire’s voltage. The input pin observes it passively. Even with multiple listeners, there’s no conflict — inputs don’t drive anything:
Chip A (output)
┌──────────┐
│ Pin: OUT │──── wire ──┬──────────────▶ Chip B (Pin: IN, reading)
│ (push- │ │
│ pull) │ ├──────────────▶ Chip C (Pin: IN, reading)
└──────────┘ │
└──────────────▶ Chip D (Pin: IN, reading)
One driver, three listeners. No conflict possible.
This Is How Most SPI Wires Work
SPI uses separate physical wires for each direction of data flow. Each wire has exactly one driver:
MCU Slave A Slave B
MOSI pin ─────────────────▶ MOSI pin MOSI pin
(OUTPUT, push-pull) (INPUT, reading) (INPUT, reading)
"MCU sends data to slaves"
SCK pin ──────────────────▶ SCK pin SCK pin
(OUTPUT, push-pull) (INPUT, reading) (INPUT, reading)
"MCU sends clock to slaves"
CS_A pin ─────────────────▶ CS pin
(OUTPUT, push-pull) (INPUT, reading)
"MCU selects/deselects A"
CS_B pin ──────────────────────────────────────────▶ CS pin
(OUTPUT, push-pull) (INPUT, reading)
"MCU selects/deselects B"
MOSI carries data from MCU to slaves. The MCU’s MOSI pin is the sole output. Every slave’s MOSI pin is an input. Only the MCU ever drives this wire.
SCK carries the clock from MCU to slaves. Same arrangement — one output, multiple inputs.
CS lines are each a dedicated wire from the MCU to one slave. One output, one input.
None of these wires can ever have a conflict, because each has exactly one driver.
When you initialize SPI on the MCU (via a HAL or Embassy), the SPI peripheral automatically configures MOSI and SCK as push-pull outputs and MISO as an input. You don’t control individual FETs — the peripheral’s hardware handles pin direction.
The One SPI Wire That Needs Special Handling: MISO
MISO (Master In, Slave Out) carries data from slave to MCU. The MCU’s MISO pin is an input (both FETs off, just reading). But each slave’s MISO pin is an output — slaves need to send data back (a flash chip returns stored bytes, a sensor returns a reading, a display reports its status).
With two slaves, both have output pins connected to the same wire:
MCU Slave A Slave B
MISO pin ◀────── wire ────── MISO pin MISO pin
(INPUT, │ (OUTPUT) (OUTPUT)
reading) │
└── same wire!
If both slave outputs were active simultaneously and one drove high (P-FET on, connecting to 3.3V) while the other drove low (N-FET on, connecting to GND), current would flow from Slave A’s 3.3V supply through the wire into Slave B’s ground — a short circuit between two chips. The MCU is not involved in this conflict; its MISO pin is an input, passively reading.
Important
The short circuit danger on MISO is specifically between two slaves, not between the MCU and a slave. The MCU’s MISO pin is an input — it cannot conflict with anything.
SPI prevents this with chip select (CS). Each slave chip has CS-controlled high-impedance behavior built into its silicon:
Inside a slave chip:
CS pin (voltage from MCU)
│
▼
┌──────────────┐
3.3V │ │
│ │ CS LOW? │
[P-FET] ←── controlled by ──┤ (selected?) │
│ │ │
├──── MISO pin ── to wire │ YES: push- │
│ │ pull active, │
[N-FET] ←── controlled by ──┤ drive data │
│ │ │
GND │ NO: both │
│ FETs OFF │
│ (high-Z) │
└──────────────┘
When CS is high (not selected): both FETs forced off. MISO pin is high-Z — electrically disconnected from the wire.
When CS is low (selected): FETs operate normally, driving MISO high or low to send data bits.
This is automatic — hardwired into the slave chip’s logic. The developer’s only job is to pull one CS line low at a time. With this discipline, the MISO wire always has exactly one active output:
MCU selects Slave A: CS_A = LOW, CS_B = HIGH
MCU Slave A (selected) Slave B (not selected)
MISO pin ◀────── wire ────── MISO pin MISO pin
(INPUT, (OUTPUT, push-pull (HIGH-Z, both FETs
reading) active, driving off, disconnected
data bits) from wire)
→ One driver (Slave A), one reader (MCU). Safe.
MCU selects Slave B: CS_A = HIGH, CS_B = LOW
MCU Slave A (not selected) Slave B (selected)
MISO pin ◀────── wire ────── MISO pin MISO pin
(INPUT, (HIGH-Z, (OUTPUT, push-pull
reading) disconnected) active, driving
data bits)
→ One driver (Slave B), one reader (MCU). Safe.
Concrete Example: MCU Reads Status from E-Paper Display
The MCU sends a “read status” command byte on MOSI, and the display responds with a status byte on MISO. These happen on separate wires, simultaneously:
MOSI wire (MCU → display, always unidirectional)
MCU ──────────────────────────────────────────▶ Display
MOSI pin MOSI pin
(OUTPUT) (INPUT)
Sends: 0x71 ("read status" command) Receives command
MISO wire (display → MCU, unidirectional while selected)
MCU ◀────────────────────────────────────────── Display
MISO pin MISO pin
(INPUT) (OUTPUT, CS is low)
Reads: 0x00 ("ready") Drives response bits
SCK wire (MCU → display, always unidirectional)
MCU ──────────────────────────────────────────▶ Display
SCK pin SCK pin
(OUTPUT) (INPUT)
Generates 8 clock pulses Uses edges to time
each bit on MISO
No wire ever has two active outputs. The MCU and the display are never both driving the same wire.
Summary: Why Unidirectional Wires Are Safe
The core principle: if every wire has at most one active output, no conflict is possible. Input pins are passive — they cannot create short circuits. SPI achieves this by using separate wires per direction (MOSI for MCU→slave, MISO for slave→MCU) and CS-controlled high-Z to ensure only one slave drives MISO at a time.
Bidirectional Wires: Multiple Drivers on One Wire
Some protocols — most notably I²C — use a single wire for data flowing in both directions. The master sends on it, then the slave responds on the same wire. Both the master and the slave must have output capability on this wire. At different points in a transaction, either one might be driving.
This is where the push-pull short circuit problem becomes unavoidable with the techniques described above. If the master’s push-pull output drives high (P-FET on, connecting to 3.3V) while the slave’s push-pull output drives low (N-FET on, connecting to GND), current flows from 3.3V to GND through both chips — a damaging short circuit:
DANGER: two push-pull outputs on the same bidirectional wire
Master (MCU) Slave (sensor)
3.3V 3.3V
│ │
[P-FET] ON ← driving high [P-FET] OFF
│ │
├──── SDA wire ──────────────────────┤
│ │
[N-FET] OFF [N-FET] ON ← driving low
│ │
GND GND
Current flows: Master 3.3V → wire → Slave GND
SHORT CIRCUIT — potential damage to both chips
CS lines cannot help here — there’s only one slave involved, and it must be able to drive the wire at certain points in the conversation.
The Solution: Open-Drain Outputs
I²C solves this by requiring every device on the bus — master and slaves — to use open-drain outputs instead of push-pull. An open-drain output has only the N-FET (pull-to-ground switch). There is no P-FET. The pin can actively pull the wire to 0V, but it cannot drive the wire to 3.3V.
Open-drain output (what every I²C pin uses):
3.3V
│
[no P-FET — nothing here]
├──── Pin ──── wire
│
[N-FET] ← ON: wire pulled to 0V. OFF: wire floats.
│
GND
This creates a problem: when the N-FET is off, nothing is driving the wire high. The wire floats at an undefined voltage — in the danger zone between 0.8V and 2.0V — and the chips read unpredictable values.
The Pull-Up Resistor: Providing the High State
A pull-up resistor solves the floating problem. It’s a physical component — a small ceramic or carbon cylinder soldered onto the circuit board — connecting the wire to 3.3V:
3.3V (supply)
│
[4.7kΩ] ← pull-up resistor (physical component
│ on the circuit board)
│
Master │ Slave
[N-FET]─── Pin ─────┼───── Pin ───[N-FET]
│ │ │
GND wire GND
Here’s why this works — and specifically why the N-FET “overpowers” the resistor. It comes down to resistance:
When no device is pulling low (both N-FETs off), the wire is connected to 3.3V through the 4.7kΩ resistor. Almost no current flows (there’s nowhere for it to go). The wire sits at 3.3V. The chips read high (1).
When a device pulls low (one N-FET on), the wire is now connected to both 3.3V (through the 4.7kΩ resistor) and GND (through the N-FET). The N-FET’s on-resistance is very low — typically under 1Ω. So you have a voltage divider: 4,700Ω to 3.3V vs ~1Ω to GND. Almost all the voltage drops across the resistor, and the wire sits at approximately:
That’s essentially 0V. The chips read low (0). The current flowing through the resistor is 3.3V / 4700Ω ≈ 0.7 mA — harmless. No damage, no short circuit.
Tip
Think of it like a tug-of-war: the N-FET (very strong, very low resistance) pulls toward ground, and the pull-up resistor (very weak, high resistance) pulls toward 3.3V. The N-FET always wins. But when the N-FET lets go, the resistor gently pulls the wire back up to 3.3V — slowly, because it’s weak. That slowness is why I²C is limited to ~1 MHz while SPI (using strong push-pull) runs at tens of MHz.
Why Open-Drain Cannot Cause Short Circuits
Consider every possible combination of two devices on an open-drain bus:
| Master | Slave | Wire state | What happens |
|---|---|---|---|
| N-FET off (released) | N-FET off (released) | Resistor pulls to 3.3V → high | No current through either chip |
| N-FET on (pulling low) | N-FET off (released) | N-FET wins → low | 0.7 mA through resistor. Safe. |
| N-FET off (released) | N-FET on (pulling low) | N-FET wins → low | 0.7 mA through resistor. Safe. |
| N-FET on (pulling low) | N-FET on (pulling low) | Both connect to GND → low | Both pulling same direction. No conflict. |
There is no combination that creates a short circuit. No device can drive high — only the passive resistor does that. The resistor limits current to a fraction of a milliamp by definition. Even if every device on the bus pulls low at the same time, they’re all pulling in the same direction (toward GND), through the same resistor — no opposing forces, no damage.
This is fundamentally different from push-pull, where one device can connect a wire to 3.3V while another connects it to GND, creating a low-resistance path (short circuit) with potentially amps of destructive current.
Important
Without the pull-up resistor, an open-drain bus floats at an undefined voltage whenever no device is pulling low. The chips would read random values. The resistor is not optional — it’s a required part of the circuit. When a datasheet says “requires external pull-ups,” it means you must place physical resistors on the board. Some development boards include them already; bare breakout modules often do not.
This Is Why I²C Is Slower Than SPI
The pull-up resistor is weak by design (high resistance keeps current low and prevents damage). But it also means that when a device releases the wire (N-FET turns off), the resistor must charge the wire’s parasitic capacitance (the tiny but real capacitance of the copper trace and the input pins of all connected chips). Charging through a high resistance is slow. The transition from low to high takes much longer than a push-pull output, which actively shoves the wire to 3.3V with a low-resistance P-FET.
This slow rise time is the fundamental reason I²C tops out at ~1 MHz while SPI runs at tens of MHz. It’s a direct consequence of the safety tradeoff: open-drain is safe for bidirectional shared wires, but the pull-up resistor that makes it safe is also what makes it slow.
Active-Low Signaling
Many control signals in digital electronics are active-low: the signal performs its function when the wire is at 0V (low), not when it’s at 3.3V (high). Pin names indicate this with a bar (C̅S̅), a trailing # (CS#), a leading n (nCS), or a leading / (/CS).
The SPI chip select (CS) signal is active-low: pulling it to 0V activates the slave device. This convention exists because of power-on behavior. At power-on, before any firmware runs, most digital pins default to a high state (either through internal pull-ups or because output drivers are inactive). An active-low convention means that at power-on, CS is naturally high (inactive), and no slave device is accidentally selected during the undefined period before the MCU initializes. If the convention were active-high, an uninitialized MCU might accidentally activate a slave, causing bus conflicts or unintended writes to a flash memory chip.
The same logic applies to reset pins (RST, RESET): they are almost always active-low. At power-on, the pin is high (device runs normally). The MCU can pull it low to force a hardware reset when needed.
Clock Edges and Data Sampling
The Problem: When Exactly to Read
When a sending device changes the voltage on a data wire to encode a new bit, the transition is not instantaneous — the voltage ramps between 0V and 3.3V over a few nanoseconds, passing through the undefined zone. If the receiving device reads the wire during that ramp, it might see a 0 or a 1 randomly.
Voltage
3.3V ─────────╲ ╱───────────
╲ ╱
╲ ╱
~2.0V ─ ─ ─ ─ ─╲─ ─ ─ ─ ─ ╱─ ─ ─ ─ ─ ─ ─ ← threshold: above = high
╲ ╱
╲ ╱
~0.8V ─ ─ ─ ─ ─ ─ ╲─ ─ ╱─ ─ ─ ─ ─ ─ ─ ─ ─ ← threshold: below = low
╲ ╱
0V ────────────────╲╱────────────────────
↑
DANGER: undefined zone
reading here is unreliable
A clock signal solves this. A separate wire carries a square wave, and both sides agree: “sample the data line on a specific edge of this clock.” The sender arranges for the data line to be stable (not transitioning) when that edge occurs. The receiver ignores the data wire at all other times.
Both SPI and I²C use a clock wire (SCK and SCL respectively). UART does not — it uses pre-agreed timing instead (see Serial Communication Protocols).
How to Read a Timing Diagram
Embedded datasheets and tutorials represent signal behavior using timing diagrams — a plot of voltage over time for each wire, stacked vertically so you can see what all wires are doing at the same moment. Time flows left to right. Each wire is either high (line at the top) or low (line at the bottom):
time ──────────────────────────────▶
Wire A ‾‾‾‾‾‾‾‾╲___________╱‾‾‾‾‾‾‾‾‾‾‾‾‾
high low high
Wire B _________╱‾‾‾‾‾‾‾‾‾‾╲______________
low high low
A vertical line drawn at any point in time tells you the state of every wire at that instant. In the diagram above, at the moment Wire A goes low, Wire B goes high — they transition at the same time.
The key to reading a timing diagram: pick a moment in time (a vertical slice) and read across all wires to see the full system state at that instant.
Polarity and Phase: Two Degrees of Freedom
The clock signal has a rising edge (low → high) and a falling edge (high → low). “Sample on a specific edge” still leaves two questions:
Clock polarity (CPOL) determines the idle state of the clock line — what voltage it sits at when no transfer is happening. CPOL=0 means idle low (0V); CPOL=1 means idle high (3.3V). This is a convention chosen by the slave chip’s designer; there is no electrical advantage to either.
Clock phase (CPHA) determines which edge triggers sampling. CPHA=0 means sample on the first edge away from idle; CPHA=1 means sample on the second edge (the return to idle).
Together they form four modes. The slave device’s datasheet specifies which mode it requires. The master’s SPI peripheral must be configured to match.
Worked Example: Sending 0xB1 in Mode 0
The byte 0xB1 in binary is 10110001. In SPI Mode 0 (CPOL=0, CPHA=0), the clock idles low, and the slave samples MOSI on the rising edge of SCK. The master changes MOSI on each falling edge (or before the first rising edge), so the data has time to stabilize before the slave reads it.
Rather than showing all 8 bits at once, here’s the transfer traced bit by bit. Each step shows a snapshot of the three relevant wires — CS, SCK, and MOSI — at a specific moment in time.
Before the transfer starts:
CS ‾‾‾‾‾‾ (high — slave not selected, ignoring everything)
SCK ______ (low — idle, CPOL=0)
MOSI ______ (irrelevant, no transfer in progress)
Step: MCU pulls CS low and sets MOSI to bit 7 (= 1):
CS ______ (low — slave is now selected, listening)
SCK ______ (still low — no clock edge yet)
MOSI ‾‾‾‾‾‾ (high — master is presenting bit 7 = 1)
MOSI is now stable at 3.3V (high). The slave sees CS is low, so it knows a transfer is starting. But it hasn’t sampled anything yet — it’s waiting for a rising edge on SCK.
Step: MCU raises SCK (rising edge → slave samples bit 7):
CS ______
SCK ‾‾‾‾‾‾ (high — this is the rising edge)
MOSI ‾‾‾‾‾‾ (still high — has been stable since before the edge)
↑
SLAVE READS MOSI HERE → sees 1 (high) → bit 7 = 1 ✓
This is the critical moment. MOSI has been sitting at 3.3V for the entire time since the master set it. The voltage is far from the undefined zone. The slave reads a clean, unambiguous 1.
Step: MCU lowers SCK (falling edge) and changes MOSI to bit 6 (= 0):
CS ______
SCK ______ (low again — falling edge)
MOSI ______ (low — master is now presenting bit 6 = 0)
The falling edge is the master’s cue to change the data. MOSI transitions from high to low. During this transition, MOSI passes through the undefined zone — but nobody is sampling right now. The slave only samples on rising edges. By the time the next rising edge arrives, MOSI will have settled at 0V.
Step: MCU raises SCK again (rising edge → slave samples bit 6):
CS ______
SCK ‾‾‾‾‾‾ (rising edge)
MOSI ______ (low — has been stable since the previous falling edge)
↑
SLAVE READS MOSI HERE → sees 0 (low) → bit 6 = 0 ✓
Again, MOSI has been stable for the entire half-clock period. Clean read.
This pattern repeats six more times — falling edge (master changes MOSI), rising edge (slave samples) — for bits 5 through 0. Here are the remaining bits of 0xB1 = 10110001:
| Clock cycle | Bit position | Bit value | MOSI voltage at rising edge | Slave reads |
|---|---|---|---|---|
| 1 | bit 7 (MSB) | 1 | 3.3V (high) | 1 ✓ |
| 2 | bit 6 | 0 | 0V (low) | 0 ✓ |
| 3 | bit 5 | 1 | 3.3V (high) | 1 ✓ |
| 4 | bit 4 | 1 | 3.3V (high) | 1 ✓ |
| 5 | bit 3 | 0 | 0V (low) | 0 ✓ |
| 6 | bit 2 | 0 | 0V (low) | 0 ✓ |
| 7 | bit 1 | 0 | 0V (low) | 0 ✓ |
| 8 | bit 0 (LSB) | 1 | 3.3V (high) | 1 ✓ |
After 8 clock cycles, the slave has received: 10110001 = 0xB1. Correct.
After the transfer, MCU raises CS:
CS ‾‾‾‾‾‾ (high — slave deselected, stops listening)
SCK ______ (low — back to idle)
MOSI ______ (irrelevant again)
The full transfer as a timing diagram — now that each step has been explained, this should be readable:
CS ‾‾‾╲________________________________________╱‾‾‾
SCK _____╱‾╲__╱‾╲__╱‾╲__╱‾╲__╱‾╲__╱‾╲__╱‾╲__╱‾╲____
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
s s s s s s s s s = slave samples
MOSI ____╱‾‾‾‾╲____╱‾‾‾‾‾‾‾‾╲_________╱····╲____╱‾‾‾
idle 1 0 1 1 0 0 0 1
b7 b6 b5 b4 b3 b2 b1 b0
Each ↑ on the SCK line is a rising edge. At every rising edge, MOSI is stable (it was changed on the previous falling edge). The slave reads one bit per rising edge, MSB first.
What Goes Wrong: Mismatched Mode
Now imagine the slave expects Mode 0 (sample on rising edge), but the master is misconfigured to Mode 1 (CPOL=0, CPHA=1 — data changes on rising edge, slave expected to sample on falling edge).
The master now changes MOSI at the rising edge — the exact moment the slave tries to read. Walking through bit 7:
Master raises SCK and simultaneously changes MOSI to bit 7:
CS ______
SCK ‾‾‾‾‾‾ (rising edge)
MOSI ~~~~~~ (TRANSITIONING — voltage is between 0V and 3.3V)
↑
SLAVE READS MOSI HERE → voltage in undefined zone!
Could read 0 or 1. Unpredictable.
The slave samples at the exact moment the data is changing. The voltage might be at 1.5V — right in the undefined zone. The slave’s input circuit might interpret this as 0 or 1 depending on noise, temperature, or how far the transition has progressed. The result: corrupted data that may even differ between consecutive transfers of the same byte.
This is one of the most common embedded debugging experiences: the wiring is physically correct, the code looks right, but every byte is garbled. The cause is a one-bit configuration error in the SPI mode register.
Warning
Mode mismatch is the most common cause of “SPI wiring is correct but I get random garbage.” The fix: look up the slave’s datasheet for its required CPOL and CPHA values, and configure the master’s SPI peripheral to match.
Signal Naming Conventions
Datasheets and board silkscreens use various names for the same signals. Knowing the aliases prevents confusion when wiring:
| Canonical | Aliases | Meaning |
|---|---|---|
| CS (chip select) | NSS, SS, CE, CSN, /CS, nCS | Active-low slave select (SPI) |
| MOSI (master out, slave in) | SDO, DIN, SI, COPI | Data: master → slave (SPI) |
| MISO (master in, slave out) | SDI, DOUT, SO, CIPO | Data: slave → master (SPI) |
| SCK (serial clock) | CLK, SCLK | Clock from master (SPI) |
| SDA (serial data) | — | Bidirectional data (I²C) |
| SCL (serial clock) | — | Clock from master (I²C) |
| TX (transmit) | TXD, TXO | Output (UART) |
| RX (receive) | RXD, RXI | Input (UART) |
Visual Resources
For diagrams and animations of these concepts:
- “push-pull vs open-drain output explained” — Texas Instruments application notes and SparkFun tutorials have clear schematics
- “MOSFET as a switch tutorial” — for understanding P-FET and N-FET as controlled switches without semiconductor physics
- “push pull shoot-through dead time” — for the both-FETs-on failure mode inside a single output stage
- “SPI bus topology diagram” — shows the shared-bus-with-CS-lines layout
- “I2C open drain pull-up resistor diagram” — shows the shared-bus open-drain layout
Tip
The YouTube channel Ben Eater has excellent visual explanations of digital signaling, bus wiring, and timing. His breadboard computer series covers many of these concepts with physical demonstrations. For a shorter treatment, EEVblog episodes on push-pull vs open-drain include oscilloscope captures of real signals.