SPI Protocol in Depth: Clock Modes, Chip Select and Data Exchange

SPI, the Serial Peripheral Interface, is the fast, simple bus used by SD cards, TFT displays, external flash chips, ADCs and radio modules like the nRF24L01 and SX1276. Where I2C saves wires, SPI saves time: there are no addresses, no acknowledgements and no pull-up limited edges, so clock rates of several megahertz to tens of megahertz are routine.

This article explains the shift-register model behind SPI, the four clock modes that cause most beginner bugs, how to connect several devices, and a complete example reading the MCP3008 10-bit ADC.

The Four Signals

SignalOther namesDirectionArduino Uno pin
SCLKSCK, CLKMaster to slave13
MOSISDI on slave, COPIMaster to slave11
MISOSDO on slave, CIPOSlave to master12
CSSS, CE, nCSMaster to slave, active low10 (or any GPIO)

Lines are push-pull, driven actively both high and low, which is why SPI is fast. The newer names COPI (controller out, peripheral in) and CIPO mean the same as MOSI and MISO.

SPI Is a Ring of Shift Registers

Inside, the master and slave each have an 8-bit shift register. On every clock pulse, one bit leaves the master on MOSI and enters the slave, while one bit leaves the slave on MISO and enters the master. After 8 clocks the two registers have swapped contents.

        MASTER                         SLAVE
   +-----------------+   MOSI    +-----------------+
   | 7 6 5 4 3 2 1 0 | --------> | 7 6 5 4 3 2 1 0 |
   +-----------------+           +-----------------+
            ^           MISO              |
            +-----------------------------+
                 SCLK drives both

This has two consequences. First, every transfer is full duplex: you cannot send without receiving. To read, the master sends dummy bytes (often 0x00 or 0xFF) just to generate clocks. Second, the slave cannot start a conversation; the master controls all timing.

CPOL, CPHA and the Four Modes

CPOL sets the idle level of the clock. CPHA sets whether data is sampled on the first or second clock edge after CS goes low. Master and slave must use the same mode or every bit is read at the wrong moment.

ModeCPOLCPHAClock idleData sampled onData changes on
000LowRising edgeFalling edge
101LowFalling edgeRising edge
210HighFalling edgeRising edge
311HighRising edgeFalling edge

Mode 0 is the most common. SD cards use mode 0; many chips, including the MCP3008, accept both mode 0 and mode 3. Always read the timing diagram in the slave's datasheet: if the clock idles low and the data is valid at the first rising edge, it is mode 0.

Connecting More Than One Device

  • Independent chip selects: SCLK, MOSI and MISO are shared, and each slave gets its own CS pin. Only the selected slave drives MISO; the others put it in high impedance. This is the normal arrangement.
  • Daisy chain: MISO of one slave feeds MOSI of the next, with one shared CS. Data ripples through the chain like one long shift register. Used with shift registers like the 74HC595 and LED drivers like the MAX7219.
  • Pull-ups on CS: add 10 k pull-ups so that slaves stay deselected while the microcontroller is resetting.

Be careful with modules that do not release MISO when deselected. A badly behaved slave can corrupt reads from every other device on the bus.

Example: Reading the MCP3008 ADC

The MCP3008 is an 8-channel, 10-bit ADC. To read a single-ended channel, the master sends three bytes: a start bit, then the single-ended flag with a 3-bit channel number, then a dummy byte. The 10-bit result comes back spread across the last two received bytes.

Byte sent:      0000 0001    1CCC 0000     0000 0000
                start bit    SGL + ch     dummy
Byte received:  ???? ????    ???? ?0RR     RRRR RRRR
                                  null + B9 B8   B7..B0

result = ((rx1 & 0x03) << 8) | rx2
Arduino sketch reading MCP3008 channel 0 at 1 MHz, SPI mode 0
#include <SPI.h>

const uint8_t CS_PIN = 10;

uint16_t readMCP3008(uint8_t channel) {
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
  digitalWrite(CS_PIN, LOW);
  SPI.transfer(0x01);                                   // start bit
  uint8_t hi = SPI.transfer(0x80 | ((channel & 0x07) << 4)); // single-ended + channel
  uint8_t lo = SPI.transfer(0x00);                      // dummy byte, clocks out B7..B0
  digitalWrite(CS_PIN, HIGH);
  SPI.endTransaction();
  return ((uint16_t)(hi & 0x03) << 8) | lo;
}

void setup() {
  Serial.begin(115200);
  pinMode(CS_PIN, OUTPUT);
  digitalWrite(CS_PIN, HIGH);   // deselect before starting SPI
  SPI.begin();
}

void loop() {
  uint16_t raw = readMCP3008(0);
  float volts = raw * 5.0 / 1023.0;  // VREF tied to 5 V
  Serial.print(raw);
  Serial.print("  ");
  Serial.println(volts, 3);
  delay(200);
}

The 1 MHz clock is chosen because the MCP3008 maximum clock depends on supply voltage and is lower at 2.7 V than at 5 V. Check the datasheet table for your supply before raising it.

Speed and Troubleshooting

Raw throughput = f_SCLK / 8 bytes per second
8 MHz SCLK -> 1 MB/s maximum (less in practice, due to
software gaps between bytes and CS toggling)
  • All bytes read 0xFF or 0x00: MISO not connected, wrong CS pin, or slave not powered.
  • Data shifted by one bit: wrong SPI mode.
  • Works on the bench, fails with long wires: reduce clock speed; keep SPI wiring short, under about 20 to 30 cm for MHz rates.
  • Another library breaks your device: always wrap transfers in beginTransaction and endTransaction so each device gets its own settings.
  • Pin 10 left as input on an Uno: the AVR may switch into slave mode; keep it as an output.

Summary

SPI uses SCLK, MOSI, MISO and an active-low CS per device. Each clock swaps one bit between master and slave shift registers, so reading always means sending. CPOL and CPHA define four modes, and the slave's datasheet decides which one you use. With independent chip selects and per-transaction settings, several fast devices can share one bus reliably.