FreeRTOS从入门到精通 第十一章(FreeRTOS时间管理)
参考教程:【正点原子】手把手教你学FreeRTOS实时系统_哔哩哔哩_bilibili
一、延时函数
1、相对延时与绝对延时
(1)函数概览:
函数 | 描述 |
vTaskDelay | 相对延时,函数参数为阻塞时间 |
xTaskDelayUntil | 绝对延时,函数参数依次为指向上一次任务唤醒时间的指针pxPreviousWakeTime、绝对延时时间xTimeIncrement |
(2)相对延时是指每次延时都是从执行函数vTaskDelay开始,直到延时指定的时间结束,一旦调用则阻塞任务自身。
(3)绝对延时往往将整个任务的运行周期看成一个整体,适用于需要按照一定频率运行的任务,如果任务中调用绝对延时,那么从最近一次开始执行到阻塞过程中任务本身耗费的时间、调用绝对延时所耗费的时间以及任务阻塞时间,三者之和即为绝对延时的时间,如下图所示。(调用绝对延时函数时,绝对延时函数会通过指向上一次任务唤醒时间的指针更新任务最近一次的唤醒时间)
2、相对延时函数源码剖析
(1)void vTaskDelay函数源码:
void vTaskDelay(const TickType_t xTicksToDelay)
{
BaseType_t xAlreadyYielded = pdFALSE;
traceENTER_vTaskDelay(xTicksToDelay);
if(xTicksToDelay > (TickType_t) 0U) //函数参数需要大于0,否则无效
{
vTaskSuspendAll(); //用户可选择性地实现该函数
{
configASSERT(uxSchedulerSuspended == 1U);
traceTASK_DELAY();
prvAddCurrentTaskToDelayedList(xTicksToDelay, pdFALSE); //将当前任务移至阻塞列表(第二个参数是pdTRUE则移动至挂起列表)
}
xAlreadyYielded = xTaskResumeAll(); //恢复任务调度器
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( xAlreadyYielded == pdFALSE ) //判断是否需要任务切换
{
taskYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
traceRETURN_vTaskDelay();
}
(2)prvAddCurrentTaskToDelayedList函数源码:
static void prvAddCurrentTaskToDelayedList(TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely)
{
TickType_t xTimeToWake;
const TickType_t xConstTickCount = xTickCount; //记录当前系统的时钟节拍
List_t * const pxDelayedList = pxDelayedTaskList;
List_t * const pxOverflowDelayedList = pxOverflowDelayedTaskList;
#if ( INCLUDE_xTaskAbortDelay == 1 )
{
pxCurrentTCB->ucDelayAborted = ( uint8_t ) pdFALSE;
}
#endif
if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
#if ( INCLUDE_vTaskSuspend == 1 )
{
if((xTicksToWait == portMAX_DELAY) &&(xCanBlockIndefinitely != pdFALSE))
{
/* xCanBlockIndefinitely为pdFALSE时,不支持直接将任务挂到挂起列表,走else分支 */
listINSERT_END(&xSuspendedTaskList, &(pxCurrentTCB->xStateListItem) );
}
else
{
//解除阻塞时间(唤醒时间) = 当前系统时间 + 阻塞时间
xTimeToWake = xConstTickCount + xTicksToWait;
//按唤醒时间顺序将任务插入阻塞列表
listSET_LIST_ITEM_VALUE(&(pxCurrentTCB->xStateListItem),xTimeToWake);
if(xTimeToWake <xConstTickCount) //阻塞时间溢出,任务挂到溢出阻塞列表
{
traceMOVED_TASK_TO_OVERFLOW_DELAYED_LIST();
vListInsert(pxOverflowDelayedList, &(pxCurrentTCB->xStateListItem));
}
else //阻塞时间未溢出,任务挂到阻塞列表
{
traceMOVED_TASK_TO_DELAYED_LIST();
vListInsert(pxDelayedList, &(pxCurrentTCB->xStateListItem));
if( xTimeToWake < xNextTaskUnblockTime )
{
//更新阻塞列表中距当前时刻最近唤醒的任务的唤醒时间
xNextTaskUnblockTime = xTimeToWake;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
}
#else /* INCLUDE_vTaskSuspend */
{
xTimeToWake = xConstTickCount + xTicksToWait;
listSET_LIST_ITEM_VALUE(&(pxCurrentTCB->xStateListItem), xTimeToWake);
if( xTimeToWake < xConstTickCount )
{
traceMOVED_TASK_TO_OVERFLOW_DELAYED_LIST();
vListInsert( pxOverflowDelayedList, &( pxCurrentTCB->xStateListItem ) );
}
else
{
traceMOVED_TASK_TO_DELAYED_LIST();
vListInsert( pxDelayedList, &( pxCurrentTCB->xStateListItem ) );
if( xTimeToWake < xNextTaskUnblockTime )
{
xNextTaskUnblockTime = xTimeToWake;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
( void ) xCanBlockIndefinitely;
}
#endif /* INCLUDE_vTaskSuspend */
}
二、延时函数演示实验
1、原理图与实验目标
(1)原理图:
(2)实验目标:
①设计3个任务——start_task、task1、task2:
[1]start_task:用于创建其它两个任务,然后删除自身。
[2]task1:用于展示相对延时函数vTaskDelay的使用。
[3]task2:用于展示绝对延时函数vTaskDelayUntil的使用。
②预期实验现象:两个LED灯不同步闪烁。
2、实验步骤
(1)将上一章中“任务时间统计API函数实验”的工程文件夹复制一份,在拷贝版中进行实验。
(2)移除task3任务的相关内容,并更改task1和task2的实现,task1中死等100ms+相对延时500ms,task2中死等100ms+绝对延时500ms。
void task1(void)
{
while(1)
{
LED1_Turn(); //LED1状态翻转
Delay_ms(100); //死等100ms
vTaskDelay(500); //相对延时500ms
}
}
void task2(void)
{
TickType_t xLastWakeTime;
xLastWakeTime = xTaskGetTickCount(); //获取任务上一次唤醒的时间
while(1)
{
LED2_Turn(); //LED2状态翻转
Delay_ms(100); //死等100ms
vTaskDelayUntil(&xLastWakeTime, 500); //绝对延时500ms
}
}
(3)程序完善好后点击“编译”,然后将程序下载到开发板上。
3、程序执行流程
(1)main函数全流程(省略OLED屏显示字符串部分):
①初始化OLED模块、按键模块、LED模块(还有串口模块,下图未示出)。
②调用FreeRTOS_Test函数。
(2)测试函数全流程:
①创建任务start_task。
②开启任务调度器。
(3)多任务调度执行阶段(发生在开启任务调度器以后):
①start_task任务函数首先进入临界区,在临界区中start_task任务不会被其它任务打断,接着start_task任务依次创建任务task1、task2,然后删除自身,接着退出临界区,让出CPU资源。
②task2的优先级较高,优先执行task2,如下所示,在不考虑task1的情况下(或者说task1运行时task2一直处于自我阻塞状态),task2依次执行LED2状态翻转、死等100ms及绝对延时500ms,这些操作加上阻塞时间共500ms。
③task2阻塞时task1可以执行,task1依次执行LED1状态翻转、死等100ms及相对延时500ms,这些操作加上阻塞时间共约600ms。
④随着时间的推移,task1在进行死等的时候很可能task2的阻塞时间正好结束,由于task2的优先级较高,task1会直接被打断,从而引发task1非预期的等待,这就导致LED1在这次闪烁轮回中的闪烁频率会产生细微的变化。