STM32 UCOS3+HAL库集成中的FPU HardFault问题
环境
- MCU:STM32F407IGT6
- IDE:IAR EWARM V8.32
- RTOS:uC/OS-III V3.04.04
- HAL库:STM32Cube FW_F4 V1.28.3
问题描述
在将 Micrium uC/OS-III 移植到 STM32F407 HAL 库项目中时,系统启动后立即进入 HardFault_Handler。通过调试器单步执行发现,HardFault 发生在 OSSched() 函数中 CPU_INT_EN() 之后,也就是第一次任务上下文切换的地方。
1 | |
根因分析
问题出在 FPU(浮点单元)的使能时序 上。
IAR 工程配置了 FPU
在 IAR 工程的 General Options → FPU 中,选择了 FPUv4-SP(单精度浮点),这意味着编译器会在适当的地方生成 FPU 指令。
但 startup 文件没有使能 FPU
Cortex-M4 复位后,CP10 和 CP11 协处理器(即 FPU)默认是 禁用 的。必须在使用任何 FPU 指令之前,通过写 CPACR(Coprocessor Access Control Register)寄存器来使能它。
标准的 HAL 库 startup 文件(由 STM32CubeMX 生成)不包含 FPU 使能代码——这是设计上的预期行为,因为 HAL 本身不需要 FPU。
UCOS3 上下文切换会访问 FPU 寄存器
uC/OS-III 的 Cortex-M4 移植层在任务切换钩子 OSTaskSwHook 中包含了 FPU 寄存器保存/恢复的逻辑:
1 | |
当 IAR 工程启用了 FPU 时,__ARMVFP__ 宏被定义,OS_CPU_ARM_FP_EN 变为 DEF_ENABLED。此时 os_cpu_a.asm 中的 OS_CPU_PendSVHandler 会尝试使用 VMRS、VSTMDB 等 FPU 指令。
如果 CPACR 没有使能 FPU,执行这些指令就会触发 UsageFault,进而升级为 HardFault。
解决方案
在 startup_stm32f407xx.s 的 Reset_Handler 中,调用 SystemInit 之前加入 FPU 使能代码:
1 | |
关键寄存器说明
| 寄存器 | 地址 | 作用 |
|---|---|---|
| CPACR | 0xE000ED88 | 协处理器访问控制,bits[23:20] 控制 CP10/CP11(FPU) |
| FPCCR | 0xE000EF34 | 浮点上下文控制,ASPEN(bit31) 和 LSPEN(bit30) 控制自动保存 |
- ASPEN(Automatic State Preservation Enable):为1时,异常入口/退出自动保存/恢复 FPU 状态
- LSPEN(Lazy State Preservation Enable):为1时,延迟保存 FPU 状态直到真正使用
uC/OS-III 需要自己管理 FPU 上下文(在 OSTaskSwHook 中),所以必须禁用硬件自动保存,避免冲突。
等价的 Keil 版本
如果使用 Keil MDK,代码如下(语法不同但功能相同):
1 | |
UCOS3 + HAL 库集成要点
记录一下完整的集成过程中踩过的坑:
1. 中断时序问题
HAL_Init() 会立即配置 SysTick 并使能其中断。如果 UCOS3 还没初始化(OSInit() 未调用),SysTick 中断触发后会访问未初始化的 UCOS3 变量,导致 HardFault。
解决:在 main() 最开始调用 __disable_irq(),让 OSStart() → OSStartHighRdy() 在准备好后通过 CPSIE I 使能中断。
2. PendSV_Handler 重定向
UCOS3 的上下文切换通过 PendSV 异常实现,处理函数是 OS_CPU_PendSVHandler(在 os_cpu_a.asm 中定义)。需要在 startup 文件中将 PendSV_Handler 重定向到它:
1 | |
注意:不能用 C 函数包装 PendSV_Handler 来调用 OS_CPU_PendSVHandler,因为 PendSV 处理函数修改栈指针后通过异常返回机制退出,C 包装函数会破坏这个流程。
3. SysTick_Handler
HAL 和 UCOS3 共用 SysTick。在 stm32f4xx_it.c 中定义:
1 | |
4. CPU_TS_TmrRd
UCOS3 的时间戳功能需要 CPU_TS_TmrRd() 函数。使用 Cortex-M4 的 DWT 周期计数器实现:
1 | |
初始化时需要使能 DWT:
1 | |

