【STM32】STM32实战笔记-SPI驱动W25Q64 Flash存储芯片(37)
1. SPI与W25Q64基础认知第一次接触SPI Flash时我盯着W25Q64的数据手册发了半小时呆。这个指甲盖大小的芯片居然能存储8MB数据后来才明白SPI就像是我们和芯片之间的摩尔斯电码而W25Q64就是个会听SPI指令的记忆大师。SPI协议最迷人的地方在于它的简洁高效。四根线就能搞定全双工通信SCK时钟线像节拍器一样指挥数据传输节奏MOSI主机输出从机输入相当于我说你听MISO主机输入从机输出相当于你说我听CS片选线相当于点名时的到W25Q64的存储结构就像一本厚厚的书全书共128章块Block每章16节扇区Sector每节有16页Page每页256个字Byte擦除时最小按扇区4KB写入时最小按页256B2. 硬件连接实战去年给STM32F103接W25Q64时我犯了个低级错误——把MOSI和MISO接反了。调试时数据死活传不出去后来用逻辑分析仪抓波形才发现这个乌龙。正确连接方式/* 引脚定义 */ #define FLASH_CS_PIN GPIO_Pin_4 // PA4作为片选 #define FLASH_SCK_PIN GPIO_Pin_5 // PA5作为时钟 #define FLASH_MISO_PIN GPIO_Pin_6 // PA6作为主机输入 #define FLASH_MOSI_PIN GPIO_Pin_7 // PA7作为主机输出硬件设计三个关键点上拉电阻MISO建议加4.7K上拉避免浮空状态走线长度SCK与数据线长度差控制在5mm内电源滤波VCC对地加0.1μF陶瓷电容距离芯片不超过2cm实测中发现当SPI时钟超过20MHz时必须使用阻抗匹配的PCB设计。有次用洞洞板搭电路超过10MHz就开始丢数据换成四层板后稳定跑到50MHz。3. SPI初始化配置配置STM32的SPI就像教机器人跳舞每个参数都要和W25Q64的舞步完美配合void SPI_Config(void) { SPI_InitTypeDef SPI_InitStructure; // 时钟配置 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); // 参数设置 SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; // 主机模式 SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; // 8位数据 SPI_InitStructure.SPI_CPOL SPI_CPOL_High; // 时钟极性 SPI_InitStructure.SPI_CPHA SPI_CPHA_2Edge; // 时钟相位 SPI_InitStructure.SPI_NSS SPI_NSS_Soft; // 软件控制片选 SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_4; // 18MHz SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; // 高位在前 SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); // 使能SPI }这里有个坑W25Q64支持Mode 0和Mode 3我最初误用Mode 0CPOL0,CPHA0发现写入总出错。后来对照时序图才发现应该用Mode 3CPOL1,CPHA1因为芯片在时钟下降沿采样数据。4. Flash指令集解析W25Q64有套自己的语言常用的指令就像控制记忆大师的咒语指令名称编码功能描述WRITE_ENABLE0x06开启写操作权限PAGE_PROGRAM0x02写入数据最大256字节SECTOR_ERASE0x20擦除4KB扇区READ_DATA0x03读取数据READ_ID0x9F读取芯片ID实现写使能指令的代码示例void Flash_WriteEnable(void) { CS_LOW(); // 片选拉低 SPI_SendByte(0x06); // 发送写使能指令 CS_HIGH(); // 片选拉高 Delay_us(10); // 等待指令执行 }有个容易忽略的细节每条指令执行后要检查状态寄存器。有次我连续写入时数据丢失后来发现是没等上一个写操作完成就发起新操作。正确做法是轮询状态寄存器的BUSY位while(Flash_ReadStatusReg() 0x01); // 等待就绪5. 读写功能完整实现5.1 页编程实现页编程就像在笔记本上写字必须按页操作void Flash_PageProgram(uint32_t addr, uint8_t *data, uint16_t len) { Flash_WriteEnable(); // 开启写权限 CS_LOW(); SPI_SendByte(0x02); // 页编程指令 SPI_SendByte((addr 16) 0xFF); // 地址高位 SPI_SendByte((addr 8) 0xFF); SPI_SendByte(addr 0xFF); for(uint16_t i0; ilen; i) SPI_SendByte(data[i]); // 写入数据 CS_HIGH(); Flash_WaitBusy(); // 等待写入完成 }注意点地址必须对齐到页起始256字节边界写入长度不能超过256字节跨页写入需要分多次操作5.2 数据读取实现读取数据相对简单但要注意时序void Flash_ReadData(uint32_t addr, uint8_t *buf, uint32_t len) { CS_LOW(); SPI_SendByte(0x03); // 读指令 SPI_SendByte((addr 16) 0xFF); SPI_SendByte((addr 8) 0xFF); SPI_SendByte(addr 0xFF); for(uint32_t i0; ilen; i) buf[i] SPI_SendByte(0xFF); // 发送dummy获取数据 CS_HIGH(); }实测发现连续读取时如果SCK频率超过50MHz需要用Fast Read指令0x0B并插入dummy cycle。有次读取大文件时出现数据错位就是忽略了这个问题。6. 擦除操作精要6.1 扇区擦除擦除就像用橡皮擦掉笔记本内容最小擦除单位是4KB扇区void Flash_SectorErase(uint32_t addr) { Flash_WriteEnable(); CS_LOW(); SPI_SendByte(0x20); // 扇区擦除指令 SPI_SendByte((addr 16) 0xFF); SPI_SendByte((addr 8) 0xFF); SPI_SendByte(addr 0xFF); CS_HIGH(); Flash_WaitBusy(); // 等待擦除完成 }重要提示擦除时间约100ms必须等待完成擦除后所有bit变为10xFF地址会自动对齐到4KB边界6.2 批量擦除技巧需要整片擦除时可以用以下优化方案先检查扇区是否需要擦除全FF可跳过采用非阻塞方式在擦除期间执行其他任务使用DMA搬运数据提高效率void Flash_BulkErase(void) { Flash_WriteEnable(); CS_LOW(); SPI_SendByte(0xC7); // 整片擦除指令 CS_HIGH(); // 此处可添加任务调度 Flash_WaitBusy(); }7. 性能优化实战7.1 时钟配置技巧SPI时钟就像数据传输的心跳配置不当会导致性能瓶颈分频系数时钟频率适用场景236MHz短距离PCB布线418MHz通用场景89MHz长导线连接164.5MHz高噪声环境实测发现使用杜邦线连接时超过10MHz就容易出错换成PCB直连后可以稳定运行在36MHz。7.2 DMA传输实现大数据量传输时DMA就像个勤劳的搬运工void Flash_Read_DMA(uint32_t addr, uint8_t *buf, uint32_t len) { CS_LOW(); SPI_SendByte(0x03); SPI_SendByte((addr 16) 0xFF); SPI_SendByte((addr 8) 0xFF); SPI_SendByte(addr 0xFF); SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE); DMA_Cmd(DMA1_Channel2, ENABLE); // 等待DMA完成中断 CS_HIGH(); }使用DMA后读取1MB数据从原来的2.3秒缩短到0.8秒效率提升65%。但要注意缓冲区地址对齐问题有次因为buf没4字节对齐导致数据错位。8. 常见问题排查8.1 典型故障案例症状能读取ID但无法写入检查写保护引脚WP确认发送了WRITE_ENABLE指令测量电源电压要求2.7-3.6V症状随机数据错误检查PCB走线等长降低时钟频率测试添加磁珠滤波症状DMA传输卡死确认DMA中断优先级设置检查缓冲区是否越界添加超时机制8.2 调试工具推荐逻辑分析仪Saleae Logic Pro 8可解码SPI协议支持最高500MHz采样示波器测量信号完整性检查上升沿/下降沿时间观察过冲和振铃ST-Link利用STM32CubeMonitor实时监控变量性能分析记得第一次用逻辑分析仪抓SPI波形时发现CS信号有毛刺后来在CS脚加了个10nF电容就解决了。调试硬件就是要在蛛丝马迹中找线索。