环境说明
| 项目 | 版本 |
|---|---|
| MCU | STM32F407IGT6 |
| IDE | IAR EWARM V8.32 |
| RTOS | uC/OS-III V3.04.04 (Micrium) |
| HAL库 | STM32Cube FW_F4 V1.28.3 |
| CubeMX | 6.15.0 |
本文基于一个由 CubeMX 生成的 STM32F407 HAL 库工程,详细记录如何将 uC/OS-III 移植进去。
一、工程目录结构
移植前的工程结构(CubeMX 生成):
1 | |
移植后新增的目录和文件:
1 | |
二、源码获取
Micrium 官方源码可从 Micrium GitHub 或官网下载。下载后的目录结构为:
1 | |
将这三个文件夹直接复制到工程根目录下的 uCOS3/ 文件夹中。
三、创建配置头文件
uC/OS-III 需要 4 个用户配置头文件。放在 Core/Inc/ 下,因为 IAR 工程已有的 include 路径 $PROJ_DIR$/../Core/Inc 能覆盖到。
路径说明:IAR 的
$PROJ_DIR$指向.ewp文件所在的目录,即EWARM/。$PROJ_DIR$/../即工程根目录TrainCamp_wangjunjie_Modbus/。
3.1 os_cfg.h — 内核功能开关
1 | |
3.2 os_cfg_app.h — 应用层配置
1 | |
3.3 cpu_cfg.h — CPU 层配置
1 | |
3.4 lib_cfg.h — 库配置
1 | |
四、修改 IAR 工程配置(XDM-60T4-E.ewp)
4.1 添加 Include 路径
在 IAR 工程选项 C/C++ Compiler → Additional include directories 中,原有路径保持不变,追加以下 6 条:
1 | |
其中 $PROJ_DIR$ 是 IAR 的内置变量,指向 .ewp 文件所在目录(即 EWARM/),$PROJ_DIR$/../ 即工程根目录。以上路径展开后分别对应:
| IAR 路径 | 实际文件系统路径 |
|---|---|
$PROJ_DIR$/../uCOS3/uCOS-III/Source |
TrainCamp_wangjunjie_Modbus/uCOS3/uCOS-III/Source/ |
$PROJ_DIR$/../uCOS3/uCOS-III/Ports/ARM-Cortex-M4/Generic/IAR |
TrainCamp_wangjunjie_Modbus/uCOS3/uCOS-III/Ports/ARM-Cortex-M4/Generic/IAR/ |
$PROJ_DIR$/../uCOS3/uC-CPU |
TrainCamp_wangjunjie_Modbus/uCOS3/uC-CPU/ |
$PROJ_DIR$/../uCOS3/uC-CPU/ARM-Cortex-M4/IAR |
TrainCamp_wangjunjie_Modbus/uCOS3/uC-CPU/ARM-Cortex-M4/IAR/ |
$PROJ_DIR$/../uCOS3/uC-LIB |
TrainCamp_wangjunjie_Modbus/uCOS3/uC-LIB/ |
$PROJ_DIR$/../uCOS3/uC-LIB/Ports/ARM-Cortex-M4/IAR |
TrainCamp_wangjunjie_Modbus/uCOS3/uC-LIB/Ports/ARM-Cortex-M4/IAR/ |
4.2 添加源文件到工程
在 IAR 工程面板中创建以下分组,添加对应源文件。路径均相对于 $PROJ_DIR$/../uCOS3/:
UCOS-III → Source 组(文件位于 uCOS3/uCOS-III/Source/):
os_core.c,os_flag.c,os_int.c,os_mem.c,os_msg.cos_mutex.c,os_pend_multi.c,os_prio.c,os_q.c,os_sem.cos_stat.c,os_task.c,os_tick.c,os_time.c,os_tmr.cos_var.c,os_dbg.c,os_cfg_app.c
UCOS-III → Ports 组(文件位于 uCOS3/uCOS-III/Ports/ARM-Cortex-M4/Generic/IAR/):
os_cpu_a.asmos_cpu_c.c
uC-CPU 组:
uCOS3/uC-CPU/cpu_core.cuCOS3/uC-CPU/ARM-Cortex-M4/IAR/cpu_a.asmuCOS3/uC-CPU/ARM-Cortex-M4/IAR/cpu_c.c
uC-LIB 组:
uCOS3/uC-LIB/lib_ascii.c,lib_math.c,lib_mem.c,lib_str.cuCOS3/uC-LIB/Ports/ARM-Cortex-M4/IAR/lib_mem_a.asm
五、修改启动文件(EWARM/startup_stm32f407xx.s)
这是移植中最关键的一步,需要修改两处。
5.1 在 Reset_Handler 中使能 FPU
Cortex-M4 复位后 FPU 默认禁用。如果 IAR 工程开启了 FPU(General Options → FPU → FPUv4-SP),必须在 Reset_Handler 中、调用 SystemInit 之前使能 CP10/CP11 协处理器:
1 | |
为什么必须这样做?
- CPACR (0xE000ED88):不使能 CP10/CP11 的话,UCOS3 上下文切换中的 FPU 指令(
VMRS/VSTMDB)会触发 UsageFault → HardFault - FPCCR (0xE000EF34):UCOS3 在
OSTaskSwHook中手动管理 FPU 寄存器(S0-S31)的保存/恢复,必须禁用硬件自动保存
5.2 重定向 PendSV_Handler
UCOS3 的上下文切换通过 PendSV 异常实现。需要将 startup 文件中的默认 PendSV_Handler 替换为跳转到 UCOS3 的 OS_CPU_PendSVHandler:
找到原文件中的:
1 | |
替换为:
1 | |
注意点:
NOROOT(2)而非NOROOT(1):LDR R0, =label生成的字面量池需要 4 字节对齐,NOROOT(1)只保证 2 字节对齐,会产生 Warning[440]- 不能用 C 函数包装
PendSV_Handler来调用OS_CPU_PendSVHandler:PendSV 处理函数修改栈指针后通过异常返回退出,C 函数的返回流程会破坏这个机制
六、修改中断处理文件(Core/Src/stm32f4xx_it.c)
6.1 添加 UCOS3 头文件
在文件顶部的 USER CODE Includes 区域加入:
1 | |
6.2 实现 SysTick_Handler
HAL 和 UCOS3 共享 SysTick 定时器。SysTick_Handler 需要同时服务两者。该函数定义在 stm32f4xx_it.c 的 USER CODE 区域,会覆盖 startup 文件中的 weak 默认实现:
1 | |
七、实现 CPU_TS_TmrRd(Core/Src/main.c)
UCOS3 的时间戳功能(用于测量调度锁时间等)需要 CPU_TS_TmrRd() 函数。使用 Cortex-M4 内置的 DWT 周期计数器实现。
在 main.c 的 USER CODE 0 区域添加函数定义:
1 | |
在 main() 的 USER CODE Init 区域初始化 DWT:
1 | |
DWT 是 ARM Cortex-M 核心调试组件,STM32CubeMX 没有对应的配置入口,只能手动初始化。这是标准做法,Micrium 官方 BSP 也是如此。
八、编写 main.c 框架
8.1 完整的关键代码
1 | |
8.2 关于 __disable_irq()
HAL_Init() 内部会调用 HAL_InitTick() 配置 SysTick 并使能其中断。理论上如果 SysTick 中断在 OSInit() 之前触发,OS_CPU_SysTickHandler() 会访问未初始化的 UCOS3 变量导致 HardFault。
实际测试中,从 HAL_Init() 到 OSInit() 之间的执行时间极短(微秒级),SysTick 的首次中断(1ms 后)几乎不可能在这个窗口触发,因此不加 __disable_irq() 通常也能正常运行。
作为防御性编程,可以在 main() 最开头加 __disable_irq(),OSStart() → OSStartHighRdy() 会在启动第一个任务时执行 CPSIE I 开中断。这不是必须的,但推荐加上。
九、踩坑总结
| 问题 | 现象 | 原因 | 解决 |
|---|---|---|---|
| FPU 未使能 | PendSV 上下文切换时 HardFault | IAR 开启了 FPU 但 startup 文件没有使能 CPACR | Reset_Handler 中写 CPACR 使能 CP10/CP11 |
| PendSV_Handler 链接错误 | 上下文切换异常 | C 包装函数破坏异常返回流程 | 用汇编重定向,NOROOT(2) 对齐 |
| CPU_TS_TmrRd 未定义 | 链接报错 Error[Li005] |
时间戳功能需要该函数 | 用 DWT CYCCNT 实现 |
| 字面量池对齐警告 | Warning[440] / Warning[22] |
LDR =label 需要 4 字节对齐 |
NOROOT(1) 改为 NOROOT(2) |
























