【STM32】STM32实战笔记-SPI驱动W25Q64 Flash存储芯片(37)
2026/7/15 2:00:02 网站建设 项目流程

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),写入时最小按页(256B)

2. 硬件连接实战

去年给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作为主机输出

硬件设计三个关键点:

  1. 上拉电阻:MISO建议加4.7K上拉,避免浮空状态
  2. 走线长度:SCK与数据线长度差控制在5mm内
  3. 电源滤波: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 0(CPOL=0,CPHA=0),发现写入总出错。后来对照时序图才发现应该用Mode 3(CPOL=1,CPHA=1),因为芯片在时钟下降沿采样数据。

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 i=0; i<len; i++) SPI_SendByte(data[i]); // 写入数据 CS_HIGH(); Flash_WaitBusy(); // 等待写入完成 }

注意点:

  1. 地址必须对齐到页起始(256字节边界)
  2. 写入长度不能超过256字节
  3. 跨页写入需要分多次操作

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 i=0; i<len; 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变为1(0xFF)
  • 地址会自动对齐到4KB边界

6.2 批量擦除技巧

需要整片擦除时,可以用以下优化方案:

  1. 先检查扇区是否需要擦除(全FF可跳过)
  2. 采用非阻塞方式,在擦除期间执行其他任务
  3. 使用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 典型故障案例

  1. 症状:能读取ID但无法写入

    • 检查写保护引脚(WP)
    • 确认发送了WRITE_ENABLE指令
    • 测量电源电压(要求2.7-3.6V)
  2. 症状:随机数据错误

    • 检查PCB走线等长
    • 降低时钟频率测试
    • 添加磁珠滤波
  3. 症状:DMA传输卡死

    • 确认DMA中断优先级设置
    • 检查缓冲区是否越界
    • 添加超时机制

8.2 调试工具推荐

  1. 逻辑分析仪:Saleae Logic Pro 8

    • 可解码SPI协议
    • 支持最高500MHz采样
  2. 示波器:测量信号完整性

    • 检查上升沿/下降沿时间
    • 观察过冲和振铃
  3. ST-Link:利用STM32CubeMonitor

    • 实时监控变量
    • 性能分析

记得第一次用逻辑分析仪抓SPI波形时,发现CS信号有毛刺,后来在CS脚加了个10nF电容就解决了。调试硬件就是要在蛛丝马迹中找线索。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询