What is SPI Protocol — High-Speed Synchronous Serial-Embedded C Programming Training Online

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

Tutorial 14 — Communication

SPI Protocol — High-Speed Synchronous Serial

Complete STM32F407 SPI guide. CPOL/CPHA modes explained, CR1/CR2/SR/DR registers bit-by-bit, software/hardware NSS, full-duplex transfers, and W25Q32 flash memory example.

⚡ Up to 42 MHz Speed📋 4 Registers Explained🔄 4 CPOL/CPHA Modes💾 W25Q32 Flash Example
SECTION 1

SPI Protocol Overview

SPI (Serial Peripheral Interface) uses 4 wires: SCK (clock), MOSI (Master Out Slave In), MISO (Master In Slave Out), and NSS/CS (Chip Select, active low). It’s a full-duplex protocol — master and slave exchange data simultaneously every clock edge.

Key advantages over I2C: much faster (up to 42 MHz on STM32F407), simpler protocol (no ACK/NACK/addressing), full-duplex. Disadvantage: needs one CS line per slave device, plus 3 shared signal wires.

SPI Instances on STM32F407

InstanceBusIRQ#Max SpeedMOSIMISOSCK
SPI1APB23542 MHzPA7 or PB5 (AF5)PA6 or PB4 (AF5)PA5 or PB3 (AF5)
SPI2APB13621 MHzPB15 or PC3 (AF5)PB14 or PC2 (AF5)PB13 or PB10 (AF5)
SPI3APB15121 MHzPB5 or PC12 (AF6)PB4 or PC11 (AF6)PB3 or PC10 (AF6)

On the STM32F407 Discovery, SPI1 is used for the LIS3DSH accelerometer (PA5=SCK, PA6=MISO, PA7=MOSI, PE3=CS).

SECTION 2

CPOL and CPHA — Clock Phase and Polarity

SPI has 4 modes defined by two bits: CPOL (clock polarity when idle) and CPHA (clock edge for data capture). You must match the slave device’s requirements — check its datasheet for “SPI Mode” or “CPOL/CPHA”.

Diagram: CPOL and CPHA — Clock Phase and Polarity

Mode 0 — CPOL=0, CPHA=0 (most common)

Clock idle = LOW
Data captured on RISING edge
Data changed on FALLING edge
SCK:
▲ idle
MOSI:
?XXX
D7
D6
D5…
▲ capture here

Used by: SD cards, W25Q32, many sensors

Mode 1 — CPOL=0, CPHA=1

Clock idle = LOW
Data captured on FALLING edge
Data changed on RISING edge
SCK:
▲ idle
MOSI:
???XX
D7
D6…
▲ capture

Used by: some ADCs

Mode 2 — CPOL=1, CPHA=0

Clock idle = HIGH
Data captured on FALLING edge
SCK:
▲ idle
MOSI:
?XXX
D7
D6…
▲ capture here

Less common

Mode 3 — CPOL=1, CPHA=1

Clock idle = HIGH
Data captured on RISING edge
SCK:
▲ idle
MOSI:
???XX
D7
D6…
▲ capture

Used by: LIS3DSH accelerometer

Checking slave datasheet: Look for “SPI Mode” or a timing diagram showing SCK idle state and which edge captures data. If the datasheet says “data stable on falling SCK edge, clocked on rising” → that’s Mode 0.
SECTION 3

SPI Registers — SPI1 Base: 0x40013000

SPI_CR1 — Control Register 1

BitsNameDescription
0CPHAClock Phase. 0=first edge capture, 1=second edge capture.
1CPOLClock Polarity. 0=idle LOW, 1=idle HIGH.
2MSTRMaster Mode. 1=master (generates SCK). 0=slave.
[5:3]BR[2:0]Baud Rate Control. Sets SCK = f_PCLK / (2^(BR+1)). 000=÷2, 001=÷4, …, 111=÷256.
6SPESPI Enable. Set LAST after all configuration.
7LSBFIRSTFrame Format. 0=MSB first (standard), 1=LSB first.
8SSIInternal Slave Select. When SSM=1, this bit value is applied to NSS signal internally.
9SSMSoftware Slave Management. 1=NSS driven by SSI bit (software). 0=NSS pin used (hardware).
10RXONLYReceive Only. 1=disable MOSI output (half-duplex receive).
11DFFData Frame Format. 0=8-bit, 1=16-bit frames.
12CRCNEXTCRC Transfer Next. Send CRC after next transfer.
13CRCENHardware CRC Enable.
14BIDIOEOutput Enable in Bidirectional Mode.
15BIDIMODEBidirectional Data Mode. 1=1-wire bidirectional.
/* SPI baud rates from SPI1 on APB2 = 84 MHz: */
/* BR=000 → 84MHz/2  = 42 MHz */
/* BR=001 → 84MHz/4  = 21 MHz */
/* BR=010 → 84MHz/8  = 10.5 MHz */
/* BR=011 → 84MHz/16 = 5.25 MHz */
/* BR=100 → 84MHz/32 = 2.625 MHz */
/* BR=101 → 84MHz/64 = 1.3125 MHz */
/* BR=110 → 84MHz/128 = 656 kHz */
/* BR=111 → 84MHz/256 = 328 kHz */

#define SPI_BR_DIV2    (0b000 << 3)
#define SPI_BR_DIV4    (0b001 << 3)
#define SPI_BR_DIV8    (0b010 << 3)
#define SPI_BR_DIV16   (0b011 << 3)
#define SPI_BR_DIV32   (0b100 << 3)
#define SPI_BR_DIV64   (0b101 << 3)
#define SPI_BR_DIV128  (0b110 << 3)
#define SPI_BR_DIV256  (0b111 << 3)

SPI_SR — Status Register

BitNameDescription
0RXNEReceive Buffer Not Empty. 1=data available in DR to read.
1TXETransmit Buffer Empty. 1=DR is empty, can write next byte.
4CRCERRCRC Error flag.
5MODFMode Fault. NSS went low while in master mode.
6OVROverrun flag. New data arrived before old was read.
7BSYBusy flag. 1=SPI is transferring data. Do NOT disable SPI while BSY=1.
8FREFrame format error (TI mode).
SECTION 4

SPI Initialization and Transfer Functions

/*
 * SPI1 initialization — Mode 0 (CPOL=0, CPHA=0), 8-bit, MSB first
 * PA5=SCK, PA6=MISO, PA7=MOSI (AF5), CS managed by software
 * SPI1 on APB2 = 84MHz, BR=001 → 84/4 = 21 MHz
 */
#include <stdint.h>

#define RCC_AHB1ENR   (*((volatile uint32_t*)0x40023830))
#define RCC_APB2ENR   (*((volatile uint32_t*)0x40023844))
#define GPIOA_MODER   (*((volatile uint32_t*)0x40020000))
#define GPIOA_OSPEEDR (*((volatile uint32_t*)0x40020008))
#define GPIOA_AFRL    (*((volatile uint32_t*)0x40020020))

#define SPI1_BASE     0x40013000UL
#define SPI1_CR1      (*((volatile uint32_t*)(SPI1_BASE + 0x00)))
#define SPI1_CR2      (*((volatile uint32_t*)(SPI1_BASE + 0x04)))
#define SPI1_SR       (*((volatile uint32_t*)(SPI1_BASE + 0x08)))
#define SPI1_DR       (*((volatile uint32_t*)(SPI1_BASE + 0x0C)))

/* Software NSS — use any GPIO as CS */
#define GPIOA_BSRR    (*((volatile uint32_t*)0x40020018))
#define CS_LOW()      GPIOA_BSRR = (1 << (4+16))   /* PA4 low */
#define CS_HIGH()     GPIOA_BSRR = (1 << 4)         /* PA4 high */

void SPI1_Init(void) {
    /* Enable clocks */
    RCC_AHB1ENR |= (1<<0);  /* GPIOA */
    RCC_APB2ENR |= (1<<12); /* SPI1 */

    /* PA4=CS output, PA5=SCK, PA6=MISO, PA7=MOSI */
    /* PA4: output mode (10 in MODER) */
    GPIOA_MODER &= ~((3<<8)|(3<<10)|(3<<12)|(3<<14));
    GPIOA_MODER |=   (1<<8)|(2<<10)|(2<<12)|(2<<14);
    /* PA4=01 (output), PA5=10 (AF), PA6=10 (AF), PA7=10 (AF) */

    /* High speed for all SPI pins */
    GPIOA_OSPEEDR |= (3<<10)|(3<<12)|(3<<14);

    /* AF5 (SPI1) for PA5, PA6, PA7 */
    GPIOA_AFRL &= ~((0xF<<20)|(0xF<<24)|(0xF<<28));
    GPIOA_AFRL |=   (5<<20)|(5<<24)|(5<<28);

    /* CS high (deselected) */
    CS_HIGH();

    /* SPI1 CR1 configuration */
    SPI1_CR1 = 0;            /* Reset */
    SPI1_CR1 =
        (0 << 0) |           /* CPHA=0: first edge capture */
        (0 << 1) |           /* CPOL=0: idle LOW */
        (1 << 2) |           /* MSTR=1: master mode */
        (1 << 3) |           /* BR=001: 84MHz/4 = 21MHz */
        (0 << 7) |           /* LSBFIRST=0: MSB first */
        (1 << 8) |           /* SSI=1: NSS driven high internally */
        (1 << 9) |           /* SSM=1: software NSS management */
        (0 << 11);           /* DFF=0: 8-bit */
    SPI1_CR2 = 0;
    SPI1_CR1 |= (1 << 6);   /* SPE=1: enable SPI (LAST) */
}

/* Send and receive 1 byte (full-duplex) */
uint8_t SPI1_Transfer(uint8_t tx) {
    while (!(SPI1_SR & (1<<1)));   /* Wait TXE */
    SPI1_DR = tx;                    /* Write byte to transmit */
    while (!(SPI1_SR & (1<<0)));   /* Wait RXNE */
    return (uint8_t)SPI1_DR;       /* Read received byte */
}

/* Send multiple bytes — discard received data */
void SPI1_Write(const uint8_t* buf, uint32_t len) {
    for (uint32_t i = 0; i < len; i++) SPI1_Transfer(buf[i]);
}

/* Read multiple bytes — send dummy 0xFF */
void SPI1_Read(uint8_t* buf, uint32_t len) {
    for (uint32_t i = 0; i < len; i++) buf[i] = SPI1_Transfer(0xFF);
}

/* IMPORTANT: Wait for BSY=0 before raising CS */
void SPI1_CS_Deassert(void) {
    while (SPI1_SR & (1<<7));   /* Wait BSY=0 (transfer complete) */
    CS_HIGH();
}
Common SPI bug — raising CS too early: Always wait for BSY=0 (busy flag clear) before asserting CS high. The BSY flag stays set until the last bit is fully shifted out on MOSI. If you raise CS while BSY=1, the slave sees an incomplete transfer and corrupts its receive buffer.
SECTION 5

NSS — Chip Select Management

STM32 SPI NSS can be managed two ways: software (SSM=1) or hardware (SSM=0). For most applications with multiple slave devices, software NSS is preferable — you manually control GPIO pins and can select any device.

ModeSSM bitNSS pin usageBest for
Software NSS (SSM=1)1NSS pin unused — use any GPIO as CSMultiple slaves, most cases
Hardware NSS output (SSM=0, SSOE=1)0NSS automatically driven LOW during transfer, HIGH afterSingle slave, DMA-driven SPI
Hardware NSS input (SSM=0, SSOE=0)0NSS monitors for multi-master conflictsMulti-master environments
/* Hardware NSS output (SSOE=1 in CR2) — single slave */
SPI1_CR1 &= ~(1<<9);    /* SSM=0: hardware NSS */
SPI1_CR2 |=  (1<<2);    /* SSOE=1: NSS output enabled */
/* NSS (PA4 on SPI1) goes LOW automatically when SPE=1 and transfer starts */
/* NSS goes HIGH automatically after STOP */

/* Software NSS with multiple slaves */
/* Define CS for each slave separately: */
#define FLASH_CS_LOW()   GPIOA_BSRR = (1<<(4+16))  /* PA4 */
#define FLASH_CS_HIGH()  GPIOA_BSRR = (1<<4)
#define ACCEL_CS_LOW()   GPIOE_BSRR = (1<<(3+16))  /* PE3 (LIS3DSH) */
#define ACCEL_CS_HIGH()  GPIOE_BSRR = (1<<3)
SECTION 6

W25Q32 SPI Flash Memory Example

W25Q32 is a 32-Mbit (4MB) SPI NOR flash. It uses SPI Mode 0 or Mode 3, up to 80 MHz. Basic operations: Read (opcode 0x03), Read JEDEC ID (0x9F), Write Enable (0x06), Page Program (0x02), Sector Erase (0x20).

/* W25Q32 command opcodes */
#define W25_CMD_READ_ID     0x9F   /* Read JEDEC ID: EF 15 xx (manufacturer+density) */
#define W25_CMD_READ_SR     0x05   /* Read Status Register */
#define W25_CMD_WRITE_EN    0x06   /* Write Enable (must call before write/erase) */
#define W25_CMD_READ        0x03   /* Read Data (24-bit address) */
#define W25_CMD_PAGE_PROG   0x02   /* Page Program (write up to 256 bytes) */
#define W25_CMD_SECTOR_ERASE 0x20  /* Erase 4KB sector */
#define W25_SR_BUSY         0x01   /* Status Register BUSY bit */

void W25_WaitBusy(void) {
    uint8_t sr;
    do {
        CS_LOW();
        SPI1_Transfer(W25_CMD_READ_SR);
        sr = SPI1_Transfer(0xFF);
        SPI1_CS_Deassert();
    } while (sr & W25_SR_BUSY);
}

uint32_t W25_ReadID(void) {
    CS_LOW();
    SPI1_Transfer(W25_CMD_READ_ID);
    uint8_t mfr  = SPI1_Transfer(0xFF);
    uint8_t type = SPI1_Transfer(0xFF);
    uint8_t cap  = SPI1_Transfer(0xFF);
    SPI1_CS_Deassert();
    return ((uint32_t)mfr << 16) | ((uint32_t)type << 8) | cap;
    /* W25Q32: returns 0xEF4016 */
}

void W25_Read(uint32_t addr, uint8_t* buf, uint32_t len) {
    CS_LOW();
    SPI1_Transfer(W25_CMD_READ);
    SPI1_Transfer((addr >> 16) & 0xFF);  /* Address byte 2 (MSB) */
    SPI1_Transfer((addr >>  8) & 0xFF);  /* Address byte 1 */
    SPI1_Transfer((addr      ) & 0xFF);  /* Address byte 0 (LSB) */
    SPI1_Read(buf, len);
    SPI1_CS_Deassert();
}

void W25_WritePage(uint32_t addr, const uint8_t* data, uint32_t len) {
    /* Write Enable first — required before any write/erase */
    CS_LOW();
    SPI1_Transfer(W25_CMD_WRITE_EN);
    SPI1_CS_Deassert();

    CS_LOW();
    SPI1_Transfer(W25_CMD_PAGE_PROG);
    SPI1_Transfer((addr >> 16) & 0xFF);
    SPI1_Transfer((addr >>  8) & 0xFF);
    SPI1_Transfer((addr      ) & 0xFF);
    SPI1_Write(data, len);
    SPI1_CS_Deassert();

    W25_WaitBusy();  /* Page program takes ~0.7ms */
}

void W25_SectorErase(uint32_t addr) {
    CS_LOW();
    SPI1_Transfer(W25_CMD_WRITE_EN);
    SPI1_CS_Deassert();

    CS_LOW();
    SPI1_Transfer(W25_CMD_SECTOR_ERASE);
    SPI1_Transfer((addr >> 16) & 0xFF);
    SPI1_Transfer((addr >>  8) & 0xFF);
    SPI1_Transfer((addr      ) & 0xFF);
    SPI1_CS_Deassert();

    W25_WaitBusy();  /* Sector erase takes ~50ms */
}

int main(void) {
    SystemClock_Config();
    SysTick_Init(168000000);
    USART1_Init(84000000, 115200);
    SPI1_Init();

    uint32_t id = W25_ReadID();
    printf("Flash ID: 0x%06lX\r\n", id);  /* Should be 0xEF4016 for W25Q32 */

    uint8_t buf[16];
    W25_Read(0x000000, buf, 16);            /* Read first 16 bytes */
    while(1) {}
}
SUMMARY

Summary

SPI_CR1

CPOL/CPHA select mode. BR[2:0] set speed. MSTR=1 for master. SPE=1 to enable (last).

SPI_SR

TXE(1): can write. RXNE(0): data ready. BSY(7): transfer in progress. Wait BSY=0 before raising CS.

SPI Transfer

Wait TXE, write DR, wait RXNE, read DR. Always full-duplex — master clocks in and out simultaneously.

NSS Software

SSM=1, SSI=1 in CR1. Control CS manually via GPIO BSRR for multiple slaves.

Mode selection

Mode 0 (CPOL=0,CPHA=0): most sensors. Mode 3 (CPOL=1,CPHA=1): LIS3DSH accelerometer.

Speed

BR=000: APB_clk/2. SPI1 max = 42 MHz. SPI2/3 max = 21 MHz (APB1-based).

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *