环境说明
| 项目 |
版本 |
| 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 2 3 4 5 6 7 8 9 10 11
| TrainCamp_wangjunjie_Modbus/ ├── Core/ │ ├── Inc/ ← HAL 头文件(main.h, stm32f4xx_hal_conf.h 等) │ └── Src/ ← HAL 源码(main.c, stm32f4xx_it.c 等) ├── Drivers/ │ ├── CMSIS/ │ └── STM32F4xx_HAL_Driver/ ├── EWARM/ ← IAR 工程目录(.ewp 文件所在位置) │ ├── XDM-60T4-E.ewp │ └── startup_stm32f407xx.s └── XDM-60T4-E.ioc
|
移植后新增的目录和文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| TrainCamp_wangjunjie_Modbus/ ├── Core/Inc/ ← 新增 4 个配置头文件 │ ├── os_cfg.h │ ├── os_cfg_app.h │ ├── cpu_cfg.h │ └── lib_cfg.h ├── EWARM/ │ ├── startup_stm32f407xx.s ← 修改(FPU 使能 + PendSV 重定向) │ └── XDM-60T4-E.ewp ← 修改(include 路径 + 源文件分组) ├── uCOS3/ ← 新增,从 Micrium 源码复制 │ ├── uC-CPU/ │ │ ├── cpu_core.c, cpu_core.h, cpu_def.h │ │ └── ARM-Cortex-M4/IAR/ │ │ ├── cpu_a.asm │ │ ├── cpu_c.c │ │ └── cpu.h │ ├── uC-LIB/ │ │ ├── lib_ascii.c/.h, lib_math.c/.h, lib_mem.c/.h, lib_str.c/.h, lib_def.h │ │ └── Ports/ARM-Cortex-M4/IAR/ │ │ └── lib_mem_a.asm │ └── uCOS-III/ │ ├── Source/ ← 内核源码(20 个 .c 和 .h) │ └── Ports/ARM-Cortex-M4/Generic/IAR/ │ ├── os_cpu_a.asm │ ├── os_cpu_c.c │ └── os_cpu.h └── Core/Src/ ├── main.c ← 修改(UCOS3 初始化 + 任务) └── stm32f4xx_it.c ← 修改(SysTick_Handler)
|
二、源码获取
Micrium 官方源码可从 Micrium GitHub 或官网下载。下载后的目录结构为:
1 2 3 4
| Micrium/Software/ ├── uC-CPU/ ├── uC-LIB/ └── uCOS-III/
|
将这三个文件夹直接复制到工程根目录下的 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| #ifndef OS_CFG_H #define OS_CFG_H
#define OS_CFG_APP_HOOKS_EN 1u #define OS_CFG_ARG_CHK_EN 1u #define OS_CFG_CALLED_FROM_ISR_CHK_EN 0u #define OS_CFG_DBG_EN 1u #define OS_CFG_ISR_POST_DEFERRED_EN 0u #define OS_CFG_OBJ_TYPE_CHK_EN 1u #define OS_CFG_TS_EN 1u #define OS_CFG_PEND_MULTI_EN 0u #define OS_CFG_PRIO_MAX 64u #define OS_CFG_SCHED_LOCK_TIME_MEAS_EN 1u #define OS_CFG_SCHED_ROUND_ROBIN_EN 0u #define OS_CFG_STK_SIZE_MIN 64u
#define OS_CFG_FLAG_EN 1u #define OS_CFG_FLAG_DEL_EN 1u #define OS_CFG_FLAG_MODE_CLR_EN 1u #define OS_CFG_FLAG_PEND_ABORT_EN 1u
#define OS_CFG_MEM_EN 1u
#define OS_CFG_MUTEX_EN 1u #define OS_CFG_MUTEX_DEL_EN 1u #define OS_CFG_MUTEX_PEND_ABORT_EN 1u
#define OS_CFG_Q_EN 1u #define OS_CFG_Q_DEL_EN 1u #define OS_CFG_Q_FLUSH_EN 1u #define OS_CFG_Q_PEND_ABORT_EN 1u
#define OS_CFG_SEM_EN 1u #define OS_CFG_SEM_DEL_EN 1u #define OS_CFG_SEM_PEND_ABORT_EN 1u #define OS_CFG_SEM_SET_EN 1u
#define OS_CFG_STAT_TASK_EN 1u #define OS_CFG_STAT_TASK_STK_CHK_EN 1u #define OS_CFG_TASK_CHANGE_PRIO_EN 1u #define OS_CFG_TASK_DEL_EN 1u #define OS_CFG_TASK_Q_EN 1u #define OS_CFG_TASK_Q_PEND_ABORT_EN 1u #define OS_CFG_TASK_PROFILE_EN 1u #define OS_CFG_TASK_REG_TBL_SIZE 1u #define OS_CFG_TASK_SEM_PEND_ABORT_EN 1u #define OS_CFG_TASK_SUSPEND_EN 1u
#define OS_CFG_TIME_DLY_HMSM_EN 1u #define OS_CFG_TIME_DLY_RESUME_EN 1u #define OS_CFG_TLS_TBL_SIZE 0u
#define OS_CFG_TMR_EN 1u #define OS_CFG_TMR_DEL_EN 1u
#define TRACE_CFG_EN 0u
#endif
|
3.2 os_cfg_app.h — 应用层配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #ifndef OS_CFG_APP_H #define OS_CFG_APP_H
#define OS_CFG_MSG_POOL_SIZE 100u #define OS_CFG_ISR_STK_SIZE 128u #define OS_CFG_TASK_STK_LIMIT_PCT_EMPTY 10u
#define OS_CFG_IDLE_TASK_STK_SIZE 64u
#define OS_CFG_INT_Q_SIZE 10u #define OS_CFG_INT_Q_TASK_STK_SIZE 100u
#define OS_CFG_STAT_TASK_PRIO 11u #define OS_CFG_STAT_TASK_RATE_HZ 10u #define OS_CFG_STAT_TASK_STK_SIZE 100u
#define OS_CFG_TICK_RATE_HZ 1000u #define OS_CFG_TICK_TASK_PRIO 1u #define OS_CFG_TICK_TASK_STK_SIZE 100u
#define OS_CFG_TMR_TASK_PRIO 11u #define OS_CFG_TMR_TASK_RATE_HZ 10u #define OS_CFG_TMR_TASK_STK_SIZE 100u
#endif
|
3.3 cpu_cfg.h — CPU 层配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #ifndef CPU_CFG_MODULE_PRESENT #define CPU_CFG_MODULE_PRESENT
#define CPU_CFG_NAME_EN DEF_ENABLED #define CPU_CFG_NAME_SIZE 16
#define CPU_CFG_TS_32_EN DEF_ENABLED #define CPU_CFG_TS_64_EN DEF_DISABLED #define CPU_CFG_TS_TMR_SIZE CPU_WORD_SIZE_32
#if 0 #define CPU_CFG_INT_DIS_MEAS_EN #endif #define CPU_CFG_INT_DIS_MEAS_OVRHD_NBR 1u
#if 1 #define CPU_CFG_LEAD_ZEROS_ASM_PRESENT #endif
#if 0 #define CPU_CFG_TRAIL_ZEROS_ASM_PRESENT #endif
#define CPU_CFG_CACHE_MGMT_EN DEF_DISABLED
#endif
|
3.4 lib_cfg.h — 库配置
1 2 3 4 5 6 7 8 9 10 11 12 13
| #ifndef LIB_CFG_MODULE_PRESENT #define LIB_CFG_MODULE_PRESENT
#define LIB_MEM_CFG_ARG_CHK_EXT_EN DEF_DISABLED #define LIB_MEM_CFG_OPTIMIZE_ASM_EN DEF_ENABLED #define LIB_MEM_CFG_DBG_INFO_EN DEF_DISABLED #define LIB_MEM_CFG_HEAP_SIZE (3u * 1024u) #define LIB_MEM_CFG_HEAP_PADDING_ALIGN LIB_MEM_PADDING_ALIGN_NONE
#define LIB_STR_CFG_FP_EN DEF_DISABLED #define LIB_STR_CFG_FP_MAX_NBR_DIG_SIG LIB_STR_FP_MAX_NBR_DIG_SIG_DFLT
#endif
|
四、修改 IAR 工程配置(XDM-60T4-E.ewp)
4.1 添加 Include 路径
在 IAR 工程选项 C/C++ Compiler → Additional include directories 中,原有路径保持不变,追加以下 6 条:
1 2 3 4 5 6
| $PROJ_DIR$/../uCOS3/uCOS-III/Source $PROJ_DIR$/../uCOS3/uCOS-III/Ports/ARM-Cortex-M4/Generic/IAR $PROJ_DIR$/../uCOS3/uC-CPU $PROJ_DIR$/../uCOS3/uC-CPU/ARM-Cortex-M4/IAR $PROJ_DIR$/../uCOS3/uC-LIB $PROJ_DIR$/../uCOS3/uC-LIB/Ports/ARM-Cortex-M4/IAR
|
其中 $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.c
os_mutex.c, os_pend_multi.c, os_prio.c, os_q.c, os_sem.c
os_stat.c, os_task.c, os_tick.c, os_time.c, os_tmr.c
os_var.c, os_dbg.c, os_cfg_app.c
UCOS-III → Ports 组(文件位于 uCOS3/uCOS-III/Ports/ARM-Cortex-M4/Generic/IAR/):
uC-CPU 组:
uCOS3/uC-CPU/cpu_core.c
uCOS3/uC-CPU/ARM-Cortex-M4/IAR/cpu_a.asm
uCOS3/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.c
uCOS3/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 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 STR R1, [R0] ; 写回 DSB ;; 禁用 FPU 上下文自动保存(UCOS3 自己管理) 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):不使能 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 2 3 4
| PUBWEAK PendSV_Handler SECTION .text:CODE:REORDER:NOROOT(1) PendSV_Handler B PendSV_Handler
|
替换为:
1 2 3 4 5 6
| EXTERN OS_CPU_PendSVHandler PUBWEAK PendSV_Handler SECTION .text:CODE:REORDER:NOROOT(2) PendSV_Handler LDR R0, =OS_CPU_PendSVHandler BX R0
|
注意点:
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 区域加入:
6.2 实现 SysTick_Handler
HAL 和 UCOS3 共享 SysTick 定时器。SysTick_Handler 需要同时服务两者。该函数定义在 stm32f4xx_it.c 的 USER CODE 区域,会覆盖 startup 文件中的 weak 默认实现:
1 2 3 4 5
| void SysTick_Handler(void) { HAL_IncTick(); OS_CPU_SysTickHandler(); }
|
七、实现 CPU_TS_TmrRd(Core/Src/main.c)
UCOS3 的时间戳功能(用于测量调度锁时间等)需要 CPU_TS_TmrRd() 函数。使用 Cortex-M4 内置的 DWT 周期计数器实现。
在 main.c 的 USER CODE 0 区域添加函数定义:
1 2 3 4 5 6
| CPU_TS_TMR CPU_TS_TmrRd (void) { return ((CPU_TS_TMR)DWT->CYCCNT); }
|
在 main() 的 USER CODE Init 区域初始化 DWT:
1 2 3 4 5
| CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
|
DWT 是 ARM Cortex-M 核心调试组件,STM32CubeMX 没有对应的配置入口,只能手动初始化。这是标准做法,Micrium 官方 BSP 也是如此。
八、编写 main.c 框架
8.1 完整的关键代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
| #include "main.h" #include "os.h"
#define APP_TASK_START_STK_SIZE 256u static OS_TCB AppTaskStartTCB; static CPU_STK AppTaskStartStk[APP_TASK_START_STK_SIZE];
#define APP_TASK_GPIO_STK_SIZE 256u static OS_TCB AppTaskGPIOTCB; static CPU_STK AppTaskGPIOStk[APP_TASK_GPIO_STK_SIZE];
static void AppTaskStart(void *p_arg); static void AppTaskGPIO(void *p_arg);
CPU_TS_TMR CPU_TS_TmrRd(void) { return ((CPU_TS_TMR)DWT->CYCCNT); }
int main(void) { OS_ERR err;
HAL_Init();
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init();
OSInit(&err);
OSTaskCreate((OS_TCB *)&AppTaskStartTCB, (CPU_CHAR *)"App Task Start", (OS_TASK_PTR ) AppTaskStart, (void *) 0, (OS_PRIO ) 3, (CPU_STK *)&AppTaskStartStk[0], (CPU_STK_SIZE) APP_TASK_START_STK_SIZE / 10, (CPU_STK_SIZE) APP_TASK_START_STK_SIZE, (OS_MSG_QTY ) 0, (OS_TICK ) 0, (void *) 0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), (OS_ERR *)&err);
OSStart(&err);
while (1) {} }
static void AppTaskStart(void *p_arg) { OS_ERR err; (void)p_arg;
OSTaskCreate((OS_TCB *)&AppTaskGPIOTCB, (CPU_CHAR *)"App Task GPIO", (OS_TASK_PTR ) AppTaskGPIO, (void *) 0, (OS_PRIO ) 4, (CPU_STK *)&AppTaskGPIOStk[0], (CPU_STK_SIZE) APP_TASK_GPIO_STK_SIZE / 10, (CPU_STK_SIZE) APP_TASK_GPIO_STK_SIZE, (OS_MSG_QTY ) 0, (OS_TICK ) 0, (void *) 0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), (OS_ERR *)&err);
OSTaskDel((OS_TCB *)0, &err); }
static void AppTaskGPIO(void *p_arg) { OS_ERR err; (void)p_arg;
while (DEF_TRUE) { HAL_GPIO_WritePin(Y0_GPIO_Port, Y0_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(Y2_GPIO_Port, Y2_Pin, GPIO_PIN_SET); OSTimeDlyHMSM(0, 0, 0, 500, OS_OPT_TIME_HMSM_STRICT, &err);
HAL_GPIO_WritePin(Y0_GPIO_Port, Y0_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(Y2_GPIO_Port, Y2_Pin, GPIO_PIN_RESET); OSTimeDlyHMSM(0, 0, 0, 500, OS_OPT_TIME_HMSM_STRICT, &err); } }
|
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) |
参考