电机驱动开发学习23. 保护体系与故障状态机
- 一、本章在系列中的位置与设计目标
-
- [1.1 课程上下文演进](#1.1 课程上下文演进)
- [1.2 本章目标与防护架构](#1.2 本章目标与防护架构)
- 二、硬件快断与软件分级双层保护架构
-
- [2.1 微秒级(µs)硬件快断与动态恢复机制](#2.1 微秒级(µs)硬件快断与动态恢复机制)
- [2.2 毫秒级(ms)软件监控与故障分级](#2.2 毫秒级(ms)软件监控与故障分级)
- [2.3 缺相保护深挖:物理模型、采样陷阱与扇区卡死兜底](#2.3 缺相保护深挖:物理模型、采样陷阱与扇区卡死兜底)
-
- [2.3.1 物理本质:三相开路不等于瞬间失去力矩](#2.3.1 物理本质:三相开路不等于瞬间失去力矩)
- [2.3.2 采样陷阱:双电阻/三电阻采样(R3_2)中的重构虚像](#2.3.2 采样陷阱:双电阻/三电阻采样(R3_2)中的重构虚像)
- [2.3.3 精确缺相检测算法:只信实测,排除重构](#2.3.3 精确缺相检测算法:只信实测,排除重构)
-
- [路径 1:转子维持旋转(扇区演进)](#路径 1:转子维持旋转(扇区演进))
- [路径 2:转子完全卡死(扇区卡死盲区与兜底)](#路径 2:转子完全卡死(扇区卡死盲区与兜底))
- [2.3.4 缺相原理与调试日志对照表](#2.3.4 缺相原理与调试日志对照表)
- [三、统一故障状态机 (Fault FSM) 设计](#三、统一故障状态机 (Fault FSM) 设计)
-
- [3.1 核心设计原则](#3.1 核心设计原则)
- [3.2 故障码定义表](#3.2 故障码定义表)
- 四、核心代码架构与关键模块实现
-
- [4.1 核心数据结构与头文件 (`bsp_fault.h`)](#4.1 核心数据结构与头文件 (
bsp_fault.h)) - [4.2 统一故障状态机与监控轮询实现 (`bsp_fault.c`)](#4.2 统一故障状态机与监控轮询实现 (
bsp_fault.c))
- [4.1 核心数据结构与头文件 (`bsp_fault.h`)](#4.1 核心数据结构与头文件 (
- 五、调试命令、验证矩阵与工程最佳实践
-
- [5.1 串口指令与 FreeMASTER 监控变量](#5.1 串口指令与 FreeMASTER 监控变量)
-
- 串口常用指令
- [FreeMASTER 核心监控表](#FreeMASTER 核心监控表)
- [5.2 故障注入实验验证矩阵](#5.2 故障注入实验验证矩阵)
-
- [5.2.1 启动前缺相实测(已验证,符合预期)](#5.2.1 启动前缺相实测(已验证,符合预期))
- [5.2.2 转子堵转实测(已验证,符合预期)](#5.2.2 转子堵转实测(已验证,符合预期))
- 六、总结与后续演进

一、本章在系列中的位置与设计目标
1.1 课程上下文演进
在电机驱动系统的开发路径中,从"能让电机旋转"到"能够在工业级复杂恶劣环境下长期稳定稳定运行",其核心分水岭在于系统是否具备完善的异常诊断、故障闩锁与安全恢复机制。
| 章节 | 核心能力 | 物理/软件局限 | 本章补齐与强化 |
|---|---|---|---|
| Lesson 6 | 软件过流保护雏形 | 阈值单一、未锁存故障源、缺少硬件级联断 | 建立统一故障状态机(FSM),引入首故障锁存与分级恢复机制 |
| Lesson 20~22 | Hall FOC 闭环、双电阻/三电阻采样、相移与角度标定 | 驱动虽可旋转,但遇到拖尾断线、过温、欠压、堵转时易烧毁功率 MOSFET | 实现硬件微秒级关断 + 软件毫秒级监控 的双层防护,实现能长期跑、异常可诊断/可安全恢复 |
| 官方 MCSDK | 基于 STM(State Machine)状态机,集成过温/欠压/Break |
库代码过于庞大且耦合度高,不便于定制与底层调优 | 设计自研精简版高内聚保护模块 bsp_fault |
核心逻辑 :Lesson 20~22 解决了电机驱动的**运动控制(Motion Control)逻辑;
本章 Lesson 23 是构建系统的安全防护(Safety System)**逻辑,确保系统在发生任何硬件或软件异常时均能"及时停机、安全闩锁、准确报警"。
1.2 本章目标与防护架构
本章旨在构建一套全方位的防护链条,硬件快速响应与软件精确诊断相互配合:
text
硬件快断(功率级 OCP 比较器 / TIM1 BKIN 硬件关断)
+
软件分级检测(过流 / 欠压 / 过温 / 堵转 / 缺相 / 卡死兜底)
↓
统一故障状态机(FAULT_LATCHED 锁存)
↓
硬件防护动作(关闭 PWM、切断桥臂、LED2 闪烁、记录 g_fm_fault_code)
↓
上位机 / 串口交互(串口 fc 查询码表 / r 清障恢复)
| 序号 | 目标模块 | 关键落点文件 | 核心职责 |
|---|---|---|---|
| 1 | 统一故障状态机 | User/protect/bsp_fault.c/h |
管理系统全局故障状态,实现首故障锁存与解除 |
| 2 | 软件多源故障轮询 | fault_poll() |
实时监控 OC(过流)、UV(欠压)、OT(过温)、PHASE_LOSS(缺相) |
| 3 | 堵转与转子异常 | motor_stall_poll() |
监测霍尔边沿超时,触发 fault_raise(FAULT_STALL) |
| 4 | 硬件 OCP 动态恢复 | FAULT_HW_OCP |
监控驱动芯片 SD(Shut Down)脚状态,实现有限次恢复后自动闩锁 |
| 5 | TIM Break 硬件钩子 | FAULT_HW_BKIN_EN |
保留刹车引脚中断响应,实现纳秒/微秒级 PWM 硬件封锁 |
| 6 | 人机交互与调试恢复 | bsp_pid_app.c |
提供串口 fc(Fault Clear/Check)及 r(Reset/Recover)命令 |
二、硬件快断与软件分级双层保护架构
在电力电子驱动设计中,单一的软件保护难以兼顾"极速响应"与"复杂异常识别"两大诉求,因此必须采用硬件快断 + 软件分级的双层防护架构。

2.1 微秒级(µs)硬件快断与动态恢复机制
MOSFET 的短路耐受时间(SCWT)通常仅有几个微秒。过流发生时,如果等待主循环软件去读取 ADC 并计算,功率器件可能已经击穿烧毁。
硬件防护的典型响应路径如下:
- 驱动芯片内部比较器 / OCP 硬件电路 在检测到母线或相电路过流时,第一时间将硬件
SD(Shut Down,如 PG12 脚)引脚拉低,强行关闭栅极驱动信号。 - 软件故障检测层 在主程序或周期定时器中检测到"控制器输出电压指令巨大( V d , V q V_d, V_q Vd,Vq 高),但相电流实测值趋近于零 "或直接读取到
SD引脚异常电平时,判定驱动触发了硬件过流。 - SD 动态恢复逻辑 :由于工业现场可能存在短暂的尖峰干扰,系统通过
bldcm_sd_fault_recover()尝试发送解锁脉冲。如果连续尝试恢复次数超过阈值FAULT_HW_OCP_MAX_RECOVER,则触发FAULT_HW_OCP硬件闩锁,终止重试。
注意(基于官方 MCSDK / 骄阳驱动板硬件设计) :
在官方 Hall FOC 工程中,默认
BKIN_MODE = NONE(即硬件比较器输出未硬连线至 STM32 的 TIM1_BKIN 引脚)。因此本方案配置:
- 默认
FAULT_HW_BKIN_EN = 0,不启用 TIM1 刹车中断。- 硬件保护主要依赖驱动芯片 OCP + SD 引脚配合软件重试机制 ;同时在代码中保留
fault_on_hw_break()中断回调钩子,若后续硬件板卡将比较器引脚连接至 TIM1_BKIN,仅需将FAULT_HW_BKIN_EN置 1 即可开启纳秒级硬件刹车。
2.2 毫秒级(ms)软件监控与故障分级
软件防护运行于 1 kHz ~ 10 kHz 的周期性中断或主循环中,负责采集连续物理量,结合去抖滤波算法,消除噪声误触发。
| 故障类型 | 状态码 | 判据(默认配置) | 采样/计算源 | 去抖/滤波时间 | 关键代码位置 |
|---|---|---|---|---|---|
| 软件过流 (OC) | OVERCURRENT |
任一相电流绝对值 ∥ I x ∥ > I max \Vert{}I_x\Vert{} > I_{\text{max}} ∥Ix∥>Imax | ADC 运放实测相电流 | MOTOR_OVERCURRENT_HOLD_MS |
bsp_fault.c → fault_check_overcurrent();阈值在 bsp_adc.h |
| 母线欠压 (UV) | UNDERVOLTAGE |
V bus < 18 V V_{\text{bus}} < 18\text{ V} Vbus<18 V(24V 电压系统) | 母线分压 ADC 采集 | 200 ms 连续判定 | bsp_fault.c → fault_check_undervoltage();FAULT_VBUS_UV_* |
| 功率板过温 (OT) | OVERTEMP |
T board > 85 ∘ C T_{\text{board}} > 85^\circ\text{C} Tboard>85∘C | NTC 热敏电阻 ADC | 500 ms 连续判定 | bsp_fault.c → fault_check_overtemp();FAULT_TEMP_OT_* |
| 转子堵转 (Stall) | STALL |
霍尔接口长时间无捕获边沿 | TIM 捕获中断 / 轮询 | 启动宽限期后计时 | bsp_motor_tim.c → motor_stall_poll() |
| 开路缺相 (Phase) | PHASE_LOSS |
实测相窗口峰值≈0(limp 仍转);或霍尔冻结+零速 | 仅统计 ADC 实测相 | 250 ms 峰值窗 / 卡死约 0.6~1 s | bsp_fault.c → fault_check_phase_loss();FAULT_PHASE_* |
| 硬件过流闩锁 | HW_OCP |
重试恢复次数达到上限 | SD 状态与重试计数器 | 重试超限(3次)立刻闩锁 | bsp_fault.c → fault_check_hw_ocp() / fault_on_hw_break() |
统一调度:fault_poll();闩锁入口:fault_raise()。阈值调参说明见 bsp_fault.h 头注释。
当任何一项软件判据成立时,系统将调用统一的入口函数 fault_raise(code):
- 立即停止速度环与电流环计算,复位 PID 积分项;
- 中止当前 Bootstrap 预充电及启动流程(Abort Boot);
- 强制关闭 PWM 输出(把上下桥臂栅极关断);
- 点亮 / 闪烁 LED2 报警指示灯;
- 将故障码写入
g_fm_fault_code并置位锁存标志。
2.3 缺相保护深挖:物理模型、采样陷阱与扇区卡死兜底
开路缺相是电机驱动调试中最具隐蔽性的故障之一。现场常常出现看似矛盾的现象:
- 拔掉了一根电机相线(如 W 相),电机居然还能缓慢旋转 ,或者反过来卡死完全不动;
- 打开 FreeMASTER 或串口查看
Iu / Iv / Iw时,发现三相电流都有数据 ,但串口查询fc却一直返回OK。
要彻底消除缺相的误报与漏报,必须从物理磁场、ADC 采样机制及矢量控制盲区进行深度剖析。
2.3.1 物理本质:三相开路不等于瞬间失去力矩
在三相 PMSM/BLDC 电机中,三相绕组在空间对称分布(相位差 120°)。当其中某一相(如 W 相)断开后,剩下的两相(U、V 相)实际上串联构成了一个单相回路。
I u = − I v , I w = 0 I_u = -I_v, \quad I_w = 0 Iu=−Iv,Iw=0
此时系统中的电流只能产生沿着 U-V 相轴线方向的脉动磁场。由于脉动磁场可以分解为正转与反转两个旋转磁场,在转子已有初始转速或负载极轻的情况下,转子在惯性与正向旋转分量的作用下依然能够维持旋转(但伴有剧烈的转矩脉动、异响与发热)。
因此:
- 电机还能转动,绝对不能证明三相接线完好;
- 电机转不动,也不等于软件已经识别到了缺相------它仅仅表明产生的合力矩不足以克服静态阻力矩。
2.3.2 采样陷阱:双电阻/三电阻采样(R3_2)中的重构虚像
在基于双电阻/三电阻(R3_2)采样架构的 FOC 系统中,为了避开 PWM 切换时的开关噪声以及下桥臂导通时间过短的问题,每个 PWM 周期只选择下桥臂导通时间足够长(采样窗口开得够大)的两相进行 ADC 采样,第三相电流则基于基尔霍夫电流定律(KCL)通过软件重构得出:
I reconstructed = − ( I measured_1 + I measured_2 ) I_{\text{reconstructed}} = - (I_{\text{measured\1}} + I{\text{measured\_2}}) Ireconstructed=−(Imeasured_1+Imeasured_2)
在代码实现中,用 pair 变量标识当前 PWM 拍的采样模式:
pair 值 |
ADC 真实采样的两相 | 软件重构计算的第三相 |
|---|---|---|
| 0 | U 相、V 相 | I w = − I u − I v I_w = -I_u - I_v Iw=−Iu−Iv (重构) |
| 1 | V 相、W 相 | I u = − I v − I w I_u = -I_v - I_w Iu=−Iv−Iw (重构) |
| 2 | W 相、U 相 | I v = − I w − I u I_v = -I_w - I_u Iv=−Iw−Iu (重构) |
电机正常旋转时,SVPWM 的矢量扇区在 1~6 之间不断循环,pair 也会在 0 -> 1 -> 2 -> 0 之间频繁切换。
text
【关键推论与欺骗性现象】
假设物理上 W 相线已经彻底断开(Iw_real = 0):
当 PWM 扇区处于 pair = 0 时,ADC 实测的是 U 相和 V 相(例如测得 Iu = +5A, Iv = -5A)。
此时软件会自动套用公式计算 Iw = - (Iu + Iv) = 0A;
但如果存在测量偏置或动态响应时,计算出的 Iw 可能会是一个非零数值。
更严重的是:如果 U 相和 V 相构成了串联回路,实测得 Iu = +2A, Iv = -2A,
软件计算出的 Iw 竟然是 0A,而在波形图上看起来"三相都有电流",给开发者造成三相正常的假象!
传统的缺相检测如果简单计算三相电流幅值的有效值,或者直接要求 I q ∗ I_q^* Iq∗ 很大时才判定,就会因为重构相的虚假数值 导致严重的漏报。
2.3.3 精确缺相检测算法:只信实测,排除重构
为了彻底消除重构相的干扰,现行的开路检测算法 fault_check_phase_loss() 严格遵循"只信实测相"的原则:

路径 1:转子维持旋转(扇区演进)
当电机带开路相旋转时,pair 会依次轮转到开路相参与实测的采样对中。算法仅检查当前拍 ADC 硬件真正采样的两相:
- 若其中一相实测电流持续接近 0,而另一相实测电流明显非 0(满足串联或开路特征);
- 则在 250 ms 统计窗口内累加一次"开路命中";
- 命中率达标后触发
FAULT_PHASE_LOSS锁存。
路径 2:转子完全卡死(扇区卡死盲区与兜底)
在实际调试中,启动瞬间断开一相往往会导致电机无法起动。此时系统会陷入以下死循环盲区:
text
W 相断开 / 启动力矩不足 → 转子卡死在原位 → 霍尔信号不跳变 (hall frozen)
→ 电机角度 θ 锁定在某一固定位置
→ SVPWM 矢量扇区完全固定 (例如 Sector 4)
→ pair 永远停留在 0 (仅实时采集 U、V 相)
→ 开路相 (W相) 永远无法进入 ADC "硬件实测" 窗口
→ 路径 1 的命中率统计机制彻底失效!
→ FOC 控制器以为系统正常,输出极高的 Vq/Vd,但电机无反应,fc 显示 OK
为了弥补路径 1 在静态下的盲区,算法特别增加了扇区卡死兜底机制(FAULT_PHASE_STUCK_*):
- 系统检测到 r p m = 0 rpm = 0 rpm=0 且霍尔信号长周期冻结;
- 同时 FOC 输出了显著的电压幅值( ∥ V d ∥ + ∥ V q ∥ > V stuck_th \Vert{}V_d\Vert{} + \Vert{}V_q\Vert{} > V_{\text{stuck\_th}} ∥Vd∥+∥Vq∥>Vstuck_th),但 I q I_q Iq 电流依然无法有效建立;
- 维持时间超过
FAULT_PHASE_STUCK_MS(约 1 秒)后,系统强制强行锁存缺相/卡死故障。
优化 :在电机启动阶段的强拖(
DRAG)或对齐(ALIGN)过程中,foc_boot_is_active()为真。若在此阶段跳过缺相检测,会导致启动缺相永远无法被识别。目前的兜底算法在DRAG/ALIGN阶段依然保持后台监控(约 1 s 限时),避免启动阶段发生功率级长时间干烧。
2.3.4 缺相原理与调试日志对照表
在调试过程中,通过串口日志或 FreeMASTER 输出的变量与底层物理逻辑的对应关系如下:
| 现象 / 日志信息 | 底层物理与算法解释 | 排查建议 |
|---|---|---|
Iu/Iv/Iw 三相波形均有非零电流 |
当前显示的极有可能是软件重构相 的数据,需通过 pair 确认哪两相才是硬件真正测得 |
查看当前 pair 状态;不要依赖第三相数据判断硬件完好 |
pair 在 0 ↔ 1 ↔ 2 间周期切换 |
说明电机正在旋转,扇区正常演进,路径 1(动态命中率统计)有效 | 若有缺相,系统会在 250 ms 内提示 Phase loss: X open? |
pair 长期固定为单一数值且 rpm=0 |
说明矢量扇区卡死,开路相可能脱离了实测窗口,路径 1 失效,路径 2 启动 | 观察是否触发 Phase/stall: stuck hall frozen= 兜底 |
| 拔掉一相线后电机依然缓慢转动 | 属于两相单相运行的正常物理现象(矩形/脉动磁场) | 严禁长时间运行,等待算法锁存或手动发送 s 0 停机 |
| I q ∗ I_q^* Iq∗ 给定很大,实测 I q ≈ 0 I_q \approx 0 Iq≈0,且 V d , V q V_d, V_q Vd,Vq 饱合 | 环路输出已达最大能力但无法建立有效电流,典型的电机开路或线缆脱落现象 | 优先排查相线接插件是否松动,测量三相线间电阻 |
三、统一故障状态机 (Fault FSM) 设计
状态机是系统安全运行的中枢,负责故障的捕获、闩锁、报警以及复位恢复。
text
protect_en = 0 (关闭保护)
+---------------------------------+
| |
▼ |
+-----------+ fault_raise() +---------------+
| FAULT_OK | ------------------> | FAULT_LATCHED |
+-----------+ +---------------+
▲ |
| |
+---------------------------------+
串口指令 'r' / 故障清除复位
3.1 核心设计原则
- 首故障优先锁存(First-Fault Latching) :
当系统触发第一个故障(如欠压UNDERVOLTAGE)并进入FAULT_LATCHED状态后,后续由停机过程引发的次生异常(如停转导致的堵转STALL)不会覆盖初始故障码。这确保了调试人员在查看故障日志时能够准确定位最初的真实诱因。 - 硬件/软件联动切断 :
一旦进入FAULT_LATCHED,状态机立刻调用底层的硬件关闭接口(关闭 PWM、切断桥臂使能、停止 PID 计算),杜绝故障扩大化。 - 全域变量同步 :
状态机实时维护 FreeMASTER 全局变量g_fm_fault_code与g_fm_protect_en,实现上位机图形化界面的同步实时显示。
3.2 故障码定义表
在 bsp_fault.h 中定义的全局统一故障码如下表所示:
故障码 (code) |
宏定义标识符 | 触发源与故障物理含义 | 恢复策略 |
|---|---|---|---|
| 0 | FAULT_NONE |
系统正常,无任何故障锁存 | N/A |
| 1 | FAULT_STALL |
电机堵转 / 霍尔信号异常失效 / 无法起动 | 停机检查负载,串口发送 r 清障 |
| 2 | FAULT_OVERCURRENT |
软件检测相电流超过安全阈值 MOTOR_OVERCURRENT_MA |
检查负载及电流环 PID 参数后 r 清障 |
| 3 | FAULT_BOOT_TIMEOUT |
自举电容预充电或对齐启动(Boot)过程超时 | 检查母线供电及预充电逻辑后 r 清障 |
| 4 | FAULT_UNDERVOLTAGE |
母线电压低于安全下限 FAULT_VBUS_UV_V |
恢复母线电压至正常范围后 r 清障 |
| 5 | FAULT_OVERTEMP |
功率板 NTC 传感器温度超过上限 FAULT_TEMP_OT_C |
待功率板冷却至安全温度以下后 r 清障 |
| 6 | FAULT_PHASE_LOSS |
电机相线开路 / 接线脱落 / 启动扇区卡死 | 检查电机三相线连接后发送 r 清障 |
| 7 | FAULT_HW_OCP |
驱动芯片硬件过流比较器触发,且 SD 重试超限 | 彻底排查功率 MOS 及电机短路后 r 清障 |
四、核心代码架构与关键模块实现
4.1 核心数据结构与头文件 (bsp_fault.h)
完整定义故障枚举、保护参数阈值以及外部接口函数:
c
#ifndef __BSP_FAULT_H
#define __BSP_FAULT_H
#include <stdint.h>
#include <stdbool.h>
/* -------------------------------------------------------------------------- */
/* 故障码定义 */
/* -------------------------------------------------------------------------- */
typedef enum {
FAULT_NONE = 0, /* 正常状态 */
FAULT_STALL = 1, /* 电机堵转 */
FAULT_OVERCURRENT = 2, /* 软件相电流过流 */
FAULT_BOOT_TIMEOUT = 3, /* 启动/预充电超时 */
FAULT_UNDERVOLTAGE = 4, /* 母线欠压 */
FAULT_OVERTEMP = 5, /* 功率板过温 */
FAULT_PHASE_LOSS = 6, /* 相线缺相 / 开路卡死 */
FAULT_HW_OCP = 7 /* 硬件 OCP 锁存 */
} FaultCode_e;
/* -------------------------------------------------------------------------- */
/* 保护参数阈值配置 */
/* -------------------------------------------------------------------------- */
#define FAULT_HW_BKIN_EN 0 /* 0:关闭TIM1_BKIN硬中断, 1:开启 */
#define FAULT_HW_OCP_MAX_RECOVER 3 /* SD 硬件过流最大重试恢复次数 */
#define FAULT_VBUS_UV_V 18.0f /* 母线欠压阈值 (V) */
#define FAULT_TEMP_OT_C 85.0f /* 板载过温阈值 (°C) */
/* 缺相检测参数 */
#define FAULT_PHASE_LOSS_OPEN_MA 150 /* 判定为开路的电流上限阈值 (mA) */
#define FAULT_PHASE_LOSS_HIT_RATIO 0.70f /* 窗口内开路判定命中率阈值 */
#define FAULT_PHASE_LOSS_HOLD_MS 250 /* 缺相检测统计时间窗口 (ms) */
#define FAULT_PHASE_LOSS_IQ_MIN_MA 800 /* 启动缺相检测的最低 Iq 指令电流 (mA) */
/* 扇区卡死兜底参数 */
#define FAULT_PHASE_STUCK_MS 1000 /* 运行状态下扇区卡死超时时间 (ms) */
#define FAULT_PHASE_STUCK_BOOT_MS 1200 /* 启动过程(DRAG/ALIGN)卡死超时时间 (ms) */
#define FAULT_PHASE_STUCK_VABS_V 4.0f /* 卡死判定电压阈值 |Vd|+|Vq| (V) */
#define FAULT_PHASE_STUCK_IABS_MA 100 /* 卡死判定最高实测相电流阈值 (mA) */
/* -------------------------------------------------------------------------- */
/* 全局接口与变量 */
/* -------------------------------------------------------------------------- */
extern volatile uint8_t g_fm_fault_code; /* 供 FreeMASTER 监视的故障码 */
extern volatile uint8_t g_fm_protect_en; /* 保护总使能标志 */
void Fault_Init(void);
void Fault_Poll(void);
void Fault_Raise(FaultCode_e code, const char *reason);
void Fault_Clear(void);
bool Fault_IsLatched(void);
FaultCode_e Fault_GetCode(void);
#endif /* __BSP_FAULT_H */
4.2 统一故障状态机与监控轮询实现 (bsp_fault.c)
c
#include "bsp_fault.h"
#include "bsp_motor_tim.c"
#include "bsp_pid_app.h"
#include <stdio.h>
/* 私有状态变量 */
static volatile FaultCode_e s_fault_code = FAULT_NONE;
static volatile bool s_is_latched = false;
volatile uint8_t g_fm_fault_code = 0;
volatile uint8_t g_fm_protect_en = 1;
/**
* @brief 初始化故障保护模块
*/
void Fault_Init(void)
{
s_fault_code = FAULT_NONE;
s_is_latched = false;
g_fm_fault_code = 0;
g_fm_protect_en = 1;
}
/**
* @brief 触发故障并实施首故障锁存
* @param code: 故障代码
* @param reason: 触发原因字符串
*/
void Fault_Raise(FaultCode_e code, const char *reason)
{
if (!g_fm_protect_en) return;
/* 首故障锁存原则:若已有故障处于锁存状态,不再被后续故障覆盖 */
if (!s_is_latched) {
s_is_latched = true;
s_fault_code = code;
g_fm_fault_code = (uint8_t)code;
/* ------------------ 执行硬件及控制回路切断动作 ------------------ */
Motor_PWM_Disable(); /* 强制关闭 PWM 上下桥臂 */
PID_Reset_All(); /* 复位 PID 控制器及积分项 */
FOC_Boot_Abort(); /* 中止启动及预充电流程 */
LED2_Control(LED_STATE_BLINK); /* LED2 闪烁指示故障 */
printf("\r\n[FAULT LATCHED] Code:%d, Reason:%s\r\n", code, reason ? reason : "UNK");
}
}
/**
* @brief 清除故障锁存(响应串口 'r' 指令)
*/
void Fault_Clear(void)
{
s_is_latched = false;
s_fault_code = FAULT_NONE;
g_fm_fault_code = 0;
PID_Reset_All();
LED2_Control(LED_STATE_OFF);
printf("[FAULT] Cleared. System Reset to OK.\r\n");
}
bool Fault_IsLatched(void) { return s_is_latched; }
FaultCode_e Fault_GetCode(void) { return s_fault_code; }
/* -------------------------------------------------------------------------- */
/* 内部检测子函数 */
/* -------------------------------------------------------------------------- */
/**
* @brief 缺相及扇区卡死兜底检测逻辑(只信硬件实测相)
*/
static void fault_check_phase_loss(void)
{
static uint32_t window_start_tick = 0;
static uint32_t total_samples = 0;
static uint32_t hit_samples = 0;
static uint32_t stuck_start_tick = 0;
if (s_is_latched) return;
/* 获取当前系统的关键运行参数 */
float v_bus = Motor_GetVbus();
float i_q_ref = Motor_GetIqRef();
float v_d = Motor_GetVd();
float v_q = Motor_GetVq();
int32_t rpm = Motor_GetRpm();
uint8_t pair = Motor_GetADC12_SamplePair(); /* 0:UV, 1:VW, 2:WU */
int16_t i_u_raw = Motor_GetIuRaw();
int16_t i_v_raw = Motor_GetIvRaw();
int16_t i_w_raw = Motor_GetIwRaw();
uint32_t now = HAL_GetTick();
/* ----------------路径 2:转子完全卡死 (rpm == 0, 扇区冻结) 兜底------------- */
float v_mag = sqrtf(v_d * v_d + v_q * v_q);
bool is_booting = FOC_Boot_IsActive();
uint32_t stuck_limit = is_booting ? FAULT_PHASE_STUCK_BOOT_MS : FAULT_PHASE_STUCK_MS;
if (rpm == 0 && (v_mag > FAULT_PHASE_STUCK_VABS_V) && (fabsf(i_q_ref) > FAULT_PHASE_LOSS_IQ_MIN_MA)) {
if (stuck_start_tick == 0) {
stuck_start_tick = now;
} else if (now - stuck_start_tick > stuck_limit) {
Fault_Raise(FAULT_PHASE_LOSS, "Phase/stall: stuck, hall frozen & Vout high");
stuck_start_tick = 0;
return;
}
} else {
stuck_start_tick = 0;
}
/* ----------------路径 1:电机处于旋转状态(仅对硬件实测相记账)--------------- */
if (fabsf(i_q_ref) < FAULT_PHASE_LOSS_IQ_MIN_MA) {
window_start_tick = now;
total_samples = 0;
hit_samples = 0;
return;
}
total_samples++;
/* 根据当前 pair 提取真实的 ADC 硬件采样相,排除软件重构相 */
int16_t meas_1 = 0, meas_2 = 0;
switch (pair) {
case 0: meas_1 = i_u_raw; meas_2 = i_v_raw; break; /* 仅采 U, V */
case 1: meas_1 = i_v_raw; meas_2 = i_w_raw; break; /* 仅采 V, W */
case 2: meas_1 = i_w_raw; meas_2 = i_u_raw; break; /* 仅采 W, U */
default: break;
}
/* 开路判据:实测的两相中,一相电流近乎为零,而另一相有明显电流 */
if ((abs(meas_1) < FAULT_PHASE_LOSS_OPEN_MA && abs(meas_2) > FAULT_PHASE_LOSS_OPEN_MA) ||
(abs(meas_2) < FAULT_PHASE_LOSS_OPEN_MA && abs(meas_1) > FAULT_PHASE_LOSS_OPEN_MA)) {
hit_samples++;
}
/* 统计 250ms 窗口内的命中率 */
if (now - window_start_tick >= FAULT_PHASE_LOSS_HOLD_MS) {
if (total_samples > 50) {
float ratio = (float)hit_samples / (float)total_samples;
if (ratio >= FAULT_PHASE_LOSS_HIT_RATIO) {
Fault_Raise(FAULT_PHASE_LOSS, "Phase loss: open-circuit detected on measured channel");
}
}
window_start_tick = now;
total_samples = 0;
hit_samples = 0;
}
}
/**
* @brief 主循环故障轮询总入口
*/
void Fault_Poll(void)
{
if (s_is_latched || !g_fm_protect_en) return;
/* 1. 母线欠压检测 */
if (Motor_GetVbus() < FAULT_VBUS_UV_V) {
static uint32_t uv_tick = 0;
if (uv_tick == 0) uv_tick = HAL_GetTick();
else if (HAL_GetTick() - uv_tick > 200) {
Fault_Raise(FAULT_UNDERVOLTAGE, "Bus voltage below 18V threshold");
uv_tick = 0;
}
} else {
uv_tick = 0;
}
/* 2. 功率板过温检测 */
if (Motor_GetBoardTemp() > FAULT_TEMP_OT_C) {
Fault_Raise(FAULT_OVERTEMP, "Board NTC temperature over 85C");
}
/* 3. 缺相及扇区卡死检测 */
fault_check_phase_loss();
/* 4. SD 驱动过流重试恢复监测 */
if (Driver_SD_IsFaulted()) {
static uint8_t recover_cnt = 0;
if (recover_cnt < FAULT_HW_OCP_MAX_RECOVER) {
recover_cnt++;
Driver_SD_RecoverPulse(); /* 发送 SD 解锁恢复脉冲 */
} else {
Fault_Raise(FAULT_HW_OCP, "Hardware OCP: SD recover limit exceeded");
recover_cnt = 0;
}
}
}
五、调试命令、验证矩阵与工程最佳实践
为保证保护逻辑方便调试,系统整合了串口指令集与上位机 FreeMASTER 配合的调试手段。
5.1 串口指令与 FreeMASTER 监控变量
串口常用指令
text
fc : 查询当前系统故障状态机 (显示 state, code, stuck 统计信息)
r : 清除故障闩锁,复位 PID 积分项与系统状态 (Fault Reset)
s 0 : 紧急停止电机 (Stop)
pid : 获取当前运行瞬时快照 (电流, 电压, PWM 占空比, 角度)
log 1 / 0 : 打开 / 关闭周期性状态调试日志
FreeMASTER 核心监控表
在 FreeMASTER 中拉出以下变量观察 Scope 波形:
text
g_fm_fault_code : [FLOAT] 全局当前故障码 (0:NONE, 1:STALL, 2:OC, 4:UV, 6:PHASE_LOSS...)
g_fm_protect_en : [FLOAT] 保护全局使能 (1:开启, 0:强制关闭)
g_fm_target_iq : [FLOAT] Iq 目标指令电流 (mA)
g_fm_iq_actual : [FLOAT] Iq 实际反馈电流 (mA)
g_fm_vd, g_fm_vq : [FLOAT] 控制器输出的 d/q 轴电压 (V)
g_fm_sample_pair : [FLOAT] 当前拍硬件采集对 (0:UV, 1:VW, 2:WU)
g_fm_ph_peak_u/v/w: [FLOAT] 缺相窗内实测相峰值 (mA);开路相应远小于另外两相
以上均定义在
User/freemaster/bsp_fm_vars.h。缺相峰值在 ADC 注入中断里累计,主循环只做窗口判决。
5.2 故障注入实验验证矩阵
在系统调试阶段,必须依次进行针对性的故障注入(Fault Injection)实验,以确保各防护机制触发符合预期:
安全警示 :做过流、堵转及缺相拔线测试时,必须将直流电源的限流设置为小电流(如 1.5A~2A),且实验持续时间控制在秒级以内,防止功率 MOSFET 热击穿。
| 实验项 | 故障注入操作 | 预期打印与日志 | 硬件/软件动作 | 复位与恢复流程 |
|---|---|---|---|---|
| 1. 正常运行回归 | 三相接好:t 500 → w 1 |
fc 显示 state=OK, code=0 |
电机平稳运转 | s 0 |
| 2. 软件过流 (OC) | 临时调低 MOTOR_OVERCURRENT_MA 或突加负载 |
Overcurrent: ... → FAULT LATCHE: code=2 |
关 PWM,LED2,g_fm_fault_code=2 |
s 0 → r → 恢复阈值 → w 1 |
| 3. 母线欠压 (UV) | 电源从 24 V 拉低至约 17.5 V | Undervoltage: ... → code=4 |
约 200 ms 后关 PWM | 升回 24 V → r |
| 4. 转子堵转 (Stall) | 见下方 §5.2.2 实录步骤 | Stall timeout → FAULT LATCHE: code=1 |
关 PWM;霍尔长时间无边沿后闩锁 | 释放转子 → s 0 → r → w 1 |
| 5. 启动前缺相(推荐) | 见下方 §5.2.1 实录步骤 | Phase loss: W open? peak=... → code=6 |
进 RUN 后约 0.25~1 s 闩锁停机 | 插回相线 → s 0 → r → w 1 |
| 6. 运行中拔相 | 限流下运转时拔掉一相 | 同上 Phase loss: ? open? → code=6 |
limp 仍可能转片刻,随后停机 | 插回 → s 0 → r → w 1 |
| 7. 启动卡死缺相 | 拔相后完全转不动(霍尔冻结) | Phase/stall: hall frozen=... → code=6 |
约 0.6~1 s 卡死兜底闩锁 | 插回 → s 0 → r → w 1 |
5.2.1 启动前缺相实测(已验证,符合预期)
现象说明 :官方路径为 charge → offset → 直接 RUN(无 Align/Drag)。缺相后两相仍可能 limp 起转,保护目标是进闭环后尽快报 PHASE_LOSS 并停机,而不是保证"完全转不起来"。
步骤(限流 1.5~2 A):
text
1. 断电或停机后,拔掉电机 W 相线(U/V 保持连接)
2. 串口:
r
s 0
t 400
w 1
期望日志(与实机一致):
text
FOC boot: offset done -> hall+Iq run (no align, official path)
FOC boot handover: hall=... rpm=0 Iq=... (hall+PI)
FOC run: Iq handover ...
Phase loss: W open? peak=3852/2270/246 samp=2582/2311/2603
FAULT LATCHE: code=6 (PHASE_LOSS)
如何读 peak=U/V/W:
| 项 | 含义 |
|---|---|
peak 前两相很大、第三相很小 |
该相开路(上例 W=246 ≪ U/V) |
| 进 RUN → 报警间隔 | 约一个 FAULT_PHASE_LOSS_HOLD_MS(默认 250 ms)量级 |
| FreeMASTER | 可同步看 g_fm_ph_peak_u/v/w、g_fm_fault_code=6 |
恢复: 插回 W → s 0 → r → t 400 → w 1,应能正常爬速且 fc 为 OK。
5.2.2 转子堵转实测(已验证,符合预期)
判据 :霍尔长时间无有效边沿时,TIM1 Update 累计超过 MOTOR_STALL_PWM_PERIODS(默认 4000),且已过 MOTOR_STALL_STARTUP_MS(默认 1500 ms)启动宽限 → FAULT_STALL。计时只在霍尔边沿清零,不在改占空比/CCR 时清零。
步骤(限流 1.5~2 A,三相接好):
text
1. 串口:
r
s 0
t 400
w 1
2. 电机开始转动后,用手(或工具)短时卡住转子,保持到报警
期望日志(与实机一致):
text
FOC boot: offset done -> hall+Iq run (no align, official path)
FOC boot handover: hall=4 rpm=0 Iq=1600 Vq=+2.50 th=210 (hall+PI)
FOC run: Iq handover RPM=55 Iq=1600 mA (hall+PI)
...(卡住转子,霍尔边沿停止)...
Stall timeout
FAULT LATCHE: code=1 (STALL)
| 项 | 说明 |
|---|---|
| 关键代码 | bsp_motor_tim.c → HAL_TIM_PeriodElapsedCallback / motor_stall_poll() |
| 故障码 | code=1(FAULT_STALL),fc 显示 LATCHED |
| 耗时 | 含启动宽限 + 卡住时机;实录从 handover 到报警约数秒~十余秒属正常 |
| 注意 | 卡住时务必限流,时间尽量短,避免功率管过热 |
恢复: 松开转子 → s 0 → r → t 400 → w 1,应能正常再起。
注意 :故障闩锁后必须先
r清障再w 1。若只发w 1不发r,旧逻辑会再次启动但二次堵转只打Stall timeout却不再停机;现已改为:未清障时拒绝w 1,且已闩锁时fault_raise仍会强制停机。
六、总结与后续演进
本章通过设计自研的 bsp_fault 保护体系,将原本散落在驱动各处的硬件过流(SD)、软件过流、欠压、过温、堵转以及复杂的开路缺相检测,收束为统一抽象、首故障锁存、可串口查询、可复位恢复的状态机架构(Fault FSM)。
在后续的无感 FOC(Sensorless FOC,如观测器 SMO / Flux-Observer)开发中,缺相与卡死兜底检测逻辑依然可以无缝复用,只需将霍尔信号冻结判据替换为观测器收敛状态与反电动势(Observed BEMF)幅值即可,从而为高可靠性的驱动开发奠定坚实的底层安全架构。