给STM32F103的Blue Pill开发板装上µC/OS-III:从CubeMX配置到多任务LED闪烁的保姆级教程
第一次接触实时操作系统(RTOS)时,看着开发板上两个LED灯以不同频率交替闪烁,那种成就感至今难忘。本教程将带你用最经典的Blue Pill开发板(STM32F103C8T6)和µC/OS-III系统,重现这个"Hello World"级别的多任务实验。不同于单纯的理论讲解,我们会从CubeMX图形化配置开始,手把手完成RTOS移植、任务创建、调试的全过程,让你在1小时内看到实际运行效果。
1. 硬件准备与开发环境搭建
1.1 所需材料清单
- Blue Pill开发板(STM32F103C8T6核心板)
- USB转TTL串口模块(如CH340G)
- 2个LED灯及220Ω限流电阻(板载PC13已自带LED)
- ST-Link V2调试器(可选,用于烧录和调试)
- 杜邦线若干
提示:如果使用板载PC13 LED,其驱动能力较弱,建议外接LED到PA3等IO口获得更明显视觉效果。
1.2 软件工具安装
需要提前准备好以下开发环境:
# 开发工具链 - STM32CubeMX 6.9.0+ - Keil MDK-ARM 5.37+ 或 STM32CubeIDE 1.13+ - µC/OS-III源码包(可从Micrium官网获取评估版) # 驱动支持 - STM32F1xx HAL库 - ST-Link驱动(如使用ST-Link调试)安装完成后,建议在CubeMX中检查芯片支持包是否包含STM32F1系列:
- 启动STM32CubeMX
- 点击"Help" → "Manage embedded software packages"
- 在"STM32F1"系列前打勾安装
2. CubeMX基础工程配置
2.1 时钟树设置
在CubeMX中新建工程选择STM32F103C8T6后,首先配置时钟源:
RCC配置:
- High Speed Clock (HSE):Crystal/Ceramic Resonator
- Low Speed Clock (LSE):Disable
时钟树参数:
- HSE频率:8MHz(匹配外部晶振)
- PLL倍频:×9
- 系统时钟:72MHz
- APB1分频:/2(36MHz)
- APB2分频:/1(72MHz)
2.2 GPIO与外设配置
我们需要配置两个LED引脚和一个串口用于调试输出:
| 功能 | 引脚 | 模式 | 参数 |
|---|---|---|---|
| LED1 | PC13 | GPIO_Output | 默认低电平 |
| LED2 | PA3 | GPIO_Output | 默认低电平 |
| USART1_TX | PA9 | Alternate | 波特率115200 |
| USART1_RX | PA10 | Alternate | 无硬件流控 |
在CubeMX的"Pinout & Configuration"界面完成上述配置后,生成代码前还需:
- 在"Project Manager"选项卡设置工程名称和路径
- 选择Toolchain为MDK-ARM或STM32CubeIDE
- 勾选"Generate peripheral initialization as a pair of .c/.h files"
3. µC/OS-III源码移植
3.1 源码目录结构规划
将µC/OS-III源码整合到工程中时,建议采用以下目录结构:
Project/ ├── Core/ │ ├── Inc/ │ ├── Src/ │ └── OS/ # µC/OS配置文件 ├── Drivers/ ├── µC/ # µC/OS-III核心源码 │ ├── uC-CPU/ # CPU相关接口 │ ├── uC-LIB/ # 轻量级库 │ └── uCOS-III/ # 内核源码 └── MDK-ARM/ # Keil工程文件具体操作步骤:
- 在工程根目录创建
µC文件夹 - 将源码中的
uC-CPU、uC-LIB、uCOS-III三个目录复制到µC下 - 在
Core/Src下新建OS文件夹存放配置文件
3.2 Keil工程配置
在Keil中需要添加源码路径和调整编译选项:
添加文件分组:
- uCOSIII-CPU:包含
cpu_core.c、cpu_a.asm等 - uCOSIII-LIB:包含
lib_mem.c、lib_str.c等 - uCOSIII-Ports:包含
os_cpu_c.c、os_cpu_a.asm - uCOSIII-Source:内核源文件如
os_task.c、os_time.c
- uCOSIII-CPU:包含
设置包含路径:
../µC/uC-CPU../µC/uC-CPU/ARM-Cortex-M3/RealView../µC/uC-LIB../µC/uCOS-III/Ports/ARM-Cortex-M3/Generic/RealView../µC/uCOS-III/Source
编译选项调整:
- 在"C/C++"选项卡的"Define"中添加:
OS_CFG_APP_HOOKS_EN=0 - 优化等级建议选择-O1平衡代码大小和性能
- 在"C/C++"选项卡的"Define"中添加:
4. 多任务LED闪烁实现
4.1 系统初始化流程
完整的RTOS启动流程需要在main.c中实现:
#include "includes.h" // 任务栈和TCB定义 #define TASK_STACK_SIZE 128 OS_TCB AppTaskStartTCB; CPU_STK AppTaskStartStk[TASK_STACK_SIZE]; int main(void) { OS_ERR err; // 初始化µC/OS-III OSInit(&err); if (err != OS_ERR_NONE) { while(1); // 初始化失败处理 } // 创建起始任务 OSTaskCreate(&AppTaskStartTCB, "App Task Start", AppTaskStart, (void *)0, 1, // 最高优先级 &AppTaskStartStk[0], TASK_STACK_SIZE/10, TASK_STACK_SIZE, 0, 0, (void *)0, OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR, &err); // 启动多任务调度 OSStart(&err); while(1); }4.2 LED任务实现
在起始任务中创建两个LED闪烁任务:
// LED任务控制块和栈 OS_TCB Led1TaskTCB, Led2TaskTCB; CPU_STK Led1TaskStk[64], Led2TaskStk[64]; void AppTaskStart(void *p_arg) { OS_ERR err; (void)p_arg; // 硬件初始化 BSP_Init(); // 包含GPIO初始化 // 创建LED1任务(1Hz闪烁) OSTaskCreate(&Led1TaskTCB, "LED1 Task", Led1Task, (void *)0, 2, &Led1TaskStk[0], 64/10, 64, 0, 0, (void *)0, OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR, &err); // 创建LED2任务(0.33Hz闪烁) OSTaskCreate(&Led2TaskTCB, "LED2 Task", Led2Task, (void *)0, 3, &Led2TaskStk[0], 64/10, 64, 0, 0, (void *)0, OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR, &err); // 起始任务自我删除 OSTaskDel(NULL, &err); } void Led1Task(void *p_arg) { (void)p_arg; OS_ERR err; while(1) { HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); OSTimeDlyHMSM(0, 0, 0, 500, OS_OPT_TIME_HMSM_STRICT, &err); } } void Led2Task(void *p_arg) { (void)p_arg; OS_ERR err; while(1) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_3); OSTimeDlyHMSM(0, 0, 1, 500, OS_OPT_TIME_HMSM_STRICT, &err); } }4.3 调试技巧
当多任务系统运行不正常时,可以通过以下方法排查:
串口打印调试信息:
printf("Task %s running at %d ms\n", OSTaskNameGet(OSTCBCurPtr->Prio, &err), OSTimeGet(&err));检查任务栈使用情况:
CPU_STK_SIZE used; OS_TaskStkChk(ledTaskPrio, &used, &free, &err); printf("LED Task Stack: %d/%d used\n", used, TASK_STACK_SIZE);使用RTOS感知调试:
- 在Keil的Debug模式下,打开"View" → "System Viewer" → "RTX RTOS"
- 可以实时查看任务状态、信号量、消息队列等信息
5. 进阶功能扩展
5.1 添加串口调试任务
在现有基础上增加一个串口输出任务:
OS_TCB UartTaskTCB; CPU_STK UartTaskStk[128]; void UartTask(void *p_arg) { OS_ERR err; (void)p_arg; while(1) { printf("[%d] System running\n", OSTimeGet(&err)); OSTimeDlyHMSM(0, 0, 2, 0, OS_OPT_TIME_HMSM_STRICT, &err); } }需要在CubeMX中配置USART并启用重定向:
// 在usart.c中添加 #include <stdio.h> int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY); return ch; }5.2 使用信号量同步任务
实现LED闪烁与串口输出的同步:
定义信号量:
OS_SEM UartSem; OS_ERR err; OSSemCreate(&UartSem, "UART Sem", 1, &err);修改任务:
void Led1Task(void *p_arg) { while(1) { OSSemPend(&UartSem, 0, OS_OPT_PEND_BLOCKING, NULL, &err); HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); OSSemPost(&UartSem, OS_OPT_POST_ALL, &err); OSTimeDlyHMSM(0, 0, 0, 500, OS_OPT_TIME_HMSM_STRICT, &err); } }
5.3 低功耗优化
当系统需要省电时,可以启用Tickless模式:
修改os_cfg.h:
#define OS_CFG_TICKLESS_EN 1实现钩子函数:
void OSTimeTickHook(void) { if(OSIntNestingCtr == 0) { if(OSPrioHighRdy != OSPrioCur) { // 进入低功耗模式 __WFI(); } } }
6. 常见问题解决
6.1 编译错误处理
undefined symbol OS_CPU_SysTickHandler: 在
stm32f1xx_it.c中注释掉原有的SysTick_Handler,使用µC/OS提供的版本堆栈溢出: 增大任务栈大小或在
startup_stm32f103xb.s中调整Heap_Size
6.2 运行异常排查
LED不闪烁: 检查GPIO初始化是否正确,确认
BSP_Init()被调用系统卡死: 检查是否所有任务都调用了延时函数,避免忙等待
6.3 性能优化建议
- 在
os_cfg.h中关闭不需要的功能(如统计任务、时间测量) - 根据实际需求调整
OS_CFG_TICK_RATE_HZ(通常10-100Hz) - 使用
OS_OPT_TIME_DLY替代OS_OPT_TIME_HMSM_STRICT减少开销
移植完成后,可以尝试扩展更多功能,如:
- 添加按键检测任务
- 实现PWM调光控制
- 接入传感器数据采集
- 建立TCP/IP网络连接