STM32F103驱动W25Q64 SPI Flash读写全流程与实战避坑指南

发布时间:2026/10/21 1:47:00
STM32F103驱动W25Q64 SPI Flash读写全流程与实战避坑指南 1. 为什么选 SPI 接口的 W25Q64 做存储扩展做嵌入式项目的人迟早会遇到一个尴尬局面主控芯片的 Flash 空间不够用了。STM32F103 系列常见的型号比如 C8T6内部 Flash 只有 64KB跑个带协议栈的固件就快满了更别提还要存字库、图片、日志或者配置参数。这时候外挂一颗 SPI Flash 是最经济实惠的方案而 W25Q64 几乎是这个需求下的默认答案。W25Q64 是 8MB64Mbit容量的串行 Flash工作电压 2.7V 到 3.6V标准 SPI 接口支持最高 104MHz 时钟。它便宜、好买、资料多关键是 STM32F103 的 SPI 外设跟它配合起来非常顺手。我选它还有一个原因它的指令集设计得很规整读、写、擦除各司其职不像某些 Flash 芯片把状态机和指令混在一起让人摸不着头脑。但 SPI Flash 有个特点跟 I2C 的 EEPROM 完全不同——它不能直接改写某个字节。Flash 的物理特性决定了写入操作只能把 bit 从 1 变成 0要把 0 变回 1 必须整块擦除。这个特性是后面所有操作逻辑的根源也是新手最容易翻车的地方。你写了一个字节想改一个 bit结果发现怎么都写不进去不是代码错了是物理规律在起作用。这篇文章我会从 SPI 外设配置开始一步步把 W25Q64 的读写全流程拆开讲清楚。包括 GPIO 复用怎么设、SPI 模式怎么选、指令怎么发、状态怎么查、页编程和扇区擦除怎么配合。中间会穿插我实际调试时踩过的坑比如 CS 片选时序不对导致读出来全是 0xFF比如没等忙状态结束就发下一条指令导致数据错乱。目标很明确你看完能直接把这套代码移植到自己的板子上跑起来。2. 硬件连接与 SPI 外设初始化2.1 引脚分配与硬件设计要点STM32F103 通常有 SPI1 和 SPI2 两个外设。SPI1 挂在 APB2 总线上时钟频率最高 72MHzSPI2 挂在 APB1 上最高 36MHz。如果你对读写速度有要求优先用 SPI1。我一般把 W25Q64 接在 SPI1 上引脚分配是这样的PA5 作为 SCK时钟输出PA6 作为 MISO主入从出PA7 作为 MOSI主出从入PA4 作为 CS普通 GPIO 推挽输出这里有个细节值得说CS 片选我习惯用软件控制不依赖 SPI 外设的 NSS 引脚。原因很简单STM32 的硬件 NSS 在多主机或者需要精确控制片选时序的场景下反而添乱。用普通 GPIO 手动拉低拉高时序完全由你掌控调试起来也直观。硬件上还有两个容易被忽略的点。第一W25Q64 的 WP 引脚和 HOLD 引脚如果不用建议上拉到 VCC否则浮空可能引入干扰。第二SCK 和 MOSI 走线尽量短如果板子上 Flash 离主控比较远可以在 SCK 上串一个 22 欧姆到 100 欧姆的电阻做阻抗匹配减少过冲和振铃。我在一块四层板上没加这个电阻跑到 36MHz 的时候读数据偶尔出错加了 33 欧姆之后稳如磐石。2.2 SPI 参数配置的取舍逻辑SPI 初始化结构体里有几个参数需要你明确决策不是随便填的。时钟极性 CPOL 和时钟相位 CPHAW25Q64 支持模式 0CPOL0CPHA0和模式 3CPOL1CPHA1。我选模式 0因为这是最常用的配置SCK 空闲为低电平数据在第一个边沿采样。模式 3 也能用但没必要给自己增加理解成本。数据帧格式8 位MSB 先行。W25Q64 的指令和数据都是高位在前这个没得选。时钟分频SPI1 挂在 72MHz 的 APB2 上分频系数选 4 得到 18MHz 的 SCK。W25Q64 标称支持 104MHz但实际布线质量、杜邦线长度都会影响信号完整性。我建议先用低速跑通比如 72 分频得到 1MHz确认读写正常后再逐步提高。直接上高频然后发现数据不对你会花大量时间在排查是代码问题还是信号问题。NSS 管理选软件模式SPI_NSS_Soft。这样 SPI 外设不会去控制 NSS 引脚你用自己的 GPIO 操作 CS。初始化代码大概长这样void SPI1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); // SCK, MISO, MOSI 复用推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // MISO 浮空输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); // CS 推挽输出默认拉高 GPIO_InitStructure.GPIO_Pin GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_SetBits(GPIOA, GPIO_Pin_4); SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_4; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial 7; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }MISO 设成浮空输入是有讲究的。有些教程会设成上拉输入但在 SPI 模式下从机的 MISO 在 CS 有效时才输出CS 无效时是高阻态。如果主控端上拉CS 无效期间 MISO 被拉高虽然不会损坏器件但会增加不必要的功耗。浮空输入让总线自然处于高阻状态更符合 SPI 协议的设计意图。2.3 收发一个字节的底层函数SPI 是全双工发一个字节的同时也会收一个字节。STM32F103 的标准库没有提供现成的收发函数需要自己写u8 SPI1_ReadWriteByte(u8 TxData) { u8 retry 0; while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET) { retry; if (retry 200) return 0; } SPI_I2S_SendData(SPI1, TxData); retry 0; while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) RESET) { retry; if (retry 200) return 0; } return SPI_I2S_ReceiveData(SPI1); }这个函数里的超时重试机制很重要。如果 SPI 外设配置有问题或者硬件连接断开没有超时的话程序会死等在 while 循环里。加上重试计数至少能返回一个错误值让上层逻辑处理。200 次这个数字不是绝对的你可以根据实际时钟频率调整原则是给足正常传输的时间但不要长到让故障响应变得迟钝。3. W25Q64 指令集与操作时序拆解3.1 核心指令一览与使用场景W25Q64 的指令不少但常用的就那么十来条。我把它们按功能分类整理成表格方便你查阅指令名称指令码功能说明使用场景Write Enable0x06写使能每次写/擦除前必须发送Write Disable0x04写禁止写操作完成后可选发送Read Status Reg10x05读状态寄存器1查询 BUSY 位和 WEL 位Read Data0x03读数据任意地址读取Fast Read0x0B快速读高频时钟下使用Page Program0x02页编程写入数据每页最多256字节Sector Erase0x20扇区擦除擦除4KBBlock Erase 32KB0x52块擦除擦除32KBBlock Erase 64KB0xD8块擦除擦除64KBChip Erase0xC7整片擦除擦除全部8MBJEDEC ID0x9F读制造商和设备ID检测芯片是否存在Release Power Down0xAB释放掉电模式唤醒芯片这里面最核心的是Write Enable0x06。W25Q64 有一个写使能锁存器WEL每次执行页编程、扇区擦除、块擦除之前都必须先发 0x06 把 WEL 置 1。操作完成后 WEL 会自动清零。如果你忘了发 0x06 就直接发 0x02芯片会默默忽略你的写入指令数据不会报错但也不会写进去。这个坑我踩过当时读出来全是 0xFF查了半天以为是硬件问题。Read Status Register0x05是另一个高频指令。状态寄存器的 bit0 是 BUSY 位芯片在执行擦除或编程时会把它置 1完成后自动清零。你在发完擦除指令后必须循环读状态寄存器直到 BUSY 变 0才能发下一条指令。擦除一个扇区典型耗时 45ms最坏情况 400ms如果你不等它完成就发读指令读出来的数据是不可靠的。3.2 读操作从 JEDEC ID 到任意地址读取调试任何 SPI Flash第一步都是读 JEDEC ID。这个操作不依赖擦除状态也不改变芯片内容纯粹是验证通信链路是否正常。u32 W25Q64_ReadID(void) { u32 temp 0; W25Q64_CS_LOW(); SPI1_ReadWriteByte(0x9F); temp | SPI1_ReadWriteByte(0xFF) 16; temp | SPI1_ReadWriteByte(0xFF) 8; temp | SPI1_ReadWriteByte(0xFF); W25Q64_CS_HIGH(); return temp; }正常应该返回 0xEF4017。0xEF 是厂商 ID0x4017 是设备 ID。如果你读出来是 0x000000 或者 0xFFFFFF先检查硬件连接再检查 SPI 模式配置。我遇到过读出来 0xEF4017 但偶尔变成 0xEF0017 的情况后来发现是 SCK 走线太长导致高位数据采样错误降低时钟频率就好了。读数据用 0x03 指令发送指令码和 24 位地址后就可以连续读出任意长度的数据。地址会自动递增读到芯片末尾会回卷到 0x000000。这里有个细节读操作不需要写使能也不需要等待 BUSY 位只要芯片不在擦除或编程中随时可以读。void W25Q64_Read(u8 *pBuffer, u32 ReadAddr, u16 NumByteToRead) { W25Q64_CS_LOW(); SPI1_ReadWriteByte(0x03); SPI1_ReadWriteByte((ReadAddr 16) 0xFF); SPI1_ReadWriteByte((ReadAddr 8) 0xFF); SPI1_ReadWriteByte(ReadAddr 0xFF); while (NumByteToRead--) { *pBuffer SPI1_ReadWriteByte(0xFF); } W25Q64_CS_HIGH(); }注意发送地址时是高位在前先发 bit23 到 bit16再发 bit15 到 bit8最后发 bit7 到 bit0。这个顺序搞反了读出来的数据就完全不对。3.3 写操作页编程的边界与等待逻辑写数据比读数据复杂得多因为 Flash 的物理特性限制。W25Q64 的写入最小单位是页每页 256 字节。你可以一次写少于 256 字节但绝对不能跨页写。比如你想从地址 0x0000F0 开始写 32 字节写到 0x000110 就跨页了芯片会在页边界处回卷到本页开头覆盖掉之前写的数据。正确的做法是每次写入前计算当前页剩余空间确保不跨页。如果数据长度超过页剩余空间就分多次写。void W25Q64_WritePage(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite) { W25Q64_Write_Enable(); W25Q64_CS_LOW(); SPI1_ReadWriteByte(0x02); SPI1_ReadWriteByte((WriteAddr 16) 0xFF); SPI1_ReadWriteByte((WriteAddr 8) 0xFF); SPI1_ReadWriteByte(WriteAddr 0xFF); while (NumByteToWrite--) { SPI1_ReadWriteByte(*pBuffer); } W25Q64_CS_HIGH(); W25Q64_WaitForWriteEnd(); }W25Q64_WaitForWriteEnd()是必须的它循环读状态寄存器直到 BUSY 位清零。页编程典型耗时 0.7ms最坏 3ms。如果你不等它完成就发下一条写指令芯片会忽略后面的指令数据就丢了。还有一个关键点写入之前目标区域必须是擦除过的0xFF。如果你往一个已经有数据的地址写新数据只能把 1 变成 0不能把 0 变成 1。比如原来存的是 0x00你想写 0xFF结果还是 0x00。所以完整的写入流程永远是擦除 → 等待擦除完成 → 写使能 → 页编程 → 等待编程完成。3.4 擦除操作扇区、块与整片的选择策略W25Q64 提供三种粒度的擦除4KB 扇区擦除、32KB 块擦除、64KB 块擦除还有整片擦除。选哪种取决于你要擦多大范围。如果你只需要改几个字节但目标区域不是 0xFF就必须擦除整个扇区4KB。这意味着你要先把扇区里其他数据读到 RAM 里擦除后再把修改后的数据写回去。这个“读-改-写”流程是 SPI Flash 使用的核心模式后面我会详细展开。扇区擦除典型耗时 45ms最坏 400ms。块擦除和整片擦除更慢整片擦除可能要几十秒。所以除非必要不要用整片擦除。我在量产测试时用过一次整片擦除200 片板子同时跑等了将近一分钟才全部完成。void W25Q64_EraseSector(u32 SectorAddr) { W25Q64_Write_Enable(); W25Q64_WaitForWriteEnd(); W25Q64_CS_LOW(); SPI1_ReadWriteByte(0x20); SPI1_ReadWriteByte((SectorAddr 16) 0xFF); SPI1_ReadWriteByte((SectorAddr 8) 0xFF); SPI1_ReadWriteByte(SectorAddr 0xFF); W25Q64_CS_HIGH(); W25Q64_WaitForWriteEnd(); }擦除前发写使能擦除后等 BUSY 清零这个流程跟页编程一样。注意擦除地址只需要 24 位中的有效位低 12 位会被忽略因为扇区擦除总是对齐到 4KB 边界。4. 完整读写流程的代码组织与状态机设计4.1 从“读-改-写”到通用写入函数实际项目中你很少会直接调用页编程去写数据。更常见的需求是在某个地址写入一段数据不管这个地址之前是什么内容。这就需要封装一个通用的写入函数内部处理擦除和跨页逻辑。我的做法是分三层最底层是W25Q64_WritePage和W25Q64_EraseSector中间层是W25Q64_WriteNoCheck最上层是W25Q64_Write。W25Q64_WriteNoCheck假设目标区域已经是 0xFF只处理跨页拆分W25Q64_Write则先检查目标区域是否全为 0xFF如果不是就执行读-改-写。void W25Q64_WriteNoCheck(u8 *pBuffer, u32 WriteAddr, u16 NumByteToWrite) { u16 pageremain; pageremain 256 - (WriteAddr % 256); if (NumByteToWrite pageremain) pageremain NumByteToWrite; while (1) { W25Q64_WritePage(pBuffer, WriteAddr, pageremain); if (NumByteToWrite pageremain) break; else { pBuffer pageremain; WriteAddr pageremain; NumByteToWrite - pageremain; if (NumByteToWrite 256) pageremain 256; else pageremain NumByteToWrite; } } }这个函数的逻辑是先算当前地址到页末尾还有多少字节如果剩余数据不超过这个数一次写完否则先写满当前页然后地址跳到下一页开头继续写。循环直到所有数据写完。W25Q64_Write在此基础上增加了擦除判断。它先读取目标区域的数据检查是否全为 0xFF。如果是直接调用W25Q64_WriteNoCheck如果不是就需要把整个扇区的数据读到缓冲区修改对应部分擦除扇区再写回去。这个缓冲区至少需要 4KB 的 RAM对 STM32F103 来说是可以接受的但如果你同时还有其他大内存需求就要考虑用外部 RAM 或者分块处理。4.2 状态机视角BUSY 位与 WEL 位的协同把 W25Q64 的操作看成状态机会清晰很多。芯片有两个关键状态位BUSY 和 WEL。BUSY 位在擦除、编程、写状态寄存器时自动置 1完成后清零。你发任何会改变芯片内容的指令之前都必须确认 BUSY 为 0。WEL 位在收到写使能指令后置 1在页编程、擦除、写状态寄存器完成后自动清零。如果你发了写使能但没执行写操作WEL 会一直保持 1直到你发写禁止指令或者执行一次写操作。这两个位的协同关系决定了操作顺序等 BUSY 清零 → 发写使能 → 发写/擦除指令 → 等 BUSY 清零。任何一步顺序错了操作就会失败。我见过有人在擦除指令后没有等 BUSY 清零直接发下一条写使能结果写使能被忽略后面的页编程也失败。因为芯片还在擦除中根本不处理新指令。这种问题在低速调试时不容易发现因为擦除很快完成了但如果你用高频时钟擦除时间相对变长问题就暴露了。4.3 实测中的异常数据与排查路径调试 SPI Flash 最让人头疼的是“数据不对但不知道哪里不对”。我总结了一套排查路径按顺序走基本能定位问题。第一步读 JEDEC ID。如果 ID 不对问题在硬件或 SPI 配置。检查 CS 是否正常拉低拉高SCK 是否有波形MISO 是否被正确采样。用逻辑分析仪抓一下 SPI 波形最直观。第二步擦除一个扇区然后读出来。如果读出来不是全 0xFF说明擦除没成功。检查写使能是否发送BUSY 是否等待擦除地址是否正确。第三步写一页数据然后读出来。如果数据不对检查是否跨页是否在擦除后的区域写入写使能是否在页编程前发送。第四步写多页数据然后读出来。如果只有第一页对后面不对检查跨页逻辑。如果数据整体偏移检查地址发送顺序。我遇到过一个很隐蔽的问题读出来的数据每隔 256 字节就错一个字节。查了很久发现是页编程时当前页写完后没有正确计算下一页的起始地址导致地址偏移了一位。这种问题用逻辑分析仪抓波形能很快定位因为你能看到地址字节的变化。5. 性能优化与可靠性加固的实战经验5.1 时钟频率与信号完整性的平衡SPI 时钟频率直接决定读写速度。W25Q64 标称支持 104MHz但实际能跑多快取决于你的硬件设计。STM32F103 的 SPI1 最高 72MHz分频系数最小为 2所以理论最高 36MHz。我在不同板子上测试过杜邦线连接的开发板18MHz 稳定36MHz 偶尔出错PCB 走线短且阻抗匹配良好的板子36MHz 完全稳定。如果你追求速度建议先用 18MHz 跑通功能再逐步提高到 36MHz 并做长时间压力测试。提高时钟频率后读操作可以用 Fast Read0x0B指令替代普通 Read0x03。Fast Read 多一个 dummy 字节但允许更高的时钟频率。在 36MHz 下Fast Read 的稳定性明显好于普通 Read。5.2 擦除均衡与寿命管理W25Q64 的每个扇区擦除寿命是 10 万次。如果你频繁写同一个扇区比如每秒写一次配置参数不到 28 小时就能把一个扇区写坏。虽然芯片有坏块管理但那是出厂时的管理运行时的擦除均衡需要你自己做。我的做法是把配置参数分成两份存在两个不同的扇区轮流写入。每次写入时更新一个计数器记录当前使用哪个扇区。读取时比较两个扇区的计数器取较新的那个。这样每个扇区的擦除次数减半寿命翻倍。如果数据量小还可以用日志式写入在一个扇区内顺序写入写满后擦除整个扇区从头开始。这样擦除次数取决于扇区能容纳多少条记录而不是写入次数。5.3 掉电保护与数据完整性Flash 写入过程中如果掉电可能导致数据损坏。W25Q64 内部有掉电保护机制但只能保证正在编程的页不会影响其他页不能保证这一页的数据完整。对于关键数据我采用“双备份校验”策略。数据写入时先写主备份再写从备份每个备份都带 CRC 校验。读取时先读主备份CRC 校验通过就用不通过就读从备份再校验。如果两个都坏了说明发生了极端情况这时候只能报错让上层处理。这个策略的代价是需要两倍存储空间和两倍写入时间但对于配置参数这种小数据量场景完全值得。我有个项目跑在工业现场电网波动频繁用了双备份之后再也没有出现过参数丢失的问题。5.4 常见问题速查表现象可能原因排查方法读 ID 返回 0x000000CS 未拉低或 SPI 未使能检查 GPIO 和 SPI_Cmd读 ID 返回 0xFFFFFFMISO 未连接或从机未响应检查硬件连接擦除后读不是 0xFF写使能未发送或 BUSY 未等待检查操作顺序写入后读数据错乱跨页写入或地址顺序错误检查页边界和地址发送偶尔数据错误时钟频率过高或信号完整性问题降低时钟或加匹配电阻写入无效目标区域未擦除先擦除再写入状态寄存器读不到 BUSYSPI 模式配置错误检查 CPOL/CPHA这张表是我调试时积累的基本上覆盖了 90% 的常见问题。遇到问题先查表能省很多时间。6. 从裸机驱动到项目集成的衔接思路6.1 驱动层的接口设计裸机驱动跑通之后下一步是把它集成到项目里。我建议把 W25Q64 的驱动封装成独立的模块对外只暴露几个接口初始化、读、写、擦除、读 ID。内部的状态管理和等待逻辑全部隐藏起来上层不需要关心 BUSY 位和写使能。接口设计上读和写用u32地址和u16长度返回值用u8表示成功或失败。不要用void返回值否则上层无法判断操作是否完成。擦除接口可以按扇区粒度暴露也可以按地址范围暴露后者对上层更友好。如果你用的是 RTOS还需要考虑互斥。SPI 总线是共享资源多个任务同时访问会冲突。我通常用一个互斥信号量保护整个 W25Q64 操作序列从 CS 拉低到 CS 拉高作为一个原子操作。这样虽然降低了并发性但保证了数据一致性。6.2 文件系统移植的注意事项如果你打算在 W25Q64 上跑文件系统比如 FatFs 或者 LittleFS有几个点需要特别注意。FatFs 需要扇区读写接口W25Q64 的扇区是 4KB正好匹配 FatFs 的默认扇区大小。但 FatFs 的写入操作可能只写部分扇区而 Flash 必须先擦除。所以你需要实现一个“读-改-写”的磁盘写入函数把整个扇区读到缓冲区修改后擦除再写回。这个操作很耗时4KB 扇区的读-改-写大概需要 50ms 到 100ms。LittleFS 对 Flash 更友好它本身就是为 Flash 特性设计的支持磨损均衡和掉电保护。但 LittleFS 需要块设备接口块大小可以配置为 4KB。移植 LittleFS 的工作量比 FatFs 大一些但长期来看更可靠。不管用哪个文件系统都建议在 W25Q64 上划分区域前面一部分给文件系统后面一部分留给裸数据。这样即使文件系统损坏裸数据区域也不受影响。6.3 调试工具与验证方法调试 SPI Flash逻辑分析仪是必备工具。我用的是 8 通道 24MHz 采样率的入门款抓 SPI 波形足够了。重点看四个信号CS、SCK、MOSI、MISO。CS 的拉低拉高时机、SCK 的边沿对齐、MOSI 的指令码和地址、MISO 的响应数据一目了然。如果没有逻辑分析仪可以用 STM32 的 GPIO 翻转来辅助调试。在关键操作前后翻转一个空闲引脚用示波器看时序。虽然不如逻辑分析仪直观但也能定位大部分问题。软件层面我习惯在驱动里加一个调试打印宏把关键操作的地址、长度、返回值打印出来。量产时关掉宏调试时打开。这样既不增加量产固件的大小又能在调试时快速定位问题。验证方法上我通常做三轮测试第一轮写固定模式0xAA、0x55、0x00、0xFF验证基本读写第二轮写随机数据验证跨页和跨扇区第三轮做长时间循环读写验证稳定性和寿命。三轮都通过才认为驱动可靠。7. 个人实操中的几个关键体会W25Q64 这颗芯片我用了很多年从最早的 SPI 驱动调试到后来的文件系统移植踩过的坑不计其数。有几个体会特别深分享出来供你参考。第一个体会是永远不要相信“应该没问题”。SPI 时序、Flash 状态、电源稳定性任何一个环节出问题都会导致数据错误。我养成了一个习惯每次修改驱动后先读 JEDEC ID再擦除一个扇区读出来验证再写一页读出来验证。这三步花不了两分钟但能避免后面花两小时排查。第二个体会是等待 BUSY 位的时间要留足。数据手册上写扇区擦除典型 45ms最坏 400ms。我一开始只等 50ms大部分情况没问题但偶尔失败。后来改成循环查询 BUSY 位不设固定超时问题就消失了。Flash 的擦除时间受温度、电压、寿命影响固定延时不可靠。第三个体会是地址计算要反复验证。跨页写入的地址计算是最容易出错的地方。我现在的做法是写一个测试函数从地址 0 开始每次写 100 字节写满 10KB然后读出来逐字节比较。这个测试能覆盖各种跨页情况跑一遍就能发现地址计算的问题。第四个体会是硬件设计比软件优化更重要。同样的驱动代码在布线良好的板子上跑 36MHz 没问题在杜邦线连接的开发板上 18MHz 都偶尔出错。如果你发现软件怎么调都不稳定先检查硬件。SCK 串电阻、缩短走线、加去耦电容这些硬件措施比软件重试有效得多。最后一点W25Q64 的驱动代码网上很多但质量参差不齐。有些代码没有超时处理有些没有等待 BUSY 位有些跨页逻辑是错的。我建议你理解每一行代码的原理而不是直接复制粘贴。理解之后你才能根据实际需求调整比如改成 DMA 传输、改成中断驱动、改成 RTOS 下的异步操作。这些进阶用法都建立在扎实的基础之上。

关于本文作者

来自尧图内容编辑团队

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

尧图内容编辑团队

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

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

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

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

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

网站改版的5个关键决策

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

获取专属建站方案

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

立即免费咨询