惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 叶小钗
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
SegmentFault 最新的问题
博客园 - 三生石上(FineUI控件)
雷峰网
雷峰网
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
博客园 - 【当耐特】
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
V
Visual Studio Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 聂微东
P
Proofpoint News Feed
Last Week in AI
Last Week in AI
U
Unit 42
W
WeLiveSecurity
博客园 - Franky
Recent Announcements
Recent Announcements
Hacker News - Newest:
Hacker News - Newest: "LLM"
Attack and Defense Labs
Attack and Defense Labs
月光博客
月光博客
The Cloudflare Blog
Spread Privacy
Spread Privacy
腾讯CDC
P
Privacy International News Feed
N
News and Events Feed by Topic
AWS News Blog
AWS News Blog
NISL@THU
NISL@THU
T
Troy Hunt's Blog
小众软件
小众软件
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Microsoft Security Blog
Microsoft Security Blog
L
Lohrmann on Cybersecurity
Webroot Blog
Webroot Blog
Y
Y Combinator Blog
量子位
P
Palo Alto Networks Blog
N
News and Events Feed by Topic
V
Vulnerabilities – Threatpost
K
Kaspersky official blog
IT之家
IT之家
T
Threat Research - Cisco Blogs
Cloudbric
Cloudbric
云风的 BLOG
云风的 BLOG
C
Check Point Blog
Blog — PlanetScale
Blog — PlanetScale
爱范儿
爱范儿
G
Google Developers Blog
S
Secure Thoughts

洛屿的小站

STM32F407 IAR环境下uC/OS-III移植完整指南 一种简单的卡尔曼滤波器设计 Cadence - 应律而动,落指成音 Zephyr - 聆风之息,为你而奏 不改一行插件代码,实现消息优先级与阻断 洛玖定时任务系统 在 butterfly 主题中添加首页点集动画(基于p2line项目) 你好,2026 stm32f4xx-ads1256驱动 stm32f4xx-ad9854并行驱动 主动式网站状态监测实现及其应用 右键菜单加入用Trae打开文件和文件夹 三角洲行动ID映射表 洛玖SDK说明 为网页文章开头添加原文连接 Hexo-Butterfly主题在主页添加GitHub贡献日历 Proteus中555定时器仿真问题 装饰器 洛玖开发日记 STS3032舵机获取力矩输出 kotlin网页前后端那些事 mspm0g3507-ad9850 奇怪的bug Paddle模型转PaddleLite 人工智能考核 构建一个yolov3网络 yolo和paddle模型常见输出参数 PaddleDetection 随便写的一些东西 实验室C语言第一次考核题目讲解及相关代码解读 C语言神经网络房价预测系统 C、C++数组,指针,指针数组,数组指针的区别 C、C++的大括号是必须的部分吗? C、C++预处理详解 C、C++其他关键字详解 C、C++存储类型关键字详解 C、C++控制语句关键字详解 C、C++数据类型关键字详解 C、C++关键字 C++药品管理系统 Bi-LSTM(Attention)的PyTorch实现 C语言实现波士顿房价预测 easy库的使用 edgeboardFZ3A相关问题 Predict.py的编写 Paddle环境搭建 百度Paddle模型训练 GMD09601-0.96OLED显示屏 【LeetCode 1617】统计子树中城市之间最大距离 C语言学习相关 STM32阵列按键 CH32(F10X F20X) 最短路 拓扑排序
STM32 UCOS3+HAL库集成中的FPU HardFault问题
洛屿 · 2026-07-23 · via 洛屿的小站

环境

  • 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
2
3
4
5
6
void OSSched(void)
{
// ... 省略前面的代码 ...
OS_TASK_SW(); // 触发 PendSV 进行上下文切换
CPU_INT_EN(); // ← 执行到这里后进入 HardFault
}

根因分析

问题出在 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
2
3
4
5
6
7
8
9
10
11
12
void OSTaskSwHook(void)
{
#if (OS_CPU_ARM_FP_EN == DEF_ENABLED)
if ((OSTCBCurPtr->Opt & OS_OPT_TASK_SAVE_FP) != (OS_OPT)0) {
OS_CPU_FP_Reg_Push(OSTCBCurPtr->StkPtr); // 保存 S0-S31
}
if ((OSTCBHighRdyPtr->Opt & OS_OPT_TASK_SAVE_FP) != (OS_OPT)0) {
OS_CPU_FP_Reg_Pop(OSTCBHighRdyPtr->StkPtr); // 恢复 S0-S31
}
#endif
// ...
}

当 IAR 工程启用了 FPU 时,__ARMVFP__ 宏被定义,OS_CPU_ARM_FP_EN 变为 DEF_ENABLED。此时 os_cpu_a.asm 中的 OS_CPU_PendSVHandler 会尝试使用 VMRSVSTMDB 等 FPU 指令。

如果 CPACR 没有使能 FPU,执行这些指令就会触发 UsageFault,进而升级为 HardFault。

解决方案

startup_stm32f407xx.sReset_Handler 中,调用 SystemInit 之前加入 FPU 使能代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Reset_Handler

;; 使能 FPU(CP10 和 CP11 协处理器)
LDR R0, =0xE000ED88 ; CPACR 寄存器地址
LDR R1, [R0] ; 读取 CPACR
ORR R1, R1, #(0xF << 20) ; 设置 bits 20-23,使能 CP10 和 CP11
STR R1, [R0] ; 写回 CPACR
DSB ; 数据同步屏障
;; 禁用 FPU 上下文自动保存
LDR R0, =0xE000EF34 ; FPCCR 寄存器地址
LDR R1, [R0]
BIC R1, R1, #0xC0000000 ; 清除 ASPEN 和 LSPEN 位
STR R1, [R0]
ISB ; 指令同步屏障

LDR R0, =SystemInit
BLX R0
LDR R0, =__iar_program_start
BX R0

关键寄存器说明

寄存器 地址 作用
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
2
3
4
5
6
7
8
9
10
11
12
13
IF {FPU} != "SoftVFP"
LDR.W R0, =0xE000ED88
LDR R1, [R0]
ORR R1, R1, #(0xF << 20)
STR R1, [R0]
DSB

LDR.W R0, =0xE000EF34
LDR R1, [R0]
AND R1, R1, #(0x3FFFFFFF)
STR R1, [R0]
ISB
ENDIF

记录一下完整的集成过程中踩过的坑:

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
2
3
4
5
6
EXTERN  OS_CPU_PendSVHandler
PUBWEAK PendSV_Handler
SECTION .text:CODE:REORDER:NOROOT(1)
PendSV_Handler
LDR R0, =OS_CPU_PendSVHandler
BX R0

注意:不能用 C 函数包装 PendSV_Handler 来调用 OS_CPU_PendSVHandler,因为 PendSV 处理函数修改栈指针后通过异常返回机制退出,C 包装函数会破坏这个流程。

3. SysTick_Handler

HAL 和 UCOS3 共用 SysTick。在 stm32f4xx_it.c 中定义:

1
2
3
4
5
void SysTick_Handler(void)
{
HAL_IncTick(); // HAL 超时计数
OS_CPU_SysTickHandler(); // UCOS3 tick
}

4. CPU_TS_TmrRd

UCOS3 的时间戳功能需要 CPU_TS_TmrRd() 函数。使用 Cortex-M4 的 DWT 周期计数器实现:

1
2
3
4
CPU_TS_TMR CPU_TS_TmrRd(void)
{
return ((CPU_TS_TMR)DWT->CYCCNT);
}

初始化时需要使能 DWT:

1
2
3
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;