衡山派Luban-Lite系统A/B分区OTA升级机制深度解析与配置指南最近在衡山派平台上做项目经常有朋友问我“你们的设备怎么远程升级固件升级失败了会不会变砖” 这确实是嵌入式产品开发中一个关键又让人头疼的问题。今天我就结合衡山派Luban-Lite系统的A/B分区OTA方案给大家从头到尾捋一遍从分区设计、环境变量控制到升级包制作和网络升级手把手教你搞定安全可靠的远程升级。这个A/B分区方案的核心思想很简单就像给电脑装双系统一个系统A系统正在运行升级包会下载并安装到另一个备用系统B系统上。升级完成后重启设备就从B系统启动。如果B系统启动失败还能自动回退到A系统确保设备永远有个能用的系统彻底告别“变砖”风险。1. 核心概念与文件结构在开始动手配置之前咱们先搞清楚OTA升级涉及哪些关键文件和它们的作用。理解了这些后面的操作才不会迷糊。1.1 OTA源码与配置文件在哪里OTA功能的源代码位于Luban-Lite SDK的特定目录下主要是一些C语言实现的接口和逻辑。packages/artinchip/ota/ ├── absystem.c # A/B系统文件系统挂载的核心逻辑 ├── absystem.h ├── burn.c # 负责把数据写入FLASH的底层接口 ├── burn.h ├── ota.c # OTA升级的主要接口函数 ├── ota.h └── test_ota.c # 一个从TF卡升级的测试程序示例而和具体开发板相关的配置文件则放在对应板级的目录里。比如你的板子叫d21x-evb那么路径就是target/artinchip/d21x-evb/pack/ ├── bootloader.bin # 引导程序 ├── ddr_init.json # DDR初始化配置 ├── env.txt # **关键** 存放控制A/B切换的环境变量 ├── image_cfg.json # **关键** 定义FLASH分区表 └── ota-subimgs.cfg # **关键** 定义要打包进升级包的文件注意env.txt、image_cfg.json和ota-subimgs.cfg是咱们今天要重点折腾的三个文件它们共同决定了OTA升级的行为。1.2 什么是A/B分区简单来说就是把存储空间FLASH划出两套独立的区域来存放系统。A系统当前正在运行的系统所在的分区。B系统备用系统分区OTA升级时新固件就写到这里。这种设计的好处是显而易见的升级过程安全升级只操作B分区不影响正在运行的A系统。即使升级中途断电A系统依然是完好的。支持回滚如果升级后的B系统启动失败可以自动切换回A系统。无缝切换用户感知不到复杂的升级过程重启后就是新系统。在衡山派的方案里A/B备份不仅针对操作系统OS本身还可以扩展到只读文件系统rodata和读写文件系统data。当然如果你的项目不需要升级文件系统可以只保留OS的备份分区节省FLASH空间。2. 分区表配置打造你的存储布局分区表定义了FLASH这块“地盘”怎么划分这是OTA的基础。配置都在target/.../pack/image_cfg.json文件里。2.1 默认的A/B分区配置我们以128MB的SPI NAND Flash为例看看默认的分区表长什么样spi-nand: { size: 128m, partitions: { spl: { size: 1m }, env: { size: 256k }, env_r: { size: 256k }, os: { size: 4m }, os_r: { size: 4m }, rodata: { size: 12m }, rodata_r: { size: 12m }, data: { size: 40m, nftl: { data: { size: - }, }, }, data_r: { size: 40m, nftl: { data: { size: - }, }, }, }, }我来解释一下这些分区spl引导加载程序分区负责最开始的硬件初始化和加载主引导。envenv_r环境变量分区。这里用了双备份env_r是env的备份确保记录启动信息、OTA状态的环境变量不会因为单点故障丢失。osos_r操作系统内核根文件系统的A/B分区。os是A系统os_r带_r后缀是B系统。rodatarodata_r只读文件系统比如放一些字体、图片资源的A/B分区。datadata_r读写文件系统存放用户数据、应用配置的A/B分区。注意它内部通过nftlNAND Flash转换层管理这是因为NAND Flash需要坏块管理和磨损均衡。注意所有分区的大小总和不能超过size: 128m这个总容量。修改时一定要算清楚。2.2 如何裁剪分区如果你的产品功能简单不需要升级rodata或data完全可以删掉对应的备份分区来节省空间。操作步骤修改image_cfg.json直接删除你不需要的_r备份分区项。例如只升级OS不升级文件系统就删掉rodata_r和data_r这两项。同步修改ota-subimgs.cfg这个文件告诉打包工具哪些文件要放进升级包。删除了分区对应的文件自然也不能打包。这个我们下一节详细讲。分区与打包文件的对应关系要升级的部分在image_cfg.json中的分区名在ota-subimgs.cfg中要删除的项操作系统(OS)os_r[file]段里的d21x_os.itb:os;只读文件系统rodata_r[file]段里的rodata.fatfs:rodata;读写文件系统data_r[file]段里的data.fatfs:blk_data;提示d21x_os.itb、rodata.fatfs这些是编译后生成的实际固件文件名字可能因芯片型号而异。3. 环境变量系统升级的“指挥棒”环境变量就像系统的“记忆”和“指令”它记录了当前系统从哪启动、下次该从哪启动、升级是否完成等关键状态。它们保存在env.txt文件中由Bootloader和操作系统共同读取和更新。3.1 环境变量详解打开target/.../pack/env.txt你会看到类似下面的内容osAB_nextA osAB_nowA rodataAB_nextA rodataAB_nowA dataAB_nextA dataAB_nowA upgrade_available0 bootlimit5 bootcount0 rodata_partnameblk_rodata rodata_partname_rblk_rodata_r data_partnameblk_data data_partname_rblk_data_r这些变量是什么意思我列个表说明一下环境变量定义说明与操作osAB_now当前OS启动分区系统自动设置表示本次启动是从A还是B分区。不要手动改。osAB_next下次OS启动分区可以手动修改告诉系统下次重启时从A还是B分区启动。OTA升级成功后程序会将其改为B。rodataAB_now/dataAB_now当前挂载的分区类似osAB_now对应只读/读写文件系统系统自动设置。rodataAB_next/dataAB_next下次挂载的分区类似osAB_next对应只读/读写文件系统可以手动修改。upgrade_available升级完成标志0表示无可用升级1表示OTA升级已完成Bootloader需要处理切换。bootcount启动失败计数当前启动分区启动系统失败的次数。失败一次加1成功启动后清零。bootlimit启动失败限制允许的最大连续启动失败次数。默认5次超过则触发版本回退。*_partname分区设备名告诉系统A/B分区在系统中对应的设备节点名如/dev/blk_rodata。如果分区表改了这里也要同步改。3.2 OTA过程中环境变量如何变化理解了每个变量的含义我们就能串起整个升级流程了升级前设备运行在A系统。osAB_nowA,osAB_nextA,upgrade_available0。升级中应用程序将新固件下载并写入B系统分区os_r,rodata_r等。升级完成应用程序调用aic_upgrade_end()函数。这个函数会做两件大事将osAB_next、rodataAB_next等变量修改为B。将upgrade_available设置为1。重启切换设备重启Bootloader看到upgrade_available1就知道有升级。然后它根据osAB_nextB的指示尝试从B系统启动。启动成功后会将osAB_now更新为B并将upgrade_available清零。回退机制如果从B系统启动失败比如连续5次即bootcount超过bootlimitBootloader会自动将osAB_next改回A并清除upgrade_available然后尝试从A系统启动。这就是安全的回滚。4. 制作OTA升级包升级包就是一个包含了新系统所有必要文件OS、文件系统等的压缩包。衡山派使用.cpio格式。4.1 配置文件ota-subimgs.cfg这个文件是制作升级包的“菜单”它列出了要打包哪些文件。内容如下[image] size ; version 1.0.0; [file] ota_info.bin:file; d21x_os.itb:os; rodata.fatfs:rodata; data.fatfs:blk_data;[image]段size升级包的总大小。这个字段是空的编译时由打包脚本自动计算并填充。你的应用程序可以解析这个值来显示下载进度条。version新系统的版本号。注意这里写1.0.0没用真正的版本号是从image_cfg.json里解析出来的。所以你要改版本号得去改image_cfg.json。[file]段每一行定义了一个要打包的文件。d21x_os.itb:os;d21x_os.itb是OS的镜像文件冒号后的os是一个标签对应image_cfg.json里的os_r分区。打包工具知道要把这个文件烧写到os_r分区去。同理rodata.fatfs:rodata;对应rodata_r分区data.fatfs:blk_data;对应data_r分区。ota_info.bin:file;这个比较特殊它是ota-subimgs.cfg文件本身转换成的二进制格式会被打包进升级包。系统升级时会先解析这个文件知道后面跟着什么数据、要烧到哪里。重要提示对于NAND Flashrodata和data虽然都是文件系统但烧写接口不同。rodata通过MTD设备接口直接烧写而data因为用了NFTL块设备层需要通过块设备接口(blk_data)来烧写。这在配置时已经自动处理好了你只需要知道有这个区别。4.2 生成升级包你不需要手动运行打包命令。在Luban-Lite项目根目录执行编译命令比如make或scons时编译系统会自动调用tools/scripts/mkcpio.py这个Python脚本根据ota-subimgs.cfg的配置将指定的镜像文件打包生成最终的ota.cpio文件。这个.cpio文件就是可以用于网络下载或本地更新的升级包。它的内部结构有一个头部包含了文件格式、大小、校验和等信息后面跟着各个镜像文件的原始数据。5. 网络OTA流程与代码实战最后我们来看看在应用程序中如何发起并完成一次网络OTA升级。5.1 网络OTA执行流程概览整个过程可以概括为下载 - 解析 - 烧写 - 切换。下载应用程序例如一个叫http_ota的程序从指定的URL下载ota.cpio文件。这里采用了分片下载的方式不是等整个文件下载完再处理而是下一片处理一片节省内存。解析每下载一片数据就尝试解析.cpio包的头部信息获取到内部包含的文件信息哪个文件、多大、要烧到哪个分区。烧写解析出文件信息后后续的数据片就会被送入烧写流程。系统会先找到对应的分区设备如/dev/os_r擦除该分区然后将数据写入FLASH。写入完成后还会进行校验。切换与重启所有文件都烧写成功后调用aic_upgrade_end()函数。这个函数就是我们前面说的它会更新环境变量设置osAB_nextB和upgrade_available1。最后调用rt_hw_cpu_reset()重启设备。启动新系统设备重启后Bootloader检查环境变量发现upgrade_available1且osAB_nextB于是尝试从B分区加载并启动新的系统。如果启动成功万事大吉如果连续失败超过bootlimit次则自动回退到A系统。5.2 编程示例实现本地OTA升级网络OTA的底层逻辑和本地OTA是相通的。这里我给出一个从SD卡读取升级包进行升级的示例代码基于test_ota.c你可以清晰地看到整个编程流程#include stdio.h #include ota.h // 包含OTA模块头文件 // 假设BUFFER_SIZE已定义例如 4096 #define BUFFER_SIZE 4096 char buffer[BUFFER_SIZE]; int local_ota_update(void) { FILE *upgrade_file; int read_size; int ret; // 0. 准备升级包假设我们已经把ota.cpio文件放在了SD卡根目录 upgrade_file fopen(/sdcard/ota.cpio, rb); if (upgrade_file NULL) { printf(错误无法打开升级文件\n); return -1; } // 1. OTA模块初始化分配必要的缓冲区等资源 ret ota_init(); if (ret ! RT_EOK) { printf(错误OTA初始化失败\n); goto cleanup_file; // 使用goto进行统一的错误处理 } // 2. 循环读取文件并提交给OTA引擎处理 while (!feof(upgrade_file)) { read_size fread(buffer, 1, BUFFER_SIZE, upgrade_file); if (read_size 0) { // ota_shard_download_fun 会处理数据解析头部、烧写FLASH if(ota_shard_download_fun(buffer, read_size) 0) { printf(错误OTA下载处理失败\n); goto cleanup_ota; // 烧写出错跳转到清理 } } } // 3. 升级包处理完毕更新环境变量标记下次启动B系统 ret aic_upgrade_end(); if (ret) { printf(警告aic_upgrade_end 返回非零值但继续重启。\n); } printf(升级成功设备即将重启...\n); rt_thread_mdelay(1000); // 延时1秒让日志输出完 // 4. 重启设备让Bootloader加载新系统 extern void rt_hw_cpu_reset(void); rt_hw_cpu_reset(); // 重启函数不会返回以下代码不会执行 return 0; // 错误处理标签 cleanup_ota: ota_deinit(); // 释放OTA资源 cleanup_file: fclose(upgrade_file); // 关闭文件 return -1; }代码关键点解析ota_init():必须首先调用用于初始化OTA内部的数据结构和缓冲区。ota_shard_download_fun(): 这是核心函数。你不需要关心.cpio格式的解析和烧写细节只需要把从文件或网络收到的数据块不断喂给它就行。它内部会完成解析分区信息、擦除、写入、校验等一系列复杂操作。aic_upgrade_end():所有数据都成功处理后调用。它的作用就是修改环境变量告诉Bootloader“升级好了下次启动B系统”。如果这步忘了调用设备重启后还是会进A系统升级就白干了。rt_hw_cpu_reset(): 软件重启设备。调用这个之后程序就结束了控制权交还给Bootloader。在实际的网络OTA项目中你只需要把fread从文件读取数据替换成从网络socket接收数据然后同样调用ota_shard_download_fun()提交即可。衡山派的OTA模块已经帮你封装好了最复杂的部分。好了关于衡山派Luban-Lite系统A/B分区OTA升级的机制和配置核心内容就是这些。这套方案在实际项目中非常稳定我负责的几个带远程升级的产品都用的它。最关键的就是理解image_cfg.json、env.txt、ota-subimgs.cfg这三个配置文件的关系以及升级程序初始化-喂数据-结束升级-重启这个标准流程。动手配置一两次你就能完全掌握了。