电机驱动开发学习23. 保护体系与故障状态机

电机驱动开发学习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))
  • 五、调试命令、验证矩阵与工程最佳实践
    • [5.1 串口指令与 FreeMASTER 监控变量](#5.1 串口指令与 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 并计算,功率器件可能已经击穿烧毁。

硬件防护的典型响应路径如下:

  1. 驱动芯片内部比较器 / OCP 硬件电路 在检测到母线或相电路过流时,第一时间将硬件 SD(Shut Down,如 PG12 脚)引脚拉低,强行关闭栅极驱动信号。
  2. 软件故障检测层 在主程序或周期定时器中检测到"控制器输出电压指令巨大( V d , V q V_d, V_q Vd,Vq 高),但相电流实测值趋近于零 "或直接读取到 SD 引脚异常电平时,判定驱动触发了硬件过流。
  3. 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.cfault_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.cfault_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.cfault_check_overtemp()FAULT_TEMP_OT_*
转子堵转 (Stall) STALL 霍尔接口长时间无捕获边沿 TIM 捕获中断 / 轮询 启动宽限期后计时 bsp_motor_tim.cmotor_stall_poll()
开路缺相 (Phase) PHASE_LOSS 实测相窗口峰值≈0(limp 仍转);或霍尔冻结+零速 仅统计 ADC 实测相 250 ms 峰值窗 / 卡死约 0.6~1 s bsp_fault.cfault_check_phase_loss()FAULT_PHASE_*
硬件过流闩锁 HW_OCP 重试恢复次数达到上限 SD 状态与重试计数器 重试超限(3次)立刻闩锁 bsp_fault.cfault_check_hw_ocp() / fault_on_hw_break()

统一调度:fault_poll();闩锁入口:fault_raise()。阈值调参说明见 bsp_fault.h 头注释。

当任何一项软件判据成立时,系统将调用统一的入口函数 fault_raise(code)

  1. 立即停止速度环与电流环计算,复位 PID 积分项;
  2. 中止当前 Bootstrap 预充电及启动流程(Abort Boot);
  3. 强制关闭 PWM 输出(把上下桥臂栅极关断);
  4. 点亮 / 闪烁 LED2 报警指示灯;
  5. 将故障码写入 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 相轴线方向的脉动磁场。由于脉动磁场可以分解为正转与反转两个旋转磁场,在转子已有初始转速或负载极轻的情况下,转子在惯性与正向旋转分量的作用下依然能够维持旋转(但伴有剧烈的转矩脉动、异响与发热)。

因此:

  1. 电机还能转动,绝对不能证明三相接线完好
  2. 电机转不动,也不等于软件已经识别到了缺相------它仅仅表明产生的合力矩不足以克服静态阻力矩。

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 状态;不要依赖第三相数据判断硬件完好
pair0 ↔ 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 核心设计原则

  1. 首故障优先锁存(First-Fault Latching)
    当系统触发第一个故障(如欠压 UNDERVOLTAGE)并进入 FAULT_LATCHED 状态后,后续由停机过程引发的次生异常(如停转导致的堵转 STALL不会覆盖初始故障码。这确保了调试人员在查看故障日志时能够准确定位最初的真实诱因。
  2. 硬件/软件联动切断
    一旦进入 FAULT_LATCHED,状态机立刻调用底层的硬件关闭接口(关闭 PWM、切断桥臂使能、停止 PID 计算),杜绝故障扩大化。
  3. 全域变量同步
    状态机实时维护 FreeMASTER 全局变量 g_fm_fault_codeg_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 500w 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 0r → 恢复阈值 → w 1
3. 母线欠压 (UV) 电源从 24 V 拉低至约 17.5 V Undervoltage: ...code=4 约 200 ms 后关 PWM 升回 24 V → r
4. 转子堵转 (Stall) 见下方 §5.2.2 实录步骤 Stall timeoutFAULT LATCHE: code=1 关 PWM;霍尔长时间无边沿后闩锁 释放转子 → s 0rw 1
5. 启动前缺相(推荐) 见下方 §5.2.1 实录步骤 Phase loss: W open? peak=...code=6 进 RUN 后约 0.25~1 s 闩锁停机 插回相线 → s 0rw 1
6. 运行中拔相 限流下运转时拔掉一相 同上 Phase loss: ? open?code=6 limp 仍可能转片刻,随后停机 插回 → s 0rw 1
7. 启动卡死缺相 拔相后完全转不动(霍尔冻结) Phase/stall: hall frozen=...code=6 约 0.6~1 s 卡死兜底闩锁 插回 → s 0rw 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/wg_fm_fault_code=6

恢复: 插回 W → s 0rt 400w 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.cHAL_TIM_PeriodElapsedCallback / motor_stall_poll()
故障码 code=1FAULT_STALL),fc 显示 LATCHED
耗时 含启动宽限 + 卡住时机;实录从 handover 到报警约数秒~十余秒属正常
注意 卡住时务必限流,时间尽量短,避免功率管过热

恢复: 松开转子 → s 0rt 400w 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)幅值即可,从而为高可靠性的驱动开发奠定坚实的底层安全架构。

相关推荐
别动我齐刘海2 小时前
Day6 unitree_G1人形机器人GMR—— MotionInput
c语言·c++·人工智能·学习·机器学习·机器人·github
zzzll11114 小时前
0基础入门大模型:一份清晰的学习路线图
人工智能·学习·chatgpt
正经人_x4 小时前
学习日记48:DenseCLIP
学习
DevangLic5 小时前
monkeycode-使用说明
学习·ai
red_redemption6 小时前
自由学习记录(211)
学习
ThsPool7 小时前
【ENVI二次开发学习整理 02】IDL语言基础与程序设计
学习·中间件
一只小菜鸡..7 小时前
南京大学 操作系统 (JYY) 学习笔记:文件系统实现——从 FAT、ext2 到现代 B-Tree 黑科技
笔记·科技·学习
深爱水瓶 血影S狂风7 小时前
Linux.NET学习手记(2)
linux·学习·.net
明明真系叻8 小时前
RAG学习(1)
学习·rag