ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

TrustZone下LPBAM引发HardFault的排查与修复

2026/8/30 23:59:00 拓冰建站 浏览量
TrustZone下LPBAM引发HardFault的排查与修复 前阵子搞一个低功耗数据采集项目主控选了STM32U585Cortex-M33内核带TrustZone安全架构。为了把待机功耗压到个位数uA我决定用LPBAM做后台采集LPTIM定时触发LPDMA把ADC数据搬到SRAMCPU全程待在STOP2模式。方案听起来没什么问题结果第一次把LPBAM跑起来板子一进STOP2就翻车——HardFault而且不是卡在某个明确的代码行是后台DMA在低功耗状态下跑着跑着就触发了总线错误Debugger断下来时连现场都看不明白。这篇文章把TrustZone架构下LPBAM导致HardFault的完整排查过程记录下来包含定位方案、根因分析、修复代码以及后续的调试技巧给在M33TrustZone平台上碰LPBAM的人一点参考。1. 项目背景为什么要把LPBAM和TrustZone放在一起用1.1 需求与选型这个项目是一个电池供电的工业传感器节点要求平均功耗在10uA以内MCU要定期采集ADC数据采集完还要做加密存储。主控选STM32U585的核心原因有两个一是它有TrustZone可以做安全启动和密钥隔离密钥放在Secure World应用逻辑只能跑在Non-Secure World应用层代码接触不到密钥二是U5系列带LPBAM可以把数据采集和搬运交给低功耗DMA在STOP2模式下独立完成CPU几乎不用参与。常规的低功耗采集方案是LPTIM定时中断唤醒CPUCPU自己去做ADC采样和数据搬运然后重新进入低功耗。但这个方案的问题是每次唤醒CPU都要重新拉起时钟、恢复外设、处理完再返回休眠这个过程的峰值电流和唤醒延迟都比较可观。LPBAM的好处是把这些动作全部挪到低功耗模式下由LPDMA根据链表描述符自动完成CPU在STOP2里从头睡到尾平均功耗能显著降低。TrustZone选型自然是有代价的启动流程要先在Secure World跑一遍配置安全属性然后切到Non-Secure World跑主应用。LPBAM的初始化代码我一开始放在Non-Secure侧理由很简单——LPBAM属于普通应用功能没必要占用Secure侧的固件空间也没有触碰机密数据。现在回头看正是这个决定让后面的HardFault排查变得曲折不少。1.2 故障现象第一次进入STOP2就翻车LPBAM功能代码写完之后我先在正常运行模式下测了一遍LPTIM触发LPDMA搬运数据功能正常中断也能正确收到。当时心里松了口气觉得这波稳了。结果把低功耗流程加进去让系统真正进入STOP2之后问题立刻出现——板子在进入STOP2后大约几十毫秒内程序就冲进了HardFault_Handler。更麻烦的是这个HardFault不是从普通代码路径触发的它发生在CPU处于STOP2深度睡眠状态、由LPDMA硬件自主执行搬运的过程中。Debugger连上以后断点停在HardFault_Handler但Call Stack里看不到任何有效函数PC停在异常向量入口整个现场看起来非常干净完全不像常见的数组越界或空指针。我第一反应是LPBAM配置有问题比如链表描述符写错了、地址没对齐、长度字段配错导致DMA把数据搬到非法地址。但把所有描述符的地址、长度、控制字段全部仔仔细细检查了一遍没有发现异常。后来我把LPBAM关掉只用传统的中断唤醒方式跑同样流程同样进入STOP2完全正常。这就把怀疑范围缩小到了LPBAM本身以及它访问的资源上而不是低功耗状态切换流程。2. 在IAR里把HardFault现场完整挖出来2.1 让HardFault_Handler变成有效的调试起点遇到HardFault我最先做的事是把HardFault_Handler改成一段能保留现场的代码不要让它直接跑进一个空while循环。我在IAR里的做法是在HardFault_Handler函数第一行打断点然后打开View-Registers里的Core Registers窗口。断点命中之后先记录当前内核寄存器再决定下一步怎么走。有一点要注意如果IAR版本比较老或者优化等级开得比较高断点命中后可能看不到有效信息因为编译器可能把HardFault_Handler内联了寄存器可能已经被覆盖。遇到这种情况我一般会临时把HardFault_Handler改成汇编实现或者在启动文件里直接跳转到HardFault_Handler处打断点确保现场不丢。更实用的做法是在HardFault_Handler里直接读内核寄存器存到全局变量中这样即使后来复位了故障现场也能保留在内存里。代码很简单uint32_t g_hfsr, g_cfsr, g_bfar, g_msp, g_psp; void HardFault_Handler(void) { __disable_irq(); g_hfsr SCB-HFSR; g_cfsr SCB-CFSR; g_bfar SCB-BFAR; g_msp __get_MSP(); g_psp __get_PSP(); __asm(BKPT #0); while(1); }注意读SCB-CFSR这步一定要放在HardFault_Handler最前面做代码一旦跑起来后续任何内存访问、中断响应都可能改变这些状态位的值。先把现场快照存下来再慢慢分析这是所有HardFault调试的第一步。2.2 根据CFSR和BFAR判断异常类型复位进入HardFault_Handler之后我第一个看的是HFSR和CFSR。Cortex-M33会通过这两个状态寄存器告诉我们异常发生的具体类型和原因。这个项目里我读到的关键数值是HFSR 0x40000000FORCED置位说明HardFault是由更低优先级的异常升级而来的CFSR中的BFSR字段PRECISERR和BFARVALID同时置位BFAR 0x20040800看到PRECISERR加BFARVALID基本可以断定这是一个精确总线错误而且硬件把出错地址锁存到了BFAR里就是0x20040800。如果是数组越界后写坏内存导致的问题BFAR往往是一个随机的野地址而这里是一个固定地址明确落在SRAM的某个bank内。进一步查看链接脚本和GTZC配置后我发现这个地址所在的SRAM区域正好是STOP2模式下保持供电、但被配置成了Secure属性的那块区域。这个线索让我意识到问题不是简单的指针写飞而是总线访问权限被TrustZone的硬件保护机制拦截了。于是我开始怀疑LPDMA访问这个地址时因为安全属性不匹配被拒绝了。如果是这样BFAR给出的就是LPDMA想要访问却无法访问的硬件禁区。2.3 从栈帧恢复现场确认崩溃PC虽然BFAR已经给出大量信息但我还是想确认HardFault触发时CPU到底在跑什么