Cortex-M0汇编延迟函数多次调用触发HardFault问题排查
Cortex-M0汇编延迟函数触发HardFault问题排查
我为Cortex-M0 CPU和Keil uVision 5.38编写了如下汇编延迟函数:
static __INLINE __ASM void _asm_delay10us(unsigned int num) { /* R0 contains "num"*/ PUSH {R4, LR} /* R4 will contain number of loops for a single 10us delay (158 loops) */ MOVS R4, #158 /* Calculate total number of loops as R4 = 158*value */ MULS R4, R0, R4 // loop SUBS R4, #1 BEQ exit B loop exit POP {R4, pc} }
通过C代码调用该函数:
void delay1ms(unsigned int u32Cnt) { _asm_delay10us(100 * u32Cnt); }
运行几次后出现挂死,后续排查发现:
- 更新1:第二次调用函数时触发HardFault
- 更新2:编译器生成的反汇编代码片段如下:
汇编函数反汇编:
__asm___5_hal_c_0731ac3b___asm_delay10us 0x08000154: b510 .. PUSH {r4,lr} 0x08000156: 249e .$ MOVS r4,#0x9e 0x08000158: 4344 DC MULS r4,r0,r4 0x0800015a: 3c01 .< SUBS r4,#1 0x0800015c: d000 .. BEQ 0x8000160 ; __asm___5_hal_c_0731ac3b___asm_delay10us + 12 0x0800015e: e7fc .. B 0x800015a ; __asm___5_hal_c_0731ac3b___asm_delay10us + 6 0x08000160: bd10 .. POP {r4,pc} 0x08000162: 0000 .. MOVS r0,r0
调用函数及相关代码反汇编:
.text delay1ms 0x080026bc: b510 .. PUSH {r4,lr} 0x080026be: 4604 .F MOV r4,r0 0x080026c0: 2164 d! MOVS r1,#0x64 0x080026c2: 4361 aC MULS r1,r4,r1 0x080026c4: 4608 .F MOV r0,r1 0x080026c6: f7fdfd45 ..E. BL __asm___5_hal_c_0731ac3b___asm_delay10us ; 0x8000154 0x080026ca: bd10 .. POP {r4,pc} hal_init 0x080026cc: b570 p. PUSH {r4-r6,lr} 0x080026ce: f7fdfe57 ..W. BL SystemCoreClockUpdate ; 0x8000380 0x080026d2: 48a2 .H LDR r0,[pc,#648] ; [0x800295c] = 0x9c4 0x080026d4: f7fffff2 .... BL delay1ms ; 0x80026bc 0x080026d8: 2164 d! MOVS r1,#0x64
问题根源
Cortex-M0的MULS指令是16x16乘法,结果仅保留低16位存入目标寄存器。当num较大时,158 * num的结果会超出16位范围(>65535),导致R4被截断为低16位数值:
- 若截断后数值为0,循环直接退出,延迟失效
- 若截断后数值为负数(SUBS指令会影响标志位),循环会进入异常死循环,破坏上下文触发HardFault
第一次调用可能刚好数值在16位范围内,第二次调用时参数超出阈值,触发故障。
修复方案
方案1:拆分循环避免乘法溢出
把延迟拆分为外层循环(处理num次)和内层循环(固定158次实现10us),彻底规避乘法操作:
static __INLINE __ASM void _asm_delay10us(unsigned int num) { /* R0 = num */ PUSH {R4, LR} outer_loop: CBZ R0, exit ; num为0直接退出 MOVS R4, #158 ; 单10us循环次数 inner_loop: SUBS R4, #1 BNE inner_loop SUBS R0, #1 ; num减1 B outer_loop exit: POP {R4, PC} }
方案2:模拟32位乘法(纯汇编)
如果必须保留乘法逻辑,用循环累加模拟32位乘法,避免数值截断:
static __INLINE __ASM void _asm_delay10us(unsigned int num) { /* R0 = num */ PUSH {R4, R5, LR} MOVS R4, #158 ; 单10us循环次数 MOVS R5, #0 ; 32位结果低16位 MOVS R3, #0 ; 32位结果高16位 mult_loop: ADDS R5, R0 ; 累加num到低16位 ADC R3, #0 ; 处理进位到高16位 SUBS R4, #1 ; 乘法循环计数减1 BNE mult_loop ; 完成乘法 MOVS R4, R5 ORRS R4, R3 ; 检查结果是否为0 BEQ exit loop: SUBS R4, #1 BEQ exit B loop exit: POP {R4, R5, PC} }
方案3:C语言实现延迟(更可靠)
放弃手动汇编,用C语言编写延迟函数,开启Keil优化后会生成高效且安全的汇编代码:
static inline void delay10us(unsigned int num) { while(num--) { volatile unsigned int i = 158; while(i--); } }
验证建议
- 调用
delay1ms(1000)测试1秒延迟,确认能正常退出 - 查看反汇编代码,确认循环逻辑无数值截断
- 用Keil调试功能查看HardFault状态寄存器,验证故障已解决
内容的提问来源于stack exchange,提问作者Vladimir T
相关产品推荐
相关产品推荐

