文章目录
- 1. 任务调度器
- 2. UCOSIII 移植过程
- 0. 准备
- 1. 在基础工程的 Middlewares 文件夹中新建一个 uC-OS3 子文件夹
- 2. 将uCOSIII 源代码的3个文件夹都拷贝到该文件夹中
- 3. 打开基础工程,新建4个分组
- 4. 在分组Middlewares/uC-OS3/BSP 中添加以下源文件
- 5. 在分组Middlewares/uC-OS3/CPU 中添加以下源文件
- 6. 在分组Middlewares/uC-OS3/LIB 中添加以下源文件
- 7. 在分组Middlewares/uC-OS3/OS3中添加以下源文件
- 8. 包含头文件路径
- 9. 修改System 文件中的sys.h、usart.c、delay.c
- 10. 在`stm32f1xx_it.c`和`stm32f1xx_it.h`中注释下面代码,避免中断服务函数定义重复异常
- 11. 在头文件`cpu_cfg_h`中修改宏定义
- 12. 添加4个配置文件
- 13. 最后工作
- 14. 添加uCOSIII 任务,验证是否成功移植
- - 备份移植完成工程
- 3. 系统配置文件说明
- 4. 任务创建与删除
-
μCos-Ⅲ全称是Micro C OS Ⅲ,由Micriμm 公司发布的一个基于C 语言编写的第三代小型实时操作系统(RTOS);
-
RTOS 与裸机相比最大的优势在于多任务管理与实时性,它提供了多任务管理和任务间通信的功能;
-
μCOS-Ⅲ与FreeRTOS的区别:μCOS-III 的源码可读性比较强,代码写的非常规范。国内资料较多;
-
RTOS 的任务调度结构:如下图,高优先级任务能抢占低优先级任务,而中断能打断任意任务,每个任务都有自己的任务堆栈,用于保存任务的寄存器值;除非高优先级任务挂起,否则在一直运行高优先级任务过程中,低优先级任务无法被运行;
-
μCos-Ⅲ官方文档:https://micrium.atlassian.net/wiki/spaces、https://docs.silabs.com/micrium/latest/micrium-common-api/
1. 任务调度器
任务调度器就是决定当前执行哪个任务;
μCos-Ⅲ 支持2种任务调度方式:
- 抢占式调度:针对优先级不同的任务,优先级高的任务可抢占优先级低的任务;
- 时间片调度:针对优先级相同的任务,当多个任务优先级相同且就绪时,调度器会根据用户设置的时间片轮流运行这些任务。时间片以一次系统时钟节拍为单位(滴答定时器的中断频率),µC/OS-III 默认设置的任务时间片为 100,则 µC/OS-III 会在当前任务运行 100 次系统时钟节拍的时间后,切换到另一个相同任务优先级的任务中运行。
1.1 抢占式调度
- 创建3个任务;
- 任务1、任务2、任务3的优先级分别设置为3、2、1(数字越小,优先级越高);
- 任务1先运行,过程中任务2就绪,在抢占式调度器作用下任务2抢占运行,任务1进入就绪态;
- 任务2运行中任务3就绪,在抢占式调度器作用下任务3抢占运行,任务2进入就绪态;
- 任务3运行中被挂起(系统延时或等待信号量等),此时处于就绪态中优先级最高的任务2运行;
- 任务3阻塞解除(延时时间到货接收到信号量),任务3恢复到就绪态,在抢占式调度器作用下任务3抢占运行,任务2进入就绪态;
1.2 时间片调度
- 创建3个任务;
- 3个任务的优先级同等;
- 3个任务的时间片默认设置为100;
- 任务1运行完100个时间片后,任务2运行;
- 任务2运行完100个时间片后,任务3运行;
- 任务3运行过程中(100个时间片未运行完)被挂起(系统延时或等待信号量等),此时直接切换到下一个任务,即任务1;
- 任务1运行完100个时间片后,任务2运行,以此循环;
一个时间片运行时间,取决于滴答定时器中断频率;
1.3 任务状态
- 运行态:即正在运行的任务,STM32是单核CPU,故同一时间只能运行一个任务;
- 就绪态:即任务能被执行但还没被执行;
- 挂起态:任务在运行中因延时或等待某一事件发生时被挂起;
- 休眠态:任务被创建在内存中,但未在任务调度器中(任务被删除);
- 中断态:任务在运行中被中断任务打断,CPU 跳转去执行中断服务函数,直到中断结束;
上诉5种任务状态并非可互相转换,转换关系如下图:
1.4 任务列表
- 就绪列表:存放就绪态任务的列表,
OSRdyList[x]
,其中x
表示任务优先级值,一般范围值为0~ 31;任务调度器总是在就绪列表中,选择最高优先级的任务来执行; - Tick 列表:存放正在等待延时超时或正在等待挂起超时的任务的列表,
OSTickList
; - 挂起列表:存放等待信号量、事件的任务的列表,
PendList
;
2. UCOSIII 移植过程
- 源码获取途径:
- UCOSSIII 官网下载:
- 正点原子 - 软件资料 - ucos-III 学习资料 - ucos-III 及其相关组件源代码.zip
源代码包含:uC-OS3、uC-CPU、uC-LIB
- uC-OS3源码文件简介:
Cfg
文件夹: µC/OS-III 配置文件的模板文件;Ports
文件夹:接口文件,与硬件相关的移植文件;Source
文件夹:µC/OS-III 的源码文;Template
文件夹:与动态 Tick 管理相关的文件;
- uC-CPU 源码文件简介:STM32主要看
ARM Cortex-M
,内含与 ARM Cortex-M 内核的 CPU 相关的移植文件;
- uC-LIB 源码文件简介:官方提供的ASCII 字符操作、数学、内存管理、字符串操作的库;
0. 准备
参考:《UCOS-III 开发指南》 - 第二章 µC/OS-III 移植
- uC-OSIII 源码:正点原子精英V2资料\6,软件资料\2,UCOS学习资料\UCOSIII资料\UCOS-III及其相关组件源代码
- 基础工程:正点原子精英V2资料\4,程序源码\2,标准例程-HAL库版本\2,标准例程-HAL库版本\实验33 内存管理实验
- 开始移植:
1. 在基础工程的 Middlewares 文件夹中新建一个 uC-OS3 子文件夹
2. 将uCOSIII 源代码的3个文件夹都拷贝到该文件夹中
3. 打开基础工程,新建4个分组
Middlewares/uC-OS3/BSP、Middlewares/uC-OS3/CPU、Middlewares/uC-OS3/LIB 和 Middlewares/uC-OS3/OS3;
4. 在分组Middlewares/uC-OS3/BSP 中添加以下源文件
路径分别为:
- bsp_cpu.c:uC-CPU-1.32.01\BSP\Template
- bsp_os_dt.c:uC-OS3-3.08.01\Template
5. 在分组Middlewares/uC-OS3/CPU 中添加以下源文件
路径分别为:
- cpu_a.asm:uC-CPU-1.32.01\ARM-Cortex-M\ARMv7-M\ARM
- cpu_c.c:uC-CPU-1.32.01\ARM-Cortex-M\ARMv7-M
- cpu_core.c:uC-CPU-1.32.01
6. 在分组Middlewares/uC-OS3/LIB 中添加以下源文件
路径都为:uC-LIB-1.39.01
7. 在分组Middlewares/uC-OS3/OS3中添加以下源文件
路径分别为:
- os_app_hooks.c:uC-OS3-3.08.01\Cfg\Template
- os_cpu_a.asm:uC-OS3-3.08.01\Ports\ARM-Cortex-M\ARMv7-M\ARM
- os_cpu_c.c:uC-OS3-3.08.01\Ports\ARM-Cortex-M\ARMv7-M
- 其他源文件:uC-OS3-3.08.01\Source
8. 包含头文件路径
9. 修改System 文件中的sys.h、usart.c、delay.c
- 在sys.h 中将宏定义
#define SYS_SUPPORT_OS
改为1; - 在usart.c 中:修改头文件包含
/* 如果使用os,则包括下面的头文件即可. */
#if SYS_SUPPORT_OS
//#include "includes.h" /* os 使用 */
#include "os.h" /* os 使用 */
#endif
- delay.c:删除或注释下面代码
///* 定义g_fac_ms变量, 表示ms延时的倍乘数, 代表每个节拍的ms数, (仅在使能os的时候,需要用到) */
//static uint16_t g_fac_ms = 0;
///*
// * 当delay_us/delay_ms需要支持OS的时候需要三个与OS相关的宏定义和函数来支持
// * 首先是3个宏定义:
// * delay_osrunning :用于表示OS当前是否正在运行,以决定是否可以使用相关函数
// * delay_ostickspersec:用于表示OS设定的时钟节拍,delay_init将根据这个参数来初始化systick
// * delay_osintnesting :用于表示OS中断嵌套级别,因为中断里面不可以调度,delay_ms使用该参数来决定如何运行
// * 然后是3个函数:
// * delay_osschedlock :用于锁定OS任务调度,禁止调度
// * delay_osschedunlock:用于解锁OS任务调度,重新开启调度
// * delay_ostimedly :用于OS延时,可以引起任务调度.
// *
// * 本例程仅作UCOSII和UCOSIII的支持,其他OS,请自行参考着移植
// */
///* 支持UCOSII */
//#ifdef OS_CRITICAL_METHOD /* OS_CRITICAL_METHOD定义了,说明要支持UCOSII */
//#define delay_osrunning OSRunning /* OS是否运行标记,0,不运行;1,在运行 */
//#define delay_ostickspersec OS_TICKS_PER_SEC /* OS时钟节拍,即每秒调度次数 */
//#define delay_osintnesting OSIntNesting /* 中断嵌套级别,即中断嵌套次数 */
//#endif
///* 支持UCOSIII */
//#ifdef CPU_CFG_CRITICAL_METHOD /* CPU_CFG_CRITICAL_METHOD定义了,说明要支持UCOSIII */
//#define delay_osrunning OSRunning /* OS是否运行标记,0,不运行;1,在运行 */
//#define delay_ostickspersec OSCfg_TickRate_Hz /* OS时钟节拍,即每秒调度次数 */
//#define delay_osintnesting OSIntNestingCtr /* 中断嵌套级别,即中断嵌套次数 */
//#endif
///**
// * @brief us级延时时,关闭任务调度(防止打断us级延迟)
// * @param 无
// * @retval 无
// */
//static void delay_osschedlock(void)
//{
//#ifdef CPU_CFG_CRITICAL_METHOD /* 使用UCOSIII */
// OS_ERR err;
// OSSchedLock(&err); /* UCOSIII的方式,禁止调度,防止打断us延时 */
//#else /* 否则UCOSII */
// OSSchedLock(); /* UCOSII的方式,禁止调度,防止打断us延时 */
//#endif
//}
///**
// * @brief us级延时时,恢复任务调度
// * @param 无
// * @retval 无
// */
//static void delay_osschedunlock(void)
//{
//#ifdef CPU_CFG_CRITICAL_METHOD /* 使用UCOSIII */
// OS_ERR err;
// OSSchedUnlock(&err); /* UCOSIII的方式,恢复调度 */
//#else /* 否则UCOSII */
// OSSchedUnlock(); /* UCOSII的方式,恢复调度 */
//#endif
//}
///**
// * @brief us级延时时,恢复任务调度
// * @param ticks: 延时的节拍数
// * @retval 无
// */
//static void delay_ostimedly(uint32_t ticks)
//{
//#ifdef CPU_CFG_CRITICAL_METHOD
// OS_ERR err;
// OSTimeDly(ticks, OS_OPT_TIME_PERIODIC, &err); /* UCOSIII延时采用周期模式 */
//#else
// OSTimeDly(ticks); /* UCOSII延时 */
//#endif
//}
注释或直接修改下面SysTick_Handler
滴答定时器中断服务函数代码:
/**
* @brief systick中断服务函数,使用OS时用到
* @param ticks: 延时的节拍数
* @retval 无
*/
//void SysTick_Handler(void)
//{
// if (delay_osrunning == 1) /* OS开始跑了,才执行正常的调度处理 */
// {
// OSIntEnter(); /* 进入中断 */
// OSTimeTick(); /* 调用ucos的时钟服务程序 */
// OSIntExit(); /* 触发任务切换软中断 */
// }
// HAL_IncTick();
//}
void SysTick_Handler(void)
{
/* OS 开始跑了,才执行正常的调度处理 */
if (OSRunning == OS_STATE_OS_RUNNING)
{
/* 调用 uC/OS-III 的 SysTick 中断服务函数 */
OS_CPU_SysTickHandler();
}
HAL_IncTick();
}
注释或直接修改下面delay_init
函数代码:
/**
* @brief 初始化延迟函数
* @param sysclk: 系统时钟频率, 即CPU频率(HCLK)
* @retval 无
*/
//void delay_init(uint16_t sysclk)
//{
//#if SYS_SUPPORT_OS /* 如果需要支持OS. */
// uint32_t reload;
//#endif
// SysTick->CTRL = 0; /* 清Systick状态,以便下一步重设,如果这里开了中断会关闭其中断 */
// HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK_DIV8); /* SYSTICK使用内核时钟源8分频,因systick的计数器最大值只有2^24 */
// g_fac_us = sysclk / 8; /* 不论是否使用OS,g_fac_us都需要使用,作为1us的基础时基 */
//#if SYS_SUPPORT_OS /* 如果需要支持OS. */
// reload = sysclk / 8; /* 每秒钟的计数次数 单位为M */
// reload *= 1000000 / delay_ostickspersec; /* 根据delay_ostickspersec设定溢出时间
// * reload为24位寄存器,最大值:16777216,在9M下,约合1.86s左右
// */
// g_fac_ms = 1000 / delay_ostickspersec; /* 代表OS可以延时的最少单位 */
// SysTick->CTRL |= 1 << 1; /* 开启SYSTICK中断 */
// SysTick->LOAD = reload; /* 每1/delay_ostickspersec秒中断一次 */
// SysTick->CTRL |= 1 << 0; /* 开启SYSTICK */
//#endif
//}
void delay_init(uint16_t sysclk)
{
#if SYS_SUPPORT_OS
uint32_t reload;
#endif
SysTick->CTRL = 0;
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
g_fac_us = sysclk;
#if SYS_SUPPORT_OS
reload = sysclk;
reload *= 1000000 / OSCfg_TickRate_Hz;
SysTick->CTRL |= 1 << 1;
SysTick->LOAD = reload;
SysTick->CTRL |= 1 << 0;
#endif
}
注释或直接修改下面delay_us
函数代码:
/**
* @brief 延时nus
* @param nus: 要延时的us数.
* @note nus取值范围: 0 ~ 477218588(最大值即2^32 / g_fac_us @g_fac_us = 9)
* @retval 无
*/
//void delay_us(uint32_t nus)
//{
// uint32_t ticks;
// uint32_t told, tnow, tcnt = 0;
// uint32_t reload;
// reload = SysTick->LOAD; /* LOAD的值 */
// ticks = nus * g_fac_us; /* 需要的节拍数 */
// delay_osschedlock(); /* 阻止OS调度,防止打断us延时 */
// told = SysTick->VAL; /* 刚进入时的计数器值 */
// while (1)
// {
// tnow = SysTick->VAL;
// if (tnow != told)
// {
// if (tnow < told)
// {
// tcnt += told - tnow; /* 这里注意一下SYSTICK是一个递减的计数器就可以了. */
// }
// else
// {
// tcnt += reload - tnow + told;
// }
// told = tnow;
// if (tcnt >= ticks) break; /* 时间超过/等于要延迟的时间,则退出. */
// }
// }
// delay_osschedunlock(); /* 恢复OS调度 */
//}
void delay_us(uint32_t nus)
{
uint32_t ticks;
uint32_t told, tnow, tcnt = 0;
uint32_t reload;
/* 定义用于接收错误代码的变量 */
OS_ERR err;
reload = SysTick->LOAD;
ticks = nus * g_fac_us;
/* 锁定 uC/OS-III 的任务调度器 */
OSSchedLock(&err);
told = SysTick->VAL;
while (1)
{
tnow = SysTick->VAL;
if (tnow != told)
{
if (tnow < told)
{
tcnt += told - tnow;
}
else
{
tcnt += reload - tnow + told;
}
told = tnow;
if (tcnt >= ticks) break;
}
}
/* 恢复 uC/OS-III 的任务调度器 */
OSSchedUnlock(&err);
}
注释或直接修改下面delay_ms
函数代码:
/**
* @brief 延时nms
* @param nms: 要延时的ms数 (0< nms <= 65535)
* @retval 无
*/
//void delay_ms(uint16_t nms)
//{
// if (delay_osrunning && delay_osintnesting == 0) /* 如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度) */
// {
// if (nms >= g_fac_ms) /* 延时的时间大于OS的最少时间周期 */
// {
// delay_ostimedly(nms / g_fac_ms); /* OS延时 */
// }
// nms %= g_fac_ms; /* OS已经无法提供这么小的延时了,采用普通方式延时 */
// }
// delay_us((uint32_t)(nms * 1000)); /* 普通方式延时 */
//}
void delay_ms(uint16_t nms)
{
uint32_t i;
for (i=0; i<nms; i++)
{
delay_us(1000);
}
}
10. 在stm32f1xx_it.c
和stm32f1xx_it.h
中注释下面代码,避免中断服务函数定义重复异常
///**
// * @brief This function handles PendSVC exception.
// * @param None
// * @retval None
// */
//void PendSV_Handler(void)
//{
//}
///**
// * @brief This function handles SysTick Handler.
// * @param None
// * @retval None
// */
//void SysTick_Handler(void)
//{
// HAL_IncTick();
//}
//void PendSV_Handler(void);
//void SysTick_Handler(void);
- 在启动文件
startup_stm32f103xe.s
中用UCOSIII 的PendSV 中断服务函数名覆盖原PendSV 中断服务函数名(一共3处)
DCD OS_CPU_PendSVHandler ; PendSV Handler
...
OS_CPU_PendSVHandler PROC
EXPORT OS_CPU_PendSVHandler [WEAK]
11. 在头文件cpu_cfg_h
中修改宏定义
#if 1 // 此处0改为1
#define CPU_CFG_NVIC_PRIO_BITS 4u
#endif
12. 添加4个配置文件
- 在已经移植好的工程文件中找到这4个头文件,并覆盖到基础工程中,他们分别是:
- cpu_cfg.h:uC-OS3\uC-CPU-1.32.01\Cfg\Template
- lib_cfg.h:uC-OS3\uC-LIB-1.39.01\Cfg\Template
- os_cfg.h:uC-OS3\uC-OS3-3.08.01\Cfg\Template
- os_cfg_app.h:uC-OS3\uC-OS3-3.08.01\Cfg\Template(与os_cfg.h 同一路径)
工程路径:正点原子精英V2资料\精英STM32F103开发板 V2-资料盘(A盘)\4,程序源码\3,扩展例程\3,扩展例程\1,uC-OS3例程\1,uC-OS3例程\UCOS-III实验例程2 UCOS-III移植实验
13. 最后工作
- 修改工程名称:
- 移除USMART 组件(如果没有用到)
在main
函数中,删除头文件包含语句:#include "./USMART/usmart.h"
,删除初始化函数语句usmart_dev.init(72); /* 初始化USMART */
,
14. 添加uCOSIII 任务,验证是否成功移植
- 在已经移植好的工程文件中找到这源码与对应头文件,粘贴到基础工程中:
- 工程路径 - User 文件夹中的
uc-os3_demo.c
、uc-os3_demo.h
;
- 工程路径 - User 文件夹中的
- 在基础工程中添加源代码与包含头文件路径;
- 删除
main
函数中的while(1)
循环,并在main
函数最后,调用uCOSIII 的入口函数uc_os3_demo();
,并包含其头文件#include "uc-os3_demo.h"
; - 烧录到开发板中,查看是否正常运行;
工程路径:正点原子精英V2资料\精英STM32F103开发板 V2-资料盘(A盘)\4,程序源码\3,扩展例程\3,扩展例程\1,uC-OS3例程\1,uC-OS3例程\UCOS-III实验例程2 UCOS-III移植实验
- 备份移植完成工程
移植完成工程备份路径:百度网盘 - 知识资源 - 电子工程 - 正点原子stm32f103精英开发板V2资料 - 资料备份
3. 系统配置文件说明
简单了解即可,主要配置文件有以下4个:
《UCOS-III开发指南_V1.5.pdf》 - 第三章 µC/OS-III 配置项,有关于上述4个头文件的宏定义的详细讲解;
如:头文件
os_cfg.h
中,宏定义OS_CFG_ARG_CHK_EN
的意思如下:
3.1 os.cfg.h
重点宏定义
主要用于配置 µC/OS-III 内核的一些功能,这里重点讲解几个重要的宏定义:
OS_CFG_PRIO_MAX
:定义任务优先级的最大数值。系统中最高任务优先级的数值为 0,最低任务优先级的数值为OS_CFG_PRIO_MAX-1
,默认值为32;OS_CFG_SCHED_ROUND_ROBIN_EN
:配置使能或禁用时间片调度功能。当此宏配置为 1 时,则使能时间片调度功能;OS_CFG_FLAG_EN
、OS_CFG_FLAG_DEL_EN
、OS_CFG_FLAG_MODE_CLR_EN
、OS_CFG_FLAG_PEND_ABORT_EN
:事件标志是能或禁用;
3.2 os.cfg_app.h
重点宏定义
主要用于配置于应用程序相关的 µC/OS-III 内核配置,这里重点讲解几个重要的宏定义:
OS_CFG_ISR_STK_SIZE
:定义用于异常的 Main Stack 栈的大小,默认单位为字(由变量类型 CPU_STK 决定),如配置为128u
即Main Stack 栈的大小为128字;OS_CFG_IDLE_TASK_STK_SIZE
:定义空闲任务任务栈的大小,默认单位为字(由变量类型 CPU_STK 决定);OS_CFG_TICK_RATE_HZ
:定义系统时钟节拍的频率,单位:赫兹,如配置为1000u
即滴答定时器的中断频率为1000us=1ms;
3.3 cpu_cfg.h
重点宏定义
主要用于配置 µC/CPU 组件,配置一些于 CPU 硬件相关的配置项,这里重点讲解几个重要的宏定义:
CPU_CFG_TS_32_EN
、CPU_CFG_TS_64_EN
:32/64位硬件定时器作为时间戳功能的时基定时器,STM32硬件定时器为32位;CPU_CFG_LEAD_ZEROS_ASM_PRESENT
:用于指示 CPU 具有硬件计算前导零的指令,如果硬件支持,则必须打开,否则失能该宏定义,则使用软件实现;
3.4 lib_cfg.h
重点宏定义
主要用于配置 µC/LIB 组件,这里重点讲解几个重要的宏定义:
LIB_MEM_CFG_HEAP_SIZE
:定义用于内存库管理的内存堆的大小,单位:字节;对于内存较小的cpu,简易直接定义成0;
4. 任务创建与删除
- 任务创建函数
OSTaskCreat()
:任务的任务控制块由用户手动分配,当任务被创建成功后,任务就处于就绪态; - 任务删除函数
OSTaskDel()
:任务被删除后,其内存不会被释放,只代表该任务的代码和任务栈不再由UCOS-III 内核管理,即不再能被UCOS-III 所调用;
4.1 任务创建函数OSTaskCreat()
函数原型输入形参:
void OSTaskCreate (OS_TCB *p_tcb, // 指向任务控制块的指针,一个结构体
CPU_CHAR *p_name, // 指向作为任务名的 ASCII 字符串的指针
OS_TASK_PTR p_task, // 指向任务函数的指针
void *p_arg, // 指向任务函数参数的指针,一般写0
OS_PRIO prio, // 任务的任务优先级,数字越小,优先级越高
CPU_STK *p_stk_base, // 指向任务栈起始地址的指针
CPU_STK_SIZE stk_limit, // 任务栈的使用警戒线,一般为任务栈大小的10%
CPU_STK_SIZE stk_size, // 任务的任务栈大小
OS_MSG_QTY q_size, // 任务内嵌消息队列的大小,一般写0
OS_TICK time_quanta, // 任务的时间片,不使用时写0
void *p_ext, // 指向用户扩展内存的指针,任务栈的扩展,很少用
OS_OPT opt, // 任务选项,共5个
OS_ERR *p_err) // 指向接收错误代码变量的指针
- 任务控制块的结构体成员介绍:任务控制块的结构体成员有很多,这里只挑几个常用的讲解:
struct os_tcb {
CPU_STK *StkPtr; // 任务栈栈顶,必须为TCB 的第一个成员,在任务切换与任务的上下文保存、恢复相关
CPU_STK *StkLimitPtr; // 指向任务栈使用警戒线的指针
OS_TCB *NextPtr; // 指向任务链表中下一个任务控制块的指针
OS_TCB *PrevPtr; // 指向任务链表中上一个任务控制块的指针
OS_TCB *TickNextPtr; // 指向 Tick 任务链表中下一个任务控制块的指针
OS_TCB *TickPrevPtr; // 指向 Tick 任务链表中上一个任务控制块的指针
OS_PRIO Prio; // 任务优先级,数字越小,优先级越高
OS_TICK TickRemain; // 任务延时的剩余时钟节拍数
OS_TICK TimeQuanta; // 任务时间片
OS_TICK TimeQuantaCtr; // 任务剩余时间片
...
- 任务选项:
OS_OPT_TASK_NONE // 没有选项,一般不要
OS_OPT_TASK_STK_CHK // 是否允许对任务进行堆栈检查,检查堆栈是否安全/正常,一般需要
OS_OPT_TASK_STK_CLR // 是否需要清除任务堆栈,一般需要
OS_OPT_TASK_SAVE_FP // 是否保存浮点寄存器,一般不要
OS_OPT_TASK_NO_TLS // 不需要对正在创建的任务提供TLS(线程本地存储)支持,一般不要
- 函数错误代码:
OS_ERR_NONE // 任务创建成功
OS_ERR_ILLEGAL_CREATE_RUN_TIME // 定义了 OS_SAFETY_CRITICAL_IEC61508,且在 OSStart()之后非法地创建内核对象
OS_ERR_PRIO_INVALID // 非法的任务优先级数值
OS_ERR_STAT_STK_SIZE_INVALID // 任务栈在初始化期间溢出
OS_ERR_STK_INVALID // 指向任务栈起始地址的指针为空
OS_ERR_STK_SIZE_INVALID // 任务栈小于配置项 OS_CFG_STK_SIZE_MIN
OS_ERR_STK_LIMIT_INVALID // 任务栈“水位”限制大小大于或等于任务栈大小
OS_ERR_TASK_CREATE_ISR // 在中断中非法地创建任务,即任务创建函数不能用在中断服务函数中
OS_ERR_TASK_INVALID // 指向任务函数的指针为空
OS_ERR_TCB_INVALID // 指向任务控制块的指针为空
4.2 任务删除函数OSTaskDel()
函数原型输入形参:
void OSTaskDel (OS_TCB *p_tcb, // 指向任务控制块的指针
OS_ERR *p_err) // 接受错误代码变量的指针
如果p_tcb
传入的参数为NULL
,代表删除当前任务本身
- 函数错误代码:
OS_ERR_NONE // 任务删除成功
OS_ERR_ILLEGAL_DEL_RUN_TIME // 定义了 OS_SAFETY_CRITICAL_IEC61508,且在 OSStart()之后非法地删除内核对象
OS_ERR_OS_NOT_RUNING µC/OS-III // 内核还未运行
OS_ERR_STATE_INVALID // 任务处于无效状态
OS_ERR_TASK_DEL_IDLE // 非法删除空闲任务
OS_ERR_TASK_DEL_INVALID // 非法删除 µC/OS-III 的中断服务任务
OS_ERR_TASK_DEL_ISR // 在中断中非法地删除任务,即任务删除函数不能用在中断服务函数中
4.3 任务创建流程
- 在调用任何UCOS-III 函数之前,必须先初始化UCOS-III,即调用函数
OSInit();
- 在任务被创建之后,需开启任务调度器,任务才能被任务调度器调度运行,即调用
OSStart();
任务创建流程如下:
- 定义函数入口参数(任务堆栈、任务优先级…);
- 调用创建任务API 函数;
- 实现任务函数功能;
上述3步后,任务即被创建,之后,任务立即进入就绪态,由任务调度器调度运行;
下面开始进行任务创建与删除实验:
- 实验目的:创建以下4个任务:
- start_task:初始化cup 库,配置Systick 中断及优先级,创建其他3个任务;
- task1:实现LED0每500ms闪烁一次;
- task2:实现LED1每500ms闪烁一次;
- task3:判断按键KEY0 是否按下,按下则删除task1;
- 实验过程:
- 在移植好UCOS-III 的工程中,创建一个
uc-os3_demo.c
的源文件和uc-os3_demo.h
的头文件,其初始内容分别如下:
// 源文件
#include "uc-os3_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./MALLOC/malloc.h"
#include "./BSP/KEY/key.h"
/*uC/OS-III*********************************************************************************************/
#include "os.h"
#include "cpu.h"
/******************************************************************************************************/
/*uC/OS-III配置*/
/* START_TASK 任务 配置
* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
/**
* @brief uC/OS-III例程入口函数
* @param 无
* @retval 无
*/
void uc_os3_demo(void)
{
OS_ERR err;
/* 初始化uC/OS-III ,在调用任何UCOS-III 函数之前,必须先调用它*/
OSInit(&err);
/* 开始任务调度 */
OSStart(&err);
}
// 头文件
#ifndef __UC_OS3_DEMO_H
#define __UC_OS3_DEMO_H
void uc_os3_demo(void);
#endif
- 在UCOS-III 初始化函数
OSInit(&err);
后面创建一个任务:
OSTaskCreate ( (OS_TCB*) p_tcb,
(CPU_CHAR*) p_name,
(OS_TASK_PTR) p_task,
(void*) p_arg,
(OS_PRIO) prio,
(CPU_STK*) p_stk_base,
(CPU_STK_SIZE) stk_limit,
(CPU_STK_SIZE) stk_size,
(OS_MSG_QTY) q_size,
(OS_TICK) time_quanta,
(void*) p_ext,
(OS_OPT) opt,
(OS_ERR*) p_err);
- 定义任务控制块结构体:
OS_TCB start_task_tcb
- 定义并编写start_task 任务函数:
void start_task(void *p_arg)
{
OS_ERR err;
OSTaskDel((OS_TCB*)0,&err); // 由于任务start_task 只执行一次,故在其最后添加自我删除的函数;
}
- 定义任务优先级:
#define START_TASK_PRIO 5
- 定义任务堆栈:
#define START_TASK_STACK_SIZE 256
CPU_STK start_task_stack[START_TASK_STACK_SIZE];
- 同样的方式,定义并编写task1·、task2、task3任务函数:
最后源文件代码如下:
#include "uc-os3_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./MALLOC/malloc.h"
#include "./BSP/KEY/key.h"
/*uC/OS-III*********************************************************************************************/
#include "os.h"
#include "cpu.h"
/******************************************************************************************************/
/*uC/OS-III配置*/
/* START_TASK 任务 配置
* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define START_TASK_PRIO 5
#define START_TASK_STACK_SIZE 256
CPU_STK start_task_stack[START_TASK_STACK_SIZE];
OS_TCB start_task_tcb;
void start_task(void *p_arg);
/* TASK1 任务 配置
* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define TASK1_PRIO 4
#define TASK1_STACK_SIZE 256
CPU_STK* task1_stack; // 申请一个内存给task1 的任务堆栈
OS_TCB task1_tcb;
void task1(void *p_arg);
/* TASK2 任务 配置
* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 256
CPU_STK* task2_stack; // 申请一个内存给task1 的任务堆栈
OS_TCB task2_tcb;
void task2(void *p_arg);
/* TASK3 任务 配置
* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define TASK3_PRIO 2
#define TASK3_STACK_SIZE 256
CPU_STK* task3_stack; // 申请一个内存给task1 的任务堆栈
OS_TCB task3_tcb;
void task3(void *p_arg);
/**
* @brief uC/OS-III例程入口函数
* @param 无
* @retval 无
*/
void uc_os3_demo(void)
{
OS_ERR err;
OSTaskCreate ((OS_TCB*) &start_task_tcb,
(CPU_CHAR*) "start_task",
(OS_TASK_PTR) start_task,
(void*) 0,
(OS_PRIO) START_TASK_PRIO,
(CPU_STK*) &start_task_stack[0],// 以数组方式提供任务堆栈
(CPU_STK_SIZE) START_TASK_STACK_SIZE/10,
(CPU_STK_SIZE) START_TASK_STACK_SIZE,
(OS_MSG_QTY) 0,
(OS_TICK) 0,
(void*) 0,
(OS_OPT) (OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),
(OS_ERR*) &err);
/* 初始化uC/OS-III */
OSInit(&err);
/* 开始任务调度 */
OSStart(&err);
}
void start_task(void *p_arg)
{
OS_ERR err;
CPU_INT32U cnts = 0;
CPU_Init(); // 初始化cpu 库
cnts = HAL_RCC_GetSysClockFreq()/OS_CFG_TICK_RATE_HZ;// 这里用的STM32 的主频为168MHz,使用HAL_RCC_GetSysClockFreq() 来获取芯片主频,
OS_CPU_SysTickInit(cnts);// 初始化滴答定时器
/* 创建task1 */
task1_stack = mymalloc(SRAMIN,TASK1_STACK_SIZE*sizeof(CPU_STK));
OSTaskCreate ((OS_TCB*) &task1_tcb,
(CPU_CHAR*) "task1",
(OS_TASK_PTR) task1,
(void*) 0,
(OS_PRIO) TASK1_PRIO,
(CPU_STK*) task1_stack,// 以申请内存方式提供任务堆栈
(CPU_STK_SIZE) TASK1_STACK_SIZE/10,
(CPU_STK_SIZE) TASK1_STACK_SIZE,
(OS_MSG_QTY) 0,
(OS_TICK) 0,
(void*) 0,
(OS_OPT) (OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),
(OS_ERR*) &err);
/* 创建task2 */
task2_stack = mymalloc(SRAMIN,TASK2_STACK_SIZE*sizeof(CPU_STK));
OSTaskCreate ((OS_TCB*) &task2_tcb,
(CPU_CHAR*) "task2",
(OS_TASK_PTR) task2,
(void*) 0,
(OS_PRIO) TASK2_PRIO,
(CPU_STK*) task2_stack,// 以申请内存方式提供任务堆栈
(CPU_STK_SIZE) TASK2_STACK_SIZE/10,
(CPU_STK_SIZE) TASK2_STACK_SIZE,
(OS_MSG_QTY) 0,
(OS_TICK) 0,
(void*) 0,
(OS_OPT) (OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),
(OS_ERR*) &err);
/* 创建task3 */
task3_stack = mymalloc(SRAMIN,TASK3_STACK_SIZE*sizeof(CPU_STK));
OSTaskCreate ((OS_TCB*) &task3_tcb,
(CPU_CHAR*) "task3",
(OS_TASK_PTR) task3,
(void*) 0,
(OS_PRIO) TASK3_PRIO,
(CPU_STK*) task3_stack,// 以申请内存方式提供任务堆栈
(CPU_STK_SIZE) TASK3_STACK_SIZE/10,
(CPU_STK_SIZE) TASK3_STACK_SIZE,
(OS_MSG_QTY) 0,
(OS_TICK) 0,
(void*) 0,
(OS_OPT) (OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),
(OS_ERR*) &err);
OSTaskDel((OS_TCB*)0,&err);// 由于任务start_task 只执行一次,故在其最后添加自我删除的函数;
}
/* 实现LED0每500ms闪烁一次 */
void task1(void *p_arg)
{
OS_ERR err;
while(1)
{
printf("task1正在运行\r\n");
LED0_TOGGLE();
OSTimeDly(500,OS_OPT_TIME_DLY,&err); // UCOS-III 延时
}
}
/* 实现LED1每500ms闪烁一次 */
void task2(void *p_arg)
{
OS_ERR err;
while(1)
{
printf("task2正在运行\r\n");
LED1_TOGGLE();
OSTimeDly(500,OS_OPT_TIME_DLY,&err); // UCOS-III 延时
}
}
/* 判断按键KEY0 是否按下,按下则删除task1 */
void task3(void *p_arg)
{
OS_ERR err;
uint8_t key = 0;
while(1)
{
printf("task3正在运行\r\n");
key = key_scan(0);
if(key == KEY0_PRES)
{
printf("删除task1\r\n");
OSTaskDel(&task1_tcb,&err);
}
OSTimeDly(500,OS_OPT_TIME_DLY,&err); // UCOS-III 延时
}
}
代码运行结果如下:
代码运行逻辑如下:
- start_task 被创建并开始执行,task1在start_task 内被创建,由于优先级比start_task 高,故抢占运行;
- 在task1 进入延时时,返回start_task,此时task2在start_task 内被创建,由于优先级比start_task 和task1高,故抢占运行;
- 在task2 进入延时时,返回start_task,此时task3在start_task 内被创建,由于优先级比start_task 、task1和task2都高,故抢占运行;
- 此后,start_task 被删除,task1、task2、task3 循环运行
若想要在start_task 任务创建过程中,关闭任务调度器功能,即不允许task1、task2、task3开始执行,可使用临界区的方法实现:
CPU_CRITICAL_ENTER(); // 进入临界区
/* 创建task1 */
...
/* 创建task2 */
...
/* 创建task3 */
...
CPU_CRITICAL_EXIT(); // 退出临界区()
代码运行结果如下:
参考:
- 正点原子精英V2资料;