烧录与固件升级
本文档介绍 AC79NN SDK(AC79_AIoT_SDK)中从"产线烧录"到"运行时固件升级"的完整链路:基于 ISD Download 工具与 isd_config.ini 的离线烧录机制,以及基于 loader 与多通道(SD/U 盘、网络、RCSP)的固件升级机制。
Purpose and Scope
本页覆盖 AC79NN SDK 中与固件写入相关的两大机制:
- 烧录(Flashing):产线/开发阶段通过杰理 ISD Download 工具配合
isd_config.ini/isd_config_debug.ini配置,把代码与资源写入 SPI Nor Flash 的完整配置体系,包括cpu/wl82/tools/isd_config_rule.c与cpu/wl82/tools/isd_config_rule_loader.c所实现的工具端规则解析。 - 固件升级(Firmware Upgrade):设备运行阶段通过
apps/common/update/升级库(如norflash_update.c)配合分区表与 loader 实现的新固件下载、校验、落盘与启动切换机制,以及各升级通道(SD/U 盘、HTTP/FTP、RCSP 等)的入口。
明确留给兄弟页面的话题:
- 具体某个升级通道的完整应用示例(如
apps/common/example/update/http_upgrade/的ext_ota_example.c、ftp_upgrade、lc_flash_upgrade)属于各通道独立页面,本页只说明它们在升级体系中的位置与调用方式。 - RCSP 协议(杰理私有双端通信协议)本身的报文格式与命令交互属于 RCSP 相关页面,本页仅提及
rcsp_update.c作为升级通道之一。 - 分区表整体设计(
app_main.c中的磁盘/分区配置)若单独成页,本页只引用其中update/dw_update分区条目。
Overview
在 AC79NN 这类带 SPI Nor Flash 的 AIoT SoC 上,"让固件进入芯片"分为两个截然不同的阶段:
- 烧录(一次性的、产线/开发阶段):芯片尚未运行用户程序或处于可被工具接管的状态,上位机通过 ISD Download 工具(
isd_download.exe,位于cpu/wl82/tools/)读取isd_config.ini中描述的 SPI 时序、入口地址、产品标识与各 flash 区域属性,将编译产物(代码 + 资源)逐段写入 Nor Flash。这个阶段由 PC 工具主导,芯片侧的 UBOOT 配合完成。 - 升级(运行阶段、面向终端用户):芯片已经在运行应用程序,新固件以文件形式(
loader.bin等)通过 SD 卡/U 盘、网络下载或 RCSP 通道进入系统,先写入临时区域,经 CRC16 校验后由 loader 在重启时完成新旧固件的搬运与切换。这个阶段由芯片侧固件主导,PC 工具不再参与。
两者的共同基础是地址分区约定:烧录配置(isd_config.ini 的 [RESERVED_CONFIG]、[VM_CONFIG]、[BURNER_CONFIG])和运行时分区表(app_main.c 中的 {"update", 21, 512, 32} 等条目)共同决定了哪些区域可写、哪些区域受保护、升级数据落在哪里。
Architecture
下图展示烧录与升级两条链路在系统中的整体架构与各自的关键组件:
flowchart TD
subgraph sg_PC["PC 侧(产线/开发)"]
ISDTool["isd_download.exe<br/>ISD Download 工具"]
RuleLoader["isd_config_rule_loader.c<br/>配置规则加载"]
RuleParse["isd_config_rule.c<br/>配置解析"]
end
subgraph sg_Flash["芯片侧(SPI Nor Flash)"]
UBOOT["UBOOT 引导"]
App["APP 应用固件"]
Loader["loader.bin<br/>升级搬运器"]
UpdateArea["update 分区"]
VM["VM 配置区<br/>VM_CONFIG"]
Reserved["保留区<br/>RESERVED_CONFIG"]
end
subgraph sg_Upgrade["运行时升级通道"]
SDUdisk["SD/U 盘升级<br/>sd_udisk_upgrade"]
NetUp["网络升级<br/>net_update / http_update"]
RCSPUp["RCSP 升级<br/>rcsp_update"]
NorUp["norflash_update.c<br/>Nor 升级核心"]
end
ISDTool --> RuleLoader --> RuleParse
RuleParse -->|"isd_config.ini<br/>SPI 时序/分区属性"| ISDTool
ISDTool -->|"下载代码/资源"| UBOOT
UBOOT -->|"写入"| Reserved
UBOOT -->|"写入"| VM
UBOOT -->|"写入"| App
UBOOT -->|"写入"| Loader
SDUdisk --> NorUp
NetUp --> NorUp
RCSPUp --> NorUp
NorUp -->|"fopen/fwrite loader.bin<br/>storage/nor_fs/C/"| Loader
Loader -->|"重启后搬运新固件"| App
App -->|"分区表: update 条目"| UpdateArea
UpdateArea -->|"临时存放新固件"| Loader
架构解读:
- PC 侧(
cpu/wl82/tools/):isd_download.exe是杰理官方烧录工具,isd_config_rule.c/isd_config_rule_loader.c负责解析isd_config.ini中的规则(SPI 模式、时钟、入口地址、区域操作属性),这些规则决定了工具"怎么写、写哪里、写之前是否擦除、是否加保护"。 - 芯片侧 Nor Flash 布局:
isd_config_debug.ini的[RESERVED_CONFIG]定义 BTIF/WTIF/PRCT 等保留区(含代码保护属性),[VM_CONFIG]定义虚拟机参数存储区大小,[BURNER_CONFIG]定义烧录器配置区;这些区域在烧录阶段一次性建立,运行阶段由 APP 与 loader 共同遵守。 - 运行时升级:
apps/common/update/norflash_update.c是 Nor Flash 升级的核心实现,它把下载到的新固件以loader.bin文件形式写入storage/nor_fs/C/,校验 CRC 后由 loader 在重启时完成搬运;SD/U 盘、网络、RCSP 只是不同"取数通道",最终都汇聚到同一套 loader 写入/校验接口。
烧录机制(ISD Download)
配置文件结构
cpu/wl82/tools/jtag/isd_config_debug.ini 是烧录配置的权威示例,文件头部注释明确说明:"配置数据按照 长度+配置名字+数据的方式存储",即工具解析时采用定长键值存储格式,键的顺序不可随意调整。整个文件按功能分为三大段:
- UBOOT 配置项(文件头部,注释标明"UBOOT 配置项,请勿随意调整顺序"):描述 SPI 时序参数与芯片启动参数。
- 产品标识区:
CHIP_NAME、PID、VID,用于烧录时匹配产品。 - Flash 空间使用配置区(
[RESERVED_CONFIG]、[VM_CONFIG]、[BURNER_CONFIG]):定义各区域地址、长度与操作属性。
##################################################### UBOOT配置项,请勿随意调整顺序 ##################################################
ENTRY=0x1D00020;//程序入口地址
SPIMD=RD_OUTPUT; #RD_OUTPUT,RD_I/O,RD_I/O_CONTINUE
SPIDW=4; #[0 1 2 4]
SPICK=3; #不能为小于3
OSC=btosc;
OSC_FREQ=24MHz; #[24MHz 12MHz]
SYS_CLK=48MHz; #[48MHz 24MHz]
#UTTX=PA05;//uboot串口tx
#UTBD=1000000;//uboot串口波特率
RESET_PIN=PB01_00_0; //port口_上下拉控制_边沿或者电平
设计意图:SPIMD/SPIDW/SPICK 决定工具以何种 SPI 读模式、数据宽度和时钟分频去访问 Nor Flash——这必须与芯片 UBOOT 的初始化时序完全一致,否则烧录后芯片无法自举;ENTRY 给出程序入口地址,RESET_PIN 则让工具能够通过 IO 控制芯片复位以进入下载模式。UTTX/UTBD 被注释掉,说明当前方案不使用 UART 下载通道(默认走 sdtap)。
Flash 分区与操作属性
配置文件的 [RESERVED_CONFIG] 区域定义烧录时对各区块的处理策略,这是"烧录"与"升级"能够共存的关键:
[RESERVED_CONFIG]
BTIF_ADR=AUTO;
BTIF_LEN=0x1000;
BTIF_OPT=1;
WTIF_ADR=BEGIN_END;
WTIF_LEN=0x1000;
WTIF_OPT=1;
PRCT_ADR=0;
PRCT_LEN=CODE_LEN;
PRCT_OPT=2;
[VM_CONFIG]
SIZE=24K;
[BURNER_CONFIG]
SIZE=32;
XXXX_ADR:区域起始地址;AUTO表示由工具自动分配起始地址,BEGIN_END表示紧接前一段的结束位置。XXXX_LEN:区域长度;CODE_LEN表示随代码长度而定。XXXX_OPT:区域操作属性(注释中定义)——0下载代码时擦除指定区域;1下载代码时不操作指定区域;2下载代码时给指定区域加上保护。
PRCT_OPT=2 表明代码区(PRCT_ADR=0、PRCT_LEN=CODE_LEN)在烧录时会被加上保护,防止运行期 APP 意外破坏自身代码;BTIF/WTIF 为保留区(蓝牙/无线相关信息),OPT=1 表示下载时跳过不操作,从而保留产线写入的射频校准数据。VM_CONFIG 的 SIZE=24K 预留虚拟机参数区,BURNER_CONFIG 的 SIZE=32 预留烧录器配置区——这些区域一旦建立,运行时升级不得越界写入。
工具端规则解析
cpu/wl82/tools/isd_config_rule.c 与 isd_config_rule_loader.c 是烧录工具侧的规则解析实现(随 SDK 以源码形式提供给客户,便于定制产线烧录流程)。isd_config_rule_loader.c 负责把配置文件加载为可执行规则,isd_config_rule.c 提供规则查询/校验逻辑;两者配合 isd_download.exe 完成"读配置 → 解析分区 → 按 OPT 执行擦除/写入/保护"的流程。工具端解析必须与芯片侧 UBOOT 对配置项顺序的预期保持一致,这正是配置文件头部反复强调"请勿随意调整顺序"的原因——配置按 长度+配置名字+数据 定长存储,顺序即协议。
固件升级机制(运行时)
Nor Flash 升级核心:norflash_update.c
运行时升级的核心实现在 apps/common/update/norflash_update.c。它通过文件系统接口把新固件写入固定路径 storage/nor_fs/C/loader.bin,并提供统一的"打开 → 写入 → 校验 → 关闭 → 启动 loader"生命周期。整个模块围绕一个全局参数结构 nor_fs_parm 工作:
typedef struct _nor_fs_part {
u32 update_file_addr; // 升级文件在 Flash 中的绝对地址
u32 update_area_start_addr; // 升级区域起始地址
u32 update_area_end_addr; // 升级区域结束地址
} nor_fs_parm;
nor_fs_parm update_norfs_parm;
#define NORFLASH_LOADER_PATH "storage/nor_fs/C/loader.bin"
static void *fd = NULL;
void register_loader_write_handler(u32(*hdl)(void *, u32));
设计意图:升级文件落地采用"文件系统 + 固定路径"而非裸地址直写,好处是上层升级通道(SD/U 盘、网络、RCSP)可以复用同一套 fopen/fwrite/fread 接口,无需感知底层 Flash 布局;update_norfs_parm 记录了升级文件地址与升级区域边界,供校验与 loader 搬运时使用。
打开升级文件的逻辑同时完成升级区域参数的采集,并做空间预检:
int norflash_f_open(u32 loader_len)
{
fd = fopen(NORFLASH_LOADER_PATH, "w+");
if (!fd) {
r_printf("update fopen err\n");
return -1;
} else {
g_printf("update fopen succ\n");
update_norfs_parm.update_file_addr = nor_get_absolute_addr();
update_norfs_parm.update_area_start_addr = nor_get_start_addr();
update_norfs_parm.update_area_end_addr = update_norfs_parm.update_area_start_addr + nor_get_capacity();
if (loader_len > nor_get_capacity) {
return -2;
}
}
return 0;
}
- 返回
-1:文件创建失败(文件系统异常或路径不可写)。 - 返回
-2:新固件长度超过升级区域容量(nor_get_capacity()),属于空间不足错误,上层应中止升级。 - 成功时
update_file_addr为文件绝对地址,update_area_*界定可写范围。
写入与校验分别由 norflash_f_write 与 norflash_update_verify 承担。校验采用增量 CRC16(CRC16_with_initval),按 512 字节分块读取并滚动计算,避免一次性申请大缓冲区:
u32 norflash_update_verify(u32 loader_len, u32 loader_crc)
{
u32 len = loader_len;
u32 crc_temp = 0;
if (fd) {
fseek(fd, NOR_FS_SEEK_SET, 0); //偏移到文件起始
u16 r_len;
u8 *temp_buf = malloc(512);
if (NULL == temp_buf) {
goto _ERR_RET;
}
u16 temp_buf_len = 512;
while (len) {
r_len = (len > temp_buf_len) ? temp_buf_len : len;
fread(fd, temp_buf, r_len);
crc_temp = CRC16_with_initval(temp_buf, r_len, crc_temp);
len -= r_len;
}
}
...
if (crc_temp == loader_crc) {
return 0; // 校验通过
}
return 1; // 校验失败
}
升级启动入口为 norflash_loader_start,它把本模块的写回调注册为 loader 的写处理器,再打开升级文件——这保证 loader 后续搬运固件时使用与下载阶段完全一致的 Flash 访问方式:
int norflash_loader_start(u32 loader_len)
{
register_loader_write_handler(norflash_f_write);
return norflash_f_open(loader_len);
}
升级通道汇聚
apps/common/update/ 目录下的其余文件均为不同取数通道,最终都调用上述 Nor 升级接口(或同类接口):
| 文件 | 通道 | 说明 |
|---|---|---|
fs_update.c | 文件系统升级 | 从可访问的文件系统(SD/U 盘等)读取升级包 |
net_update.c / net_single_backup_update.c | 网络升级 | 通过网络下载升级包,后者支持单备份方案 |
http_update.c | HTTP 升级 | HTTP 协议下载,配套 apps/common/example/update/http_upgrade/ 示例 |
lc_flash_ufw_update.c / norflash_ufw_update.c | UFW 升级 | 解析统一固件包格式(UFW)的升级 |
expand_zone_file_update.c / ex_flash_file_download.c | 扩展区升级 | 扩展 Flash 区文件下载 |
apps/common/third_party_profile/jieli/rcsp/.../rcsp_update.c | RCSP 升级 | 通过杰理 RCSP 私有协议由手机 APP 推送升级 |
示例入口位于 apps/common/example/update/:sd_udisk_upgrade/main.c(SD/U 盘升级)、ftp_upgrade/main.c(FTP 升级)、http_upgrade/example1~3(HTTP 升级,含 ext_ota_example.c 外部 OTA 示例)、lc_flash_upgrade/lc_flash_upgrade_test.c(LC Flash 升级测试)。这些示例展示的调用模式均为:获取新固件长度与 CRC → norflash_loader_start(len) → 分块写入 → norflash_update_verify(len, crc) → 重启进入 loader。
分区表对升级的支撑
运行时分区表在 apps/wifi_camera/app_main.c 与 apps/scan_box/app_main.c 中定义,其中 update 与 dw_update 条目为升级保留空间:
{"update", \t\t21, 512, 32 },
{"dw_update", \t\t21, 512, 32 },
来源:app_main.c
分区条目含义(名称、磁盘号/类型、扇区数、属性):update 分区为升级流程提供 512 扇区的暂存空间,属性 32 通常表示该分区可读写且仅升级流程使用;dw_update 是双备份(dual-write)场景下的镜像分区。升级包先落在此分区,校验通过后由 loader 搬运到代码区——这与烧录配置里 PRCT_OPT=2(代码区保护)形成闭环:运行期无法直接写代码区,只能经由 loader 在受控流程中切换。
Core Flow:一次典型升级的完整流程
以"SD 卡/U 盘放置升级包 → 设备检测并升级"为例,展示从取数到新固件生效的完整时序:
sequenceDiagram
participant App as APP 应用
participant Fs as 文件系统<br/>(storage/nor_fs)
participant Nor as norflash_update.c
participant Loader as loader.bin
participant Flash as Nor Flash
App->>Fs: 检测升级包文件 (SD/U 盘)
App->>Nor: norflash_loader_start(loader_len)
Nor->>Nor: register_loader_write_handler(norflash_f_write)
Nor->>Fs: fopen("storage/nor_fs/C/loader.bin", "w+")
Fs-->>Nor: 返回 fd,记录升级区域参数
loop 分块写入
App->>Nor: norflash_f_write(buff, len)
Nor->>Fs: fwrite(fd, buff, len)
Fs->>Flash: 写入 update 分区
end
App->>Nor: norflash_update_verify(loader_len, loader_crc)
Nor->>Fs: fseek + 分块 fread
Nor->>Nor: CRC16_with_initval 增量校验
alt 校验通过 (crc_temp == loader_crc)
Nor-->>App: 返回 0
App->>App: 记录升级标志并重启
App->>Loader: 系统重启进入 loader
Loader->>Flash: 搬运 loader.bin 到代码区
Loader->>App: 启动新固件
else 校验失败
Nor-->>App: 返回 1
App->>App: 中止升级,保留旧固件
end
流程要点:
- 取数:升级通道(SD/U 盘、网络、RCSP)只负责拿到新固件的字节流及其长度/CRC,不关心 Flash 细节。
- 落盘:
norflash_loader_start注册写回调并打开固定路径文件,后续所有写入经norflash_f_write走文件系统进入update分区,天然获得文件系统层的坏块/擦写管理。 - 校验:
norflash_update_verify用增量 CRC16 对整包重算并与下发方提供的loader_crc比对——先校验后切换,杜绝损坏固件进入代码区。 - 切换:校验通过后重启,loader 负责把暂存固件搬运到受保护的代码区。烧录配置中
PRCT_OPT=2的写保护保证了这段搬运只能由 loader 完成,APP 无法越权改写自身。
状态视图
stateDiagram-v2
[*] --> Idle: 系统正常运行
Idle --> Downloading: 检测到升级包
Downloading --> Verifying: 写入完成
Verifying --> Ready: CRC 校验通过
Verifying --> Idle: 校验失败(保留旧固件)
Ready --> Rebooting: 记录标志并重启
Rebooting --> LoaderRunning: loader 搬运固件
LoaderRunning --> Idle: 启动新固件
Downloading --> Idle: 空间不足/写入失败
失败路径集中在 Downloading(fopen 失败返回 -1、容量不足返回 -2、写入异常)与 Verifying(CRC 不匹配)两个状态;任何失败都回退到 Idle,旧固件不受影响。
Configuration Options
isd_config.ini(烧录配置)
以下配置项来自 isd_config_debug.ini(注释中同时解释了 PDCTNAME、BOOT_FIRST、UPVR_CTL 等可选升级控制项)。
| 配置项 | 类型 | 默认值/示例 | 说明 |
|---|---|---|---|
ENTRY | 十六进制地址 | 0x1D00020 | 程序入口地址,UBOOT 启动跳转目标 |
SPIMD | 枚举 | RD_OUTPUT | SPI 读模式:RD_OUTPUT / RD_I/O / RD_I/O_CONTINUE |
SPIDW | 整数 | 4 | SPI 数据宽度,可选 0/1/2/4 |
SPICK | 整数 | 3 | SPI 时钟分频,不能小于 3 |
OSC | 枚举 | btosc | 晶振来源选择 |
OSC_FREQ | 频率 | 24MHz | 晶振频率,可选 24MHz/12MHz |
SYS_CLK | 频率 | 48MHz | 系统主频,可选 48MHz/24MHz |
UTTX / UTBD | 串口 | 注释关闭 | UBOOT UART 下载 TX 引脚与波特率(当前方案关闭) |
RESET_PIN | IO 定义 | PB01_00_0 | 复位引脚:port口_上下拉控制_边沿或电平 |
sdtap | 整数 | 2 | 下载通道选择:0 关闭;1 PA9/PA10;2 USB;3 PB1/PB2;4 PB6/PB7 |
CHIP_NAME | 字符串 | AC693X | 芯片型号标识 |
PID | 字符串 | 6939B_EARPHONE_BTxx | 产品 ID,格式 芯片封装_应用方向_方案名称,长度 27 字节 |
VID | 版本字符串 | 0.0.0.1 | 厂商版本标识,长度 27 字节 |
PDCTNAME | 字符串 | 注释说明 | 产品名,升级时用于匹配产品 |
BOOT_FIRST | 布尔 | 注释说明 | 1=代码更新后提示 APP 首次启动;0=不提示 |
UPVR_CTL | 布尔 | 注释说明 | 0=不允许高版本升级低版本(禁降级);1=允许降级 |
XXXX_ADR | 地址 | AUTO/BEGIN_END/数值 | 区域起始地址;AUTO 由工具自动分配 |
XXXX_LEN | 长度 | CODE_LEN/数值 | 区域长度;CODE_LEN 随代码长度 |
XXXX_OPT | 整数 | 0/1/2 | 0=下载时擦除该区;1=下载时不操作;2=下载时加保护 |
[RESERVED_CONFIG] | 段 | BTIF/WTIF/PRCT | 保留区配置:BTIF/WTIF 各 0x1000 且 OPT=1;PRCT 覆盖整个代码区且 OPT=2(保护) |
[VM_CONFIG] SIZE | 容量 | 24K | 虚拟机参数存储区大小 |
[BURNER_CONFIG] SIZE | 容量 | 32 | 烧录器配置区大小 |
注意:配置按
长度+配置名字+数据定长存储,键的顺序不可调整;工具端isd_config_rule.c/isd_config_rule_loader.c的解析严格依赖该顺序。
运行时升级参数(代码内)
| 参数 | 类型 | 来源/默认 | 说明 |
|---|---|---|---|
NORFLASH_LOADER_PATH | 宏 | storage/nor_fs/C/loader.bin | 升级文件固定落盘路径 |
update_norfs_parm | nor_fs_parm | 全局变量 | update_file_addr、update_area_start_addr、update_area_end_addr,记录升级区域边界 |
| 校验缓冲区 | 动态 | 512 字节 | norflash_update_verify 内 malloc(512),分块读整包 |
update 分区 | 分区条目 | {"update", 21, 512, 32} | 升级暂存分区:512 扇区,属性 32 |
dw_update 分区 | 分区条目 | {"dw_update", 21, 512, 32} | 双备份升级镜像分区 |
API Reference
以下接口来自 norflash_update.c,是各升级通道对接 Nor Flash 升级的标准入口。
int norflash_loader_start(u32 loader_len)
升级流程入口:注册写回调并打开升级文件。
参数:
loader_len(u32):新 loader 的长度,用于打开文件前的空间预检。
返回:
0:成功,升级文件已创建,fd可用。-1:fopen失败(文件系统异常/路径不可写)。-2:loader_len超过nor_get_capacity(),升级区域空间不足。
说明: 内部先 register_loader_write_handler(norflash_f_write),再调用 norflash_f_open(loader_len);只有打开成功才允许后续写入。
u32 norflash_f_write(u8 *buff, u16 len)
分块写入升级数据。
参数:
buff(u8*):待写入数据块。len(u16):块长度。
返回: 实际写入字节数;fd 为空时返回 0。
u32 norflash_update_verify(u32 loader_len, u32 loader_crc)
对已写入的整包文件做增量 CRC16 校验。
参数:
loader_len(u32):loader 总长度。loader_crc(u32):下发方提供的期望 CRC16 值(调用CRC16_with_initval的初值约定)。
返回:
0:校验通过(crc_temp == loader_crc)。1:校验失败,或内存申请失败、fd无效。
内部行为: fseek 到文件起始,按 512 字节分块 fread,用 CRC16_with_initval 滚动累加 CRC;临时缓冲区用完即 free。
void norflash_update_close(void)
关闭升级文件句柄,释放 fd。升级完成或失败后应调用。
int get_nor_update_param(void *buf)
导出升级区域参数。
参数:
buf(void*):接收nor_fs_parm结构的内存指针。
返回: sizeof(nor_fs_parm);buf 为空时不拷贝。用于向 loader/上层告知升级文件的 Flash 地址与升级区域边界。
配套外部接口
void register_loader_write_handler(u32(*hdl)(void *, u32)):由 loader 侧实现,注册 Flash 写回调。u16 CRC16_with_initval(const void *ptr, u32 len, u16 i_val):增量 CRC16 计算。u32 nor_get_absolute_addr(void)/u32 nor_get_start_addr(void)/u32 nor_get_capacity(void):查询 Nor Flash 绝对地址、起始地址与容量。
Failure Modes, Edge Cases & Concurrency
以下失败模式与边界行为均可在源码中找到直接依据(norflash_update.c 的错误码与分支、isd_config_debug.ini 的注释约束)。
升级侧失败模式
| 失败场景 | 源码证据 | 行为与后果 |
|---|---|---|
| 升级文件创建失败 | norflash_f_open 返回 -1(r_printf("update fopen err")) | 升级中止;需检查文件系统挂载与 storage/nor_fs/C/ 可写性 |
| 新固件超过升级区容量 | norflash_f_open 返回 -2(loader_len > nor_get_capacity) | 升级中止,防止越界写坏相邻分区 |
| CRC 校验失败 | norflash_update_verify 返回 1 | 丢弃新固件、保留旧固件运行,不会进入重启切换 |
| 校验内存申请失败 | malloc(512) 返回 NULL 走 _ERR_RET | 返回 1 视为校验失败,安全降级 |
写入时 fd 无效 | norflash_f_write 返回 0 | 写入静默失败,最终在校验环节暴露 |
| 升级中途断电 | 校验/切换两阶段设计 | 未校验通过的半包数据不会生效;若断电发生在 loader 搬运中,由 loader 的引导保护逻辑处理(见下) |
边界与约束
- 禁止降级:
UPVR_CTL注释明确0=不允许高版本升级低版本、1=允许。默认策略为禁止降级,防止产线或用户误刷旧固件导致功能回退。 - 首次启动提示:
BOOT_FIRST控制代码更新后 APP 是否提示"首次启动",用于升级后初始化差异化数据。 - 代码区写保护:烧录配置
PRCT_OPT=2在烧录时给整个代码区(PRCT_ADR=0、PRCT_LEN=CODE_LEN)加保护;BTIF/WTIF保留区OPT=1保证升级不覆盖产线校准数据。 - 配置顺序即协议:
isd_config.ini按长度+配置名字+数据定长存储且"请勿随意调整顺序",增删配置项必须同时修改工具端isd_config_rule*.c解析逻辑,否则烧录工具与 UBOOT 解析错位。 - SPI 参数强一致:
SPIMD/SPIDW/SPICK/OSC_FREQ/SYS_CLK必须与芯片 UBOOT 初始化一致,否则"烧录成功但无法自举"。
并发与一致性
norflash_update.c使用全局单实例fd与update_norfs_parm,模块并非重入安全设计:同一时刻只允许一个升级通道执行升级。SD/U 盘与网络通道若同时触发,需由上层任务互斥(如升级标志/状态机)串行化,源码本身未提供加锁。- 校验与切换之间必须保证文件不被再次写入:
norflash_update_verify先fseek到文件头再顺序读取,任何并发写都会破坏 CRC 结果——这既是校验的正确性前提,也意味着写入完成到校验完成期间应停止该通道的数据流。 - 分区表层面,
update与dw_update分属不同分区条目,双备份升级时两个区域可独立管理;但 loader 搬运期间不允许 APP 访问代码区,重启切换是原子的(由 boot 流程保证)。
Performance & Operational Notes
- 内存占用:校验路径固定使用 512 字节临时缓冲,写入路径按
u16 len分块,整包升级的内存峰值很低,适合 RAM 受限的嵌入式场景;代价是 CRC 计算与写入均为顺序 IO,整包耗时与固件大小线性相关。 - IO 模式:升级文件经
storage/nor_fs/C/文件系统读写,相比裸地址直写多一层文件系统开销,但换来了坏块管理与擦写调度,对 Nor Flash 寿命更友好;norflash_loader_start注册写回调也保证了 loader 搬运与下载阶段使用同一 Flash 访问路径。 - 产线烧录吞吐:
isd_config.ini的SPIDW=4、SYS_CLK=48MHz等参数决定工具端写入速率;OPT=1(不操作)的保留区避免每次烧录都擦除校准数据,缩短产线节拍。 - 升级通道选择:网络/HTTP 通道(
net_update.c、http_update.c)适合 OTA 批量升级;SD/U 盘(fs_update.c、sd_udisk_upgrade)适合无网络环境;RCSP(rcsp_update.c)适合手机 APP 直连升级。各通道共享同一套校验/切换逻辑,业务上按产品形态选用即可。 - 日志:关键路径有
g_printf(成功)与r_printf(错误)日志,如update fopen succ/err、升级区域地址打印,便于产线与现场排障。
Extension Points
- 自定义写回调:
register_loader_write_handler(u32(*hdl)(void *, u32))允许替换 loader 数据写入方式,norflash_loader_start内部已用它把norflash_f_write注册为默认实现;自定义通道可在不改动校验逻辑的前提下更换落盘介质。 - 新升级通道接入:参照
apps/common/update/现有实现(fs_update.c、http_update.c、net_update.c)与apps/common/example/update/示例,新通道只需实现"获取新固件长度/CRC →norflash_loader_start→ 分块norflash_f_write→norflash_update_verify→ 重启",即可复用全部校验与切换机制。 - 版本策略:
UPVR_CTL(禁降级开关)与BOOT_FIRST(首启提示)在烧录配置中开放,产品无需改代码即可调整升级策略。 - 分区布局:
update/dw_update分区条目位于app_main.c分区表,调整扇区数即可改变升级暂存容量;配合isd_config.ini的[RESERVED_CONFIG]可重新划分烧录期区域属性。
Related Links
- 烧录工具与规则源码:isd_config_rule.c、isd_config_rule_loader.c、isd_config_debug.ini
- 配置文件说明文档:ISD_CONFIG.INI配置文件说明.pdf
- Nor 升级核心实现:norflash_update.c
- 升级通道库(
apps/common/update/):fs_update.c、http_update.c、net_update.c、net_single_backup_update.c、lc_flash_ufw_update.c、norflash_ufw_update.c - 升级示例(
apps/common/example/update/):sd_udisk_upgrade/main.c、http_upgrade/example1~3、ftp_upgrade/main.c、lc_flash_upgrade/lc_flash_upgrade_test.c - RCSP 协议升级:rcsp_update.c
- 分区表与 update 分区定义:app_main.c(wifi_camera)、app_main.c(scan_box)