软件SPI驱动W25Q64:从时序原理到避坑实战

发布时间:2026/10/17 7:45:01
软件SPI驱动W25Q64:从时序原理到避坑实战 简介本资源面向STM32单片机与嵌入式开发初学者及进阶学习者聚焦软件SPI通信方式读写W25Q64 Flash存储器这一典型实战场景。W25Q64内部可存储8M字节数据且掉电不丢失案例完整演示了主机STM32与从机W25Q64模块之间的通信实现接线采用SPI1外设引脚方案PA4至PA7分别对应NSS、SCK、MISO、MOSI便于后续过渡到硬件SPI章节。压缩包共83个文件约1004KB以36个h头文件与33个c源文件为核心涵盖MySPI、W25Q64、OLED、Key等驱动模块另含8个s启动文件及uvprojx、uvoptx等Keil工程配置目录按User、Hardware、Library、Start分层组织结构清晰。目前已有2052人学习下载。读者可从中获得软件SPI时序模拟、Flash读写指令封装、OLED显示调试等完整代码与排错思路适合作为嵌入式存储外设学习的参考范例。1. 软件SPI读写W25Q64为什么时序比外设更值得先搞懂很多人第一次在 STM32 上驱动 W25Q64 时都会下意识打开 CubeMX把 SPI1 配成 Mode 0、8 位数据、MSB 先出然后调用 HAL_SPI_Transmit 就等着读 ID。结果要么读回 0xFF要么读回 0x00要么第一次能读、第二次就卡死。翻车之后才发现问题往往不在 W25Q64而在自己根本没搞清楚 SPI 的时钟极性和相位到底在干什么。硬件 SPI 外设把这些细节封装得太好好到你根本不知道它替你做了什么。而软件 SPI 用普通 GPIO 一位一位地翻转电平反而把整个通信过程摊开在你面前什么时候拉低片选、第几个边沿采样、MOSI 在时钟高电平还是低电平改变全部由你决定。搞懂软件 SPI再去用硬件 SPI你才知道那些配置项到底在配什么。W25Q64 这颗 8MB 的 Flash 存储器恰好是一个足够简单又足够典型的 SPI 从设备它的指令集、状态寄存器、页编程和扇区擦除机制几乎覆盖了嵌入式开发中 SPI 通信的全部核心问题。2. SPI 的四种模式CPOL 和 CPHA 到底在采样什么2.1 从一根时钟线说起SPI 是全双工同步串行总线四根线各司其职SCK 提供时钟节拍MOSI 是主机输出从机输入MISO 是主机输入从机输出CS 是片选。通信的本质是移位寄存器交换主机和从机各有一个 8 位或更多位移位寄存器在 SCK 的驱动下每个时钟周期双方各移出一位、移入一位8 个周期后两个寄存器的内容就完成了交换。这个模型解释了为什么 SPI 没有应答机制也能工作——发送和接收在物理上是同一个动作你发一个字节出去必然同时收一个字节回来。但这里有一个关键问题数据在时钟的哪个边沿被采样又在哪个边沿被改变如果主机在上升沿改变数据、从机也在上升沿采样那从机采到的就是正在跳变的不稳定电平。所以 SPI 协议规定采样边沿和改变边沿必须错开。CPOL 决定时钟空闲时是高电平还是低电平CPHA 决定在第一个边沿采样还是第二个边沿采样。两者组合出四种模式W25Q64 支持 Mode 0CPOL0CPHA0和 Mode 3CPOL1CPHA1最常用的是 Mode 0。2.2 软件 SPI 的位操作实现软件 SPI 的核心就是一个循环每次处理一个 bit。下面这段代码展示了 Mode 0 下发送并接收一个字节的完整过程// 软件 SPI 读写一个字节Mode 0CPOL0, CPHA0 // 空闲时 SCK 低电平第一个边沿上升沿采样 uint8_t SoftSPI_ReadWriteByte(uint8_t txData) { uint8_t rxData 0; for (int i 7; i 0; i--) // MSB 先出从 bit7 到 bit0 { // 1. 主机在 SCK 低电平期间把数据放到 MOSI 上 if (txData (1 i)) GPIO_SetBits(GPIOA, GPIO_Pin_7); // MOSI 输出高 else GPIO_ResetBits(GPIOA, GPIO_Pin_7); // MOSI 输出低 // 2. 拉高 SCK产生上升沿从机在此刻采样 MOSI GPIO_SetBits(GPIOA, GPIO_Pin_5); // 3. 主机在上升沿之后读取 MISO获取从机输出的位 if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6)) rxData | (1 i); // 4. 拉低 SCK产生下降沿为下一个 bit 做准备 GPIO_ResetBits(GPIOA, GPIO_Pin_5); } return rxData; }这段代码里每一步的先后顺序都不是随便写的。第 1 步在 SCK 低电平期间改变 MOSI保证数据在上升沿到来之前已经稳定。第 2 步拉高 SCK从机在上升沿采样 MOSI 上的数据。第 3 步在 SCK 还是高电平的时候读取 MISO因为从机也是在上升沿把数据放到 MISO 上的此时读取最稳定。第 4 步拉低 SCK完成一个周期。如果换成 Mode 3CPOL1 意味着空闲时 SCK 是高电平CPHA1 意味着在第二个边沿采样代码里拉高拉低的顺序就要反过来。注意软件 SPI 的速率完全取决于 GPIO 翻转速度和循环开销。在 72MHz 的 STM32F103 上上面这个循环大概能跑到 1-2MHz 的 SCK 频率。W25Q64 支持最高 80MHz 的时钟所以软件 SPI 的速度瓶颈非常明显这也是为什么实际产品中更常用硬件 SPI 的原因。2.3 为什么 W25Q64 选 Mode 0 和 Mode 3W25Q64 的数据手册明确写了支持 Mode 0 和 Mode 3。这两种模式的共同点是数据都在时钟的上升沿被采样对于 Mode 0上升沿是第一个边沿对于 Mode 3上升沿是第二个边沿。区别只在于空闲电平不同。这样设计的好处是从设备只需要在上升沿采样即可下降沿用于数据切换逻辑统一。如果你的软件 SPI 用 Mode 0 能读到 ID 但读数据出错大概率是某个环节的边沿顺序搞反了而不是 W25Q64 本身有问题。3. W25Q64 的指令交互从读 ID 到页编程的完整链路3.1 读 JEDEC ID验证通信是否打通在驱动任何 SPI 从设备之前第一步永远是读 ID。W25Q64 的 JEDEC ID 指令是 0x9F发送后会返回 3 个字节制造商 ID0xEF、存储器类型0x40、容量0x18表示 8MB。读 ID 的好处是它不涉及任何写操作不需要等待忙状态纯粹验证时序是否正确。// 读取 W25Q64 的 JEDEC ID void W25Q64_ReadID(uint8_t *mid, uint8_t *did) { W25Q64_CS_LOW(); // 拉低片选选中从机 SoftSPI_ReadWriteByte(0x9F); // 发送 JEDEC ID 指令 *mid SoftSPI_ReadWriteByte(0xFF); // 读制造商 ID0xEF *did SoftSPI_ReadWriteByte(0xFF); // 读设备 ID W25Q64_CS_HIGH(); // 拉高片选结束通信 }这里发送 0xFF 是一个常见做法SPI 是全双工主机需要产生时钟才能接收数据发送什么内容无所谓0xFF 只是习惯用法。片选 CS 的拉低和拉高必须完整包裹整个指令序列中间不能松开否则 W25Q64 会认为指令结束后续数据全部无效。3.2 状态寄存器与忙等待W25Q64 在执行擦除或编程操作时内部需要时间完成这段时间内除了读状态寄存器指令0x05之外其他指令一律不响应。状态寄存器的 bit0 是 BUSY 位1 表示忙0 表示空闲。所以每次擦除或写入之后必须轮询等待 BUSY 位清零否则后续操作会直接失败。// 等待 W25Q64 空闲 void W25Q64_WaitBusy(void) { uint8_t status; W25Q64_CS_LOW(); SoftSPI_ReadWriteByte(0x05); // 读状态寄存器指令 do { status SoftSPI_ReadWriteByte(0xFF); } while (status 0x01); // bit0 为 BUSY 位 W25Q64_CS_HIGH(); }这段代码有一个容易踩坑的地方片选在轮询期间一直保持低电平。有些实现会在每次读状态寄存器之间拉高片选再拉低这样虽然也能工作但效率更低而且如果中间被中断打断可能引入额外的时序问题。保持片选低电平连续轮询是更稳妥的做法。3.3 扇区擦除与页编程的边界W25Q64 的存储结构是8MB 分为 128 个块Block每块 64KB每块分为 16 个扇区Sector每扇区 4KB每扇区分为 16 页Page每页 256 字节。擦除的最小单位是扇区4KB编程的最小单位是页256 字节。这意味着你无法单独擦除一个字节也无法跨页写入。写数据之前必须先擦除对应扇区因为 Flash 的物理特性是只能把 1 写成 0不能把 0 写回 1。擦除操作把整个扇区恢复为 0xFF。页编程指令0x02最多写入 256 字节如果写入超过 256 字节地址会自动回卷到本页开头覆盖之前的数据。这个回卷行为是硬件层面的不会报错但数据会静默损坏。// 页编程在指定地址写入数据长度不超过 256 字节且不跨页 void W25Q64_PageProgram(uint32_t addr, uint8_t *data, uint16_t len) { W25Q64_WriteEnable(); // 必须先发送写使能指令 0x06 W25Q64_CS_LOW(); SoftSPI_ReadWriteByte(0x02); // 页编程指令 SoftSPI_ReadWriteByte(addr 16); // 发送 24 位地址高字节先出 SoftSPI_ReadWriteByte(addr 8); SoftSPI_ReadWriteByte(addr); for (uint16_t i 0; i len; i) SoftSPI_ReadWriteByte(data[i]); W25Q64_CS_HIGH(); W25Q64_WaitBusy(); // 等待编程完成 }写使能指令 0x06 是每次编程或擦除之前都必须发送的W25Q64 内部有一个写使能锁存器WEL发送 0x06 后置 1编程或擦除完成后自动清零。如果忘记发写使能编程指令会被直接忽略数据写不进去而且不会有任何错误提示。4. 避坑与排查软件SPI驱动W25Q64的五个血泪教训4.1 读回全是 0xFF 或 0x00现象调用读 ID 函数返回的制造商 ID 不是 0xEF而是 0xFF 或 0x00。原因0xFF 通常意味着 MISO 线一直处于高电平可能是从机没有响应或者片选没有正确拉低。0x00 则可能是 MISO 线一直低电平或者 GPIO 模式配置错误。常见的情况是 MISO 引脚没有配置成浮空输入或上拉输入而是配成了推挽输出导致主机自己把自己的输出拉死了。解决检查 MISO 引脚的 GPIO 模式必须配置为输入模式GPIO_Mode_IN_FLOATING 或 GPIO_Mode_IPU。同时用示波器或逻辑分析仪确认 CS 是否在通信期间保持低电平SCK 是否有 8 个完整的脉冲。4.2 第一次能读、第二次卡死现象上电后第一次读 ID 成功但紧接着读数据就卡在忙等待循环里出不来。原因W25Q64 在上电后如果 CS 引脚悬空或受到干扰可能进入一种异常状态。另外如果上一次操作没有正确结束比如 CS 没有拉高W25Q64 会一直等待后续时钟状态机无法复位。解决在初始化时先拉高 CS发送 0xFF 几个周期让 W25Q64 内部状态机复位。每次操作结束后确保 CS 拉高。忙等待循环里加超时计数避免死循环。4.3 写入数据后读出来不对现象页编程写入 0x01 0x02 0x03读出来却是 0xFF 0xFF 0xFF 或者部分数据错误。原因最常见的原因是忘记在编程前擦除扇区。Flash 只能把 1 写成 0如果目标地址之前有数据且不是 0xFF写入结果就是旧数据和新数据的按位与。另一个原因是写入跨页了超过 256 字节后地址回卷覆盖了本页开头的数据。解决写入前先擦除对应扇区并等待擦除完成。确保单次页编程不超过 256 字节且起始地址到页末尾的剩余空间足够。如果需要写入超过 256 字节拆分成多次页编程每次重新发送写使能和页编程指令。4.4 擦除后读出来还是旧数据现象调用了扇区擦除指令等待忙状态结束再读取该扇区发现数据没有变成 0xFF。原因擦除指令发送前没有发送写使能指令 0x06。W25Q64 的写使能锁存器在每次擦除或编程后自动清零所以每次操作前都必须重新发送。另一个可能的原因是擦除的地址没有对齐到 4KB 边界W25Q64 会忽略低 12 位地址实际擦除的是包含该地址的整个扇区但如果你传的地址本身有误可能擦错了扇区。解决每次擦除前调用写使能函数。擦除地址按 4KB 对齐即 addr 0xFFFFF000。擦除后读回验证确认数据确实变成了 0xFF。4.5 软件SPI速率太慢导致超时现象用软件 SPI 连续写入大量数据时上位机或看门狗超时复位。原因软件 SPI 每个字节需要 8 次循环每次循环包含多次 GPIO 操作在 72MHz 主频下实际 SCK 频率可能只有几百 kHz。写入 1MB 数据需要的时间可能达到数十秒如果看门狗没有及时喂狗就会复位。解决在忙等待和长循环中插入喂狗操作。如果对速度有要求考虑切换到硬件 SPI或者优化软件 SPI 的 GPIO 操作比如直接操作 BSRR 寄存器代替 GPIO_SetBits/GPIO_ResetBits减少函数调用开销。5. 软件SPI与硬件SPI的取舍什么时候该用哪个软件 SPI 和硬件 SPI 的选择本质上是在灵活性和性能之间做权衡。软件 SPI 的优势在于引脚任意映射不受硬件外设引脚复用的限制。比如某个项目里 SPI 外设的引脚被其他功能占用了或者需要同时挂多个 SPI 从设备但硬件 CS 引脚不够用软件 SPI 就能派上用场。另外软件 SPI 的时序完全可控遇到一些时序要求奇怪的从设备可以精确调整每个边沿的延迟。但软件 SPI 的代价也很明显。前面代码里每个字节的读写需要几十条指令在 72MHz 的 STM32F103 上实测 SCK 频率大约在 1MHz 左右而硬件 SPI 可以轻松跑到 18MHz 甚至 36MHz。对于 W25Q64 这种支持 80MHz 的存储器来说软件 SPI 完全是在浪费它的性能。如果只是偶尔读写几个字节的配置参数软件 SPI 够用如果要存储大量数据、做文件系统、或者需要快速启动硬件 SPI 是必然选择。我一般会这么理解软件 SPI 是学习 SPI 协议的最佳工具因为它强迫你面对每一个时序细节。当你用软件 SPI 把 W25Q64 完整驱动起来之后再去看硬件 SPI 的配置寄存器CPOL、CPHA、波特率预分频、数据帧格式这些位域就不再是黑匣子了。你知道每个配置项对应的是哪个物理行为出了问题也知道从哪个环节去排查。从那以后我每次调试新的 SPI 从设备都会先用软件 SPI 把 ID 读出来确认时序无误再切换到硬件 SPI 跑高速。这个习惯帮我省下了大量猜测硬件配置的时间。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询