Skip to content

Bus Architecture and Communication Interfaces

Bus Architecture and Communication Interfaces hero image
Modified:
Published:

Microcontrollers communicate with sensors, displays, memory chips, and other MCUs through serial buses. SPI, I2C, and UART are the three protocols you will use most often in embedded programming. This lesson takes you below the HAL function calls to the actual signal waveforms on the wires, so that when something goes wrong (and it will), you can look at a logic analyzer capture and diagnose the problem. #SPI #I2C #UART

Parallel vs Serial Communication

Parallel Buses

A parallel bus sends multiple bits simultaneously, one per wire. The internal buses inside your MCU (AHB, APB) are parallel: 32 data lines carry a full word in one clock cycle.

Advantages: Fast (high throughput per clock cycle).

Disadvantages: Many wires, crosstalk at high frequencies, difficult to route on PCBs, expensive connectors.

Serial Buses

A serial bus sends bits one at a time over fewer wires. SPI, I2C, and UART are all serial.

Advantages: Few wires (2 to 4), simple PCB routing, long-distance capable.

Disadvantages: Lower throughput per clock cycle (but high clock rates compensate).

Comparison

PropertyParallel (32-bit AHB)SPII2CUART
Data wires321 (+ 1 return)1 (bidirectional)1 TX + 1 RX
Clock wire1 (shared)11None (asynchronous)
Additional wiresAddress, controlCS (one per slave)NoneNone
Typical speed72 MHz (internal)1-50 MHz100-400 kHz (up to 3.4 MHz)9600-115200 baud (up to ~1 Mbaud)
TopologyPoint-to-pointMaster + multiple slavesMulti-master, multi-slavePoint-to-point

SPI: Serial Peripheral Interface



Signal Lines

SPI uses four signals (three for unidirectional communication):

SignalNameDirectionFunction
SCLKSerial ClockMaster to slaveClock signal generated by the master
MOSIMaster Out Slave InMaster to slaveData from master to slave
MISOMaster In Slave OutSlave to masterData from slave to master
CS/SSChip SelectMaster to slaveActive-low, selects which slave is active

How SPI Works

SPI is essentially two shift registers connected in a ring:

Master Slave
┌──────────┐ ┌──────────┐
│ Shift │──── MOSI ──────→│ Shift │
│ Register │ │ Register │
│ (8-bit) │←─── MISO ──────│ (8-bit) │
└──────────┘ └──────────┘
│ │
└──────── SCLK ──────────────→│
│ │
└──────── CS ────────────────→│

On each clock edge, one bit shifts out of the master’s shift register (on MOSI) and one bit shifts in from the slave (on MISO). After 8 clock cycles, the master and slave have exchanged one byte.

This is exactly the 74HC595 shift register from Lesson 4, but now the shift register has both an input and an output, forming a bidirectional data exchange.

Clock Polarity and Phase (CPOL/CPHA)

SPI has four modes defined by two parameters:

  • CPOL (Clock Polarity): the idle state of the clock (0 = low when idle, 1 = high when idle)
  • CPHA (Clock Phase): which clock edge captures data (0 = first edge, 1 = second edge)
ModeCPOLCPHAClock IdleData Captured OnData Changed On
000LowRising edgeFalling edge
101LowFalling edgeRising edge
210HighFalling edgeRising edge
311HighRising edgeFalling edge

Mode 0 is the most common. Many sensors and displays default to Mode 0.

SPI Timing Diagram (Mode 0)

CS: ─────┐ ┌─────
└──────────────────────────────────────────┘
SCLK: ─────┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌─
└──┘ └──┘ └──┘ └──┘ └──┘ └──┘ └──┘ └──┘
MOSI: ═══╡ D7 ╞╡ D6 ╞╡ D5 ╞╡ D4 ╞╡ D3 ╞╡ D2 ╞╡ D1 ╞╡ D0 ╞═══
MISO: ═══╡ D7 ╞╡ D6 ╞╡ D5 ╞╡ D4 ╞╡ D3 ╞╡ D2 ╞╡ D1 ╞╡ D0 ╞═══
  1. Master pulls CS low to select the slave.
  2. Master generates 8 clock pulses on SCLK.
  3. On each rising edge (Mode 0), both master and slave sample the data line.
  4. On each falling edge, both sides set up the next bit.
  5. After 8 clocks, one byte has been exchanged in each direction.
  6. Master pulls CS high to deselect the slave.

Multiple Slaves

Each slave needs its own CS line. The master pulls one CS low to communicate with that slave while keeping all others high (deselected).

Master
┌──────┐
SCLK ─┤ ├─── CS0 ──→ Slave 0
MOSI ─┤ ├─── CS1 ──→ Slave 1
MISO ─┤ ├─── CS2 ──→ Slave 2
└──────┘

This means SPI requires one additional MCU pin per slave device. With many slaves, you can use a decoder (Lesson 3) to generate CS signals from a smaller number of address pins.

I2C: Inter-Integrated Circuit



Signal Lines

I2C uses only two wires for any number of devices:

SignalNameFunction
SCLSerial ClockClock signal (driven by master)
SDASerial DataBidirectional data line (shared)

Both lines use open-drain drivers with external pull-up resistors (typically 4.7K ohm to VCC). Any device can pull the line low, but no device drives it high. The pull-up resistor pulls the line high when no device is pulling it low.

I2C Bus with Multiple Devices

I2C bus topology
VCC VCC
│ │
[4.7K] [4.7K]
│ │
──────┼────────────┼──────── SCL
──────┼────────────┼──────── SDA
│ │
┌─────┴──┐ ┌────┴───┐
│ Master │ │BME280 │
│ (MCU) │ │ 0x76 │
└────────┘ └────────┘
│ │
┌─────┴──┐ ┌────┴───┐
│SSD1306 │ │DS3231 │
│ 0x3C │ │ 0x68 │
└────────┘ └────────┘
All devices share two wires.
Pull-ups required on both lines.

I2C Addressing

Each slave device on the bus has a unique 7-bit address (some devices use 10-bit addressing). The master sends the address at the start of each transaction, and only the addressed slave responds.

Common I2C addresses:

DeviceTypical Address
BME280 sensor0x76 or 0x77
SSD1306 OLED0x3C or 0x3D
DS3231 RTC0x68
EEPROM (24C256)0x50-0x57

I2C Protocol: Write Transaction

A complete I2C write transaction to send one data byte:

SDA: ─┐ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬─┐ ┌──
└─┤A6 │A5 │A4 │A3 │A2 │A1 │A0 │R/W│ACK│D7 │D6 │D5 │D4 │D3 │D2 │D1 │D0 │ACK├─┘
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
START 7-bit address W Ack 8-bit data Ack STOP
(from master) (0) (slave) (from master) (slave)
  1. START condition: Master pulls SDA low while SCL is high. This signals all slaves to listen.
  2. Address byte: Master sends the 7-bit slave address followed by a R/W bit (0 = write, 1 = read). MSB first.
  3. ACK from slave: The addressed slave pulls SDA low during the 9th clock pulse to acknowledge. If no slave responds, SDA stays high (NACK), indicating an error.
  4. Data byte: Master sends 8 data bits, MSB first.
  5. ACK from slave: Slave acknowledges receipt of the data byte.
  6. STOP condition: Master releases SDA while SCL is high.

I2C Clock Stretching

A unique feature of I2C: the slave can hold SCL low to pause the transfer if it needs more time to process data. The master must wait for SCL to go high before continuing. This is called clock stretching and is why SCL must be open-drain (the slave needs to pull it low).

I2C Speed Modes

ModeMaximum Clock
Standard100 kHz
Fast400 kHz
Fast-Plus1 MHz
High-Speed3.4 MHz

Most embedded projects use Standard (100 kHz) or Fast (400 kHz) mode.

UART: Universal Asynchronous Receiver/Transmitter



Signal Lines

UART uses two data lines with no clock:

SignalDirectionFunction
TXTransmitter to receiverData output
RXReceiver to transmitterData input
GNDCommonShared ground reference

Since there is no clock, both sides must agree on the data rate (baud rate) beforehand. This is why UART is “asynchronous.”

UART TX/RX Connection

UART point-to-point wiring
Device A Device B
┌────────┐ ┌────────┐
│ TX ├───────────→│ RX │
│ │ │ │
│ RX │◄───────────┤ TX │
│ │ │ │
│ GND ├────────────┤ GND │
└────────┘ └────────┘
TX of one device connects to RX
of the other (crossover wiring).
Shared ground is required.

UART Frame Format

A standard UART frame consists of:

Idle ─────┐ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬─────── Idle
└───┤D0 │D1 │D2 │D3 │D4 │D5 │D6 │D7 │(P)├───────
Start 8 data bits (LSB first) Parity Stop
bit (opt.) bit(s)
ComponentBitsFunction
Start bit1Always 0 (low). Signals the beginning of a frame.
Data bits5-9 (usually 8)The actual data, sent LSB first.
Parity bit0-1Optional error detection (even or odd parity).
Stop bit(s)1-2Always 1 (high). Signals the end of the frame.

Baud Rate

The baud rate specifies how many bits per second are transmitted. Common rates:

Baud RateBit PeriodByte Period (8N1)
9600104.2 us1.04 ms
1152008.68 us86.8 us
9216001.09 us10.9 us

“8N1” means 8 data bits, No parity, 1 stop bit (the most common configuration). Each byte takes 10 bit periods (1 start + 8 data + 1 stop).

How the Receiver Synchronizes

Without a clock, the receiver uses the start bit to synchronize:

  1. The line is idle (high).
  2. The receiver detects a falling edge (start bit).
  3. The receiver waits half a bit period to sample the middle of the start bit, confirming it is still low.
  4. The receiver then samples at one-bit-period intervals to read each data bit.
  5. After the last data bit (and optional parity), the receiver checks for the stop bit (high).
  6. The receiver returns to waiting for the next start bit.

For this to work reliably, the transmitter and receiver baud rates must match within about 2-3%. If the rates drift too far apart, bit sampling falls at the wrong time and data corrupts.

UART vs RS-232 vs TTL

StandardVoltage LevelsConnector
TTL UART0V (low) and 3.3V or 5V (high)Pin headers
RS-232-12V (high) and +12V (low)DB-9 connector
RS-485Differential pairScrew terminals

MCU UART pins operate at TTL levels (3.3V or 5V). To connect to a PC serial port (RS-232), you need a level converter like the MAX232. Most modern connections use a USB-to-TTL serial adapter (like the FTDI FT232 or CH340) that handles the conversion.

Comparing SPI, I2C, and UART



FeatureSPII2CUART
Wires3 + 1 per slave2 (shared)2 (point-to-point)
ClockYes (master)Yes (master)No (asynchronous)
SpeedUp to 50 MHzUp to 3.4 MHzUp to ~1 Mbaud
DuplexFull duplexHalf duplexFull duplex
AddressingCS pin per slave7-bit addressNone (point-to-point)
AcknowledgmentNone (by default)ACK/NACK per byteNone (or parity bit)
Typical useFast peripherals (display, Flash, ADC)Sensors, RTCs, EEPROMsDebug console, GPS, Bluetooth modules
Pin count for 4 slaves7 (SCLK, MOSI, MISO, 4xCS)2 (SCL, SDA)8 (4 TX + 4 RX pairs)

When to Use Which

Choose SPI When

You need high speed (displays, external Flash, high-speed ADCs), you have pins to spare for CS lines, and you want full-duplex communication. SPI is the fastest standard serial bus.

Choose I2C When

You have many low-speed devices (sensors, RTCs, EEPROMs) and want to minimize pin usage. Two wires connect to all devices on the bus regardless of count.

Choose UART When

You need a simple point-to-point connection with no clock synchronization, such as a debug serial console, GPS module, or Bluetooth module. UART is also the simplest protocol to debug with a logic analyzer.

Practical: Observe Serial Signals



Using a Logic Analyzer

A logic analyzer captures digital signals and displays them as timing diagrams. Inexpensive USB logic analyzers (Saleae clones or compatible) cost a few dollars and support protocol decoding for SPI, I2C, and UART.

  1. Connect probes to the signal lines (SCLK, MOSI, MISO, CS for SPI; SCL, SDA for I2C; TX for UART). Always connect the analyzer’s GND to the circuit’s GND.

  2. Set the sample rate to at least 4 times the bus clock frequency. For SPI at 1 MHz, sample at 4 MHz or higher.

  3. Trigger on the CS falling edge (SPI), START condition (I2C), or TX falling edge (UART).

  4. Capture a transaction and examine the waveform.

  5. Enable protocol decoding in your analyzer software. It will overlay the decoded bytes on the waveform, showing addresses, data values, and ACK/NACK.

What to Look For

SPI problems:

  • Clock polarity mismatch (CPOL): data looks shifted or garbled
  • CS not going low: slave ignores all data
  • MISO stuck high: slave not responding (wrong CS, slave not powered, or wrong SPI mode)

I2C problems:

  • NACK after address: wrong address, slave not powered, or missing pull-up resistors
  • SDA stuck low: slave holding the bus (reset the slave or power cycle)
  • Missing pull-up resistors: signals do not reach VCC, causing intermittent failures

UART problems:

  • Baud rate mismatch: data appears as garbage characters
  • TX/RX swapped: no data in either direction
  • Missing ground connection: no data or random noise

Exercises



Exercise 1: SPI Byte Exchange

A master sends 0xA5 to a slave via SPI Mode 0, MSB first. Draw the sequence of bits on MOSI for all 8 clock cycles.

Solution

0xA5 = 10100101 in binary.

Clock CycleSCLK Rising EdgeMOSI
1First1 (bit 7)
2Second0 (bit 6)
3Third1 (bit 5)
4Fourth0 (bit 4)
5Fifth0 (bit 3)
6Sixth1 (bit 2)
7Seventh0 (bit 1)
8Eighth1 (bit 0)

Each bit is sampled on the rising edge and changed on the falling edge.

Exercise 2: I2C Address Calculation

You want to write to a device at address 0x50. What is the full first byte sent on the bus (including the R/W bit) for a write operation? What about for a read?

Solution

The first byte is the 7-bit address shifted left by 1, with the R/W bit in bit 0.

Write: (0x50 << 1) | 0 = 0xA0 (binary: 10100000)

Read: (0x50 << 1) | 1 = 0xA1 (binary: 10100001)

In C:

uint8_t write_addr = (0x50 << 1); // 0xA0
uint8_t read_addr = (0x50 << 1) | 1; // 0xA1

Exercise 3: UART Timing

At 115200 baud, how long does it take to transmit the string “Hello” (5 characters)? Assume 8N1 framing.

Solution

Each character requires 10 bits (1 start + 8 data + 1 stop).

5 characters = 50 bits.

Time per bit = \textmu is only supported in math mode\frac{1}{115200} = 8.68 \text{ \textmu s}

Total time = \textmu is only supported in math mode50 \times 8.68 \text{ \textmu s} = 434 \text{ \textmu s} \approx 0.43 \text{ ms}

How This Connects to Embedded Programming



HAL Functions Demystified

HAL_SPI_Transmit() generates SCLK pulses, shifts data out on MOSI, shifts data in on MISO, and manages CS. HAL_I2C_Master_Transmit() generates START, sends the address, checks ACK, sends data, and generates STOP. Now you know every step.

Debugging Communication

When a sensor does not respond, a logic analyzer shows you exactly what is on the wire. Is the address correct? Is the slave ACKing? Is the clock running? These are impossible to diagnose from code alone.

Bit-Banging

If your MCU does not have a hardware SPI or I2C peripheral on the pins you need, you can implement the protocol in software by toggling GPIO pins. This is called bit-banging. Understanding the signal-level protocol is essential for writing a correct bit-bang implementation.

Protocol Selection

Choosing between SPI, I2C, and UART for a project is a design decision that affects pin count, speed, PCB complexity, and software effort. Understanding each protocol at the signal level helps you make the right choice.

Summary



ProtocolSignalsClockSpeedBest For
SPISCLK, MOSI, MISO, CSSynchronous (master)Up to 50 MHzHigh-speed peripherals
I2CSCL, SDASynchronous (master)Up to 3.4 MHzMultiple low-speed devices
UARTTX, RXAsynchronousUp to ~1 MbaudPoint-to-point, debug console

In the next lesson, you will explore ADC and DAC: how analog voltages are converted to digital numbers and back.

Comments

Loading comments...


© 2021-2026 SiliconWit®. All rights reserved.