UCOSIII学习笔记(二)
文章目录
一、UCOSIII任务管理
1、任务的基本概念
实时应用中一般将工作拆分为多个任务,每个任务都需要是可靠的。使用uC/OS-III 可以轻松地解决这个问题。任务(也叫做线程)是简单的程序。单CPU 中,在任何时刻只能是一个任务被执行。
UCOSIII中的任务由三部分组成:任务堆栈、任务控制块和任务函数。
- 任务堆栈:上下文切换的时候用来保存任务的工作环境,就是STM32的内部寄存器值。
- 任务控制块:任务控制块用来记录任务的各个属性。
- 任务函数:由用户编写的任务处理代码。
2、UCOSIII系统任务
UCOSIII默认有5个系统任务:
-
空闲任务:UCOSIII创建的第一个任务,UCOSIII必须创建的任务,此任务有UCOSIII自动创建,不需要用户手动创建,优先级总是为OS_CFG_PRIO_MAK-1(最低)。空闲任务中不能调用任何可使空闲任务进入等待态的函数!
-
时钟节拍任务:此任务也是必须创建的任务。时钟节拍任务用来跟踪任务延时和任务等待超时,任务优先级用宏OS_CFG_TICK_TASK_PRIO来定义 ,一般时钟节拍任务的任务应该设置一个相对较高的优先级。
-
统计任务:可选任务,用来统计CPU使用率和各个任务的堆栈使用量。此任务是可选任务,由宏OS_CFG_STAT_TASK_EN控制是否使用此任务。默认情况下统计任务是不会创建的,如果要开启统计任务的话需要做如下步骤:
1、将宏OS_CFG_STAT_TASK_EN置1。
2、必须在main函数创建的以一个任务也是唯一的一个应用任务里面调用函数OSStatTaskCPUUsageInit()。
3、统计任务的优先级通过宏OS_CFG_STAT_TASK_PRIO来设置,一般设置OS_CFG_PRIO_MAX-2(倒数第二)。
-
定时任务:用来向用户提供定时服务,也是可选任务,由宏OS_CFG_TMR_EN控制是否使用此任务。在OSInit()中将会调用函数OS_TmrInit()来创建定时任务。定时任务的优先级通过宏OS_CFG_TMR_TASK_PRIO定义。
-
中断服务管理任务:可选任务,由宏OS_CFG_ISR_POST_DEFERRED_EN控制是否使用此任务。使用中断服务管理任务的好处就是可以减少中断关闭的时间(定时器中断也会关闭,影响UCOSIII系统时钟),中断服务管理任务的优先级永远为0(最高),不可更改!
3、UCOSIII的任务的状态
任务状态 | 描述 |
---|---|
休眠态 | 任务已经在CPU的flash中了,但是还不受UCOSIII管理。 |
就绪态 | 系统为任务分配了任务控制块,并且任务已经在就绪表中登记,这时这个任务就具有了运行的条件,此时任务的状态就是就绪态。 |
运行态 | 任务获得CPU的使用权,正在运行。 |
等待态 | 正在运行的任务需要等待一段时间,或者等待某个事件,这个任务就进入了等待态,此时系统就会把CPU使用权转交给别的任务。 |
中断服务态 | 当发送中断,当前正在运行的任务会被挂起,CPU转而去执行中断服务函数,此时任务的任务状态叫做中断服务态。 |
4、任务控制块
任务控制块OS_TCB,用来记录与任务相关的信息的数据结构,每个任务都要有自己的任务控制块。任务控制块由用户自行创建。OS_TCB为一个结构体,描述了任务控制块,任务控制块中的成员变量用户不能直接访问,更不可能改变他们。函数OSTaskCreate()在创建任务的时候会对任务的任务控制块进行初始化。函数OS_TaskInitTCB()用来初始化任务控制块。用户不需要自行初始化任务控制块。
5、任务堆栈
堆栈是在RAM中按照“先进先出(FIFO)”的原则组织的一块连续的存储空间。为了满足任务切换和响应中断时保存CPU寄存器中的内容及任务调用其它函数时的需要,每个任务都应该有自己的堆栈。
#define START_STK_SIZE 512 //堆栈大小
CPU_STK START_TASK_STK[START_STK_SIZE]; //定义一个数组来作为任务堆栈
堆栈大小:CPU_STK为4字节,堆栈大小 = 4 * START_STK_SIZE。
UCOSIII提供了堆栈初始化的函数:OSTaskStkInit() ,用户一般不会直接操作堆栈初始化函数,任务堆栈初始化函数由任务创建函数OSTaskCreate()调用。在移植UCOSIII的时候需要根据不同CPU来编写任务堆栈初始化函数。
6、任务就绪表
UCOSIII中就绪表由2部分组成:
1、优先级位映射表OSPrioTbl[]:用来记录哪个优先级下有任务就绪。函数OS_PrioGetHighest()用于找到就绪了的最高优先级的任务。
UCOSIII中任务优先级数由宏OS_CFG_PRIO_MAX来配置。
2、就绪任务列表OSRdyList[]:用来记录每一个优先级下所有就绪的任务。
同一优先级下如果有多个任务的话最先运行的永远是HeadPtr所指向的任务!
7、任务调度和切换
1、任务调度
UCOSIII中的任务调度是由任务调度器来完成的,任务调度器有2种:任务级调度器和中断级调度器。
-
任务级调度器为函数OSSched()。
-
中断级调度器为函数OSIntExit(),当退出外部中断服务函数的时候使用中断级任务调度。
-
OSSchedLock()对调度器加锁。
-
OSSchedUnlock()给已经上锁的任务调度器解锁。
时间片轮转调度
UCOSIII允许同一个优先级下有多个任务,每个任务可以执行指定的时间(时间片),然后轮到下一个任务,这个过程就是时间片轮转调度,当一个任务不想在运行的时候就可以放弃其时间片。要使用这个功能需定义 OS_CFG_SCHED_ROUND_ROBIN_EN为1。使用时间片轮转需要先进行配置,调用OSSchedRoundRobinCfg()函数对时间片轮转进行配置。
2、任务切换
当UCOSIII需要切换到另外一个任务时,它将保存当前任务的现场到当前任务的堆栈中,主要是CPU寄存器值,然后恢复新的现场并且执行新的任务,这个过程就是任务切换。任务切换分为两种:任务级切换和中断级切换。
任务级切换函数为:OSCtxSw()。
中断级切换函数为:OSIntCtxSw()。
任务调度函数OSSched()先确定是否已准备好新的高优先级任务,如果有则执行任务切换函数;任务切换OSCtxSw()的作用时进行上下文切换。
8、UCOSIII的任务管理常用API
创建任务OSTaskCreate
#include "includes.h" //UCOSIII头文件
CPU_SR_ALLOC(); //为了保存/恢复中断状态。通过#define变量定义,用于保存SR状态。只有定义此变量,才可以使用临界区函数OS_CRITICAL_ENTER()
//任务创建所需参数、全局变量
#define START_TASK_PRIO 3 //任务优先级
#define START_STK_SIZE 128 //任务堆栈大小
CPU_STK START_TASK_STK[START_STK_SIZE]; //任务堆栈
OS_TCB StartTaskTCB; //任务控制块
void start_task(void *p_arg); //任务函数声明
int main()
{
OS_ERR err; //创建任务需使用的局部变量,存放返回错误类型。引用时需要取地址,&err。否则UCOSIII会跑飞。
delay_init(168);
USART1_init(115200);
OSInit(&err); //初始化UCOSIII,初始化前完成各外设初始化
OS_CRITICAL_ENTER(); //进入临界区
//创建开始任务
OSTaskCreate((OS_TCB * )&StartTaskTCB, //任务控制块
(CPU_CHAR * )"start task", //任务名字
(OS_TASK_PTR )start_task, //任务函数
(void * )0, //传递给任务函数的参数
(OS_PRIO )START_TASK_PRIO, //任务优先级
(CPU_STK * )&START_TASK_STK[0], //任务堆栈基地址
(CPU_STK_SIZE)START_STK_SIZE/10, //任务堆栈深度限位
(CPU_STK_SIZE)START_STK_SIZE, //任务堆栈大小
(OS_MSG_QTY )0, //任务内部消息队列能够接收的最大消息数目,为0时禁止接收消息
(OS_TICK )0, //当使能时间片轮转时的时间片长度,为0时为默认长度
(void * )0, //用户补充的存储区
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR, //任务选项
(OS_ERR * )&err); //存放该函数错误时的返回值
OS_CRITICAL_EXIT(); //退出临界区
OSStart(&err); //开启UCOSIII
}
删除任务OSTaskDel
OSTaskDel((OS_TCB*)0,&err); //删除任务自身
OSTaskDel(&Task1_TaskTCB,&err); //删除任务1;Task1_TaskTCB任务1的任务控制块
任务挂起OSTaskSuspend
OSTaskSuspend(&Task3_TaskTCB,&err); //挂起任务3
任务恢复OSTaskResume
OSTaskResume(&Task3_TaskTCB,&err); //恢复任务3
一个任务可以多次挂起,对一个任务挂起了n次,必须再对该任务恢复n次才可以成功恢复任务。
时间片轮转调度配置OSSchedRoundRobinCfg
os_cfg_app.h文件中的OS_CFG_TICK_RATE_HZ来调整时钟节拍的频率,默认为200Hz(5ms)。
#if OS_CFG_SCHED_ROUND_ROBIN_EN //当使用时间片轮转的时候
OSSchedRoundRobinCfg(DEF_ENABLED,1,&err); //使能时间片轮转调度功能,时间片长度为1个系统时钟节拍,既1*5=5ms
#endif
//创建任务时,将 (OS_TICK)time_quanta 设为该任务所需时间片数
OSTaskCreate((OS_TCB * )&Task3_TaskTCB,
(CPU_CHAR * )"Task3 task",
(OS_TASK_PTR )task3_task,
(void * )0,
(OS_PRIO )TASK3_TASK_PRIO,
(CPU_STK * )&TASK3_TASK_STK[0],
(CPU_STK_SIZE)TASK3_STK_SIZE/10,
(CPU_STK_SIZE)TASK3_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )1, //时间片数为1,任务所需时间为1*5ms
(void * )0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR * )&err);
放弃本次时间片OSSchedRoundRobinYield
OSSchedRoundRobinYield(&err); //该任务放弃本次时间片
二、UCOSIII的Hook函数
钩子函数一般主要是用来扩展其他函数(任务)功能的,发生相关状态时自动调用,钩子函数有如下几个:
函数名 | 功能 |
---|---|
OSIdleTaskHook() | 空闲任务调用这个函数,可以用来让CPU进入低功耗模式。 |
OSInitHook() | 系统初始化函数OSInit()调用此函数。 |
OSStatTaskHook() | 统计任务每秒中都会调用这个函数,此函数允许你向统计任务中添加自己的应用函数。 |
OSTaskCreateHook() | 任务创建的钩子函数。 |
OSTaskDelHook() | 任务删除的钩子函数。 |
OSTaskReturnHook() | 任务意外返回时调用的钩子函数,比如删除某个任务 |
OSTaskSwHook() | 任务切换时候调用的钩子函数。 |
OSTimeTickHook() | 滴答定时器调用的钩子函数。 |
三、UCOSIII中断
UCOSIII中,在CPU 执行中断服务程序的时候有可能有更高优先级的任务就绪,那么当退出中断服务程序的时候,CPU 就会直接执行这个高优先级的任务。UCOSIII 支持中断嵌套,既高优先级的中断可以打断低优先级的中断,在 UCOSIII 中使用OSIntNestingCtr 来记录中断嵌套次数,最大支持250 级的中断嵌套,每进入一次中断服务函数OSIntNestingCtr 就会加1,当退出中断服务函数的时候OSIntNestingCtr 就会减1。
在编写UCOSIII的中断服务程序的时候需要使用到两个中断级任务调度器函数OSIntEnter()和OSIntExit()。
void XXX_IRQHandler(void) //中断服务模板
{
OSIntEnter();
... //具体中断服务程序
OSIntExit();
}
UCOSIII 对从中断发布消息或者信号的处理有两种模式:直接发布和延迟发布两种方式。宏 OS_CFG_ISR_POST_DEFERRED_EN 在 os_cfg.h 文件中有定义,当定义为 0 时使用直接发布模式,定义为 1 的时候使用延迟发布模式。
1、直接发布
使用直接发布模式的话,UCOSIII 会对临界段代码采用关闭中断的保护措施,这样就会延长中断的响应时间。虽然UCOSIII 已经采用了所有可能的措施来降低中断关闭时间,但仍然有一些复杂的功能会使得中断关闭相对较长的时间。
2、延迟发布
UCOSIII 通过给任务调度器上锁的方法来保护临界段代码。在延迟发布模式下,UCOSIII 在访问中断队列时,仍然需要关闭中断,但这个时间是非常短的。