STM32实战:基于STM32F103的SPI通信驱动W25Qxx Flash存储

文章目录

    • 一、前言
      • [1.1 技术背景](#1.1 技术背景)
      • [1.2 本文目标](#1.2 本文目标)
      • [1.3 技术栈](#1.3 技术栈)
    • 二、环境准备
      • [2.1 硬件准备](#2.1 硬件准备)
      • [2.2 W25Q128存储结构](#2.2 W25Q128存储结构)
    • 三、SPI通信协议
      • [3.1 SPI基本原理](#3.1 SPI基本原理)
      • [3.2 SPI时序图](#3.2 SPI时序图)
    • 四、W25Q128指令集
      • [4.1 常用指令](#4.1 常用指令)
      • [4.2 状态寄存器](#4.2 状态寄存器)
    • 五、驱动程序实现
      • [5.1 SPI外设初始化](#5.1 SPI外设初始化)
      • [5.2 主程序](#5.2 主程序)
    • 六、测试与验证
      • [6.1 硬件测试](#6.1 硬件测试)
      • [6.2 软件测试](#6.2 软件测试)
    • 七、故障排查
      • [7.1 常见问题](#7.1 常见问题)
      • [7.2 调试技巧](#7.2 调试技巧)
    • 八、总结
      • [8.1 核心知识点](#8.1 核心知识点)
      • [8.2 扩展应用](#8.2 扩展应用)

一、前言

1.1 技术背景

在嵌入式系统中,数据存储是一个核心需求。STM32F103内部Flash容量有限(64KB或128KB),且擦写次数受限(约1万次),不适合频繁写入的场景。外部SPI Flash存储器成为扩展存储容量的理想选择。

W25Q系列是Winbond(华邦)公司生产的SPI接口NOR Flash,具有以下特点:

  • 大容量:从512Kb到512Mb多种规格
  • 高速度:标准SPI模式80MHz,Dual/Quad SPI模式可达104MHz
  • 低功耗:待机电流仅1μA
  • 长寿命:擦写次数10万次,数据保持20年
  • 小封装:SOP8、WSON8、USON8等多种封装

1.2 本文目标

通过本教程,你将学会:

  • SPI通信协议原理和时序
  • STM32F103的SPI外设配置
  • W25Q128 Flash的读写操作
  • Flash存储器的扇区擦除和页编程
  • 文件系统和数据管理

适合读者:

  • 需要扩展存储容量的嵌入式开发者
  • 学习SPI通信协议的初学者
  • 需要实现数据记录功能的开发者

1.3 技术栈

组件 型号/版本 说明
主控芯片 STM32F103C8T6 ARM Cortex-M3
Flash芯片 W25Q128JV 128Mbit(16MB) SPI Flash
开发环境 Keil MDK 5 嵌入式开发IDE
通信协议 SPI 串行外设接口

二、环境准备

2.1 硬件准备

核心硬件清单:

  • STM32F103C8T6最小系统板 × 1
  • W25Q128JV Flash模块 × 1
  • USB转TTL模块 × 1
  • 杜邦线若干

硬件连接图:

复制代码
STM32F103C8T6          W25Q128JV
┌─────────────┐        ┌─────────────┐
│             │        │             │
│  PA5(SCK) ──┼────────┼────> CLK    │
│  PA6(MISO) <┼────────┼────  DO     │
│  PA7(MOSI) ─┼────────┼────> DI     │
│  PA4(CS)  ──┼────────┼────> /CS    │
│  3.3V     ──┼────────┼────  VCC    │
│  GND      ──┼────────┼────  GND    │
│             │        │             │
└─────────────┘        └─────────────┘

W25Q128引脚定义:

引脚 名称 类型 功能
1 /CS 输入 片选,低电平有效
2 DO 输出 数据输出(MISO)
3 /WP 输入 写保护,低电平有效
4 GND 电源
5 DI 输入 数据输入(MOSI)
6 CLK 输入 串行时钟
7 /HOLD 输入 暂停,低电平有效
8 VCC 电源 2.7-3.6V

注意: /WP和/HOLD引脚如果不使用,应接VCC(高电平)。

2.2 W25Q128存储结构

容量划分:

复制代码
总容量:128Mbit = 16MB = 16,777,216字节

┌─────────────────────────────────────────────────────────────┐
│                      W25Q128 存储结构                        │
├─────────────────────────────────────────────────────────────┤
│  块(Block)    │  64个块 × 64KB = 4096KB (4MB)              │
│  扇区(Sector) │  每个块16个扇区,共1024个扇区 × 4KB         │
│  页(Page)     │  每个扇区16页,共16384页 × 256字节          │
│  字节(Byte)   │  每页256字节,共4,194,304字节               │
└─────────────────────────────────────────────────────────────┘

地址范围:0x000000 ~ 0xFFFFFF (24位地址)

重要特性:

  • 最小擦除单位:扇区(4KB)
  • 最小编程单位:页(256字节)
  • 页内地址连续递增可跨页写入
  • 擦除后所有位变为1(0xFF)
  • 编程只能将1变为0

三、SPI通信协议

3.1 SPI基本原理

SPI(Serial Peripheral Interface)是一种同步串行通信协议,特点:

  • 全双工通信:同时收发数据
  • 主从架构:一主多从
  • 4线接口:SCK、MOSI、MISO、CS
  • 时钟极性和相位可配置

SPI模式:

模式 CPOL CPHA 时钟极性 采样边沿
0 0 0 空闲低 上升沿采样
1 0 1 空闲低 下降沿采样
2 1 0 空闲高 下降沿采样
3 1 1 空闲高 上升沿采样

W25Q128支持模式0和模式3,我们使用模式0。

3.2 SPI时序图

复制代码
标准SPI模式0时序:

        ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐
SCK  ───┘  └──┘  └──┘  └──┘  └──┘  └──┘  └──┘  └──┘  └──
        ↑     ↑     ↑     ↑     ↑     ↑     ↑     ↑
      采样   采样   采样   采样   采样   采样   采样   采样
      (上升沿)

MOSI ───┐     ┌─────┐           ┌─────────┐
        └─────┘     └───────────┘         └──────────────
        D7    D6    D5          D4        D3...

MISO ─────────┐     ┌─────┐           ┌─────────┐
              └─────┘     └───────────┘         └────────
              D7    D6    D5          D4        D3...

CS   ─┐                                                  ┌──
      └──────────────────────────────────────────────────┘
      选中                                               释放

四、W25Q128指令集

4.1 常用指令

指令 代码 功能 地址字节 数据字节
WREN 0x06 写使能 0 0
WRDI 0x04 写禁止 0 0
RDID 0x9F 读ID 0 3
RDSR 0x05 读状态寄存器 0 1-∞
READ 0x03 读数据 3 1-∞
PP 0x02 页编程 3 1-256
SE 0x20 扇区擦除(4KB) 3 0
BE32 0x52 块擦除(32KB) 3 0
BE64 0xD8 块擦除(64KB) 3 0
CE 0xC7 全片擦除 0 0

4.2 状态寄存器

复制代码
状态寄存器1 (S1):
┌────┬────┬────┬────┬────┬────┬────┬────┐
│ SRP│ SEC│ TB │ BP2│ BP1│ BP0│ WEL│ BUSY│
└────┴────┴────┴────┴────┴────┴────┴────┘
  7    6    5    4    3    2    1    0

BUSY (Bit0): 忙标志,1=正在擦除或编程
WEL (Bit1): 写使能锁存,1=写使能
BP0-BP2 (Bit2-4): 块保护位
TB (Bit5): 顶部/底部保护
SEC (Bit6): 扇区保护
SRP (Bit7): 状态寄存器保护

五、驱动程序实现

5.1 SPI外设初始化

📄 创建文件:spi_flash.c

c 复制代码
/**
 * @file spi_flash.c
 * @brief W25Q128 SPI Flash驱动程序
 * 
 * 功能:
 * - SPI1初始化(模式0,18MHz)
 * - W25Q128基本操作(读ID、读状态、写使能)
 * - 数据读写(页编程、扇区擦除)
 * - 高级功能(全片擦除、写保护)
 */

#include "spi_flash.h"
#include "stm32f10x.h"
#include <string.h>

/* 片选引脚定义 */
#define FLASH_CS_PORT   GPIOA
#define FLASH_CS_PIN    GPIO_Pin_4
#define FLASH_CS_LOW()  GPIO_ResetBits(FLASH_CS_PORT, FLASH_CS_PIN)
#define FLASH_CS_HIGH() GPIO_SetBits(FLASH_CS_PORT, FLASH_CS_PIN)

/* 超时时间定义 */
#define FLASH_TIMEOUT_MS    1000
#define SECTOR_ERASE_TIMEOUT_MS  400
#define CHIP_ERASE_TIMEOUT_MS    10000

/**
 * @brief 初始化SPI1和GPIO
 * 
 * 配置参数:
 * - SPI模式:全双工主模式
 * - 时钟极性:空闲低电平(CPOL=0)
 * - 时钟相位:第一个边沿采样(CPHA=0)
 * - 波特率:18MHz(72MHz/4)
 * - 数据格式:8位,MSB先
 */
void SPI_Flash_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    SPI_InitTypeDef SPI_InitStruct;
    
    /* 1. 使能时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE);
    
    /* 2. 配置GPIO */
    // PA5 - SCK (复用推挽)
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // PA6 - MISO (浮空输入)
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // PA7 - MOSI (复用推挽)
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // PA4 - CS (推挽输出)
    GPIO_InitStruct.GPIO_Pin = FLASH_CS_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(FLASH_CS_PORT, &GPIO_InitStruct);
    
    // 初始状态:CS高电平(未选中)
    FLASH_CS_HIGH();
    
    /* 3. 配置SPI1 */
    SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
    SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;      // 时钟极性:空闲低
    SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;    // 时钟相位:第一个边沿
    SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;       // 软件NSS
    SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;  // 18MHz
    SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStruct.SPI_CRCPolynomial = 7;
    SPI_Init(SPI1, &SPI_InitStruct);
    
    /* 4. 使能SPI */
    SPI_Cmd(SPI1, ENABLE);
}

/**
 * @brief SPI发送/接收一个字节
 * @param byte 要发送的字节
 * @return 接收到的字节
 */
static uint8_t SPI_SendByte(uint8_t byte)
{
    // 等待发送缓冲区空
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
    
    // 发送数据
    SPI_I2S_SendData(SPI1, byte);
    
    // 等待接收缓冲区非空
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
    
    // 返回接收到的数据
    return SPI_I2S_ReceiveData(SPI1);
}

/**
 * @brief 发送指令(无地址无数据)
 * @param cmd 指令代码
 */
static void Flash_SendCmd(uint8_t cmd)
{
    FLASH_CS_LOW();
    SPI_SendByte(cmd);
    FLASH_CS_HIGH();
}

/**
 * @brief 读取Flash ID
 * @return 设备ID(0xEF4018表示W25Q128)
 */
uint32_t Flash_ReadID(void)
{
    uint32_t id = 0;
    
    FLASH_CS_LOW();
    
    // 发送读ID指令
    SPI_SendByte(FLASH_CMD_RDID);
    
    // 读取3字节ID
    id |= (uint32_t)SPI_SendByte(0xFF) << 16;  // Manufacturer ID
    id |= (uint32_t)SPI_SendByte(0xFF) << 8;   // Memory Type
    id |= (uint32_t)SPI_SendByte(0xFF);        // Capacity
    
    FLASH_CS_HIGH();
    
    return id;
}

/**
 * @brief 读取状态寄存器
 * @return 状态寄存器值
 */
uint8_t Flash_ReadStatus(void)
{
    uint8_t status;
    
    FLASH_CS_LOW();
    SPI_SendByte(FLASH_CMD_RDSR);
    status = SPI_SendByte(0xFF);
    FLASH_CS_HIGH();
    
    return status;
}

/**
 * @brief 检查是否忙
 * @return 1=忙,0=空闲
 */
uint8_t Flash_IsBusy(void)
{
    return (Flash_ReadStatus() & FLASH_SR_BUSY) ? 1 : 0;
}

/**
 * @brief 等待操作完成
 * @param timeout_ms 超时时间(毫秒)
 * @return 0=成功,1=超时
 */
uint8_t Flash_WaitReady(uint32_t timeout_ms)
{
    uint32_t start_time = GetSysTick();
    
    while (Flash_IsBusy()) {
        if ((GetSysTick() - start_time) > timeout_ms) {
            return 1;  // 超时
        }
    }
    
    return 0;
}

/**
 * @brief 写使能
 */
void Flash_WriteEnable(void)
{
    Flash_SendCmd(FLASH_CMD_WREN);
}

/**
 * @brief 写禁止
 */
void Flash_WriteDisable(void)
{
    Flash_SendCmd(FLASH_CMD_WRDI);
}

/**
 * @brief 读取数据
 * @param addr 起始地址(24位)
 * @param buffer 数据缓冲区
 * @param len 读取长度
 */
void Flash_ReadData(uint32_t addr, uint8_t *buffer, uint32_t len)
{
    FLASH_CS_LOW();
    
    // 发送读指令
    SPI_SendByte(FLASH_CMD_READ);
    
    // 发送24位地址
    SPI_SendByte((addr >> 16) & 0xFF);
    SPI_SendByte((addr >> 8) & 0xFF);
    SPI_SendByte(addr & 0xFF);
    
    // 读取数据
    for (uint32_t i = 0; i < len; i++) {
        buffer[i] = SPI_SendByte(0xFF);
    }
    
    FLASH_CS_HIGH();
}

/**
 * @brief 页编程(256字节)
 * @param addr 页起始地址
 * @param data 数据指针
 * @param len 数据长度(最大256)
 * @return 0=成功,1=失败
 */
uint8_t Flash_PageWrite(uint32_t addr, const uint8_t *data, uint16_t len)
{
    // 检查参数
    if (len == 0 || len > FLASH_PAGE_SIZE) {
        return 1;
    }
    
    // 检查地址是否页对齐
    if ((addr & (FLASH_PAGE_SIZE - 1)) != 0) {
        return 1;
    }
    
    // 等待空闲
    if (Flash_WaitReady(FLASH_TIMEOUT_MS)) {
        return 1;
    }
    
    // 写使能
    Flash_WriteEnable();
    
    FLASH_CS_LOW();
    
    // 发送页编程指令
    SPI_SendByte(FLASH_CMD_PP);
    
    // 发送地址
    SPI_SendByte((addr >> 16) & 0xFF);
    SPI_SendByte((addr >> 8) & 0xFF);
    SPI_SendByte(addr & 0xFF);
    
    // 发送数据
    for (uint16_t i = 0; i < len; i++) {
        SPI_SendByte(data[i]);
    }
    
    FLASH_CS_HIGH();
    
    // 等待编程完成
    if (Flash_WaitReady(FLASH_TIMEOUT_MS)) {
        return 1;
    }
    
    return 0;
}

/**
 * @brief 扇区擦除(4KB)
 * @param addr 扇区内的任意地址
 * @return 0=成功,1=失败
 */
uint8_t Flash_SectorErase(uint32_t addr)
{
    // 等待空闲
    if (Flash_WaitReady(FLASH_TIMEOUT_MS)) {
        return 1;
    }
    
    // 写使能
    Flash_WriteEnable();
    
    FLASH_CS_LOW();
    
    // 发送扇区擦除指令
    SPI_SendByte(FLASH_CMD_SE);
    
    // 发送地址(扇区地址,低12位会被忽略)
    SPI_SendByte((addr >> 16) & 0xFF);
    SPI_SendByte((addr >> 8) & 0xFF);
    SPI_SendByte(addr & 0xFF);
    
    FLASH_CS_HIGH();
    
    // 等待擦除完成(扇区擦除约45-400ms)
    if (Flash_WaitReady(SECTOR_ERASE_TIMEOUT_MS)) {
        return 1;
    }
    
    return 0;
}

/**
 * @brief 块擦除(64KB)
 * @param addr 块内的任意地址
 * @return 0=成功,1=失败
 */
uint8_t Flash_BlockErase(uint32_t addr)
{
    // 等待空闲
    if (Flash_WaitReady(FLASH_TIMEOUT_MS)) {
        return 1;
    }
    
    // 写使能
    Flash_WriteEnable();
    
    FLASH_CS_LOW();
    
    // 发送块擦除指令
    SPI_SendByte(FLASH_CMD_BE64);
    
    // 发送地址(块地址,低16位会被忽略)
    SPI_SendByte((addr >> 16) & 0xFF);
    SPI_SendByte((addr >> 8) & 0xFF);
    SPI_SendByte(addr & 0xFF);
    
    FLASH_CS_HIGH();
    
    // 等待擦除完成(块擦除约150-1000ms)
    if (Flash_WaitReady(2000)) {
        return 1;
    }
    
    return 0;
}

/**
 * @brief 全片擦除
 * @return 0=成功,1=失败
 */
uint8_t Flash_ChipErase(void)
{
    // 等待空闲
    if (Flash_WaitReady(FLASH_TIMEOUT_MS)) {
        return 1;
    }
    
    // 写使能
    Flash_WriteEnable();
    
    // 发送全片擦除指令
    Flash_SendCmd(FLASH_CMD_CE);
    
    // 等待擦除完成(全片擦除约20-100秒)
    if (Flash_WaitReady(CHIP_ERASE_TIMEOUT_MS)) {
        return 1;
    }
    
    return 0;
}

/**
 * @brief 无校验写入(自动处理页边界)
 * @param addr 起始地址
 * @param data 数据指针
 * @param len 数据长度
 * @return 0=成功,1=失败
 */
uint8_t Flash_WriteData(uint32_t addr, const uint8_t *data, uint32_t len)
{
    uint32_t page_addr;
    uint16_t page_offset;
    uint16_t write_len;
    uint32_t remaining = len;
    uint32_t data_index = 0;
    
    while (remaining > 0) {
        // 计算当前页地址和偏移
        page_addr = addr & ~(FLASH_PAGE_SIZE - 1);
        page_offset = addr & (FLASH_PAGE_SIZE - 1);
        
        // 计算本次可写入的长度
        write_len = FLASH_PAGE_SIZE - page_offset;
        if (write_len > remaining) {
            write_len = remaining;
        }
        
        // 如果写入跨越页边界,只写到页尾
        if (page_offset + write_len > FLASH_PAGE_SIZE) {
            write_len = FLASH_PAGE_SIZE - page_offset;
        }
        
        // 页编程
        if (Flash_PageWrite(page_addr, data + data_index, write_len) != 0) {
            return 1;
        }
        
        addr += write_len;
        data_index += write_len;
        remaining -= write_len;
    }
    
    return 0;
}

/**
 * @brief 带擦除的写入(自动擦除扇区)
 * @param addr 起始地址
 * @param data 数据指针
 * @param len 数据长度
 * @return 0=成功,1=失败
 */
uint8_t Flash_WriteDataWithErase(uint32_t addr, const uint8_t *data, uint32_t len)
{
    uint32_t sector_addr;
    uint32_t end_addr = addr + len;
    uint8_t sector_buffer[FLASH_SECTOR_SIZE];
    
    // 处理起始地址未对齐的情况
    if ((addr & (FLASH_SECTOR_SIZE - 1)) != 0) {
        sector_addr = addr & ~(FLASH_SECTOR_SIZE - 1);
        uint32_t offset = addr - sector_addr;
        uint32_t write_len = FLASH_SECTOR_SIZE - offset;
        
        if (write_len > len) {
            write_len = len;
        }
        
        // 读取原扇区数据
        Flash_ReadData(sector_addr, sector_buffer, FLASH_SECTOR_SIZE);
        
        // 修改数据
        memcpy(sector_buffer + offset, data, write_len);
        
        // 擦除并写入
        if (Flash_SectorErase(sector_addr) != 0) {
            return 1;
        }
        
        // 逐页写入
        for (uint16_t i = 0; i < FLASH_SECTOR_SIZE; i += FLASH_PAGE_SIZE) {
            if (Flash_PageWrite(sector_addr + i, sector_buffer + i, FLASH_PAGE_SIZE) != 0) {
                return 1;
            }
        }
        
        addr += write_len;
        data += write_len;
        len -= write_len;
    }
    
    // 处理完整的扇区
    while (len >= FLASH_SECTOR_SIZE) {
        sector_addr = addr;
        
        // 擦除扇区
        if (Flash_SectorErase(sector_addr) != 0) {
            return 1;
        }
        
        // 写入数据
        for (uint16_t i = 0; i < FLASH_SECTOR_SIZE; i += FLASH_PAGE_SIZE) {
            if (Flash_PageWrite(sector_addr + i, data + i, FLASH_PAGE_SIZE) != 0) {
                return 1;
            }
        }
        
        addr += FLASH_SECTOR_SIZE;
        data += FLASH_SECTOR_SIZE;
        len -= FLASH_SECTOR_SIZE;
    }
    
    // 处理剩余数据
    if (len > 0) {
        sector_addr = addr;
        
        // 读取原扇区数据
        Flash_ReadData(sector_addr, sector_buffer, FLASH_SECTOR_SIZE);
        
        // 修改数据
        memcpy(sector_buffer, data, len);
        
        // 擦除并写入
        if (Flash_SectorErase(sector_addr) != 0) {
            return 1;
        }
        
        for (uint16_t i = 0; i < FLASH_SECTOR_SIZE; i += FLASH_PAGE_SIZE) {
            if (Flash_PageWrite(sector_addr + i, sector_buffer + i, FLASH_PAGE_SIZE) != 0) {
                return 1;
            }
        }
    }
    
    return 0;
}

📄 创建文件:spi_flash.h

c 复制代码
/**
 * @file spi_flash.h
 * @brief W25Q128 Flash驱动头文件
 */

#ifndef __SPI_FLASH_H
#define __SPI_FLASH_H

#include <stdint.h>

/* Flash容量定义 */
#define FLASH_PAGE_SIZE         256         // 页大小
#define FLASH_SECTOR_SIZE       4096        // 扇区大小
#define FLASH_BLOCK_SIZE        65536       // 块大小
#define FLASH_CHIP_SIZE         0x1000000   // 16MB

/* 指令定义 */
#define FLASH_CMD_WREN          0x06        // 写使能
#define FLASH_CMD_WRDI          0x04        // 写禁止
#define FLASH_CMD_RDID          0x9F        // 读ID
#define FLASH_CMD_RDSR          0x05        // 读状态寄存器
#define FLASH_CMD_READ          0x03        // 读数据
#define FLASH_CMD_PP            0x02        // 页编程
#define FLASH_CMD_SE            0x20        // 扇区擦除
#define FLASH_CMD_BE32          0x52        // 32KB块擦除
#define FLASH_CMD_BE64          0xD8        // 64KB块擦除
#define FLASH_CMD_CE            0xC7        // 全片擦除

/* 状态寄存器位 */
#define FLASH_SR_BUSY           0x01        // 忙标志
#define FLASH_SR_WEL            0x02        // 写使能锁存

/* 函数声明 */
void SPI_Flash_Init(void);
uint32_t Flash_ReadID(void);
uint8_t Flash_ReadStatus(void);
uint8_t Flash_IsBusy(void);
uint8_t Flash_WaitReady(uint32_t timeout_ms);
void Flash_WriteEnable(void);
void Flash_WriteDisable(void);
void Flash_ReadData(uint32_t addr, uint8_t *buffer, uint32_t len);
uint8_t Flash_PageWrite(uint32_t addr, const uint8_t *data, uint16_t len);
uint8_t Flash_SectorErase(uint32_t addr);
uint8_t Flash_BlockErase(uint32_t addr);
uint8_t Flash_ChipErase(void);
uint8_t Flash_WriteData(uint32_t addr, const uint8_t *data, uint32_t len);
uint8_t Flash_WriteDataWithErase(uint32_t addr, const uint8_t *data, uint32_t len);

#endif /* __SPI_FLASH_H */

5.2 主程序

📄 创建文件:main.c

c 复制代码
/**
 * @file main.c
 * @brief SPI Flash测试主程序
 */

#include "stm32f10x.h"
#include "spi_flash.h"
#include "usart1.h"
#include <stdio.h>
#include <string.h>

static volatile uint32_t sys_tick = 0;

void SysTick_Handler(void)
{
    sys_tick++;
}

uint32_t GetSysTick(void)
{
    return sys_tick;
}

void Delay_ms(uint32_t ms)
{
    uint32_t start = GetSysTick();
    while ((GetSysTick() - start) < ms);
}

/**
 * @brief 测试Flash ID读取
 */
void Test_ReadID(void)
{
    uint32_t id = Flash_ReadID();
    
    printf("Flash ID: 0x%06X\r\n", id);
    
    // 解析ID
    uint8_t manufacturer = (id >> 16) & 0xFF;
    uint8_t memory_type = (id >> 8) & 0xFF;
    uint8_t capacity = id & 0xFF;
    
    printf("Manufacturer: 0x%02X ", manufacturer);
    if (manufacturer == 0xEF) {
        printf("(Winbond)\r\n");
    } else {
        printf("(Unknown)\r\n");
    }
    
    printf("Memory Type: 0x%02X\r\n", memory_type);
    printf("Capacity: 0x%02X ", capacity);
    
    // 计算容量
    uint32_t size_mb = 1 << (capacity - 0x14);  // 0x18 = 128Mbit = 16MB
    printf("(%lu MB)\r\n", size_mb);
}

/**
 * @brief 测试数据读写
 */
void Test_ReadWrite(void)
{
    uint32_t test_addr = 0x000000;
    uint8_t write_data[256];
    uint8_t read_data[256];
    uint8_t error = 0;
    
    printf("\r\n=== Flash读写测试 ===\r\n");
    
    // 准备测试数据
    for (uint16_t i = 0; i < 256; i++) {
        write_data[i] = i;
    }
    
    // 擦除扇区
    printf("擦除扇区...");
    if (Flash_SectorErase(test_addr) == 0) {
        printf("OK\r\n");
    } else {
        printf("FAILED\r\n");
        return;
    }
    
    // 写入数据
    printf("写入数据...");
    if (Flash_PageWrite(test_addr, write_data, 256) == 0) {
        printf("OK\r\n");
    } else {
        printf("FAILED\r\n");
        return;
    }
    
    // 读取数据
    printf("读取数据...");
    Flash_ReadData(test_addr, read_data, 256);
    printf("OK\r\n");
    
    // 校验数据
    printf("校验数据...");
    for (uint16_t i = 0; i < 256; i++) {
        if (write_data[i] != read_data[i]) {
            error++;
            printf("Error at %d: W=0x%02X, R=0x%02X\r\n", i, write_data[i], read_data[i]);
        }
    }
    
    if (error == 0) {
        printf("PASS\r\n");
    } else {
        printf("FAILED (%d errors)\r\n", error);
    }
}

/**
 * @brief 测试大数据量写入
 */
void Test_BigDataWrite(void)
{
    uint32_t test_addr = 0x001000;  // 从4KB处开始
    uint8_t buffer[4096];
    uint32_t errors = 0;
    
    printf("\r\n=== 大数据量写入测试 ===\r\n");
    
    // 准备数据
    for (uint16_t i = 0; i < 4096; i++) {
        buffer[i] = i & 0xFF;
    }
    
    // 擦除扇区
    printf("擦除扇区...");
    if (Flash_SectorErase(test_addr) != 0) {
        printf("FAILED\r\n");
        return;
    }
    printf("OK\r\n");
    
    // 写入数据(使用自动分页函数)
    printf("写入4KB数据...");
    uint32_t start_time = GetSysTick();
    
    if (Flash_WriteData(test_addr, buffer, 4096) != 0) {
        printf("FAILED\r\n");
        return;
    }
    
    uint32_t write_time = GetSysTick() - start_time;
    printf("OK (%lu ms)\r\n", write_time);
    
    // 读取并校验
    printf("读取并校验...");
    uint8_t read_buffer[4096];
    start_time = GetSysTick();
    Flash_ReadData(test_addr, read_buffer, 4096);
    uint32_t read_time = GetSysTick() - start_time;
    
    for (uint16_t i = 0; i < 4096; i++) {
        if (buffer[i] != read_buffer[i]) {
            errors++;
        }
    }
    
    if (errors == 0) {
        printf("PASS (read: %lu ms)\r\n", read_time);
        printf("写入速度: %.2f KB/s\r\n", 4096.0 / write_time);
        printf("读取速度: %.2f KB/s\r\n", 4096.0 / read_time);
    } else {
        printf("FAILED (%lu errors)\r\n", errors);
    }
}

/**
 * @brief 显示菜单
 */
void ShowMenu(void)
{
    printf("\r\n");
    printf("========== SPI Flash 测试菜单 ==========\r\n");
    printf("1. 读取Flash ID\r\n");
    printf("2. 读取状态寄存器\r\n");
    printf("3. 基本读写测试\r\n");
    printf("4. 大数据量测试\r\n");
    printf("5. 全片擦除\r\n");
    printf("6. 读取指定地址数据\r\n");
    printf("7. 写入指定地址数据\r\n");
    printf("0. 清屏\r\n");
    printf("========================================\r\n");
    printf("请输入选项: ");
}

int main(void)
{
    char cmd;
    
    // 初始化
    SystemInit();
    SysTick_Config(SystemCoreClock / 1000);
    USART1_Init();
    SPI_Flash_Init();
    
    printf("\r\n");
    printf("====================================\r\n");
    printf("    W25Q128 SPI Flash 测试程序\r\n");
    printf("====================================\r\n");
    
    ShowMenu();
    
    while (1) {
        if (USART1_GetRxDataLength() > 0) {
            if (USART1_ReadData((uint8_t *)&cmd, 1) > 0) {
                switch (cmd) {
                    case '1':
                        Test_ReadID();
                        break;
                    case '2':
                        printf("Status: 0x%02X\r\n", Flash_ReadStatus());
                        break;
                    case '3':
                        Test_ReadWrite();
                        break;
                    case '4':
                        Test_BigDataWrite();
                        break;
                    case '5':
                        printf("全片擦除中,请等待...\r\n");
                        if (Flash_ChipErase() == 0) {
                            printf("全片擦除完成\r\n");
                        } else {
                            printf("全片擦除失败\r\n");
                        }
                        break;
                    case '6':
                        // 读取指定地址(简化处理)
                        {
                            uint8_t buf[16];
                            Flash_ReadData(0, buf, 16);
                            printf("Addr 0x000000: ");
                            for (int i = 0; i < 16; i++) {
                                printf("%02X ", buf[i]);
                            }
                            printf("\r\n");
                        }
                        break;
                    case '7':
                        // 写入测试数据到地址0
                        {
                            uint8_t test[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
                            Flash_SectorErase(0);
                            Flash_PageWrite(0, test, 8);
                            printf("写入完成\r\n");
                        }
                        break;
                    case '0':
                        for (int i = 0; i < 50; i++) printf("\r\n");
                        break;
                    default:
                        printf("无效选项\r\n");
                        break;
                }
                ShowMenu();
            }
        }
    }
}

六、测试与验证

6.1 硬件测试

连接检查:

  • 测量VCC电压:3.3V
  • 检查SPI信号:SCK、MOSI、MISO、CS
  • 使用示波器观察SPI时序

6.2 软件测试

测试项目:

  1. ID读取测试
  2. 单字节读写测试
  3. 页编程测试(256字节)
  4. 扇区擦除测试(4KB)
  5. 跨页写入测试
  6. 大数据量读写测试
  7. 速度测试

预期结果:

  • 写入速度:约50-100KB/s
  • 读取速度:约1-2MB/s
  • 扇区擦除:约45-400ms

七、故障排查

7.1 常见问题

问题 原因 解决方案
读ID失败 接线错误 检查MOSI/MISO是否接反
写入失败 未擦除 Flash写入前必须先擦除
数据错误 时序问题 降低SPI速度
全片擦除慢 正常现象 全片擦除需要20-100秒

7.2 调试技巧

  • 使用示波器观察SPI波形
  • 先测试读ID,确认基本通信正常
  • 逐步测试:单字节→页→扇区

八、总结

8.1 核心知识点

  1. SPI通信协议和时序
  2. W25Q128存储结构和指令集
  3. Flash擦写特性(先擦后写)
  4. STM32 SPI外设配置

8.2 扩展应用

  • 实现FATFS文件系统
  • 存储日志数据
  • 固件升级(IAP)
  • 参数存储
相关推荐
iCxhust2 小时前
【无标题】8086/8088裸机对于学习微机原理的重要意义
汇编·单片机·嵌入式硬件·嵌入式·微机原理
asjodnobfy2 小时前
啥是电压应力
嵌入式硬件·硬件工程
iCxhust2 小时前
led_pattern = (led_pattern << 1) | (led_pattern >> 7)执行顺序
stm32·单片机·嵌入式硬件·51单片机·微机原理
Deitymoon2 小时前
STM32——串口通信发送字符串
stm32·单片机·嵌入式硬件
这波不该贪内存的11 小时前
裸机开发VS单片机:架构与实战对比
单片机
山羊硬件Time11 小时前
自动化管理Linux的好工具:shell script
linux·嵌入式硬件·硬件工程师·基带工程·硬件开发
神一样的老师11 小时前
【兆易创新GD32VW553开发板试用】红外遥控接入天气时钟实战
驱动开发·单片机·嵌入式硬件
asjodnobfy12 小时前
Π型滤波电路
嵌入式硬件·硬件工程
多看多敲多思考14 小时前
华润微CS32ME10 MCU使用教程(2)---CS32ME10之UART串口模块使用
stm32·单片机·嵌入式硬件·mcu