LECTURE 09

Synchronous Buses: SPI and I2C

Duration: 121 min of instruction Level: undergraduate, year II - recommended after Lecture 08 Discipline: Microcontrollers and Microprocessors Associated laboratory: Laboratory 06 PDF: download the material RO versiunea română

UART handles a link with a single partner well, but a real system has a temperature sensor, an accelerometer, a real-time clock, external memory and maybe a display - all at once. The asynchronous interface cannot carry this load: it has no shared clock, so speed is capped by oscillator precision, and it is strictly point to point, so every new peripheral demands new wires. This lecture presents the two synchronous buses that solve the problem, with opposite trade-offs: I2C, minimalist in wires but elaborate as a protocol, and SPI, generous in wires but with almost no protocol at all.

1Scope and structure of the lecture6 min

Both buses are synchronous - they explicitly carry a clock signal alongside data, which removes at a stroke the oscillator-drift problem discussed in Lecture 08. We will trace the electrical mechanism that makes wire sharing possible on I2C, the complete write and read transactions, then the circular shift register principle of SPI, the clock modes and possible topologies, and finally the concrete ATmega328P registers for both.

Recap from Lecture 08
  • Asynchronous communication requires resynchronization on every frame, and clock error accumulates - the reason UART speed is capped (Lecture 08)
  • UART is strictly point to point - with no addressing mechanism (Lecture 08)
  • Explain why carrying a clock signal removes the oscillator-drift problem
  • Describe the open-drain plus pull-up mechanism and its role in sharing the I2C bus
  • Trace, step by step, an I2C write transaction and a read transaction, including the role of ACK/NACK
  • Explain why SPI cannot read without writing, starting from the circular shift register
  • Identify the four SPI modes (CPOL, CPHA) and recognize the symptom of a mismatch
  • Choose, with justification, between UART, SPI and I2C for a given application

2Why a synchronous bus is needed10 min

Three limitations of UART become visible as soon as the application grows. First: the absence of a shared clock - the receiver latches onto the start edge and samples on trust; if the relative drift between the two oscillators exceeds about 2.5%, the last bit of the frame is sampled incorrectly. Second, a consequence of the first: speed is bounded by oscillator precision, not by the electronics of the line - a UART link on a ceramic resonator becomes unreliable well before one megabit per second, insufficient for a graphic display or an SD card.

Third: the number of devices. UART is strictly point to point, with no addressing - three devices on the same two wires have no way to know who a byte is meant for. An ATmega328P has a single USART module; if every peripheral required a dedicated link, the ability to expand would run out after a few modules.

The solution: a bus, not a dedicated link Instead of separate wires for each peripheral, a small set of wires shared by all devices, plus a protocol that says who talks, when, and to whom. I2C reduces the wires to the absolute minimum, two, at the cost of lower speed and a more complicated protocol. SPI goes the opposite way: four wires, almost no protocol, speed an order of magnitude higher. Both explicitly carry the clock, which removes the speed ceiling imposed by oscillator drift.

3I2C: overview, open drain, addressing13 min

I2C (Inter-Integrated Circuit, Philips, 1980s) uses two lines: SDA (Serial Data) and SCL (Serial Clock), plus a shared ground. The number of wires stays the same whether two devices are connected or twelve - the great advantage of the bus.

The protocol is master-slave: the master (usually the microcontroller) generates the clock, initiates every transfer and decides when it ends. The slaves (sensors, memories, real-time clocks) never speak unprompted - they only respond on request.

Open drain and pull-up resistors

If several devices are tied to the same wire, who wins when one drives a level and another drives the opposite? The solution: the output stage of every device is open drain - a single transistor, between the line and ground. When conducting, it pulls the line to zero; when off, it releases it. No device can force the line to logic one. Each line is tied to the supply through a pull-up resistor, which brings the line back to logic one whenever nobody is pulling it down - the result is a wired-AND function: the line is at one only if every device releases it.

Why an electrical collision becomes impossible There are no two sources opposing each other - at most, several transistors pulling in the same direction. The value of the resistors is a trade-off: too large slows the rising edge (risk that the transition does not reach level before the next clock edge); too small increases the current drawn when pulling the line low. Usual values: about 4.7 kΩ for 100 kHz, about 2.2 kΩ for 400 kHz or buses with many devices. On the Arduino Uno, SDA/SCL are on A4/A5 (PC4/PC5).
Commercial modules with their own pull-ups, wired in parallel

Many modules already have the resistors mounted on their board - four or five such modules on the same bus put the resistors in parallel, dropping the equivalent resistance below one kilohm, raising the current, and potentially destabilizing the bus. Remedy: remove the resistors from every module but one.

Seven-bit addressing

Each slave has a 7-bit address (128 combinations, of which 112 usable, the rest reserved). Every transfer begins by sending this address - all slaves listen, compare it with their own address, and only the one that recognizes itself continues the dialog. The address is set by the manufacturer, not the designer; some chips have configuration pins for the last bits, to allow several units on the same bus.

The 7-bit address versus the byte sent on the bus

The 7 address bits are followed by an eighth direction bit (0=write, 1=read). The same address sometimes appears as 0x68 (the 7-bit convention, used by the Arduino libraries, which do the shifting) and sometimes as 0xD0 (the full byte sent on the wire) - a frequent confusion that makes the device fail to respond at all.

Standardized speeds: standard mode 100 kHz, fast mode 400 kHz, more recent variants 1 MHz and 3.4 MHz - all of these are maximums, not required values; the master can go slower without anything breaking, precisely because the clock is transmitted, not assumed.

Sizing the pull-up resistors on the I2C bus

4Start, stop conditions and ACK/NACK confirmation11 min

What remains to be solved is delimiting messages. The general rule: SDA is only allowed to change state while SCL is low; whenever SCL is high, the data must be stable, because that is exactly when the receiver samples it. A transition of SDA while SCL is high is, by definition, impossible during data transport - so it can be used as a control signal.

The start, stop and repeated start conditions
Start: SDA from 1 to 0 while SCL is high - signals to everyone that the bus has become busy and that an address follows.
Stop: SDA from 0 to 1 while SCL is high - releases the bus.
Repeated start: identical to start, but with no stop before it - allows the direction of the transfer to change without releasing the bus, preventing another master from slipping in between the two halves.

ACK/NACK confirmation

Unlike UART, I2C requires confirmation after every byte. Each byte occupies 9 clock periods: eight for data (MSB first), the ninth for confirmation. On the ninth period, the transmitter releases SDA; if the receiver accepts the byte, it pulls the line to ground (ACK); if it does nothing, the pull-up resistor keeps the line high (NACK).

NACK has a different meaning depending on context After the address byte: NACK means that no slave on the bus carries that address - the principle behind bus scanning. After a data byte sent by the master: NACK means the slave cannot accept more data (buffer full). On reading: the role reverses - the master confirms every byte received, but deliberately sends NACK after the last byte it wants, telling the slave to stop. A master that confirmed even the last byte would be telling the slave to keep going indefinitely.

5I2C transactions: write, read, arbitration13 min

All the elements above combine into two patterns that every I2C library executes.

The write transaction

To write 0x1F into register 0x07 of a slave at address 0x68: the master generates start, sends 0xD0 (address+write), reads ACK; sends 0x07 (the register number, a convention of the peripheral, not of the bus), reads ACK; sends 0x1F (the value), reads ACK; generates stop. For the I2C protocol, the transfer is: start, address with write, three confirmations, two data bytes, stop - the interpretation "register plus value" belongs to the datasheet of the peripheral.

The read transaction

More subtle, because the slave usually has to be told first what is wanted, which means a write. Phase 1: start, address with write (0xD0), ACK, register number (0x07), ACK - the slave has positioned its internal pointer, but has sent nothing. Phase 2: repeated start, address with read (0xD1), ACK - the roles on SDA reverse, the slave becomes the transmitter, the master the receiver, but the master keeps generating the clock. For each byte received, the master confirms with ACK if it wants more data, or with NACK if it was the last one wanted, then generates stop.

The order NACK-then-stop is mandatory

If the master generated stop right after an ACK, the slave could be left with its output active, holding SDA to ground - the bus stays locked until power is cycled. If the microcontroller resets in the middle of a read, the slave keeps holding SDA low, waiting for the clock; the master can no longer even generate a start. Unlocking: manually generating, in software, up to nine pulses on SCL, followed by a stop condition.

Reading a register from an I2C slave: put the steps in order

Arbitration and clock stretching

Arbitration resolves the collision when two masters start simultaneously: each sends the address bits and reads SDA at the same time - if it reads 0 when it sent 1, someone else is pulling the line, and it withdraws immediately, with no bits lost. The master with the numerically smaller address wins (zero dominates). Clock stretching resolves the slow slave: SCL is also open drain, so a slave that needs time holds it pulled low after the master has released it; the master, which checks the actual level before considering the period finished, waits. It is a flow-control mechanism that neither UART nor SPI has.

6SPI: the circular shift register11 min

SPI (Serial Peripheral Interface) starts from the opposite premises - it does not economize on wires, it transfers as fast as possible with as little logic as possible. It has no single official specification; it is a hardware mechanism for exchanging bits, on top of which every peripheral defines its own conventions.

Communication is full duplex, master-slave, over four lines: SCLK (clock, generated exclusively by the master), MOSI (Master Out Slave In), MISO (Master In Slave Out), and SS (Slave Select, active on logic zero - an unselected slave puts its MISO into high impedance).

The central idea: it is not a transmission, it is an exchange The master and the slave each have an 8-bit shift register, linked end to end through MOSI and MISO, forming a single 16-bit circular register. On every clock pulse, one bit leaves on MOSI and enters the slave, and one bit leaves on MISO and enters the master - simultaneously. After eight pulses, the two devices have exchanged one byte each.
On SPI you cannot read without writing, or the reverse

Shifting in both registers is driven by the same clock - there is no "write only" or "read only". If the master only wants to send, it still gets back the byte from the slave register (usually ignored). If it only wants to read, it still has to send a dummy byte, because otherwise it would have no reason to generate clock pulses. This is where the shape of every SPI library function comes from: a single transfer function, which takes a byte and returns a byte.

A second consequence: the slave cannot respond immediately. The byte it sends is the one already prepared in the register at the start of the transfer, not one computed from the byte received at the same time. The usual dialog has two stages: the first transfer sends a command and receives a byte with no value; the second sends a dummy byte and receives the response to the previous command. Anyone who ignores this offset systematically reads the response one position late.

The SS line has two roles: it selects the slave, and it delimits the transaction - many peripherals reset their internal logic on every falling edge of selection, and their commands are interpreted relative to the start of the transaction.

7SPI modes: polarity and phase11 min

A shift register needs both clock edges, for different operations: on one, the output is updated (the next bit goes onto the line); on the other, the input is sampled (the current bit is captured). The two must happen on opposite edges, half a period apart, otherwise the receiver would read the line right at the moment of switching.

CPOL and CPHA
CPOL (polarity): the idle state of the SCLK line - 0 = low (the first edge is rising), 1 = high (the first edge is falling).
CPHA (phase): which edge samples - 0 = sampling on the first edge, change on the second; 1 = the reverse.
ModeCPOLCPHASampling
000rising edge (first)
101falling edge (second)
210falling edge (first)
311rising edge (second)
Match each SPI mode to the behavior of the clock line
The four SPI modes - choose CPOL and CPHA

Modes 0 and 3 are the most common - in both, sampling happens on the rising edge, only the idle state differs. Mode 0 is the default for the Arduino library and for most memories, SD cards and converters.

A wrong SPI mode is the silent error par excellence

If the master is in mode 0 (drives on falling, reads on rising) and the slave is in mode 1 (the reverse), the entire sequence shifts by half a period - the slave captures the next bit instead of the current one. SPI has no parity, checksum or confirmation - the master register fills with eight bits just as conscientiously as if everything were correct. Symptom: a sensor reports absurd values, a display shows scattered pixels, an SD card fails to initialize, and the errors can appear intermittently, depending on the stray delays of the layout. Diagnosis: the peripheral datasheet, an oscilloscope, or methodically trying all four modes.

The same category of silent errors: bit order Most peripherals expect the most significant bit first, but some work the other way - AVR hardware allows both through the DORD bit. With the wrong order, exactly the same eight bits are received, but mirrored: 0x01 becomes 0x80, and the program has no way to notice the difference.

8SPI topologies and the ATmega328P SPI module13 min

The single-slave case ties the four lines directly - SS can sometimes be tied permanently to ground, if the peripheral accepts it. The independent selection configuration: all slaves share SCLK, MOSI, MISO, but each has its own SS line - complete independence, but costing 3+n pins for n slaves. Daisy chaining: the MISO of the first goes to the MOSI of the second and so on, SCLK and SS shared by all; the registers form a single long chain - the pin count stays 4 regardless of how many slaves, but the devices can no longer be addressed individually, and the duration of a transfer grows proportionally. Used only for identical devices that get updated together anyway (a chain of display drivers), never for different sensors.

The ATmega328P SPI module

Four physical pins: PB2 (SS), PB3 (MOSI), PB4 (MISO), PB5 (SCK) - pins 10-13 on the Arduino Uno.

RegisterBitRole
SPCRSPE / MSTRmodule enable / role (1=master)
SPCRCPOL / CPHA / DORDoperating mode, bit order
SPCRSPR1:0clock prescaling: f_osc/4, /16, /64, /128
SPSRSPIFtransfer finished (8 pulses consumed)
SPSRSPI2Xdoubles the frequency, up to f_osc/2
SPDR-data register - writing starts the transfer, reading returns the byte received
PB2 (SS) must be configured as an output, even if selection is done on another pin

If it stays an input and something pulls it low, the module automatically switches from master to slave, clearing the MSTR bit - a mechanism meant for multi-master systems, but which, in an ordinary application, produces a hard-to-explain fault: communication stops abruptly, for no apparent reason.

void SPI_MasterInit(void) {
    DDRB |= (1<<PB3)|(1<<PB5)|(1<<PB2);   /* MOSI, SCK, SS: outputs */
    DDRB &= ~(1<<PB4);                     /* MISO: input */
    SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0);  /* master, clock f_osc/16 */
    PORTB |= (1<<PB2);                      /* SS inactive (High) */
}
uint8_t SPI_MasterTransfer(uint8_t data) {
    PORTB &= ~(1<<PB2);         /* select the slave */
    SPDR = data;                  /* writing starts the transfer */
    while (!(SPSR & (1<<SPIF))) { }
    PORTB |= (1<<PB2);
    return SPDR;                  /* byte received from the slave */
}
Worked exercise - choosing the prescaler for a peripheral limited to 2 MHz

ATmega328P at 16 MHz, peripheral limited to 2 MHz on SCLK.

See the solution

Available dividers (without SPI2X): 4, 16, 64, 128, giving 4 MHz, 1 MHz, 250 kHz, 125 kHz. The first value (4 MHz) exceeds the limit - divide by 16 is chosen (SPR0=1, SPR1=0), a 1 MHz clock. Bit period: 1 µs, so eight bits transfer in 8 µs. At high speeds, the software overhead (writing SPDR, testing SPIF, driving the select line) becomes comparable to the duration of the transfer itself, and the effective useful rate stays noticeably below the theoretical one.

9The SPI and Wire libraries, scanning the I2C bus9 min

The SPI library provides the notion of a transaction, which declares the speed, bit order and mode required by the peripheral:

SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
digitalWrite(PIN_SS, LOW);
SPI.transfer(address | 0x80);        /* command; bit 7 marks a read */
uint8_t value = SPI.transfer(0x00);  /* dummy byte, receives the response */
digitalWrite(PIN_SS, HIGH);
SPI.endTransaction();

Selection is still driven explicitly by the program (the pin number depends on the wiring); the two transfers reflect exactly the offset discussed earlier.

The Wire library, for I2C, implements the transactions discussed above:

Wire.beginTransmission(ADDRESS);    /* prepares START + address + W */
Wire.write(reg);
Wire.endTransmission(false);        /* false = repeated START, no STOP */
Wire.requestFrom(ADDRESS, (uint8_t)1);
if (Wire.available()) value = Wire.read();

beginTransmission sends nothing - it accumulates bytes in an internal buffer; only endTransmission generates start, address, data and (unless given false) stop. The returned value is an error code: 0=success, 2=NACK on address (peripheral absent), 3=NACK on data.

Scanning the I2C bus

This follows directly from the confirmation mechanism: start plus address plus stop, with no data - if ACK comes back, a device exists at that address; if NACK, it does not.

for (uint8_t address = 8; address < 120; address++) {
    Wire.beginTransmission(address);
    if (Wire.endTransmission() == 0) {
        Serial.print("Found at 0x"); Serial.println(address, HEX);
    }
}
Interpreting the scan result

An empty result: check for swapped SDA/SCL, a missing shared ground, absent pull-ups. A result with devices at every address: usually indicates an SDA line stuck at logic zero - every query appears confirmed.

Worked exercise - configuring TWBR for 100 kHz and 400 kHz

ATmega328P at 16 MHz. The TWI module: f_SCL = f_osc / (16 + 2·TWBR·4^TWPS).

See the solution

With TWPS=0 (4^0=1): f_SCL = f_osc/(16+2·TWBR).

For 100 kHz: 16×10⁶/100000 = 160 = 16+2·TWBR ⇒ TWBR=72.

For 400 kHz: 16×10⁶/400000 = 40 = 16+2·TWBR ⇒ TWBR=12.

Both values are exact integers. Unlike UART, where rate error is a permanent concern, here a deviation from nominal would have no functional consequence - the slaves follow whatever clock they receive, as long as it does not exceed their maximum limit.

10Comparing UART, SPI and I2C6 min

CriterionUARTSPII2C
Wires23 shared + n select2
Shared clocknoyesyes
Usual speed9600-115200 bpsup to 10 Mbit/s100-400 kHz
Devices2, point to point1 master + n slavesup to 112 addresses
Confirmationoptional parity onlynoneACK/NACK per byte
Typical usecomputer, debuggingmemories, SD, fast displaysslow sensors, RTC, EEPROM
The practical rule of thumb If the partner is far away, a computer, or talks asynchronously anyway: UART. If the transfer is large and fast, and the number of peripherals is small: SPI - a graphic display or a converter sampling thousands of times per second has no other reasonable option. If the peripherals are many, small and slow, and pins are scarce: I2C - a sensor read once a second does not need a megabit per second, and saving pins is decisive.

The three coexist without conflict on the same microcontroller, because they use different hardware modules and pins - an ordinary project has, at the same time, UART to a computer for debugging, I2C with two or three sensors, and SPI to a memory card or a display.

11Common mistakes4 min

  • I connected SDA and SCL with no pull-up resistors - the devices have power pins anyway. False - without the pull-up resistors, lines released by every device stay floating, at an undefined level, instead of logic one. Every I2C line needs a pull-up resistor, either external or already present on a module on the bus.
  • The SPI peripheral does not respond correctly, but the code looks right - it must be a power problem. Often not - it is a mismatched SPI mode (CPOL/CPHA), which produces no error message, only wrong data. Check the datasheet for the required mode and, if it is not clearly specified, methodically try the four combinations.
  • I confirmed (ACK) even the last byte of an I2C read, to make sure everything went well. False - ACK on the last byte tells the slave to keep transmitting, not that everything is fine. Deliberately send NACK after the last byte wanted, then generate the stop condition.

12Summary and glossary5 min

Both buses solve the limits of UART by explicitly carrying a clock signal. SPI does it with four lines and almost no protocol: the master and the slave form, through MOSI and MISO, a single circular shift register, and on every pulse one bit leaves and one bit enters simultaneously - reading and writing are inseparable. The four modes (CPOL, CPHA) must match exactly between master and slave, otherwise wrong data appears with no warning at all. Independent selection costs one pin per slave; daisy chaining saves pins at the price of individual addressing. I2C achieves the same with two lines, paying with an elaborate protocol: open drain and pull-up resistors make the bus shareable without conflicts, the start/stop conditions use the only transitions forbidden during data, and ACK/NACK confirmation after every byte provides a flow control that neither UART nor SPI has. The choice between them is not preference but requirements: speed for SPI, pin economy for I2C, distance and compatibility for UART.

Open drain
an output stage that can only pull the line to ground, never to the supply.
ACK / NACK
confirmation / non-confirmation on I2C, generated by the receiver through the open drain.
Circular shift register
the SPI mechanism by which the master and the slave exchange one bit each, simultaneously.
CPOL / CPHA
the polarity and phase of the SPI clock, which define the four modes.
Dummy byte
a byte sent with no useful value, only to generate the clock pulses needed for an SPI read.
Clock stretching
a slow I2C slave stretching the clock by holding SCL low.

13Review questions7 min

  1. Explain why a UART link cannot connect three devices on the same wires, while I2C can connect ten. What element of the protocol makes the difference?
  2. Why does explicitly carrying the clock remove the oscillator-drift problem?
  3. Describe what would happen electrically on SDA if one device had a push-pull output instead of open drain, while a second device pulled the line to ground at the same time.
  4. Explain why the start condition cannot be confused with an ordinary data bit.
  5. A master reads five bytes from an I2C slave. Who confirms each byte, and what happens if the master confirms even the last one?
  6. Justify the statement that on SPI you cannot read without writing, starting from the circular shift register.
  7. An SPI peripheral requires mode 2, the program configures it for mode 0. What happens at the level of the edges, and why does no error message appear?

14Further directions2 min

The next lectures move from the individual mechanisms of peripherals to designing a complete application: how the resources discussed so far - ports, interrupts, timers, ADC, communication - are chosen and combined into a functional, tested and documented system.

SPI configuration, the I2C protocol and bus scanning from this lecture become real hardware in Laboratory 06.