差分升级全流程说明:通路验证 → OTA 收包 → 写分区/解包

差分升级 = 设备端把「当前运行的 old 镜像 + 收到的差分包」合并出 new 镜像写入备份槽。
整条链路分三个阶段,下面按数据流顺序描述。


一、验证通路:蓝牙 / 天眼 / 总线

三条通道互不干扰,各自"硬件收字节 → 进队列 → 帧解析 → 进协议栈"。

1.1 蓝牙(USART1,PA9/PA10,DMA)

手机 App ──蓝牙──> BLE 模块 ──UART──> USART1 (DMA 接收)
                                        │
                     boot_ble.c: Bsp_Usart_Init() 初始化 USART1+DMA
                                        ▼
                           usart2_queue(环形缓冲,boot_ble.c)
                                        ▼
              ota_updata.c: ble_receive_frame_decode()  帧解析
                · 帧头/命令/长度/校验和校验
                · 控制帧(20B):开始升级/包长度/CRC 指令
                · 数据包:bt_serial 序号 + bt_length + 数据
                                        ▼
                    update_ble()  蓝牙升级状态机(见第二节)

1.2 天眼(CMC,UART4,PA0/PA1,中断)

天眼/云端 ──> 主控转发 ──UART──> UART4
                                  │
                boot_cmc.c: comm_CMC_Init() 初始化 UART4
                                  ▼
               QUEUE_RX_BUF_CMC(boot_cmc.c 队列缓冲)
                                  ▼
          boot_cmc.c: CMCAnalysisData_Boot()  帧解析(返回收到字节数)
                                  ▼
               FRAME_RX_BUF_CMC(完整帧缓冲)
                                  ▼
             app_updata.c: update_app_bycmc()  CMC 升级状态机

1.3 总线(RS485,USART2,PA2/PA3,9600,9bit 偶校验)

仪表总线/其他节点 ──RS485──> USART2
                              │
          boot_rs485.c: Bsp_Rs485_Init() 初始化(9600/9bit/偶校验)
                              ▼
        Bsp_Rs485_ReceiveOK() 一帧收齐判定
                              ▼
           RS485_BUF.str / strlen(帧数据)
                              ▼
         app_updata.c: update_app()  RS485 升级状态机

1.4 通路验证方式(上板前可先自测)

  • PC 模拟器(无硬件):./tools/ota_sim/ota_sim.exe old_os new_os out/diff/ota_diff.cpio
    已验证整条解析/重建链路(1KB 分包模拟通道到达 → 两条 COMPARE MATCH)。
  • 真机逐通道:RTT 看对应日志
    • 蓝牙:[MAIN][BLE] queue: N -> M bytes processed
    • 天眼:[MAIN][CMC] data received: N bytes, calling update_app_bycmc
    • 总线:[MAIN][RS485] data received: N bytes, calling update_app
    • 收到开始指令后:[OTA-CH] open: staging …,说明通路与协议栈已接通。

二、走 OTA 收包(协议状态机)

三条通道的帧解析结果统一进入升级协议状态机(同事协议,ACK/重传原样保留):

UP_Start → UP_Receive → UP_CRC → UP_OK(成功)/ UP_ERROR(失败)

2.1 开始升级(各通道的第一条指令)

通道 开始指令 处理位置 动作
蓝牙 0x0A + 子命令 0x01(开始)/ 0x02(总长度 programlen) ota_updata.c update_ble() 回 ACK;ota_base==NIU_DB(仪表)时 ota_channel_open(programlen) —— 擦除 app 分区暂存区(0x08EC0000),登记总长度
天眼 CMC CMD 0x05(含 programlen) app_updata.c update_app_bycmc() 同上:**ota_channel_open(programlen)**
总线 0x11(含 programlen,4 字节大端) app_updata.c update_app() 同上:**ota_channel_open(programlen)**

open 失败(超长/擦除失败)→ 直接置 UP_ERROR,不进入收包。

2.2 数据包(边收边落盘,不驻留 RAM)

通道 数据指令 处理
蓝牙 0x0A 子命令 0x03(每包带序号 bt_serial) 累积到 updata_buff[2048],凑满 1KB(PACKSIZE)调 flash_writes()
天眼 CMC CMD 0x06(带包序号 packnum) 直接调 flash_writes(flash_addr, data, words)
总线 0x12 数据帧(1KB/包) flash_writes(flash_addr, data, words)

关键桥接app_updata.c flash_writes() 一处分流):

  • 地址落在 APP 备份区(仪表镜像数据)→ **ota_channel_write()**:按 256B 页缓冲,
    凑满一页写一页到 app 分区暂存区,s_written 累计,每 16KB 打印进度
  • 其它地址(素材区)→ 原 flash 写入逻辑不变

每包收到后按协议回 ACK/NACK(send_ble_ota_data_ack / RS485 应答帧 / CMC 应答),
发送端重传机制原样生效。60 秒无数据 → 超时复位(Delay_TIME),看门狗全程喂。

2.3 收包完成(CRC 校验 → 触发重建)

通道 CRC 指令 处理
蓝牙 0x0A 子命令 0x04(远端 CRC) 本地算 CRC16_Ble(OTA_CHANNEL_STAGE_ADDR, programlen)(暂存区全量校验)对比
天眼 CMC CMD 0x07 本地 CRC16(OTA_CHANNEL_STAGE_ADDR, programlen) 对比
总线 0x13 CRC 帧 同上
  • CRC 一致 → **ota_channel_finish()**(进入第三节:解包+差分重建)→ UP_OK
  • CRC 不一致 → **ota_channel_abort()**(清空暂存区)→ UP_ERROR,回 NACK

下发端注意:payload 从"全量镜像"换成差分包 cpio(out/diff/ota_diff.cpio,
约 518KB),协议帧格式、包长、CRC 算法全部不变。


三、写分区、解包(ota_channel_finish 内部)

ota_channel_finish()(mh1000_ota/ota_channel.c)按序完成五步:

3.1 校验与冲刷

  • 检查 s_written == s_total(收包完整性,缺包直接失败清空)
  • 把最后一页(不足 256B 以 0xFF 补齐)写进暂存区

3.2 流式读回解包(CPIO 解析)

app 分区暂存区(0x08EC0000)
   │ 4KB 块流式读回(ch_flash_read,QSPI 内存映射)
   ▼
ota_parse_pack_feed()  CPIO 070702 容器状态机
   ├─ ota_info.bin   → ota_parse_manifest() 解析 [file] 段
   │                    (os_a.patch:os_a; 映射 + [diff] 段分区表)
   │                    → hook: ota_diff_on_manifest() 缓存 diff manifest
   └─ os_a.patch      → 按 [file] 映射找到目标分区 os_a
                        → staging 层 accumulate(malloc 517KB,仅此一个文件)
                        → ota_diff_apply()   ← 进入 3.3

3.3 差分重建与写分区(HDiffPatch)

ota_diff_apply("os_a", patch_data, 517KB, old_size, new_size, cache 128KB)
   │
   ├─ 目标分区解析:os_a(源)→ 配对槽 os_b
   ├─ 擦除 os_b 整个分区(4KB sector 循环)
   ├─ old 流:QSPI 内存映射随机读 os_a——源分区保持只读不破坏
   ├─ diff 流:patch 数据在 RAM(staging accumulate)
   ├─ new 流:256B 页缓冲,凑满一页 FLASH_ProgramPage 烧写 os_b
   │           (每 64KB 打印 burn 进度)
   └─ patch_decompress_with_cache():old+patch 流式重组 → 逐页写入 os_b
       峰值 RAM ≈ patch 517KB + cache 128KB ≈ 650KB

3.4 完成与清场

  • 校验写入字节数 == new_size(写短即失败)
  • 清空 app 分区暂存区(防残留旧包)
  • 打印 [OTA-CH] upgrade applied OK, reboot to switch A/B slot
  • 返回 0 → 状态机 UP_OK → 系统重启(NVIC_SystemReset)→ bootloader 切到 os_b 跑新系统

3.5 失败路径(任意一步出错)

  • ota_channel_abort():清空暂存区 + 复位状态
  • 状态机 UP_ERROR:回 NACK,不重启不切槽——设备继续跑 os_a 当前版本
    (A/B 机制天然回退,下次可重新发起升级)

附:全程数据流总览

手机/天眼/总线(payload=差分包 cpio)
   │ 蓝牙 USART1 ┃ 天眼 UART4 ┃ 总线 USART2
   ▼
帧解析(boot_ble / boot_cmc / boot_rs485)
   ▼
协议状态机(update_ble / update_app_bycmc / update_app)
   │ 开始指令 → ota_channel_open(总长)
   │ 数据包   → flash_writes 分流 → ota_channel_write(页写 app 分区暂存)
   │ CRC 指令 → ota_channel_finish() / abort()
   ▼
ota_channel_finish:
   暂存区流式读回 → CPIO 解包(ota_parse_pack)
   → os_a.patch 累积(staging)
   → ota_diff_apply:old(os_a 0x08040000) + patch ─HDiffPatch─> new
   → 逐页烧写 os_b(0x083E0000)
   ▼
UP_OK → 重启 → bootloader 切槽 → 新系统启动(失败则继续跑旧系统)
Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐