# MCU OTA 升级问题分析 — 方案设计文档

## 1. 项目背景

| 项目 | 说明 |
|------|------|
| 车机 SoC | Realtek RTD1861（Android 系统） |
| MCU 芯片 | HDSC HC32A4A8（Cortex-M4） |
| 通信链路 | Android(JNI/独立C测试) → UART(/dev/ttyS7) → MCU(USART3) |
| 波特率 | 460800, 8N1 |
| Android 架构 | aarch64 |
| 工具链 | NDK 30 (`aarch64-linux-android21-clang`) 位于 `/Users/admin/Library/Android/sdk/ndk/30.0.14904198` |
| 调试工具 | Mac + J-Link（已配置 HC32A4A8 SWD）+ ADB（192.168.0.55） |
| 车机权限 | root（可 su） |
| Mac 密码 | 1234 |

## 2. 固件 / 文件清单

### 2.1 MCU 固件

| 文件 | 路径 | 说明 |
|------|------|------|
| MCU 升级 bin | `/Users/admin/workspace/jyq/mcubin/mcu_app.bin` | OTA 升级文件（216,860 bytes ≈ 211.8 KB） |
| MCU 线刷 hex | `/Users/admin/workspace/jyq/mcubin/mcu_app.hex` | J-Link 直接烧录 |
| MCU 反汇编 | `/Users/admin/workspace/jyq/mcubin/mcu_app_disasm.txt` | 3.5 MB 反汇编，用于 SWD 调试分析 |
| 文件说明 | `/Users/admin/workspace/jyq/mcubin/reamin.txt` | bin=升级文件, hex=线刷文件 |

### 2.2 协议文档

| 文件 | 路径 | 说明 |
|------|------|------|
| 升级协议详细定义 | `/Users/admin/workspace/jyq/qsjnic/docs/MCU_ARM_Reference/MCU升级-Update.xlsx` | **核心文档**：定义了 0x80~0x86 所有升级命令和参数 |
| 主协议定义 | `/Users/admin/workspace/jyq/qsjnic/docs/MCU_ARM_Reference/MCU-ARM_v2.xlsx` | 帧格式 + 所有 MsgType 命令定义 |
| 序列号规则 | `/Users/admin/workspace/jyq/update/关于序列号.txt` | Setup/ACK/HB/Data 帧序列号规则 |
| 通信链路设计图 | `/Users/admin/workspace/jyq/qsjnic/docs/MCU_ARM_Reference/MCU & Linux 通信链路设计.doc` | Visio 架构图（离职工程师遗留） |
| Public 命令 | `/Users/admin/workspace/jyq/qsjnic/docs/MCU_ARM_Reference/Public_v2.xlsx` | 公共命令集（0x81~0x8A） |

### 2.3 已有测试代码（关键！离职工程师的工作成果）

| 文件 | 路径 | 说明 |
|------|------|------|
| 协议头 | `/Users/admin/workspace/jyq/qsjnic/test_Serial/protocol.h` | 完整帧类型、命令宏定义 |
| 协议实现 | `/Users/admin/workspace/jyq/qsjnic/test_Serial/protocol.c` | 帧构建/解析 |
| 串口 IO | `/Users/admin/workspace/jyq/qsjnic/test_Serial/serial_io.c` + `.h` | UART 打开/读写/配置 |
| 升级 Bug 复现 | `/Users/admin/workspace/jyq/qsjnic/test_Serial/mcu_upgrade_bug_repro.c` | **最小复现程序**，测试 0x80→0x81→0x82 流程 |
| 全功能测试 | `/Users/admin/workspace/jyq/qsjnic/test_Serial/main_test.c` | 完整协议测试套件 |
| 测试日志 | `/Users/admin/workspace/jyq/qsjnic/doc/logs_from_device/ota_run.log` | 上机实际运行日志（294行） |
| 测试报告 | `/Users/admin/workspace/jyq/qsjnic/test_Serial/test_report/MCU_Upgrade_Failure_Root_Cause_Analysis.md` | 根因分析报告 |
| JLink 脚本 | `/Users/admin/workspace/jyq/qsjnic/test_Serial/jlink_*.py` | J-Link 调试脚本（多文件） |

### 2.4 MCU 固件特征（从二进制反分析）

| 特征 | 值 |
|------|-----|
| 架构 | Cortex-M4 |
| 向量表 SP | 0x2001B310 |
| 复位向量 | 0x00000355 |
| 操作系统 | FreeRTOS |
| 升级机制 | 双 Bank Boot Swap（有 swap on/off 标志位） |
| 固件版本字符串 | SGMWV1.0.0 |
| 型号标识 (MCU Version) | E112C069XZA0120015 |
| SW 版本 | 0302_20260408（2026-04-08 编译） |
| 任务模块 | MCU_UPGRADE, sys_mgr, public_mgr, mcu_comm(armcore+smallcore), nm_mgr, power_mgr, meter_mgr, keymgr, factorymgr |
| 通信外设 | USART3(ARM 链路@460800), USART2(Small Core 链路), CAN1/CAN2 |

## 3. 通信协议

### 3.1 帧格式

```
Byte0:    0xFF              (Head1)
Byte1:    0xAA              (Head2)
Byte2-3:  Length (大端)     (整帧长度，含 Head~Checksum)
Byte4:    SerialNo          (帧序列号，0x00~0xFF 循环递增)
Byte5:    FrameType         (帧类型)
Byte6:    MsgType           (消息类型)
Byte7:    Cmd               (命令字)
Byte8~N:  Payload           (数据内容，长度 = Length - 9)
ByteN+1:  Checksum          (校验和)
```

**Checksum 算法**：
```
checksum = (～(LenMsb + LenLsb + SerialNo + FrameType + D0 + ... + Dn)) & 0xFF
```
即对 Byte2~Byte(N) 求和后取反。

### 3.2 帧类型 (FrameType, Byte5)

| 代号 | 值 | 含义 |
|------|-----|------|
| Setup | 0x01 | 建链请求 |
| ACK | 0x02 | 响应确认（链路层） |
| SetupAck | 0x03 | 建链回复 |
| Close | 0x04 | 关闭链路 |
| HB | 0x05 | 心跳 |
| Data (Normal) | 0x06 | 数据消息（普通交互走此通道） |
| Notify | 0x07 | MCU 主动推送（电压/频率等，单向，ARM不应回ACK） |
| Boot | 0x08 | BOOT 消息 |

### 3.3 序列号规则

- `recvSerialNo`：记录对方发来的最新序列号，回复时原样返回
- `sendSerialNo`：记录自己已发送的序列号，收到对方 ACK 后递增
- 序列号从 1 开始，溢出回绕时若为 0 则强制置 1
- Setup 阶段特殊：握手时双方序列号同步为相同值

### 3.4 消息类型 (MsgType, Byte6)

| 值 | 含义 |
|----|------|
| 0x01 | Public（公共接口：版本、背光、时间等） |
| 0x05 | Radio（收音机） |
| 0x07 | **Update（升级）** ← 本方案关注 |

### 3.5 建链过程 (参考 关于序列号.txt)

```
ARM → MCU: FF AA 00 07 01 01 F6         ← Setup(0x01), 双方序列号统一置1
MCU → ARM: FF AA 00 07 01 03 F4         ← SetupAck(0x03)
ARM → MCU: FF AA 00 07 01 02 F5         ← ACK(0x02), 建链成功
```

建链后双方可以发送 Data(0x06) 帧进行业务数据交互。

## 4. MCU 升级协议（完整命令集）

所有升级命令走 **MsgType=0x07**, FrameType=**0x06 (Data)**。

### 4.1 MCU → ARM 命令

| Byte7(Cmd) | 命令名 | 说明 |
|------------|--------|------|
| 0x01 | UPDATE_M2A_UPDATE_ACK | MCU 对 ARM 下发升级指令的应用层 ACK |
| 0x02 | UPDATE_M2A_UPDATE_LEN | 告知 ARM 每包数据长度 |
| 0x03 | UPDATE_M2A_UPDATE_IND_AQUIRE | MCU 向 ARM 请求对应索引的升级包 |

#### 0x01 ACK 类型 (Payload[0])

| 值 | 枚举 | 含义 |
|----|------|------|
| 0 | EN_ACK_UPDATE_NONE | 未用 |
| 1 | EN_ACK_UPDATE_REJECT | 升级拒绝（项目编号不匹配等） |
| 2 | EN_ACK_UPDATE_REQUEST | MCU 同意升级，请求数据 |
| 3 | EN_ACK_UPDATE_SUCCESS | 升级成功 |
| 4 | EN_ACK_UPDATE_FAIL | 升级失败 |
| 5 | EN_ACK_UPDATE_RX_LAST_FRAME_OK | 已收到最后一帧数据 |

#### 0x02 UPDATE_M2A_UPDATE_LEN

| Payload[0~1] | 说明 |
|------|------|
| LenH, LenL | 每包数据长度（实测为 1024，必须为 2^n） |

#### 0x03 UPDATE_M2A_UPDATE_IND_AQUIRE

| Payload[0~1] | 说明 |
|------|------|
| IndexH, IndexL | 请求的包索引（MCU 控制，ARM 被动执行） |

### 4.2 ARM → MCU 命令

| Byte7(Cmd) | 命令名 | 说明 |
|------------|--------|------|
| 0x80 | UPDATE_A2M_UPDATE_REQ | ARM 启动升级请求 |
| 0x81 | UPDATE_A2M_UPDATE_BEGIN | ARM 启动 MCU 升级（含 iBinLength） |
| 0x82 | UPDATE_A2M_UPDATE_DATA | ARM 下发 MCU 升级数据 |
| 0x83 | UPDATE_A2M_UPDATE_END | ARM 结束 MCU 升级（含 CRC16） |
| 0x84 | UPDATE_A2M_BOOT_UPDATE_BEGIN | ARM 启动 MCU BOOT 升级 |
| 0x85 | UPDATE_A2M_BOOT_UPDATE_DATA | ARM 下发 BOOT 升级数据 |
| 0x86 | UPDATE_A2M_BOOT_UPDATE_CHECKSUM | ARM 发送 BOOT MD5 校验 |

### 4.3 各命令详细参数

#### 0x80 UPDATE_A2M_UPDATE_REQ

Payload[0] (P0) = 命令值:

| P0 | 含义 |
|----|------|
| 1 | 请求 ARM 升级 |
| 2 | ARM 升级中 |
| 3 | ARM 升级完成 |
| **4** | **请求 MCU 升级** ← 本方案使用 |
| 5 | 请求 MCU BOOT 升级 |
| 6~7 | MCU+ARM 联合升级 |

当 **P0=4** 时，Payload 完整结构：
```
Byte8  = P0     = 0x04
Byte9~13 = P1~P5 = 项目编号 (5字节，实测：45 31 31 32 43 = "E112C")
Byte14~17 = P6~P9 = iBinLength (4字节大端，文件总大小，补0xFF对齐)
```

**总帧长 = 2(Head) + 2(Len) + 1(Serial) + 1(FT) + 1(MT) + 1(Cmd) + 10(Payload) + 1(CS) = 19**

#### 0x81 UPDATE_A2M_UPDATE_BEGIN

```
Byte8~11  = P0~P3  = iBinLength (4字节大端)
Byte12~16 = P4~P8  = 项目编号 (5字节："E112C")
```

**总帧长 = 9 + 9 = 18**

#### 0x82 UPDATE_A2M_UPDATE_DATA

```
Byte8~9   = IndexH, IndexL (由 MCU 的 0x03 命令指定)
Byte10~n  = UpdateData (每包数据，补 0xFF 至整包)
```

**总帧长 = 9 + 2(Index) + 1024(Data) = 1035**

#### 0x83 UPDATE_A2M_UPDATE_END

```
Byte8~9   = CRC16 校验值 (高位在前)
```

### 4.4 升级流程（正常路径）

```
ARM                              MCU
  │                                │
  ├── Setup(0x01) ───────────────►│
  │◄── SetupAck(0x03) ────────────┤
  ├── ACK(0x02) ─────────────────►│   建链完成
  │                                │
  ├── 0x80(REQ, P0=4, Proj, Len)►│   请求MCU升级
  │◄── 链路ACK(0x02) ─────────────┤   传输层确认
  │◄── 0x01(UPDATE_ACK,type=2) ──┤   MCU同意升级  ← 这里在实测中缺失！
  │                                │
  ├── 0x81(BEGIN, Len, Proj) ───►│   开始升级
  │◄── 链路ACK(0x02) ─────────────┤
  │◄── 0x02(UPDATE_LEN=1024) ────┤   每包长度
  │◄── 0x03(IND_AQUIRE, idx=0) ──┤   请求第0包
  │                                │
  ├── 0x82(DATA, idx=0, data...) ►│   下发第0包
  │◄── ??? 无响应 ────────────────┤   ← Bug在这里！
  │                                │
  │          (超时重试 2 次后放弃)            │
```

## 5. 已有测试结果分析

### 5.1 从 ota_run.log 提取的完整交互

日志路径：`/Users/admin/workspace/jyq/qsjnic/doc/logs_from_device/ota_run.log`

| 步骤 | 结果 |
|------|------|
| 建链（Setup/SetupAck） | ✅ 成功 |
| Public 接口测试（版本查询/背光设置等） | ✅ 全部 12 项通过 |
| 0x80 UPDATE_REQ（P0=4, Proj="E112C", Len=217088） | ⚠️ 链路ACK收到，应用层UPDATE_ACK未回复 |
| 0x81 UPDATE_BEGIN（Len=217088, Proj="E112C"） | ✅ 收到 UPDATE_LEN=1024 + IND_AQUIRE idx=0 |
| **0x82 UPDATE_DATA（idx=0, 1024字节）** | **❌ MCU 完全不回复！重试2次后超时** |

### 5.2 关键结论

1. **项目编号已知**：`45 31 31 32 43` = ASCII `"E112C"`（5字节）
2. **项目编号不是卡点**：MCU 在 0x81 后正常返回了 UPDATE_LEN 和 IND_AQUIRE，说明它认可了升级请求
3. **Bug 精确位置：0x82 数据帧**：第一包数据发过去后 MCU 完全不回任何帧
4. 0x82 帧格式正确（从日志可见完整帧内容）
5. 链路层没问题（后续心跳仍在正常工作）

### 5.3 0x82 失败的疑似原因

| 假设 | 验证方法 |
|------|----------|
| MCU 接收缓冲区溢出（1035 字节一帧） | 减小 payload 到 256/512 字节测试 |
| MCU 等待某种握手确认后才接数据 | 发送 0x80 后，等待一个带 type=2 的 UPDATE_ACK |
| MCU 的 Erase/Write 操作耗时导致超时 | 增大每包间隔 |
| iBinLength 与实际数据不符 | 检查对齐后的 iBinLength=217088 是否正确 |
| MCU 层面 CRC 校验失败 | 用 J-Link 断点跟踪 MCU 升级任务 |
| 0x80 返回的 ACK type=2 缺失导致 MCU 状态不对 | 先确保收到 type=2 再发 0x81 |

## 6. 方案：ARM 端 C 测试程序

### 6.1 目标

基于已有代码（`mcu_upgrade_bug_repro.c` + `protocol.h`），编写一个**精确控制变量的测试程序**，聚焦 0x82 阶段。

### 6.2 程序架构

```
┌─────────────────────────────────────────┐
│           mcu_upgrade_test               │
├─────────────────────────────────────────┤
│  main()                                  │
│    ├── serial_open("/dev/ttyS7", 460800) │
│    ├── do_handshake()                    │
│    ├── send_0x80_req(proj, bin_len)      │
│    ├── wait_for_update_ack()             │
│    ├── send_0x81_begin(bin_len, proj)    │
│    ├── recv_update_len() → pkt_size      │
│    ├── recv_ind_aquire() → idx           │
│    ├── send_0x82_data(idx, data, len)    │
│    ├── wait_for_mcu_response()           │
│    └── dump_log(stdout)                  │
└─────────────────────────────────────────┘
```

### 6.3 UART 配置

```
Baud: 460800, 8N1, no flow control
```

### 6.4 关键技术细节

| 项目 | 值 |
|------|------|
| 项目编号 | `E112C`（5字节ASCII：0x45 0x31 0x31 0x32 0x43） |
| iBinLength | bin 大小向上取整到 pkt_size 的整数倍 |
| 每包大小 | 0x02 命令回复中获取（实测 1024） |
| 补 0xFF | 不足一帧的数据用 0xFF 填充 |
| CRC16 | 默认 CCITT 0x1021（待 MCU 端确认） |

## 7. 实施计划

```
Phase 1: 编写 mcu_upgrade_test.c
  ├── UART 打开/配置
  ├── 帧构建/发送/接收/校验
  ├── 建链 + 0x80 + 0x81 + 0x82 流程
  └── 详细日志输出

Phase 2: 编译 & 推送
  ├── NDK 交叉编译
  ├── adb push + chmod +x
  ├── adb shell 运行
  └── 收集日志

Phase 3: 根因定位
  ├── 分析 0x82 无响应的具体原因
  ├── 调整参数重试（包大小 / 延时 / 对齐方式）
  └── 必要时用 J-Link 读 MCU 寄存器/内存

Phase 4: 报告
  └── update_report.md
```

## 8. 风险与应对

| 风险 | 应对 |
|------|------|
| MCU 固件升级模块有 Bug 不接受数据 | 用 J-Link 跟踪 MCU 升级任务状态 |
| 波特率过高导致丢帧 | 降低分包大小，增大帧间隔 |
| MCU flash 写保护 | 用 J-Link 检查 EFM 寄存器 |
| 0x80 缺少 UPDATE_ACK 导致 MCU 状态异常 | 在 0x80 之后等待 3~5s 确保收到应用层 ACK |

连接jlink，重新开一个终端/Applications/SEGGER/JLink_V942/JLinkGDBServer -Device HC32A4A8xI -If SWD -Speed 4000 -port 2331
