w25q64扇区擦除前必须先发送0x06写使能指令并确认状态寄存器wel位为1,再发0x20+地址;页写入需确保目标页全为0xff,且地址对齐、长度≤256字节;spidev须配置spi_mode_3和≤25mhz时钟,并轮询busy位等待操作完成。

Linux 用户态下用 spidev 驱动 W25Q64 扇区擦除前必须发写使能指令
W25Q64 是标准 SPI NOR Flash,所有写/擦操作前必须先置位 WEL(Write Enable Latch)位,否则 SE(Sector Erase)或 PP(Page Program)命令会被芯片静默忽略——你不会收到错误,但读回来的数据完全不变。
实操时需严格按顺序发送三组 SPI 帧:
- 先发
0x06(WREN指令),无数据负载,仅 1 字节 - 再发
0x05(RDSR)读状态寄存器,检查 bit 0(WEL)是否为 1;若为 0,说明写使能失败,常见于忙状态未清(BUSYbit=1)或上电后未初始化 - 确认
WEL==1后,才发0x20+ 3 字节地址(扇区起始地址,低 12 位恒为 0)
注意:W25Q64 扇区大小为 4KB,地址必须对齐,例如擦除地址 0x000000、0x001000、0x002000 合法,而 0x000001 会擦错区域甚至触发保护机制。
页写入(PP)必须确保目标页未被写过,且单次最多 256 字节
W25Q64 的页写入是“只能从 1→0”的位操作,不支持覆盖写。如果某页已写入(含非 0xFF 数据),直接 PP 会导致位冲突,部分字节写不进去——现象是读回数据与预期不符,但 SPI 传输本身无报错。
安全做法是:页写入前,先用 0x03(READ)读取整页(256 字节),确认全为 0xFF;如非全 0xFF,必须先执行扇区擦除(SE)再写。
页写入命令格式固定:
- 1 字节指令
0x02 - 3 字节地址(24-bit,高位在前)
- 1–256 字节有效载荷(不可跨页)
示例(C++ 使用 spidev):
uint8_t tx_buf[260] = {0x02, 0x00, 0x00, 0x00}; // 地址 0x000000
memcpy(tx_buf + 4, data, len); // data 最多 256 字节
ioctl(fd, SPI_IOC_MESSAGE(1), &tr); // tr 中指定 tx_buf、rx_buf、len=4+len
漏掉地址或长度超 256,芯片会截断或响应异常。
spidev 设备打开后必须配置 SPI_MODE_3 和合理时钟频率
W25Q64 要求 CPOL=1、CPHA=1(即 SPI_MODE_3),若设成默认的 SPI_MODE_0(CPOL=0, CPHA=0),通信可能偶然成功(因信号边沿巧合),但极易在高速或温漂下丢帧,表现为状态寄存器读值乱码、擦除后仍可读旧数据等诡异问题。
时钟频率也不能太高:官方标称最大 104MHz(Quad IO 模式),但标准 SPI 模式下建议 ≤ 30MHz;实测中 Linux spidev 在 50MHz 下常出现 spi_transfer 超时或 EIO 错误。稳妥起见,初始化时显式设置:
-
ioctl(fd, SPI_IOC_WR_MODE, &mode)写入SPI_MODE_3 -
ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed)设为25000000(25MHz) - 务必检查 ioctl 返回值,失败时不要继续发命令
另外,W25Q64 的 HOLD 和 WP 引脚需接上拉电阻(通常 10kΩ),否则在连续操作中可能被意外拉低导致挂起。
擦除/写入后必须轮询 BUSY 状态,不能靠固定延时
扇区擦除典型耗时 100–400ms,页写入约 0.8ms,但实际取决于温度、电压和芯片个体差异。用 usleep(500000) 硬延时看似保险,但既浪费 CPU,又在低温下可能不足(擦除变慢),高温下又过度等待。
正确做法是循环读状态寄存器(0x05),检查 bit 7(BUSY):
- 每次读前仍需发
WREN(因为状态寄存器读不改变WEL,但某些批次芯片要求重置) - 两次读之间至少间隔 1μs(实际用
usleep(1)即可) - 建议加超时保护(如 1000 次循环 ≈ 1s),避免死锁
容易被忽略的是:状态寄存器 bit 0(WEL)在擦除/写入完成后自动清零,所以轮询结束后若要再次写,必须重新发 WREN——这点在批量页写入时极易遗漏,导致后续页全部写失败。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











