RTOS 中的延时叫阻塞延时,即任务需要延时的时候,任务会放弃 CPU 的使用权,CPU 可以去干其它的事情,当任务延时时间到,重新获取 CPU 使用权,任务继续运行,这样就充分地利用了 CPU 的资源,而不是干等着。
当任务需要延时,进入阻塞状态。此时如果没有其它任务可以运行,RTOS 都会为 CPU 创建一个空闲任务,这个时候 CPU 就运行空闲任务。在FreeRTOS 中,空闲任务是系统在【启动调度器】的时候创建的优先级最低的任务,空闲任 务主体主要是做一些系统内存的清理工作。
1、实现空闲任务 (1)定义空闲任务的栈#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 ) StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE];(2)定义空闲任务的任务控制块
TCB_t IdleTaskTCB;(3)创建空闲任务
extern TCB_t IdleTaskTCB;
void vApplicationGetIdleTaskMemory( TCB_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize );
void vTaskStartScheduler( void )
{
TCB_t *pxIdleTaskTCBBuffer = NULL;
StackType_t *pxIdleTaskStackBuffer = NULL;
uint32_t ulIdleTaskStackSize;
vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer,
&pxIdleTaskStackBuffer,
&ulIdleTaskStackSize );
xIdleTaskHandle =
xTaskCreateStatic( (TaskFunction_t)prvIdleTask,
(char *)"IDLE",
(uint32_t)ulIdleTaskStackSize ,
(void *) NULL,
(StackType_t *)pxIdleTaskStackBuffer,
(TCB_t *)pxIdleTaskTCBBuffer );
vListInsertEnd( &( pxReadyTasksLists[0] ), &( ((TCB_t *)pxIdleTaskTCBBuffer)->xStateListItem ) );
pxCurrentTCB = &Task1TCB;
if ( xPortStartScheduler() != pdFALSE ){
}
}
2、实现阻塞延时
阻塞延时的阻塞是指任务调用该延时函数后,任务会被剥离 CPU 使用权,然后进入阻塞状态,直到延时结束,任务重新获取 CPU 使用权才可以继续运行。
在任务阻塞的这段时间,CPU 可以去执行其它的任务,如果其它的任务也在延时状态,那么 CPU 就将运行空闲任务。
(1)vTaskDelay ()函数void vTaskDelay( const TickType_t xTicksToDelay )
{
TCB_t *pxTCB = NULL;
pxTCB = pxCurrentTCB;
pxTCB->xTicksToDelay = xTicksToDelay;
taskYIELD();
}
xTicksToDelay 定义
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack;
ListItem_t xStateListItem;
StackType_t *pxStack;
char pcTaskName[ configMAX_TASK_NAME_LEN ];
TickType_t xTicksToDelay;
} tskTCB;
(2)修改 vTaskSwitchContext()函数
调用 tashYIELD()会产生 PendSV中断,在 PendSV中断服务函数中会调用上下文切换函数 vTaskSwitchContext(),该函数的作用是寻找最高优先级的就绪任务,然后更新 pxCurrentTCB。
多增加了一个空闲任务,则需要让pxCurrentTCB 在这三个任务中切换,算法需要改变:
#if 0
void vTaskSwitchContext( void )
{
if ( pxCurrentTCB == &Task1TCB ){
pxCurrentTCB = &Task2TCB;
}
else{
pxCurrentTCB = &Task1TCB;
}
}
#else
void vTaskSwitchContext( void )
{
if ( pxCurrentTCB == &IdleTaskTCB ){
if (Task1TCB.xTicksToDelay == 0){
pxCurrentTCB =&Task1TCB;
}
else if (Task2TCB.xTicksToDelay == 0) {
pxCurrentTCB =&Task2TCB;
}
else {
return;
}
}
else
{
if (pxCurrentTCB == &Task1TCB) {
if (Task2TCB.xTicksToDelay == 0) {
pxCurrentTCB =&Task2TCB;
}
else if (pxCurrentTCB->xTicksToDelay != 0) {
pxCurrentTCB = &IdleTaskTCB;
}
else {
return;
}
}
else if (pxCurrentTCB == &Task2TCB) {
if (Task1TCB.xTicksToDelay == 0) {
pxCurrentTCB =&Task1TCB;
}
else if (pxCurrentTCB->xTicksToDelay != 0) {
pxCurrentTCB = &IdleTaskTCB;
}
else {
return;
}
}
}
}
#endif
3、 SysTick 中断服务函数
在任务上下文切换函数 vTaskSwitchContext ()中,会判断每个任务的任务控制块中的延时成员 xTicksToDelay 的值是否为 0,如果为 0就要将对应的任务就绪,如果不为 0 就继续延时。
如果一个任务要延时,一开始 xTicksToDelay 肯定不为 0,当 xTicksToDelay 变为0 的时候表示延时结束。
xTicksToDelay 是以什么周期在递减?在哪里递减在FreeRTOS 中,这个周期由 SysTick 中断提供,操作系统里面的最小的时间单位就是SysTick 的中断周期,我们称之为一个 tick。
SysTick 中断服务函数
void xPortSysTickHandler( void )
{
vPortRaiseBASEPRI();
xTaskIncrementTick();
vPortClearBASEPRIFromISR();
}
xTaskIncrementTick()函数:
void xTaskIncrementTick( void )
{
TCB_t *pxTCB = NULL;
BaseType_t i = 0;
const TickType_t xConstTickCount = xTickCount + 1;
xTickCount = xConstTickCount;
for (i=0; ixTicksToDelay > 0){
pxTCB->xTicksToDelay --;
}
}
portYIELD();
}
4、SysTick 初始化函数
SysTick 的中断服务函数要想被顺利执行,则 SysTick 必须先初始化。



