跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

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

文章目录 一、前言 1.1 技术背景 1.2 本文目标 1.3 技术栈 二、环境准备 2.1 硬件准备 2.2 W25Q128存储结构 三、SPI通信协议 3.1 SPI基本原理 3.2 SPI时序图 四、W25Q128指令集 4.1 常用指令 4.2 状态寄存器 五、驱动程序实现 5.1 SPI外设初始化 5.2 主程序 六、测试与验证 6.1 硬件测试 6.2 软件测试 七、故障排查 7.1 常见问题 7.2 调试技巧 八、总结 8.1 核心知识点 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 技术栈 组件型号/版本说明 主控芯片STM32F103C8T6ARM Cortex-M3Flash芯片W25Q128JV128Mbit(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输入片选,低电平有效2DO输出数据输出(MISO)3/WP输入写保护,低电平有效4GND电源地5DI输入数据输入(MOSI)6CLK输入串行时钟7/HOLD输入暂停,低电平有效8VCC电源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模式: 模式CPOLCPHA时钟极性采样边沿 000空闲低上升沿采样101空闲低下降沿采样210空闲高下降沿采样311空闲高上升沿采样 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 常用指令 指令代码功能地址字节数据字节 WREN0x06写使能00WRDI0x04写禁止00RDID0x9F读ID03RDSR0x05读状态寄存器01-∞READ0x03读数据31-∞PP0x02页编程31-256SE0x20扇区擦除(4KB)30BE320x52块擦除(32KB)30BE640xD8块擦除(64KB)30CE0xC7全片擦除00 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 /**

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

#include "spi_flash.h" #include "stm32f10x.h" #include

/* 片选引脚定义 */ #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 /**

* @file spi_flash.h * @brief W25Q128 Flash驱动头文件 */

#ifndef __SPI_FLASH_H #define __SPI_FLASH_H

#include

/* 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 /**

* @file main.c * @brief SPI Flash测试主程序 */

#include "stm32f10x.h" #include "spi_flash.h" #include "usart1.h" #include #include

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 软件测试 测试项目: ID读取测试 单字节读写测试 页编程测试(256字节) 扇区擦除测试(4KB) 跨页写入测试 大数据量读写测试 速度测试 预期结果: 写入速度:约50-100KB/s 读取速度:约1-2MB/s 扇区擦除:约45-400ms 七、故障排查 7.1 常见问题 问题原因解决方案 读ID失败接线错误检查MOSI/MISO是否接反写入失败未擦除Flash写入前必须先擦除数据错误时序问题降低SPI速度全片擦除慢正常现象全片擦除需要20-100秒 7.2 调试技巧 使用示波器观察SPI波形 先测试读ID,确认基本通信正常 逐步测试:单字节→页→扇区 八、总结 8.1 核心知识点 SPI通信协议和时序 W25Q128存储结构和指令集 Flash擦写特性(先擦后写) STM32 SPI外设配置 8.2 扩展应用 实现FATFS文件系统 存储日志数据 固件升级(IAP) 参数存储

相关文章