4. 移植uC/OS-III到STM32
任务目标
在STM32F407裸机工程基础上集成uC/OS-III内核,完成以下闭环:配置SysTick作为OS时钟节拍,创建两个不同优先级的任务,通过延时函数触发任务调度,用LED翻转验证任务切换是否正常。
环境与依赖
- 硬件:STM32F407开发板(含2个LED,分别接PD12、PD13)
- IDE:Keil MDK-ARM 5.x
- uC/OS-III源码:从Micrium官方获取V3.08版本,包含
uC/OS-III、uC/CPU、uC/LIB三个核心目录 - STM32标准外设库或HAL库(本文以标准外设库为例)
移植步骤概览
- 将uC/OS-III源码中的
Source、Ports/ARM-Cortex-M4/Generic/GNU(或对应Keil目录)加入工程。 - 在
bsp.c中实现CPU_TS_TmrInit()(使用DWT时钟计数器)和OS_CPU_SysTickInit()(配置SysTick)。 - 修改
os_cpu_a.asm中的PendSV_Handler和SysTick_Handler为uC/OS-III的OS_CPU_PendSVHandler与OS_CPU_SysTickHandler。 - 在
main.c中初始化OS并创建任务。
关键完整代码
以下代码为最小可运行实现,包含时钟节拍配置、两个任务及调度验证逻辑:
// main.c —— uC/OS-III双任务切换验证
#include <stm32f4xx.h>
#include "os.h"
#include "bsp.h"
// 任务栈
static CPU_STK task1_stk[128];
static CPU_STK task2_stk[128];
// 任务控制块
static OS_TCB task1_tcb;
static OS_TCB task2_tcb;
// 任务函数声明
static void task1(void *p_arg);
static void task2(void *p_arg);
// LED初始化(PD12/PD13)
static void led_init(void) {
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
GPIO_InitTypeDef gpio;
gpio.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13;
gpio.GPIO_Mode = GPIO_Mode_OUT;
gpio.GPIO_OType = GPIO_OType_PP;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
gpio.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOD, &gpio);
}
int main(void) {
OS_ERR err;
led_init();
CPU_Init(); // 初始化CPU组件(时间戳等)
OSInit(&err); // 初始化uC/OS-III
// 创建任务1:高优先级(优先级3)
OSTaskCreate(&task1_tcb,
"task1",
task1,
0,
3,
task1_stk,
128,
0,
0,
0,
OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR,
&err);
// 创建任务2:低优先级(优先级4)
OSTaskCreate(&task2_tcb,
"task2",
task2,
0,
4,
task2_stk,
128,
0,
0,
0,
OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR,
&err);
OSStart(&err); // 启动调度器(不会返回)
}
// 任务1:翻转LED1,延时500ms
static void task1(void *p_arg) {
OS_ERR err;
(void)p_arg;
while (1) {
GPIO_ToggleBits(GPIOD, GPIO_Pin_12);
OSTimeDlyHMSM(0, 0, 0, 500, OS_OPT_TIME_HMSM_STRICT, &err);
}
}
// 任务2:翻转LED2,延时1000ms
static void task2(void *p_arg) {
OS_ERR err;
(void)p_arg;
while (1) {
GPIO_ToggleBits(GPIOD, GPIO_Pin_13);
OSTimeDlyHMSM(0, 0, 0, 1000, OS_OPT_TIME_HMSM_STRICT, &err);
}
}
// bsp.c —— 时钟节拍与时间戳初始化
#include "bsp.h"
#include "os.h"
// 使用SysTick产生1ms节拍
void OS_CPU_SysTickInit(void) {
SysTick_Config(SystemCoreClock / 1000); // 1ms中断
NVIC_SetPriority(SysTick_IRQn, 0); // 最高优先级
}
// 使用DWT提供高精度时间戳(uC/OS-III统计需要)
void CPU_TS_TmrInit(void) {
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}
// 启动文件需将PendSV_Handler重定向到OS_CPU_PendSVHandler
// 将SysTick_Handler重定向到OS_CPU_SysTickHandler
运行与验证
- 将上述代码加入工程,确保uC/OS-III源码路径正确。
- 在启动文件
startup_stm32f407xx.s中,将PendSV_Handler替换为OS_CPU_PendSVHandler,将SysTick_Handler替换为OS_CPU_SysTickHandler。 - 编译下载后,观察两个LED:
通过条件:LED1以约0.5秒间隔翻转,LED2以约1秒间隔翻转,两者互不阻塞。若LED1翻转时LED2保持状态不变,且翻转频率符合延时设定,说明任务切换与时钟节拍工作正常。
常见问题
- 编译报错“Undefined symbol OS_CPU_PendSVHandler”:检查汇编文件是否已加入工程且中断向量表已重定向。
- 任务不切换:确认
OSStart()前已调用OSInit(),且SysTick中断优先级高于PendSV(数值上更小)。 - 进入HardFault:检查任务栈大小是否足够(128字节可能偏小,建议256),并确认
CPU_Init()已调用。