做嵌入式这些年,Flash 状态寄存器这关我栽过不少跟头。最近又在一颗 Winbond W25Q256JV 上被 QE 位狠狠教育了一轮:芯片 ID 读得出来,单线读和写都正常,但只要一进 QSPI 的四线读,数据就是乱的,甚至读回来全是 0xFF。排查到最后,问题不是时序,不是供电,就是状态寄存器里的 QE 位没有置 1,而且更离谱的是,软件写了半天,读回来还是老样子。
这颗 W25Q256JV 是颗 256Mbit 的 QSPI NOR Flash,容量大、速度上限高,很多板子拿它存固件、字库、日志,或者做 FOTA 升级。它本身属于很耐用的片子,但状态寄存器里那些写保护、QE、SRP 的位,任何一个环节不对,都能让你对着一块“看起来正常、死活不进四线模式”的 Flash 怀疑人生。这篇文章我就把这次踩坑的完整过程、状态寄存器的读法写法、以及 QE 位改不动的几种真实原因和解决方案都梳理一遍,希望你能少走点弯路。
1. 这颗 W25Q256JV 到底是什么来头
1.1 芯片本尊与小参数
W25Q256JV 是 Winbond 家 256Mbit(也就是 32MB)的 SPI NOR Flash,工作电压 2.7V 到 3.6V,常见封装有 SOIC-8、SOIC-16、WSON 等。它支持单线 SPI、双线 DIO/DOUT、四线 QIO/QOUT,也能进 QPI 模式。最高时钟可以跑到 133MHz 左右,如果是 QSPI 四线读,实际传输带宽比普通 SPI 高出一大截,这也是很多主控选它做代码运行介质或者高速日志存储的原因。
它的 JEDEC ID 通常是EF 40 19,EF是 Winbond 厂商标识,40是 SPI NOR 系列,19对应 256Mbit 容量。用9Fh命令读三个字节就能拿到这些信息。实际项目中,如果读回的厂家 ID 对不上,那就要检查接线、电平或者是不是买到了打磨片,而不是先怀疑状态寄存器。
和常见的小容量 SPI Flash 不同,W25Q256JV 有个特殊点:它默认是 3 字节地址模式,但 3 字节地址最多只能访问 16MB 空间,要访问完整 32MB,要么切换到 4 字节地址模式,要么通过扩展地址寄存器去选高字节地址。很多人在大容量 Flash 上“只能写前 16MB”的坑,其实和 QE 位没有关系,是地址模式没搞清楚。这个我放到后面专门讲。
1.2 为什么 QSPI 和 QE 位扯得这么紧
W25Q256JV 支持 Quad SPI,也就是用 IO0 到 IO3 四根线同时传数据。但芯片上电默认状态是标准 SPI 模式,IO2 和 IO3 这两个引脚并不是一开始就作为数据线工作的,它们有第二功能:IO2 还兼 WP#(写保护),IO3 还兼 HOLD#(保持)。只有把状态寄存器里的 QE 位置 1,IO2 和 IO3 才会被切换成数据线。
这就带来了一个很尴尬的先后关系:你要用四线 QSPI 功能,必须先通过单线 SPI 把 QE 位置 1;但 QE 位一旦置 1,IO2 和 IO3 就不再是 WP# 和 HOLD# 了。如果再想通过拉低 WP# 去保护状态寄存器,会发现这个引脚已经变成数据线,硬件上已经扯不上关系了。
所以 QE 位是 QSPI 功能的总开关。出厂默认值是 0,也就是默认关闭四线功能。如果软件没有初始化这个位,任何 Quad 命令都不会生效。更麻烦的是,很多主控的 QSPI 外设初始化代码不会主动去检查这个位,而是假定 Flash 出厂就配好了,结果就是各种“读 ID 正常、四线读失败”的灵异现象。
1.3 最容易踩到的应用场景
我这次就是在给一块量产板子做烧录适配时遇到的。板子用的是 STM32H7 的 QSPI 接口,Flash 就是 W25Q256JV。烧录工具在单线 SPI 模式下读 ID、擦除、写入都正常,换到 QSPI 模式做高速校验就失败。通过调试器看 Flash 状态寄存器,QE 位果然是 0。
这种问题在下面几类场景里最容易出现:
- 新板卡刚贴片,Flash 是全新原厂芯片,QE 位从未被写过。
- 换了 Flash 供应商或者批次,来料默认状态不一致。
- 以前用的是国产替换料,QE 位默认值是 1,程序依赖了这个默认值,换回 Winbond 就翻车。
- 调试阶段用上位机工具写坏过状态寄存器,导致 BP 位、SRP 位异常,Flash 被锁死。
如果你是在做量产烧录或者 bootloader 升级,这类问题影响面会很大,因为不是一两片的问题,可能是整批板子都同一现象。先把状态寄存器搞清楚,比盲目调时序要靠谱得多。
2. 状态寄存器逐位拆解
2.1 三个状态寄存器,各自管什么
W25Q256JV 的状态寄存器不是只有一个,而是有三个:Status Register 1(SR1)、Status Register 2(SR2)、Status Register 3(SR3)。每个都是 8 位,功能完全不同,千万不要混在一起。网上很多老代码只操作 SR1,遇到需要设置 QE 位时,就容易在一堆历史资料里绕晕。
SR1 主要负责写保护相关的控制,常见位包括:
- WIP(bit0):写忙标志,1 表示 Flash 正在执行写、擦除或状态寄存器写操作,完成后自动变 0。
- WEL(bit1):写使能锁存位,执行
06h写使能命令后置 1,完成一次写操作后自动清 0。 - 其他位:SRP0、SEC、TB、BPx 这些,都是区段保护相关,配合 WP# 引脚和 CMP 位决定哪些区域可写。
SR2 里有我们关心的 QE 位,也有其他重要位:
- QE(bit1):Quad 功能使能位,0 表示禁用四线模式,1 表示启用。
- SRP1、SUS、CMP、LB1-LB3、WPS 这些位,分别和状态寄存器保护、挂起、区段保护极性、OTP 锁定、保护模式选择有关。
SR3 里主要是扩展的配置位,比如某些 dummy cycle 配置、保持/复位相关功能等。实际产品开发中,改 SR3 的频率不高,但读回来确认也是必要的。
如果你手上的芯片是其他厂商或者新版型号,位定义可能有细微差别。最稳妥的方式是读 SFDP 或者查对应型号的数据手册,而不是凭经验硬套 W25Q128JV 的位布局。
2.2 QE 位在哪一个寄存器,怎么读回来
在 W25Q256JV 上,QE 位在 SR2 的 bit1。也就是说,读 SR2 后,判断SR2 & 0x02是否为 0x02 就行。
读寄存器的命令如下:
05h:读 SR1,后面跟一个空字节,返回 SR1。35h:读 SR2,后面跟一个空字节,返回 SR2。15h:读 SR3,后面跟一个空字节,返回 SR3。
在标准 SPI 模式下,这些命令都是单线指令。哪怕你最终要跑 QSPI,第一次设置 QE 时也请用 SPI 模式去操作,等 QE 置 1 后再切到 QSPI 模式。顺序反了,主控和 Flash 会在“这是数据线还是控制脚”这个问题上吵起来。
调试时我最常用的操作是:开一个 Linux spidev 或者逻辑分析仪,先发35h读回 SR2,看一眼 bit1 到底是 0 还是 1。如果读出来一直是 0,不管怎么写都还是 0,那就说明写状态寄存器的路径有问题,而不是 QE 位定义搞错了。
2.3 和状态寄存器相关的命令清单
写状态寄存器有几个命令,我列一下,方便你对照:
01h:写 SR1。31h:写 SR2。11h:写 SR3。
这三个命令都属于非易失性写操作,修改的结果掉电不会丢,适合在量产初始化时把 QE 位固定写好。但非易失性写有寿命限制,虽然状态寄存器的写次数远高于普通扇区擦写,也不要没事反复去写它。
除了非易失性写,W25Q256JV 这类 Winbond Flash 一般还支持易失性状态寄存器写,典型流程是有一条易失写使能命令配合易失写命令。易失性写的优势是快,而且不消耗非易失写寿命,适合调试。但掉电或者复位后,修改就丢了。如果你用的是易失性写,看到“重启以后 QE 又变回 0”,不要太意外,这是正常的。
几乎所有写状态寄存器操作,前面都要先发06h(Write Enable),让 WEL 位置 1,否则写命令会被忽略。这一点和普通写扇区一样,是最容易被忽略的一步。
3. QE 位“改不动”的现场复盘
3.1 现象:读取都正常,四线读写就是不行
先说当时的现象。我用逻辑分析仪抓了命令,确认主控确实在发9Fh、05h、35h,返回的数据也都符合预期,但 QE 位就是 0。于是我尝试直接发31h写 SR2,把读回来的 SR2 或上 0x02 再写回去,然后立刻读35h,结果还是 0。
这时候我最先怀疑的是命令格式有问题。因为有些同学会把命令写成01h + 数据,以为一个命令就能写所有状态寄存器。实际上01h只写 SR1,你把 QE 位的数据塞在第二个字节里,等于改的是 SR1 的某些位,SR2 根本没被动过。
另一个常见误操作是写状态寄存器之前忘了发06h。哪怕你命令发得再标准,WEL 没置 1,芯片也会礼貌地忽略你的写请求,读回来自然是原样。排除掉这两个软件层面的低级问题,就轮到硬件和锁定位了。
3.2 第一个坑:WP#/IO3 被硬件设计焊死
W25Q256JV 在标准 SPI 模式下,IO3 引脚是 WP#。这颗芯片的很多写保护机制,特别是 SRP0,都和 WP# 的电平有关。WP# 拉低时,状态寄存器的某些保护位会被锁住,你想用31h去清 QE 或者清保护位,命令发进去也是石沉大海。
我在现场排查时,万用表量到 WP# 引脚电压是 0V,再顺着板子一查,发现原理图上 WP# 就没接上拉,直接连到了地。这种情况在一些“简化设计”的板子上很常见,因为有些人觉得标准 SPI 模式下 WP# 不用,就直接焊死到地,结果后续要改状态寄存器就卡住了。
解决方法是把 WP# 拉高到 VCC,或者在 PCB 上预留一个上拉电阻位。如果板子已经量产,只能飞线。我见过为了省一颗电阻,硬生生把整批板子返工的案例。这里有个细节要注意:QE 位一旦置 1,IO3 就变成数据线了,此时 WP# 硬件保护就和这个引脚没有关系了,所以“先拉高 WP# 改 QE,再拉低 WP# 做写保护”这个先后顺序,在硬件上要提前设计好。
3.3 第二个坑:SRP0/SRP1 把状态寄存器整个锁住
除了硬件引脚,Flash 内部也有软件写保护。SR1 里的 SRP0、SR2 里的 SRP1,以及 SR2 里的 CMP、WPS 等位,组合起来可以形成非常复杂的保护规则。如果这些位被设置成某个状态,状态寄存器本身会变成只读。
常见的场景是这样的:有人在调试时想保护某个区域,把 BP 和 SRP 位都设置了一遍,结果把自己关在门外了。最典型的是 SRP0 = 1,这时候如果 WP# 又被拉低,状态寄存器写保护直接开启,任何01h、31h、11h命令都无效。
遇到这种情况,要先检查 WP# 是否为高。WP# 为高时,用01h写 SR1 把 SRP0 清 0,再检查 SR2 的 SRP1 是否也为 0。如果 SRP1 和 QE 位都在 SR2 里,写之前务必把原本读到的 SR2 值保留下来,只动你需要动的位,不要无脑写 0x00 或者 0xFF。否则可能 QE 位置 1 了,SRP1 也顺便被改出问题。
3.4 第三个坑:把命令和参数对齐,WP 位也一样
我在代码评审时发现过一类问题:写 SR2 时,直接照抄别人例程,参数写死0x02,结果把 SR2 里原有的 SUS、CMP、LB 这些位全部清掉了。后续 Flash 出现挂起功能异常,或者 OTP 锁定被误改,排查很久才发现是初始化代码把整个 SR2 覆盖了。
正确做法是:先读 SR2,保留原值,然后对这个值做按位修改,最后再写回。比如:
uint8_t sr2 = read_status_register_2(); sr2 |= 0x02; // 置 QE write_status_register_2(sr2);同样,写 SR1 时也不要只想着清某个 BP 位,就把其他区被保护区域的信息一起抹掉。最好的习惯永远是“读-改-写”三步走。
另外要注意,状态寄存器写操作不是瞬间完成的。W25Q256JV 的0x01、0x31、0x11写命令,执行时间手册给的是毫秒级别,实际上需要轮询 WIP 位判断是否写完。很多工程师写完命令马上读寄存器,自然会读到旧值,误以为“写不进去”。这不是芯片坏了,是你没等它写完。
3.5 第四个坑:写了,但用的是易失性写
有些调试工具为了方便,默认用的是易失性状态寄存器写流程。易失性写的好处是快、不伤寿命,但重启后立刻恢复出厂默认值。如果你在调试环境里成功看到 QE 位变成 1,一断电再上电又变成 0,大概率就是工具写的只是临时配置,没有落盘。
量产时一定要用非易失性写命令,写到真正的非易失性状态寄存器里。判断方法很简单:写入后断电再上电,重新读状态寄存器,如果还是希望的值,说明写进去了。这个方法也是我每次确认 Flash 配置是否生效时的标准动作。
4. 一次改到位的解决方案
4.1 排查流程图(纯步骤)
如果你现在也被 QE 位困住,可以按下面这个顺序来排查,不要一上来就换芯片:
- 读取 JEDEC ID,确认芯片型号确实是 W25Q256JV,ID 应该是
EF 40 19。 - 读 SR2,看 bit1 是否为 1。如果是 1,说明 QE 已经开了,问题可能在主控配置。
- 如果 QE 是 0,用万用表测 WP#/IO3 引脚电压,确保在标准 SPI 模式下是高电平。
- 读 SR1,确认 SRP0 为 0;读 SR2,确认 SRP1 为 0。如果任一为 1,先清掉。
- 发送
06h写使能。 - 发送
31h+ 修改后的 SR2 值。 - 轮询
05h读 SR1,等 WIP 位从 1 变成 0。 - 发送
35h读 SR2,确认 QE 位是 1。 - 断电重启,再读一次 SR2,确认非易失性写入生效。
- 主控切换到 QSPI 模式,跑四线读测试。
这套流程看起来简单,但每一步都卡住过不同的项目。特别是第 4 步,很多人以为只有 SRP0 会锁状态寄存器,忽略了 SR2 里的 SRP1。Winbond 的写保护体系是两个 SRP 位配合硬件引脚工作的,两个都必须处理干净。
4.2 Linux spidev 下的读写示例
如果你是在 Linux 下调试,最方便的是使用 spidev 驱动。下面是一段简化但能跑的示例,用来读 ID、读 SR2、写 QE 位:
#include <stdio.h> #include <stdint.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include <linux/spi/spidev.h> static int spi_fd; static void spi_xfer(uint8_t *tx, uint8_t *rx, int len) { struct spi_ioc_transfer tr; memset(&tr, 0, sizeof(tr)); tr.tx_buf = (unsigned long)tx; tr.rx_buf = (unsigned long)rx; tr.len = len; tr.speed_hz = 4000000; tr.bits_per_word = 8; if (ioctl(spi_fd, SPI_IOC_MESSAGE(1), &tr) < 0) { perror("spi_xfer"); } } static uint8_t read_sr2(void) { uint8_t tx[2] = {0x35, 0x00}; uint8_t rx[2] = {0, 0}; spi_xfer(tx, rx, 2); return rx[1]; } static void write_sr(uint8_t cmd, uint8_t val) { uint8_t wren[1] = {0x06}; uint8_t wr[2] = {cmd, val}; uint8_t rx[2] = {0, 0}; spi_xfer(wren, rx + 0, 1); // 写使能 spi_xfer(wr, rx, 2); // 写状态寄存器 usleep(20000); // 先等一段时间 // 更可靠的是轮询 WIP,这里只做示例 } int main(void) { spi_fd = open("/dev/spidev0.0", O_RDWR); if (spi_fd < 0) { perror("open"); return 1; } uint8_t mode = 0; // SPI Mode 0 ioctl(spi_fd, SPI_IOC_WR_MODE, &mode); uint8_t tx[4] = {0x9F, 0x00, 0x00, 0x00}; uint8_t rx[4] = {0, 0, 0, 0}; spi_xfer(tx, rx, 4); printf("JEDEC ID: %02X %02X %02X\n", rx[1], rx[2], rx[3]); uint8_t sr2 = read_sr2(); printf("SR2 = 0x%02X\n", sr2); if ((sr2 & 0x02) == 0) { printf("Enable QE...\n"); write_sr(0x31, sr2 | 0x02); printf("SR2 after write = 0x%02X\n", read_sr2()); } close(spi_fd); return 0; }这段代码在真实板卡上验证过基础流程。要注意的是,spi_xfer里发写使能的那一行,你如果觉得原地复制有隐患,可以单独定义一个接收缓冲,避免同一个 buffer 同时挂在 tx_buf 和 rx_buf 上。生产环境建议把usleep替换成轮询 WIP。
4.3 STM32 QSPI 初始化时的 QE 处理
如果是 STM32 这类带 QSPI 外设的 MCU,初始化步骤里可以先把 QSPI 配置成单线模式,完成 QE 设置后再切到 QSPI 模式。下面是一个基于 HAL 库的简化片段:
uint8_t check_and_enable_qe(QSPI_HandleTypeDef *hqspi) { uint8_t sr2 = 0; QSPI_CommandTypeDef cmd = {0}; // 35h: Read Status Register-2 cmd.Instruction = 0x35; cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE; cmd.AddressMode = QSPI_ADDRESS_NONE; cmd.DataMode = QSPI_DATA_1_LINE; cmd.DummyCycles = 0; cmd.NbData = 1; if (HAL_QSPI_Command(hqspi, &cmd, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return 1; } if (HAL_QSPI_Receive(hqspi, &sr2, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return 1; } if ((sr2 & 0x02) != 0) { return 0; // QE already set } // 06h: Write Enable cmd.Instruction = 0x06; cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE; cmd.AddressMode = QSPI_ADDRESS_NONE; cmd.DataMode = QSPI_DATA_NONE; cmd.DummyCycles = 0; cmd.NbData = 0; if (HAL_QSPI_Command(hqspi, &cmd, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return 1; } // 31h: Write Status Register-2, set QE sr2 |= 0x02; cmd.Instruction = 0x31; cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE; cmd.AddressMode = QSPI_ADDRESS_NONE; cmd.DataMode = QSPI_DATA_1_LINE; cmd.NbData = 1; if (HAL_QSPI_Command(hqspi, &cmd, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return 1; } if (HAL_QSPI_Transmit(hqspi, &sr2, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return 1; } HAL_Delay(20); // 重新读回,确认 QE 已经写入 memset(&cmd, 0, sizeof(cmd)); cmd.Instruction = 0x35; cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE; cmd.AddressMode = QSPI_ADDRESS_NONE; cmd.DataMode = QSPI_DATA_1_LINE; cmd.NbData = 1; uint8_t verify = 0; HAL_QSPI_Command(hqspi, &cmd, HAL_QSPI_TIMEOUT_DEFAULT_VALUE); HAL_QSPI_Receive(hqspi, &verify, HAL_QSPI_TIMEOUT_DEFAULT_VALUE); return (verify & 0x02) ? 0 : 1; }这段代码有几个细节值得说一下。第一,初始化阶段 QSPI 外设的指令模式虽然是QSPI_INSTRUCTION_1_LINE,但 QSPI 外设本身已经在跑了,只是指令和数据的物理接口都走单线。第二,写完非易失性状态寄存器后,我用了一个固定延时,实际项目里更严谨的做法是轮询 SR1 的 WIP 位。第三,写完后一定要读回验证,不能想当然认为函数执行成功就万事大吉。
4.4 实在不能改,有哪些绕路方案
如果 QE 位确实改不上,可能是芯片被锁死,也可能硬件设计不给你改的机会。这时可以先尝试全片擦除的方式恢复默认状态。多数 Winbond Flash 在全片擦除后,状态寄存器会回到默认值,但前提是它当前没有被保护到连擦除都不允许。如果 BP 位把全部空间保护了,擦除命令也会失败。
还有一个办法是用临时易失性写。如果你的设备只在当前运行期间需要 QSPI 高速读,不需要永久保存 QE 位,可以考虑每次上电后用易失性写开启 QE。这样做的好处是不动非易失状态,坏处是每次开机都要初始化,而且 bootloader 阶段如果还没执行到初始化代码,可能读不了 Flash 里的程序。所以这条路只适合应急,不适合正式量产。
真正到了 QE 位怎么都写不进去、又无法恢复的情况,我会建议检查:
- WP# 是否被外部拉低。
- SRP0、SRP1 是否为 1。
- 是否用错了命令,把
31h写成了01h。 - 是否忘记发写使能命令。
- 是否在 QPI 模式下乱发了 SPI 命令,导致状态机错乱。
如果这些都排除了,再用一颗新片替换试一下,仍然不行,就要考虑是不是来料有质量问题。
5. 更多隐藏坑与速查表
5.1 Flash 工作模式与 4 字节地址
W25Q256JV 容量是 32MB,默认 3 字节地址模式只能寻址低 16MB。如果你想访问后面 16MB,必须用 4 字节地址模式,或者把高地址字节写到扩展地址寄存器里。这个坑在项目里往往和 QE 位一起出现,因为很多人刚把 QSPI 调通,往高地址读数据又发现全 F。
4 字节地址模式不是状态寄存器位,而是一种独立的工作模式,有专门的进入和退出命令。要注意的是,如果你在产品里依赖 4 字节地址模式,上电后需要执行进入命令,因为这种模式不是默认状态。有些 bootloader 代码没做这一步,导致内核起来后访问高地址空间失败,这类问题排查起来非常费时间。
我建议在 Flash 初始化阶段,把这几个状态一起收敛:
- 读取并确认型号 ID。
- 检查并设置 QE 位。
- 确认地址模式,按需进入 4 字节模式。
- 检查 BP 区段保护位,避免某些区域意外写保护。
- 检查 SRP0/SRP1,确定状态寄存器本身可写。
5.2 国产替代片的 QE 差异
现在市面上很多 Pin-to-Pin 兼容 W25Q256JV 的国产 Flash,比如一些厂商的 25Q 系列。它们大体兼容,但状态寄存器布局、QE 默认值、写命令细节都可能不同。有的片子 QE 位默认是 1,有的 QE 位不是 SR2 bit1,有的甚至要通过 SFDP 里约定的命令去配置。
如果你把一套代码从原厂片换成替换片,最忌讳的就是只对比数据手册的容量和引脚,不看状态寄存器部分。我在一个项目里就遇到过一次:Winbond 片子的状态查询命令05h读 WIP,替换片却要求必须用15h才能读到完整状态字,导致量产刷机时经常出现“校验失败”。
所以选型和替换前,都要用 SFDP 去读一次芯片描述。W25Q256JV 支持 SFDP,命令是5Ah,首字节读到53h 46h 44h 50h,也就是 ASCII 字符 “SFDP”。通过 SFDP 参数可以拿到状态寄存器布局、擦除时间、是否支持四线等信息,而不是靠猜。
5.3 常见问题速查表
| 现象 | 可能原因 | 解决方向 |
|---|---|---|
| ID 正常,QSPI 四线读失败 | QE 位为 0 | 用31h写 SR2,置 bit1 |
31h写了 QE,读回来还是 0 | 没发06h,或 WP# 拉低 | 先写使能,量 WP# 电平 |
| 状态寄存器整体写不进去 | SRP0/SRP1 为 1 | 先清保护位,再写目标位 |
| 写 SR2 后其他功能异常 | 直接覆盖了整字节 | 先读原值,按位修改再写回 |
| QE 位断电消失 | 用了易失性写 | 改用非易失性写命令 |
| 只能访问低 16MB | 地址模式不对 | 切换 4 字节地址模式 |
| 擦除写操作一直 WIP 忙 | 供电不足或频率过高 | 检查电源和 SPI 时钟 |
| 替换国产片后行为异常 | 寄存器布局不兼容 | 用 SFDP 识别并按对应手册操作 |
这张表是我平时做 Flash 调试时的速查底稿。很多问题一眼看上去像是硬件故障,实际上都是状态寄存器配置没到位。
5.4 个人实操体会
这次 W25Q256JV 的 QE 位问题,最后修起来其实很简单,就是硬件飞线把 WP# 拉高,再按标准流程把 SR2 的 bit1 置 1。但排查过程花了大半天,因为一开始我太相信软件代码,反复调总线时序,甚至怀疑主控 QSPI 外设的引脚映射配错了,完全没往 WP# 这个硬件引脚上想。
多做几次这类问题后,我的习惯变了:任何 SPI/QSPI NOR Flash 出问题,先查三个东西——供电、WP#/HOLD# 引脚电平、状态寄存器。状态寄存器里再按顺序检查 WIP、WEL、SRP、BP、QE 这几个位。只要这五类都正常,Flash 本身基本就是健康的,剩下的才是主控时序和命令格式问题。
另外,量产初始化代码里我一定会保留一个“确认 QE 位”的步骤,而不是默认一次配置永久有效。因为来料批次、烧录工具、调试器都可能在调试阶段把状态寄存器动过,代码里多一次读取和判断,能省掉很多售后排查时间。
最后分享一个小技巧:所有写状态寄存器的代码,记得在最后加一个回读校验。不要只在初始化时写一次就再也不看它,因为某些异常掉电场景下,状态寄存器可能出现非预期变化。回读校验的动作很简单,成本极低,但能帮你把“我觉得应该写进去了”变成“我确认写进去了”。这个习惯,我建议所有做 Flash 相关开发的工程师都保留下来。