2. QSPI Flash工作原理
讲XIP之前,必须先把QSPI Flash本身搞清楚。说白了,Flash就是一块可以反复擦写的存储芯片,里面密密麻麻排着存储单元。我刚开始做嵌入式那会儿,总觉得Flash和RAM差不多,后来才发现完全不是一回事——Flash读起来快,写起来慢,擦起来更慢,而且还有寿命限制。这块你不摸透,后面做XIP地址映射一定会踩坑。
QSPI Flash,全称是Quad SPI Flash。标准SPI只有4根线:CS、CLK、MOSI、MISO,一次只传1位。QSPI多出4条数据线,一次能传4位。数据吞吐量直接翻4倍,这对XIP非常关键。
2.1 串行Flash基本结构
串行Flash的内部结构,从大往小分:
- 存储阵列 —— 真正的数据存放区,由浮栅晶体管组成
- 页缓冲区 —— 页写时的临时缓存,大小通常是256字节或512字节
- 状态寄存器 —— 记录Flash忙/闲、写保护等状态
- 命令解码器 —— 解析主机发来的SPI指令
- 控制逻辑 —— 协调擦除、编程、读取等操作
我有一回调试一个Flash读超时的问题,怎么查都查不出来。后来发现是状态寄存器的忙标志位没清干净,控制逻辑卡死在内部状态机里。从那以后,我每次读写Flash都会先检查状态寄存器,确保Flash真的准备好了再操作。
| 结构单元 | 作用 | 注意 |
|---|---|---|
| 存储阵列 | 存放用户数据 | 按扇区组织,可擦除 |
| 页缓冲区 | 页写临时缓存 | 写满才触发编程 |
| 状态寄存器 | 反映Flash状态 | 读之前先查忙标志 |
| 命令解码器 | 解析SPI命令 | 常见的命令:READ、PP、SE |
关键理解:Flash的存储阵列不能按字节直接改写。你想写一个新值,必须先擦除整个扇区。这是个硬约束,设计XIP时一定要考虑进去。
2.2 页读 / 页写
页读是QSPI最基础的操作。原理很简单:CPU发一个读命令,带上起始地址,Flash就开始往外吐数据。读不需要擦除,也不需要等待——这也是为什么XIP能直接在上面跑代码。
页写就复杂了。写数据前,目标扇区必须处于擦除状态(全为0xFF)。然后CPU把要写的数据送到页缓冲区,发一个写命令,Flash内部会把缓冲区里的数据编程到存储阵列。这个过程要花点时间,典型的页写时间在0.5ms-3ms之间。
千万注意:页写前一定要确认当前页所在的扇区已经擦除过。我曾经在量产固件里忘记检查这个,结果每次写的数据都是和上一次的残留数据做"与"操作,出来的全是乱码。那个bug查了我整整两天。
页写的流程,我习惯这么记:
- 发送写使能命令(WREN)
- 发送页写命令(PP)和起始地址
- 连续发送要写入的数据(不超过页大小)
- 等待内部编程完成(读状态寄存器查忙标志)
- 验证写入的数据是否正确
你想想看,如果每次写数据都要先擦再写,那效率得多低?所以很多Flash还支持连续写——只要你写在同一个页里,可以一次性发完。这在大批量数据写入时特别有用。
2.3 扇区擦除
扇区擦除是Flash的"大动作"。擦除的时候,整个扇区(通常4KB、32KB或64KB)全部被置成0xFF。这个过程比较慢,大扇区可能要几百毫秒甚至超过1秒。
常用的擦除命令:
- 擦除4KB扇区(用20h命令)
- 擦除32KB块(用52h命令)
- 擦除64KB块(用D8h命令)
- 整片擦除(用C7h命令)
我的经验:尽量用4KB扇区擦除,虽然看起来慢,但灵活性大。我见过有人为了图快,总是用64KB块擦除——结果只是改了一个字节,却把整个64KB都擦了一遍。固件跑着跑着就崩了,因为其他代码也被擦了。
擦除的时间一般会标在数据手册里。比如说:
- 4KB扇区擦除:典型45ms,最大400ms
- 64KB块擦除:典型150ms,最大1000ms
- 整片擦除:典型2s,最大20s
这里有个坑:擦除期间的Flash是不能响应其他命令的。如果你在擦除过程中切到XIP模式,CPU读到的全是垃圾数据。所以擦除操作一定要在Flash空闲时做,或者加同步保护。
图上其实已经讲得很清楚了。QSPI接口收到命令后,命令解码器解析出要做什么。读的话,数据就直接从存储阵列出去。写的话,数据先到页缓冲区,再一次性编程进阵列。擦除则是控制逻辑直接操作存储阵列的整个扇区。
说到寿命,每一页的擦写次数是有上限的,通常10万次左右。代码区因为很少擦写,基本不用担心。但日志或者配置数据区就不一样了——我建议用磨损均衡算法,或者在设计时就留大一点空间。
嗯,关于页读、页写和扇区擦除,核心就这些。记住了:读不需要擦除,写之前必须先擦,擦除最费时间。这三个操作的差异,直接决定了你在XIP模式下该怎么安排代码和数据的位置。