第二章:固件基础——嵌入式固件的组成结构、Bootloader与App分区、固件镜像格式解析

大家好,我是你们的老朋友。今天咱们聊聊固件基础。说实话,很多刚入行的朋友觉得固件就是“把代码烧进去就完事了”。嗯,我当年也这么想,直到第一次做量产时,发现固件分区搞错了,整批板子都得返工……那滋味,真不好受。

所以这一章,咱们把固件的“骨架”拆开看看。你想想看,一个游戏机固件,说白了就是三样东西:Bootloader、App分区、还有镜像格式。搞懂了这些,OTA升级才有的聊。

2.1 嵌入式固件的组成结构

一个典型的嵌入式固件,我习惯把它比作“三层小楼”:

  • 底层:启动代码(Bootloader)——负责初始化硬件、加载App
  • 中间层:操作系统/运行时库——比如FreeRTOS、裸机调度
  • 顶层:应用程序(App)——游戏逻辑、UI、通信协议

但实际在Flash里,它们不是简单堆叠的。我见过一个项目,把Bootloader和App放在同一个扇区,结果升级时一擦除,整个系统就变砖了。所以,物理分区是底线

核心原则:固件在Flash中的布局,必须保证Bootloader和App互不干扰。哪怕App升级失败,Bootloader还能救回来。

我个人习惯把Flash分成这么几块:

区域 起始地址 大小 内容
Bootloader 0x08000000 64KB 启动代码、升级入口
App分区A 0x08010000 512KB 主应用程序
App分区B 0x08090000 512KB 备份/升级用
参数区 0x08110000 16KB 配置、校准数据

你看,这样分区后,哪怕App升级写到一半断电了,Bootloader还能从分区B启动。这就是所谓的A/B分区升级,后面章节会细讲。

2.2 Bootloader与App分区

Bootloader,说白了就是“看门大爷”。它干三件事:

  1. 硬件初始化——时钟、内存、外设
  2. 校验App——检查CRC或签名,防止坏固件启动
  3. 跳转到App——修改向量表,然后跳转

这里有个坑,我踩过。跳转前一定要关闭全局中断,否则中断向量表一换,中断服务函数就飞了。我曾经在项目里忘了关中断,结果跳转后直接HardFault,查了两天才找到原因。

避坑指南:跳转App前,记得把外设复位到默认状态。尤其是DMA、定时器这些,不然App启动时可能莫名其妙跑飞。

App分区呢,我建议至少保留两个:

  • 分区A:当前运行版本
  • 分区B:新版本下载区

升级时,先把新固件下载到分区B,校验通过后,把启动标志指向分区B。下次重启,Bootloader就加载新版本了。如果新版本有问题,还能回滚到分区A。这个机制,我在游戏机项目里用了好几年,稳得很。

2.3 固件镜像格式解析(BIN/HEX/ELF)

说到镜像格式,很多新手会问:“我该用BIN还是HEX?” 我的回答是:看场景

2.3.1 BIN格式

BIN就是纯二进制,从起始地址开始,一个字节一个字节地排列。没有地址信息,没有校验。所以烧录时,你得告诉工具“从哪个地址开始放”。

优点:简单、体积小。缺点:没有校验,容易烧错位置。

我一般在量产时用BIN,因为生产工具可以自己加校验。但调试阶段,我从来不用BIN——万一地址错了,你都不知道固件跑哪去了。

2.3.2 HEX格式

HEX是Intel发明的格式,每行都包含地址、数据和校验。比如:

:020000040800F2
:1000000000000000000000000000000000000000F0
:1000100000000000000000000000000000000000E0
:00000001FF

每行的结构是:

  • 冒号开头
  • 长度(1字节)——本行数据字节数
  • 地址(2字节)——数据存放的起始地址
  • 类型(1字节)——00=数据,01=结束,04=扩展地址
  • 数据——实际内容
  • 校验和(1字节)——前面所有字节的和取反

你看,HEX自带地址和校验,烧录工具可以自动解析。我个人调试时特别喜欢用HEX,因为哪怕烧录一半断了,工具也能发现校验错误。

小技巧:如果你用HEX做OTA升级,记得把HEX解析成BIN再传输。因为HEX是文本格式,体积比BIN大30%左右,传输效率低。

2.3.3 ELF格式

ELF是Linux和很多高级MCU用的格式。它不光包含代码和数据,还有符号表、调试信息、段信息。比如:

ELF Header:
  Magic: 7f 45 4c 46
  Class: 32-bit
  Entry point address: 0x08000100

Sections:
  .text: 代码段,地址0x08000100,大小0x2000
  .data: 初始化数据段,地址0x20000000,大小0x400
  .bss: 未初始化数据段,地址0x20000400,大小0x800

ELF的好处是,你可以用工具(比如objdump)查看每个段的详细信息。但ELF体积大,不适合直接烧录。我一般只在开发阶段用ELF调试,发布时转成BIN或HEX。

这里有个经验:OTA升级时,千万别直接传ELF。我曾经有个同事,把ELF当BIN传了,结果固件大小翻了5倍,升级时间从30秒变成3分钟,用户投诉不断。

2.4 总结一下

这一章咱们聊了固件的组成、分区设计、还有三种镜像格式。说白了,就是让你明白:固件不是一堆代码随便扔进Flash就完事了。分区要合理,格式要选对,Bootloader要可靠。

下一章,咱们会深入OTA升级的通信协议。到时候,你会看到今天讲的分区设计是怎么派上用场的。

好,今天就到这儿。有什么问题,咱们评论区见。