1. 项目概述与核心价值
搞嵌入式开发,特别是基于TI CC254x这类经典蓝牙4.0芯片做智能硬件,固件升级是个绕不开的坎。传统方式要么拆外壳接调试器,要么预留串口,对已经部署的产品和用户体验来说都是噩梦。空中固件升级(OAD, Over-the-Air Download)技术,就是为解决这个痛点而生的。它允许你通过蓝牙BLE连接,直接把新的固件程序“无线推送”到设备里,实现无感更新,这对于物联网设备、可穿戴产品的生命周期维护至关重要。
上篇我们搭建了基础工程并配置了Image A,也就是设备出厂时内置的、带有OAD引导功能的初始固件。这篇我们接着干,目标是配置出用于升级的Image B,并最终完成从生成升级文件到实际无线烧录的全过程。整个过程涉及IAR工程配置、链接脚本修改、版本号管理以及上位机工具使用,任何一个环节的疏漏都可能导致升级失败。我把自己在多个项目里趟过的坑、总结的技巧都揉在这里了,目标是让你看完就能动手,一次成功。
2. Image B工程配置:镜像的“另一半”
Image B,顾名思义,就是我们准备通过空中方式推送给设备的新固件。它在逻辑上是独立于Image A的另一个应用程序镜像。在TI的OAD架构里,芯片的Flash存储空间被划分为多个区域,分别存放Image A、Image B以及相关的元数据(如CRC校验、版本号等)。我们的工作,就是在IAR环境中,复制并改造Image A的配置,生成一个针对Image B的编译目标。
2.1 创建Image B编译配置
首先,在IAR Embedded Workbench中打开你的工程。找到菜单栏的Project -> Edit Configurations...。这里会列出当前工程所有的编译配置,默认应该已经有了Debug和Release,以及我们上篇创建的Image_A。
点击
New...按钮,创建一个新的配置。在弹出的对话框中,输入配置名称,例如Image_B。这里有个关键技巧:在“Copy settings from”下拉框中,务必选择Image_A。这能确保Image B继承Image A的所有编译器、链接器基本设置,避免从头配置可能引入的差异和错误。点击OK后,Image_B就会出现在配置列表中。设置活动配置:创建完成后,在IAR工具栏的编译配置下拉框(通常显示为
Debug或Active的地方),选择刚刚创建的Image_B,将其设为当前活动配置。这意味着后续所有的编译、链接操作都是针对Image B进行的。
注意:很多朋友在这一步会忽略“复制自Image_A”的操作,或者错误地复制了Debug配置,导致后续的预定义宏、链接脚本路径对不上,编译虽然能过,但生成的二进制文件根本无法用于OAD。务必确保源头正确。
2.2 关键配置项修改:指向B镜像
创建配置只是搭好了架子,核心在于修改几个关键选项,让编译系统知道:“现在是在为Image B区域生成代码”。这里涉及到三个地方的改动,它们共同决定了最终二进制文件的链接地址、内容标识和输出格式。
2.2.1 预处理器定义切换
这是最核心的一步,它通过宏定义来控制代码的编译条件。点击Project -> Options...(快捷键Alt+F7),确保左上角选中的是Image_B配置。
- 在选项对话框中,找到
C/C++ Compiler分类。 - 选择其下的
Preprocessor选项卡。 - 在
Defined symbols(已定义符号)输入框中,找到原本为Image A定义的HAL_IMAGE_A。 - 将其修改为
HAL_IMAGE_B。这个宏会在TI的协议栈和驱动源码中被广泛使用,例如,它决定了OAD服务初始化时是作为接收方(Image A)还是作为被升级的应用程序(Image B),也决定了中断向量表重映射等底层操作。改错了,代码逻辑就全乱了。
2.2.2 编译后命令修正
这一步确保编译成功后,能自动调用Hex转换工具,并且输出到正确的目录。同样在Image_B的配置选项下:
- 找到
Build Actions分类。 - 查看
Post-build command line(构建后命令行)。这里通常是一行调用hex500.exe或类似工具的命令,用于将链接生成的.out或.xcl文件转换成Intel Hex格式(.hex)。 - 检查这行命令中的输出路径。通常,路径里会包含配置名(如
Image_A)。你需要将其中的路径指向Image_B的文件夹。例如,将$PROJ_DIR$\Exe\Image_A\*.hex改为$PROJ_DIR$\Exe\Image_B\*.hex。这样,Image B生成的Hex文件就不会和Image A的混在一起。
2.2.3 链接器配置文件更换
链接器脚本(.xcl或.icf文件)决定了代码、常量、变量在芯片内存地址空间中的具体存放位置。Image A和Image B必须被链接到Flash中不同的、非重叠的地址区间。
- 在选项对话框中,找到
Linker分类。 - 选择
Config选项卡。 - 在
Linker configuration file区域,你会看到当前使用的链接器配置文件,例如lnk51ew_cc2540F256_boot.xcl(这是Image A用的,通常带boot字样)。 - 点击
Override default,然后浏览选择专为Image B准备的链接器脚本。这个文件通常和Image A的脚本在同一目录,但名称不同,例如lnk51ew_cc2540F256.xcl(不带boot)。TI的BLE协议栈包里通常会提供这两类文件。务必确认你选择的文件确实是将代码链接到Image B区域的脚本。
完成以上三步,Image B的工程配置才算基本正确。此时,你可以尝试编译一下工程。如果配置无误,应该能成功生成Image_B.hex文件。但请注意,到此为止生成的Hex文件,并不能直接用于空中升级。它只是我们开发调试过程中的一个中间产物。真正的OAD升级文件,需要我们下一步专门生成。
3. 生成OAD专用BIN文件
为什么需要单独的BIN文件?因为TI的OAD协议在上位机(如BLE Device Monitor)与设备传输时,使用的是特定的二进制(BIN)格式。这种格式不仅包含纯粹的应用程序机器码,还在文件头部或尾部添加了OAD所需的元数据,如图像类型(Image A/B)、版本号、CRC校验和、映像大小等。Hex文件是包含地址信息的ASCII文本格式,不适合直接用于无线传输和协议解析。
3.1 配置链接器以生成Simple-Code格式
IAR链接器默认输出的是带调试信息的.out文件或标准的.hex文件。我们需要让它输出一种称为“simple-code”的纯二进制格式,作为生成OAD BIN文件的基础。
- 在
Image_B的工程选项(Alt+F7)中,导航至Linker -> Extra Output选项卡。 - 你会看到一个
Override default的复选框,以及下面的Output file和Output format选项。 - 首先,取消勾选
Override default复选框(如果它默认是勾选的)。这一步很关键,目的是先清空可能存在的旧配置。 - 然后,再次勾选
Override default。此时,下面的输入框变为可编辑状态。 - 在
Output format下拉菜单中,选择simple-code。这个格式会生成一个纯粹的、不含地址信息的二进制数据块文件(通常是.bin后缀)。 Output file一般会自动生成一个路径,例如$PROJ_DIR$\Exe\Image_B\Image_B.bin。你可以保持默认,也可以自定义。确保输出目录(Exe\Image_B)存在。
这个.bin文件是包含了整个Image B应用程序代码的原始二进制,但它仍然缺少OAD协议要求的头信息。
3.2 修改OAD映像版本号
OAD协议依靠版本号来判定设备中的固件是否需要更新。每次生成新的升级文件,都必须递增版本号。设备端的OAD服务会比较接收到的映像版本号与当前运行映像的版本号,只有新版本的映像才会被接受并写入Flash。
这个版本号通常定义在工程中的一个源文件里,例如OAD_target.c或oad.h。根据你的工程引用,找到它。
- 在IAR的工程文件树中找到并打开
OAD_target.c文件。 - 搜索
OAD_IMAGE_VERSION或类似的宏定义。你会找到类似这样的行:
或者对于Image B的配置,它可能已经是另一个值。#define OAD_IMAGE_VERSION 0x0000 // 初始Image A的版本可能是0 - 将其修改为一个比当前设备中Image B版本更高的值。例如,如果这是你第一次生成Image B,设备里没有,你可以设为
0x0001。如果是为了升级,则必须大于设备中现有的版本。在示例中,常会看到从0x0000改为0x0002。版本号通常用16位无符号整数表示,递增即可。#define OAD_IMAGE_VERSION 0x0002 // 修改为新的版本号
实操心得:版本号管理是OAD的纪律。我建议建立一个简单的版本管理规则,比如主版本号(高8位)用于重大功能更新,次版本号(低8位)用于小修复。每次发布新固件前,务必确认版本号已更新且大于所有已发布版本。忘记改版本号是导致“升级无效”最常见的原因之一。
3.3 执行编译与文件生成
完成上述配置后,点击IAR的编译按钮(或按F7)进行完整重建(Rebuild All)。这次编译会执行两个关键动作:
- 根据
HAL_IMAGE_B宏和Image B的链接脚本,编译链接出针对Image B地址空间的应用程序。 - 根据
Extra Output设置,生成simple-code格式的.bin文件。
编译成功后,去Exe\Image_B目录下查看。你应该能看到至少两个文件:Image_B.hex(调试用)和Image_B.bin(OAD基础文件)。
但是,请注意!直接生成的这个Image_B.bin可能仍然不是最终可用的OAD文件。TI的OAD工具链通常还需要一个后处理步骤,使用一个名为oad_image_tool.exe的工具(或类似工具,具体名称可能随协议栈版本变化),为这个原始的.bin文件添加OAD头信息(包含版本号、CRC、长度等),生成最终的.bin文件。这个工具可能在协议栈的Tools\OAD目录下。你需要查阅你所用BLE协议栈版本(如BLE-CC254x-1.4.0)的官方文档,找到正确的工具和命令行参数。一个典型的命令可能像这样:
oad_image_tool.exe Image_B.bin -i 0x0002 -t onchip其中-i指定映像ID(常与版本号关联),-t指定映像类型(如onchip表示片上Flash)。务必使用协议栈自带的工具处理,不同版本芯片(CC2540/CC2541)和Flash容量(128K/256K)的参数可能不同。
4. 空中升级实战与问题排查
万事俱备,只欠东风。现在我们有了一开始的Image A固件(已烧录到设备中),也有了准备好的、带正确OAD头的Image B升级文件(.bin)。接下来就是通过上位机软件进行真正的无线升级。
4.1 升级操作步骤
硬件准备:确保你的CC254x开发板或产品已烧录好包含OAD Profile的Image A固件,并已上电运行。设备应该处于广播状态(例如,KeyFobDemo例程中,按下某个键开始广播)。
启动上位机工具:使用TI提供的
BLE Device Monitor或SmartRF Flash Programmer 2(如果支持OAD)。这里以BLE Device Monitor为例。扫描与连接:
- 打开BLE Device Monitor,在扫描界面应该能看到你的设备(例如“KeyFobDemo”)。
- 点击“Connect”建立BLE连接。连接成功后,在“Event Log”或“Services”标签页中,你应该能看到一个名为“OAD”的服务被发现。这表明设备端的OAD功能已就绪。
选择升级文件:
- 在BLE Device Monitor的菜单或标签页中找到OAD/编程相关的功能,通常叫“Program (OAD)”或“Firmware Update”。
- 点击后,会弹出文件选择对话框。导航并选择你刚刚生成的、经过
oad_image_tool处理后的最终Image B的.bin文件。
验证与启动升级:
- 选择文件后,上位机软件通常会解析文件头,并显示映像的元信息,如版本号、大小等。这是极其重要的一步!你必须在这里确认显示的版本号与你代码中定义的
OAD_IMAGE_VERSION一致,并且高于设备当前运行的Image B版本(如果是首次升级,则高于Image A中记录的B版本,通常为0)。如果版本号没有更新或反而更低,升级会被设备拒绝。 - 确认信息无误后,点击“Start”或“Program”按钮。
- 选择文件后,上位机软件通常会解析文件头,并显示映像的元信息,如版本号、大小等。这是极其重要的一步!你必须在这里确认显示的版本号与你代码中定义的
等待升级完成:
- 上位机会通过BLE连接,将BIN文件分块发送给设备。设备端的OAD服务接收数据,校验,并写入Flash的Image B区域。
- 界面会显示传输进度条,从0%到100%。
- 传输完成后,设备通常会自动复位(或需要手动复位),并从新的Image B启动。此时,你可以通过设备的新功能或串口打印信息来验证升级是否成功。
4.2 常见问题与深度排查技巧
即使步骤看似正确,OAD过程也常常会遇到各种问题。下面是我在多个项目中总结的“避坑指南”。
| 问题现象 | 可能原因 | 排查思路与解决方案 |
|---|---|---|
| 上位机找不到OAD服务 | 1. 设备运行的固件不是Image A,或Image A未正确包含OAD服务。 2. 设备未进入可连接/可发现模式。 3. BLE连接参数不兼容。 | 1.确认固件:使用调试器读取芯片Flash,或通过串口打印,确认当前运行的是正确的Image A。 2.检查广播:使用手机BLE扫描App(如LightBlue)确认设备在广播,且广播数据中可能包含OAD服务UUID(0xF000FFC0-0451-4000-B000-000000000000)。 3.简化环境:关闭其他BLE设备,靠近测试。 |
| 能连接,但选择BIN文件后无法开始升级 | 1. BIN文件格式错误,缺少OAD头或头信息错误。 2. 版本号未递增或设置错误。 3. BIN文件大小超出Image B区域容量。 | 1.验证BIN文件:使用十六进制编辑器查看文件开头,检查是否有OAD头结构(魔数、CRC、版本等)。与协议栈文档对照。 2.核对版本号:用 oad_image_tool或自定义脚本打印BIN文件头信息,与代码定义、设备当前版本对比。3.检查链接脚本:确认Image B的链接地址和大小未超出芯片Flash的Image B分区。计算BIN文件大小。 |
| 升级进度条走到一半或特定百分比失败 | 1. BLE连接不稳定,数据包丢失。 2. 设备端Flash写入出错(电压不稳、时钟配置问题)。 3. 设备端OAD代码缓冲区或处理逻辑有bug。 | 1.优化连接:确保设备与上位机距离近,无强干扰源。尝试降低MTU(最大传输单元)。 2.电源与时钟:给设备提供稳定、充足的电源(特别是Flash写入时电流较大)。检查芯片主时钟源配置是否稳定。 3.调试设备端:在设备端OAD代码的关键点(如收包、校验、写Flash前)添加串口打印,观察失败时的状态。 |
| 升级显示100%成功,但设备重启后行为异常或变砖 | 1. Image B应用程序本身有bug,无法正常运行。 2. 中断向量表重映射错误,Image B启动失败。 3. Image B破坏了Image A或共享数据区。 | 1.独立测试Image B:将Image B的Hex文件通过调试器直接烧录到芯片的Image B起始地址,然后复位从Image B启动,测试其基本功能是否正常。这是隔离OAD过程,检验Image B自身质量的关键步骤。 2.检查启动代码:确认Image B的启动文件(如 startup_cc254x.s51)正确配置,特别是中断向量表的偏移量是否与链接地址匹配。3.检查内存布局:使用IAR的 .map文件,仔细对比Image A和Image B的符号地址,确保没有非预期的重叠。 |
| 无法生成BIN文件 | 1. IAR版本或配置问题。 2. 协议栈安装路径问题。 3. 编译后命令执行失败。 | 1.IAR版本:这可能是最坑的一点。TI的BLE-CC254x-1.4.0协议栈的工具链对IAR版本有强依赖。强烈建议使用IAR for 8051 8.20.2版本。我亲身经历,用8.30版本折腾数天,各种配置都正确,就是无法生成有效的BIN文件或OAD头工具报错。换回8.20.2后一切顺利。这可能是TI提供的post-build脚本或工具与新版IAR不兼容所致。 2.协议栈路径:确保TI BLE协议栈(如 BLE-CC254x-1.4.0)安装在没有中文和空格的路径下,最好按默认安装在C盘根目录。有些perl或批处理脚本对路径解析非常脆弱。3.查看Build Log:在IAR的编译输出窗口中,仔细查看“Post-build command line”执行后的输出信息,是否有“找不到文件”、“命令语法错误”等提示。 |
关于IAR版本和协议栈安装路径的特别强调:这绝不是玄学。TI的许多旧版工具链对构建环境非常敏感。我团队曾有一个项目,因为一位同事的电脑用户名是中文,导致协议栈相关脚本路径解析出错,同样无法生成BIN文件。解决方案是:第一,使用IAR 8.20.2;第二,将TI BLE协议栈安装到C:\Texas Instruments\这样的纯英文路径下;第三,以管理员身份运行IAR。这三点能解决90%与环境相关的生成问题。
5. OAD方案进阶思考与优化
完成基础的OAD功能只是第一步。在产品化过程中,我们需要考虑更多关于可靠性、安全性和用户体验的问题。
5.1 升级过程的安全性与可靠性加固
完整性校验双重保险:除了OAD协议自带的CRC校验,可以在应用程序层面增加额外的校验机制。例如,在Image B的固定位置(如Flash末尾)写入一个自定义的校验和(如SHA-256哈希)。设备启动时,Image A的引导程序或Image B自身在跳转前,可以计算这个哈希并与预设值比较。这能防止因Flash位翻转或传输中未检出的错误导致的程序错乱。
断点续传与状态机:简单的OAD实现可能在升级中断后需要从头开始。可以设计一个状态机,在Flash中开辟一个小区域(如InfoPage)记录当前已接收的块号、CRC临时值等。升级中断后重新连接,可以从断点处继续,而不是重启。这需要修改设备端OAD服务逻辑和上位机协议。
回滚(Rollback)机制:这是产品级OAD的必备特性。如果升级后的Image B无法正常启动(例如,启动后若干秒内无法成功握手或报告就绪),引导程序应能自动回滚到上一个已知良好的版本(Image A)。实现方式通常是在Image B区域也存储一个“备份”的Image A,或者在升级前将当前的Image A复制到安全区域。这需要更多的Flash空间和更复杂的引导逻辑。
5.2 资源受限场景下的优化
CC2540/2541的Flash资源(128KB/256KB)非常紧张,被划分为Image A、Image B后,每个镜像可用的空间可能只有40-50KB。
镜像压缩:在生成BIN文件前,对应用程序二进制进行压缩(如LZ77、Huffman编码)。设备端的引导程序或Image A需要集成解压算法。这能显著减小传输文件大小,节省传输时间和功耗,但增加了代码复杂度和启动延迟。
差分升级:不传输整个新镜像,只传输新版本(Image B v2)与旧版本(Image B v1)之间的差异(delta)。设备端根据差异文件和旧版本重构出新版本。这在修复小bug时效率极高,但需要上位机生成差分包,设备端实现合并算法,复杂度最高。
链接脚本精打细算:仔细优化链接脚本,移除未使用的库函数,将常量数据尽量放入CODE空间而非XDATA,使用
--code-const等编译选项。每一个字节的节省,都可能为功能增加赢得空间。
5.3 生产与测试流程整合
自动化脚本:将生成Image A、Image B、运行
oad_image_tool、甚至调用BLE Device Monitor进行升级测试的步骤,编写成批处理脚本或Python脚本。这能确保每次构建的一致性,减少人工操作错误。版本信息管理:将固件版本号、编译时间、Git提交哈希等元信息,自动编译到固件的一个固定段(如
.version段)中。这样,设备可以通过串口命令或蓝牙特征值上报这些信息,方便现场排查问题。出厂测试与升级测试:在生产线上,除了烧录初始Image A,可以增加一个自动化OAD测试工位。该工位自动连接设备,推送一个测试用的Image B(例如,点亮所有LED并循环),验证OAD功能是否完好,然后将其擦除或恢复回Image A。这能确保出厂设备的无线升级通道是有效的。
OAD不是一个孤立的“功能”,而是一个涉及嵌入式软件、无线协议、上位机工具、生产流程的系统工程。从能跑到稳定,再到高效、安全,每一步都需要仔细设计和充分测试。希望这篇从配置到实战再到进阶思考的详细梳理,能帮你不仅实现OAD,更能理解其背后的逻辑,打造出更可靠的产品。