SPI Communication Technology: Practical Applications of MCU and FLASH

1. Introduction

1.1 Document Background

The Serial Peripheral Interface (SPI) is a full-duplex synchronous serial communication interface developed by Motorola, widely used for data transmission between microcontrollers and peripheral devices. This document takes the communication between MCU and FLASH memory as an example, elaborating on the hardware design, software implementation, and practical applications of SPI communication.

1.2 Application Scenarios

SPI communication plays an important role in embedded systems, mainly applied in:

  • Communication between microcontrollers and memory (FLASH, EEPROM)

  • Sensor data acquisition (temperature, pressure, acceleration sensors)

  • Display driver (LCD, OLED)

  • ADC/DAC converter control

  • Real-time clock (RTC) module communication

1.3 Technical Advantages

Compared to other communication protocols, SPI has the following advantages:

  • High-speed transmission: Clock frequencies can reach several tens of MHz

  • Full-duplex communication: Simultaneous data transmission and reception

  • Simple hardware: Requires only 4 signal lines

  • High flexibility: Supports multiple slave devices in a daisy chain

2. Basic Theory of SPI Protocol

2.1 Protocol Architecture

SPI adopts a master-slave architecture, where one master device can control multiple slave devices. Communication is based on a clock synchronization mechanism, and data transmission strictly follows the clock pulse.

SPI Communication Technology: Practical Applications of MCU and FLASH

2.2 Signal Line Definitions

SPI Communication Technology: Practical Applications of MCU and FLASH

2.3 Working Sequence

The core of SPI communication is the configuration of clock polarity (CPOL) and clock phase (CPHA):

2.3.1 Clock Polarity (CPOL)

  • CPOL=0: SCLK is low when idle

  • CPOL=1: SCLK is high when idle

2.3.2 Clock Phase (CPHA)

  • CPHA=0: Sample data on the first edge of SCLK

  • CPHA=1: Sample data on the second edge of SCLK

2.3.3 Four Working Modes

SPI Communication Technology: Practical Applications of MCU and FLASH

3. Hardware Design Solutions

3.1 System Architecture Design

The entire system consists of three core modules:MCU main controller, SPI communication interface, FLASH memory.

Core Module Description:

  • MCU Module:STM32F407 serves as the main controller, responsible for the control logic of the entire system

  • SPI Module: Four-wire communication interface (SCLK/MOSI/MISO/CS), achieving high-speed data transmission

  • FLASH Module:W25Q64 provides 8MB of non-volatile storage space

3.2 Pin Connection Scheme

3.2.1 MCU Pin Configuration (using SPI1)

SPI Communication Technology: Practical Applications of MCU and FLASH

3.2.2 FLASH Pin Connection

SPI Communication Technology: Practical Applications of MCU and FLASH

3.3 Circuit Design Points

3.3.1 Power Design

Plain TextVCC (3.3V) ──┬── 100nF ──┬── GND             │                        └── W25Q64 VCC

3.3.2 Signal Integrity Design

  • Pull-up Resistor: Add a 10kΩ pull-up resistor to the CS signal to ensure a default high level

  • Decoupling Capacitor: Add a 100nF decoupling capacitor to the power supply of the FLASH

  • Trace Impedance: Control high-speed signal lines to have a characteristic impedance of 50Ω

  • Trace Length: Match the lengths of SPI signal lines, with a maximum difference of <5mm

4. Software Architecture Design

4.1 System Software Architecture

The software architecture adopts a simple three-layer design:

Architecture Level Description:

  • Application Layer: Implements specific business functions, such as data logging, querying, etc.

  • Driver Layer: Encapsulates SPI communication and FLASH operations, providing a unified interface

  • Hardware Layer: Based on STM32 HAL library, handling low-level hardware operations

4.2 Module Design

4.2.1 SPI Driver Module Interface Design

Based on STM32 HAL library, the SPI driver module mainly includes configuration structures and basic operation interfaces:

C// SPI configuration structuretypedef struct {uint32_t baudrate;     // Baud rateuint8_t  mode;         // Operating mode (0-3)uint8_t  data_size;    // Data size (8/16)uint8_t  cs_pin;       // Chip select pin} spi_config_t;// SPI driver interfaceHAL_StatusTypeDef SPI_Init(spi_config_t* config);HAL_StatusTypeDef SPI_Transmit(uint8_t* data, uint16_t size);HAL_StatusTypeDef SPI_Receive(uint8_t* data, uint16_t size);HAL_StatusTypeDef SPI_TransmitReceive(uint8_t* tx_data, uint8_t* rx_data, uint16_t size);HAL_StatusTypeDef SPI_DeInit(void);

Interface Design Points::

  • Configuration Structure: Contains key parameters such as baud rate, operating mode, data size, and chip select pin

  • Initialization Interface: Completes the configuration of the SPI peripheral and GPIO settings

  • Data Transmission Interface: Supports sending, receiving, and bidirectional transmission

  • Error Handling Mechanism: Provides unified status return and error handling

4.2.2 FLASH Driver Module Interface Design

Designed for the characteristics of W25Q64 device:

C// FLASH device information structuretypedef struct {uint32_t sector_size;      // Sector size (4KB)uint32_t block_size;       // Block size (64KB)uint32_t total_size;       // Total capacity (8MB)uint16_t manufacturer_id;  // Manufacturer ID (0xEF)uint16_t device_id;        // Device ID (0x4017)uint8_t  unique_id[8];     // Unique identifier} w25q64_info_t;// FLASH operation interfaceHAL_StatusTypeDef W25Q64_Init(void);HAL_StatusTypeDef W25Q64_ReadID(w25q64_info_t* info);HAL_StatusTypeDef W25Q64_ReadData(uint32_t address, uint8_t* data, uint32_t size);HAL_StatusTypeDef W25Q64_WritePage(uint32_t address, uint8_t* data, uint32_t size);HAL_StatusTypeDef W25Q64_EraseSector(uint32_t address);HAL_StatusTypeDef W25Q64_EraseBlock(uint32_t address);HAL_StatusTypeDef W25Q64_EraseChip(void);HAL_StatusTypeDef W25Q64_ReadStatus(uint8_t* status);uint8_t W25Q64_IsBusy(void);

Interface Design Points::

  • Device Information Structure: Stores information such as FLASH capacity, sector size, manufacturer ID, etc.

  • Basic Operation Interface: Includes initialization, read ID, data read/write, sector erase, etc.

  • Address Management: Supports complete access to a 24-bit address space

  • Status Detection: Implements busy status detection and write protection management

5. Driver Implementation

5.1 SPI Communication Driver Implementation

The core task of the SPI driver is to enable the MCU and FLASH to “communicate“. Just like two people on a phone call need to follow communication etiquette, the communication between MCU and FLASH also needs to follow the SPI protocol.

5.1.1 SPI Initialization – Establishing Communication Connection

Imagine the MCU wants to call the FLASH, first it needs to:

1. Open the phone line (enable clock)

2. Configure the phone (set GPIO pins)

3. Agree on the communication method (configure SPI parameters)

C#include "stm32f4xx_hal.h"static SPI_HandleTypeDef hspi1;  // SPI's "phone"HAL_StatusTypeDef SPI_Init(void){// Step 1: Open the "phone line" - enable clock    __HAL_RCC_GPIOA_CLK_ENABLE();  // GPIO clock    __HAL_RCC_SPI1_CLK_ENABLE();   // SPI clock// Step 2: Connect the "phone line" - configure pins    GPIO_InitTypeDef GPIO_InitStruct = {0};// Data line configuration (SCK, MISO, MOSI)    GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7;    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;        // Alternate push-pull output    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; // High-speed mode    GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;     // Select SPI1 function    HAL_GPIO_Init(GPIOA, &amp;GPIO_InitStruct);// Chip select line configuration (CS)     GPIO_InitStruct.Pin = GPIO_PIN_4;    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;    // Normal output    GPIO_InitStruct.Pull = GPIO_PULLUP;            // Pull-up, default high level    HAL_GPIO_Init(GPIOA, &amp;GPIO_InitStruct);// Step 3: Set the "communication rules" - configure SPI parameters    hspi1.Instance = SPI1;                         // Use SPI1    hspi1.Init.Mode = SPI_MODE_MASTER;             // MCU is the master    hspi1.Init.DataSize = SPI_DATASIZE_8BIT;       // Send 8 bits of data each time    hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;     // Clock is low when idle    hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;         // Sample on the first edge    hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; // Speed: 42MHz÷4=10.5MHzreturn HAL_SPI_Init(&amp;hspi1);  // Start the "phone system"}

Key Parameter Explanation:

  • Clock Prescaler: Just like adjusting the call speed, a larger prescaler slows down the speed but makes it more stable

  • Polarity and Phase: Determine when to “speak” and when to “listen” during communication

  • Chip Select Signal: Equivalent to a phone number, determining who to call

5.1.2 Data Transmission – The Actual “Call” Process

Data transmission is like the process of making a phone call:

1. Dialing (pulling down the CS signal)

2. Talking (sending/receiving data)

3. Hanging up (pulling up the CS signal)

C// Define "dialing" and "hanging up" operations#define FLASH_CALL_START()   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET)  // Dial#define FLASH_CALL_END()     HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET)    // Hang up// Bidirectional communication - MCU both speaks and listensHAL_StatusTypeDef SPI_Chat(uint8_t* send_data, uint8_t* receive_data, uint16_t length){    HAL_StatusTypeDef status;    FLASH_CALL_START();  // Start the call// Simultaneously send and receive data (full-duplex communication)    status = HAL_SPI_TransmitReceive(&amp;hspi1, send_data, receive_data, length, 1000);    FLASH_CALL_END();    // End the callreturn status;}// Unidirectional sending - only speakingHAL_StatusTypeDef SPI_Tell(uint8_t* data, uint16_t length){    HAL_StatusTypeDef status;    FLASH_CALL_START();  // Dial    status = HAL_SPI_Transmit(&amp;hspi1, data, length, 1000);  // Send command    FLASH_CALL_END();    // Hang upreturn status;}// Unidirectional receiving - only listeningHAL_StatusTypeDef SPI_Listen(uint8_t* data, uint16_t length){    HAL_StatusTypeDef status;    FLASH_CALL_START();  // Dial    status = HAL_SPI_Receive(&amp;hspi1, data, length, 1000);   // Receive data    FLASH_CALL_END();    // Hang upreturn status;}

Communication Points:

  • CS Signal: Just like dialing and hanging up a phone, you must dial before you can talk

  • Timeout Settings: 1000ms timeout to avoid the program “hanging”

  • Full Duplex: SPI can send and receive simultaneously, just like chatting face-to-face

5.2 FLASH Memory Driver Implementation

FLASH is like a smart storage cabinet, and the MCU needs to use a specific “password” (command) to operate it.

5.2.1 FLASH “Password Book” – Command Set

Just like different remote control buttons have different functions, FLASH also has its own command set:

C// FLASH's "password book" - various operation commands#define CMD_READ_ID         0x9F  // "Who are you?" - Check ID#define CMD_READ_DATA       0x03  // "Give me the data" - Read data  #define CMD_WRITE_ENABLE    0x06  // "I want to write something" - Request write permission#define CMD_PAGE_WRITE      0x02  // "Store this data" - Write data#define CMD_SECTOR_ERASE    0x20  // "Clear this area" - Erase 4KB#define CMD_CHIP_ERASE      0xC7  // "Clear everything" - Erase the entire chip#define CMD_READ_STATUS     0x05  // "Are you busy?" - Query status// FLASH's "status light" - meaning of each bit in the status register#define STATUS_BUSY         0x01  // Busy flag: 1=working, 0=idle#define STATUS_WRITE_ENABLE 0x02  // Write enable flag: 1=can write, 0=write prohibited

Understanding Command Classification:

  • Identity Class:READ_ID – Confirm FLASH model to prevent misidentification

  • Read Class:READ_DATA – Read data from the specified address

  • Write Class:WRITE_ENABLE + PAGE_WRITE – Request permission before writing data

  • Erase Class:SECTOR_ERASE, CHIP_ERASE – Delete data (must erase before writing)

  • Status Class:READ_STATUS – Query whether FLASH is busy

5.2.2 Basic Operations – Daily “Conversations” with FLASH

Implementing basic functions to communicate with FLASH is like common phrases in daily conversations:

C// "Are you busy?" - Wait for FLASH to be freestatic HAL_StatusTypeDef Flash_WaitFree(void){    uint8_t status;    uint32_t patience = 1000;  // Be patient for 1000 millisecondsdo {// Ask FLASH: "Are you busy?"        FLASH_CALL_START();        uint8_t ask_cmd = CMD_READ_STATUS;        HAL_SPI_Transmit(&amp;hspi1, &amp;ask_cmd, 1, 100);        HAL_SPI_Receive(&amp;hspi1, &amp;status, 1, 100);        FLASH_CALL_END();// If FLASH answers "not busy" (BUSY bit is 0)if (!(status &amp; STATUS_BUSY)) {            return HAL_OK;  // Can continue operation        }        HAL_Delay(1);      // Wait 1 millisecond        patience--;    } while (patience > 0);    return HAL_TIMEOUT;    // Waited too long, give up}// "I want to write something" - Request write permission  static HAL_StatusTypeDef Flash_AskPermission(void){    uint8_t permit_cmd = CMD_WRITE_ENABLE;    FLASH_CALL_START();    HAL_StatusTypeDef result = HAL_SPI_Transmit(&amp;hspi1, &amp;permit_cmd, 1, 100);    FLASH_CALL_END();    return result;}// "Who are you?" - Read FLASH IDHAL_StatusTypeDef Flash_CheckID(void){    uint8_t ask_id_cmd = CMD_READ_ID;    uint8_t id_card[3];  // FLASH's 3-byte ID card    HAL_StatusTypeDef status;    FLASH_CALL_START();// Ask: "Who are you?"    status = HAL_SPI_Transmit(&amp;hspi1, &amp;ask_id_cmd, 1, 100);    if (status != HAL_OK) {        FLASH_CALL_END();        return status;    }// Listen to FLASH's answer for identity    status = HAL_SPI_Receive(&amp;hspi1, id_card, 3, 100);    FLASH_CALL_END();    if (status == HAL_OK) {// Interpret the ID carduint8_t manufacturer = id_card[0];              // Manufacturer IDuint16_t device_type = (id_card[1] &lt;&lt; 8) | id_card[2]; // Device type// Verify if it is the expected W25Q64if (manufacturer == 0xEF &amp;&amp; device_type == 0x4017) {            printf("Found W25Q64 FLASH! Capacity: 8MB\n");            return HAL_OK;        } else {            printf("Error: This is not a W25Q64 chip\n");            return HAL_ERROR;        }    }    return status;}

Operation Points:

  • Be Patient: FLASH will be busy during erase or write operations, so be patient

  • Request Permission First: Must send the write enable command before writing

  • Verify Identity: Confirm chip model through ID to avoid misoperation

5.2.3 Data Read/Write – The Actual “Access” Operation

Data read/write is like depositing or withdrawing money at a bank, requiring accurate address information:

C// "Give me the data at address X" - Read dataHAL_StatusTypeDef Flash_ReadData(uint32_t address, uint8_t* buffer, uint32_t size){    HAL_StatusTypeDef status;    uint8_t command_packet[4];  // Command packet: 1 byte command + 3 bytes address// Safety check: address cannot exceed FLASH capacityif (address + size > 0x800000) { // 8MB capacity limitprintf("Error: Read address exceeds range\n");        return HAL_ERROR;    }// Wait for FLASH to be free    status = Flash_WaitFree();    if (status != HAL_OK) return status;// Prepare "read" command packet    command_packet[0] = CMD_READ_DATA;               // Command: "Give me the data"    command_packet[1] = (address >> 16) &amp; 0xFF;     // High byte of address    command_packet[2] = (address >> 8) &amp; 0xFF;      // Middle byte of address      command_packet[3] = address &amp; 0xFF;             // Low byte of address    FLASH_CALL_START();// First, state the address    status = HAL_SPI_Transmit(&amp;hspi1, command_packet, 4, 1000);    if (status != HAL_OK) {        FLASH_CALL_END();        return status;    }// Then receive data    status = HAL_SPI_Receive(&amp;hspi1, buffer, size, 1000);    FLASH_CALL_END();    if (status == HAL_OK) {        printf("Successfully read %lu bytes from address 0x%06lX\n", size, address);    }    return status;}

// “Store this data at address X” – Write data (page programming)

CHAL_StatusTypeDef Flash_WriteData(uint32_t address, uint8_t* data, uint32_t size){    HAL_StatusTypeDef status;    uint8_t command_packet[4];  // Command packet// FLASH write rules: maximum of 256 bytes can be written at once (one page), and cannot cross page boundariesif (size > 256 || (address &amp; 0xFF) + size > 256) {        printf("Error: Writing data exceeds page boundary\n");        return HAL_ERROR;    }// Step 1: Wait for FLASH to be free    status = Flash_WaitFree();    if (status != HAL_OK) return status;// Step 2: Request write permission (a necessary courtesy)    status = Flash_AskPermission();    if (status != HAL_OK) return status;// Step 3: Prepare write command packet    command_packet[0] = CMD_PAGE_WRITE;             // Command: "I want to write data"    command_packet[1] = (address >> 16) &amp; 0xFF;    // High byte of target address    command_packet[2] = (address >> 8) &amp; 0xFF;     // Middle byte of target address    command_packet[3] = address &amp; 0xFF;            // Low byte of target address    FLASH_CALL_START();// Step 4: Send command and address    status = HAL_SPI_Transmit(&amp;hspi1, command_packet, 4, 1000);    if (status != HAL_OK) {        FLASH_CALL_END();        return status;    }// Step 5: Send actual data    status = HAL_SPI_Transmit(&amp;hspi1, data, size, 1000);    FLASH_CALL_END();// Step 6: Wait for FLASH to complete writing (writing takes time)if (status == HAL_OK) {        status = Flash_WaitFree();  // Be patient while waiting for writing to completeif (status == HAL_OK) {            printf("Successfully wrote %lu bytes to address 0x%06lX\n", size, address);        }    }    return status;}

Important Rules for FLASH Writing:

  • Erase before Write: FLASH can only change 1 to 0, so it must be erased before writing

  • Page Write: Maximum of 256 bytes can be written at once, cannot cross page boundaries

  • Write Permission: Must request write enable before each write operation

  • Be Patient: Writing takes a few milliseconds to complete

6. Conclusion

This document presents a complete implementation plan for SPI communication technology:

  • Basic Knowledge of SPI: SPI is a 4-wire communication protocol, including clock line (SCLK), data output line (MOSI), data input line (MISO), and chip select line (CS).

  • Hardware Connection: Use the STM32F407’s PA4-PA7 pins to connect to the W25Q64 FLASH chip, achieving communication between the MCU and memory.

  • Software Implementation: Configure the SPI peripheral using the HAL library, and write driver functions to implement read, write, and erase operations for FLASH.

  • Practical Applications: After mastering the content of this document, you can flexibly use the SPI interface to connect various peripheral devices in your projects.

SPI communication is simple and reliable, making it an important technology in embedded development. Through theoretical learning and practical programming, one can quickly master and apply it in real projects.

Leave a Comment