← 返回目录

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

feature_motorstuck 分支专项

文档版本: v1.4(§8.3 分层安全停板 LSS + §11.8 停板实现)
生成日期: 2026-05-26
代码基线: JetWaveECU-feature_motorstuck/(分支 feature_motorstuck)
对比基线: JetWaveECU-master-2d55e90f4a54d61a83efab89195082109acd3472(原 master 分析)
前置文档:
- JETWAVE_ECU_POWERTRAIN_ANALYSIS_PLAN.md
- JETWAVE_ECU_POWERTRAIN_DEEP_ANALYSIS_REPORT.md(master 版)


执行摘要

核心问题(与 master 相同,用户仍会遇到)

失速 + 「断联」+ 遥控器推油门无动力

在 feature_motorstuck 上,根因结构未变:ESC 真保护 / ECU 不拉闸 / 通信超时误报 / 断链不发零速。该分支 没有 实现完整的「电机卡死(motor stuck)安全状态机」,仅改了 ECU 二次故障合成的一处逻辑,且存在 故障位映射错误 风险。

本分支相对 master 的关键结论

项 master (2d55e90f) feature_motorstuck 评价
STALL 后限扭/零速 ❌ 无 ❌ 仍无 未解决主诉
BLE 断链 mode=255 零速 ❌ 无 SendSetSpeedRequest(0) ❌ 仍无 P0 未修
转速异常合成位 running_error bit1 → esc_event 0x20000 (STALL) bit6 → 0x400000 (过压) ⚠️ 语义错误
转速异常条件 speed <= -20000 \|speed\| >= 20000 略扩宽
空转保护执行 MotorSpeed=0 注释掉 bit9 已启用 MotorSpeed=0 ✅ 小幅改进
ALC 俯仰自稳 有 已删除 简化固件
NearLink 双链路 有 无(仅 ESP32 BLE) 产品形态不同
RC Ulog 注释 已打开 UlogWriteRcMessage ✅ 利于复盘
堵转 bit 清除滞回 无 仍无 误报可锁死

分支命名 feature_motorstuck 与代码效果不完全一致: 注释写「堵转」,但置位的是 running_error bit6,上报为 ESC_FAULT_OVERVOLTAGE_PROTECTION (4194304),不是 现场主分析的 ESC_FAULT_STALL_PROTECTION (131072)。若荷兰等问题会话以 131072 为主,则该分支 不能替代 ESC 固件 STALL,且可能 新增误报过压。


〇、用户感知再确认:「断联 + 收不到指令 + 自己疯狂运行」

重要前提(方法论): 失速/通信异常之后,云端轨迹与 OSS 明细有可能根本回传不全甚至中断,不能以「失速后 motorSpeed=0」等日志形态否定现场口述。
本节 仅依据固件因果链 判断:您描述的现象 在现有代码下可以成立,且恰恰是 日志最可能丢失 的那类故障。

0.1 为何不能依赖失速后的日志

链路环节 失速/异常后可能发生什么 对云端数据的影响
ESC→ECU(CAN) 电调保护或 Bus 异常 → ReceiveEscRunningDataFromQueue 长期失败 latestEscRunningData 不更新,但 不会被清零
ECU→App(BLE 心跳 500ms) 仍用 上一份 latestEscRunningData 填 motor_speed 上报 云端看到 过时转速,或 App 停记会话
ECU→App(事件) FAULT_ESC_COMMUNICATION 置位后用户感知断联 会话可能 提前结束,失速后段不上传
App→云端 断联、崩溃、弱网 OSS 缺口/截断,不能反推「当时电机已停」
板载 Ulog(SD) 依赖文件系统;通信故障期未必落盘完整 售后若未拉 SD,仍 无据可查

代码依据(心跳在收不到 ESC 新数据时仍发旧值):

// ecu_manager.c:队列失败时 latestEscRunningData 保持上次 memcpy 结果,不清零
if (ReceiveEscRunningDataFromQueue(...) == pdPASS) {
    memcpy(&latestEscRunningData, ...);
} else if (GetRcConnectionStatus()) {
    ESC_CommunicationFailure++;  // 12s 后置 FAULT_ESC_COMMUNICATION
}
// 500ms 仍执行:
UpdateEcuHeatbeatDataByDevice(latestEscRunningData, ...);  // motor_speed = 陈旧值
SendMavlinkToBleRC(&HeatbeatMsg);

因此:历史上 OSS 里「STALL 后 motorSpeed≈0」最多说明 「有上传的那几帧」 的电调反馈;不能证明 失速瞬间及之后物理电机状态,更不能排除 「断联 + 还在转」。

0.2 从固件推导:您描述的三句话 可以同时成立

现场口述:板体与遥控器断联 → 板体收不到遥控指令 → 自己疯狂运行。

在 不查日志 的前提下,按当前 feature_motorstuck 代码,这是一条 合法故障轨迹:

sequenceDiagram
    participant U as 遥控器
    participant ECU as ECU
    participant ESC as 电调/电机

    Note over U,ESC: ① 正常运行
    U->>ECU: 油门(BLE 正常)
    ECU->>ESC: SetSpeed(高) 每 20ms

    Note over U,ESC: ② 失速/堵转(ESC 固件)
    ESC->>ESC: STALL 或保护态
    ESC--xECU: CAN 反馈变少或中断

    Note over U,ESC: ③ ECU 仍按油门发 CAN(缺陷)
    U->>ECU: 油门可能仍在发
    ECU->>ESC: SetSpeed(非零) 仍每 20ms
    Note over ESC: 物理上:可能继续转/抖动/保持 — 日志不可见

    Note over U,ESC: ④ ~12s 后 ECU 判通信故障
    ECU->>U: FAULT_ESC_COMMUNICATION
    U->>U: 用户感知「断联」

    Note over U,ESC: ⑤ BLE 真断或 RF 恶化
    U--xECU: 蓝牙断
    ECU->>ECU: mode=255,MotorSpeed=0 但不发 CAN 零速
    ESC->>ESC: 若保持最后一帧转速指令
    Note over U: 「收不到指令 + 还在转」
用户感受 不依赖日志的代码解释
断联 FAULT_ESC_COMMUNICATION 弹窗/埋点;和/或 BLE 真断(ble_server → mode=255)
收不到指令 真断链:不再更新 ThrottleAndGear;假断联:收到了但 电机不跟,用户也会说「控不住」
疯狂运行 ① 失速前 ECU 持续发高转速;② 断链后 未 SendSetSpeedRequest(0),ESC 可能维持末指令;③ STALL 后 ECU 不拉闸 仍发非零 CAN — 物理结果 必须以台架/示波器为准,不能由缺失的 OSS 否定

0.3 与「仅有日志时」结论的差异(务必读)

论断 若 只看 OSS 失速后点位 若 承认日志可缺失(应采用)
失速后电机一定停转 常被统计为 motorSpeed=0 无法证明;应用台架验证
失速后 RC 一定还连着 心跳里仍有 throttle BLE 可能随后断;断在日志最后一帧之后
用户「疯狂运行」是误判 与部分轨迹矛盾 与 mode255 不零速 + 持续 SetSpeed 不矛盾,且 更危险
修复优先级 可能低估 M3(断链零速) M2+M3 同为 P0

对程序员: 分析、定责、验收 不得以「OSS 失速后 motorSpeed=0」为通过标准;必须通过 CAN 抓包 + 断 BLE + 堵转台架 验证 §十一。

0.4 仅用固件逻辑的失败模式分类(供复现)

模式 触发 遥控链路 CAN 输出 物理电机(需台架) 日志可信度
F1 ESC STALL,BLE 仍通 通 仍发非零 SetSpeed 停/抖/保持 — 不确定 失速后 低/无
F2 STALL → 12s → ECU 通信故障 可能仍通 同 F1 同 F1 通信故障后 常缺失
F3 BLE 断 mode=255 断 不发 零速指令 最像「断联还在转」 会话 常中断
F4 F1+F3 串联 先通后断 高转速 → 断链不拉闸 用户口述 最常见叙事 几乎无法靠 OSS 还原

您描述的现象与模式 F3、F4 一致性最高;模式 F1 表现为「还在发指令但像失控」——用户也会说成「疯狂」。

0.5 现场核对(不依赖云端轨迹)

  1. 问题当时 蓝牙是否已断?(设置页 + 板子指示灯)
  2. 听/看螺旋桨:断链后是否仍转 ≥1s?(验证 F3)
  3. 能否导出 板载 Ulog(SD) 或 App 本地缓存?(比 OSS 完整)
  4. 返厂 CAN 抓包:断链/失速瞬间是否仍有 非零 SetSpeed?

不应再问:「后台 motorSpeed 是不是 0」——失速后后台 可能没有有效后段。


一、问题定义(与 master 报告一致)

用户说法 日志/App 代码枚举 本分支是否处理
失速 escEventCode & 131072 ESC_FAULT_STALL_PROTECTION 0x20000 ❌ 仅透传 ESC;ECU 合成不再置位 0x20000
断联 ecuEventCode & 1073741824 FAULT_ESC_COMMUNICATION ❌ 逻辑同 master(12s 无队列数据)
无法控制 日志中或见油门>0、转速≈0(失速后日志可能不回传,不作硬依据) — ❌ STALL 未拉闸
真蓝牙断链 mode=255 ECU 私有 ❌ 仍不发 CAN 零速

位域(未改):

esc_event = ((uint32_t)runningError << 16) | selfcheckError;
// STALL 0x20000  <=> runningError bit1
// OV    0x400000 <=> runningError bit6  ← 本分支误用此位表示「堵转」

二、分支差异总览(第一性原理:改了什么、没改什么)

2.1 架构差异

flowchart LR
    subgraph FM["feature_motorstuck"]
        BLE_ONLY[仅 ESP32 BLE]
        ESC_M2[esc_manager 无 ALC]
        SYN[UpdateESC: bit6 转速异常]
    end
    subgraph MS["master 2d55e90f"]
        NL[NearLink + BLE 可选]
        ESC_M1[esc_manager + ALC PID]
        SYN2[UpdateESC: bit1 转速异常]
    end
    FM --> SAME[相同缺陷层]
    MS --> SAME
    SAME --> GAP[无 SafetySupervisor<br/>STALL 不拉闸<br/>mode255 不零速]
模块 feature_motorstuck master
main.c 仅 BleInit(),日志默认 ESP_LOG_NONE NearLink 分支 + ESP_LOG_INFO
esc_manager.c ~553 行,无 ALC/无 ReadAutoLevelControlParams ~666 行,含 ALC
ecu_manager.c 无 NearLink 回调注册 有 SLE/BLE 动态注册
uavcan_twai.c 10:1 降频入队 相同
JetWave.xml 故障枚举 与 master 一致 一致

2.2 本分支唯一与「motorstuck」相关的代码变更

```225:231:JetWaveECU-feature_motorstuck/App/src/esc_manager.c // 转速异常 if (escSpeedInfo->speed <= -20000 || escSpeedInfo->speed >= 20000) { runningErrorState |= (uint16_t)(0x0001 << 6); // 置位堵转故障码 }

// 更新 ESC 的运行错误码 escSpeedInfo->runningError = runningErrorState;


**master 对应逻辑:**

```c
if (escSpeedInfo->speed <= -20000) {
    runningErrorState |= (uint16_t)(0x0001 << 1);  // 注释为堵转,实际映射到 STALL 位
}

2.3 故障位映射分析(本报告最重要的新增发现)

running_error 位 左移 16 位后 esc_event JetWave.xml 名称 现场/App 常用值
bit 1 (0x0002) 0x00020000 ESC_FAULT_STALL_PROTECTION 131072
bit 6 (0x0040) 0x00400000 ESC_FAULT_OVERVOLTAGE_PROTECTION 4194304

因此:

单片机/底层视角: runningErrorState 为静态变量,整体覆盖 escSpeedInfo->runningError,且 bit6 无滞回清除 → 一旦触发可能 永久 OR 在状态字,直到重启。


三、根因场景(feature_motorstuck 分支逐项复核)

Scenario A:ESC 真失速(STALL 131072)— 仍为第一主因

Scenario B:FAULT_ESC_COMMUNICATION 假断联 — 未改

```488:497:JetWaveECU-feature_motorstuck/App/src/ecu_manager.c } else if (GetRcConnectionStatus()) { ESC_CommunicationFailure ++; if (ESC_CommunicationFailure >= 240) { // 12S ecuEventCode |= FAULT_ESC_COMMUNICATION; } } else if (!GetRcConnectionStatus()) { ESC_CommunicationFailure = 0; ecuEventCode &= ~FAULT_ESC_COMMUNICATION;


叠加因素仍为:UAVCAN **10:1 入队**、`ProcessEscDataTask` 100ms、STALL 后反馈稀疏。

**额外注意(本分支):** BMS 通信故障判断使用 `GetRcConnectionStatus()`(510 行),与 master 的 `GetBmsConnectionStatus()` **不同** → 可能 **误用 RC 连接状态判断 BMS**,属 **回归风险**,需单独评审。

### Scenario C:BLE 断链 mode=255 — **未修,P0**

```372:376:JetWaveECU-feature_motorstuck/App/src/esc_manager.c
      case 255:  // 蓝牙断链
        ESP_LOGD(LOG_TAG, "NOBLUETOOTH");
        ThrottleAndGear.throttle = 0;
        MotorSpeed = 0;
        break;   // 仍无 SendSetSpeedRequest(0)

Scenario D:油门丢失 1s → mode 254 — 仍有效

```365:370:JetWaveECU-feature_motorstuck/App/src/esc_manager.c case 254: ... SendSetSpeedRequest(MotorSpeed); // MotorSpeed=0


### Scenario E:本分支「转速异常 → bit6」— **新路径,可能误导**

| 条件 | 后果 |
|------|------|
| `\|speed\| >= 20000`(0.1rpm 即 2000rpm) | 置 `running_error` bit6 → 上报 **过压** |
| 高速工况(进阶档满速 15000=1500rpm) | 通常不触发;若 ESC 反馈溢出/符号错误 | **误报过压** |
| 触发后 | **无清除逻辑**;**不限扭**;与 STALL 131072 **并存时** App 若用 `==` 判断可能只显示一种 |

### Scenario F:空转保护 bit9 — **本分支改进点**

```358:363:JetWaveECU-feature_motorstuck/App/src/esc_manager.c
        if (LastEscSpeedData.runningError & (uint16_t)(0x0001 << 9)) {
          MotorSpeed = 0;
          ESP_LOGD(LOG_TAG, "Idle protection in progress, speed forced to 0");
        }
        SendSetSpeedRequest(MotorSpeed);

Scenario G:OTA 挂起控制任务 — 同 master

StartUpgradeNotification → MotorSpeedControlTaskSuspend(),表现仍为「无法控制」。


四、通信与子系统(与 master 相同部分摘要)

项目 状态 说明
SendSetSpeedRequest 每 20ms(正常模式) rpm=0 时 CAN 最高优先级
ESC 反馈 10 帧入队 1 次 通信故障检测欠采样
TWAI Bus-Off 自动 recovery 恢复窗口无强制零速
心跳 500ms motor_speed 滞后于控制环
事件上报 EventCodeProcessByBit ~12s/bit 复合故障感知慢
Ulog RC 本分支已写盘 利于对齐 OSS「有油门无电机」

五、控制逻辑(C/控制工程师视角)

5.1 模式与转速表

```66:71:JetWaveECU-feature_motorstuck/App/src/esc_manager.c static float maxSpeedForModes[3][16] = { {7600.0, ... 11200.0, 10000.0}, // BEGINNER {11400.0, ... 15000.0, 10000.0}, // 注释 SPORT,枚举仍为 INTERMEDIATE {15000.0, ... 15000.0, 10000.0} // ADVANCED };


- 无 ALC:高阶模式 **满速更激进**,STALL 触发概率可能 **高于** master(需台架对比)。  
- 仍含 **20/40 rpm per 20ms** 斜坡、`LowPassFilter` 油门。

### 5.2 安全相关缺失(两分支共性)

1. 无 **STALL / 通信故障 / 断链** 统一 `torque_enable`  
2. 无 **松油门恢复** 状态机  
3. 无 **MAVLink 清除故障** 指令  

---

## 六、FMEA(feature_motorstuck 增量)

| ID | 风险 | 本分支变化 | 严重度 |
|----|------|------------|--------|
| R0 | 堵转改 bit6 | 上报 **过压** 而非 STALL | **高**(误导排查) |
| R1 | STALL 后不拉闸 | 无变化 | 高 |
| R2 | mode255 不零速 | 无变化 | 高 |
| R3 | bit6 无清除 | 新增锁死风险 | 中 |
| R4 | BMS 通信用 RC 状态 | 新增/回归 | 中 |
| R5 | 去掉 ALC | 高阶模式操控不同 | 低~中 |

---

## 七、与现场数据的关系

| 现场现象 | feature_motorstuck 解释 |
|----------|-------------------------|
| 荷兰 STALL **131072** | 仍主要来自 **ESC 固件**;本分支 ECU 合成 **不再** 打 0x20000 |
| 会话「STALL+ECU 通信」 | Scenario A+B 叠加,逻辑未改 |
| 若升级本分支后出现 **4194304** 增多 | 怀疑 **bit6 转速异常误报过压** |
| `throttle>0, motorSpeed=0` | 仍无 ECU 侧强制零速(除 bit9 空转) |

**判定树(在本分支固件上):**

推油门无动力? ├─ esc & 131072 → ESC 真 STALL(与本分支 bit6 无关)→ 查 ESC + 仍缺 ECU 拉闸 ├─ esc & 4194304 且近期升级 feature_motorstuck → 先查 speed 是否 |speed|>=20000 误触 bit6 ├─ ecu & 0x40000000 → ECU↔ESC 12s 无数据(同 master) ├─ mode 255 → 断链未 SendSetSpeed(0)(同 master) └─ 仅 bit9 空转 → 本分支会强制 MotorSpeed=0(小幅缓解)


---

## 八、修改建议(针对 feature_motorstuck 的评审意见)

### 8.1 优先级总表

| ID | 优先级 | 项 | 主要文件 |
|----|--------|-----|----------|
| M1 | **P0** | 修正转速异常 → **bit1(STALL)** + 滞回清除 | `App/src/esc_manager.c` |
| M2 | **P0** | 扭矩安全门:STALL/通信/断链 → 强制零速 | `App/src/esc_manager.c`(可选新建 `App/src/powertrain_safety.c`) |
| M3 | **P0** | `mode=255` 断链发 `SendSetSpeedRequest(0)` | `App/src/esc_manager.c` |
| M4 | **P0** | `running_error` 与 ESC 原生字段 **OR 合并** | `App/src/esc_manager.c` |
| M5 | **P1** | BMS 通信判断改回 `GetBmsConnectionStatus()` | `App/src/ecu_manager.c` |
| M6 | **P1** | ESC 通信故障:降频/置位时联动零速 | `Protocol/src/uavcan_twai.c`、`App/src/ecu_manager.c` |
| M7 | **P1** | 安全状态机 + 恢复条件(松油门) | `App/inc/powertrain_safety.h`、`App/src/powertrain_safety.c` |
| M8 | **P0** | 零速看门狗 50ms(LSS L3) | `App/src/powertrain_safety.c` |
| M9 | **P0** | ESC 反馈丢失 500ms 停板(与 12s 报障分离) | `ecu_manager.c` + `powertrain_safety.c` |
| M10 | P2 | LIMP 超 3s 可选 `SendEscRebootRequest` | `powertrain_safety.c` |

### 8.2 不建议合并到量产前的状态

当前 `feature_motorstuck` **不适合**作为「失速/卡死修复」量产分支,除非 **M1~M4 + M8 全部合入** 且台架通过 §九 用例。

### 8.3 安全停板总体方案(断联 / 失速 / 不可控故障)— **推荐采用**

> **目标:** 一旦 **与遥控器断联**,或出现 **不可控类故障(含失速 STALL)**,在 **不依赖云端日志、不依赖用户松手** 的前提下,**尽快、持续、可验证** 地让板体停止输出(CAN 上为零转速指令;物理停转由 ESC 执行)。  
> **原则:** 失联 / 失控 → **默认零扭(fail-safe)**;恢复必须 **显式条件**,不能靠「再发一次大油门」碰运气。

#### 8.3.1 推荐方案名:**分层安全停板(Layered Safe Stop, LSS)**

在现有架构上增加 **「安全监督层」**,与 `MotorSpeedControlTask`(20ms 控制)并行,**专责停板**,避免「控制任务还在发非零转速」或「断链未发 CAN 零速」。

```mermaid
flowchart TB
    subgraph TRIG["停板触发(任一满足即 LIMP)"]
        T1[RC 蓝牙断链 mode=255]
        T2[RC 连接但 1s 无油门 → mode254]
        T3[ESC STALL 0x20000]
        T4[ESC 严重故障 OV/OC/OT 等]
        T5[ESC 反馈丢失 ≥500ms 且此前在驱动]
        T6[ECU FAULT_ESC_COMM 置位]
    end
    subgraph LSS["安全监督层 LIMP"]
        Z1[safe_rpm = 0]
        Z2[每 20ms SetSpeed 0 最高优先级]
        Z3[独立看门狗 50ms 重复发 0]
    end
    subgraph REC["恢复 RECOVERY_WAIT"]
        R1[油门≤死区 持续 2s]
        R2[ESC 无 STALL 位]
        R3[ESC 通信恢复]
    end
    TRIG --> LSS
    LSS --> REC
    REC -->|满足| NORMAL[正常控制]

8.3.2 停板触发条件(建议固件统一配置)

触发类 检测方式 建议动作延迟 说明
A. 遥控断联 GetRcConnectionStatus()==false 或 mode==255 立即(≤20ms) ble_server 断开时同步调用 PowertrainSafetyOnRcLinkLost()
B. 遥控失联等效 1s 无 ThrottleAndGear(现有 mode254) 1s 后发零速 已有 SendSetSpeed(0),保留
C. 失速 STALL esc_event & 0x20000 或 running_error bit1 立即 不等 12s 通信故障
D. 不可控/严重 ESC 故障 esc_event 掩码:STALL、过流 0x10000、过温 0x40000 等(可配置表) 立即 与 App 弹窗掩码对齐
E. ESC 反馈中断 连续 500ms(建议 25×20ms)未收到 EscRunningData 且上一周期 safe_rpm≠0 500ms 发零速 不要等 12s 才停;12s 仅用于 上报 FAULT_ESC_COMMUNICATION
F. 通信故障已置位 FAULT_ESC_COMMUNICATION 保持 LIMP 上报与停板解耦

关键设计: 停板(E)要快于报障(F 的 12s)。当前固件 12s 才置通信故障,但 停板应在亚秒级。

8.3.3 停板执行层(三层,缺一不可)

层级 做什么 解决什么问题
L1 控制门 MotorSpeedControlTask 出口统一 ApplySpeedWithSafety(),LIMP 下 只发 0 失速后仍发非零 CAN
L2 断链拉闸 mode=255 必须 SendSetSpeedRequest(0);ble_server 断开回调 再发一次 0 断联后 ESC 保持末指令 →「疯狂运行」
L3 零速看门狗 独立任务 SafeStopWatchdogTask 50ms,在 LIMP 或 !GetRcConnectionStatus() 时 周期性 SendSetSpeedRequest(0) OTA 挂起 MotorSpeedControl、任务异常时仍能拉闸

为何需要 L3: StartUpgradeNotification 会 vTaskSuspend(MotorSpeedControlTask),若仅依赖控制任务发零速,升级/异常挂起时板体可能不停。

8.3.4 方案对比(为何不用「单点修补」)

方案 做法 断联停板 失速停板 任务挂起时 推荐
S0 现状 无统一策略 ❌ mode255 不发 CAN ❌ 仍发非零 ❌ 否
S1 仅修 mode255 断链加一行 SendSetSpeed(0) ⚠️ 仅一次 ❌ ❌ 不足
S2 仅 STALL 限扭 if (stall) MotorSpeed=0 ❌ ⚠️ 仅控制任务内 ❌ 不足
S3 等 12s 通信故障再停 依赖 FAULT_ESC_COMM ❌ 太晚 ❌ 太晚 ❌ 否
S4 LSS(推荐) L1+L2+L3 + 快触发 E + 状态机恢复 ✅ ✅ ✅ 是
S5 LSS + ESC 重启 L4 下 LIMP 3s 仍无反馈则 SendEscRebootRequest() ✅ ✅ ✅ 可选增强

8.3.5 恢复策略(停板之后如何「能再次开」)

条件 必须同时满足 原因
进入 RECOVERY_WAIT LIMP 下油门 ≤ 死区 用户明确卸负载
回到 NORMAL 松油门 ≥2s + ESC 无 STALL + 收到 ESC 反馈 避免顶着油门再次堵转;通信未恢复不加扭
禁止 仅 App 点「确定」即恢复 与现场「不可控」体验不符

断联恢复:须 BLE 重新连接(mode 从 255→254/正常档)+ 上述条件。

8.3.6 与 App / 现场的边界

责任方 停板相关职责
ECU 固件(必须) LSS 三层停板;不依赖日志回传
ESC 固件(应确认) 收到 SetSpeed(0) 后多少 ms 内卸扭;断 CAN 后是否超时自停
App(建议) 断联/STALL 弹窗;不在 LIMP 期间向用户暗示「仍可满油门」;可选「长按 3s 强制恢复」仅发 查询/复位 指令,不直接绕过 ECU 安全层
验收 CAN 抓包 + 断 BLE 台架;不以 OSS 为准

8.4 修改项补充(停板专项)

ID 优先级 项 文件
M8 P0 SafeStopWatchdogTask 50ms 周期发零速 App/src/powertrain_safety.c
M9 P0 ESC 反馈丢失 500ms 进 LIMP(与 12s 报障分离) App/src/ecu_manager.c + powertrain_safety.c
M10 P2 LIMP 持续 3s 且无 ESC 应答 → SendEscRebootRequest() powertrain_safety.c

十一、程序员实施指南(伪代码 / 文件 / 修改后流程)

本节供固件工程师直接对照改代码。伪代码与现有 FreeRTOS / MAVLink 风格一致,合入时需保留现有命名与头文件包含关系。

11.1 涉及文件与改动范围

文件 操作 改动摘要
App/inc/powertrain_safety.h 新建 安全状态枚举、阈值宏、对外 API
App/src/powertrain_safety.c 新建 状态机、IsTorqueAllowed()、OnEscFeedback()
App/CMakeLists.txt 修改 将 powertrain_safety.c 加入编译
App/src/esc_manager.c 修改 M1/M2/M3/M4;MotorSpeedControlTask 调用安全层
App/src/ecu_manager.c 修改 M5/M6;通信故障时通知安全层
Protocol/src/uavcan_twai.c 修改 M6;故障帧或每帧入队策略
Ble/src/ble_server.c 可选 断链时已发 mode=255,与安全层重复校验无妨

不建议改: JetWave.xml(沿用 ESC_FAULT_STALL_PROTECTION=0x20000)、SendSetSpeedRequest 签名。


11.2 新建头文件 App/inc/powertrain_safety.h(伪代码)

#ifndef POWERTRAIN_SAFETY_H
#define POWERTRAIN_SAFETY_H

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

/* 与 JetWave.xml / 现场日志一致 */
#define ESC_RUN_ERR_BIT_STALL       ((uint16_t)(1u << 1))   /* -> esc_event 0x20000 */
#define ESC_EVT_MASK_STALL          (0x00020000u)
#define ECU_EVT_MASK_ESC_COMM       (0x40000000u)

#define THROTTLE_DEADBAND           5       /* 与 esc_manager 死区一致 */
#define STALL_RECOVERY_HOLD_MS      2000    /* 松油门持续时间 */
#define SPEED_ANOMALY_LOW           (-20000)
#define SPEED_ANOMALY_HIGH          (20000) /* 仅用于异常检测,非满速工况 */

typedef enum {
    PWR_SAFE_NORMAL = 0,
    PWR_SAFE_LIMP,           /* 强制零扭:STALL / 通信丢失 / 断链 */
    PWR_SAFE_RECOVERY_WAIT,  /* 等待用户松油门 + ESC 故障清除 */
} PowertrainSafetyState_t;

void PowertrainSafetyInit(void);

/* 每 20ms 在 MotorSpeedControlTask 调用 */
bool PowertrainSafetyFilterSpeed(int32_t requested_rpm, uint16_t throttle,
                                 uint8_t throttle_mode, int32_t *out_rpm);

/* 每 100ms 在 ProcessEscDataTask 收到 ESC 反馈后调用 */
void PowertrainSafetyOnEscFeedback(uint16_t running_error, uint16_t selfcheck_error,
                                   int32_t esc_speed);

/* ecu_manager 置位 FAULT_ESC_COMMUNICATION 时调用 */
void PowertrainSafetyOnEscCommFault(bool active);

/* ble 断链 / mode255 时调用 */
void PowertrainSafetyOnRcLinkLost(void);

PowertrainSafetyState_t PowertrainSafetyGetState(void);

#endif

11.3 M1:修正 UpdateESCErrorCodeByRunningStatus(esc_manager.c)

替换当前 225~231 行(bit6 无清除)为:

/* ----- 文件: App/src/esc_manager.c ----- */
/* ----- 函数: UpdateESCErrorCodeByRunningStatus ----- */

#define FAULT_STALL_SETTLE_CYCLES   (500 / 200)   /* 500ms 置位,ProcessEscDataTask ~200ms/次 */
#define FAULT_STALL_CLEAR_CYCLES    (3000 / 200)  /* 3s 清除滞回 */

static void UpdateESCErrorCodeByRunningStatus(EscSetSpeedResponseData_t *escSpeedInfo) {
    static uint32_t faultStallSettleCycle = 0;
    static uint32_t faultStallClearCycle = 0;
    static uint16_t runningErrorState = 0;
    static uint16_t escNativeRunningError = 0;

    /* ... 保留现有过温 bit8、过流 bit7、空转 bit9 逻辑 ... */

    /* [M1] 转速异常 -> bit1(STALL),禁止 bit6(过压) */
    bool speed_anomaly = (escSpeedInfo->speed <= SPEED_ANOMALY_LOW)
                      || (escSpeedInfo->speed >= SPEED_ANOMALY_HIGH);

    if (speed_anomaly) {
        faultStallSettleCycle++;
        faultStallClearCycle = 0;
        if (faultStallSettleCycle >= FAULT_STALL_SETTLE_CYCLES) {
            runningErrorState |= ESC_RUN_ERR_BIT_STALL;  /* bit1 -> 0x20000 */
        }
    } else {
        if (faultStallSettleCycle > 0) {
            faultStallSettleCycle--;
        }
        if (runningErrorState & ESC_RUN_ERR_BIT_STALL) {
            faultStallClearCycle++;
            if (faultStallClearCycle >= FAULT_STALL_CLEAR_CYCLES) {
                runningErrorState &= ~ESC_RUN_ERR_BIT_STALL;
                faultStallClearCycle = 0;
            }
        } else {
            faultStallClearCycle = 0;
        }
    }

    /* [M4] 保留 ESC 固件上报位,再叠加 ECU 合成位 */
    escNativeRunningError = escSpeedInfo->runningError;
    escSpeedInfo->runningError = escNativeRunningError | runningErrorState;
}

正当性:


11.4 M2 + M7:安全层 powertrain_safety.c(核心伪代码)

/* ----- 文件: App/src/powertrain_safety.c ----- */

static PowertrainSafetyState_t s_state = PWR_SAFE_NORMAL;
static uint32_t s_throttle_low_ms = 0;
static bool s_esc_comm_fault = false;

static bool EscReportsStall(uint16_t running_error, uint16_t selfcheck_error) {
    uint32_t esc_event = ((uint32_t)running_error << 16) | selfcheck_error;
    return (esc_event & ESC_EVT_MASK_STALL) != 0
        || (running_error & ESC_RUN_ERR_BIT_STALL) != 0;
}

void PowertrainSafetyOnEscFeedback(uint16_t running_error, uint16_t selfcheck_error,
                                 int32_t esc_speed) {
    (void)esc_speed;
    if (s_state == PWR_SAFE_NORMAL && EscReportsStall(running_error, selfcheck_error)) {
        s_state = PWR_SAFE_LIMP;
        ESP_LOGW("PWR_SAFE", "Enter LIMP: STALL detected");
    }
    if (s_state == PWR_SAFE_RECOVERY_WAIT && !EscReportsStall(running_error, selfcheck_error)) {
        /* ESC 已清除,仍须等松油门计时 */
    }
}

void PowertrainSafetyOnEscCommFault(bool active) {
    s_esc_comm_fault = active;
    if (active && s_state == PWR_SAFE_NORMAL) {
        s_state = PWR_SAFE_LIMP;
        ESP_LOGW("PWR_SAFE", "Enter LIMP: ESC comm fault");
    }
}

void PowertrainSafetyOnRcLinkLost(void) {
    s_state = PWR_SAFE_LIMP;
    ESP_LOGW("PWR_SAFE", "Enter LIMP: RC link lost");
}

bool PowertrainSafetyFilterSpeed(int32_t requested_rpm, uint16_t throttle,
                                 uint8_t throttle_mode, int32_t *out_rpm) {
    /* [M3] 断链:mode 255 与 ble 回调双路径均会进 LIMP */
    if (throttle_mode == 255) {
        s_state = PWR_SAFE_LIMP;
    }

    switch (s_state) {
    case PWR_SAFE_NORMAL:
        *out_rpm = requested_rpm;
        return true;

    case PWR_SAFE_LIMP:
        *out_rpm = 0;
        if (throttle <= THROTTLE_DEADBAND && !s_esc_comm_fault) {
            s_state = PWR_SAFE_RECOVERY_WAIT;
            s_throttle_low_ms = 0;
        }
        return false;  /* 表示已限扭 */

    case PWR_SAFE_RECOVERY_WAIT:
        *out_rpm = 0;
        if (throttle > THROTTLE_DEADBAND) {
            s_throttle_low_ms = 0;  /* 用户未松油门,不允许恢复 */
        } else {
            s_throttle_low_ms += 20;  /* 调用周期 20ms */
            if (s_throttle_low_ms >= STALL_RECOVERY_HOLD_MS) {
                s_state = PWR_SAFE_NORMAL;
                ESP_LOGI("PWR_SAFE", "Exit to NORMAL after throttle release");
            }
        }
        return false;

    default:
        *out_rpm = 0;
        return false;
    }
}

在 MotorSpeedControlTask 末尾统一发 CAN(替换多处 SendSetSpeedRequest):

/* ----- 文件: App/src/esc_manager.c ----- */
/* ----- 函数: MotorSpeedControlTask,在计算出 MotorSpeed 之后 ----- */

static void ApplySpeedWithSafety(int32_t raw_motor_speed, uint16_t throttle, uint8_t mode) {
    int32_t safe_rpm = raw_motor_speed;

    PowertrainSafetyFilterSpeed(raw_motor_speed, throttle, mode, &safe_rpm);

    /* 空转 bit9:与安全层叠加,取更严格者 */
    if (LastEscSpeedData.runningError & (uint16_t)(1u << 9)) {
        safe_rpm = 0;
    }

    SendSetSpeedRequest(safe_rpm);
}

/* case BEGINNER/INTERMEDIATE/ADVANCED: */
    /* ... 计算 MotorSpeed ... */
    ApplySpeedWithSafety(MotorSpeed, ThrottleAndGear.throttle, ThrottleAndGear.mode);
    break;

/* case 254: */
    ApplySpeedWithSafety(0, 0, 254);
    break;

/* case 255: [M3] 必须发零速 */
    ApplySpeedWithSafety(0, 0, 255);
    break;

正当性(为何能解决「有油门无电机仍狂发转速」):

阶段 旧逻辑 新逻辑
ESC STALL 仍 SendSetSpeed(N) LIMP → 恒发 0
用户继续推油门 ECU 与 ESC 对抗 out_rpm=0,直至进入 RECOVERY_WAIT
恢复 无定义 松油门 ≥2s 且(通信正常后)回 NORMAL
断链 不发 CAN 强制 0

11.5 M3:mode=255 补丁(最小改动,不建安全层时)

若暂不引入 powertrain_safety.c,至少改 esc_manager.c:

case 255:
    ThrottleAndGear.throttle = 0;
    MotorSpeed = 0;
    SendSetSpeedRequest(0);   /* ← 新增这一行 */
    break;

并在 Ble/src/ble_server.c 的 ESP_GATTS_DISCONNECT_EVT 中可选调用 SendSetSpeedRequest(0) 双保险(断链回调与 20ms 任务可能存在 ≤20ms 窗口)。


11.6 M5:ecu_manager.c BMS 通信判断

/* ----- 文件: App/src/ecu_manager.c ----- */
/* 约 510 行:将 GetRcConnectionStatus() 改回 ----- */

} else if (GetBmsConnectionStatus()) {  /* 未收到电池数据且 BMS 蓝牙已连接 */
    BMS_CommunicationFailure++;
    ...
}

正当性: BMS 链路状态应由 BMS BLE Client 决定;用 RC Server 连接态会 误清零或误置位 BMS 故障。


11.7 M6:ESC 通信故障与 UAVCAN 降频

6a — uavcan_twai.c:故障时全帧入队

static void HandleEscSetSpeedResponse(CanardRxTransfer *transfer) {
    static uint8_t sendCount = 0;
    if (esc_SetSpeedResponse_decode(transfer, &escSetSpeedResponseData)) {
        return;
    }

    bool has_fault = (escSetSpeedResponseData.running_error != 0)
                  || (escSetSpeedResponseData.selfcheck_error != 0);

  if (has_fault || (++sendCount >= 10)) {
        SendEscSpeedResponseDataToQueue(escSetSpeedResponseData, pdMS_TO_TICKS(10));
        sendCount = 0;
    }
}

6b — ecu_manager.c:置位通信故障时通知安全层

if (ESC_CommunicationFailure >= 240) {
    ecuEventCode |= FAULT_ESC_COMMUNICATION;
    PowertrainSafetyOnEscCommFault(true);   /* 新增 */
} else if (收到 ESC 数据) {
    ecuEventCode &= ~FAULT_ESC_COMMUNICATION;
    PowertrainSafetyOnEscCommFault(false);
}

正当性: 降低「STALL 后反馈变慢 → 12s 假断联」概率;假断联时 同样拉闸,避免用户在「通信异常」下继续推油门。


11.8 安全停板专项实现(M8 / M9 / LSS 三层)

对应 §8.3 分层安全停板(LSS);解决 断联、失速、不可控故障 后如何 更好停止板体。

11.8.1 停板触发统一入口(powertrain_safety.c)

/* ----- 文件: App/src/powertrain_safety.c ----- */

#define ESC_FEEDBACK_LOST_STOP_MS    500   /* M9:500ms 无反馈即停板(≠12s 报障) */
#define ESC_FEEDBACK_LOST_REPORT_MS  12000 /* 仍用 ecu_manager 12s 置 FAULT_ESC_COMM */
#define CRITICAL_ESC_EVENT_MASK      (ESC_EVT_MASK_STALL | 0x00010000u | 0x00040000u)

static bool s_limp = false;
static bool s_was_driving = false;
static uint32_t s_esc_feedback_lost_ms = 0;

void PowertrainSafetySetLimp(const char *reason) {
    if (!s_limp) {
        s_limp = true;
        ESP_LOGW("PWR_SAFE", "LIMP: %s", reason);
    }
}

bool PowertrainSafetyIsLimp(void) {
    return s_limp;
}

/* 每 100ms:ProcessEscDataTask 收到 ESC 时调用 */
void PowertrainSafetyOnEscFeedback(uint16_t running_error, uint16_t selfcheck_error,
                                 int32_t esc_speed) {
    uint32_t esc_event = ((uint32_t)running_error << 16) | selfcheck_error;
    s_esc_feedback_lost_ms = 0;

    if (esc_event & CRITICAL_ESC_EVENT_MASK) {
        PowertrainSafetySetLimp("esc_critical_fault");
    }
}

/* 每 50ms:ecu_manager 未收到 EscRunningData 时调用 */
void PowertrainSafetyOnEscFeedbackTimeout(bool rc_connected, bool had_recent_drive) {
    if (!rc_connected) {
        return;
    }
    s_esc_feedback_lost_ms += 50;
    if (had_recent_drive && s_esc_feedback_lost_ms >= ESC_FEEDBACK_LOST_STOP_MS) {
        PowertrainSafetySetLimp("esc_feedback_lost");  /* M9:快停,不等 12s */
    }
}

/* 断链:ble_server DISCONNECT 与 mode255 双路径 */
void PowertrainSafetyOnRcLinkLost(void) {
    PowertrainSafetySetLimp("rc_link_lost");
    SendSetSpeedRequest(0);  /* L2:立即拉闸,不等待 20ms 任务 */
}

11.8.2 L1:控制任务出口(esc_manager.c)

static void ApplySpeedWithSafety(int32_t raw_rpm, uint16_t throttle, uint8_t mode) {
    int32_t safe_rpm = raw_rpm;

    if (mode == 255 || !GetRcConnectionStatus()) {
        PowertrainSafetyOnRcLinkLost();
    }

    if (PowertrainSafetyIsLimp()) {
        safe_rpm = 0;
    } else {
        PowertrainSafetyFilterSpeed(raw_rpm, throttle, mode, &safe_rpm);
        if (safe_rpm != 0) {
            s_was_driving = true;
        }
    }

    if (LastEscSpeedData.runningError & (1u << 9)) {
        safe_rpm = 0;
    }

    SendSetSpeedRequest(safe_rpm);
}

11.8.3 L3:零速看门狗任务(M8,新建)

/* ----- 文件: App/src/powertrain_safety.c ----- */

#define SAFE_STOP_WATCHDOG_PERIOD_MS  50

static void SafeStopWatchdogTask(void *arg) {
    while (1) {
        if (PowertrainSafetyIsLimp() || !GetRcConnectionStatus()) {
            /* 周期性重复零速:覆盖 ESC 丢帧、OTA 挂起控制任务等情况 */
            SendSetSpeedRequest(0);
        }
        vTaskDelay(pdMS_TO_TICKS(SAFE_STOP_WATCHDOG_PERIOD_MS));
    }
}

void PowertrainSafetyInit(void) {
    xTaskCreatePinnedToCore(SafeStopWatchdogTask, "SafeStopWatchdog",
                            2048, NULL, 16, NULL, 1);  /* 优先级 ≥ MotorSpeedControl */
}

正当性:

场景 仅 L1 加 L3 看门狗
失速后控制任务正常 每 20ms 发 0 20ms + 50ms 双保险
OTA 挂起 MotorSpeedControl ❌ 可能无零速 ✅ 50ms 仍发 0
断链只发一次 0 但 ESC 未执行 ❌ ✅ 50ms 重试
CAN Bus-Off 恢复窗口 间歇 恢复后持续补 0

11.8.4 ecu_manager.c:快停与慢报障分离(M9)

/* ProcessEcuDataTask 内,ReceiveEscRunningDataFromQueue 失败分支 */

static bool HadRecentDrive(void) {
    return PowertrainSafetyIsLimp() || (GetLatestEcuHeatbeatData()->motor_speed != 0);
    /* 或维护「过去 2s 内发过非零 SetSpeed」标志 */
}

if (ReceiveEscRunningDataFromQueue(...) == pdPASS) {
    ...
    PowertrainSafetyOnEscFeedback(esc.runningError, esc.selfcheckError, esc.speed);
    ESC_CommunicationFailure = 0;
    ecuEventCode &= ~FAULT_ESC_COMMUNICATION;
} else if (GetRcConnectionStatus()) {
    PowertrainSafetyOnEscFeedbackTimeout(true, HadRecentDrive());  /* 500ms → LIMP */

    ESC_CommunicationFailure++;
    if (ESC_CommunicationFailure >= 240) {  /* 12s:仅上报,停板已由 M9 完成 */
        ecuEventCode |= FAULT_ESC_COMMUNICATION;
    }
}

11.8.5 ble_server.c:断链双保险(L2)

/* ESP_GATTS_DISCONNECT_EVT 内,SendRcThrottleAndGearCmd 之后 */
PowertrainSafetyOnRcLinkLost();  /* 内已 SendSetSpeedRequest(0) */

11.8.6 停板后逻辑流(修改后 · 正当性)

flowchart TD
    START[20ms 控制周期] --> CHK_LINK{RC 连接?}
    CHK_LINK -->|否| LIMP[进入 LIMP + 立即 CAN 0]
    CHK_LINK -->|是| CHK_FB{ESC 反馈 500ms 内?}
    CHK_FB -->|否且曾在驱动| LIMP
    CHK_FB -->|是| CHK_STALL{STALL/严重故障?}
    CHK_STALL -->|是| LIMP
    CHK_STALL -->|否| NORMAL[按油门计算 rpm]
    NORMAL --> GATE[ApplySpeedWithSafety]
    LIMP --> ZERO[safe_rpm=0]
    ZERO --> CAN[SendSetSpeed 0]
    CAN --> WD[看门狗 50ms 再发 0]
    WD --> REC{松油门2s且故障清除?}
    REC -->|是| NORMAL
    REC -->|否| LIMP

为何这是「更好停止板体」的方案:

  1. 断联:不等用户、不等日志 — 断链 立即 + 周期 发 CAN 零速。
  2. 失速/不可控:不依赖 12s 通信故障 — 500ms 无反馈 或 STALL 位 即停。
  3. 不依赖单任务 — 看门狗在控制任务挂起时仍拉闸。
  4. 恢复可预期 — 必须松油门 + 故障清除,避免「停不下来」和「起不来」两种极端。

11.8.7 可选增强 M10:长时间 LIMP 后重启 ESC

static uint32_t s_limp_duration_ms = 0;

/* 在 SafeStopWatchdogTask 中 */
if (PowertrainSafetyIsLimp()) {
    SendSetSpeedRequest(0);
    s_limp_duration_ms += SAFE_STOP_WATCHDOG_PERIOD_MS;
    if (s_limp_duration_ms >= 3000 && !EscFeedbackSeenRecently()) {
        SendEscRebootRequest();  /* 仅当产品/ESC 手册允许 */
        s_limp_duration_ms = 0;
    }
} else {
    s_limp_duration_ms = 0;
}

需与电调厂家确认:重启是否 Safe、水上是否允许。


11.9 修改后端到端逻辑流程(正当性说明)

11.8.1 总体数据流(修改后)

flowchart TB
    RC[RC ThrottleAndGear 20ms]
    MSC[MotorSpeedControlTask]
    SAFE[PowertrainSafetyFilterSpeed]
    CAN[SendSetSpeedRequest]
    ESC[ESC 固件]
    PED[ProcessEscDataTask 100ms]
    UPD[UpdateESCErrorCodeByRunningStatus]
    SAFE_FB[PowertrainSafetyOnEscFeedback]
    ECU[ProcessEcuDataTask 50ms]
    HB[Heartbeat / Event → App]

    RC --> MSC
    MSC -->|raw MotorSpeed| SAFE
    SAFE -->|safe_rpm 可能强制 0| CAN
    CAN --> ESC
    ESC --> PED
    PED --> UPD
    UPD --> SAFE_FB
    SAFE_FB --> SAFE
    PED --> ECU
    ECU -->|FAULT_ESC_COMM| SAFE
    ECU --> HB

设计原则对照:

原则 修改后如何满足
失效安全 fail-safe LIMP 默认 rpm=0;断链/通信/STALL 均进入 LIMP
单点写 CAN 转速 仅 ApplySpeedWithSafety → SendSetSpeedRequest
控制与解耦 安全层只输出 safe_rpm,不修改油门协议
可恢复 RECOVERY_WAIT 要求松油门 2s,避免「顶着油门」反复触发 STALL
协议一致 STALL 仍报 0x20000,与 App/OSS 一致

11.8.2 安全状态机(修改后)

stateDiagram-v2
    [*] --> NORMAL
    NORMAL --> LIMP: ESC STALL 或 ECU_COMM 或 RC 断链 mode255
    LIMP --> RECOVERY_WAIT: 油门≤死区 且 非通信故障阻塞
    RECOVERY_WAIT --> NORMAL: 松油门≥2s
    RECOVERY_WAIT --> LIMP: 油门再次推高 或 再次 STALL
    LIMP --> LIMP: 持续 SendSetSpeed 0
    NORMAL --> NORMAL: 无故障且 safe_rpm=requested

为何需要 RECOVERY_WAIT(正当性):

11.8.3 时序:STALL 场景(修改后预期)

sequenceDiagram
    participant U as 用户
    participant ECU as MotorSpeedControl + Safety
    participant ESC as 电调

    U->>ECU: 油门 800
    ECU->>ESC: SetSpeed(N)
    ESC-->>ECU: running_error STALL
    ECU->>ECU: state=LIMP
    ECU->>ESC: SetSpeed(0) 每20ms
    U->>ECU: 油门仍 800
    ECU->>ESC: SetSpeed(0) 忽略请求
    U->>ECU: 油门回 0 持续2s
    ECU->>ECU: state=NORMAL
    U->>ECU: 油门 300
    ECU->>ESC: SetSpeed(n) 恢复

对比 修改前:用户在步骤「油门仍 800」时 ECU 仍发 SetSpeed(N),与 ESC 保护冲突。

11.8.4 时序:BLE 断链(修改后预期)

sequenceDiagram
    participant BLE as ble_server
    participant ECU as MotorSpeedControl
    participant ESC as 电调

    BLE->>ECU: mode=255
    ECU->>ECU: LIMP
    ECU->>ESC: SetSpeed(0)
    Note over ESC: ≤20ms 内收到零速指令

11.10 合入顺序与自测清单

步骤 内容 验证(不以 OSS 为准)
1 M3 + L2 断链零速 断 BLE,CAN 立即且持续 SetSpeed=0
2 M8 看门狗 OTA 挂起 MotorSpeedControl 后 CAN 仍每 50ms 为 0
3 M9 500ms 快停 拔 CAN 模拟反馈丢失,<1s 内零速(不必等 12s)
4 M1 bit1 + 清除 堵转语义对齐 131072
5 M4 OR 合并 ESC 原生 STALL 能进 LIMP
6 M2/M7 安全层 堵转/失速后持续推油门,CAN 恒为 0
7 M5 BMS BMS 断开不误用 RC 状态
8 M6 降频 故障帧全量入队;12s 仅作 App 上报

停板专项台架(§8.3):

用例 操作 通过标准
T-STOP-1 满速运行中断 BLE 200ms 内 CAN 零速;50ms 看门狗持续补 0
T-STOP-2 台架堵转 STALL 检测到 STALL ≤1 周期 进入 LIMP,CAN 恒 0
T-STOP-3 运行中拔 ESC CAN 500ms 内 停板(与 12s 弹窗无关)
T-STOP-4 LIMP 中持续推油门 电机不得再次加扭,直至松油门 2s

11.11 与当前 feature_motorstuck 的差异小结(给 Code Review)

位置 当前分支 建议修改后
esc_manager.c:227 bit6 堵转注释 bit1 + 滞回
esc_manager.c:231 runningError = runningErrorState 覆盖 native \| state
esc_manager.c:363 直接 SendSetSpeedRequest ApplySpeedWithSafety
esc_manager.c:376 255 无 CAN SendSetSpeed(0)
无 — powertrain_safety.c + SafeStopWatchdogTask
无 — 500ms 反馈丢失快停(M9)

九、测试用例(分支专项)

ID 步骤 期望(合入前) 期望(修完后)
T-M1 台架堵转至 ESC 报 131072 仍可能持续发转速 零速 + 松油门恢复
T-M2 注入 speed=25000 误报 4194304(当前) 应报 STALL 或专用码,且拉闸
T-M3 对比 master/bit1 路径 master 可能 131072 行为一致且可恢复
T-M4 BLE 断开 CAN 可能保持旧转速 200ms 内零速
T-M5 Ulog RC+ESC 本分支可记录 对齐 OSS 15349 时间轴

十、结论

  1. feature_motorstuck 未解决用户主诉「失速 + 断联 + 遥控器无法控制」的三大结构性缺陷(STALL 不拉闸、假断联逻辑、断链不零速)。
  2. 分支 实质改动 是:转速异常从 bit1(STALL) 改为 bit6(过压),并启用 空转 bit9 限扭、打开 RC Ulog、移除 ALC/NearLink。
  3. 从协议语义看,bit6 标注「堵转」是错误的,会导致与现场 131072 分析口径 脱节,并可能 误触发过压告警。
  4. 建议:不要将当前分支当作 motor stuck 修复版合并;断联/失速如何更好停板见 §8.3 分层安全停板(LSS) 与 §11.8 实现。
  5. 合入后必须以 §11.10 T-STOP-1~4(CAN 抓包 + 断 BLE 台架)为提测门槛,不以 OSS 失速后数据为准。

附录 A:master vs feature_motorstuck 对照表

文件/逻辑 master feature_motorstuck
转速异常 ECU 合成 speed<=-20000 → bit1 → STALL \|speed\|>=20000 → bit6 → OV
STALL 后限扭 无 无
空转 bit9 执行 注释 MotorSpeed=0
mode255 零速 无 无
ALC 有 无
NearLink 有 无
Ulog RC 注释 开启
BMS 通信条件 GetBmsConnectionStatus GetRcConnectionStatus ⚠️
esc_manager 行数 ~666 ~553

附录 B:关键源码索引(本分支)

主题 路径
转速异常 bit6 App/src/esc_manager.c 225~231
电机控制任务 同上 282~390
ESC 通信故障 App/src/ecu_manager.c 479~497
UAVCAN 降频 Protocol/src/uavcan_twai.c 58~62
入口 BLE only main/main.c 124~139

附录 C:相关报告

文档 说明
JETWAVE_ECU_POWERTRAIN_DEEP_ANALYSIS_REPORT.md master 深度报告
NETHERLANDS_ABNORMAL_REPORT_20260526_190035.md 荷兰 STALL 样本
CODE_REVIEW_交互架构与问题导读.md App 131072/1073741824

附录 D:§十一 修改项速查

ID 文件 函数/位置
M1 App/src/esc_manager.c UpdateESCErrorCodeByRunningStatus
M2/M7 App/src/powertrain_safety.c(新) PowertrainSafetyFilterSpeed
M2/M7 App/src/esc_manager.c ApplySpeedWithSafety / MotorSpeedControlTask
M3 App/src/esc_manager.c case 255
M4 App/src/esc_manager.c UpdateESCErrorCodeByRunningStatus 末尾合并
M5 App/src/ecu_manager.c BMS 通信 ~510 行
M6a Protocol/src/uavcan_twai.c HandleEscSetSpeedResponse
M6b App/src/ecu_manager.c FAULT_ESC_COMMUNICATION 置位/清除
M8 App/src/powertrain_safety.c SafeStopWatchdogTask(停板 L3)
M9 ecu_manager.c + powertrain_safety.c OnEscFeedbackTimeout 500ms 快停
M10 powertrain_safety.c 可选 SendEscRebootRequest

附录 E:停板方案速查(§8.3)

场景 推荐动作
蓝牙断联 立即 SendSetSpeed(0) + 看门狗 50ms 重复
失速 STALL 立即 LIMP,safe_rpm=0
ESC 反馈丢失 500ms 停板;12s 仅上报通信故障
OTA 挂起控制任务 看门狗仍发零速
恢复 松油门 2s + 无 STALL + 通信恢复

静态审计结论;ESC 固件 STALL 阈值与恢复需 CAN 抓包与厂家文档确认。实施细节以 §十一 为准。