You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

27 KiB

STM32F407 + IAR 工程 uC/OS-II 移植说明

1. 文档目的

本文记录本项目将 uC/OS-II 移植到 STM32F407IG + IAR 工程的完整过程,并结合当前已经能够正常调试、运行串口与 Modbus RTU 从站的实际代码进行说明。

本文不是通用示例,文中目录、文件名、任务优先级、时基和接口均以当前工程为准,主要用于:

  • 回顾本次 uC/OS-II 的移植步骤;
  • 理解每个移植文件的作用;
  • 在 CubeMX 重新生成代码后检查移植内容是否仍然完整;
  • 后续增加任务、信号量、消息队列等功能;
  • 排查任务不运行、延时不生效、无法切换任务等问题。

当前工程已经完成以下验证:

  • uC/OS-II 可以初始化并启动;
  • 应用任务 AppTaskStart 可以运行;
  • OSTimeDly()OSTimeGet() 可以正常使用;
  • HAL 和 uC/OS-II 共用 SysTick 时基;
  • USART1 + DMA 可以正常通信;
  • Modbus RTU 从站可以在任务中轮询处理请求。

2. 当前工程环境

项目 当前配置
MCU STM32F407IG
CPU 内核 ARM Cortex-M4
工程生成工具 STM32CubeMX
编译器 IAR Embedded Workbench for ARM
IAR 工程配置名 Modbus
uC/OS-II 版本 V2.92.11
操作系统节拍 1000 Hz,即 1 ms/节拍
串口 USART1
串口参数 115200 bit/s、8 个有效数据位、偶校验、1 个停止位(8E1)
串口接收 DMA2 Stream2 Channel4
串口发送 DMA2 Stream7 Channel4
应用任务 AppTaskStart
应用任务优先级 5
应用任务栈 256 个 OS_STK 单元

IAR 命令行编译工具位于:

F:\IAR Systems\Embedded Workbench 8.3\common\bin

3. 移植工作的本质

将 uC/OS-II 移植到一个新的 MCU 工程,不只是把源码复制进来。完整移植至少包含以下五部分:

  1. 内核源码:任务调度、延时、信号量、消息队列等通用功能;
  2. CPU 移植层:任务栈初始化、临界区、寄存器保存和恢复;
  3. 工程配置:选择启用哪些内核功能、允许多少任务、系统节拍频率等;
  4. 硬件连接:将 SysTick 和 PendSV 正确接入操作系统;
  5. 应用启动:初始化内核、创建任务并启动调度器。

它们的关系如下:

应用任务 AppTaskStart
        │
        │ 调用 OSTimeDly、OSTimeGet 等 API
        ▼
uC/OS-II 内核源码
        │
        │ 请求任务切换、初始化任务栈
        ▼
Cortex-M4 + IAR 移植层
        │
        ├── SysTick:产生操作系统节拍
        └── PendSV:保存旧任务现场并恢复新任务现场

4. 工程中的 uC/OS-II 目录

当前相关文件位于:

Middlewares/Third_Party/Micrium/
├─ Config/
│  ├─ app_cfg.h
│  ├─ cpu_cfg.h
│  └─ os_cfg.h
├─ uCOS-II/
│  ├─ Source/
│  │  ├─ ucos_ii.h
│  │  ├─ os_core.c
│  │  ├─ os_task.c
│  │  ├─ os_time.c
│  │  ├─ os_sem.c
│  │  ├─ os_mutex.c
│  │  ├─ os_q.c
│  │  ├─ os_mbox.c
│  │  ├─ os_flag.c
│  │  ├─ os_mem.c
│  │  └─ os_tmr.c
│  └─ Ports/ARM-Cortex-M4/IAR/
│     ├─ os_cpu.h
│     ├─ os_cpu_a.asm
│     ├─ os_cpu_c.c
│     └─ os_dbg.c
├─ uC-CPU/
└─ uC-LIB/

4.1 各类文件的作用

文件或目录 作用
ucos_ii.h uC/OS-II 总头文件,应用程序通常只需包含它
os_core.c 内核初始化、调度和中断嵌套管理
os_task.c 任务创建、删除、挂起、恢复和栈检查
os_time.c 任务延时及系统节拍计数
os_sem.c 信号量
os_mutex.c 互斥信号量
os_q.c 消息队列
os_mbox.c 邮箱
os_flag.c 事件标志组
os_mem.c 固定大小内存分区
os_tmr.c 软件定时器
os_cpu.h Cortex-M4/IAR 的数据类型、临界区和任务切换宏
os_cpu_c.c 初始任务栈构造、CPU 钩子及 SysTick 接口
os_cpu_a.asm 上下文切换和寄存器保存/恢复的汇编实现
os_dbg.c 向调试器提供内核配置及数据尺寸信息
os_cfg.h 内核功能裁剪和资源数量配置
app_cfg.h 应用任务优先级和栈大小配置

uC-CPUuC-LIB 目录还提供 Micrium 的 CPU 抽象和通用库头文件。本工程已将相应目录加入头文件搜索路径。


5. IAR 工程配置

5.1 添加源码分组

在 IAR 工程中建立 Micrium 分组,并添加下列内容。

Micrium Config

app_cfg.h
cpu_cfg.h
os_cfg.h

uCOS-II Port

os_cpu_a.asm
os_cpu_c.c
os_dbg.c

uCOS-II Source

os_core.c
os_flag.c
os_mbox.c
os_mem.c
os_mutex.c
os_q.c
os_sem.c
os_task.c
os_time.c
os_tmr.c

即使某项功能在 os_cfg.h 中被关闭,对应 .c 文件留在工程中通常也不会产生完整功能代码,因为源码内部会根据配置宏进行条件编译。

5.2 添加头文件搜索路径

在 IAR 中进入:

Project → Options → C/C++ Compiler → Preprocessor

将以下路径加入 Additional include directories

$PROJ_DIR$\..\Middlewares\Third_Party\Micrium\Config
$PROJ_DIR$\..\Middlewares\Third_Party\Micrium\uCOS-II\Source
$PROJ_DIR$\..\Middlewares\Third_Party\Micrium\uCOS-II\Ports\ARM-Cortex-M4\IAR
$PROJ_DIR$\..\Middlewares\Third_Party\Micrium\uC-CPU
$PROJ_DIR$\..\Middlewares\Third_Party\Micrium\uC-CPU\ARM-Cortex-M4\IAR
$PROJ_DIR$\..\Middlewares\Third_Party\Micrium\uC-LIB

如果编译提示找不到 ucos_ii.hos_cpu.hcpu.hlib_def.h,应首先检查这些搜索路径。


6. 内核和应用配置

6.1 app_cfg.h

当前应用任务配置为:

#define APP_TASK_START_PRIO       5u
#define APP_TASK_TEST_PRIO        6u

#define APP_TASK_START_STK_SIZE   256u
#define APP_TASK_TEST_STK_SIZE    256u

其中:

  • APP_TASK_START_PRIO 是启动任务优先级;
  • APP_TASK_TEST_PRIO 是预留的测试任务优先级,当前还未创建该任务;
  • APP_TASK_START_STK_SIZE 是启动任务栈的元素个数,不是字节数;
  • Cortex-M4 移植中 OS_STK 为 32 位,因此 256 个栈单元约占 1024 字节 RAM。

6.2 uC/OS-II 的任务优先级规则

uC/OS-II V2 中:

  • 数值越小,任务优先级越高;
  • 每个任务必须使用不同的优先级;
  • 当前 AppTaskStart 使用优先级 5;
  • 后续可让普通测试任务使用 6、7 等更低优先级;
  • 不建议随意占用 0~4,可为以后对实时性要求更高的任务预留。

任务优先级 5 和 NVIC 中断优先级 5 是两个不同体系,不能直接比较大小。

6.3 os_cfg.h 的当前关键配置

配置项 当前值 含义
OS_LOWEST_PRIO 63 应用可用的最低任务优先级范围
OS_MAX_TASKS 10 最大应用任务数量
OS_TICKS_PER_SEC 1000 每秒 1000 个系统节拍
OS_TASK_CREATE_EXT_EN 1 启用 OSTaskCreateExt()
OS_TASK_STAT_EN 0 不启用统计任务
OS_MUTEX_EN 1 启用互斥信号量
OS_Q_EN 1 启用消息队列
OS_SEM_EN 1 启用信号量
OS_TIME_GET_SET_EN 1 允许读取和设置系统时间
OS_TIME_TICK_HOOK_EN 1 启用节拍钩子
OS_TMR_EN 0 不启用软件定时器
OS_MEM_EN 0 不启用固定内存分区
OS_CPU_HOOKS_EN 1 使用 CPU 移植层钩子
OS_DEBUG_EN 1 保留内核调试信息

因为:

#define OS_TICKS_PER_SEC 1000u

所以:

OSTimeDly(1U);       /* 延时约 1 ms */
OSTimeDly(100U);     /* 延时约 100 ms */
OSTimeDly(1000U);    /* 延时约 1 s */

OSTimeDly() 的参数单位是“节拍”,只有在当前 1000 Hz 配置下才刚好等于毫秒。


7. SysTick 时基接入

7.1 为什么 HAL 和 uC/OS-II 都需要 SysTick

STM32 HAL 使用 SysTick 维护自己的毫秒计数,HAL_GetTick()、HAL 超时判断等功能依赖 HAL_IncTick()

uC/OS-II 也需要周期节拍,用于:

  • 更新任务延时;
  • 唤醒延时到期的任务;
  • 更新 OSTimeGet() 返回的系统节拍;
  • 在需要时触发任务调度。

因此,本工程在同一个 SysTick_Handler() 中同时服务 HAL 和 uC/OS-II。

7.2 当前 SysTick 处理

Core/Src/stm32f4xx_it.c 中的核心代码为:

void SysTick_Handler(void)
{
    HAL_IncTick();

    if (OSRunning == OS_TRUE)
    {
        OS_CPU_SysTickHandler();
    }
}

必须保留 OSRunning 判断。原因是 HAL_Init() 会先启动 SysTick,而此时 OSInit() 尚未执行。如果在内核未初始化时直接调用 uC/OS-II 的节拍处理,可能访问尚未准备好的内核状态。

OS_CPU_SysTickHandler() 内部的主要过程是:

进入 SysTick 中断
    ↓
OSIntEnter()
    ↓
OSTimeTick():更新延时和系统节拍
    ↓
OSIntExit():检查是否需要调度更高优先级任务

不能只调用 OS_CPU_SysTickHandler() 而删除 HAL_IncTick(),否则 HAL 的毫秒时基和超时机制会停止。


8. PendSV 与任务上下文切换

8.1 PendSV 的作用

SysTick 负责告诉内核“时间过去了一个节拍”,PendSV 才真正执行任务上下文切换。

一次任务切换大致包括:

  1. 保存当前任务的 CPU 寄存器;
  2. 将当前栈指针保存到任务控制块;
  3. 选择当前最高优先级的就绪任务;
  4. 取出新任务的栈指针;
  5. 恢复新任务的 CPU 寄存器;
  6. 从异常返回,新任务继续运行。

这些底层工作由 os_cpu_a.asm 中的 OS_CPU_PendSVHandler 完成。

8.2 当前工程的特殊接法

当前工程不是在 C 语言的空 PendSV_Handler() 中调用内核,而是直接修改了 IAR 启动文件:

EWARM/startup_stm32f407xx.s

启动文件中声明:

EXTERN  OS_CPU_PendSVHandler

并将向量表中的 PendSV 项直接设置为:

DCD     OS_CPU_PendSVHandler

所以 Core/Src/stm32f4xx_it.c 中虽然还有一个空的 PendSV_Handler(),实际 PendSV 向量并不指向它。

这是本工程移植中非常关键的一步。若 CubeMX 或其他工具重新生成、替换启动文件,必须确认 PendSV 向量仍然指向 OS_CPU_PendSVHandler。否则常见现象是:

  • 第一个任务似乎能够启动;
  • 一旦调用 OSTimeDly() 或发生任务切换就卡住;
  • 多任务无法正常调度。

不要同时让向量表指向两个 PendSV 实现,也不要在已有直接向量映射的情况下重复定义另一个同名强符号。


9. 在 main.c 中启动 uC/OS-II

9.1 包含头文件

当前应用包含:

#include "ucos_ii.h"
#include "modbus_rtu_slave.h"

ucos_ii.h 内部会继续包含 app_cfg.h,因此 main.c 可以使用任务优先级和栈大小宏。

9.2 定义任务栈

static OS_STK AppTaskStartStk[APP_TASK_START_STK_SIZE];

任务栈不能定义成任务函数内的普通局部数组,否则函数退出或栈被覆盖后会破坏任务运行现场。这里使用 static,使内存在整个程序运行期间一直存在。

9.3 声明任务函数

static void AppTaskStart(void *p_arg);

uC/OS-II 任务函数必须符合以下形式:

void TaskName(void *p_arg);

任务通常包含永久循环,不能执行完后直接返回。

9.4 正确的初始化顺序

本工程顺序为:

HAL_Init();
SystemClock_Config();

MX_GPIO_Init();
MX_DMA_Init();
MX_USB_DEVICE_Init();
MX_USART1_UART_Init();

OSInit();

/* 创建应用任务 */
OSTaskCreateExt(...);

OSStart();

各步骤含义如下:

  1. HAL_Init():初始化 HAL 和 SysTick;
  2. SystemClock_Config():配置芯片主频和总线时钟;
  3. MX_xxx_Init():初始化任务要使用的外设;
  4. OSInit():初始化 uC/OS-II 内核数据结构;
  5. OSTaskCreateExt():创建至少一个应用任务;
  6. OSStart():启动最高优先级就绪任务。

正常情况下 OSStart() 不会返回。CubeMX 在后面生成的 while (1) 仅作为程序结构保留,操作系统成功启动后不会再执行到那里。

9.5 当前任务创建代码

os_err = OSTaskCreateExt(AppTaskStart,
                         0,
                         &AppTaskStartStk[APP_TASK_START_STK_SIZE - 1u],
                         APP_TASK_START_PRIO,
                         APP_TASK_START_PRIO,
                         &AppTaskStartStk[0],
                         APP_TASK_START_STK_SIZE,
                         0,
                         (OS_TASK_OPT_STK_CHK |
                          OS_TASK_OPT_STK_CLR));

if (os_err != OS_ERR_NONE)
{
    Error_Handler();
}

参数说明:

参数 当前传入值 作用
任务入口 AppTaskStart 任务开始执行的函数
任务参数 0 传给 p_arg,本任务不需要参数
初始栈顶 栈数组最后一个元素 Cortex-M 栈向低地址增长
优先级 APP_TASK_START_PRIO 当前为 5
任务 ID 同任务优先级 本工程用优先级作为 ID
栈底 栈数组第一个元素 用于扩展创建和栈检查
栈大小 APP_TASK_START_STK_SIZE 256 个栈单元
扩展数据 0 当前不使用
任务选项 STK_CHK \| STK_CLR 启用栈检查并在创建时清零栈

检查 os_err 非常重要。如果任务创建失败却继续调用 OSStart(),后续现象不容易定位。


10. 当前应用任务与 Modbus 的结合

当前启动任务主要代码如下:

static void AppTaskStart(void *p_arg)
{
    (void)p_arg;

    if (ModbusSlave_Init(&huart1,
                         MODBUS_SLAVE_DEFAULT_ADDRESS) != HAL_OK)
    {
        Error_Handler();
    }

    while (1)
    {
        (void)ModbusSlave_SetHoldingRegister(
            HMI_REG_UPTIME_SECONDS,
            (uint16_t)(OSTimeGet() / OS_TICKS_PER_SEC));

        (void)ModbusSlave_SetHoldingRegister(
            HMI_REG_RX_FRAME_COUNT,
            (uint16_t)g_modbus_slave_stats.valid_frame_count);

        ModbusSlave_Poll();
        OSTimeDly(1U);
    }
}

10.1 任务在做什么

  1. 任务只在开始时调用一次 ModbusSlave_Init()
  2. 初始化 USART1 的 Modbus 从站和 DMA 接收;
  3. 在永久循环中更新运行时间寄存器;
  4. 更新有效接收帧计数寄存器;
  5. 调用 ModbusSlave_Poll() 在任务上下文中解析已收到的帧;
  6. 延时 1 个节拍,把 CPU 让给其他任务或空闲任务。

10.2 为什么要调用 OSTimeDly(1U)

如果永久循环内没有延时、等待信号量或其他阻塞操作,优先级 5 的任务会一直占用 CPU,使更低优先级任务无法运行。

调用 OSTimeDly(1U) 后:

  • 当前任务进入延时状态;
  • 调度器转而执行其他就绪任务;
  • 1 ms 后 SysTick 更新延时计数;
  • 当前任务重新进入就绪状态;
  • 当它成为最高优先级就绪任务时继续运行。

11. 串口中断、DMA 与操作系统任务的关系

当前 Modbus 接收采用“中断/DMA 快速接收,任务中解析”的结构:

USART1 收到数据
      ↓
DMA 自动搬运到接收缓冲区
      ↓
串口空闲事件/接收事件中断
      ↓
HAL_UARTEx_RxEventCallback()
      ↓
ModbusSlave_OnRxEvent()
      │
      │ 仅记录数据、帧状态并尽快退出
      ▼
AppTaskStart 周期执行 ModbusSlave_Poll()
      ↓
CRC 校验、功能码解析、寄存器读写、组织应答
      ↓
USART1 DMA 发送应答

当前回调接口包括:

HAL_UARTEx_RxEventCallback()
    → ModbusSlave_OnRxEvent()

HAL_UART_TxCpltCallback()
    → ModbusSlave_OnTxComplete()

HAL_UART_ErrorCallback()
    → ModbusSlave_OnUartError()

这种结构的优点是:

  • 中断处理时间短;
  • CRC 和协议解析不会长时间占用中断;
  • 复杂业务逻辑运行在任务上下文,便于扩展;
  • 串口接收与协议处理在执行过程上是异步的。

当前串口回调没有直接调用 uC/OS-II 的信号量、队列等服务,因此不需要额外在这些回调外层手工调用 OSIntEnter()OSIntExit()

如果后续在外设中断中直接调用 OSSemPost()OSQPost() 等内核服务,则必须:

  • 确认该服务允许在中断中调用;
  • 正确执行 uC/OS-II 的中断进入和退出处理;
  • 检查 NVIC 中断优先级是否满足移植层要求;
  • 不在中断中调用会阻塞的接口。

12. 常用 uC/OS-II 接口解释

12.1 OSInit()

初始化内核全局变量、就绪表、任务链表,并建立空闲任务等内部对象。必须先调用它,再创建应用任务和启动调度器。

12.2 OSTaskCreateExt()

创建一个扩展任务。与简化版创建函数相比,它可以传入栈底、栈大小和任务选项,因此可使用栈检查功能。

12.3 OSStart()

启动调度器,并切换到当前最高优先级的就绪任务。成功后不会返回 main() 的普通执行流程。

12.4 OSTimeDly()

使当前任务延时指定节拍。它是操作系统延时,不是原地空转:

  • 延时期间 CPU 可以执行其他任务;
  • 不能在中断服务程序中调用;
  • 参数为 0 时通常不会产生期望的延时。

12.5 OSTimeGet()

读取内核从启动后累计的节拍数。当前节拍频率为 1000 Hz,因此:

seconds = OSTimeGet() / OS_TICKS_PER_SEC;

可得到近似运行秒数。计数最终会溢出并回绕,不能把它当作永久不溢出的实时时钟。

12.6 OSTaskStkChk()

在任务使用了 OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR 时,可以检查任务栈使用情况,用于判断栈是否配置过大或过小。


13. 后续增加第二个任务

13.1 定义栈和任务函数

static OS_STK AppTaskTestStk[APP_TASK_TEST_STK_SIZE];

static void AppTaskTest(void *p_arg)
{
    (void)p_arg;

    while (1)
    {
        /* 在这里编写周期性测试功能。 */

        OSTimeDly(100U);
    }
}

13.2 创建任务

在调用 OSStart() 之前增加:

os_err = OSTaskCreateExt(AppTaskTest,
                         0,
                         &AppTaskTestStk[APP_TASK_TEST_STK_SIZE - 1u],
                         APP_TASK_TEST_PRIO,
                         APP_TASK_TEST_PRIO,
                         &AppTaskTestStk[0],
                         APP_TASK_TEST_STK_SIZE,
                         0,
                         (OS_TASK_OPT_STK_CHK |
                          OS_TASK_OPT_STK_CLR));

if (os_err != OS_ERR_NONE)
{
    Error_Handler();
}

也可以先只创建启动任务,再由启动任务创建其他任务。但无论采用哪种方式,都必须保证:

  • 每个任务有独立且长期有效的栈;
  • 每个任务优先级唯一;
  • 任务函数不能直接返回;
  • 周期任务必须适当延时或等待事件;
  • 总任务数不得超过 OS_MAX_TASKS

14. CubeMX 重新生成代码时的注意事项

14.1 能够自动保留的内容

CubeMX 的 Keep User Code when re-generating 选项只能保留位于标准标记内的代码:

/* USER CODE BEGIN ... */
/* USER CODE END ... */

本工程下列自定义内容应放在对应的 USER CODE 区域内:

  • main.c 中的 uC/OS-II 和 Modbus 头文件;
  • 任务栈、任务函数声明;
  • AppTaskStart()
  • OSInit()、任务创建和 OSStart()
  • stm32f4xx_it.c 中的 OS_CPU_SysTickHandler() 调用;
  • HAL 串口回调中的 Modbus 接口调用。

写在 USER CODE 标记之外的修改可能被覆盖。

14.2 需要手工重点检查的内容

每次 CubeMX 重新生成后,至少检查:

  • EWARM/startup_stm32f407xx.s 的 PendSV 向量是否仍指向 OS_CPU_PendSVHandler
  • stm32f4xx_it.c 是否仍调用 HAL_IncTick()
  • stm32f4xx_it.c 是否在 OSRunning 为真时调用 OS_CPU_SysTickHandler()
  • main.c 是否仍然调用 OSInit()、创建任务并调用 OSStart()
  • IAR 工程中的 Micrium 源文件分组是否仍存在;
  • IAR 的 Micrium 头文件搜索路径是否仍存在;
  • 串口 DMA、NVIC 和回调代码是否仍完整。

CubeMX 通常不会主动删除独立放置的 Middlewares/Third_Party/Micrium 源文件,但重新生成 IAR 工程文件时,有可能改变 .ewp 中手工添加的分组或路径,因此重新生成前应提交 Git 或备份工程。


15. 编译和运行验证

15.1 IAR 图形界面

在 IAR 中执行:

Project → Rebuild All

确认:

  • 0 errors;
  • 0 warnings;
  • os_cpu_a.asmos_cpu_c.c 和内核源码均参与编译;
  • 链接阶段没有重复的中断处理函数。

15.2 命令行完整编译

在工程根目录执行:

& 'F:\IAR Systems\Embedded Workbench 8.3\common\bin\IarBuild.exe' `
  'EWARM\Modbus.ewp' `
  -build Modbus `
  -log all

链接映射文件位于:

EWARM\Modbus\List\Modbus.map

该文件可以查看代码、只读数据和读写数据的详细占用。

15.3 调试时可观察的内容

建议设置或观察:

  • AppTaskStart() 入口设置断点,确认任务已进入;
  • OSRunning 应在内核启动后变为真;
  • OSTimeGet() 应持续增加;
  • 执行 OSTimeDly(1U) 后任务应能再次被唤醒;
  • Modbus 运行时间寄存器应每秒变化;
  • 串口请求应能收到正常应答。

16. 常见故障排查

16.1 提示找不到 ucos_ii.h

原因通常是 IAR 头文件搜索路径未添加或路径层级错误。检查:

Middlewares\Third_Party\Micrium\uCOS-II\Source

16.2 OSStart() 后卡死或任务不运行

依次检查:

  1. 是否先调用了 OSInit()
  2. OSTaskCreateExt() 返回值是否为 OS_ERR_NONE
  3. 任务栈顶是否传入数组最后一个元素;
  4. os_cpu_a.asm 是否参与编译和链接;
  5. 启动文件 PendSV 向量是否指向 OS_CPU_PendSVHandler
  6. 任务函数是否包含永久循环。

16.3 OSTimeDly() 后任务再也不运行

重点检查:

  • SysTick 中断是否正常进入;
  • 是否保留 OS_CPU_SysTickHandler()
  • OSRunning 是否已经变为真;
  • OS_TICKS_PER_SEC 是否与实际 SysTick 频率匹配;
  • PendSV 向量是否被 CubeMX 恢复成空的 PendSV_Handler()

16.4 HAL 延时或超时异常

检查 SysTick_Handler() 中是否仍调用:

HAL_IncTick();

uC/OS-II 和 HAL 共用 SysTick 时,两边的节拍处理都要保留。

16.5 链接时出现重复的 PendSV 或 SysTick 符号

说明同一异常被多个强符号实现。应明确采用一种连接方式。

当前工程采用:

  • SysTick 向量指向 SysTick_Handler(),其中同时服务 HAL 和 uC/OS-II;
  • PendSV 向量直接指向汇编函数 OS_CPU_PendSVHandler

16.6 任务运行一段时间后进入 HardFault

常见原因包括:

  • 任务栈过小;
  • 使用了错误的 CPU/编译器移植层;
  • 栈顶和栈底参数传反;
  • 局部数组过大;
  • 中断或任务越界写内存;
  • 浮点上下文或编译选项与移植层不匹配。

应先增加任务栈并使用栈检查,再检查 HardFault 时的栈和寄存器。

16.7 高优先级任务导致其他任务不运行

uC/OS-II 是基于优先级的抢占式内核。若高优先级任务永久循环且从不延时、不挂起、也不等待事件,低优先级任务不会得到运行机会。

周期任务中应使用:

  • OSTimeDly()
  • 等待信号量;
  • 等待消息队列;
  • 等待事件标志;
  • 或其他会让任务进入非就绪状态的接口。

16.8 修改了配置宏后出现未定义符号

例如打开信号量、队列、统计任务或软件定时器后,应同时确认:

  • 对应源码已加入工程;
  • 相关最大数量配置不为 0;
  • 所需钩子函数已经实现;
  • RAM 和任务数量足够。

17. 当前移植的关键文件清单

文件 当前项目中的关键内容
Core/Src/main.c 内核初始化、任务创建、启动调度器、Modbus 应用任务
Core/Src/stm32f4xx_it.c HAL 与 uC/OS-II 共用的 SysTick 处理
EWARM/startup_stm32f407xx.s PendSV 向量直接指向 OS_CPU_PendSVHandler
EWARM/Modbus.ewp Micrium 源文件分组及头文件搜索路径
Middlewares/Third_Party/Micrium/Config/app_cfg.h 应用任务优先级和栈大小
Middlewares/Third_Party/Micrium/Config/os_cfg.h 内核裁剪、节拍频率和资源数量
Middlewares/Third_Party/Micrium/uCOS-II/Ports/ARM-Cortex-M4/IAR/os_cpu_a.asm 上下文切换
Middlewares/Third_Party/Micrium/uCOS-II/Ports/ARM-Cortex-M4/IAR/os_cpu_c.c 任务栈初始化及 SysTick 接口
Modbus/Src/modbus_rtu_slave.c Modbus RTU 从站协议处理

18. 移植完成检查表

源码与工程

内核配置

中断连接

应用启动

运行验证


19. 关于 uC/OS-II 授权

当前源码头部的授权说明允许评估、教学或研究等用途。若代码用于正式商业产品,应根据所使用版本核实 Micrium/版权方的商业授权要求,不能仅因为源码可以编译就默认具备商业发布许可。


20. 总结

本工程的 uC/OS-II 移植可以概括为:

复制内核和 Cortex-M4/IAR 移植层
        ↓
加入 IAR 源文件与头文件路径
        ↓
配置 os_cfg.h 和 app_cfg.h
        ↓
SysTick 同时服务 HAL 与 uC/OS-II
        ↓
启动文件 PendSV 向量接入 OS_CPU_PendSVHandler
        ↓
OSInit → 创建任务 → OSStart
        ↓
在任务中运行 ModbusSlave_Poll 和应用逻辑

其中最容易在 CubeMX 重新生成后遗漏的是 IAR 启动文件中的 PendSV 向量修改;最容易在增加任务后遇到的问题是 任务优先级重复、任务栈不足,以及高优先级任务没有延时或等待事件