ST电机库学习记录——触发HardFault
2026/8/8 8:00:32 网站建设 项目流程

使用15V电压供电,电机能正常启动,电压提高到16V时就会进入HardFault_Handler

开始怀疑可能是:电压升高后,启动瞬间的相电流/电流尖峰增大,触发了硬件过流保护;随后中断处理或嵌套调度导致真正的CPU HardFault。

16 V启动 → 电流尖峰触发 COMP1/2/4 → TIM1_BRK_M1_IRQHandler → 在Break中断里再次调用 MC_RunMotorControlTasks() → ADC/FOC中断嵌套、栈压力或非法访问 → HardFault_Handler

内存窗口中看到的0x0800566B位于工程映射文件中的:

R3_2_CurrentReadingPolarization()

附近。因此目前最可疑的路径是:

电流偏置校准 / PWM电流采样 → R3_2_CurrentReadingPolarization() → 电流采样或COMP过流触发 → 内存/栈/中断上下文异常 → HardFault

调试发现

SCB->CFSR = 0x00000100 SCB->HFSR = 0x40000000
  • CFSR bit 8 = IBUSERR:指令总线错误;
  • HFSR.FORCED = 1:BusFault升级成了HardFault;
  • PRECISERR = 0BFARVALID = 0:不是一个可定位到具体数据地址的普通数据访问错误;
  • SCB->BFAR = 0xE000EDF8目前无效,因为BFARVALID没有置位,不能把它当作真实故障地址。

出现新方向:按键干扰,由于

VECTPENDING = 0x29

根据该芯片的中断向量表,0x29对应:

EXTI15_10_IRQn

也就是PC13启动/停止按键中断当前处于Pending状态。16 V时如果功率级噪声耦合到PC13,可能会产生按键误触发。

改为程序自动触发,可能性排除

系统能够在16-18V稳定启动

星期一13:54

已处理 1m 52s

目前16–18 V能够稳定启动,主要得益于以下修改。

移除Break中断中的重入调度

文件:

[stm32g4xx_mc_it.c]

删除了:

MC_RunMotorControlTasks();

原来过流/过压Break中断中会再次调用完整电机状态机,可能与ADC电流环、SysTick任务嵌套,造成栈或异常现场破坏。

缩小问题发生时间和范围

通过已确认稳定的按键或串口启动。不再出现HardFault,问题集中在“上电后立即启动”的时序,而不是电机运行阶段。

当前最可能的链路是:

MC_StartMotor1() → MCI_StartMotor() → 直接进入启动状态 → 中断/初始化时序冲突或异常栈损坏 → 非法PC 0xA7F708F4 → HardFault

自动启动命令已经正确执行,故障发生在START启动阶段,而不是按键或MCI句柄初始化阶段。

由于

gHardFaultStackPC = 0x07F6F734 gHardFaultStackLR = 0x07F739CD

这两个地址都不在Flash区0x080xxxxx,说明CPU在进入START后发生了非法取指,异常栈中的返回地址已经被破坏。

怀疑栈空间不足

Stack_Size EQU 0x1000

即将栈从1 KB增加到4 KB。有一定效果。异常栈已经恢复正常:

gHardFaultCFSR = 0x00000100 gHardFaultStackPC = 0x08004450 gHardFaultStackLR = 0x0800448B gHardFaultStackXPSR = 0x41000000

地址映射为:

0x08004450 → MCI_GetSTMState() 0x0800448B → MCI_StartMotor() 内部

因此这次不是非法函数指针,也不像栈溢出。当前是:

CFSR = 0x100 → IBUSERR,指令总线错误

也就是CPU在Flash中执行MCI_GetSTMState()附近代码时,发生了指令取指错误。BFARVALID没有置位,所以不是数据访问地址错误。

锁定问题:CPU 取指令时发生总线错误,开始查询发生原因

还有一个线索宏定义#define M1_CHARGE_BOOT_CAP_DUTY_CYCLES ((uint32_t)0.000\是自举电容充电占空比,初始值是0,当调大他的数值时系统就不会进入HardFault_Handler,而是稳定触发过流保护:说明问题集中在“启动初期低侧导通/自举充电”阶段,而不是正常运行阶段。

  • FOC 已完整执行;
  • 高频任务已返回;
  • ADC 中断主体已完成;
  • PWM 回调指针预检查正常;

ADC 注入转换状态没有及时清除,TIM1 更新中断卡在:

while (ADCDataReg1[...]->JSQR != 0) {}

这会阻塞 TIM1 ISR,造成后续中断时序异常,最终出现 HardFault。

FOC、高频任务和 Break ISR 都执行过,但随后仍发生未定义指令。更重要的是:刚才加入的 ADC 超时保护在超时后直接返回,这会跳过后续。如果 ADC 等待确实超时,PWM 更新中断就不会重新配置 ADC 触发,电机自然没有启动意向。也就是说,这次“更稳定但电机不启动”很可能是超时保护把启动链路截断了。

重新测试发现故障不是 ADC 超时,由于发生了未对齐访问。故障地址0x0800968E对应 OPAMP 初始化时的STRD指令,该指令要求目标地址 8 字节对齐,但诊断变量改变了全局变量布局,使hopamp1/hopamp2只满足 4 字节对齐。

后经过设置变量验证,说明不是 OPAMP 句柄未对齐,而是异常栈/返回现场再次损坏。

观察到电机启动后的中断活动导致HAL_GetTick()访问地址异常

  1. 按键启动后,定时器中断确实继续运行了约0xD9E次;
  2. FOC 已经完整执行到0x1E
  3. 没有检测到回调指针错误;
  4. ADC 等待没有超时;
  5. 栈只下降了 8 字节,不像栈溢出;
  6. 故障每次都落在同一个位置,具有确定性。

故障现场仍然是:

gHardFaultStackPC = 0x080023D2 -> HAL_GetTick() gHardFaultStackLR = 0x08002171 -> HAL_Delay() gHardFaultStackR0 = 0xF8802201 BFAR = 0xF8802201

这里不能理解为“HAL_Delay()本身有问题”。真正异常是:执行HAL_GetTick()时,CPU试图访问0xF8802201

查询官方 MCSDK 的启动路径

MC_StartMotor1() → MCI_StartMotor() → SysTick 中的 MC_RunMotorControlTasks() → TSK_MediumFrequencyTaskM1() → IDLE → R3_2_TurnOnLowSides() → CHARGE_BOOT_CAP(约10 ms) → OFFSET_CALIB / START → PWMC_SwitchOnPWM() → TIM1_UP / ADC1_2 中断 → FOC_HighFrequencyTask()

加入启动过程快照

gStartTraceStage变量:

1:收到按键启动请求之前的快照 2:MC_StartMotor1() 返回之后 3:进入 IDLE 启动分支,准备低侧导通 4:自举电容充电结束,准备切换 5:调用 PWMC_SwitchOnPWM() 之前 6:PWMC_SwitchOnPWM() 返回之后
  • 停在stage = 1:启动请求没有被正常消费。
  • 停在stage = 3:低侧导通或状态机没有继续。
  • 停在stage = 4:自举充电、READY/NFAULT 或定时器状态异常。
  • 停在stage = 5:重点怀疑pFctSwitchOnPwm函数指针或 PWM 句柄。
  • 到达stage = 6后卡住:重点检查首次 TIM1/ADC 中断和电流采样。

经过测试,启动前 / 自举充电 / PWM首次打开等过程已排除,范围缩小到START 状态进入后的 Rev-Up / FOC 高频中断阶段

检查START 状态进入后问题发生位置

加入变量后调试发现

  • 按键请求已正常进入状态机;
  • 自举充电已完成;
  • PWMC_SwitchOnPWM()已返回;
  • READY/NFAULT 没有触发保护;
  • PWM 函数指针没有异常。
  • FOC 环仍然在运行,但给出的Iq转矩参考为零或接近零。此时电机可能只得到很小的电压矢量,没有形成足够启动转矩。
  • 同时存在异常栈/返回地址破坏

发现StackPC=0x50519178不是有效代码地址,XPSR也不像正常 Cortex-M 异常栈帧。这说明 HardFault 发生时,异常栈或返回地址已经被破坏,不能再把StackPC当作可靠的故障源码位置。

怀疑硬件过流导致出现问题

启动时的实际路径是:

分流电阻电压 ↓ COMP1/2/4 比较器 ↓ TIM1 BKIN ↓ TIM1 Break 中断 ↓ TIMx_BRK_M1_IRQHandler() ↓ PWMC_OCP_Handler() ↓ 关闭 PWM,置位 OverCurrentFlag

这一条路径中,COMP 比较器是硬件异步工作的,ADC 软件滤波无法阻止它触发。

因此:

  • 软件 ADC 滤波只能改善 FOC 电流采样;
  • 不能阻止 COMP 硬件过流;
  • HAL_Delay()或等待也不能阻止硬件 Break;
  • 真正要抑制误触发,需要比较器迟滞、消隐、数字滤波、RC 滤波或改善功率级振铃。

先区分“真实硬件过流”和“过流中断后软件崩溃”

  1. 在 Break 中断入口、清除 TIM1 标志之前,锁存:

    • TIM1SR/BDTR/AF1/AF2
    • COMP1/2/4CSR
    • DAC3DOR1/DOR2
    • GPIOEIDR
    • 当时的 CFSR/HFSR
  2. 增加 OCP 回调指针检查。

    如果pFctSwitchOffPwm地址异常,则:

    • 不再调用错误函数指针;
    • 直接关闭 TIM1 主输出;
    • 设置过流标志;
    • 记录gBreakTraceGuardFlags
    • 避免在 Break 中断中再次 HardFault。
  3. 加固 HardFault 诊断代码。

    现在会先验证 MSP、PSP、faultFrame 和 PWM 句柄地址,再读取内容,避免 HardFault 处理函数因读取坏指针造成二次故障。

  4. 本次没有直接增加 ADC 软件滤波或大段延时,因为 COMP 过流保护是硬件异步路径,软件滤波无法阻止它,而且可能掩盖真实的功率级振铃问题。

测试发现:不是过流保护触发,而是 CPU 的未对齐访问故障。启动后某个指针、结构体地址或栈数据被破坏,导致未对齐访问;不是 ADC 数值本身直接触发 HardFault。

判断是中频任务执行时出了问题还是电流ADC转换完成中断完成后的高频任务出的冲突

变量当前值结论
gDiagLastContext0x1F最后记录的是 ADC 转换完成中断正常结束
gDiagFOCStage0x1EFOC 高频任务已执行到阶段 30,主体已返回
gDiagHFStage4高频任务已完成
gHardFaultFrameValid0HardFault 堆栈帧已经损坏或无效
gHardFaultStackPointer0x10880021不在 SRAM,属于异常指针
gBreakTraceCount启动前后均为1启动瞬间没有新的 Break/OCP 事件

因此:

  1. 当前没有证据表明是中频任务直接触发。
  2. ADC 高频任务本体已经执行完成。
  3. 更像是高频中断返回阶段、异常返回阶段,或此前发生了栈/指针破坏。
  4. 目前无法排除“中频任务进入前后”的边界问题,因为原有gDiagLastContext不够细。

加入变量gDiagMFStage:

1 进入中频调度 2 开始处理启动/停止命令 3 启动/停止命令处理完成 4 即将进入 TSK_MediumFrequencyTaskM1 5 中频任务及回调完成 6 即将进入安全任务 7 中频调度完成 0xE1 运行完整性检查失败 0xE2 MCboot 尚未完成

不是中频任务出错,而是:

中频任务已经完成,随后 ADC 转换完成中断中的高频 FOC 已完成;故障更可能发生在 ADC 高频中断退出、异常返回,或此前已经发生的栈/指针破坏。

屏蔽高频任务,看看电机启动后中频任务是否有问题

ADC 转换中断仍然运行,但不会调用:
TSK_HighFrequencyTask();
同时会清除:
TIM1->BDTR.MOE
因此这次测试中电机不会真正输出 PWM 旋转,只用于验证中频任务和状态机。重新启用时将:
gDiagDisableHFTask = 0;

屏蔽高频任务后:

  • ADC 中断仍在运行。
  • 中频任务持续执行并正常返回。
  • 中频任务没有引起 HardFault。
  • 原来的 HardFault 需要高频 FOC 任务或其与 PWM/ADC 的交互才会出现。

排除:

  • 中频任务本身直接触发 HardFault;
  • SysTick 中频调度栈溢出;
  • 本次测试中的硬件 Break/OCP 触发。

高频任务问题判断

修改代码实现

  • 高频 FOC 继续执行;
  • ADC 电流采样继续执行;
  • FOC 运算继续执行;
  • 跳过PWMC_SetPhaseVoltage()
  • 每次强制清除TIM1->BDTR.MOE,禁止实际 PWM 输出。

测试后发现

  • 高频任务确实在执行;
  • FOC 高频任务主体能够完成;
  • 中频任务也能够完整执行;
  • 最后进入FAULT_OVERPastFaults=0x0010MC_START_UP启动失败,不是 HardFault;
  • 因为 PWM 更新被屏蔽,启动失败是预期结果。

允许 PWM 更新重新测试

执行真实 PWM 后出现了指令总线故障。

从数据看:

  • gDiagPWMUpdateRunCount = 0x25:已实际执行 37 次PWMC_SetPhaseVoltage()
  • gDiagPWMUpdateLastError = 0:PWM 计算函数返回正常。
  • gDiagPWMStateBefore = 1:PWM 当时已开启。
  • gDiagPWMBDTRBefore/After = 0x2333BC3B:MOE 未被软件清除。
  • gDiagValpha = -10gDiagVbeta = 86:初始电压指令很小,没有明显越界。
  • gHardFaultFrameValid = 1gHardFaultFrameSource = 1:HardFault 堆栈帧有效,来自 MSP。

得到结论:电机启动、PWM 开始输出后,CPU 在取指令时发生 BusFault。

尝试软件上解决问题:滤除窄毛刺、保留真实过流保护、降低 24 V 启动冲击

  • COMP1/COMP2/COMP4 增加10 mV迟滞,减少比较器阈值附近振荡。
  • TIM1 实际使用的是Break1,已把滤波编码从3调整为8
    • 当前 170 MHz、CKD=DIV2 下约要求故障持续0.56 µs才触发。
    • 小于此宽度的开关毛刺会被滤除。
    • 持续过流和 STSPIN32G4NFAULT仍会立即关闭 PWM。

测试结果:启动后:

  • gDiagPWMUpdateRunCount=0x22:真实 PWM 更新成功运行了 34 次。
  • gDiagPWMUpdateStage=3PWMC_SetPhaseVoltage()已正常返回。
  • gDiagPWMUpdateLastError=0:占空比计算没有报告错误。
  • gDiagFOCStage=0x1E:整个 FOC 高频任务已经走到出口。
  • gDiagHFStage=4:高频任务完整返回。
  • gDiagLastContext=0x1F:ADC 中断已经执行到末尾。
  • Valpha=4、Vbeta=11:故障前并没有输出异常大的电压矢量。
  • gBreakTraceCountTIM1_SR、电机故障字均未变化。
  • gOcpFilterCode=8、COMP CSR=0x00010040:比较器迟滞和 Break 滤波已经生效。

测试后发现:不是比较器过流,且无法通过配置COMP解决,而是物理 PWM 开始输出后,ADC 高频中断完整执行若干次,随后在中断返回或紧邻位置发生非法取指。

阶段性总结

问题测试方式结果
按键误触发初始化后直接调用MC_StartMotor1()仍可复现
COMP/OPAMP重复初始化HAL_COMP_Init()等位置断点只在上电初始化进入
中频任务自身崩溃屏蔽高频任务,只运行中频状态机无 HardFault,只因无电流环进入预期故障状态
ADC/FOC计算本身高频任务运行,但禁止真实 PWM 更新可以持续运行
PWMC_SetPhaseVoltage()同步崩溃观察 Stage 和返回值Stage=3、Error=0,函数已经返回
软件电压矢量溢出记录Valpha/Vbeta故障前数值很小
硬件过流 Break比较 Break 计数、TIM1_SR、电机故障字启动前后均未变化
比较器阈值振荡加 10 mV 迟滞和 BKF=8配置已生效,但故障仍存在
普通栈溢出4 KB 栈、记录最小 MSP、栈保护标志未发现栈越界
明显空指针/外设实例错误检查 PWM、OPAMP 指针和运行保护标志地址合理、保护标志为 0
SysTick/HAL_GetTick根因屏蔽 PWM 时 SysTick长期运行正常不是独立根因
确定性的 MCSDK算法错误低电压稳定,同一程序随母线电压升高才出现不符合普通确定性算法错误特征

尝试不修改硬件的情况下能通过调整配置解决

目前现象:PWM能够输出几次才发生HardFault。

尝试一:完整执行状态机、ADC、FOC 和 CCR 更新,但禁止 TIM1.MOE,功率管不动作:

结论:24V 上电并启动。电机不转是正常现象。没有进入HardFault。

允许真实 PWM 边沿,但强制输出零电压矢量。

尝试二:允许真实 PWM 边沿,但强制输出零电压矢量。

结论锁定:HardFault 需要“真实功率级开关边沿”才会出现,与 FOC 输出电压大小、启动电流或按键无关。普通“降低FOC占空比”不能可靠解决,因为零矢量虽然三相线电压接近零,但三个桥臂仍以约50%占空比产生完整的24V开关边沿。降低调制量只降低电流,不降低每个边沿的24V幅度。

尝试三:把 STSPIN32G4 栅极驱动 VCC 从当前12V降到8V,保持20kHz和零矢量,观察边沿减缓后是否稳定。

8V测试结果无改善:配置及回读均成功,但仍在约0x22次FOC更新后HardFault,与12V时的0x23基本一致。

尝试四:栅极驱动保持8V。PWM从20kHz降至10kHz。

已确认这次三相零矢量测试仍会 HardFault:

  • gHardFaultCFSR = 0x1400:非精确 BusFault + 异常入栈失败。
  • MSP 已损坏为非法地址。
  • 10 kHz 只是让故障从约 34 次延后到 40 次 PWM 更新,不能解决问题。
  • 因此基本排除 FOC 电压矢量和电机电流本身;故障需要真实开关沿才能产生。

尝试五:仅 U/V 相半桥开关,8 V 栅极电源、10 kHz、零矢量、过流保护均保留。

没有进入HardFault 结论:单桥臂稳定运行0x40AD次,而三桥臂约0x28次就 HardFault,故障与多桥臂同步开关瞬态高度相关,不是过流保护或 FOC 运算错误。

尝试六:改为 U+V 双桥臂测试

直接进入HardFault :双桥臂正常运行时故障

尝试七:双桥臂错沿测试

  • U/V 两桥臂开启。
  • 两相比较沿相差128个定时器计数。
  • 8 V 栅极电源、10 kHz、零矢量不变。

错沿有效但尚未完全消除问题:

  • 原双桥臂同步换相通常约0x23次就故障。
  • 加入 64 tick 错沿后,多次可运行到0x37F0x40AD,说明故障概率已经显著下降。
  • 偶发 HardFault 的CFSR=0x00020000INVSTATE;异常帧中的 PC、LR、xPSR 同时损坏,仍是开关瞬态破坏 CPU 执行状态,不是程序主动跳到某个错误函数。
  • PastFaults=0x0010是双桥臂/零矢量测试无法建立转速后的预期启动失败,不是过流;Break 计数仍为 0。

尝试八:双桥臂错沿从 64 tick 加大到 128 tick

预期的启动超时:当前零矢量测试不会产生旋转,状态机随后停止 PWM。任然可能触发HardFault,IBUSERR,而且异常帧被破坏;把错沿从 64 增至 128 没有消除故障,因此暂时不再继续盲目增大占空比差。

尝试九:恢复栅极驱动 VCC到12V,PWM到20kHz

问题依旧……

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询