WS63 LiteOS OTA 如何实现

0. LiteOS OTA 实现方式概述

在 LiteOS 生态中,常见的 OTA 实现方式主要有以下三种:

方式 适用场景 核心机制
① UPG 模块模式 轻量级 MCU/SoC(如 WS63) 芯片厂商封装 UPG 接口,应用层直接调用 API 写入 Flash,由 Bootloader 完成分区切换
② IoT Link SDK 模式 物联网设备(NB-IoT 等) 基于 LwM2M/PCP 协议与云平台交互,采用 Loader + App 双镜像架构,支持差分升级
③ OpenHarmony 标准系统模式 富设备(RK3568、手机等) 独立的 updater 分区与 misc 分区,uboot 引导进入 updater 模式,有完整 Linux 用户态升级流程

本文实现的是方式 ①:基于 WS63 芯片提供的 UPG 模块,在 LiteOS 应用层构建 TCP 局域网服务端,通过流式接收固件并调用 uapi_upg_* 系列接口完成升级。相比其他两种方式,这种方式最轻量、最直接,适合局域网快速烧录和调试场景。

无论哪种实现方式,OTA 的整体流程都可以概括为以下四个步骤:

OTA主要流程

本文按实际操作的先后顺序组织,共分为以下几个部分:

  1. 了解 UPG 接口:理解底层升级模块的工作原理和 API
  2. 环境准备:生成签名密钥、全量编译并烧录底包(必须先完成,否则后续 OTA 校验会失败
  3. 代码实现:编写 OTA 服务代码
  4. OTA 升级与推送:生成升级包并远程推送到设备
  5. 问题排查:常见问题及解决方法
  6. 方案对比:LiteOS OTA 与 OpenHarmony 标准系统 OTA 的差异

1. 了解 LiteOS OTA 相关接口

WS63 的 OTA 升级依赖于底层提供的 UPG (Upgrade) 模块。应用层只需要把接收到的固件包按顺序调用接口写入即可,底层的签名校验和 Flash 分区操作由 UPG 模块完成。

UPG 内部工作流程

UPG 内部工作流程

UPG 内部 Flash 分区与数据流向

WS63 采用 AB 双分区 方案,Flash 整体布局如下图所示(总大小4MB):

WS63 整体 Flash 物理分区表

上图中与 UPG 相关的区域划分定义在 build/config/target_config/ws63/param_sector/param_sector.json 中,分区 ID 枚举见 middleware/chips/ws63/partition/include/partition_resource_id.h

区域 分区名 作用
A 区(运行区) PARTITION_APP_IMAGE 当前正在运行的固件,起始地址 0x030000,大小 0x240000(约 2.25MB)
B 区(FOTA 区) PARTITION_FOTA_DATA 起始地址 0x270000,大小 0x183000(约 1.512MB)。前半部分存放接收到的升级包数据;后半部分为升级标记区(记录升级状态、包长度、镜像数量等);最后 4KB 为 AB 配置区(记录当前从哪个分区启动)

物理分区与 A/B 逻辑分区的映射关系如下图所示:

物理分区与 A_B 逻辑分区映射关系

物理上的 imageA (2.25M)+ fota data (1.5M)被从中问强行切分,变成了等大的两个1.93MB 区域,这也解释了为什么 fota_data 不仅仅是暂存区,它的前半部分其实是 B 区代码的运行空间。

下载阶段(App 中)的数据流向:

应用层调用 UPG 存储层接口(实现在 middleware/utils/update/storage/upg_storage.c),将网络接收到的固件写入 Flash:

  1. uapi_upg_prepare() —— 擦除 PARTITION_FOTA_DATA 分区,并在升级标记区写入 head_magicpackage_length
  2. uapi_upg_write_package_sync() —— 将网络接收到的固件数据按 offset 顺序写入 FOTA 区起始地址 + offset。实际的 Flash 读写由 middleware/chips/ws63/update/common/upg_common_porting.c 中的 upg_flash_write() 完成
  3. uapi_upg_verify_file() —— 对 FOTA 暂存区中的固件进行 ECDSA 签名 + SHA256 哈希校验
  4. uapi_upg_request_upgrade(true) —— 在升级标记区写入 head_end_magicfirmware_num,标记”升级包已就绪”,然后重启

升级阶段(Bootloader 中)的数据流向:

Bootloader 启动后(入口在 bootloader/flashboot_ws63/startup/main.cstart_fastboot()),调用 ws63_upg_check() 检测升级标记。若标记完整,自动调用 uapi_upg_start()(实现在 middleware/utils/update/local_update/upg_process.c):

  1. FOTA 暂存区 读取升级包,逐个镜像处理(upg_process_update()upg_perform_image_task()
  2. 对于全镜像升级,调用 middleware/utils/update/local_update/upg_upgrade.c 中的 uapi_upg_full_image_update(),将 FOTA 区的数据读出,写入目标镜像分区(B 区或对应分区)
  3. 如果是 AB 模式,写入完成后调用 middleware/chips/ws63/update/ab_upg/upg_ab.c 中的 upg_set_run_region() 切换启动分区
  4. 设置 complete_flag,再次重启,此时从新的分区启动新固件

OTA 动作与数据流向的整体过程如下图所示:

注意图片内容是假设当前系统正在A区运行

OTA 动作与数据流向

回滚机制:AB 模式下,如果新固件连续验签失败达到阈值(3 次),Bootloader 会自动切换回另一个分区启动,实现失败回滚。

核心头文件为 include/middleware/utils/upg.h,下面是此头文件里的相关接口说明,你也可以直接打开头文件查看内容:

API 接口 功能说明 调用时机
uapi_upg_init(const upg_func_t *funcs) 初始化 UPG 模块。需要传入内存分配和串口打印的回调函数。 系统启动后,OTA 服务启动前。
uapi_upg_reset_upgrade_flag(void) 清除残留标志。防止上次失败的升级状态干扰本次 OTA。 UPG 初始化后紧接着调用。
uapi_upg_prepare(upg_prepare_info_t *info) 准备升级。告知底层即将接收的固件总大小,底层会做 Flash 空间检查。 接收到网络传输的固件 Header 时调用。
uapi_upg_write_package_sync(offset, buf, len) 同步写入数据块。将接收到的固件分块写入 UPG 暂存区。 网络接收循环中,每收到一块数据时调用。
uapi_upg_verify_file(upg_package_header_t *hdr) 固件校验。对写入完成的暂存区固件进行 ECDSA 签名和 SHA256 哈希校验。 整个固件包接收完成后调用。
uapi_upg_request_upgrade(bool reboot) 触发升级。通知 Bootloader 切换分区,并可选择是否立即重启。 verify 校验通过(返回成功)后调用。

2. 环境准备

在开始编写 OTA 代码之前,必须先完成以下两项环境准备工作。否则后续生成的 OTA 升级包将无法通过设备端的签名校验。

2.1 用脚本生成签名密钥

OTA 固件包必须经过签名,Bootloader 和 UPG 模块才会允许刷入。第一次开发或更换工程时,需要生成一套专属密钥。

脚本位置 build/config/target_config/ws63/sign_encry/gen_all_key.sh

  1. 进入 SDK 的加密配置目录:
    1
    cd build/config/target_config/ws63/sign_encry
  2. 执行一键生成密钥脚本:
    1
    bash gen_all_key.sh
    注意:执行过程中提示旧密钥会被覆盖,输入 Y 确认。
  3. 输出结果:脚本会在 build/config/target_config/ws63/sign_config/ 目录下生成一系列 .pem 私钥文件(包含 Root、App、Bootloader 私钥等),后续打包脚本会自动调用这些文件。

2.2 全量编译与本地烧录底包(极度重要)

⚠️ 为什么必须有这一步?
刚才生成的只是私钥。对应的公钥会被编译进 Bootloader 和设备的 efuse 中。如果只生成密钥而不做本地全量烧录,设备里的旧公钥将永远无法校验通过你用新私钥打包的 OTA 固件

(表现为 uapi_upg_verify_file 返回 0x01 或错误码)

  1. 编译全量工程(回到 SDK 根目录):

    1
    python build.py ws63-liteos-app
  2. **打包全量烧录包 (.fwpkg)**:
    (如果 build.py 没有自动生成,手动执行以下命令)
    脚本位置 tools/pkg/packet.py

    1
    python tools/pkg/packet.py ws63 ws63-liteos-app ""

    产物路径output/ws63/fwpkg/ws63-liteos-app/ws63-liteos-app_all.fwpkg

  3. 本地物理烧录
    使用官方烧录工具(如 HiBurn 或 JLink),通过串口/USB线ws63-liteos-app_all.fwpkg 完整烧录到开发板中。
    (完成这一步后,你的开发板就具备了接收后续 OTA 的最新公钥基础。)


3. 代码实现

我们可以用Liteos的这套接口,在UPG外部实现完整的 OTA 相关代码

工程 在 UPG外部 OTA 的代码实现

本工程中的 OTA 服务实现位于以下文件:

  • 头文件application/samples/peripheral/smart_car/apps/robot_demo/services/ota_service.h
  • 实现文件application/samples/peripheral/smart_car/apps/robot_demo/services/ota_service.c
  • 主程序初始化application/samples/peripheral/smart_car/apps/robot_demo/core/robot_mgr.c

3.1 OTA 服务接口定义

文件位置 application/samples/peripheral/smart_car/apps/robot_demo/services/ota_service.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#ifndef OTA_SERVICE_H
#define OTA_SERVICE_H

#include <stdint.h>
#include <stdbool.h>

#define OTA_TCP_PORT 8890
#define OTA_RECV_CHUNK_SIZE 4096
#define OTA_TCP_STACK_SIZE 16384
#define OTA_TCP_TASK_PRIORITY 23

#define OTA_MAGIC_LEN 4
#define OTA_MAGIC_STR "OTAx"

typedef enum {
OTA_STATE_IDLE, // 空闲
OTA_STATE_WAITING, // TCP 监听中
OTA_STATE_RECEIVING, // 接收数据中
OTA_STATE_VERIFYING, // UPG 校验中
OTA_STATE_UPGRADING, // 已请求升级,即将重启
OTA_STATE_FAILED // 失败
} ota_state_t;

typedef struct {
ota_state_t state;
uint8_t progress_percent;
uint32_t received_size;
uint32_t total_size;
} ota_status_t;

void ota_service_init(void);
bool ota_service_start(uint32_t expected_size);
void ota_service_cancel(void);
bool ota_service_is_active(void);
void ota_service_get_status(ota_status_t *out);

#endif /* OTA_SERVICE_H */

3.2 OTA 服务核心实现

文件位置 application/samples/peripheral/smart_car/apps/robot_demo/services/ota_service.c

核心逻辑分为 7 个阶段:

阶段 A:UPG 初始化

1
2
3
4
5
6
7
8
9
10
void ota_service_init(void)
{
static const upg_func_t s_upg_funcs = {
.malloc = fota_upg_malloc, // osal_kmalloc
.free = fota_upg_free, // osal_kfree
.serial_putc = fota_upg_serial_putc // uapi_uart_write
};
uapi_upg_init(&s_upg_funcs);
uapi_upg_reset_upgrade_flag(); // 清除残留升级标志
}

阶段 B:TCP 服务端启动

1
2
3
4
5
6
7
bool ota_service_start(uint32_t expected_size)
{
g_ota_task_handle = osal_kthread_create(
(osal_kthread_handler)ota_tcp_server_task, NULL,
"ota_tcp_task", OTA_TCP_STACK_SIZE);
osal_kthread_set_priority(g_ota_task_handle, OTA_TCP_TASK_PRIORITY);
}

阶段 C:接收 8 字节 Header

这个Header由 魔术字+包大小 组成

1
2
[4 bytes] Magic: "OTAx" (0x4F 0x54 0x41 0x78)
[4 bytes] Total firmware size (uint32_t, big-endian)

阶段 D:UPG 预准备

1
2
errcode_t ret = uapi_upg_prepare(&prepare_info);
/* prepare_info.package_len = total_size */

阶段 E:流式接收并写入 UPG

1
2
3
4
5
6
while (offset < total_size) {
n = recv_all(conn_fd, recv_buf, chunk_size, 30000);
ret = uapi_upg_write_package_sync(offset, recv_buf, (uint16_t)n);
offset += n;
/* 每 4KB 回复 1 byte ACK: 0x00 = OK */
}

阶段 F:UPG 校验

1
2
ret = uapi_upg_read_package(0, (uint8_t *)&hdr, sizeof(hdr));
ret = uapi_upg_verify_file(&hdr); // 签名+哈希校验

阶段 G:请求升级并重启

1
ret = uapi_upg_request_upgrade(true);  // true = 立即重启

3.3 主程序初始化

文件位置 application/samples/peripheral/smart_car/apps/robot_demo/core/robot_mgr.c

robot_mgr_init()udp_service_init() 之后添加:

1
2
3
4
5
6
7
8
9
#include "../services/ota_service.h"

void robot_mgr_init(void)
{
/* ... 其他初始化 ... */
udp_service_init();
ota_service_init(); // <-- OTA 服务初始化
/* ... */
}

4. OTA 升级与推送

完成环境准备和代码编写后,即可开始制作 OTA 升级包并推送到设备进行验证。

4.1 生成 OTA 升级包 (Pkg)

现在你的设备已经跑起来了,假设你需要修复一个 Bug 并通过 OTA 推送。

  1. 修改代码
    在应用代码中做一些改动,比如修改版本号打印:printf("OTA Version V2.0\r\n");

  2. 重新编译代码

    1
    python build.py ws63-liteos-app
  3. 获取 OTA 产物
    产物路径output/ws63/upgrade/update.fwpkg

    注意区分,这个包通常只有 1MB 左右,比全量包小很多。

  4. 执行 OTA 升级包打包脚本
    脚本位置 build/config/target_config/ws63/build_ws63_update.py

    1
    python build/config/target_config/ws63/build_ws63_update.py --pkt app

    该脚本会提取上一步编译出的 .bin 文件,使用【2.1】生成的私钥进行 ECDSA 签名,并打包成 UPG 格式。

验证方法: 可以在代码中加入明显的版本标记(如 printf("[FIRMWARE] OTA_TEST_BUILD_V2\r\n")),OTA 重启后观察串口是否出现该标记,即可确认固件已更新。


4.2 远程 OTA 推送

最后一步,通过网络将 update.fwpkg 发送给处于局域网内的开发板。

这个脚本是我自己写的,可以根据我的代码实现一个自己的脚本

在我的工程里,脚本位置是: tools/ota_sender.py

  1. 确保开发板已连接 Wi-Fi,且你编写的网络 OTA 服务端(第3节的代码)处于监听状态。
  2. 使用配套的 Python 发送端脚本推送固件:
    1
    2
    # 格式:python ota_sender.py <目标开发板IP> <OTA包路径>
    python tools/ota_sender.py 192.168.111.20 output/ws63/upgrade/update.fwpkg
  3. 观察设备端反应
    • 设备端开始接收数据,串口打印接收进度。
    • 接收完成后,触发 uapi_upg_verify_file 校验。
    • 串口输出类似 [UPG] verify success!
    • 设备自动重启,此时底层 Bootloader 会接管,将暂存区的新固件覆盖到主分区。
    • 再次开机,看到你修改的 OTA Version V2.0 打印,说明完整 OTA 流程大功告成。

4.3 日常操作流程

1
2
3
4
5
6
7
8
9
10
11
12
# Step 1: 修改代码(如 robot_mgr.c 等)

# Step 2: 重新编译(必须!生成OTA升级包的脚本不会自动编译)
python build.py ws63-liteos-app

# Step 3: 生成 OTA 升级包
python build\config\target_config\ws63\build_ws63_update.py --pkt app

# Step 4: 远程推送 OTA
python tools\ota_sender.py 192.168.111.20 output\ws63\upgrade\update.fwpkg

# Step 5: 观察串口,确认新固件已生效

完成代码后的操作流程

5. 可能遇到的问题

现象 原因 解决
TCP 连接失败 OTA 任务未启动 / 防火墙 / 网络不通 检查设备是否正常运行,确认 IP 地址和端口
Header ACK 错误 固件大小为 0 / Magic 不匹配 检查 update.fwpkg 是否完整
UPG write 失败 空间不足 / Flash 错误 检查 UPG 分区大小,确认 Flash 正常
校验失败 (0x01) 签名密钥不匹配 重新运行 gen_all_key.sh,重新全量烧录 .fwpkg
设备未重启 UPG request_upgrade 失败 检查 uapi_upg_verify_file() 返回值
OTA 成功但固件没变化 修改代码后没有重新编译 打包脚本只读取已编译的 .bin,不会自动编译。必须先执行 python build.py ws63-liteos-app

校验失败详细排查

如果最终 ACK 返回 0x01(校验失败),串口日志会打印类似:

1
[OTA] uapi_upg_verify_file failed, ret=0x800030xx (UPG_XXX)

常见错误码:

  • 0x80003040: UPG_NOT_INIT — UPG 未初始化
  • 0x80003044: UPG_INVALID_IMAGE_ID — 镜像 ID 不匹配
  • 0x80003048: UPG_FLASH_ERASE_ERROR — Flash 擦除错误
  • 0x80003051: UPG_NO_ENOUGH_SPACE — 空间不足

最可能的原因:Bootloader 公钥与新私钥不匹配。

确认步骤:

  1. 检查 fota.cfg 中的 RootKeyFileSubKeyFile 路径是否正确
  2. 确认 sign_config/ 下的 .pem 文件是最新生成的
  3. 重新全量烧录 ws63-liteos-app_all.fwpkg(这是最关键的步骤)
  4. 重新生成 update.fwpkg
  5. 再次 OTA 推送

6. LiteOS OTA 与 OpenHarmony OTA 的对比

6.1 相同点

相同点 说明
签名校验 都需要使用公私钥对升级包进行签名,设备端用公钥校验,防止刷入非法固件
分区备份机制 都有分区/镜像备份的概念,升级失败时可以回滚到旧版本
四步基本流程 都遵循”制作升级包 → 下载/推送 → 校验 → 重启生效”的基本流程
全量升级支持 都支持全量升级,这是最基础的升级方式

6.2 不同点

对比项 LiteOS OTA(本文实现) OpenHarmony 标准系统 OTA
操作系统 LiteOS(轻量级实时操作系统) OpenHarmony 标准系统(类 Linux 多进程系统)
系统架构 单进程/单任务,应用直接操作硬件 多进程、多服务,有 init、SA、IPC 等机制
升级入口 C 语言直接调用 UPG 接口 JS API → update_service SA → 系统安装服务
分区设计 Flash 划分为 Running / Backup / Download / Flag 等区域 独立的 updater 分区、misc 分区,与主系统隔离
启动流程 Bootloader 直接切换分区并跳转 uboot 读取 misc 分区指令,加载 updater.img 进入升级模式
网络协议 由应用层自行实现(本文为方便采用 TCP 局域网推送) 内置 HTTPS 下载,对接厂商 OTA 服务器
差分升级 本文实现不支持(UPG 模块本身不处理差分) 支持(bsdiff/imgdiff 差分算法)
UI 界面 有升级进度 UI 界面
服务器依赖 局域网 Python 推送脚本即可 需要搭建搜包服务器 + 下载服务器
适用设备 资源受限的 MCU、模组 富设备、开发板、手机等

下图展示了 OpenHarmony 标准系统 updater 模式的启动流程,可以看到其需要 uboot 读取 misc 分区指令、加载 updater.img、启动 init 服务等一系列步骤:

标准系统镜像启动流程

下图则是 OpenHarmony 标准系统升级子系统的整体架构,包含了 updater_service、updater、公共库、三方库等多个层级,远比本文的 UPG 轻量接口复杂:

标准系统升级子系统架构


参考文章

文章 链接 说明
[经验分享] OTA升级开发指导 https://forums.openharmony.cn/forum.php?mod=viewthread&tid=900&extra=page%3D1&mobile=no OpenHarmony 标准系统 updater 模式适配指南
OTA升级 - openharmony官方文档 https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/subsystems/subsys-ota-guide.md#%E7%94%9F%E6%88%90%E5%85%AC%E7%A7%81%E9%92%A5%E5%AF%B9 官方文档,覆盖标准系统与轻量/小型系统
如何实现OpenHarmony的OTA升级? https://cloud.tencent.com/developer/article/2516696 基于 RK3568 的标准系统 OTA 实践
漫谈LiteOS之OTA 方案概述 https://bbs.huaweicloud.com/blogs/180966 LiteOS IoT Link SDK 的 OTA 方案(HDIFFPATCH + LZMA)
漫谈LiteOS-Huawei_IoT_Link_SDK_OTA 开发指导 https://www.cnblogs.com/hwiot/p/12745773.html LiteOS 基于 LwM2M 的 FOTA/SOTA 开发指南