根据个人的学习方向,学习FreeRTOS。由于野火小哥把FreeRTOS讲得比较含蓄,打算在本专栏尽量细化一点。作为个人笔记,仅供参考或查阅。
配套资料:FreeRTOS内核实现与应用开发实战指南、野火FreeRTOS配套视频源码、b站野火FreeRTOS视频。搭配来看更佳哟!!!
延时函数
常用的延时阻塞函数vTaskDelay()
#if ( INCLUDE_vTaskDelay == 1 )
void vTaskDelay( const TickType_t xTicksToDelay )
{
BaseType_t xAlreadyYielded = pdFALSE;
if( xTicksToDelay > ( TickType_t ) 0U )
{
configASSERT( uxSchedulerSuspended == 0 );
vTaskSuspendAll(); //挂起任务调度器
{
traceTASK_DELAY();
//将当前任务添加到延时列表
prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
}
xAlreadyYielded = xTaskResumeAll(); //恢复任务调度器
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( xAlreadyYielded == pdFALSE )
{
portYIELD_WITHIN_API(); //强制切换任务,将PendSV的bit28 置1
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* INCLUDE_vTaskDelay */
上面函数调用了prvAddCurrentTaskToDelayedList()函数,如下
//xCanBlockIndefinitely:是否可以永久阻塞 pdFALSE:不允许永久阻塞,即不允许挂起当前任务 pdTRUE:允许永久阻塞,支持挂起当前任务
static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely )
{
TickType_t xTimeToWake;
const TickType_t xConstTickCount = xTickCount; //获取当前调用延时函数的时间点
/* 省略代码 */
/* 在将任务添加到阻塞列表之前,从就绪列表中删除任务,因为两个列表都使用相同的列表项。 */
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 ) ) //支持挂起
{
vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) ); //将任务插入到挂起列表
}
else
{
//计算唤醒任务的时间
xTimeToWake = xConstTickCount + xTicksToWait;
/* 列表项将按唤醒时间顺序插入 */
listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );
if( xTimeToWake < xConstTickCount ) //唤醒时间如果溢出了,则会添加到延时溢出列表
{
vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
}
else
{
//唤醒时间没有溢出,添加到延时列表
vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
//如果下一个要唤醒的任务就是当前延时的任务,那么就需要重置下一个任务的解除阻塞时间 xNextTaskUnblockTime 为唤醒当前延时任务的时间xTimeToWake。
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 )
{
vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
}
else
{
vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
if( xTimeToWake < xNextTaskUnblockTime )
{
xNextTaskUnblockTime = xTimeToWake;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
( void ) xCanBlockIndefinitely;
}
#endif /* INCLUDE_vTaskSuspend */
}
vTaskDelay ()的延时是相对的,是不确定的,它的延时是等 vTaskDelay ()调用完毕后开始计算的。并且 vTaskDelay()延时时间到了之后,如果有高优先级的任务或者中断正在执行,
被延时阻塞的任务并不会马上解除阻塞,所有每次执行任务的周期并不完全确定。
vTaskDelayUntil()延时是绝对的,适用于周期性执行的任务。 当(*pxPreviousWakeTime + xTimeIncrement)时间到达后,vTaskDelayUntil()函数立刻返回,如果任务是最高优先级的,
那么任务会立马解除阻塞,所以说 vTaskDelayUntil()函数的延时是绝对性的。
绝对延时vTaskDelayUntil()常用于较精确的周期运行任务,比如我有一个任务, 希望它以固定频率定期执行,而不受外部的影响,任务从上一次运行开始到下一次运行开始的时间间隔是绝对的。
vTaskDelayUntil()如下
#if ( INCLUDE_vTaskDelayUntil == 1 )
//pxPreviousWakeTime,保存任务最后一次解除阻塞的的时刻。第一次使用时,该变量必须初始化为当前时间,之后这个变量会在vTaskDelayUntil()函数内自动更新。
//xTimeIncrement,周期循环时间。 当 时 间 等 于 (*pxPreviousWakeTime + xTimeIncrement)时,任务解除阻塞。如果不改变参数 xTimeIncrement 的值,调用该函数的任务会按照固定频率执行。
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
{
TickType_t xTimeToWake;
BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;
configASSERT( pxPreviousWakeTime );
configASSERT( ( xTimeIncrement > 0U ) );
configASSERT( uxSchedulerSuspended == 0 );
vTaskSuspendAll();
{
//获取开始进行延时的时间点
const TickType_t xConstTickCount = xTickCount;
//计算延时到达的时间,也就是唤醒任务的时间
xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;
/* pxPreviousWakeTime 中保存的是上次唤醒时间,唤醒后需要一定时间执行任务主体代码,如果上次唤醒时间大于当前时间,说明节拍计数器溢出了*/
if( xConstTickCount < *pxPreviousWakeTime )
{
/* 如果唤醒的时间小于上次唤醒时间,并且唤醒时间大于开始计时的时间,这样子就是相当于没有溢出,也就是保证了周期性延时时间大于任务主体代码的执行时间*/
if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )
{
xShouldDelay = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
//只是唤醒时间溢出或者都没溢出的情况,保证了延时时间大于任务主体代码的执行时间
if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )
{
xShouldDelay = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
//更新上一次的唤醒时间
*pxPreviousWakeTime = xTimeToWake;
if( xShouldDelay != pdFALSE )
{
traceTASK_DELAY_UNTIL( xTimeToWake );
/* prvAddCurrentTaskToDelayedList()函数需要的是阻塞时间而不是唤醒时间,因此减去当前的滴答计数。 */
prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
xAlreadyYielded = xTaskResumeAll();
//强制执行一次上下文切换
if( xAlreadyYielded == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* INCLUDE_vTaskDelayUntil */
xTimeIncrement :任务周期时间。
pxPreviousWakeTime :上一次唤醒任务的时间点。
xTimeToWake :本次要唤醒任务的时间点。
xConstTickCount :进入延时的时间点。
绝对延时vTaskDelayUntil()实例
void vTaskA( void * pvParameters )
{
/* 用于保存上次时间。调用后系统自动更新 */
static portTickType PreviousWakeTime;
/* 设置延时时间,将时间转为节拍数 */
const portTickType TimeIncrement = pdMS_TO_TICKS(1000);
/* 获取当前系统时间 */
PreviousWakeTime = xTaskGetTickCount();
while (1)
{
/* 调用绝对延时函数,任务时间间隔为 1000 个 tick */
vTaskDelayUntil( &PreviousWakeTime, TimeIncrement );
// ...
// 这里为任务主体代码
// ...
}
}
本节官方封装了两个函数,可以直接使用。
vTaskDelay()相对延时函数。
vTaskDelayUntil()绝对延时函数。