4. Flash编程与烧录:Flash存储器类型、Flash API函数、擦除/编程/校验流程、在线烧录技术

好,咱们进入第四章。Flash编程,这可以说是Bootloader开发中最核心、也最容易出问题的一环。我见过太多工程师,前面代码写得飞起,一到烧录环节就翻车。说白了,Flash是个“娇气”的东西,你得按它的规矩来。

4.1 Flash存储器类型

先聊聊Flash的类型。DSP上用的Flash,主要分两大类:NOR FlashNAND 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系列,那玩意儿管脚多,布线麻烦,但胜在稳定。

核心要点:Bootloader的存放介质,99%是NOR Flash。别想着用NAND做启动,除非你愿意在Bootloader里塞一套完整的FTL(Flash Translation Layer)——那工作量,够你喝一壶的。

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必须在每次EraseProgram之前调用。你以为只调一次就行?不行!每次操作前都得重新使能。我曾经因为这个,调试了整整一个下午,最后发现是状态寄存器没查——芯片还在忙,你就发下一条指令,它根本不搭理你。

我的习惯:每次调用Erase或Program之前,先轮询状态寄存器,等芯片不忙了,再发WriteEnable,然后再发操作指令。三步走,一步都不能省。

4.3 擦除/编程/校验流程

好,API知道了,那完整的烧录流程是什么样的?我画了个流程图,你一看就懂:

Flash烧录核心流程 1. 解锁Flash 2. 擦除目标扇区 3. 检查擦除结果 成功? 失败 报错,退出 成功 4. 编程写入数据 5. 校验数据 通过? 失败 重试/报错 通过 烧录完成 ✅

这个流程,看着简单,但每一步都有讲究。我展开说说:

擦除阶段

Flash写入前必须擦除,这是硬件特性决定的。NOR Flash只能把1变成0,不能把0变成1。擦除就是把整个扇区恢复成全1(0xFF)。

擦除时间通常比较长,一个4KB扇区可能需要几十到几百毫秒。这时候千万别断电!我有个同事,擦除到一半看灯没闪,以为死机了,直接按了复位——好嘛,那片Flash直接废了,换芯片才解决。

警告:擦除过程中断电,可能导致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在线烧录的典型协议流程:

  1. 握手:上位机发送特定命令(比如0xAA),Bootloader回复确认(比如0x55)
  2. 获取信息:上位机读取芯片型号、Flash大小、已存固件版本等
  3. 下发固件:上位机将固件分包发送,每包通常256字节或512字节
  4. 写入Flash:Bootloader收到一包,就擦除并写入对应扇区
  5. 校验回复:每包写入后,Bootloader计算CRC并回复上位机,确认无误再发下一包
  6. 跳转执行:全部传输完成,Bootloader校验整体固件,然后跳转到应用程序入口
避坑指南:我曾经在UART烧录协议里犯过一个低级错误——没有做超时处理。结果有一次通信线松了,Bootloader卡在等待数据的状态,死都出不来。后来我加了个看门狗定时器,5秒内收不到数据就复位,重新等待握手。这才算稳了。

还有一个细节:在线烧录时,Bootloader本身不能放在要被擦除的扇区里。否则你擦到一半,把自己给擦没了——那乐子就大了。通常的做法是:

  • Bootloader放在Flash的最低地址区域(比如0x0000~0x3FFF)
  • 应用程序放在高地址区域(比如0x4000以上)
  • Bootloader只擦除和编程应用程序所在的扇区

嗯,说到这,我想起一个经典案例。有个项目,Bootloader和应用程序放在同一个扇区里,结果远程升级时,Bootloader把自己所在的扇区也给擦了。设备直接变砖,只能寄回来用编程器重烧。从那以后,我所有项目的Flash分区图,第一件事就是确认Bootloader和应用程序的扇区不重叠。

好了,Flash编程这块,核心内容就这些。记住三个关键词:擦除、编程、校验。每一步都别偷懒,尤其是校验,那是你最后的防线。


公众号:蓝海资料掘金营,微信deep3321