前言

概述

本文档主要介绍固件升级流程和接口的使用方法,指导用户应用及开发升级特性。

产品版本

与本文档相对应的产品版本如下。

产品名称

产品版本

WS63

V100

读者对象

本文档主要适用于以下工程师:

  • 技术支持工程师

  • 软件开发工程师

符号约定

在本文中可能出现下列标志,它们所代表的含义如下。

符号

说明

表示如不避免则将会导致死亡或严重伤害的具有高等级风险的危害。

表示如不避免则可能导致死亡或严重伤害的具有中等级风险的危害。

表示如不避免则可能导致轻微或中度伤害的具有低等级风险的危害。

用于传递设备或环境安全警示信息。如不避免则可能会导致设备损坏、数据丢失、设备性能降低或其它不可预知的结果。

“须知”不涉及人身伤害。

对正文中重点信息的补充说明。

“说明”不是安全警示信息,不涉及人身、设备及环境伤害信息。

修改记录

文档版本

发布日期

修改说明

07

2025-07-04

  • 更新“功能描述”章节内容。
  • 更新“升级包保存”的“注意事项”章节内容。
  • 新增“AB面升级”章节内容。
  • 新增“版本兼容性说明”章节内容。

06

2025-02-28

  • 更新“升级包制作”“开发实例”章节内容。
  • 更新“升级包本地升级”的“编程实例”章节内容。

05

2024-10-30

更新升级包保存”的“注意事项”章节内容。

04

2024-08-28

  • 更新“功能描述”、“固件升级流程”章节内容。
  • 更新“升级包制作”“流程原理”、“开发流程”、“开发实例”章节内容。
  • 更新升级包保存”的“注意事项”章节内容。
  • 更新“升级包本地升级”的“开发流程”章节内容。

03

2024-06-27

  • 更新“升级包制作”“开发实例”、“注意事项”章节内容。
  • 更新“升级包传输”章节内容。
  • 更新“升级包保存”章节内容。

02

2024-05-08

更新升级包保存”的“注意事项”章节内容。

01

2024-04-10

第一次正式版本发布。

  • 新增“FAQ”章节内容。

00B02

2024-03-29

  • 新增“升级包制作”“注意事项”章节内容。
  • 更新升级包保存”的“编程实例”章节内容。
  • 更新“升级包本地升级”“编程实例”章节内容。

00B01

2023-12-18

第一次临时版本发布。

概述

功能描述

固件升级功能可用于对设备芯片的固件进行更新。

WS63的固件包含多个固件镜像,不同镜像可能采用不同的升级方式,包括全镜像升级、压缩升级,用户可以根据情况选择合适的升级方式。

  • 全镜像升级:新的固件镜像不做处理,直接打包到固件升级包(以下简称为“升级包”)中,在设备上直接将其更新至目标位置。这种方式一般适用于固件镜像较小的场景。

  • 压缩升级:新的固件镜像经过压缩处理后,打包到升级包中,首先在设备上解压恢复,再更新至目标位置。这种方式一般适用于固件镜像较大的场景。

  • AB面升级:不制作升级包,直接将镜像传输到指定分区,然后切换使用的镜像分区,每次升级都在A面与B面之间切换,且互为备份,该方式一般适用于有一定安全性考虑的场景。该升级方式与前两种不兼容,使用方法请直接参考当前文档“AB面升级”章节,“升级包制作”~“升级包本地升级”章节不适用于该升级方式。

固件升级流程

从端到端来看,整个芯片固件的升级操作包括以下过程,如图1所示。

  • 升级包制作:固件开发完成后,根据实际情况,将要升级的固件镜像按照配置文件中对应的升级方式,生成固件升级包文件的过程。

  • 升级包传输:将升级包传输到设备上。根据实际情况,可能有多种传输方式,如蓝牙、串口等。

  • 升级包保存:将传输到设备的固件升级包保存至本地存储器,在WS63中,固件升级包存储在FLash中。

  • 升级包本地校验:升级程序替换前,在设备上校验升级包的完整性和合法性。

  • 升级包本地升级:APP程序中在设备上将新的固件更新至目标位置,重启进入flashboot继续执行升级,升级完成后重启。

图 1 固件升级流程图

开发接口

根据所在的程序的不同,升级接口可分为两部分。

  • 升级包存储,由APP程序执行。

    • 初始化接口。

    • 升级包写入本地存储器和从本地存储器读取的接口。

    • 申请开始进行本地升级接口。

  • 升级包本地升级,分两部分执行,执行流程是一致的,NV镜像的升级由APP程序完成,其他镜像的升级在flashboot程序中完成。

    • 初始化接口。

    • 升级包校验接口。

    • 升级进度通知函数注册接口。

    • 开始执行本地升级接口。

    • 获取升级结果接口。

升级模块提供的接口如表1表2所示。参见头文件:include\middleware\utils\upg.h。接口参数及返回值说明如表3所示。

表 1 升级接口(升级包存储部分)描述

接口名称

描述

uapi_upg_init

升级模块初始化。

uapi_upg_prepare

保存升级包到本地存储器前的准备工作。

uapi_upg_write_package_async/uapi_upg_write_package_sync

将升级包数据写入本地存储器。(异步方式/同步方式)

uapi_upg_read_package

从本地存储器读取升级包数据。

uapi_upg_request_upgrade

申请开始进行本地升级,所有升级包数据全部保存完成后,调用此接口。

uapi_upg_get_storage_size

获取可存放升级包的空间大小。

表 2 升级接口(本地升级部分)描述

接口名称

描述

uapi_upg_init

升级模块初始化。

uapi_upg_register_progress_callback

注册升级进度通知回调函数,注册后,在本地升级过程中会调用回调函数通知当前进度。

uapi_upg_start

开始本地升级。

uapi_upg_get_result

获取升级结果。

uapi_upg_verify_file_head

校验升级包头结构。

uapi_upg_verify_file_image

校验升级包中的升级镜像。

uapi_upg_verify_file

校验整个升级包。

uapi_upg_register_user_defined_verify_func

注册用户自定义字段的校验函数。

升级包结构中预留了48Byte用于用户自定义数据的校验。注册自定义校验函数后,被注册的函数会在调用uapi_upg_verify_file_head和uapi_upg_verify_file函数时被调用到。如果自定义数据校验失败uapi_upg_verify_file_head和uapi_upg_verify_file会返回失败。

表 3 升级接口入参及返回值描述

接口原型

参数及返回值说明

errcode_t uapi_upg_init(const upg_func_t *func_list)

  • 入参说明:

    func_list:注册回调列表,upg_func_t类型。

  • 返回值:
    • ERRCODE_SUCC:成功。
    • 其他:失败。

errcode_t uapi_upg_start(void)

  • 入参说明:无。
  • 返回值:
    • ERRCODE_SUCC:成功。
    • 其他:失败。

errcode_t uapi_upg_register_progress_callback(uapi_upg_progress_cb func)

  • 入参说明:

    func:回调函数,该函数需业务实现。

  • 返回值:
    • ERRCODE_SUCC:成功。
    • 其他:失败。

errcode_t uapi_upg_get_result(upg_result_t *result, uint32_t *last_image_index)

  • 入参说明:
    • result:出参,保存升级结果的内存地址,类型upg_result_t。
    • last_image_index:出参,保存最后一个处理的镜像的索引。
  • 返回值:
    • ERRCODE_SUCC:成功。
    • 其他:失败。

errcode_t uapi_upg_prepare(upg_prepare_info_t *prepare_info)

  • 入参说明:

    prepare_info:入参,upg_prepare_info_t*类型,准备信息的指针。

  • 返回值:
    • ERRCODE_SUCC:成功。
    • 其他:失败。

errcode_t uapi_upg_write_package_async(uint32_t offset, const uint8_t *buff, uint16_t len, uapi_upg_write_done_cb callback)

  • 入参说明:
    • offset:入参,uint32_t类型,相对升级包开头的偏移。
    • buff:入参,const uint8_t *类型,存放升级包数据的buffer。
    • len:入参,uint16_t类型,升级包数据buffer的长度。
    • callback:入参,uapi_upg_write_done_cb类型,写入完成的回调函数。
  • 返回值:
    • ERRCODE_SUCC:成功。
    • 其他:失败。

errcode_t uapi_upg_write_package_sync(uint32_t offset, const uint8_t *buff, uint16_t len)

  • 入参说明:
    • offset:入参,uint32_t类型,相对升级包开头的偏移。
    • buff:入参,const uint8_t *类型,存放升级包数据的buffer。
    • len:入参,uint16_t类型,升级包数据buffer的长度。
  • 返回值:
    • ERRCODE_SUCC:成功。
    • 其他:失败。

errcode_t uapi_upg_read_package(uint32_t offset, uint8_t *buff, uint32_t len)

  • 入参说明:
    • offset:入参,uint32_t类型,相对升级包开头的偏移。
    • buff:出参,uint8_t *类型,存放升级包数据的buffer。
    • len:入参,uint32_t类型,读取数据buffer的长度。
  • 返回值:
    • ERRCODE_SUCC:成功。
    • 其他:失败。

uint32_t uapi_upg_get_storage_size(void)

  • 入参说明:无。
  • 返回值:
    • 0:失败返回0。
    • 其他:成功返回空间大小。

errcode_t uapi_upg_request_upgrade(bool reset)

  • 入参说明:

    reset:入参,bool类型,申请流程结束后是否重启系统。

  • 返回值:
    • ERRCODE_SUCC:成功。
    • 其他:失败。

errcode_t uapi_upg_verify_file_head(const upg_package_header_t *pkg_header)

  • 入参说明:

    pkg_header:入参,upg_package_header_t *类型,指向升级包头结构的指针。

  • 返回值:
    • ERRCODE_SUCC:成功。
    • 其他:失败。

errcode_t uapi_upg_verify_file_image(const upg_image_header_t *img_header, const uint8_t *hash, uint32_t hash_len, bool verify_old)

  • 入参说明:
    • img_header:入参,upg_image_header_t*类型,指向升级包中升级镜像头结构的指针。
    • hash:入参,uint8_t*类型,升级镜像的HASH值。
    • hash_len:入参,uint32_t类型,HASH的长度(单位:Byte)。
    • verify_old:入参,bool类型,是否校验旧镜像。
  • 返回值:
    • ERRCODE_SUCC:成功。
    • 其他:失败。

errcode_t uapi_upg_verify_file(const upg_package_header_t *pkg_header)

  • 入参说明:

    pkg_header:入参,upg_package_header_t *类型,指向升级包头结构的指针。

  • 返回值:
    • ERRCODE_SUCC:成功。
    • 其他:失败。

void uapi_upg_register_user_defined_verify_func(uapi_upg_user_defined_check func, uintptr_t param)

  • 入参说明:
    • func:入参,upg_package_header_t *类型,用于校验用户自定义字段的校验函数。
    • param:入参,uintptr_t类型,注册参数。
  • 返回值:无。

升级包制作

流程原理

图 1 升级包制作流程

升级包的制作流程示意图如图1所示,具体操作步骤如下:

  1. 编译生成带签名的明文新镜像。编译生成镜像请参考《WS63V100 SDK开发环境搭建》“编译SDK(Cmake)-编译方法”章节。

  2. 对新镜像进行处理。处理方式包含场景如下:

    • 将新镜像压缩并加密,生成加密的压缩新镜像。

    • 将新镜像压缩不加密,生成压缩的新镜像。

    • 新镜像不做处理。

  3. 步骤2中生成的镜像添加升级镜像头。

  4. 对所有需要升级的镜像根据所选择的不同处理方式执行步骤1步骤3

  5. 将生成的所有带升级镜像头的升级镜像文件合并到最终升级包中,并对整包添加升级包头,进行数字签名。

说明: 2中的场景,需根据实际情况做选择。如WS63不支持镜像加密,所有需要加密的场景则不能选择。

开发流程

提供基于Windows/Linux系统下的Python入口编译能力,升级包可以在所有镜像编译完成后,通过执行“build/config/target_config/ws63/build_ws63_update.py”来制作,脚本及配置文件说明如下:

  • 在“build_ws63_update.py”文件中配置各个镜像的路径以及其他脚本所需的参数,并调用升级包制作脚本“build_upg_pkg.py”,如下所示。“build_ws63_update.py”中配置的各个路径可以根据实际路径修改。

    “build_ws63_update.py”调用“build_upg_pkg.py”代码示例如下:

    import os
    import sys
    import argparse
    file_dir = os.path.dirname(os.path.realpath(__file__))
    g_root = os.path.realpath(os.path.join(file_dir, "..", "..", "..", ".."))
    sys.path.append(os.path.join(g_root, 'build', 'script'))
    from build_upg_pkg import begin
    class upg_base_info:
        def __init__(self):
            self.root_path = g_root
            # 升级包结构配置文件
            self.fota_format_path = os.path.join(self.root_path, "build", "config", "target_config", "ws63", "fota")
            # 产品升级配置文件
            self.fota_cfg = os.path.join(self.root_path, "build", "config", "target_config", "ws63", "fota", "fota.cfg")
            # 产品镜像输出路径
            self.output = os.path.join(self.root_path, "output", "ws63")
            # 产品升级镜像包输出路径
            self.upg_output = os.path.join(self.output, "upgrade")
            # 产品升级制作临时文件输出路径
            self.temp_dir = os.path.join(self.upg_output, "temp_dir")
            # 产品镜像路径
            self.flashboot = os.path.join(self.output, "acore", "ws63-flashboot", "flashboot_sign.bin")
            self.app_bin = os.path.join(self.output, "acore", "ws63-liteos-app", "ws63-liteos-app-sign.bin")
            self.nv_bin = os.path.join(self.output, "acore", "nv_bin", "ws63_all_nv.bin")
    
            self.flashboot_old_bin = os.path.join(self.output, "acore", "old_version", "flashboot_sign.bin")
            self.app_old_bin = os.path.join(self.output, "acore", "old_version", "ws63-liteos-app-sign.bin")
            self.nv_old_bin = os.path.join(self.output, "acore", "old_version", "ws63_all_nv.bin")
    def get_new_image(input,info):
        image_list = []
        if 'app' in input:
            image_list.append("=".join([info.app_bin, "application"]))
            print(1)
        if 'boot' in input:
            image_list.append("=".join([info.flashboot, "flashboot"]))
            print(2)
        if 'nv' in input:
            image_list.append("=".join([info.nv_bin, "nv"]))
            print(3)
        new_image = "|".join(image_list)
        return new_image
    def get_old_image(input,info):
        image_list = []
        if 'app' in input:
            image_list.append("=".join([info.app_old_bin, "application"]))
            print(11)
        if 'boot' in input:
            image_list.append("=".join([info.flashboot_old_bin, "flashboot"]))
            print(22)
        if 'nv' in input:
            image_list.append("=".join([info.nv_old_bin, "nv"]))
            print(33)
        old_image = "|".join(image_list)
        return old_image
    def get_parameters():
        parser = argparse.ArgumentParser()
        parser.add_argument('--pkt', type=str, default = 'app',
                            help='需要生成的镜像,包括: app,boot,nv')
        config = parser.parse_args()
        return config
    if __name__ == '__main__':
        info = upg_base_info()
        conf = get_parameters()
        input = conf.pkt.split(",")
        conf.app_name        = "update"
        conf.upg_format_path = info.fota_format_path
        conf.base            = info.fota_cfg
        conf.temp_dir        = info.temp_dir
        conf.new_images      = get_new_image(input,info)
        conf.old_images      = get_old_image(input,info)
        conf.output_dir      = info.upg_output
        conf.type            = 0
        begin(conf)
    
  • 升级包制作脚本为:build/script/build_upg_pkg.py。

  • 配置文件为:build/config/target_config/ws63/fota/fota.cfg,配置文件的各个字段说明如表1所示。

  • 配置文件“fota.cfg”中涉及到镜像ID,请参考表2,表中的ID均为特殊的魔术字。

  • 配置文件“fota.cfg”中可配置升级校验方式,。

  • 安全校验使用ECC256_SHA256密钥算法,密钥文件使用openssl工具(https://www.openssl.org)进行生成,执行命令为:openssl ecparam -genkey -name brainpoolP256r1 -out "ec_bp256_key.pem"

表 1 fota.cfg主要内容说明

标签

属性

说明

[SIGN_CFG]

-

签名相关属性。

SignSuite

签名密钥类型。

  • 1:SHA256(RSA_4096)

UpgImagePath

升级包文件路径和名称。

UpgSignedImagePath

升级包签名后的路径和名称。

RootKeyFile

根密钥文件路径。

SubKeyFile

二级秘钥文件路径。

[TOOLS]

-

制作工具配置属性。

UpgToolPath

升级工具路径和名称(升级工具可用作制作签名文件)。

LzmaToolPath

压缩工具路径和名称。

[FOTA_KEY_AREA]

-

升级包Key区属性。

ImageId

升级包Key区镜像ID,固定为0xCB8D154E。

KeyAlg

升级包Key区域密钥算法类型。

  • 0x2A13C812:ECC256_SHA256
  • 其他值:非法。

KeyVersion

升级包Key区版本号。

KeyVersionMask

升级包Key区版本号掩码。

Msid

市场区域ID。

MsidMask

市场区域ID掩码。

[FOTA_INFO_AREA]

-

升级包INFO区属性。

ImageId

升级包INFO区镜像ID,固定为0xCB8D154E。

HardwareID

硬件版本号。

Msid

市场区域ID。

MsidMask

市场区域ID掩码。

[FlashBoot/nv/application]

-

镜像类型名称。

HeaderMagic

头结构魔术字,固定为0x464F5451。

ImageId

升级镜像ID,与原始镜像的ImageId相同,镜像ID请参考表2

DecompressFlag

升级镜像模式标记。nv不支持压缩,仅支持全量升级

  • 0x3C7896E1:压缩。
  • 其他:原始镜像不处理。

ReRncFlag

升级镜像加密标记。仅app镜像支持加密升级

  • 0x3C7896E1:加密。
  • 其他:不加密。

表 2 镜像ID列表

镜像

镜像ID

FLASHBOOT IMAGE ID

0x4B1E3C2D

APPLICATION IMAGE ID

0x4B0F2D2D

NV IMAGE ID

0xCB9E063C

开发实例

以下提供制作升级包的一个实例,一切操作都是在SDK包中的默认配置上进行修改。

  1. 修改配置文件“build/config/target_config/ws63/fota/fota.cfg”(可选)。

    • 升级方式

      配置选项[FOTA_INFO_AREA]之后的每一项[xxx]都是对应镜像、镜像签名的配置项,根据升级方式选择修改 DecompressFlag参数。

      如:

      • flashboot的镜像以全量(原镜像不作处理)升级的方式,则[flashboot]的DecompressFlag设置为0。

      • app的镜像签名以压缩升级的方式,则[application]的DecompressFlag设置为0x3C7896E1。

      • nv的镜像的升级方式由NV特性自己完成,对配置方式不感知。

      • app镜像可以开启加密升级,在开启flash在线解密功能的前提下,配置[application]的[ReRncFlag]为0x3C7896E1

    • 防回滚号

      若此次升级不希望用户进行回退版本,则可以在升级包中修改对应镜像、镜像签名配置项中的防回滚版本号version_ext。

      • 防回滚版本号可以单独对某镜像进行设置。

      • 防回滚版本号按照二进制向高位逐个置一的规则进行增加;如:0x0(0000) -> 0x1(0001) -> 0x3(0011) ->0x7(0111) -> 0xF(1111) ->0x1F(0001 1111)。

  2. 核对并修改升级包生成脚本“build/config/target_config/ws63/build_ws63_update.py”。

    • 核对产品镜像路径。确保要升级的产品镜像路径是正确的,路径不限于脚本中默认填写的路径,可根据实际情况进行修改。

  3. 生成升级包文件。

    运行脚本“build/config/target_config/ws63/build_ws63_update.py”生成升级包,本脚本支持在Linux或Windows上运行,请在运行环境安装python3.7级以上版本Python;根据需要,可添加入参 --pkt=<image1>,<image2>,不添加入参时,默认入参为app。可添加--ver=<ota_version>版本号参数,不添加参数时,默认入参为空(全0)。

    • Windows环境请在cmd窗口SDK根目录下执行以下指令:

      python build\config\target_config\ws63\build_ws63_update.py
      
    • Linux环境请在SDK根目录下执行以下指令:

      python3 build/config/target_config/ws63/build_ws63_update.py
      

    执行完成后,在默认路径“output/ws63/upgrade”生成升级包“update.fwpkg”。

注意事项

若开启flash在线解密功能,编译出来的镜像为密文,则OTA镜像配置也需要配置为加密;

若烧写镜像为非密,则OTA镜像配置也需要配置为非密。sdk默认配置为非密,以压缩升级为例:

非密配置编译步骤

  1. 烧写镜像非密配置编译。

    1. “build/config/target_config/ws63/sign_config/liteos_app_bin_ecc.cfg”配置文件中配置“SignSuite=0”。

    2. 将“Iv=FF000000000000000000000000000000”注释或删除掉。

    3. 执行“./build.py -c ws63-liteos-app”,此时编译APP镜像为非密。将该镜像烧录单单板中。

  2. OTA镜像非密配置打包。

    1. “build/config/target_config/ws63/fota/fota.cfg”配置文件中配置[application]中的“ReRncFlag=0x0”。

    2. 此时执行“./build_ws63_update.py”,将1中编译生成的app镜像打包成OTA升级包为非密升级包。

加密配置编译步骤:

  1. 烧写镜像加密配置编译。

    1. “build/config/target_config/ws63/sign_config/liteos_app_bin_ecc.cfg”配置文件中配置“SignSuite=1”。

    2. 将Iv值配置为“Iv=FF000000000000000000000000000000”。

    3. 执行“./build.py -c ws63-liteos-app”,此时编译APP镜像为加密镜像;将该加密镜像烧录到单板中。

  2. OTA镜像非密配置打包

    1. 将“build/config/target_config/ws63/sign_config/liteos_app_bin_ecc.cfg”配置文件中配置“SignSuite=0”。

    2. 执行“./build.py -c ws63-liteos-app”编译出带加密Iv、但未加密的APP镜像(由于镜像加密后无法压缩,故此处需要先编译出带Iv但不加密的原始镜像,该镜像仅能用于OTA打包,不支持直接烧录,仅压缩升级需要该配置编译)。

    3. 配置“build/config/target_config/ws63/fota/fota.cfg”文件中配置[application]中的“ReRncFlag=0x3C7896E1”。

    4. 此时执行“./build_ws63_update.py 将步骤2 2.中编译生成的app镜像打包成压缩加密OTA升级包。

说明: 开启镜像加密功能时,推荐使用数字签名服务器对OTA镜像签名加密,可参考《WS63V100 二次开发网络安全 注意事项》中数字签名服务器章节打包ota镜像

升级包传输

升级包的传输,由应用程序实现,可以有多种方式,本文中不做详细描述。

说明: 升级包的存放位置可能会需要根据实际情况有所差异,以实际的Flash分区表中FOTA分区的地址为准。

升级包保存

开发流程

在WS63中,使用文件系统保存升级包,因此升级包是以二进制数据保存在flash中。

保存升级包文件有以下两种典型场景,可以根据升级包传输的实现来选择合适的开发场景。

传输流程中,传输升级包数据到内存中

  1. 调用uapi_upg_init初始化升级模块。

  2. 调用uapi_upg_prepare函数,执行本地存储器的准备工作,包括初始化升级标记等。

  3. 应用程序开始接收升级包,调用uapi_upg_write_package_sync函数,将内存中的分包数据写到flash对应位置。

  4. 继续接收下一个分包数据,再次调用uapi_upg_write_package_sync函数,写入到对应位置。直到所有数据全部写入完成。如果该接口返回错误,则停止升级流程。

  5. 调用uapi_upg_request_upgrade,传入重启参数后可以开始升级流程。

  6. 第5步中传入参数不包含重启时,需要进行手动重启,进入flashboot自动开始升级流程。

注意事项

  • 升级包的分包数据必须按照顺序传输和保存,已经保存的分包不能再次传输。例如:第一次传输并保存了0~1023的数据,下一次必须从1024开始,否则uapi_upg_write_package_sync接口会报错。

  • 升级包文件的路径和文件名是固定的,不允许改变。

  • 在传输过程中是否有数据的分段校验,由传输过程保证。如果传输过程中数据出现错误而没有校验和重传机制,则只能等到全部保存完成后,在本地升级启动过程中调用uapi_upg_verify_file校验时会失败,导致升级失败。

  • 需要在flash上配置对应的分区分别给运行程序和升级包数据,在适配压缩升级时,推荐运行程序的flash分区大小与升级包数据存放区的flash分区大小比例为1:0.7,该比例数据来源于压缩升级中的压缩比

  • 当前WS63上flash分区如下图

    序号

    分区编号

    分区名称

    大小

    地址空间

    备注

    1

    0x00

    ssb

    24KB

    0x0_2000~0x0_7FFF

    安全boot

    2

    0x01

    FLASH Boot

    64KB

    0x2_0000~0x2_FFFF

    FlashBoot区,flash烧录需求,起始地址需要4K对齐

    3

    0x02

    FLASH Boot_Backup

    64KB

    0x1_0000~0x1_FFFF

    FlashBoot备份区

    4

    0x03

    Root public key

    2KB

    0x0_0000~0x0_07FF

    根公钥,与分区表打包为一个文件,空间需要连续

    5

    0x08

    Customer factory

    16KB

    0x0_8000~0x0_BFFF

    用户工厂区,本分区设计产测时写入,产测结束后,后续app只读,可以用于存放客户规划的比较重要的数据,该分区内容,完全又客户自行管理规划

    6

    0x09

    NV backup

    16KB

    0x0_C000~0x0_FFFF

    NV工厂区,本分区在退出产测模式时被写入,之后在app运行过程中不可写入。不建议改动此分区的地址和长度。

    7

    0x10

    NV DATA

    16KB

    0x3F_C000~0x3F_FFFF

    NV工作区

    8

    0x11

    crash info

    4KB

    0x3F_B000~0x3F_BFFF

    panic信息离线保存区

    AB面升级方案

    9

    0x20

    imageA

    1924 KB

    0x3_0000~0x21_0FFF

    app image文件

    10

    0x21

    imageB

    1928 KB

    0x21_1000~0x3F_2FFF

    app image文件,最后一个4Kpage会用来存放ab面相关配置参数

    压缩升级方案

    9

    0x20

    imageA

    2304 KB

    0x3_0000~0x26_FFFF

    app image文件

    10

    0x21

    fota data

    1548 KB

    0x27_0000~0x3F_2FFF

    压缩升级包,压缩比为0.7左右

    剩余flash空间

    11

    0x30

    reserve

    32 KB

    0x3F_3000~0x3F_AFFF

    预留区

  • 分区ID从0x00开始到0x09,共计6个分区,设计为在产测阶段写入,在后续app运行过程中处于被写保护状态,只读。

  • 分区ID最大规格支持16个,从0x30开始的分区ID,可以定制开发,无特殊要求,不建议删除分区ID,如果预留区空间过小,希望增加预留区空间,可以从imageA/imageB/fota data分区切割部分空间,调整方法可以参考《WS63V100 SDK开发环境搭建 用户指南》文档中'2.2.5 Flash分区表配置'章节。

编程实例

static void upg_serial_putc(const char c)
{
    printf("%c", c);
}
void test_update(void)
{
    uint32_t file_size = 0x2000;  /*  升级文件大小(实际大小由APP获取)  */
    uint32_t max_len;
    uint32_t read_len;
    uint32_t write_len = 0;
    errcode_t ret;
    /* 依赖分区模块 */
    ret = uapi_partition_init(); /* 该接口可以重复调用 */
    if (ret != ERRCODE_SUCC){
        printf("uapi_partition_init error. ret = 0x%08x\r\n", ret);
    }
    /* 1. 初始化update模块 */
    /*  2. 获取APP升级文件大小上限.  */
    max_len = uapi_upg_get_storage_size();
    if (file_size > max_len) {
        return ERRCODE_FAIL;
    }
    upg_prepare_info_t prepare_info;
    /* 3. 将升级文件的大小,传给uapi_upg_prepare函数,执行升级准备工作 */
    prepare_info.package_len = file_size;
    ret = uapi_upg_prepare(&prepare_info);
    if (ret != ERRCODE_SUCC) {
        printf("uapi_upg_prepare error = 0x%x\r\n", ret);
    }
    /* 4. 用户自行实现:通过串口或网络下载升级文件,调用uapi_upg_write_package_sync函数存储(场景一),
       或直接保存至文件系统中(场景二) */
    char *buf = (char *)malloc(0x1000 * sizeof(char)); /* 单次读取分包的长度可以自行调整 */
    memset_s(buf, 0x1000 * sizeof(char), 0, 0x1000 * sizeof(char));
    while (write_len < file_size) {
        通过网络或者其他方式获取升级包(&read_len, buf);
        if (write_len + read_len > max_len) {
            free(buf);
            return;
        }
        ret = uapi_upg_write_package_sync(write_len,(uint8_t *)buf, read_len);
        if (ret != ERRCODE_SUCC) {
            free(buf);
            return;
        }
        write_len += write_len;
        memset_s(buf, 0x1000 * sizeof(char), 0, 0x1000 * sizeof(char));
    }
    /* 5. 升级文件下载完成后,申请开始本地升级 */
    ret = uapi_upg_request_upgrade(false);
    if (ret != ERRCODE_SUCC) {
        printf("uapi_upg_request_upgrade error = 0x%x\r\n", ret);
    }






    /* 6. 升级准备工作完成后,复位开始本地升级 */
    upg_reboot();
    return;
}

升级包本地升级

开发流程

在WS63中,本地升级程序在被称为flashboot的程序中运行。

  1. 调用uapi_upg_init初始化升级模块。

  2. 调用uapi_upg_register_progress_callback函数注册进度通知回调函数。

  3. 如果用户有自定义的校验数据,调用uapi_upg_register_user_defined_verify_func注册自定义数据校验函数。

  4. 调用uapi_upg_start开始本地升级。

  5. 复位重启。

注意事项

  • flashboot中调用的uapi_upg_init与升级包保存中的uapi_upg_init虽然是同一个函数,但是运行在不同的程序,互相不受影响,均需要调用。

  • 虽然升级模块提供了数据校验的接口,但由于调用uapi_upg_start开始本地升级后,内部会首先调用校验的接口校验升级包,因此若无特殊需求(例如只做校验不做本地升级),正常情况下不必单独调用校验接口。

  • 如果注册了进度通知回调函数,在升级过程中会调用回调函数,并传入当前进度值。在回调函数中减少执行耗时较长的操作,否则会影响升级流程的时间。

  • 本地升级过程中不要断电,升级开始后不支持停止升级。

  • uapi_upg_start函数如果返回成功,则说明所有镜像都成功更新。更新成功后会重启重新进入flashboot,随后正常模式启动。

  • uapi_upg_start函数如果返回失败,则有两种情况:

    • 如果是更新镜像之前的校验失败,则说明升级包传输出错,或者收到的是不适合本设备的升级包,则直接退出升级流程,不会再进入flashboot,正常模式启动。

    • 如果是更新镜像已经开始后出现错误导致失败,则重启后仍然会进入flashboot进行重试,最多重试三次。恢复正常成功升级或者三次均失败后,不再重启进入flashboot,正常模式启动。

  • app和flashboot的升级在flashboot中完成,NV在app程序中升级。

编程实例

static void upg_serial_putc(const char c)
{
    printf("%c", c);
}
static void upg_progress_callback(uint32_t percent)
{
    printf("%d%% ", percent);
}
void app_init(void)
{
    uint32_t file_size = 0x2000;  /*  升级文件大小(实际大小由APP获取)  */
    uint32_t max_len;
    /* 文件系统相关的初始化 */
    /* 依赖flash */
    /* 依赖分区模块 */
    ret = uapi_partition_init(); /* 该接口可以重复初始化 */
    if(ret != ERRCODE_SUCC){
        printf("uapi_partition_init error. ret = 0x%08x\r\n", ret);
    }
    /* 注册进度通知函数 */
    (td_void)uapi_upg_register_progress_callback(upg_progress_callback); /* 此处回调函数需业务实现 */
    /* 开始本地升级 */
    ret = uapi_upg_start();
    if (!(ret == ERRCODE_UPG_NOT_NEED_TO_UPDATE || ret == ERRCODE_UPG_NOT_INIT ||  ret == ERRCODE_SUCC)) {
        printf("uapi_upg_start error = 0x%x\r\n", ret);
    }
    /* 复位 */
    upg_reboot();
    return;
}

OTA版本号配置/获取实例:
1)编译APP镜像:./build.py -c ws63-liteos-app
2)打OTA包:build/config/target_config/ws63/build_ws63_update.py --ver=hellword_654321
3)获取OTA版本号:
#include "upg_common.h"
void show_ota_ver(void)
{
    upg_package_header_t     *pkg_header = NULL;
    char ota_ver[0xF] = {0};
    errcode_t ret = upg_get_package_header(&pkg_header);
    if (ret != ERRCODE_SUCC || pkg_header == NULL) {
        PRINT("upg_get_package_header err = 0x%x \n", ret);
        return;
    }
    memcpy_s(ota_ver, 0xF, pkg_header->info_area.user_defined, 0xF);
    PRINT("show_ota_ver: %s \n", ota_ver);
}

AB面升级

接口说明

表 1 AB面升级接口入参及返回值描述

接口原型

参数及返回值说明

upg_region_index upg_get_run_region(void)

  • 接口说明:获取当前运行分区
  • 入参说明:无。
  • 返回值:
    • UPG_REGION_A:A面分区。
    • UPG_REGION_A:B面分区。

upg_region_index upg_get_upg_region(void)

  • 接口说明:获取升级分区
  • 入参说明:无。
  • 返回值:
    • UPG_REGION_A:A面分区。
    • UPG_REGION_A:B面分区。

uint32_t upg_ab_image_read(upg_region_index upg_region, uint32_t offset, uint8_t *buf, size_t len)

  • 接口说明:读取镜像
  • 入参说明:

    upg_region:需要读取的分区。

    offset:读取偏移

    buf:读取的数据的缓存区

    len:读取数据的长度

  • 返回值:
    • ERRCODE_SUCC:成功。
    • 其他:失败。

uint32_t upg_ab_image_write(upg_region_index upg_region, uint32_t offset, uint8_t *buf, size_t len)

  • 接口说明:写入镜像
  • 入参说明:

    upg_region:需要写入的分区。

    offset:写入偏移

    buf:写入的数据

    len:写入数据的长度

  • 返回值:
    • ERRCODE_SUCC:成功。
    • 其他:失败。

errcode_t upg_set_run_region(upg_region_index upg_region)

  • 接口说明:设置下次启动的镜像区域
  • 入参说明:

    upg_region:下次启动的分区

  • 返回值:
    • ERRCODE_SUCC:成功。
    • 其他:失败

errcode_t upg_ab_start(upg_region_index upg_region)

  • 接口说明:开始升级接口,擦除对应分区
  • 入参说明:

    upg_region:需要升级的分区

  • 返回值:
    • ERRCODE_SUCC:成功。
    • 其他:失败

开发实例

void app_init(void)
{
    upg_region_index upg_region = upg_get_upg_region();  /*  获取要升级的分区  */
    errcode_t ret = upg_ab_start(upg_region); /* 该接口会擦除对应分区 */
    if(ret != ERRCODE_SUCC){
        printf("upg_ab_start error. ret = 0x%08x\r\n", ret);
    }
    /* 写入镜像数据 */
    uint32_t offset;
    uint32_t max_size = 0;
    uint32_t buf_size = 0;
    uint8_t upg_buf[0x1000];
    net_get_upg_package_size(&max_size);/* 此处需要通过云端获取镜像大小,需要自行实现 */
    for (offset = 0; offset < max_size;) {
        buf_size = ((max_size - offset) < 0x1000) ? (max_size - offset) : 0x1000;
        net_get_upg_package_buf(offset, upg_buf, buf_size); /* 此处需要通过云端获取镜像数据,需要自行实现 */
        ret = upg_ab_image_write(upg_region, offset, upg_buf, buf_size);
        if (ret != ERRCODE_SUCC){
            printf("upg_ab_image_write error. ret = 0x%08x\r\n", ret); 
        }
        offset += buf_size;
    }
    /* 写入完成后切换启动分区 */
    ret = upg_set_run_region(upg_region);
    if (ret != ERRCODE_SUCC) {
        printf("upg_set_run_region error = 0x%x\r\n", ret);
    }
    /* 复位 */
    upg_reboot();
    return;
}

使用AB面升级方式的方法:

  1. SDK下执行 ./build.py ws63-flashboot menuconfig 命令配置flashboot的menuconfig,Middleware -> Chips -> Chip Configurations for ws63 -> 选中 FOTA for AB;然后保存即可

  2. 同步骤1方式,执行 ./build.py ws63_liteos_app menuconfig 命令配置flashboot的menuconfig,Middleware -> Chips -> Chip Configurations for ws63 -> 选中 FOTA for AB;然后保存即可

  3. 参考上述示例代码,编写AB面升级流程代码

  4. 配置完成后重新编译flashboot以及ws63_liteos_app即可

注意事项

  • 写入新镜像前,需要确认镜像大小没有超过对应分区大小。

  • 写入镜像时,需要确认镜像数据正常,无错传漏传。

  • 写入镜像与实际烧录镜像一致,不需要封装镜像,增加包头等。

  • AB面升级方式对flash的利用率较低,且当前已有足够的措施保证压缩升级过程的安全性以及后续运行阶段的稳定性,更推荐使用压缩升级方式。

  • 可根据自身需求选择AB面升级或者压缩升级,但选定并完成过一次相应的升级之后,不建议再切换到另外一种升级方式。

版本兼容性说明

对OTA升级镜像包校验方式更新后,SDK版本使用OTA方式更新存在如下限制:使用1.10.101及之前的SDK版本无法直接OTA升级到1.10.105及之后的SDK版本。

  • 解决方案1:

先OTA升级到1.10.102或1.10.103版本,再OTA升级到1.10.106及之后的版本

  • 解决方案2:按如下配置OTA升级后,可兼容后续SDK版本

  1. 修改新SDK版本中配置文件sdk\build\config\target_config\ws63\fota\fota.cfg中对应字段值为SignSuite=1和KeyAlg=0x2A13C812

  2. 将旧SDK版本中sdk\build\config\target_config\ws63\sign_config目录下的ec_bp256_app_private_key.pem和ec_bp256_oem_root_private_key.pem文件拷贝到新SDK版本中对应目录sdk\build\config\target_config\ws63\sign_config

  3. 新SDK版本打包OTA升级包时增加boot选项:python build/config/target_config/ws63/build_ws63_update.py --pkt=app,boot (不带参数默认打包app)

FAQ

  1. uapi_upg_prepare返回错误码:ERRCODE_PARTITION_CONFIG_NOT_FOUND。

    请检查是否烧写分区表。

  2. 新增了bin文件需要通过OTA进行升级,怎么扩展:

    1. upg_common_porting.c文件中 g_img_partition_map 中增加新配置,格式为 {新增镜像ID, 新增镜像分区表ID}。

      镜像ID 在upg_definitions_porting.h文件中进行定义。

    2. UPDATE_IMAGE_SET 宏定义后增加新镜像ID,对应UPDATE_IMAGE_SET_CNT 需要修正。

    3. fota.cfg 可参考 [hilink]新增配置。

    4. build_ws63_update.py修改升级包生成脚本。

    5. newbin的字段与fota.cfg中配置的需要对齐,否则打包会报错。

      可以调整不输入入参时,脚本默认打包的内容。