主机环境:Windows
开发环境:MDK4.7.2
FreeRTOS版本:FreeRTOS8.1.2
目标环境:STM32F030C8T6
FreeRTOS中关于时间的管理分为两部分:一部分是任务的延时管理;前面叙述过一些,还有一部分就是SysTick中断,管理任务的延时时间。SysTick是由STM32内核提供的,时钟源可选,用于产生FreeRTOS所需要的系统时钟,且是由用户可配的,用户在FreeRTOSConfig.h文件中配置configCPU_CLOCK_HZ以及configTICK_RATE_HZ两个宏来设置系统时钟,产生时间片时间,系统每隔固定时间进入SysTick中断处理时间。
有关时钟配置的初始化函数在port.c文件中,如下
void prvSetupTimerInterrupt( void )
{
/* Configure SysTick to interrupt at the requested rate. */
*(portNVIC_SYSTICK_LOAD) = ( configCPU_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
*(portNVIC_SYSTICK_CTRL) = portNVIC_SYSTICK_CLK | portNVIC_SYSTICK_INT | portNVIC_SYSTICK_ENABLE;
}
配置的是portNVIC_SYSTICK_LOAD和portNVIC_SYSTICK_CTRL两个寄存器,有关SYSTICK寄存器的说明可以在armv6-m体系结构参考手册中查看
LOAD寄存器是装载的计数值由用户配置,CTRL寄存器配置SYSTICK计数器的时钟源以及是否产生中断,且是否使能该计数器,该函数在调度器启动时调用,即在xPortStartScheduler()函数中调用,启动该计数器,这样在计数值为0时进入SysTick中断函数,即xPortSysTickHandler()中断函数
void xPortSysTickHandler( void )
{
uint32_t ulPreviousMask;
ulPreviousMask = portSET_INTERRUPT_MASK_FROM_ISR();
{
/* Increment the RTOS tick. */
if( xTaskIncrementTick() != pdFALSE )
{
/* Pend a context switch. */
*(portNVIC_INT_CTRL) = portNVIC_PENDSVSET;
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( ulPreviousMask );
}
在该中断函数中首先记录中断屏蔽位,并关闭中断,在处理完后恢复中断屏蔽位并打开中断,如果在处理完后需要产生任务切换,则进行一次任务切换调用,
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
/* Called by the portable layer each time a tick interrupt occurs.
Increments the tick then checks to see if the new tick value will cause any
tasks to be unblocked. */
traceTASK_INCREMENT_TICK( xTickCount );
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
/* Increment the RTOS tick, switching the delayed and overflowed
delayed lists if it wraps to 0. */
++xTickCount;
{
/* Minor optimisation. The tick count cannot change in this
block. */
const TickType_t xConstTickCount = xTickCount;
if( xConstTickCount == ( TickType_t ) 0U )
{
taskSWITCH_DELAYED_LISTS();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* See if this tick has made a timeout expire. Tasks are stored in
the queue in the order of their wake time - meaning once one task
has been found whose block time has not expired there is no need to
look any further down the list. */
if( xConstTickCount >= xNextTaskUnblockTime )
{
for( ;; )
{
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
/* The delayed list is empty. Set xNextTaskUnblockTime
to the maximum possible value so it is extremely
unlikely that the
if( xTickCount >= xNextTaskUnblockTime ) test will pass
next time through. */
xNextTaskUnblockTime = portMAX_DELAY;
break;
}
else
{
/* The delayed list is not empty, get the value of the
item at the head of the delayed list. This is the time
at which the task at the head of the delayed list must
be removed from the Blocked state. */
pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xGenericListItem ) );
if( xConstTickCount < xItemValue )
{
/* It is not time to unblock this item yet, but the
item value is the time at which the task at the head
of the blocked list must be removed from the Blocked
state - so record the item value in
xNextTaskUnblockTime. */
xNextTaskUnblockTime = xItemValue;
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* It is time to remove the item from the Blocked state. */
( void ) uxListRemove( &( pxTCB->xGenericListItem ) );
/* Is the task waiting on an event also? If so remove
it from the event list. */
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* Place the unblocked task into the appropriate ready
list. */
prvAddTaskToReadyList( pxTCB );
/* A task being unblocked cannot cause an immediate
context switch if preemption is turned off. */
#if ( configUSE_PREEMPTION == 1 )
{
/* Preemption is on, but a context switch should
only be performed if the unblocked task has a
priority that is equal to or higher than the
currently executing task. */
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
}
}
}
}
/* Tasks of equal priority to the currently running task will share
processing time (time slice) if preemption is on, and the application
writer has not explicitly turned time slicing off. */
#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 /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */
#if ( configUSE_TICK_HOOK == 1 )
{
/* Guard against the tick hook being called when the pended tick
count is being unwound (when the scheduler is being unlocked). */
if( uxPendedTicks == ( UBaseType_t ) 0U )
{
vApplicationTickHook();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TICK_HOOK */
}
else
{
++uxPendedTicks;
/* The tick hook gets called at regular intervals, even if the
scheduler is locked. */
#if ( configUSE_TICK_HOOK == 1 )
{
vApplicationTickHook();
}
#endif
}
#if ( configUSE_PREEMPTION == 1 )
{
if( xYieldPending != pdFALSE )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
return xSwitchRequired;
}
xTaskIncrementTick()函数看名字就晓得它的主要工作就是对时钟计数器进行更新工作即对xTickCount进行更新,延时任务链表也是在该函数中进行管理的,进入该函数中首先判断调度器是否处于挂起状态,当调度器没有挂起正常运行时对xTickCount进行加1操作,这里又申请了一个xTickCount的副本xConstTickCount记录当前xTickCount的值,感觉xTickCount的值在这里不会更改那。。。根据xConstTickCount的值判断其是否溢出,如果溢出的话需要交换两个延时任务链表,计数器溢出表明通用延时任务链表中的任务已经处理完毕,剩下的都是处理溢出延时任务链表,通过调用taskSWITCH_DELAYED_LISTS()实现
#define taskSWITCH_DELAYED_LISTS() \
{ \
List_t *pxTemp; \
\
/* The delayed tasks list should be empty when the lists are switched. */ \
configASSERT( ( listLIST_IS_EMPTY( pxDelayedTaskList ) ) ); \
\
pxTemp = pxDelayedTaskList; \
pxDelayedTaskList = pxOverflowDelayedTaskList; \
pxOverflowDelayedTaskList = pxTemp; \
xNumOfOverflows++; \
prvResetNextTaskUnblockTime(); \
}
将两个延时任务链表指针pxDelayedTaskList和pxOverflowDelayedTaskList进行交换,更新溢出次数,同时更新下一任务唤醒时间
static void prvResetNextTaskUnblockTime( void )
{
TCB_t *pxTCB;
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
/* The new current delayed list is empty. Set
xNextTaskUnblockTime to the maximum possible value so it is
extremely unlikely that the
if( xTickCount >= xNextTaskUnblockTime ) test will pass until
there is an item in the delayed list. */
xNextTaskUnblockTime = portMAX_DELAY;
}
else
{
/* The new current delayed list is not empty, get the value of
the item at the head of the delayed list. This is the time at
which the task at the head of the delayed list should be removed
from the Blocked state. */
( pxTCB ) = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
xNextTaskUnblockTime = listGET_LIST_ITEM_VALUE( &( ( pxTCB )->xGenericListItem ) );
}
}
回到中断处理函数中,如果计数器没得溢出,在正常计数的话,跟将其与下一任务唤醒时间进行比较,查看是否有任务需要被唤醒,当计数器的值大于等于下一任务唤醒时间时表明需要有任务被唤醒,当延时任务链表不为空时遍历该链表(在任务中我们可能并不需要任务阻塞即延时任务链表为空,但时钟计数器一直在跑,所以需要将下一任务唤醒时间设置为最大值,一旦有了延时任务则对其进行更新),将延时任务链表中延时时间小于等于xConstTickCount的任务都从延时任务链表中移除,如果任务也处于事件链表中,也同样溢出,添加到就绪链表中,在所有需要被唤醒的任务添加完毕后,根据下一个延时任务的延时时间更新下一任务唤醒时间,xNextTaskUnblockTime的值始终为下一个被唤醒的任务的唤醒时间。如果系统允许抢占式调用的话,还要判断唤醒任务的优先级是否需要产生任务切换。由于FreeRTOS在相同优先级下是支持时间片轮询调度的,因此如果有多个就绪任务的优先级是相同的且是最高的,则在每次SysTick中断完毕后执行一次任务切换,即标记xSwitchRequired为pdTRUE。关于相同优先级具体是怎么调度的,现在还不清楚,以后看看再说吧,如果我们定义了configUSE_TICK_HOOK=1,即使用时钟钩子函数,在uxPendedTicks为0时会进入vApplicationTickHook()函数,该函数由用户自己编写,uxPendedTicks的用处在下面一段会体现,到此调度器正常运行时的SysTick中断处理就梳理完毕。当调度器挂起时,会进入另外一段不是对xTickCount进行加1操作了,而是对uxPendedTicks进行加1操作,即调度器挂起时我们是没有对延时任务链表进行处理的,而是记录挂起的时钟数,调度器挂起时时钟钩子函数正常运行,最后函数返回是否需要任务切换。uxPendedTicks就跟我们之前的任务延时相关了,查看之前的想TaskResumeAll()函数,有一段代码
/* If any ticks occurred while the scheduler was suspended then
they should be processed now. This ensures the tick count does
not slip, and that any delayed tasks are resumed at the correct
time. */
if( uxPendedTicks > ( UBaseType_t ) 0U )
{
while( uxPendedTicks > ( UBaseType_t ) 0U )
{
if( xTaskIncrementTick() != pdFALSE )
{
xYieldPending = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
--uxPendedTicks;
}
}
在这里检测当uxPendedTicks不为0时,即调度器唤醒时检测在其挂起期间经历了几个时钟,挂起了几次时钟就调用几次TaskIncrementTick()函数以维持正确的时钟,保证任务在正确的时间唤醒。虽然调度器挂起时没有处理延时任务链表,但在其恢复时处理了。