STM32CubeMX配置SPI驱动W25Q64 Flash:从零到读写数据的完整流程(附代码)

张开发
2026/4/20 19:13:34 15 分钟阅读

分享文章

STM32CubeMX配置SPI驱动W25Q64 Flash:从零到读写数据的完整流程(附代码)
STM32CubeMX配置SPI驱动W25Q64 Flash从零到读写数据的完整流程附代码第一次接触STM32和外部Flash存储器的开发者往往会被SPI通信协议和Flash操作指令搞得晕头转向。本文将手把手带你用STM32CubeMX配置SPI接口实现对W25Q64 Flash芯片的基础读写操作。不同于单纯的理论讲解我们会从实际工程角度出发解决开发过程中可能遇到的各种实际问题。1. 硬件准备与CubeMX工程创建在开始配置之前确保你手头有以下硬件一块STM32开发板如STM32F103C8T6W25Q64 Flash模块杜邦线若干首先打开STM32CubeMX创建一个新工程。选择你使用的STM32芯片型号这里以STM32F103C8为例。创建完成后我们需要配置以下几个关键部分时钟配置根据开发板实际情况配置系统时钟通常使用外部晶振作为时钟源SPI接口配置选择SPI1或SPI2配置为主模式GPIO配置设置SPI相关引脚和片选引脚提示W25Q64的典型工作电压是3.3V确保开发板IO电压与之匹配否则可能无法正常工作或损坏芯片。2. SPI接口详细配置进入SPI配置界面我们需要设置以下参数参数项推荐值说明ModeFull-Duplex Master全双工主模式Frame FormatMotorola标准SPI帧格式Data Size8 bits每次传输8位数据First BitMSB First高位在前Prescaler4分频根据主频调整Clock PolarityLowCPOL0空闲时低电平Clock Phase1 EdgeCPHA0第一个边沿采样对于片选信号(CS/NSS)建议使用普通GPIO控制而非硬件NSS这样更灵活。选择一个空闲的GPIO引脚如PC0作为片选信号配置为输出模式初始状态为高电平。// 片选控制宏定义 #define W25Q_CS_GPIO_Port GPIOC #define W25Q_CS_Pin GPIO_PIN_0 #define W25Q_CS_LOW() HAL_GPIO_WritePin(W25Q_CS_GPIO_Port, W25Q_CS_Pin, GPIO_PIN_RESET) #define W25Q_CS_HIGH() HAL_GPIO_WritePin(W25Q_CS_GPIO_Port, W25Q_CS_Pin, GPIO_PIN_SET)3. W25Q64基础操作函数实现W25Q64的操作都是通过发送特定指令实现的。我们先实现几个基础函数3.1 基本读写函数// SPI发送函数 HAL_StatusTypeDef W25Q_SPI_Transmit(uint8_t *pData, uint16_t Size) { return HAL_SPI_Transmit(hspi1, pData, Size, 100); } // SPI接收函数 HAL_StatusTypeDef W25Q_SPI_Receive(uint8_t *pData, uint16_t Size) { return HAL_SPI_Receive(hspi1, pData, Size, 100); }3.2 常用指令实现W25Q64常用的指令包括写使能(0x06)读状态寄存器(0x05)页编程(0x02)扇区擦除(0x20)读数据(0x03)下面是写使能和读状态寄存器的实现// 写使能 void W25Q_WriteEnable(void) { uint8_t cmd 0x06; W25Q_CS_LOW(); W25Q_SPI_Transmit(cmd, 1); W25Q_CS_HIGH(); } // 读状态寄存器1 uint8_t W25Q_ReadStatusReg1(void) { uint8_t cmd[2] {0x05, 0x00}; uint8_t status; W25Q_CS_LOW(); W25Q_SPI_Transmit(cmd, 2); W25Q_SPI_Receive(status, 1); W25Q_CS_HIGH(); return status; }3.3 等待Flash就绪在执行写操作或擦除操作前需要检查Flash是否忙void W25Q_WaitForReady(void) { while(W25Q_ReadStatusReg1() 0x01); // 检查BUSY位 }4. Flash读写功能实现4.1 读取芯片ID每个W25Q64芯片都有唯一的ID可以用来验证通信是否正常void W25Q_ReadID(uint8_t *id) { uint8_t cmd 0x9F; W25Q_CS_LOW(); W25Q_SPI_Transmit(cmd, 1); W25Q_SPI_Receive(id, 3); // 读取3字节ID W25Q_CS_HIGH(); }正常情况应该返回制造商ID(0xEF)、存储器类型(0x40)、容量(0x17)。4.2 扇区擦除W25Q64的最小擦除单位是4KB的扇区void W25Q_SectorErase(uint32_t sectorAddr) { uint8_t cmd[4]; cmd[0] 0x20; // 扇区擦除指令 cmd[1] (sectorAddr 16) 0xFF; // 地址高位 cmd[2] (sectorAddr 8) 0xFF; cmd[3] sectorAddr 0xFF; W25Q_WriteEnable(); W25Q_CS_LOW(); W25Q_SPI_Transmit(cmd, 4); W25Q_CS_HIGH(); W25Q_WaitForReady(); // 等待擦除完成 }4.3 页编程写入数据W25Q64支持页编程最多256字节void W25Q_PageProgram(uint32_t addr, uint8_t *data, uint16_t len) { uint8_t cmd[4]; cmd[0] 0x02; // 页编程指令 cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; W25Q_WriteEnable(); W25Q_CS_LOW(); W25Q_SPI_Transmit(cmd, 4); W25Q_SPI_Transmit(data, len); W25Q_CS_HIGH(); W25Q_WaitForReady(); }4.4 读取数据读取数据相对简单不需要擦除操作void W25Q_ReadData(uint32_t addr, uint8_t *data, uint16_t len) { uint8_t cmd[4]; cmd[0] 0x03; // 读数据指令 cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; W25Q_CS_LOW(); W25Q_SPI_Transmit(cmd, 4); W25Q_SPI_Receive(data, len); W25Q_CS_HIGH(); }5. 完整测试流程下面是一个完整的测试流程验证Flash读写是否正常void W25Q_Test(void) { uint8_t id[3]; uint8_t writeData[10] {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA}; uint8_t readData[10] {0}; // 1. 读取芯片ID W25Q_ReadID(id); printf(Manufacturer ID: 0x%02X\r\n, id[0]); printf(Memory Type: 0x%02X\r\n, id[1]); printf(Capacity: 0x%02X\r\n, id[2]); // 2. 擦除第一个扇区(0x000000 - 0x000FFF) W25Q_SectorErase(0x000000); // 3. 写入测试数据 W25Q_PageProgram(0x000000, writeData, 10); // 4. 读取并验证数据 W25Q_ReadData(0x000000, readData, 10); for(int i0; i10; i) { if(readData[i] ! writeData[i]) { printf(Verify failed at byte %d!\r\n, i); return; } } printf(Flash test passed!\r\n); }6. 常见问题排查在实际开发中可能会遇到以下问题无法读取芯片ID检查硬件连接是否正确MOSI、MISO、SCK、CS确认SPI配置参数CPOL、CPHA是否正确测量CS信号是否正常拉低写入数据失败确保在执行写操作前调用了写使能命令检查目标地址是否已经擦除Flash只能将1改为0不能将0改为1确认写入地址没有跨页单次写入不能超过256字节擦除时间过长扇区擦除通常需要几十到几百毫秒使用读状态寄存器命令检查BUSY位不要依赖固定延时注意W25Q64的地址是24位的最大可寻址8MB空间。地址计算时要注意字节序通常是大端模式高位在前。

更多文章