STM32 SPI接口读写W25Q256串行FLASH实战指南
1. SPI读写串行FLASH实验概述在嵌入式系统开发中外部存储扩展是常见需求。SPI接口的串行FLASH因其体积小、容量大、接口简单等特点成为存储扩展的热门选择。本实验将基于STM32平台详细介绍如何通过SPI接口读写W25Q256串行FLASH芯片。2. 硬件设计与连接2.1 硬件选型与连接实验采用W25Q256JV系列FLASH芯片其主要特性如下存储容量32Mbit4MB工作电压2.7V-3.6VSPI时钟频率最高104MHz支持标准SPI、Dual SPI和Quad SPI模式硬件连接示意图STM32F4 W25Q256 PA5(SCK) - CLK PA6(MISO) - DO PA7(MOSI) - DI PA4(NSS) - CS 3.3V - VCC GND - GND2.2 重要引脚说明WP#写保护引脚高电平禁用写保护HOLD#暂停引脚高电平禁用暂停功能CS#片选信号低电平有效3. SPI协议基础3.1 SPI工作模式W25Q256支持SPI模式0和模式3模式0CPOL0CPHA0模式3CPOL1CPHA1实际使用中多采用模式3其时序特点时钟空闲时为高电平数据在时钟的第二个边沿下降沿采样3.2 SPI数据传输格式数据传输特点全双工同步传输MSB先行8位数据帧长度支持单线、双线和四线模式4. 软件实现4.1 SPI初始化配置void SPI_Config(void) { GPIO_InitTypeDef GPIO_InitStruct; SPI_InitTypeDef SPI_InitStruct; // 使能时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); // 配置GPIO GPIO_InitStruct.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode GPIO_Mode_AF; GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_OType GPIO_OType_PP; GPIO_InitStruct.GPIO_PuPd GPIO_PuPd_UP; GPIO_Init(GPIOA, GPIO_InitStruct); GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1); GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1); GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1); // 配置CS引脚 GPIO_InitStruct.GPIO_Pin GPIO_Pin_4; GPIO_InitStruct.GPIO_Mode GPIO_Mode_OUT; GPIO_Init(GPIOA, GPIO_InitStruct); GPIO_SetBits(GPIOA, GPIO_Pin_4); // 配置SPI 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_High; SPI_InitStruct.SPI_CPHA SPI_CPHA_2Edge; SPI_InitStruct.SPI_NSS SPI_NSS_Soft; SPI_InitStruct.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_2; SPI_InitStruct.SPI_FirstBit SPI_FirstBit_MSB; SPI_InitStruct.SPI_CRCPolynomial 7; SPI_Init(SPI1, SPI_InitStruct); SPI_Cmd(SPI1, ENABLE); }4.2 基本读写函数uint8_t SPI_FLASH_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); } uint8_t SPI_FLASH_ReadByte(void) { return SPI_FLASH_SendByte(DUMMY_BYTE); }5. FLASH操作指令集5.1 常用指令列表指令名称指令编码功能描述Write Enable0x06写使能Write Disable0x04写禁止Read Data0x03读取数据Page Program0x02页编程最大256字节Sector Erase0x20扇区擦除4KBBlock Erase0xD8块擦除64KBChip Erase0xC7整片擦除Read Status Reg0x05读取状态寄存器Read ID0x9F读取制造商和设备ID5.2 状态寄存器说明状态寄存器格式| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |---|---|---|---|---|---|---|---| |SUS| 0 | 0 |BP2|BP1|BP0|WEL|BUSY|关键位说明BUSY(位0)1表示设备忙0表示就绪WEL(位1)写使能锁存位BP0-2(位2-4)块保护控制位6. 关键功能实现6.1 读取设备IDuint32_t SPI_FLASH_ReadID(void) { uint32_t Temp 0; FLASH_CS_LOW(); SPI_FLASH_SendByte(READ_ID); Temp SPI_FLASH_SendByte(DUMMY_BYTE) 16; Temp | SPI_FLASH_SendByte(DUMMY_BYTE) 8; Temp | SPI_FLASH_SendByte(DUMMY_BYTE); FLASH_CS_HIGH(); return Temp; }6.2 扇区擦除void SPI_FLASH_SectorErase(uint32_t SectorAddr) { SPI_FLASH_WriteEnable(); FLASH_CS_LOW(); SPI_FLASH_SendByte(SECTOR_ERASE); SPI_FLASH_SendByte((SectorAddr 16) 0xFF); SPI_FLASH_SendByte((SectorAddr 8) 0xFF); SPI_FLASH_SendByte(SectorAddr 0xFF); FLASH_CS_HIGH(); SPI_FLASH_WaitForWriteEnd(); }6.3 页编程void SPI_FLASH_PageWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite) { SPI_FLASH_WriteEnable(); FLASH_CS_LOW(); SPI_FLASH_SendByte(PAGE_PROGRAM); SPI_FLASH_SendByte((WriteAddr 16) 0xFF); SPI_FLASH_SendByte((WriteAddr 8) 0xFF); SPI_FLASH_SendByte(WriteAddr 0xFF); while(NumByteToWrite--) { SPI_FLASH_SendByte(*pBuffer); pBuffer; } FLASH_CS_HIGH(); SPI_FLASH_WaitForWriteEnd(); }6.4 数据读取void SPI_FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead) { FLASH_CS_LOW(); SPI_FLASH_SendByte(READ_DATA); SPI_FLASH_SendByte((ReadAddr 16) 0xFF); SPI_FLASH_SendByte((ReadAddr 8) 0xFF); SPI_FLASH_SendByte(ReadAddr 0xFF); while(NumByteToRead--) { *pBuffer SPI_FLASH_SendByte(DUMMY_BYTE); pBuffer; } FLASH_CS_HIGH(); }7. 实际应用注意事项7.1 擦除与写入时序写入前必须先擦除全FF状态擦除最小单位是扇区4KB写入最大单位是页256字节跨页写入需要分多次操作7.2 性能优化建议合理规划存储结构减少擦除次数批量写入时尽量对齐页边界使用双缓冲技术提高写入效率重要数据考虑写入校验机制7.3 常见问题排查写入失败检查是否执行了写使能WP引脚是否为高电平目标区域是否已擦除电源电压是否稳定读取异常检查SPI模式设置是否正确时序参数是否匹配信号线是否有干扰8. 实验验证8.1 测试流程初始化SPI接口读取设备ID验证通信擦除测试扇区写入测试数据回读校验数据比较写入与读取数据8.2 示例代码int main(void) { uint8_t write_data[] SPI FLASH Test Data; uint8_t read_data[sizeof(write_data)]; uint32_t flash_id; // 硬件初始化 SystemInit(); SPI_Config(); // 读取ID flash_id SPI_FLASH_ReadID(); if(flash_id ! W25Q256_ID) { // ID不匹配处理 while(1); } // 擦除扇区 SPI_FLASH_SectorErase(TEST_SECTOR_ADDR); // 写入数据 SPI_FLASH_PageWrite(write_data, TEST_SECTOR_ADDR, sizeof(write_data)); // 读取数据 SPI_FLASH_BufferRead(read_data, TEST_SECTOR_ADDR, sizeof(read_data)); // 数据校验 if(memcmp(write_data, read_data, sizeof(write_data)) 0) { // 测试成功 } else { // 测试失败 } while(1); }通过本实验我们完整实现了SPI接口读写串行FLASH的功能。在实际项目中可根据需求在此基础上扩展更复杂的存储管理功能如实现简单的文件系统或数据日志存储等。