写在前面:
由于时间的不足与学习的碎片化,写博客变得有些奢侈。
但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。
既然如此
不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。
标题的结构如下:“类型”:“知识点”——“简短的解释”
部分内容由于保密协议无法上传。
点击此处进入学习日记的总目录
2024.03.10
- 二十五、UCOSIII:多优先级
- 1、定义优先级相关全局变量
- 2、修改OSInit()函数
- 3、修改任务控制块TCB
- 4、修改OSTaskCreate()函数
- 5、修改OS_IdleTaskInit()函数
- 6、修改OSStart()函数
- 7、修改PendSV_Handler()函数
- 8、修改OSTimeDly()函数
- 9、修改OSSched()函数
- 10、修改OSTimeTick()函数
- 二十六、UCOSIII:修改main()函数
- 1、修改部分:
- 2、实验现象
二十五、UCOSIII:多优先级
在本章之前,OS
还没有到优先级,只支持两个任务互相切换,从本章开始,任务中我们开始加入优先级的功能。 在μC/OS-III
中,数字优先级越小,逻辑优先级越高。
1、定义优先级相关全局变量
在支持任务多优先级的时候,需要在os.h
头文件添加两个优先级相关的全局变量
/* 在os.h中定义 */
/* 当前优先级 */
OS_EXT OS_PRIO OSPrioCur;
/* 最高优先级 */
OS_EXT OS_PRIO OSPrioHighRdy;
2、修改OSInit()函数
刚刚新添加的优先级相关的全部变量,需要在OSInit()
函数中进行初始化
其实OS
中定义的所有的全局变量都是在OSInit()
中初始化的。
void OSInit (OS_ERR *p_err)
{
/* 配置OS初始状态为停止态 */
OSRunning = OS_STATE_OS_STOPPED;
/* 初始化两个全局TCB,这两个TCB用于任务切换 */
OSTCBCurPtr = (OS_TCB *)0;
OSTCBHighRdyPtr = (OS_TCB *)0;
/* 初始化优先级变量 */
OSPrioCur = (OS_PRIO)0;
OSPrioHighRdy = (OS_PRIO)0;
/* 初始化优先级表 */
OS_PrioInit();
/* 初始化就绪列表 */
OS_RdyListInit();
/* 初始化空闲任务 */
OS_IdleTaskInit(p_err);
if (*p_err != OS_ERR_NONE) {
return;
}
}
3、修改任务控制块TCB
在任务控制块中,加入优先级字段Prio
。
优先级Prio
的数据类型为OS_PRIO
, 宏展开后是8位的整型,所以只支持255个优先级。
struct os_tcb {
CPU_STK *StkPtr;
CPU_STK_SIZE StkSize;
/* 任务延时周期个数 */
OS_TICK TaskDelayTicks;
/* 任务优先级 */
OS_PRIO Prio;
/* 就绪列表双向链表的下一个指针 */
OS_TCB *NextPtr;
/* 就绪列表双向链表的前一个指针 */
OS_TCB *PrevPtr;
};
4、修改OSTaskCreate()函数
void OSTaskCreate (OS_TCB *p_tcb,
OS_TASK_PTR p_task,
void *p_arg,
OS_PRIO prio, //(1)
CPU_STK *p_stk_base,
CPU_STK_SIZE stk_size,
OS_ERR *p_err)
{
CPU_STK *p_sp;
CPU_SR_ALLOC(); //(2)
/* 初始化TCB为默认值 */
OS_TaskInitTCB(p_tcb); //(3)
/* 初始化栈 */
p_sp = OSTaskStkInit( p_task,
p_arg,
p_stk_base,
stk_size );
p_tcb->Prio = prio; //(4)
p_tcb->StkPtr = p_sp;
p_tcb->StkSize = stk_size;
/* 进入临界段 */
OS_CRITICAL_ENTER(); //(5)
/* 将任务添加到就绪列表 */ //(6)
OS_PrioInsert(p_tcb->Prio);
OS_RdyListInsertTail(p_tcb);
/* 退出临界段 */
OS_CRITICAL_EXIT(); //(7)
*p_err = OS_ERR_NONE;
}
- (1):在函数形参中,加入优先级字段。任务的优先级由用户在创建任务的时候通过形参
Prio
传进来。 - (2):定义一个局部变量,用来存
CPU
关中断前的中断状态,因为接下来将任务添加到就绪列表这段代码属于临界短代码,需要关中断。 - (3):初始化
TCB
为默认值,其实就是全部初始化为0
,OS_TaskInitTCB()
函数在os_task.c
的开头定义
void OS_TaskInitTCB (OS_TCB *p_tcb)
{
p_tcb->StkPtr = (CPU_STK *)0;
p_tcb->StkSize = (CPU_STK_SIZE )0u;
p_tcb->TaskDelayTicks = (OS_TICK )0u;
p_tcb->Prio = (OS_PRIO )OS_PRIO_INIT;
//OS_PRIO_INIT是任务TCB初始化的时候给的默认的一个优先级,宏展开等于OS_CFG_PRIO_MAX, 这是一个不会被OS使用到的优先级。
//OS_PRIO_INIT具体在os.h中定义。
p_tcb->NextPtr = (OS_TCB *)0;
p_tcb->PrevPtr = (OS_TCB *)0;
}
- (4):将形参传进来的优先级存到任务控制块
TCB
的优先级字段。 - (5):进入临界段。
- (6):将任务插入就绪列表,这里需要分成两步来实现:
1、根据优先级置位优先级表中的相应位置;
2、将任务TCB
放到OSRdyList[优先级]
中,如果同一个优先级有多个任务,那么这些任务的TCB
就会被放到OSRdyList[优先级]
串成一个双向链表。 - (7):退出临界段。
5、修改OS_IdleTaskInit()函数
修改OS_IdleTaskInit()
函数,是因为该函数调用了任务创建函数OSTaskCreate()
,OSTaskCreate()
我们刚刚加入了优先级, 所以这里我们要跟空闲任务分配一个优先级
/* 空闲任务初始化 */
void OS_IdleTaskInit(OS_ERR *p_err)
{
/* 初始化空闲任务计数器 */
OSIdleTaskCtr = (OS_IDLE_CTR)0;
/* 创建空闲任务 */
OSTaskCreate( (OS_TCB *)&OSIdleTaskTCB,
(OS_TASK_PTR )OS_IdleTask,
(void *)0,
(OS_PRIO)(OS_CFG_PRIO_MAX - 1u),(1)
(CPU_STK *)OSCfg_IdleTaskStkBasePtr,
(CPU_STK_SIZE)OSCfg_IdleTaskStkSize,
(OS_ERR *)p_err );
}
空闲任务是μC/OS-III
的内部任务,在OSInit()
中被创建,在系统没有任何用户任务运行的情况下, 空闲任务就会被运行,优先级最低,即等于OS_CFG_PRIO_MAX- 1u
。
6、修改OSStart()函数
加入优先级之后,OSStart()
函数需要修改,具体哪一个任务最先运行,由优先级决定
/* 启动RTOS,将不再返回 */
void OSStart (OS_ERR *p_err)
{
if ( OSRunning == OS_STATE_OS_STOPPED ) {
#if 0
/* 手动配置任务1先运行 */
OSTCBHighRdyPtr = OSRdyList[0].HeadPtr;
#endif
/* 寻找最高的优先级 */
OSPrioHighRdy = OS_PrioGetHighest();(1)
OSPrioCur = OSPrioHighRdy;
/* 找到最高优先级的TCB */
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;(2)
OSTCBCurPtr = OSTCBHighRdyPtr;
/* 标记OS开始运行 */
OSRunning = OS_STATE_OS_RUNNING;
/* 启动任务切换,不会返回 */
OSStartHighRdy();
/* 不会运行到这里,运行到这里表示发生了致命的错误 */
*p_err = OS_ERR_FATAL_RETURN;
} else {
*p_err = OS_STATE_OS_RUNNING;
}
}
- (1):调取
OS_PrioGetHighest()
函数从全局变量优先级表OSPrioTbl[]
获取最高的优先级, 放到OSPrioHighRdy
这个全局变量中, 然后把OSPrioHighRdy
的值再赋给当前优先级OSPrioCur
这个全局变量。在任务切换的时候需要用到OSPrioHighRdy
和OSPrioCur
这两个全局变量。 - (2):根据
OSPrioHighRdy
的值, 作为全局变量OSRdyList[]
的下标索引找到最高优先级任务的TCB
,传给全局变量OSTCBHighRdyPtr
, 然后再将OSTCBHighRdyPtr
赋值给OSTCBCurPtr
。在任务切换的时候需要使用到OSTCBHighRdyPtr
和OSTCBCurPtr
这两个全局变量。
7、修改PendSV_Handler()函数
PendSV_Handler()
函数中添加了优先级相关的代码。
有关PendSV_Handler()
这个函数的具体讲解要参考《任务切换》这个章节,这里不再赘述。
;*******************************************************************
; PendSVHandler异常
;*******************************************************************
OS_CPU_PendSVHandler_nosave
; OSPrioCur = OSPrioHighRdy
LDR R0, =OSPrioCur
LDR R1, =OSPrioHighRdy
LDRB R2, [R1]
STRB R2, [R0]
; OSTCBCurPtr = OSTCBHighRdyPtr
LDR R0, = OSTCBCurPtr
LDR R1, = OSTCBHighRdyPtr
LDR R2, [R1]
STR R2, [R0]
LDR R0, [R2]
LDMIA R0!, {R4-R11}
MSR PSP, R0
ORR LR, LR, #0x04
CPSIE I
BX LR
NOP
ENDP
8、修改OSTimeDly()函数
任务调用OSTimeDly()
函数之后,任务就处于阻塞态,需要将任务从就绪列表中移除
/* 阻塞延时 */
void OSTimeDly(OS_TICK dly)
{
#if 0
/* 设置延时时间 */
OSTCBCurPtr->TaskDelayTicks = dly;
/* 进行任务调度 */
OSSched();
#endif
CPU_SR_ALLOC(); //(1)
/* 进入临界区 */
OS_CRITICAL_ENTER(); //(2)
/* 设置延时时间 */
OSTCBCurPtr->TaskDelayTicks = dly;
/* 从就绪列表中移除 */
//OS_RdyListRemove(OSTCBCurPtr);
OS_PrioRemove(OSTCBCurPtr->Prio); //(3)
/* 退出临界区 */
OS_CRITICAL_EXIT(); //(4)
/* 任务调度 */
OSSched();
}
- (1):定义一个局部变量,用来存
CPU
关中断前的中断状态,因为接下来将任务从就绪列表移除这段代码属于临界短代码,需要关中断。 - (2):进入临界段
- (3):将任务从就绪列表移除, 这里只需将任务在优先级表中对应的位清除即可,暂时不需要把任务
TCB从OSRdyList[]
中移除, 因为接下来OSTimeTick()
函数还是通过扫描OSRdyList[]
来判断任务的延时时间是否到期。当我们加入了时基列表之后, 当任务调用OSTimeDly()
函数进行延时,就可以把任务的TCB
从就绪列表删除,然后把任务TCB
插入时基列表,OSTimeTick()
函数判断任务的延时是否到期只需通过扫描时基列表即可,时基列表在下一个章节实现。 所以这里暂时不能把TCB
从就绪列表中删除,只是将任务优先级在优先级表中对应的位清除来达到任务不处于就绪态的目的。 - (4):退出临界段。
9、修改OSSched()函数
任务调度函数OSSched()
不再是之前的两个任务轮流切换,需要根据优先级来调度, 被迭代的代码已经通过条件编译屏蔽。
void OSSched(void)
{
#if 0
/* 如果当前任务是空闲任务,那么就去尝试执行任务1或者任务2,
看看他们的延时时间是否结束,如果任务的延时时间均没有到期,
那就返回继续执行空闲任务 */
if ( OSTCBCurPtr == &OSIdleTaskTCB )
{
if (OSRdyList[0].HeadPtr->TaskDelayTicks == 0)
{
OSTCBHighRdyPtr = OSRdyList[0].HeadPtr;
}
else if (OSRdyList[1].HeadPtr->TaskDelayTicks == 0)
{
OSTCBHighRdyPtr = OSRdyList[1].HeadPtr;
}
else
{
return; /* 任务延时均没有到期则返回,继续执行空闲任务 */
}
}
else
{
/*如果是task1或者task2的话,检查下另外一个任务,
如果另外的任务不在延时中,就切换到该任务,
否则,判断下当前任务是否应该进入延时状态,
如果是的话,就切换到空闲任务。否则就不进行任何切换 */
if (OSTCBCurPtr == OSRdyList[0].HeadPtr)
{
if (OSRdyList[1].HeadPtr->TaskDelayTicks == 0)
{
OSTCBHighRdyPtr = OSRdyList[1].HeadPtr;
} else if (OSTCBCurPtr->TaskDelayTicks != 0) {
OSTCBHighRdyPtr = &OSIdleTaskTCB;
} else {
/* 返回,不进行切换,因为两个任务都处于延时中 */
return;
}
}
else if (OSTCBCurPtr == OSRdyList[1].HeadPtr)
{
if (OSRdyList[0].HeadPtr->TaskDelayTicks == 0)
{
OSTCBHighRdyPtr = OSRdyList[0].HeadPtr;
}
else if (OSTCBCurPtr->TaskDelayTicks != 0)
{
OSTCBHighRdyPtr = &OSIdleTaskTCB;
}
else
{
/* 返回,不进行切换,因为两个任务都处于延时中 */
return;
}
}
}
/* 任务切换 */
OS_TASK_SW();
#endif
CPU_SR_ALLOC(); //(1)
/* 进入临界区 */
OS_CRITICAL_ENTER(); //(2)
/* 查找最高优先级的任务 */ //(3)
OSPrioHighRdy = OS_PrioGetHighest();
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
/* 如果最高优先级的任务是当前任务则直接返回,不进行任务切换 */ //(4)
if (OSTCBHighRdyPtr == OSTCBCurPtr)
{
/* 退出临界区 */
OS_CRITICAL_EXIT();
return;
}
/* 退出临界区 */
OS_CRITICAL_EXIT(); //(5)
/* 任务切换 */
OS_TASK_SW(); //(6)
}
- (1):定义一个局部变量,用来存
CPU
关中断前的中断状态,因为接下来查找最高优先级这段代码属于临界短代码,需要关中断。 - (2):进入临界段。
- (3):查找最高优先级任务。
- (4):判断最高优先级任务是不是当前任务,如果是则直接返回,否则将继续往下执行,最后执行任务切换。
- (5):退出临界段。
- (6):任务切换。
10、修改OSTimeTick()函数
OSTimeTick()
函数在SysTick
中断服务函数中被调用,是一个周期函数,具体用于扫描就绪列表OSRdyList[]
, 判断任务的延时时间是否到期,如果到期则将任务在优先级表中对应的位置位, 被迭代的代码则通过条件编译屏蔽。
void OSTimeTick (void)
{
unsigned int i;
CPU_SR_ALLOC(); //(1)
/* 进入临界区 */
OS_CRITICAL_ENTER(); //(2)
/* 扫描就绪列表中所有任务的TaskDelayTicks,如果不为0,则减1 */
#if 0
for (i=0; i<OS_CFG_PRIO_MAX; i++)
{
if (OSRdyList[i].HeadPtr->TaskDelayTicks > 0)
{
OSRdyList[i].HeadPtr->TaskDelayTicks --;
}
}
#endif
for (i=0; i<OS_CFG_PRIO_MAX; i++) //(3)
{
if (OSRdyList[i].HeadPtr->TaskDelayTicks > 0)
{
OSRdyList[i].HeadPtr->TaskDelayTicks --;
if (OSRdyList[i].HeadPtr->TaskDelayTicks == 0)
{
/* 为0则表示延时时间到,让任务就绪 */
//OS_RdyListInsert (OSRdyList[i].HeadPtr);
OS_PrioInsert(i);
}
}
}
/* 退出临界区 */
OS_CRITICAL_EXIT(); //(4)
/* 任务调度 */
OSSched();
}
- (1):定义一个局部变量,用来存
CPU
关中断前的中断状态, 因为接下来扫描就绪列表OSRdyList[]
这段代码属于临界短代码,需要关中断。 - (2):进入临界段。
- (3):扫描就绪列表
OSRdyList[]
,判断任务的延时时间是否到期, 如果到期则将任务在优先级表中对应的位置位。 - (4):退出临界段。
二十六、UCOSIII:修改main()函数
1、修改部分:
/*
*******************************************************************
* 全局变量
*******************************************************************
*/
uint32_t flag1;
uint32_t flag2;
uint32_t flag3;
/*
*******************************************************************
* TCB & STACK &任务声明
*******************************************************************
*/
#define TASK1_STK_SIZE 128
#define TASK2_STK_SIZE 128
#define TASK3_STK_SIZE 128
static OS_TCB Task1TCB;
static OS_TCB Task2TCB;
static OS_TCB Task3TCB;
static CPU_STK Task1Stk[TASK1_STK_SIZE];
static CPU_STK Task2Stk[TASK2_STK_SIZE];
static CPU_STK Task3Stk[TASK2_STK_SIZE];
void Task1( void *p_arg );
void Task2( void *p_arg );
void Task3( void *p_arg );
/*
*******************************************************************
* 函数声明
*******************************************************************
*/
void delay(uint32_t count);
/*
*******************************************************************
* main()函数
*******************************************************************
*/
/*
* 注意事项:1、该工程使用软件仿真,debug需选择 Ude Simulator
* 2、在Target选项卡里面把晶振Xtal(Mhz)的值改为25,默认是12,
* 改成25是为了跟system_ARMCM3.c中定义的__SYSTEM_CLOCK相同,
* 确保仿真的时候时钟一致
*/
int main(void)
{
OS_ERR err;
/* CPU初始化:1、初始化时间戳 */
CPU_Init();
/* 关闭中断 */
CPU_IntDis();
/* 配置SysTick 10ms 中断一次 */
OS_CPU_SysTickInit (10);
/* 初始化相关的全局变量 */
OSInit(&err); //(1)
/* 创建任务 */
OSTaskCreate( (OS_TCB*)&Task1TCB,
(OS_TASK_PTR )Task1,
(void *)0,
(OS_PRIO)1, //(2)
(CPU_STK*)&Task1Stk[0],
(CPU_STK_SIZE) TASK1_STK_SIZE,
(OS_ERR *)&err );
OSTaskCreate( (OS_TCB*)&Task2TCB,
(OS_TASK_PTR )Task2,
(void *)0,
(OS_PRIO)2, //(3)
(CPU_STK*)&Task2Stk[0],
(CPU_STK_SIZE) TASK2_STK_SIZE,
(OS_ERR *)&err );
OSTaskCreate( (OS_TCB*)&Task3TCB,
(OS_TASK_PTR )Task3,
(void *)0,
(OS_PRIO)3, //(4)
(CPU_STK*)&Task3Stk[0],
(CPU_STK_SIZE) TASK3_STK_SIZE,
(OS_ERR *)&err );
#if 0
/* 将任务加入到就绪列表 */ //(5)
OSRdyList[0].HeadPtr = &Task1TCB;
OSRdyList[1].HeadPtr = &Task2TCB;
#endif
/* 启动OS,将不再返回 */
OSStart(&err);
}
/*
*******************************************************************
* 函数实现
*******************************************************************
*/
/* 软件延时 */
void delay (uint32_t count)
{
for (; count!=0; count--);
}
void Task1( void *p_arg )
{
for ( ;; ) {
flag1 = 1;
OSTimeDly(2);
flag1 = 0;
OSTimeDly(2);
}
}
void Task2( void *p_arg )
{
for ( ;; ) {
flag2 = 1;
OSTimeDly(2);
flag2 = 0;
OSTimeDly(2);
}
}
void Task3( void *p_arg )
{
for ( ;; ) {
flag3 = 1;
OSTimeDly(2);
flag3 = 0;
OSTimeDly(2);
}
}
- (1):加入了优先级相关的全局变量
OSPrioCur
和OSPrioHighRdy
的初始化。 - (2)、(3)和(4):为每个任务分配了优先级,
任务1
的优先级为1
,任务2的优先级为2
,任务3的优先级为3
。 - (5):将任务插入就绪列表这部分功能由
OSTaskCreate()
实现,这里通过条件编译屏蔽掉。
2、实验现象
进入软件调试,全速运行程序,从逻辑分析仪中可以看到三个任务的波形是完全同步,就好像CPU
在同时干三件事情
任务开始的启动过程具体见图
上图是任务1、2和3
刚开始启动时的软件仿真波形图,系统从启动到任务1
开始运行前花的时间为TIME1
, 等于0.26MS
。
任务1
开始运行,然后调用OSTimeDly(1)
进入延时,随后进行任务切换,切换到任务2
开始运行, 从任务1
切换到任务2
花费的时间等于TIME2-TIME1
,等于0.01MS
。
任务2
开始运行,然后调用OSTimeDly(1)
进入延时, 随后进行任务切换,切换到任务3
开始运行,从任务2
切换到任务3
花费的时间等于TIME3-TIME1
,等于0.01MS
。
任务3
开始运行,然后调用OSTimeDly(1)
进入延时,随后进行任务切换,这个时候我们创建的3个任务都处于延时状态, 那么系统就切换到空闲任务,在三个任务延时未到期之前,系统一直都是在运行空闲任务。
当第一个SysTick
中断产生, 中断服务函数会调用OSTimeTick()
函数扫描每个任务的延时是否到期,因为是延时1个SysTick
周期, 所以第一个SysTick
中断产生就意味着延时都到期,任务1、2和3
依次进入就绪态,再次回到任务本身接着运行, 将自身的Flag
清零,然后任务1、2和3
又依次调用OSTimeDly(1)
进入延时状态,直到下一个SysTick
中断产生前, 系统都处在空闲任务中,一直这样循环下去。