目录
- 1.延时函数-介绍
- 2.相对延时函数-解析
- 2.1函数`prvAddCurrentTaskToDelayedList`-解析
- 2.3滴答定时器中断服务函数`xPortSysTickHandler()`-解析
- 2.4函数`taskSWITCH_DELAYED_LISTS() `-解析
- 3.延时函数-实验
- 4.总结
1.延时函数-介绍
函数 | 描述 |
---|
vTaskDelay() | 相对延时 |
xTaskDelayUntil() | 绝对延时 |
相对延时:指每次延时都是从执行函数vTaskDelay()开始,直到延时指定的时间结束(任务被阻塞的时间,到调用此函数开始的时间);
绝对延时:指将整个任务的运行周期看成一个整体,适用于需要按照一定频率运行的任务(整个任务执行的时间,从头到尾的时间)。
上图中的xTimeIncrement为绝对延时时间(假如绝对延时时间为100ms,那么以下三部分之和为100ms),包括以下三部分:
(1)为任务主体,也就是任务真正要做的工作;
(2)是任务函数中调用vTaskDelayUntil()对任务进行延时;
(3)为其他任务在运行(高优先级的任务进行抢占)。
2.相对延时函数-解析
首先入口参数必须大于0,延时时间有效。
void vTaskDelay( const TickType_t xTicksToDelay )
{
BaseType_t xAlreadyYielded = pdFALSE;
if( xTicksToDelay > ( TickType_t ) 0U )
vTaskSuspendAll()
挂起任务调度器,traceTASK_DELAY()
函数并没有被实现。 prvAddCurrentTaskToDelayedList
(点击函数名可跳转至解析)
将当前正在执行的任务移到阻塞列表。
vTaskSuspendAll();
{
traceTASK_DELAY();
prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
}
恢复任务调度器。
xAlreadyYielded = xTaskResumeAll();
判断xAlreadyYielded 是否需要进行任务切换。
else
{
mtCOVERAGE_TEST_MARKER();
}
if( xAlreadyYielded == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
此函数是将任务挂载到阻塞列表,解除是在滴答定时器的中断服务函数 xPortSysTickHandler()
(点击函数名可跳转至解析)中。
2.1函数prvAddCurrentTaskToDelayedList
-解析
函数prvAddCurrentTaskToDelayedList()
有两个入口参数一个是延时时间xTicksToWait,另一个是xCanBlockIndefinitely 等于pdFALSE。
static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait,
const BaseType_t xCanBlockIndefinitely )
xConstTickCount 存储时钟节拍,滴答定时器中断一次,变量xTickCount加1。宏INCLUDE_xTaskAbortDelay 判断是否是中断延时,这里并没有使用,所以不用管。
{
TickType_t xTimeToWake;
const TickType_t xConstTickCount = xTickCount;
#if ( INCLUDE_xTaskAbortDelay == 1 )
{
pxCurrentTCB->ucDelayAborted = pdFALSE;
}
#endif
将当前正在执行的任务的状态列表项使用函数uxListRemove()
从就绪列表中移除,移除完判断是否有同等优先级的任务,没有就代表只有这一个任务,被移除掉后就绪列表中剩余任务为0,那么将此优先级的任务优先级复位。
if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
宏INCLUDE_vTaskSuspend 判断是否使能挂起,判断延时时间xTicksToWait等于最大延时时间并且xCanBlockIndefinitely 不等于pdFALSE,此时将任务挂载到挂起列表中,由于传入参数为pdFALSE,所以不会挂载到挂起列表中,则执行else内容。
#if ( INCLUDE_vTaskSuspend == 1 )
{
if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )
{
listINSERT_END( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );
}
else中首先记录时间,xConstTickCount 为进入函数prvAddCurrentTaskToDelayedList()
时记录的时间,加上延时时间xTicksToWait,就是任务到截止阻塞时间该被恢复的时间;通过函数listSET_LIST_ITEM_VALUE
将延时时间写入到列表项值里,此值将用作挂载到阻塞列表时根据此值进行升序排列;
else
{
xTimeToWake = xConstTickCount + xTicksToWait;
listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );
判断需要等待截止的时间是否小于进入函数prvAddCurrentTaskToDelayedList()
时记录的时间,这里判断是否数值溢出,如果溢出就将任务挂载到溢出阻塞列表中,否则挂载到阻塞列表中。
if( xTimeToWake < xConstTickCount )
{
vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
}
else
{
vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
判断下一个阻塞超时时间如果大于新的阻塞时间,那么将新的阻塞时间更新为下一个阻塞超时时间。例如下一个xNextTaskUnblockTime 超时时间为30ms,新的阻塞时间xTimeToWake为20ms,肯定是20ms的先来到,所以将下一个阻塞超时时间更新为20ms。
if( xTimeToWake < xNextTaskUnblockTime )
{
xNextTaskUnblockTime = xTimeToWake;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
}
2.3滴答定时器中断服务函数xPortSysTickHandler()
-解析
函数xTaskIncrementTick()
的值如果不等于pdFALSE,则进行任务切换,触发PendSV中断。
void xPortSysTickHandler( void )
{
vPortRaiseBASEPRI();
{
if( xTaskIncrementTick() != pdFALSE )
{
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
}
vPortClearBASEPRIFromISR();
}
在函数xTaskIncrementTick()
中判断任务是否需要被解除。首先判断任务调度器是否被挂起,如果等于pdFALSE 则没有被挂起,进入if内容。
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
traceTASK_INCREMENT_TICK( xTickCount );
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
将系统时钟节拍xTickCount加1,然后再将值赋给自己,没进来一次时钟节拍将自加1。
const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;
xTickCount = xConstTickCount;
判断xConstTickCount 是否为0,为0则值溢出,进入函数 taskSWITCH_DELAYED_LISTS()
(点击函数名可跳转至解析)。
if( xConstTickCount == ( TickType_t ) 0U )
{
taskSWITCH_DELAYED_LISTS();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
判断当前时钟节拍ConstTickCount 是否大于等于下一个阻塞超时时间。
if( xConstTickCount >= xNextTaskUnblockTime )
{
for( ; ; )
{
判断阻塞列表中是否有任务,如果没有任务则没有需要被解除的任务,则将下一个阻塞超时时间xNextTaskUnblockTime设置为最大值。
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
xNextTaskUnblockTime = portMAX_DELAY;
break;
}
else则阻塞列表中有任务,通过函数listGET_OWNER_OF_HEAD_ENTRY()
获取阻塞列表的第一个成员的任务控制块;通过函数listGET_LIST_ITEM_VALUE()
获取列表项的数值,列表项中一般存放的是阻塞时间,则xItemValue被赋值阻塞时间。
else
{
pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
判断系统时间节拍的数值是否小于阻塞时间,代表此时发生异常。因为在首先进入if时判断了当前的系统时钟节拍比下一个阻塞超时时间大。此时将列表项的值赋值给下一个阻塞超时时间,退出。
if( xConstTickCount < xItemValue )
{
xNextTaskUnblockTime = xItemValue;
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
下面的情况为正常执行的情况。使用函数listREMOVE_ITEM()
将任务从阻塞列表中移除,同时也从使用函数listREMOVE_ITEM
从事件列表中移除。
listREMOVE_ITEM( &( pxTCB->xStateListItem ) );
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
listREMOVE_ITEM( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
使用函数prvAddTaskToReadyList()
将任务添加到就绪列表中,
prvAddTaskToReadyList( pxTCB );
判断宏configUSE_PREEMPTION是否使能抢占式任务调度,是则判断恢复的任务的任务优先级是否比当前正在执行的任务优先级高,是则将任务切换xSwitchRequired变量赋值pdTRUE。
#if ( configUSE_PREEMPTION == 1 )
{
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
}
}
}
以下程序是时间片调度:
#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
{
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
#if ( configUSE_TICK_HOOK == 1 )
{
if( xPendedTicks == ( TickType_t ) 0 )
{
vApplicationTickHook();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
#if ( configUSE_PREEMPTION == 1 )
{
if( xYieldPending != pdFALSE )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
}
else
{
++xPendedTicks;
#if ( configUSE_TICK_HOOK == 1 )
{
vApplicationTickHook();
}
#endif
}
return xSwitchRequired;
}
2.4函数taskSWITCH_DELAYED_LISTS()
-解析
只溢出之后,将就绪列表pxDelayedTaskList和溢出就绪列表pxOverflowDelayedTaskList进行互换。
#define taskSWITCH_DELAYED_LISTS() \
{ \
List_t * pxTemp; \
\
\
configASSERT( ( listLIST_IS_EMPTY( pxDelayedTaskList ) ) ); \
\
pxTemp = pxDelayedTaskList; \
pxDelayedTaskList = pxOverflowDelayedTaskList; \
pxOverflowDelayedTaskList = pxTemp; \
xNumOfOverflows++; \
prvResetNextTaskUnblockTime(); \
}
3.延时函数-实验
1、实验目的:学习 FreeRTOS 相对延时和绝对延时API 函数的使用,并了解其区别
2、实验设计:将设计三个任务:start_task、task1,task2 三个任务的功能如下:
start_task:用来创建task1和task2任务 task1用于展示相对延时函数vTaskDelay ( )的使用;
task1:用于展示相对延时函数vTaskDelay ( )的使用;
task2:用于展示绝对延时函数vTaskDelayUntil( )的使用 。
为了直观显示两个延时函数的区别,将使用LED0(PB1) 和LED1(PB0) 的翻转波形来表示
1.首先删除无关的程序内容,task1和task2程序如下,其他程序保持不变。
void task1( void * pvParameters )
{
while(1)
{
LED0=~LED0;
vTaskDelay(500);
}
}
void task2( void * pvParameters )
{
while(1)
{
vTaskDelay(10);
}
}
2.task1中的本身就是使用的相对于延时,相对于调用的时候起,到延时时间结束,所以不用改变;task2在使用绝对延时函数vTaskDelayUntil()
时,可以查看FreeRTOS官网对该函数的使用介绍,根据示例来编写函数使用,绝对延时是整个task2运行的时间。
由于LED翻转语句执行较快基本看不到差距,所以这里加了死延时。
void task1( void * pvParameters )
{
while(1)
{
LED0=~LED0;
delay_ms(20);
vTaskDelay(500);
}
}
void task2( void * pvParameters )
{
TickType_t xLastWakeTime;
xLastWakeTime = xTaskGetTickCount();
while(1)
{
LED1=~LED1;
delay_ms(20);
vTaskDelayUntil(&xLastWakeTime,500);
}
}
(这里由于手头没有示波器,我并没有做出实验结果,理论结果如下。)
理论实验结果:
由于task1和task2都加了死延时20ms,LED0翻转周期为520ms左右,而LED1的翻转周期仍为500ms。由于task2优先级比task1要高,task2会抢占task1,所以在执行时,会出现task1处于阻塞延时时结束时,task2处于死延时,此时并不能进行任务切换,task1会等到task2死延时结束进入阻塞延时再运行,LED0的亮灭时间会有所变化,LED0亮灭时间会变长,LED1的亮灭周期为500ms保持不变。将task1的优先级变高,将会影响task2的绝对延时时间。
4.总结
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)