# JetWave ECU 动力模组深度程序分析报告

## 「主控失速 + 断联 + 遥控器无法控制」根因与系统设计评审

**文档版本：** v1.0  
**生成日期：** 2026-05-26  
**代码基线：** `JetWaveECU-master-2d55e90f4a54d61a83efab89195082109acd3472`（`2d55e90f`）  
**前置计划：** [`JETWAVE_ECU_POWERTRAIN_ANALYSIS_PLAN.md`](./JETWAVE_ECU_POWERTRAIN_ANALYSIS_PLAN.md)  
**关联现场：** history2 / 荷兰异常报告 / App `CODE_REVIEW_*`  

---

## 执行摘要

### 本报告要回答的唯一核心问题

> **在什么情况下，会出现：板体进入失速保护、主控与电调通信被判异常（用户感知为「断联」）、用户仍推油门但动力无法恢复——即「失速 + 断联 + 遥控器无法控制」？**

从 **第一性原理**（水上动力安全系统）出发，控制链路应满足：

1. **感知**：可靠获知 RC 意图、ESC 状态、BMS/IMU 健康  
2. **决策**：故障时进入 **明确、可预测、可恢复** 的安全态  
3. **执行**：安全态下 **主动切断或限制扭矩**，而非仅弹窗  
4. **告知**：向用户说明 **当前能否恢复、如何恢复**  

对照 `2d55e90f` 源码与近一月轨迹数据，结论如下：

| 结论 | 严重程度 | 说明 |
|------|----------|------|
| **ECU 在 STALL 后仍按油门向 ESC 发非零转速** | **P0 安全** | `MotorSpeedControlTask` 未对 `ESC_FAULT_STALL_PROTECTION (0x20000)` 做任何限扭/清零 |
| **BLE 断链 (mode=255) 不向 ESC 发零转速** | **P0 安全** | 断链只本地 `MotorSpeed=0`，**未调用** `SendSetSpeedRequest(0)` |
| **`FAULT_ESC_COMMUNICATION` 可由数据路径丢帧触发** | **P0 误判** | 12s 无队列数据即置位，与 TWAI 10:1 降频、任务周期叠加 |
| **失速码来自 ESC `running_error` 高 16 位，ECU 又二次合成 bit1** | **P1 语义** | `speed <= -20000` 也会置位与 STALL 同位的 `running_error` bit1 |
| **App/ECU 均无「失速恢复」状态机** | **P1 体验** | 仅上报与弹窗，无强制松油门、无 ESC 复位握手 |
| **故障上报状态机单 bit 间隔约 12s** | **P2** | `FAULT_DURATION_CYCLES` 导致复合故障感知滞后 |

**最重要场景（按现场频率与危害排序）：**

1. **真失速锁死（Scenario A）**：ESC 固件堵转/失速保护 → `esc_event |= 0x20000` → 电机停转，但 ECU **继续发转速指令** → 用户推油门无效 → 日志表现为 `throttle>0 && motorSpeed=0`  
2. **假断联叠加（Scenario B）**：STALL 期间 ESC 反馈变慢或队列丢包 → 12s 后 `ecu_event |= FAULT_ESC_COMMUNICATION` → 用户看到「失速 + ESC 通信故障」→ 误以为主控断联，实则 RC 蓝牙可能仍连接  
3. **链路断链未拉闸（Scenario C）**：BLE/星闪断开 → `mode=255` → **不向 CAN 发零速** → 若 ESC 侧保持上一指令，存在 **断链后仍驱动** 风险（取决于 ESC 固件超时策略）  
4. **升级/任务挂起（Scenario D）**：`MotorSpeedControlTaskSuspend()` 后不再发转速 → 与故障无关但表现同为「无法控制」  

---

## 一、问题定义与用户语义对齐

### 1.1 用户口述 vs 日志字段 vs 代码枚举

| 用户说法 | 轨迹/App 典型信号 | 协议定义 (`JetWave.xml`) | 实际含义 |
|----------|-------------------|--------------------------|----------|
| 板子失速 | `escEventCode & 131072` | `ESC_FAULT_STALL_PROTECTION` `0x00020000` | **电调侧**堵转/失速保护，非 ECU 死机 |
| 主控断联 | `ecuEventCode & 1073741824` | `FAULT_ESC_COMMUNICATION` `0x40000000` | **ECU 判定** 12s 未收到 ESC 运行数据，非 RC↔ECU 蓝牙断链 |
| 遥控器无法控制 | `throttleLeverQuantity>0` 且 `motorSpeed=0` | — | **控制意图未转化为扭矩**；RC 链路可能正常 |
| 蓝牙真断链 | ECU 内部 `mode=255` | 非标准 `OperatingMode`，为 ECU 私有约定 | `ble_server.c` 断开时注入 |

**关键澄清（避免排查跑偏）：**

- 日志里的「ECU 断联」**几乎总是** `FAULT_ESC_COMMUNICATION`（ECU↔ESC CAN 路径），**不是** 遥控器与 ECU 的 BLE/星闪断开。  
- 「失速」是 **ESC 保护行为**；「无法控制」在多数会话中是 **保护未恢复 + ECU 未限扭**，而非 RC 报文不到达。

### 1.2 核心故障码的位域编码（必须统一理解）

ECU 打包方式（`ecu_manager.c`）：

```c
latestEcuEventData.esc_event = (uint32_t)(escData.runningError) << 16 | escData.selfcheckError;
```

因此 **`esc_event` 的 bit17（`0x20000`）= `runningError` 的 bit1（`0x0002`）左移 16 位**。

| 字段 | 宽度 | 在 `esc_event` 中的位置 |
|------|------|-------------------------|
| `selfcheckError` | uint16 | bit 0～15 |
| `runningError` | uint16 | bit 16～31 |

`ESC_FAULT_STALL_PROTECTION = 0x20000` **只能**来自 `runningError` 的 bit1，不可能单独由 `selfcheckError` 产生。

---

## 二、系统架构：控制面 vs 安全面（架构师视角）

### 2.1 逻辑分层与信任边界

```mermaid
flowchart TB
    subgraph CP["控制面 Control Plane — 软实时 ~20ms"]
        RC[RC App ThrottleAndGear 20ms]
        BLE[BLE / NearLink MAVLink]
        MSC[MotorSpeedControlTask Prio=15]
        SET[SendSetSpeedRequest]
    end
    subgraph SP["安全面 Safety Plane — 应硬实时、fail-safe"]
        ESC_HW[ESC 失速/过流/过温保护]
        ECU_SYN[UpdateESCErrorCodeByRunningStatus]
        COMM[FAULT_ESC_COMMUNICATION 12s]
        MODE[mode 254/255 油门丢失/断链]
    end
    subgraph MP["监控面 Monitor Plane — 慢 ~500ms"]
        HB[EcuHeatbeat 500ms]
        EVT[EventCodeProcessByBit 12s/bit]
        APP[App 弹窗 + 埋点]
    end
    RC --> BLE --> MSC --> SET --> ESC_HW
    ESC_HW --> ECU_SYN --> HB
    ECU_SYN --> EVT --> APP
    COMM --> HB
    MSC -.->|缺失：未订阅 STALL| ESC_HW
```

**架构缺陷（第一性原理）：**

- **控制面**（20ms 油门 → 转速）与 **安全面**（STALL、通信丢失）**未在同一任务内闭环**。  
- **监控面**（500ms 心跳、12s 才扫完一位故障）只负责「告知」，**不具备切断扭矩的权威**。  
- 安全关键决策散落在：`esc_manager`（部分）、`ecu_manager`（通信超时）、`ble_server`（mode）、ESC 固件（未知恢复策略）—— **无单一 Safety Supervisor**。

### 2.2 任务、核心与优先级

| 任务 | 周期 | 优先级 | 核心 | 职责 |
|------|------|--------|------|------|
| `MotorSpeedControlTask` | 20ms | **15** | 1 | RC 油门 → `SendSetSpeedRequest` |
| `ProcessEcuDataTask` | 50ms | 9 | 1 | 心跳、通信故障计数、事件状态机 |
| `ProcessEscDataTask` | 100ms | 8 | 1 | ESC 反馈、二次故障合成、入队 |
| TWAI RX/TX | ~15ms 级 | — | — | UAVCAN 编解码 |
| `main` 喂狗 | 1000ms | — | — | WDT |

**风险：** 控制任务优先级高于 ESC 数据处理任务，符合「先执行控制」直觉，但 ESC 故障反馈 **最晚可滞后 100ms + 队列 + 10:1 降频**，安全面信息 **系统性落后于控制面**。

### 2.3 双链路初始化（BLE vs 星闪）

`main.c`：`NearLinkUartInit()` 成功则走星闪回调注册，否则 ESP32 原生 BLE。  
`ecu_manager.c` 运行时还可切换 NearLink SLE/BLE Server 回调（`rcCallbacksRegistered`）。

**影响：** 失速/断联问题需按 **实际出货链路** 分 variant 验证；星闪与 BLE 在断链时均注入 `mode=0xFF`，行为一致，但 **RF 稳定性不同** 会导致 `FAULT_ESC_COMMUNICATION` 发生率差异（与 RC-3000 CODE_REVIEW 结论一致）。

---

## 三、端到端数据流与状态时序

### 3.1 正常控制路径

```
RC App --[MAVLink ThrottleAndGear @~20ms]--> BLE/NearLink
    --> ParseMavlinkFromRc --> SendRcThrottleAndGearCmd (queue)
    --> MotorSpeedControlTask (20ms)
        --> 滤波 / 模式表 maxSpeedForModes[3][16]
        --> 可选 ALC 俯仰修正 (BEGINNER, gear14)
        --> SendSetSpeedRequest(MotorSpeed)  [@50Hz 每控制周期]
    --> UAVCAN SetSpeed Request --> ESC 固件
    <-- SetSpeed Response (speed, running_error, selfcheck_error, ...)
    <-- uavcan_twai: 每10帧才 SendEscSpeedResponseDataToQueue 一次
    --> ProcessEscDataTask (100ms)
        --> UpdateEscSpeedInfo + UpdateESCErrorCodeByRunningStatus
        --> SendEscRunningDataToQueue
    --> ProcessEcuDataTask (50ms)
        --> UpdateEcuHeatbeatDataByDevice + EventCodeProcessByBit
        --> 500ms SendMavlinkToBleRC(EcuHeatbeatData)
```

### 3.2 故障路径（失速 + 通信故障叠加）

```mermaid
sequenceDiagram
    participant U as 用户/RC
    participant ECU as MotorSpeedControlTask
    participant CAN as TWAI/UAVCAN
    participant ESC as 电调固件
    participant M as ProcessEcuDataTask

    U->>ECU: 油门 > 0
    ECU->>CAN: SetSpeed(N>0) 每20ms
    CAN->>ESC: 转速指令
    ESC-->>ESC: 堵转/失速保护触发
    ESC-->>CAN: running_error bit1 置位
    Note over ECU: 未检测 STALL，继续 SetSpeed(N>0)
    ESC-->>CAN: speed=0 或拒绝输出
    CAN-->>M: 反馈降频/偶发丢队列
    M->>M: 12s 无 ReceiveEscRunningData
    M->>U: ecu_event FAULT_ESC_COMMUNICATION
    U->>U: 感知：失速+断联，油门仍无效
```

---

## 四、根因场景详解（嵌入式 / 单片机 / 底层系统视角）

### Scenario A：真失速锁死 — **现场主因**

**触发条件（物理 + ESC 固件，ECU 仓库不可见完整逻辑）：**

- 螺旋桨负载突变、缠绕、高速堵转、母线低压大电流等  
- ESC 固件置位 `running_error` 对应位（映射到 MAVLink `0x20000`）  
- 荷兰样本：SN `231AAAA5C001688`、长时间高速、STALL 占比 18%～32%（会话 15349/15353）

**ECU 侧行为（已证实）：**

```425:430:JetWaveECU-master-2d55e90f4a54d61a83efab89195082109acd3472/App/src/esc_manager.c
        // 如果存在空转保护故障码，强制转速为0
        if (LastEscSpeedData.runningError & (uint16_t)(0x0001 << 9)) {
          // MotorSpeed = 0;
          ESP_LOGD(LOG_TAG, "Idle protection in progress, speed forced to 0");
        }
        SendSetSpeedRequest(MotorSpeed);
```

- 仅检查 **空转保护 bit9**，且 `MotorSpeed = 0` **被注释掉**  
- **对 STALL（bit1 → esc 0x20000）无任何处理**  
- 每 20ms 仍 `SendSetSpeedRequest(MotorSpeed)`，与用户油门一致  

**为何「遥控器无法控制」：**

| 层级 | 行为 |
|------|------|
| RC App | 油门指令正常发出（蓝牙仍连接） |
| ECU | 正常转发转速请求 |
| ESC | 保护态拒绝输出 → `motorSpeed≈0` |
| 用户 | 推油门无效，**控制环路在 ESC 处被硬切断** |

**恢复缺失：**

- 源码 **无**「失速清除」MAVLink 命令处理  
- **无**「松油门 N 秒自动清零」策略  
- **无** `SendEscRebootRequest()` 与 STALL 联动  
- App 侧（CODE_REVIEW）仅弹窗 + 埋点，**不切断 UI 油门显示、不强制安全模式**

---

### Scenario B：假断联（`FAULT_ESC_COMMUNICATION`）— **与失速强共现**

**置位逻辑：**

```534:556:JetWaveECU-master-2d55e90f4a54d61a83efab89195082109acd3472/App/src/ecu_manager.c
    static uint32_t ESC_CommunicationFailure = 0;
    if (ReceiveEscRunningDataFromQueue(&EscRunningData, 0) == pdPASS) {
        ...
        ESC_CommunicationFailure = 0;
        ecuEventCode &= ~FAULT_ESC_COMMUNICATION;
    } else if (GetRcConnectionStatus()) {
      ESC_CommunicationFailure ++;
      if (ESC_CommunicationFailure >= 240) { // 12S
        ecuEventCode |= FAULT_ESC_COMMUNICATION;
      }
    } else if (!GetRcConnectionStatus()) {
      ESC_CommunicationFailure = 0;
      ecuEventCode &= ~FAULT_ESC_COMMUNICATION;
    }
```

**条件解读：**

- `ProcessEcuDataTask` 周期 **50ms** → `240 × 50ms = 12s`  
- 条件：**RC 蓝牙仍连接** + **12s 内一次都没从 `EscRunningData` 队列收到数据**  
- **不是** RC 断链检测；RC 断链反而 **清除** 该故障位  

**为何会误报/叠加：**

| 因素 | 机制 |
|------|------|
| UAVCAN 接收降频 | `HandleEscSetSpeedResponse` **每 10 帧** 才入队一次 |
| `ProcessEscDataTask` 100ms | 消费队列慢于 20ms 控制 |
| STALL 后 ESC 可能降频/停发 Response | 队列长期空 → 12s 后置通信故障 |
| `SendEscRunningDataToQueue(..., 0)` 非阻塞 | 队列满则丢运行数据，通信计数仍走 `else` 分支 |
| TWAI Bus-Off 恢复窗口 | `twai_ops_esp32.c` 恢复期间无有效 RX |

**用户感知：** App 提示「ESC 通信异常」+ 失速弹窗 → **「主控断联」**；实际上 RC↔ECU 可能正常，是 **ECU↔ESC 监控路径** 超时。

**与「无法控制」关系：** 该故障位 **不改变** `MotorSpeedControlTask` 行为（未检索 `ecuEventCode`），控制仍取决于 Scenario A/C。

---

### Scenario C：BLE/星闪断链 — **安全漏洞级**

**断链注入（`ble_server.c`）：**

```302:306:JetWaveECU-master-2d55e90f4a54d61a83efab89195082109acd3472/Ble/src/ble_server.c
          rcThrottleAndGear.mode = 0xFF;
          ...
          SendRcThrottleAndGearCmd(MAVLINK_MSG_ID_ThrottleAndGear, &rcThrottleAndGear, pdMS_TO_TICKS(10));
```

**`MotorSpeedControlTask` 处理：**

```439:443:JetWaveECU-master-2d55e90f4a54d61a83efab89195082109acd3472/App/src/esc_manager.c
      case 255:  // 蓝牙断链
        ESP_LOGD(LOG_TAG, "NOBLUETOOTH");
        ThrottleAndGear.throttle = 0;
        MotorSpeed = 0;
        break;   // ← 无 SendSetSpeedRequest(0)
```

对比 `case 254`（连接但 1s 无油门）**会** `SendSetSpeedRequest(0)`。

**失败模式：**

- 断链前最后一帧为非零转速 → 断链后 ECU **不再向 CAN 发送任何 SetSpeed**  
- ESC 若依赖 **持续指令** 或 **超时后才归零**，存在 **断链后短时不明输出** 风险  
- 这是典型的 **fail-unsafe**（未主动拉闸），违反安全系统设计「失联即安全」原则  

**注意：** 断链时 `GetRcConnectionStatus()==false`，`FAULT_ESC_COMMUNICATION` **被清零**，用户 **看不到** ECU 通信故障提示，更易误解为「遥控器坏了」而非「板子还在按最后一档跑」。

---

### Scenario D：油门丢失 1s — **部分安全、但仍可改进**

```336:342:JetWaveECU-master-2d55e90f4a54d61a83efab89195082109acd3472/App/src/esc_manager.c
    } else {
      if (ThrottleLostCnt++ >= 50) {  // 1s @20ms
        if (ThrottleAndGear.mode != 255) {
          ThrottleAndGear.mode = 254;  // 蓝牙连接但不发油门
        }
      }
    }
```

`mode 254` → `SendSetSpeedRequest(0)`。**这是 ECU 侧少数主动拉闸路径之一。**

**局限：** 若 RC **仍以 20ms 发油门**（含 STALL 后用户持续推杆），永不进入 254，Scenario A 持续。

---

### Scenario E：ECU 二次合成 `running_error` — **语义污染**

```231:237:JetWaveECU-master-2d55e90f4a54d61a83efab89195082109acd3472/App/src/esc_manager.c
  if (escSpeedInfo->speed <= -20000) {
      runningErrorState |= (uint16_t)(0x0001 << 1);  // 置位堵转故障码
  }
  escSpeedInfo->runningError = runningErrorState;
```

- ECU 用 **本地静态** `runningErrorState` **覆盖** ESC 上报的 `running_error`（先 `UpdateEscSpeedInfo` 拷贝，再 `UpdateESCErrorCodeByRunningStatus` 整体赋值）  
- `speed <= -20000`（0.1rpm 单位 → -2000rpm）可能来自 **符号/溢出/未初始化**  
- 置位的 bit1 与 **MAVLink STALL `0x20000` 同位** → 可能 **无 ESC 真失速也报 STALL**  

**单片机工程师关注点：** `runningErrorState` 无 ESC 上报清除同步策略，过温/过流有滞回清除，**堵转 bit1 无清除逻辑** → 一旦置位可能 **锁死上报** 直至重启。

---

### Scenario F：OTA / 升级挂起控制任务

```524:529:JetWaveECU-master-2d55e90f4a54d61a83efab89195082109acd3472/App/src/esc_manager.c
          case MAVLINK_MSG_ID_StartUpgradeNotification:
            StartEscFirmwareUpgrade();
            MotorSpeedControlTaskSuspend();
          case MAVLINK_MSG_ID_ExitUpgrade:
            MotorSpeedControlTaskResume();
```

升级 ESC 期间 **不再发转速**。若升级异常未收到 `ExitUpgrade`，表现为 **长期无法控制**（与失速不同，需查 OTA 状态机）。

---

### Scenario G：监控面滞后 — **加剧「不可控」焦虑**

`EventCodeProcessByBit`：每个故障 bit 上报间隔 `FAULT_DURATION_CYCLES = 10000/50 = 200`  tick × 50ms = **10s**（注释写 12s 含状态机切换）。

- 复合故障（STALL + COMM + OV）逐个 bit 扫描上报  
- 用户可能在 **保护已触发数秒後** 才看到弹窗  
- **心跳 500ms** 才更新 `motor_speed` 显示 → UI 与 20ms 控制严重不同步  

---

## 五、通信子系统专项（底层驱动 / C 工程师视角）

### 5.1 TWAI / UAVCAN

| 项目 | 实现 | 风险 |
|------|------|------|
| TX | `canardRequestWithMutex`，rpm=0 时 **最高优先级** | 正常 |
| RX 降频 | 每 **10** 次 `SetSpeedResponse` 入队 1 次 | 通信故障检测 **欠采样** |
| Bus-Off | `twai_initiate_recovery()` | 恢复期间 ESC 数据空洞 → 触发 Scenario B |
| 队列深度 | `EscSpeedResponseDataQueue` 深度 **3** | 突发时 `calloc` + `xQueueSend` 失败丢帧 |

### 5.2 MAVLink / BLE

- `SendMavlinkToBleRC`：回调未注册则 **静默失败**（`ESP_FAIL`）  
- RC 连接状态：`GetRcConnectionStatus()` → BLE/NearLink 回调，**与油门是否到达无关**  
- 连接时注入 `mode=0xFE`（有连接、油门 0），**会** `SendSetSpeedRequest(0)`（走 254 分支需先收到 ThrottleAndGear；0xFE 不在 switch 的 BEGINNER/INTERMEDIATE/ADVANCED，走 **default** → **也不发转速**）

**补充：** `mode=0xFE` 落在 `default`，既不加速也不显式清零 → 依赖后续 RC 发正常 mode。

### 5.3 故障掩码

```41:42:JetWaveECU-master-2d55e90f4a54d61a83efab89195082109acd3472/App/src/ecu_manager.c
#define ESC_FAULT_MASK  (... & ~ESC_FAULT_PWM_SIGNAL_LOST & ~ESC_FAULT_THROTTLE_SATURATION ...)
```

- **STALL 不在掩码内** → 必上报 RC  
- 掩码掉 **PWM 丢失 / 油门饱和** → 现场若伴随此类故障，**App 看不到**，但 ESC 侧可能仍影响输出  

---

## 六、控制律与逻辑（C++/控制工程师视角）

### 6.1 油门 → 转速映射

- 三模式 × 16 档表 `maxSpeedForModes[3][16]`，含 14000rpm 上限档  
- `CurrentMaxSpeed` 斜坡：加速 ≤20rpm/20ms，减速 ≤40rpm/20ms → 全油门到满速约 **14s 级**（视档位）  
- 倒挡 `gear==15`：负转速，**无 STALL 特殊处理**  

### 6.2 ALC 俯仰自稳（gear14 初学）

- PID 修正叠加 `MotorSpeed`，限幅 ±3000（0.1rpm 单位下极大）  
- 失速/通信异常时 **不冻结 ALC** → 可能在保护态仍叠加修正，加剧 ESC 负担（待台架验证）  

### 6.3 空转保护（半成品）

- 检测：`speed > 阈值 && bus_current < 动态阈值 && GPS vel<=2`  
- 置位 bit9 代码 **注释掉**，清除逻辑仍在  
- 与 STALL 无关  

---

## 七、安全风险矩阵（FMEA 简表）

| ID | 场景 | 起因 | 现有防护 | 缺口 | ASIL 类比 |
|----|------|------|----------|------|-----------|
| R1 | STALL 后推油门无效 | ESC 保护 | 无 ECU 限扭 | **无恢复策略** | 高 |
| R2 | 断链未零速 | mode 255 | 无 | **未 SendSetSpeed(0)** | 高 |
| R3 | 假 ECU 通信故障 | 队列/降频 | 无 | 误导用户/售后 | 中 |
| R4 | running_error 合成污染 | ECU 软件 | 无 | 误报 STALL | 中 |
| R5 | 升级挂起 | OTA | Suspend 任务 | 无看门狗恢复 | 中 |
| R6 | Bus-Off | EMC/线缆 | TWAI 恢复 | 恢复窗口无安全态 | 中 |
| R7 | 心跳 500ms 误导 | UI | 无 | 用户以为「还有响应」 | 低 |

---

## 八、与现场数据闭环

| 现场现象 | 本报告场景 | 证据会话 |
|----------|------------|----------|
| `throttle>0 motorSpeed=0` + esc=131072 | Scenario A | 荷兰 15349/15353；history2 多条 |
| ecu=1073741824 与 stall 同会话 | Scenario A+B | CODE_REVIEW：STALL+ECU 很少伴 OV |
| 阿姆斯特丹无 STALL | — | 非区域逻辑，支持「负载/设备」主导 |
| roll≈180° | 无关动力失控主因 | 姿态展示问题，见计划阶段 4 |

**判定树（售后/研发用）：**

```
推油门无动力？
├─ escEvent & 0x20000 → Scenario A（查 ESC 固件+负载+ECU是否仍发转速）
├─ ecuEvent & 0x40000000 且无 STALL → Scenario B（查 CAN 线/降频/队列）
├─ RC 显示未连接 / mode255 → Scenario C（查断链零速补丁）
├─ 升级中 → Scenario D
└─ 以上皆否 → 查 BMS 限功率、授权过期、档位 mode 异常
```

---

## 九、修改建议与方案优化（按优先级）

### 9.1 P0 — 安全关键（建议下一版 ECU 必做）

#### M1：STALL 统一安全态（`esc_manager.c`）

```c
// 建议在 SendSetSpeedRequest 之前：
#define ESC_RUN_ERR_STALL_BIT  ((uint16_t)(1u << 1))
#define ESC_EVT_STALL          (0x00020000u)

bool escStallActive = (LastEscSpeedData.runningError & ESC_RUN_ERR_STALL_BIT) != 0
    || ((LastEscSpeedData.runningError << 16 | LastEscSpeedData.selfcheckError) & ESC_EVT_STALL) != 0;

if (escStallActive) {
    MotorSpeed = 0;
    SendSetSpeedRequest(0);
    // 可选：要求 ThrottleAndGear.throttle < 死区 持续 2s 才允许退出 STALL 态
    continue; // 或 break 跳过 ALC 叠加
}
```

**原则：** **故障态扭矩权威在 ECU**，不能假设 ESC 自行拒绝后 ECU 仍持续请求。

#### M2：mode 255/断链必须零速

```c
case 255:
    ThrottleAndGear.throttle = 0;
    MotorSpeed = 0;
    SendSetSpeedRequest(0);  // 必须
    break;
```

**原则：** **失联即安全（fail-safe）**。

#### M3：`FAULT_ESC_COMMUNICATION` 判定加固（`ecu_manager.c`）

- 条件改为：**AND** 最近 `SendSetSpeedRequest` 成功/有 TX 回包 **AND** 队列超时  
- 或：取消 RX **10:1 降频**，至少 **running_error≠0 或 speed 变化** 时全帧入队  
- 通信故障置位时 **强制** `SendSetSpeedRequest(0)` 与 Scenario A 策略一致  

#### M4：修复 `running_error` 合成（`UpdateESCErrorCodeByRunningStatus`）

- **OR** 合并 ESC 原生 `running_error`，勿整体覆盖  
- bit1（STALL）**仅信任 ESC 上报** 或增加 **滞回清除**；删除 `speed<=-20000` 除非经 ESC 手册确认  

#### M5：启用空转保护清零（已写未生效）

取消注释 `MotorSpeed = 0` 或统一到 M1 框架。

---

### 9.2 P1 — 系统架构（1～2 个迭代）

| 编号 | 方案 | 说明 |
|------|------|------|
| A1 | **SafetySupervisor 任务** | 订阅 ESC 故障 + ECU 通信 + RC 连接，输出 `torque_enable` 布尔，唯一写 CAN 转速 |
| A2 | **显式恢复状态机** | `NORMAL → LIMP → FAULT_LATCHED → RECOVERY_PENDING → NORMAL` |
| A3 | **MAVLink `ClearEscFault` / `EscRecoveryRequest`** | App 引导用户松油门后点「恢复」，ECU 发 ESC 私有清除或 RestartNode |
| A4 | **Ulog 强制字段** | 打开 `UlogWriteRcMessage`（当前注释），对齐 OSS 分析 |
| A5 | **故障上报改为事件驱动** | 故障边沿立即 `SendEventToBleRc`，去掉 12s/bit 扫描（保留去抖即可） |

---

### 9.3 P2 — App / 云端 / 测试

| 项 | 说明 |
|----|------|
| App | `escEvent`/`ecuEvent` 改 **位运算**；STALL 时 UI **灰显油门** 或提示「请松开油门 3 秒」 |
| 云端 | 规则：`throttle>0 && motorSpeed==0 && stall` 连续 N 秒 → P0 工单 |
| 台架 | 堵转、CAN 拔线、BLE 断链、STALL 后持续推油门 四用例 |
| 回归 SN | `231AAAA5C001688`、`5G002872`、`6A000193` |

---

### 9.4 建议目标架构（架构师）

```
                    ┌─────────────────────┐
                    │  SafetySupervisor   │
                    │  torque_enable      │
                    │  fault_latched      │
                    └──────────┬──────────┘
           ┌───────────────────┼───────────────────┐
           ▼                   ▼                   ▼
   MotorSpeedControl      EscCommWatchdog      RcLinkWatchdog
   (desired_rpm)          (RX timeout)         (mode 254/255)
           │                   │                   │
           └───────────────────┴───────────────────┘
                               ▼
                    SendSetSpeedRequest(actual_rpm)
                               ▼
                          ESC Hardware
```

**设计准则：**

1. **单点写 CAN 转速**  
2. **任何故障进入 LIMP 默认零扭**  
3. **恢复需显式条件**（时间 + 油门 + ESC 应答）  
4. **监控面不得是唯一安全机制**  

---

## 十、验证计划（对应分析计划阶段 2、6）

| 用例 ID | 操作 | 期望（修补后） | 关联代码 |
|---------|------|----------------|----------|
| T1 | 台架堵转到 STALL | 零速指令、esc=0x20000、油门无效直至恢复流程 | M1 |
| T2 | STALL 后持续推油门 30s | ECU 不发非零 SetSpeed；日志无「油门>0 转速>0」 | M1 |
| T3 | 运行中拔 BLE | 200ms 内 CAN 零速 | M2 |
| T4 | 运行中拔 CAN 12s | 可报 COMM，且零速 | M3 |
| T5 | 注入 speed=-30000 | 不应误报 STALL（修补后） | M4 |
| T6 | 复现 15349 OSS 时间轴 | Ulog RC/ESC 与轨迹对齐 | A4 |

---

## 十一、结论（给决策层的一句话）

**「主控失速 + 断联 + 遥控器无法控制」并非单一 bug，而是：ESC 真失速保护（主因）+ ECU 未在故障态切断/限制扭矩（安全设计缺口）+ ECU 用 ESC 数据超时冒充「主控断联」（语义误导）+ 断链未发零速（潜在 fail-unsafe）共同构成的系统性问题。**

遥控器多数情况下 **仍在正常发油门**；用户「无法控制」是因为 **扭矩输出层（ESC+ECU策略）未恢复且 ECU 未执行 fail-safe**，而不是 RC 完全失联。

**研发优先级：** 先落地 **M1+M2+M3**（可在 ECU 单仓验证），并行抓 ESC 固件 STALL 恢复条件；App 与云端做体验与数据闭环。

---

## 附录 A：关键源码索引

| 主题 | 文件 | 行号（约） |
|------|------|------------|
| 油门控制任务 | `App/src/esc_manager.c` | 298～457 |
| STALL 未处理 | 同上 | 425～430 |
| mode 255 无零速 | 同上 | 439～443 |
| ESC 通信故障 | `App/src/ecu_manager.c` | 534～556 |
| esc_event 打包 | 同上 | 107～108 |
| 故障掩码 | 同上 | 41～42 |
| 事件上报状态机 | 同上 | 178～337 |
| UAVCAN 降频 | `Protocol/src/uavcan_twai.c` | 58～62 |
| BLE 断链 mode | `Ble/src/ble_server.c` | 292～306 |
| STALL 协议定义 | `Lib/JetWaveMavlink/xml/JetWave.xml` | 296～298 |
| SetSpeed 响应字段 | `Lib/JetWaveDsdl/esc/212.SetSpeed.uavcan` | 15～16 |

## 附录 B：相关报告

| 文档 | 用途 |
|------|------|
| `JETWAVE_ECU_POWERTRAIN_ANALYSIS_PLAN.md` | 分阶段执行计划 |
| `NETHERLANDS_ABNORMAL_REPORT_20260526_190035.md` | 荷兰 STALL 样本 |
| `CODE_REVIEW_交互架构与问题导读.md` | App 侧故障语义 |
| `HISTORY2_DAILY_REPORT_20260526_092858.md` | 昨日全量统计 |

---

*本报告为静态代码审计 + 现场日志交叉结论；ESC 固件内部 STALL 置位/清除阈值需结合电调厂家文档与 CAN 抓包最终确认。*
