|
|
@@ -0,0 +1,935 @@ |
|
|
|
|
|
# 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 命令行编译工具位于: |
|
|
|
|
|
|
|
|
|
|
|
```text |
|
|
|
|
|
F:\IAR Systems\Embedded Workbench 8.3\common\bin |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
## 3. 移植工作的本质 |
|
|
|
|
|
|
|
|
|
|
|
将 uC/OS-II 移植到一个新的 MCU 工程,不只是把源码复制进来。完整移植至少包含以下五部分: |
|
|
|
|
|
|
|
|
|
|
|
1. **内核源码**:任务调度、延时、信号量、消息队列等通用功能; |
|
|
|
|
|
2. **CPU 移植层**:任务栈初始化、临界区、寄存器保存和恢复; |
|
|
|
|
|
3. **工程配置**:选择启用哪些内核功能、允许多少任务、系统节拍频率等; |
|
|
|
|
|
4. **硬件连接**:将 SysTick 和 PendSV 正确接入操作系统; |
|
|
|
|
|
5. **应用启动**:初始化内核、创建任务并启动调度器。 |
|
|
|
|
|
|
|
|
|
|
|
它们的关系如下: |
|
|
|
|
|
|
|
|
|
|
|
```text |
|
|
|
|
|
应用任务 AppTaskStart |
|
|
|
|
|
│ |
|
|
|
|
|
│ 调用 OSTimeDly、OSTimeGet 等 API |
|
|
|
|
|
▼ |
|
|
|
|
|
uC/OS-II 内核源码 |
|
|
|
|
|
│ |
|
|
|
|
|
│ 请求任务切换、初始化任务栈 |
|
|
|
|
|
▼ |
|
|
|
|
|
Cortex-M4 + IAR 移植层 |
|
|
|
|
|
│ |
|
|
|
|
|
├── SysTick:产生操作系统节拍 |
|
|
|
|
|
└── PendSV:保存旧任务现场并恢复新任务现场 |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
## 4. 工程中的 uC/OS-II 目录 |
|
|
|
|
|
|
|
|
|
|
|
当前相关文件位于: |
|
|
|
|
|
|
|
|
|
|
|
```text |
|
|
|
|
|
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-CPU` 和 `uC-LIB` 目录还提供 Micrium 的 CPU 抽象和通用库头文件。本工程已将相应目录加入头文件搜索路径。 |
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
## 5. IAR 工程配置 |
|
|
|
|
|
|
|
|
|
|
|
### 5.1 添加源码分组 |
|
|
|
|
|
|
|
|
|
|
|
在 IAR 工程中建立 `Micrium` 分组,并添加下列内容。 |
|
|
|
|
|
|
|
|
|
|
|
`Micrium Config`: |
|
|
|
|
|
|
|
|
|
|
|
```text |
|
|
|
|
|
app_cfg.h |
|
|
|
|
|
cpu_cfg.h |
|
|
|
|
|
os_cfg.h |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
`uCOS-II Port`: |
|
|
|
|
|
|
|
|
|
|
|
```text |
|
|
|
|
|
os_cpu_a.asm |
|
|
|
|
|
os_cpu_c.c |
|
|
|
|
|
os_dbg.c |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
`uCOS-II Source`: |
|
|
|
|
|
|
|
|
|
|
|
```text |
|
|
|
|
|
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 中进入: |
|
|
|
|
|
|
|
|
|
|
|
```text |
|
|
|
|
|
Project → Options → C/C++ Compiler → Preprocessor |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
将以下路径加入 `Additional include directories`: |
|
|
|
|
|
|
|
|
|
|
|
```text |
|
|
|
|
|
$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.h`、`os_cpu.h`、`cpu.h` 或 `lib_def.h`,应首先检查这些搜索路径。 |
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
## 6. 内核和应用配置 |
|
|
|
|
|
|
|
|
|
|
|
### 6.1 `app_cfg.h` |
|
|
|
|
|
|
|
|
|
|
|
当前应用任务配置为: |
|
|
|
|
|
|
|
|
|
|
|
```c |
|
|
|
|
|
#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 | 保留内核调试信息 | |
|
|
|
|
|
|
|
|
|
|
|
因为: |
|
|
|
|
|
|
|
|
|
|
|
```c |
|
|
|
|
|
#define OS_TICKS_PER_SEC 1000u |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
所以: |
|
|
|
|
|
|
|
|
|
|
|
```c |
|
|
|
|
|
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` 中的核心代码为: |
|
|
|
|
|
|
|
|
|
|
|
```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()` 内部的主要过程是: |
|
|
|
|
|
|
|
|
|
|
|
```text |
|
|
|
|
|
进入 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 启动文件: |
|
|
|
|
|
|
|
|
|
|
|
```text |
|
|
|
|
|
EWARM/startup_stm32f407xx.s |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
启动文件中声明: |
|
|
|
|
|
|
|
|
|
|
|
```asm |
|
|
|
|
|
EXTERN OS_CPU_PendSVHandler |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
并将向量表中的 PendSV 项直接设置为: |
|
|
|
|
|
|
|
|
|
|
|
```asm |
|
|
|
|
|
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 包含头文件 |
|
|
|
|
|
|
|
|
|
|
|
当前应用包含: |
|
|
|
|
|
|
|
|
|
|
|
```c |
|
|
|
|
|
#include "ucos_ii.h" |
|
|
|
|
|
#include "modbus_rtu_slave.h" |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
`ucos_ii.h` 内部会继续包含 `app_cfg.h`,因此 `main.c` 可以使用任务优先级和栈大小宏。 |
|
|
|
|
|
|
|
|
|
|
|
### 9.2 定义任务栈 |
|
|
|
|
|
|
|
|
|
|
|
```c |
|
|
|
|
|
static OS_STK AppTaskStartStk[APP_TASK_START_STK_SIZE]; |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
任务栈不能定义成任务函数内的普通局部数组,否则函数退出或栈被覆盖后会破坏任务运行现场。这里使用 `static`,使内存在整个程序运行期间一直存在。 |
|
|
|
|
|
|
|
|
|
|
|
### 9.3 声明任务函数 |
|
|
|
|
|
|
|
|
|
|
|
```c |
|
|
|
|
|
static void AppTaskStart(void *p_arg); |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
uC/OS-II 任务函数必须符合以下形式: |
|
|
|
|
|
|
|
|
|
|
|
```c |
|
|
|
|
|
void TaskName(void *p_arg); |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
任务通常包含永久循环,不能执行完后直接返回。 |
|
|
|
|
|
|
|
|
|
|
|
### 9.4 正确的初始化顺序 |
|
|
|
|
|
|
|
|
|
|
|
本工程顺序为: |
|
|
|
|
|
|
|
|
|
|
|
```c |
|
|
|
|
|
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 当前任务创建代码 |
|
|
|
|
|
|
|
|
|
|
|
```c |
|
|
|
|
|
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 的结合 |
|
|
|
|
|
|
|
|
|
|
|
当前启动任务主要代码如下: |
|
|
|
|
|
|
|
|
|
|
|
```c |
|
|
|
|
|
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 快速接收,任务中解析”的结构: |
|
|
|
|
|
|
|
|
|
|
|
```text |
|
|
|
|
|
USART1 收到数据 |
|
|
|
|
|
↓ |
|
|
|
|
|
DMA 自动搬运到接收缓冲区 |
|
|
|
|
|
↓ |
|
|
|
|
|
串口空闲事件/接收事件中断 |
|
|
|
|
|
↓ |
|
|
|
|
|
HAL_UARTEx_RxEventCallback() |
|
|
|
|
|
↓ |
|
|
|
|
|
ModbusSlave_OnRxEvent() |
|
|
|
|
|
│ |
|
|
|
|
|
│ 仅记录数据、帧状态并尽快退出 |
|
|
|
|
|
▼ |
|
|
|
|
|
AppTaskStart 周期执行 ModbusSlave_Poll() |
|
|
|
|
|
↓ |
|
|
|
|
|
CRC 校验、功能码解析、寄存器读写、组织应答 |
|
|
|
|
|
↓ |
|
|
|
|
|
USART1 DMA 发送应答 |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
当前回调接口包括: |
|
|
|
|
|
|
|
|
|
|
|
```c |
|
|
|
|
|
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,因此: |
|
|
|
|
|
|
|
|
|
|
|
```c |
|
|
|
|
|
seconds = OSTimeGet() / OS_TICKS_PER_SEC; |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
可得到近似运行秒数。计数最终会溢出并回绕,不能把它当作永久不溢出的实时时钟。 |
|
|
|
|
|
|
|
|
|
|
|
### 12.6 `OSTaskStkChk()` |
|
|
|
|
|
|
|
|
|
|
|
在任务使用了 `OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR` 时,可以检查任务栈使用情况,用于判断栈是否配置过大或过小。 |
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
## 13. 后续增加第二个任务 |
|
|
|
|
|
|
|
|
|
|
|
### 13.1 定义栈和任务函数 |
|
|
|
|
|
|
|
|
|
|
|
```c |
|
|
|
|
|
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()` 之前增加: |
|
|
|
|
|
|
|
|
|
|
|
```c |
|
|
|
|
|
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` 选项只能保留位于标准标记内的代码: |
|
|
|
|
|
|
|
|
|
|
|
```c |
|
|
|
|
|
/* 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 中执行: |
|
|
|
|
|
|
|
|
|
|
|
```text |
|
|
|
|
|
Project → Rebuild All |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
确认: |
|
|
|
|
|
|
|
|
|
|
|
- 0 errors; |
|
|
|
|
|
- 0 warnings; |
|
|
|
|
|
- `os_cpu_a.asm`、`os_cpu_c.c` 和内核源码均参与编译; |
|
|
|
|
|
- 链接阶段没有重复的中断处理函数。 |
|
|
|
|
|
|
|
|
|
|
|
### 15.2 命令行完整编译 |
|
|
|
|
|
|
|
|
|
|
|
在工程根目录执行: |
|
|
|
|
|
|
|
|
|
|
|
```powershell |
|
|
|
|
|
& 'F:\IAR Systems\Embedded Workbench 8.3\common\bin\IarBuild.exe' ` |
|
|
|
|
|
'EWARM\Modbus.ewp' ` |
|
|
|
|
|
-build Modbus ` |
|
|
|
|
|
-log all |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
链接映射文件位于: |
|
|
|
|
|
|
|
|
|
|
|
```text |
|
|
|
|
|
EWARM\Modbus\List\Modbus.map |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
该文件可以查看代码、只读数据和读写数据的详细占用。 |
|
|
|
|
|
|
|
|
|
|
|
### 15.3 调试时可观察的内容 |
|
|
|
|
|
|
|
|
|
|
|
建议设置或观察: |
|
|
|
|
|
|
|
|
|
|
|
- 在 `AppTaskStart()` 入口设置断点,确认任务已进入; |
|
|
|
|
|
- `OSRunning` 应在内核启动后变为真; |
|
|
|
|
|
- `OSTimeGet()` 应持续增加; |
|
|
|
|
|
- 执行 `OSTimeDly(1U)` 后任务应能再次被唤醒; |
|
|
|
|
|
- Modbus 运行时间寄存器应每秒变化; |
|
|
|
|
|
- 串口请求应能收到正常应答。 |
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
## 16. 常见故障排查 |
|
|
|
|
|
|
|
|
|
|
|
### 16.1 提示找不到 `ucos_ii.h` |
|
|
|
|
|
|
|
|
|
|
|
原因通常是 IAR 头文件搜索路径未添加或路径层级错误。检查: |
|
|
|
|
|
|
|
|
|
|
|
```text |
|
|
|
|
|
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()` 中是否仍调用: |
|
|
|
|
|
|
|
|
|
|
|
```c |
|
|
|
|
|
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. 移植完成检查表 |
|
|
|
|
|
|
|
|
|
|
|
### 源码与工程 |
|
|
|
|
|
|
|
|
|
|
|
- [ ] uC/OS-II 内核源码已加入 IAR 工程; |
|
|
|
|
|
- [ ] Cortex-M4/IAR 移植层已加入工程; |
|
|
|
|
|
- [ ] Micrium 配置文件已加入工程; |
|
|
|
|
|
- [ ] 所有头文件搜索路径正确; |
|
|
|
|
|
- [ ] `os_cpu_a.asm` 确实参与编译。 |
|
|
|
|
|
|
|
|
|
|
|
### 内核配置 |
|
|
|
|
|
|
|
|
|
|
|
- [ ] `OS_TICKS_PER_SEC` 与 SysTick 实际频率一致; |
|
|
|
|
|
- [ ] `OS_MAX_TASKS` 足够; |
|
|
|
|
|
- [ ] 每个任务优先级唯一; |
|
|
|
|
|
- [ ] 所需信号量、队列、互斥量功能已启用; |
|
|
|
|
|
- [ ] 每个任务栈大小合理。 |
|
|
|
|
|
|
|
|
|
|
|
### 中断连接 |
|
|
|
|
|
|
|
|
|
|
|
- [ ] SysTick 同时调用 `HAL_IncTick()` 和 `OS_CPU_SysTickHandler()`; |
|
|
|
|
|
- [ ] 内核未运行时不会调用 OS 节拍处理; |
|
|
|
|
|
- [ ] PendSV 向量指向 `OS_CPU_PendSVHandler`; |
|
|
|
|
|
- [ ] 没有重复的 PendSV 强符号。 |
|
|
|
|
|
|
|
|
|
|
|
### 应用启动 |
|
|
|
|
|
|
|
|
|
|
|
- [ ] 外设初始化先于使用外设的任务; |
|
|
|
|
|
- [ ] 已调用 `OSInit()`; |
|
|
|
|
|
- [ ] 至少成功创建一个任务; |
|
|
|
|
|
- [ ] 已检查任务创建返回值; |
|
|
|
|
|
- [ ] 已调用 `OSStart()`; |
|
|
|
|
|
- [ ] 所有任务均包含循环并能主动让出 CPU。 |
|
|
|
|
|
|
|
|
|
|
|
### 运行验证 |
|
|
|
|
|
|
|
|
|
|
|
- [ ] 工程编译为 0 errors、0 warnings; |
|
|
|
|
|
- [ ] `AppTaskStart()` 可以进入; |
|
|
|
|
|
- [ ] `OSRunning` 为真; |
|
|
|
|
|
- [ ] 系统节拍持续增加; |
|
|
|
|
|
- [ ] `OSTimeDly()` 后任务可以恢复; |
|
|
|
|
|
- [ ] USART1 DMA 通信正常; |
|
|
|
|
|
- [ ] Modbus 请求和应答正常。 |
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
## 19. 关于 uC/OS-II 授权 |
|
|
|
|
|
|
|
|
|
|
|
当前源码头部的授权说明允许评估、教学或研究等用途。若代码用于正式商业产品,应根据所使用版本核实 Micrium/版权方的商业授权要求,不能仅因为源码可以编译就默认具备商业发布许可。 |
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
## 20. 总结 |
|
|
|
|
|
|
|
|
|
|
|
本工程的 uC/OS-II 移植可以概括为: |
|
|
|
|
|
|
|
|
|
|
|
```text |
|
|
|
|
|
复制内核和 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 向量修改**;最容易在增加任务后遇到的问题是 **任务优先级重复、任务栈不足,以及高优先级任务没有延时或等待事件**。 |