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库(本文以标准外设库为例)

移植步骤概览

  1. 将uC/OS-III源码中的Source、Ports/ARM-Cortex-M4/Generic/GNU(或对应Keil目录)加入工程。
  2. 在bsp.c中实现CPU_TS_TmrInit()(使用DWT时钟计数器)和OS_CPU_SysTickInit()(配置SysTick)。
  3. 修改os_cpu_a.asm中的PendSV_Handler和SysTick_Handler为uC/OS-III的OS_CPU_PendSVHandler与OS_CPU_SysTickHandler。
  4. 在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

运行与验证

  1. 将上述代码加入工程,确保uC/OS-III源码路径正确。
  2. 在启动文件startup_stm32f407xx.s中,将PendSV_Handler替换为OS_CPU_PendSVHandler,将SysTick_Handler替换为OS_CPU_SysTickHandler。
  3. 编译下载后,观察两个LED:

通过条件:LED1以约0.5秒间隔翻转,LED2以约1秒间隔翻转,两者互不阻塞。若LED1翻转时LED2保持状态不变,且翻转频率符合延时设定,说明任务切换与时钟节拍工作正常。

常见问题

  • 编译报错“Undefined symbol OS_CPU_PendSVHandler”:检查汇编文件是否已加入工程且中断向量表已重定向。
  • 任务不切换:确认OSStart()前已调用OSInit(),且SysTick中断优先级高于PendSV(数值上更小)。
  • 进入HardFault:检查任务栈大小是否足够(128字节可能偏小,建议256),并确认CPU_Init()已调用。