STM32F103移植uC/OS-III实现多任务
打开main.c,在下方添加:c运行#include "os.h" // 包含uC/OS头文件// 任务优先级(数值越小优先级越高,范围1~OS_CFG_PRIO_MAX-1)// 任务栈大小(单位:字,STM32为32位,1字=4字节,最小128字)// 任务栈(必须为全局变量,存放在RAM中)// 任务控制块(TCB,OS用于管理任务的数据结构)
以下是基于 STM32F103 移植 uC/OS-III 并实现 3 个周期任务的详细过程,包含移植步骤、任务设计及关键代码:
一、移植环境准备
-
硬件环境
- 开发板:STM32F103C8T6(最小系统板)
- 外设:LED(PA0、PA1)、USART1(TX:PA9,RX:PA10)
-
软件环境
- IDE:Keil MDK5.36
- 库:STM32CubeMX(生成 HAL 库工程)
- uC/OS-III 源码:从 Micrium 官网下载(
uC-CPU、uC-LIB、uC-OS3三个核心文件夹)
二、移植 uC/OS-III 到 STM32F103 步骤
Step 1:生成基础 HAL 库工程
- 打开 STM32CubeMX,选择芯片
STM32F103C8T6。 - 配置外设:
- RCC:选择
HSE(外部高速时钟),配置为 72MHz。 - SYS:调试模式选择
Serial Wire(SWD)。 - GPIO:PA0、PA1 设为
Output Push-Pull(控制 LED)。 - USART1:波特率 115200,模式
Asynchronous(异步通信)。 - NVIC:使能
USART1 global interrupt(后续用于串口中断,可选)。
- RCC:选择
- 生成工程:选择
MDK-ARM,勾选 “Generate peripheral initialization as a pair of .c/.h files per peripheral”,生成代码。
Step 2:添加 uC/OS-III 源码
- 在工程根目录创建
uC文件夹,下设 3 个子文件夹:uC-CPU:存放 CPU 相关代码(从源码复制uC-CPU文件夹)。uC-LIB:存放基础库(复制uC-LIB文件夹)。uC-OS3:存放 OS 核心代码(复制uC-OS3文件夹)。
- 在 Keil 中添加文件组:
- 右键工程→
Manage Project Items,添加uC-CPU、uC-LIB、uC-OS3组。 - 分别添加对应文件夹下的
.c文件(注意:uC-OS3需包含os_core.c、os_task.c、os_time.c等核心文件)。
- 右键工程→
Step 3:配置 uC/OS-III 头文件路径
- 点击
魔术棒→C/C++→Include Paths,添加:./uC/uC-CPU/Include./uC/uC-LIB/Include./uC/uC-OS3/Include./uC/uC-CPU/ARM-Cortex-M3/RealView/Include(CPU 架构相关)
Step 4:修改 STM32 启动文件与中断配置
-
替换启动文件:uC/OS 需要接管 PendSV 和 SysTick 中断,将
startup_stm32f103xb.s中的对应中断函数注释,改为:asm
PendSV_Handler PROC EXPORT PendSV_Handler [WEAK] B OS_CPU_PendSVHandler ; 跳转至uC/OS的PendSV处理函数 ENDP SysTick_Handler PROC EXPORT SysTick_Handler [WEAK] B OS_CPU_SysTickHandler ; 跳转至uC/OS的SysTick处理函数 ENDP -
配置 SysTick 时钟:在
stm32f1xx_hal_conf.h中确保HAL_SYSTICK_CLKSOURCE_HCLK_DIV8改为HAL_SYSTICK_CLKSOURCE_HCLK(72MHz,确保 OS 时钟正确)。
Step 5:实现 OS 初始化与启动代码
-
在
main.c中包含 uC/OS 头文件:c
运行
#include "os.h" -
定义任务栈和任务控制块(TCB):
c
运行
// 任务栈大小(单位:字,STM32为32位,即4字节/字) #define TASK1_STK_SIZE 128 #define TASK2_STK_SIZE 128 #define TASK3_STK_SIZE 128 // 任务栈 CPU_STK TASK1_STK[TASK1_STK_SIZE]; CPU_STK TASK2_STK[TASK2_STK_SIZE]; CPU_STK TASK3_STK[TASK3_STK_SIZE]; // 任务控制块 OS_TCB TASK1_TCB; OS_TCB TASK2_TCB; OS_TCB TASK3_TCB; -
实现任务函数:
c
运行
// 任务1:1s周期控制LED1(PA0)闪烁 void Task1(void *p_arg) { (void)p_arg; while (1) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0); // 翻转LED1 OSTimeDlyHMSM(0, 0, 1, 0); // 延时1s } } // 任务2:3s周期控制LED2(PA1)闪烁 void Task2(void *p_arg) { (void)p_arg; while (1) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1); // 翻转LED2 OSTimeDlyHMSM(0, 0, 3, 0); // 延时3s } } // 任务3:2s周期通过串口发送消息 void Task3(void *p_arg) { (void)p_arg; while (1) { HAL_UART_Transmit(&huart1, (uint8_t*)"hello uc/OS! 欢迎来到RTOS多任务环境!\r\n", 42, 100); OSTimeDlyHMSM(0, 0, 2, 0); // 延时2s } } -
修改
main函数,初始化 OS 并创建任务:c
运行
int main(void) { HAL_Init(); SystemClock_Config(); // 系统时钟初始化(72MHz) MX_GPIO_Init(); // GPIO初始化 MX_USART1_UART_Init(); // 串口初始化 OS_ERR err; OSInit(&err); // 初始化uC/OS-III // 创建任务(优先级:数值越小优先级越高,确保不冲突) OSTaskCreate( &TASK1_TCB, // TCB指针 "Task1", // 任务名 Task1, // 任务函数 NULL, // 任务参数 1, // 优先级(1级) &TASK1_STK[0], // 任务栈起始地址 TASK1_STK_SIZE/10,// 栈检查水线 TASK1_STK_SIZE, // 栈大小 0, // 消息队列大小(不用) 0, // 时间片(不用) NULL, // 扩展参数 OS_OPT_TASK_STK_CHK, // 启用栈检查 &err ); // 同理创建Task2(优先级2)和Task3(优先级3) OSTaskCreate(&TASK2_TCB, "Task2", Task2, NULL, 2, &TASK2_STK[0], TASK2_STK_SIZE/10, TASK2_STK_SIZE, 0, 0, NULL, OS_OPT_TASK_STK_CHK, &err); OSTaskCreate(&TASK3_TCB, "Task3", Task3, NULL, 3, &TASK3_STK[0], TASK3_STK_SIZE/10, TASK3_STK_SIZE, 0, 0, NULL, OS_OPT_TASK_STK_CHK, &err); OSStart(&err); // 启动OS(进入任务调度) while (1) { // 不会执行到这里 } }
Step 6:解决移植常见问题
- 编译报错 “os.h: No such file or directory”:检查头文件路径是否添加正确。
- 任务无法调度:确保
OSStart()前所有任务创建成功(err为OS_ERR_NONE),且优先级不重复。 - 延时不准:检查
SysTick时钟配置(需与 OS 时钟频率一致,72MHz 时OS_CFG_TICK_RATE_HZ默认 1000Hz,即 1ms 滴答)。 - 串口发送乱码:确认波特率配置(115200),并在
MX_USART1_UART_Init()中设置huart1.Init.StopBits = UART_STOPBITS_1。
三、项目结构与提交内容
-
项目代码结构(GitHub 仓库):
plaintext
STM32_uCOS_Project/ ├─ Core/ // HAL库核心代码(CubeMX生成) ├─ Drivers/ // STM32F1xx_HAL_Driver ├─ uC/ // uC/OS-III源码(uC-CPU、uC-LIB、uC-OS3) ├─ main.c // 主函数与任务定义 ├─ STM32F103C8T6.uvprojx // Keil工程文件 └─ README.md // 项目说明 -
提交内容:
- GitHub 仓库地址(包含完整代码)。
- 项目打包 ZIP 文件。
- 实验报告(Markdown+PDF,含移植步骤、现象截图、问题总结)。
- 作业博客地址(记录关键步骤与调试经验)。
四、实验现象
- LED1(PA0)每 1 秒闪烁一次。
- LED2(PA1)每 3 秒闪烁一次。
- 串口助手每 2 秒收到一次消息:
hello uc/OS! 欢迎来到RTOS多任务环境!
通过以上步骤,可完成 uC/OS-III 在 STM32F103 上的移植及多周期任务设计。关键在于 OS 中断接管、任务栈配置和优先级管理。
编辑分享
生成一个详细步骤
以下是基于 STM32F103C8T6 移植 uC/OS-III 并实现 3 个周期任务的超详细步骤,包含环境搭建、移植配置、代码编写、调试等全流程,按步骤操作可确保成功率:
一、前期准备(1 天)
1.1 硬件清单
- STM32F103C8T6 最小系统板(核心板)
- USB 转 TTL 模块(用于串口通信)
- 2 个 LED 灯(红、绿)及 2 个限流电阻(220Ω)
- 杜邦线若干、Micro-USB 数据线
- 面包板(可选,用于连接 LED)
1.2 软件工具安装
- STM32CubeMX:用于生成 HAL 库工程(官网下载,需注册)
- Keil MDK5:IDE(需安装支持 STM32F1 的包,通过 Pack Installer 安装)
- uC/OS-III 源码:从 Micrium 官网下载(需注册,获取
uC-CPU、uC-LIB、uC-OS3三个文件夹) - 串口助手:如 SSCOM、XCOM(用于接收串口消息)
- Git:用于上传代码到 GitHub
二、生成 STM32 基础工程(2 小时)
2.1 新建 CubeMX 工程
- 打开 STM32CubeMX,点击 “Access to MCU Selector”。
- 在搜索框输入 “STM32F103C8T6”,选中芯片后点击 “Start Project”。
2.2 配置系统时钟(RCC)
- 左侧菜单栏点击 “RCC”,在 “High Speed Clock (HSE)” 中选择 “Crystal/Ceramic Resonator”(外部晶振)。
- 点击 “Clock Configuration”,配置系统时钟为 72MHz:
- HSE=8MHz,PLLMUL=×9(8×9=72MHz)
- SYSCLK=72MHz,HCLK=72MHz,PCLK1=36MHz,PCLK2=72MHz
- 点击 “OK” 保存。
2.3 配置调试模式(SYS)
- 左侧菜单栏点击 “SYS”,在 “Debug” 中选择 “Serial Wire”(SWD 调试,必须配置,否则后续下载程序可能失败)。
2.4 配置 GPIO(LED 控制)
- 左侧菜单栏点击 “GPIO”,选择 PA0 和 PA1 引脚:
- 点击 PA0,选择 “GPIO_Output”(红 LED)
- 点击 PA1,选择 “GPIO_Output”(绿 LED)
- 在右侧 “GPIO Configuration” 中,将两个引脚的 “GPIO mode” 设为 “Output Push-Pull”,“GPIO Pull-up/Pull-down” 设为 “Pull-up”(默认高电平,LED 灭)。
2.5 配置 USART1(串口通信)
- 左侧菜单栏点击 “USART1”,在 “Mode” 中选择 “Asynchronous”(异步模式)。
- 配置参数:
- Baud Rate:115200
- Word Length:8 Bits
- Stop Bits:1
- Parity:None
- 点击 “NVIC Settings”,勾选 “USART1 global interrupt”(可选,本项目用查询发送,不强制)。
2.6 生成工程文件
- 点击菜单栏 “Project”→“Settings”:
- Project Name:输入 “uCOS_LED_USART”
- Project Location:选择本地路径(如
D:\Projects\) - Toolchain/IDE:选择 “MDK-ARM”,版本选 “V5”
- 点击 “Code Generator”,勾选:
- “Generate peripheral initialization as a pair of .c/.h files per peripheral”
- “Keep user code when re-generating”
- 点击 “Generate Code”,生成完成后点击 “Open Project”(自动打开 Keil 工程)。
三、移植 uC/OS-III 到 STM32(3 小时)
3.1 准备 uC/OS 源码
- 将下载的 uC/OS-III 源码解压,复制以下 3 个文件夹到工程根目录(
D:\Projects\uCOS_LED_USART\),并新建uC文件夹存放:uC-CPU(CPU 架构相关代码)uC-LIB(基础库函数)uC-OS3(OS 核心代码)
3.2 在 Keil 中添加 uC/OS 文件
- 打开 Keil 工程,右键点击工程名→“Manage Project Items”:
- 点击 “Groups” 下方的 “New”,依次创建 3 个组:
uC-CPU、uC-LIB、uC-OS3。
- 点击 “Groups” 下方的 “New”,依次创建 3 个组:
- 向各组添加文件:
- uC-CPU 组:
- 点击 “Add Files”,进入
uC/uC-CPU/ARM-Cortex-M3/RealView/,添加os_cpu_a.asm(汇编文件)。 - 进入
uC/uC-CPU/,添加cpu.c、cpu_core.c、cpu_c.c。
- 点击 “Add Files”,进入
- uC-LIB 组:
- 进入
uC/uC-LIB/,添加lib_ascii.c、lib_math.c、lib_mem.c、lib_str.c。
- 进入
- uC-OS3 组:
- 进入
uC/uC-OS3/Source/,添加所有.c文件(如os_core.c、os_task.c、os_time.c、os_flag.c等,共约 15 个文件)。
- 进入
- uC-CPU 组:
3.3 配置头文件路径
- 点击 Keil 菜单栏 “魔术棒”→“C/C++”→“Include Paths”→“...”:
- 添加以下路径(点击 “New” 逐个添加):
plaintext
./uC/uC-CPU/Include ./uC/uC-CPU/ARM-Cortex-M3/RealView/Include ./uC/uC-LIB/Include ./uC/uC-OS3/Include ./Core/Inc ./Drivers/STM32F1xx_HAL_Driver/Inc - 点击 “OK” 保存。
- 添加以下路径(点击 “New” 逐个添加):
3.4 修改 uC/OS 配置文件
- 复制
uC/uC-OS3/Source/os_cfg.h到Core/Inc/(方便修改),并在 Keil 中右键 “Core/Inc”→“Add Existing Files to Group” 添加该文件。 - 打开
os_cfg.h,修改以下配置(确保任务和时钟功能启用):c
运行
#define OS_CFG_TASK_EN 1u // 启用任务管理 #define OS_CFG_TIME_DLY_HMSM_EN 1u // 启用HMSM延时函数 #define OS_CFG_TICK_EN 1u // 启用系统滴答 #define OS_CFG_STAT_TASK_EN 0u // 禁用统计任务(简化移植) #define OS_CFG_SCHED_LOCK_EN 1u // 启用调度锁
3.5 修改 STM32 启动文件(关键步骤)
- 打开工程中的启动文件
startup_stm32f103xb.s(位于Drivers/CMSIS/Device/ST/STM32F1xx/Source/Templates/ARM/)。 - 找到
PendSV_Handler和SysTick_Handler函数,替换为 uC/OS 的处理函数:asm
; 注释原函数 ;PendSV_Handler PROC ; EXPORT PendSV_Handler [WEAK] ; B . ; ENDP ; 替换为uC/OS的PendSV处理函数 PendSV_Handler PROC EXPORT PendSV_Handler B OS_CPU_PendSVHandler ENDP ; 注释原函数 ;SysTick_Handler PROC ; EXPORT SysTick_Handler [WEAK] ; B . ; ENDP ; 替换为uC/OS的SysTick处理函数 SysTick_Handler PROC EXPORT SysTick_Handler B OS_CPU_SysTickHandler ENDP
3.6 修改 HAL 库时钟配置
- 打开
stm32f1xx_hal_conf.h(位于Core/Inc/),找到HAL_SYSTICK_CLKSOURCE_CONFIG:- 将默认的
HAL_SYSTICK_CLKSOURCE_HCLK_DIV8改为HAL_SYSTICK_CLKSOURCE_HCLK(确保 SysTick 时钟为 72MHz,与 uC/OS 滴答频率匹配)。
- 将默认的
四、编写多任务代码(2 小时)
4.1 定义任务栈和任务控制块(TCB)
- 打开
main.c,在/* USER CODE BEGIN PV */下方添加:c
运行
#include "os.h" // 包含uC/OS头文件 // 任务优先级(数值越小优先级越高,范围1~OS_CFG_PRIO_MAX-1) #define TASK1_PRIO 1u #define TASK2_PRIO 2u #define TASK3_PRIO 3u // 任务栈大小(单位:字,STM32为32位,1字=4字节,最小128字) #define TASK1_STK_SIZE 128u #define TASK2_STK_SIZE 128u #define TASK3_STK_SIZE 128u // 任务栈(必须为全局变量,存放在RAM中) CPU_STK TASK1_STK[TASK1_STK_SIZE]; CPU_STK TASK2_STK[TASK2_STK_SIZE]; CPU_STK TASK3_STK[TASK3_STK_SIZE]; // 任务控制块(TCB,OS用于管理任务的数据结构) OS_TCB TASK1_TCB; OS_TCB TASK2_TCB; OS_TCB TASK3_TCB;
4.2 实现 3 个任务函数
- 在
main.c中/* USER CODE BEGIN 0 */下方添加:c
运行
// 任务1:1s周期控制PA0(红灯)闪烁 void Task1(void *p_arg) { (void)p_arg; // 忽略参数 while (1) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0); // 翻转PA0电平 OSTimeDlyHMSM(0, 0, 1, 0, OS_OPT_TIME_HMSM_STRICT, &err); // 延时1s } } // 任务2:3s周期控制PA1(绿灯)闪烁 void Task2(void *p_arg) { (void)p_arg; while (1) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1); // 翻转PA1电平 OSTimeDlyHMSM(0, 0, 3, 0, OS_OPT_TIME_HMSM_STRICT, &err); // 延时3s } } // 任务3:2s周期通过USART1发送消息 void Task3(void *p_arg) { (void)p_arg; uint8_t msg[] = "hello uc/OS! 欢迎来到RTOS多任务环境!\r\n"; // 消息内容 while (1) { HAL_UART_Transmit(&huart1, msg, sizeof(msg)-1, 100); // 发送消息(-1是去掉字符串结束符'\0') OSTimeDlyHMSM(0, 0, 2, 0, OS_OPT_TIME_HMSM_STRICT, &err); // 延时2s } }
4.3 修改 main 函数初始化 OS 并创建任务
- 替换
main函数内容:c
运行
int main(void) { OS_ERR err; // 用于存储OS函数返回的错误码 // 1. 初始化HAL库(CubeMX生成) HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); // 2. 初始化uC/OS-III OSInit(&err); if (err != OS_ERR_NONE) { // 初始化失败(可通过LED闪烁提示) while (1) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0); HAL_Delay(500); } } // 3. 创建任务1(1s周期LED) OSTaskCreate( &TASK1_TCB, // TCB指针 "Task1_LED1", // 任务名(调试用) Task1, // 任务函数 NULL, // 任务参数(无) TASK1_PRIO, // 优先级 &TASK1_STK[0], // 任务栈起始地址 TASK1_STK_SIZE/10, // 栈检查水线(预留10%栈空间) TASK1_STK_SIZE, // 栈大小 0, // 消息队列大小(不使用) 0, // 时间片(不使用) NULL, // 扩展参数 OS_OPT_TASK_STK_CHK, // 启用栈检查 &err ); if (err != OS_ERR_NONE) while (1); // 任务创建失败则死循环 // 4. 创建任务2(3s周期LED) OSTaskCreate( &TASK2_TCB, "Task2_LED2", Task2, NULL, TASK2_PRIO, &TASK2_STK[0], TASK2_STK_SIZE/10, TASK2_STK_SIZE, 0, 0, NULL, OS_OPT_TASK_STK_CHK, &err ); if (err != OS_ERR_NONE) while (1); // 5. 创建任务3(2s周期串口发送) OSTaskCreate( &TASK3_TCB, "Task3_USART", Task3, NULL, TASK3_PRIO, &TASK3_STK[0], TASK3_STK_SIZE/10, TASK3_STK_SIZE, 0, 0, NULL, OS_OPT_TASK_STK_CHK, &err ); if (err != OS_ERR_NONE) while (1); // 6. 启动uC/OS-III(开始任务调度) OSStart(&err); // 以下代码不会执行 while (1) { } }
五、编译与下载(1 小时)
5.1 编译工程
- 点击 Keil 菜单栏 “Rebuild”(全编译),确保无错误(0 Errors,0 Warnings)。
- 若提示 “os_cpu.h: No such file or directory”:检查头文件路径是否正确。
- 若提示 “undefined reference to
OS_CPU_PendSVHandler'”:检查os_cpu_a.asm` 是否添加到工程。
5.2 配置下载器
- 点击 “魔术棒”→“Debug”→“Use”,选择 “ST-Link Debugger”,点击 “Settings”:
- “Port” 选择 “SW”,点击 “Connect”,确认能识别到 STM32 芯片(显示 “Core ID: 0x2BA01477”)。
- 切换到 “Flash Download”,勾选 “Reset and Run”(下载后自动运行)。
5.3 下载程序
- 连接 ST-Link 到开发板的 SWD 接口(SWCLK、SWDIO、GND、3.3V),再连接电脑。
- 点击 Keil 菜单栏 “Load”,程序下载完成后自动运行。
六、硬件连接与现象验证(1 小时)
6.1 硬件接线
- LED 连接:
- 红灯正极→220Ω 电阻→PA0,负极→GND
- 绿灯正极→220Ω 电阻→PA1,负极→GND
- 串口连接:
- USB 转 TTL 的 TX→PA10(USART1_RX)
- USB 转 TTL 的 RX→PA9(USART1_TX)
- USB 转 TTL 的 GND→开发板 GND
- (注意:不要接 3.3V,避免电源冲突)
6.2 现象验证
- LED 现象:
- 红灯(PA0)每 1 秒闪烁一次(亮 1s→灭 1s 循环)。
- 绿灯(PA1)每 3 秒闪烁一次(亮 3s→灭 3s 循环)。
- 串口现象:
- 打开串口助手,选择 USB 转 TTL 的 COM 口,波特率 115200,无奇偶校验,1 个停止位。
- 每 2 秒收到一条消息:
hello uc/OS! 欢迎来到RTOS多任务环境!
七、项目提交与总结(1 小时)
7.1 上传代码到 GitHub
- 在 GitHub 新建仓库(如
STM32_uCOS_LED_USART),勾选 “Initialize this repository with a README”。 - 本地工程根目录执行:
bash
git init git add . git commit -m "uC/OS-III移植到STM32F103,实现3个周期任务" git remote add origin https://github.com/你的用户名/STM32_uCOS_LED_USART.git git push -u origin main
7.2 打包项目文件
- 将工程根目录压缩为 ZIP 文件(包含
Core、Drivers、uC、.uvprojx等所有文件)。
7.3 编写实验报告(Markdown)
- 包含:实验目的、环境准备、移植步骤(附关键截图)、代码说明、现象描述、遇到的问题及解决方法(如编译报错、任务不调度等)。
- 导出为 PDF 文件。
7.4 发布作业博客
- 在 CSDN 或知乎发布博客,内容与实验报告一致,附 GitHub 仓库地址。
常见问题与解决
- 任务不执行:检查任务优先级是否重复,
OSStart()是否调用,OSTaskCreate()的错误码是否为OS_ERR_NONE。 - 延时不准:确认
SysTick时钟配置为 72MHz(HAL_SYSTICK_CLKSOURCE_HCLK),OS_CFG_TICK_RATE_HZ默认 1000(1ms 滴答)。 - 串口乱码:检查波特率是否为 115200,USB 转 TTL 与开发板共地是否连接。
- 编译提示栈溢出:增大任务栈大小(如从 128 字改为 256 字)。
按以上步骤操作,可完整实现 uC/OS-III 在 STM32F103 上的移植及多任务设计,核心是确保 OS 对中断的接管和任务栈的正确配置。
更多推荐


所有评论(0)