6. 阻塞延时与空闲任务¶
在上一章节中,任务体内的延时使用的是软件延时,即还是让CPU空等来达到延时的效果。使用RTOS的很大优势就是榨干CPU的性能,永远不能让它闲着,任务如果需要延时也就不能再让CPU空等来实现延时的效果。RTOS中的延时叫阻塞延时,即任务需要延时的时候,任务会放弃CPU的使用权,CPU可以去干其它的事情 ,当任务延时时间到,重新获取CPU使用权,任务继续运行,这样就充分地利用了CPU的资源,而不是干等着。
当任务需要延时,进入阻塞状态,那CPU又去干什么事情了?如果没有其它任务可以运行,RTOS都会为CPU创建一个空闲任务,这个时候CPU就运行空闲任务。在uC/OS- III中,空闲任务是系统在初始化的时候创建的优先级最低的任务,空闲任务主体很简单,只是对一个全局变量进行计数。鉴于空闲任务的这种特性,在实际应用中,当系统进入空闲任务的时候,可在空闲任务中让单片机进入休眠或者低功耗等操作。
6.1. 实现空闲任务¶
6.1.1. 定义空闲任务堆栈¶
空闲任务堆栈在os_cfg_app.c(os_cfg_app.c第一次使用需要自行在文件夹uCOS-IIISource中新建并添加到工程的uC/OS-III Source组)文件中定义,具体见 代码清单7-1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /*
*******************************************************************
* 数据域
*******************************************************************
*/
CPU_STK OSCfg_IdleTaskStk[OS_CFG_IDLE_TASK_STK_SIZE];(1)
/*
*******************************************************************
* 常量
*******************************************************************
*/
/* 空闲任务堆栈起始地址 */
CPU_STK * const OSCfg_IdleTaskStkBasePtr = \(2)
(CPU_STK *)&OSCfg_IdleTaskStk[0];
/* 空闲任务堆栈大小 */
CPU_STK_SIZE const OSCfg_IdleTaskStkSize = \
(CPU_STK_SIZE)OS_CFG_IDLE_TASK_STK_SIZE;
|
代码清单7-1:(1):空闲任务的堆栈是一个定义好的数组,大小由OS_CFG_IDLE_TASK_STK_SIZE这个宏控制。OS_CFG_IDLE_TASK_STK_SIZE在os_cfg_app.h这个头文件定义,大小为128,具体见 代码清单7-2。
1 2 3 4 5 6 7 8 9 10 11 12 13 | #ifndef OS_CFG_APP_H
#define OS_CFG_APP_H
/*
*******************************************************************
* 常量
*******************************************************************
*/
/* 空闲任务堆栈大小 */
#define OS_CFG_IDLE_TASK_STK_SIZE 128u
#endif/* OS_CFG_APP_H */
|
代码清单7-2:(2):空闲任务的堆栈的起始地址和大小均被定义成一个常量,不能被修改。变量OSCfg_IdleTaskStkBasePtr和OSCfg_IdleTaskStkSize同时还在os.h中声明,这样就具有全局属性,可以在其它文件里面被使用,具体声明见 代码清单7-3。
1 2 3 4 | /* 空闲任务堆栈起始地址 */
extern CPU_STK * const OSCfg_IdleTaskStkBasePtr;
/* 空闲任务堆栈大小 */
extern CPU_STK_SIZE const OSCfg_IdleTaskStkSize;
|
6.1.2. 定义空闲任务TCB¶
任务控制块TCB是每一个任务必须的,空闲任务的TCB在os.h中定义,是一个全局变量,具体见 代码清单7-4。
1 | OS_EXT OS_TCB OSIdleTaskTCB;
|
6.1.3. 定义空闲任务函数¶
空闲任务正如其名,空闲,任务体里面只是对全局变量OSIdleTaskCtr ++ 操作,具体实现见 代码清单7-5。
1 2 3 4 5 6 7 8 9 10 | /* 空闲任务 */
void OS_IdleTask (void *p_arg)
{
p_arg = p_arg;
/* 空闲任务什么都不做,只对全局变量OSIdleTaskCtr ++ 操作 */
for (;;) {
OSIdleTaskCtr++;
}
}
|
代码清单7-5: 中的全局变量OSIdleTaskCtr在os.h中定义,具体见 代码清单7-6。
1 2 | /* 空闲任务计数变量 */
OS_EXT OS_IDLE_CTR OSIdleTaskCtr;
|
代码清单7-6: 中的OS_IDLE_CTR是在os_type.h中重新定义的数据类型,具体见 代码清单7-7。
1 2 | /* 空闲任务计数变量定义 */
typedef CPU_INT32U OS_IDLE_CTR;
|
6.1.4. 空闲任务初始化¶
空闲任务的初始化在OSInit()在完成,意味着在系统还没有启动之前空闲任务就已经创建好,具体在os_core.c定义,具体代码见 代码清单7-8。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | void OSInit (OS_ERR *p_err)
{
/* 配置OS初始状态为停止态 */
OSRunning = OS_STATE_OS_STOPPED;
/* 初始化两个全局TCB,这两个TCB用于任务切换 */
OSTCBCurPtr = (OS_TCB *)0;
OSTCBHighRdyPtr = (OS_TCB *)0;
/* 初始化就绪列表 */
OS_RdyListInit();
/* 初始化空闲任务 */
OS_IdleTaskInit(p_err);(1)
if (*p_err != OS_ERR_NONE) {
return;
}
}
/* 空闲任务初始化 */
void OS_IdleTaskInit(OS_ERR *p_err)
{
/* 初始化空闲任务计数器 */
OSIdleTaskCtr = (OS_IDLE_CTR)0;(2)
/* 创建空闲任务 */
OSTaskCreate( (OS_TCB *)&OSIdleTaskTCB,(3)
(OS_TASK_PTR )OS_IdleTask,
(void *)0,
(CPU_STK *)OSCfg_IdleTaskStkBasePtr,
(CPU_STK_SIZE)OSCfg_IdleTaskStkSize,
(OS_ERR *)p_err );
}
|
代码清单7-8:(1):空闲任务初始化函数在OSInit中调用,在系统还没有启动之前就被创建。
代码清单7-8:(2):初始化空闲任务计数器,我们知道,这个是预先在os.h中定义好的全局变量。
代码清单7-8:(3):创建空闲任务,把堆栈,TCB,任务函数联系在一起。
6.2. 实现阻塞延时¶
阻塞延时的阻塞是指任务调用该延时函数后,任务会被剥离CPU使用权,然后进入阻塞状态,直到延时结束,任务重新获取CPU使用权才可以继续运行。在任务阻塞的这段时间,CPU可以去执行其它的任务, 如果其它的任务也在延时状态,那么CPU就将运行空闲任务。阻塞延时函数在os_time.c中定义,具体代码实现见 代码清单7-9。
代码清单‑9阻塞延时代码
1 2 3 4 5 6 7 8 9 | /* 阻塞延时 */
void OSTimeDly(OS_TICK dly)
{
/* 设置延时时间 */
OSTCBCurPtr->TaskDelayTicks = dly;(1)
/* 进行任务调度 */
OSSched();(2)
}
|
代码清单7-9:(1):TaskDelayTicks是任务控制块的一个成员,用于记录任务需要延时的时间,单位为SysTick的中断周期。比如我们本书当中SysTick的中断周期为10ms,调用OSTimeDly(2)则完成2*10ms的延时。TaskDelayTicks的定义具体见 代码清单7-10。
1 2 3 4 5 6 7 | struct os_tcb {
CPU_STK *StkPtr;
CPU_STK_SIZE StkSize;
/* 任务延时周期个数 */
OS_TICK TaskDelayTicks;
};
|
代码清单7-9:(2):任务调度。这个时候的任务调度与上一章节的不一样,具体见 代码清单7-11,其中加粗部分为上一章节的代码,现已用条件编译屏蔽掉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | void OSSched(void)
{
#if 0/* 非常简单的任务调度:两个任务轮流执行 */
if ( OSTCBCurPtr == OSRdyList[0].HeadPtr ) {
OSTCBHighRdyPtr = OSRdyList[1].HeadPtr;
} else {
OSTCBHighRdyPtr = OSRdyList[0].HeadPtr;
}
#endif
/* 如果当前任务是空闲任务,那么就去尝试执行任务1或者任务2,
看看他们的延时时间是否结束,如果任务的延时时间均没有到期,
那就返回继续执行空闲任务 */
if ( OSTCBCurPtr == &OSIdleTaskTCB ) {(1)
if (OSRdyList[0].HeadPtr->TaskDelayTicks == 0) {
OSTCBHighRdyPtr = OSRdyList[0].HeadPtr;
} else if (OSRdyList[1].HeadPtr->TaskDelayTicks == 0) {
OSTCBHighRdyPtr = OSRdyList[1].HeadPtr;
} else {
/* 任务延时均没有到期则返回,继续执行空闲任务 */
return;
}
} else {(2)
/*如果是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();(3)
}
|
代码清单7-11:(1):如果当前任务是空闲任务,那么就去尝试执行任务1或者任务2,看看他们的延时时间是否结束,如果任务的延时时间均没有到期,那就返回继续执行空闲任务。
代码清单7-11:(2):如果当前任务不是空闲任务则会执行到此,那就看看当前任务是哪个任务。无论是哪个任务,都要检查下另外一个任务是否在延时中,如果没有在延时,那就切换到该任务,如果有在延时,那就判断下当前任务是否应该进入延时状态,如果是的话,就切换到空闲任务。否则就不进行任务切换。
代码清单7-11:(3):任务切换,实际就是触发PendSV异常。
6.3. main函数¶
main函数和任务代码变动不大,具体见 代码清单7-12,有变动部分代码已加粗。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | int main(void)
{
OS_ERR err;
/* 关闭中断 */
CPU_IntDis();
/* 配置SysTick 10ms 中断一次 */
OS_CPU_SysTickInit (10);
/* 初始化相关的全局变量 */
OSInit(&err);(1)
/* 创建任务 */
OSTaskCreate ((OS_TCB*) &Task1TCB,
(OS_TASK_PTR ) Task1,
(void *) 0,
(CPU_STK*) &Task1Stk[0],
(CPU_STK_SIZE) TASK1_STK_SIZE,
(OS_ERR *) &err);
OSTaskCreate ((OS_TCB*) &Task2TCB,
(OS_TASK_PTR ) Task2,
(void *) 0,
(CPU_STK*) &Task2Stk[0],
(CPU_STK_SIZE) TASK2_STK_SIZE,
(OS_ERR *) &err);
/* 将任务加入到就绪列表 */
OSRdyList[0].HeadPtr = &Task1TCB;
OSRdyList[1].HeadPtr = &Task2TCB;
/* 启动OS,将不再返回 */
OSStart(&err);
}
/* 任务1 */
void Task1( void *p_arg )
{
for ( ;; ) {
flag1 = 1;
//delay( 100 );
OSTimeDly(2);(2)
flag1 = 0;
//delay( 100 );
OSTimeDly(2);
/* 任务切换,这里是手动切换 */
//OSSched();
}
}
/* 任务2 */
void Task2( void *p_arg )
{
for ( ;; ) {
flag2 = 1;
//delay( 100 );
OSTimeDly(2);(3)
flag2 = 0;
//delay( 100 );
OSTimeDly(2);
/* 任务切换,这里是手动切换 */
//OSSched();
}
}
|
代码清单7-12:(1):空闲任务初始化函数在OSInint中调用,在系统启动之前创建好空闲任务。
代码清单7-12:(2)和(3):延时函数均替代为阻塞延时,延时时间均为2个SysTick中断周期,即20ms。