4. Flash编程与烧录:Flash存储器类型、Flash API函数、擦除/编程/校验流程、在线烧录技术
好,咱们进入第四章。Flash编程,这可以说是Bootloader开发中最核心、也最容易出问题的一环。我见过太多工程师,前面代码写得飞起,一到烧录环节就翻车。说白了,Flash是个“娇气”的东西,你得按它的规矩来。
4.1 Flash存储器类型
先聊聊Flash的类型。DSP上用的Flash,主要分两大类:NOR Flash和NAND Flash。嗯,这里要注意,咱们DSP Bootloader场景下,绝大多数用的是NOR Flash。
为什么?因为NOR Flash支持XIP(Execute In Place,原地执行)。什么意思?就是代码可以直接在Flash上跑,不用先搬到RAM里。这对启动来说太重要了。你想想看,上电那一刻,RAM里空空如也,谁把代码搬过去?还得靠Flash自己。
我整理了个对比表,你一看就明白:
| 特性 | NOR Flash | NAND Flash |
|---|---|---|
| 读取速度 | 快(支持随机访问) | 较慢(按页读取) |
| 写入速度 | 慢 | 快 |
| 擦除粒度 | 扇区(通常4KB~128KB) | 块(通常128KB+) |
| XIP支持 | ✅ 支持 | ❌ 不支持 |
| 可靠性 | 高(坏块少) | 低(需坏块管理) |
| 典型容量 | 1MB~64MB | 128MB~1TB+ |
| Bootloader场景 | ✅ 主流选择 | ❌ 极少使用 |
我个人习惯,在C2000或者C6000系列DSP上,首选SPI NOR Flash。比如W25Q64、SST26VF系列,便宜又好用。当然,有些老平台还在用并行NOR Flash,比如AM29LV系列,那玩意儿管脚多,布线麻烦,但胜在稳定。
4.2 Flash API函数
好,Flash选好了,怎么操作它?每个芯片厂商都会提供一套Flash API。说白了,就是封装好的底层驱动函数。你不需要直接去抠时序图、操作寄存器,调用API就行。
以TI C2000系列为例,典型的Flash API包括:
Flash_init()—— 初始化Flash控制器,配置等待状态Flash_erase()—— 擦除指定扇区Flash_program()—— 编程(写入)数据Flash_verify()—— 校验写入数据是否正确Flash_getStatus()—— 获取操作状态
如果是外挂的SPI Flash,比如W25Q64,那API就更“裸”一些:
// 伪代码示例:SPI Flash操作
uint8_t SPI_Flash_ReadID(void); // 读ID,确认芯片存在
void SPI_Flash_WriteEnable(void); // 写使能,每次写/擦前必须调用
void SPI_Flash_EraseSector(uint32_t addr); // 擦除4KB扇区
void SPI_Flash_ProgramPage(uint32_t addr, uint8_t *data, uint16_t len); // 写一页(256字节)
void SPI_Flash_Read(uint32_t addr, uint8_t *buf, uint16_t len); // 读数据
uint8_t SPI_Flash_ReadStatusReg(void); // 读状态寄存器,查忙
这里有个坑,我当年踩过。SPI Flash的WriteEnable必须在每次Erase或Program之前调用。你以为只调一次就行?不行!每次操作前都得重新使能。我曾经因为这个,调试了整整一个下午,最后发现是状态寄存器没查——芯片还在忙,你就发下一条指令,它根本不搭理你。
4.3 擦除/编程/校验流程
好,API知道了,那完整的烧录流程是什么样的?我画了个流程图,你一看就懂:
这个流程,看着简单,但每一步都有讲究。我展开说说:
擦除阶段
Flash写入前必须擦除,这是硬件特性决定的。NOR Flash只能把1变成0,不能把0变成1。擦除就是把整个扇区恢复成全1(0xFF)。
擦除时间通常比较长,一个4KB扇区可能需要几十到几百毫秒。这时候千万别断电!我有个同事,擦除到一半看灯没闪,以为死机了,直接按了复位——好嘛,那片Flash直接废了,换芯片才解决。
编程阶段
编程是按“字”或“页”写入的。比如C2000内部Flash,一次可以写8个16-bit字(128位)。SPI Flash则是按页写,一页256字节。
这里有个性能优化点:尽量凑满一页再写。你一次写1个字节和一次写256个字节,时间开销差不了太多。我习惯在Bootloader里开个256字节的缓冲区,攒满了再一次性写入。
校验阶段
校验不是可选项,是必选项!我见过太多“烧录成功但跑不起来”的案例,最后发现是某几个bit没写进去。
校验方式有两种:
- 读回比较:把刚写的数据读出来,和源数据逐字节对比。最可靠,但慢。
- CRC校验:计算整个区域的CRC值,和预存值对比。速度快,但不能定位具体错误位置。
我个人建议:Bootloader烧录时用读回比较,确保万无一失。量产烧录时可以用CRC,效率优先。
4.4 在线烧录技术
在线烧录,也叫ISP(In-System Programming)或IAP(In-Application Programming)。说白了,就是芯片焊在板子上之后,不拆下来,直接通过通信接口烧录。
在线烧录的典型场景:
- 产线烧录:板子贴片完成后,通过JTAG或UART烧录Bootloader和应用程序
- 远程升级:设备部署在现场,通过CAN、以太网或无线方式更新固件
- 调试阶段:开发过程中频繁修改代码,不用反复拔插芯片
在线烧录的实现方式,我归纳了三种:
| 方式 | 接口 | 速度 | 适用场景 |
|---|---|---|---|
| JTAG/SWD | 专用调试接口 | 快(MHz级) | 开发调试、产线烧录 |
| UART/SCI | 串口 | 慢(115200bps典型) | 远程升级、现场维护 |
| CAN/I2C/SPI | 板级通信总线 | 中等 | 工业现场、车载设备 |
这里我想重点聊聊UART在线烧录。为什么?因为它最简单、最通用。几乎所有的DSP都带UART,而且不需要额外的硬件工具,一根USB转串口线就能搞定。
UART在线烧录的典型协议流程:
- 握手:上位机发送特定命令(比如0xAA),Bootloader回复确认(比如0x55)
- 获取信息:上位机读取芯片型号、Flash大小、已存固件版本等
- 下发固件:上位机将固件分包发送,每包通常256字节或512字节
- 写入Flash:Bootloader收到一包,就擦除并写入对应扇区
- 校验回复:每包写入后,Bootloader计算CRC并回复上位机,确认无误再发下一包
- 跳转执行:全部传输完成,Bootloader校验整体固件,然后跳转到应用程序入口
还有一个细节:在线烧录时,Bootloader本身不能放在要被擦除的扇区里。否则你擦到一半,把自己给擦没了——那乐子就大了。通常的做法是:
- Bootloader放在Flash的最低地址区域(比如0x0000~0x3FFF)
- 应用程序放在高地址区域(比如0x4000以上)
- Bootloader只擦除和编程应用程序所在的扇区
嗯,说到这,我想起一个经典案例。有个项目,Bootloader和应用程序放在同一个扇区里,结果远程升级时,Bootloader把自己所在的扇区也给擦了。设备直接变砖,只能寄回来用编程器重烧。从那以后,我所有项目的Flash分区图,第一件事就是确认Bootloader和应用程序的扇区不重叠。
好了,Flash编程这块,核心内容就这些。记住三个关键词:擦除、编程、校验。每一步都别偷懒,尤其是校验,那是你最后的防线。
公众号:蓝海资料掘金营,微信deep3321