创建完毕任务,启动调度器,任务控制,系统 SysTick 来临后判断是否需上下文切换;
如果没有其他任务执行的情况下,FreeRTOS 的 Idle 任务将被调度投入运行;
在启动调度器的时候,Idle 任务就被创建了,优先级为最低 0;
-
void vTaskStartScheduler( void )
-
{
-
.....................
-
xReturn =
xTaskCreate( prvIdleTask,
-
configIDLE_TASK_NAME,
-
configMINIMAL_STACK_SIZE,
-
(
void * )
NULL,
-
portPRIVILEGE_BIT,
-
&xIdleTaskHandle );
-
.....................
-
}
当某时刻所有优先级高于 Idle 任务的任务处于被阻塞或者部分被挂起的状态,此刻调度器会调度 Idle 任务运行,它的执行函数为:
-
/*
-
* -----------------------------------------------------------
-
* The Idle task.
-
* ----------------------------------------------------------
-
*
-
* The portTASK_FUNCTION() macro is used to allow port/compiler specific
-
* language extensions. The equivalent prototype for this function is:
-
*
-
* void prvIdleTask( void *pvParameters );
-
*
-
*/
-
static portTASK_FUNCTION( prvIdleTask, pvParameters )
-
{
-
/* Stop warnings. */
-
(
void ) pvParameters;
-
-
/** THIS IS THE RTOS IDLE TASK - WHICH IS CREATED AUTOMATICALLY WHEN THE
-
SCHEDULER IS STARTED. **/
-
-
/* In case a task that has a secure context deletes itself, in which case
-
the idle task is responsible for deleting the task's secure context, if
-
any. */
-
portALLOCATE_SECURE_CONTEXT( configMINIMAL_SECURE_STACK_SIZE );
-
-
for( ;; )
-
{
-
/* See if any tasks have deleted themselves - if so then the idle task
-
is responsible for freeing the deleted task's TCB and stack. */
-
prvCheckTasksWaitingTermination();
-
-
#if ( configUSE_PREEMPTION == 0 )
-
{
-
/* If we are not using preemption we keep forcing a task switch to
-
see if any other task has become available. If we are using
-
preemption we don't need to do this as any task becoming available
-
will automatically get the processor anyway. */
-
taskYIELD();
-
}
-
#endif /* configUSE_PREEMPTION */
-
-
#if ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) )
-
{
-
/* When using preemption tasks of equal priority will be
-
timesliced. If a task that is sharing the idle priority is ready
-
to run then the idle task should yield before the end of the
-
timeslice.
-
-
A critical region is not required here as we are just reading from
-
the list, and an occasional incorrect value will not matter. If
-
the ready list at the idle priority contains more than one task
-
then a task other than the idle task is ready to execute. */
-
if(
listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > ( UBaseType_t )
1 )
-
{
-
taskYIELD();
-
}
-
else
-
{
-
mtCOVERAGE_TEST_MARKER();
-
}
-
}
-
#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) */
-
-
#if ( configUSE_IDLE_HOOK == 1 )
-
{
-
extern void vApplicationIdleHook( void );
-
-
/* Call the user defined function from within the idle task. This
-
allows the application designer to add background functionality
-
without the overhead of a separate task.
-
NOTE: vApplicationIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES,
-
CALL A FUNCTION THAT MIGHT BLOCK. */
-
vApplicationIdleHook();
-
}
-
#endif /* configUSE_IDLE_HOOK */
-
-
/* This conditional compilation should use inequality to 0, not equality
-
to 1. This is to ensure portSUPPRESS_TICKS_AND_SLEEP() is called when
-
user defined low power mode implementations require
-
configUSE_TICKLESS_IDLE to be set to a value other than 1. */
-
#if ( configUSE_TICKLESS_IDLE != 0 )
-
{
-
TickType_t xExpectedIdleTime;
-
-
/* It is not desirable to suspend then resume the scheduler on
-
each iteration of the idle task. Therefore, a preliminary
-
test of the expected idle time is performed without the
-
scheduler suspended. The result here is not necessarily
-
valid. */
-
xExpectedIdleTime =
prvGetExpectedIdleTime();
-
-
if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
-
{
-
vTaskSuspendAll();
-
{
-
/* Now the scheduler is suspended, the expected idle
-
time can be sampled again, and this time its value can
-
be used. */
-
configASSERT( xNextTaskUnblockTime >= xTickCount );
-
xExpectedIdleTime =
prvGetExpectedIdleTime();
-
-
/* Define the following macro to set xExpectedIdleTime to 0
-
if the application does not want
-
portSUPPRESS_TICKS_AND_SLEEP() to be called. */
-
configPRE_SUPPRESS_TICKS_AND_SLEEP_PROCESSING( xExpectedIdleTime );
-
-
if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
-
{
-
traceLOW_POWER_IDLE_BEGIN();
-
portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
-
traceLOW_POWER_IDLE_END();
-
}
-
else
-
{
-
mtCOVERAGE_TEST_MARKER();
-
}
-
}
-
(
void )
xTaskResumeAll();
-
}
-
else
-
{
-
mtCOVERAGE_TEST_MARKER();
-
}
-
}
-
#endif /* configUSE_TICKLESS_IDLE */
-
}
-
}
Idle 任务也是一个无限循环:
1、调用 prvCheckTasksWaitingTermination() 判断是否有需要 Task 自己删除自己,如果有,那么在 Idle 任务中来回收这种类型的场景:
-
static void prvCheckTasksWaitingTermination( void )
-
{
-
-
/** THIS FUNCTION IS CALLED FROM THE RTOS IDLE TASK **/
-
-
#if ( INCLUDE_vTaskDelete == 1 )
-
{
-
TCB_t *pxTCB;
-
-
/* uxDeletedTasksWaitingCleanUp is used to prevent taskENTER_CRITICAL()
-
being called too often in the idle task. */
-
while( uxDeletedTasksWaitingCleanUp > ( UBaseType_t )
0U )
-
{
-
taskENTER_CRITICAL();
-
{
-
pxTCB =
listGET_OWNER_OF_HEAD_ENTRY( ( &xTasksWaitingTermination ) );
/*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
-
(
void )
uxListRemove( &( pxTCB->xStateListItem ) );
-
--uxCurrentNumberOfTasks;
-
--uxDeletedTasksWaitingCleanUp;
-
}
-
taskEXIT_CRITICAL();
-
-
prvDeleteTCB( pxTCB );
-
}
-
}
-
#endif /* INCLUDE_vTaskDelete */
-
}
如果支持任务删除,而且有需要被删除的任务的话,进入临界区,取出要被删除的任务,更新当前任务个数和待删除任务个数,退出临界区,并调用 prvDeleteTCB() 接口来删除任务的资源,其实就是调用了 vPortFree( pxTCB->pxStack ); 和 vPortFree( pxTCB ); 来释放任务的 TCB 结构和堆栈;
2、如果定义了 configUSE_PREEMPTION 为 1(支持抢占),同时 configIDLE_SHOULD_YIELD 也为 1 (如果有与 Idle 任务相同优先级的任务,并且处于 Ready 状态,那么 Idle 任务将为其让路)的情况,Idle 任务直接调用 taskYIELD(); 引发一次调度,放弃 CPU;
3、如果使能了 configUSE_IDLE_HOOK,也就是用户的 Idle 钩子函数,则调用 vApplicationIdleHook;
4、如果使能了 configUSE_TICKLESS_IDLE,就意味着要进入低功耗场景,当然,既然都调用 Idle 任务了,进入低功耗理所应当;这里的 Tickless 的含义是:进入低功耗后,Systick 不在来中断,因为 Tick 心跳很频繁的话,处理器很快就被唤醒了,失去了低功耗的意义;
5、调用 prvGetExpectedIdleTime 获取距离下一个最近的阻塞任务的执行时间,与 当前的时间做减法,得到最大的可以进入低功耗的时间,当然这里只能判断阻塞在时间上的任务,对于事件,我们并不知道什么时候会来,也许是中断激活事件,不过这样要求中断能够唤醒处理器,否则中断无法得到及时处理,那么 RTOS 的实时任务的运行也得不到实时的保证;
6、如果获取得到的最大进入低功耗的时间 xExpectedIdleTime 大于了我们配置的期望睡眠的最小时间,也就是满足进入低功耗的条件,那么挂起调度器(因为马上要进入 Tickless),调用 portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ); 进入低功耗;
7、portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ) 的实现是和处理器相关,因为是带 port 前缀,每种处理器进入低功耗的方式不尽相同,即便是同一种处理器,进入低功耗也有几种模式,所以这里交给处理器相关 port.c 去实现;
8、唤醒后,一般的,原地继续执行,调用 xTaskResumeAll 恢复调度器;
针对 Cortex-M3 进入睡眠部分:
-
#define portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ) \
-
vPortSuppressTicksAndSleep( xExpectedIdleTime )
调用到了 vPortSuppressTicksAndSleep(xExpectedIdleTime)入参是期待睡眠的时间;也就是 Tick 个数:
-
#if( configUSE_TICKLESS_IDLE == 1 )
-
-
__weak void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
-
{
-
uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements;
-
TickType_t xModifiableIdleTime;
-
-
/* Make sure the SysTick reload value does not overflow the counter. */
-
/* 睡眠的最大值不能够超过处理器支持的最大值 */
-
if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks )
-
{
-
xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
-
}
-
-
/* Stop the SysTick momentarily. The time the SysTick is stopped for
-
is accounted for as best it can be, but using the tickless mode will
-
inevitably result in some tiny drift of the time maintained by the
-
kernel with respect to calendar time. */
-
/* 禁止 SysTick */
-
portNVIC_SYSTICK_CTRL_REG &= ~portNVIC_SYSTICK_ENABLE_BIT;
-
-
/* Calculate the reload value required to wait xExpectedIdleTime
-
tick periods. -1 is used because this code will execute part way
-
through one of the tick periods. */
-
/* 计算将要配置到 SYSTICK 用于唤醒的值 */
-
ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG + ( ulTimerCountsForOneTick * ( xExpectedIdleTime -
1UL ) );
-
if( ulReloadValue > ulStoppedTimerCompensation )
-
{
-
ulReloadValue -= ulStoppedTimerCompensation;
-
}
-
-
/* Enter a critical section but don't use the taskENTER_CRITICAL()
-
method as that will mask interrupts that should exit sleep mode. */
-
/* 关闭中断 */
-
__disable_irq();
-
__dsb( portSY_FULL_READ_WRITE );
-
__isb( portSY_FULL_READ_WRITE );
-
-
/* If a context switch is pending or a task is waiting for the scheduler
-
to be unsuspended then abandon the low power entry. */
-
/* 再次 Check 有没有非 Idle 状态待执行的任务 */
-
if(
eTaskConfirmSleepModeStatus() == eAbortSleep )
-
{
-
/* Restart from whatever is left in the count register to complete
-
this tick period. */
-
/* 重新配置 SysTICK 终止睡眠流程 */
-
portNVIC_SYSTICK_LOAD_REG = portNVIC_SYSTICK_CURRENT_VALUE_REG;
-
-
/* Restart SysTick. */
-
portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
-
-
/* Reset the reload register to the value required for normal tick
-
periods. */
-
portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick -
1UL;
-
-
/* Re-enable interrupts - see comments above __disable_irq() call
-
above. */
-
/* 打开中断 */
-
__enable_irq();
-
}
-
else
-
{
-
/* Set the new reload value. */
-
/* 将 SysTick 的时间配置为之前计算好的时间 */
-
portNVIC_SYSTICK_LOAD_REG = ulReloadValue;
-
-
/* Clear the SysTick count flag and set the count value back to
-
zero. */
-
/* 清除当前 SysTick 的值 */
-
portNVIC_SYSTICK_CURRENT_VALUE_REG =
0UL;
-
-
/* Restart SysTick. */
-
/* 开启 SysTick */
-
portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
-
-
/* Sleep until something happens. configPRE_SLEEP_PROCESSING() can
-
set its parameter to 0 to indicate that its implementation contains
-
its own wait for interrupt or wait for event instruction, and so wfi
-
should not be executed again. However, the original expected idle
-
time variable must remain unmodified, so a copy is taken. */
-
xModifiableIdleTime = xExpectedIdleTime;
-
configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
-
/* 执行 WFI 睡眠 */
-
if( xModifiableIdleTime >
0 )
-
{
-
__dsb( portSY_FULL_READ_WRITE );
-
__wfi();
-
__isb( portSY_FULL_READ_WRITE );
-
}
-
/* 此处为唤醒 */
-
configPOST_SLEEP_PROCESSING( xExpectedIdleTime );
-
-
/* Re-enable interrupts to allow the interrupt that brought the MCU
-
out of sleep mode to execute immediately. see comments above
-
__disable_interrupt() call above. */
-
/* 因为可能是其他中断唤醒的 WFI,立马开启中断,进入 ISR */
-
__enable_irq();
-
__dsb( portSY_FULL_READ_WRITE );
-
__isb( portSY_FULL_READ_WRITE );
-
-
/* Disable interrupts again because the clock is about to be stopped
-
and interrupts that execute while the clock is stopped will increase
-
any slippage between the time maintained by the RTOS and calendar
-
time. */
-
/* 关闭中断,做处理 */
-
__disable_irq();
-
__dsb( portSY_FULL_READ_WRITE );
-
__isb( portSY_FULL_READ_WRITE );
-
-
/* Disable the SysTick clock without reading the
-
portNVIC_SYSTICK_CTRL_REG register to ensure the
-
portNVIC_SYSTICK_COUNT_FLAG_BIT is not cleared if it is set. Again,
-
the time the SysTick is stopped for is accounted for as best it can
-
be, but using the tickless mode will inevitably result in some tiny
-
drift of the time maintained by the kernel with respect to calendar
-
time*/
-
/* 重新配置 SysTick */
-
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT );
-
-
/* Determine if the SysTick clock has already counted to zero and
-
been set back to the current reload value (the reload back being
-
correct for the entire expected idle time) or if the SysTick is yet
-
to count to zero (in which case an interrupt other than the SysTick
-
must have brought the system out of sleep mode). */
-
if( ( portNVIC_SYSTICK_CTRL_REG & portNVIC_SYSTICK_COUNT_FLAG_BIT ) !=
0 )
-
{
-
uint32_t ulCalculatedLoadValue;
-
-
/* The tick interrupt is already pending, and the SysTick count
-
reloaded with ulReloadValue. Reset the
-
portNVIC_SYSTICK_LOAD_REG with whatever remains of this tick
-
period. */
-
ulCalculatedLoadValue = ( ulTimerCountsForOneTick -
1UL ) - ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG );
-
-
/* Don't allow a tiny value, or values that have somehow
-
underflowed because the post sleep hook did something
-
that took too long. */
-
if( ( ulCalculatedLoadValue < ulStoppedTimerCompensation ) || ( ulCalculatedLoadValue > ulTimerCountsForOneTick ) )
-
{
-
ulCalculatedLoadValue = ( ulTimerCountsForOneTick -
1UL );
-
}
-
-
portNVIC_SYSTICK_LOAD_REG = ulCalculatedLoadValue;
-
-
/* As the pending tick will be processed as soon as this
-
function exits, the tick value maintained by the tick is stepped
-
forward by one less than the time spent waiting. */
-
ulCompleteTickPeriods = xExpectedIdleTime -
1UL;
-
}
-
else
-
{
-
/* Something other than the tick interrupt ended the sleep.
-
Work out how long the sleep lasted rounded to complete tick
-
periods (not the ulReload value which accounted for part
-
ticks). */
-
ulCompletedSysTickDecrements = ( xExpectedIdleTime * ulTimerCountsForOneTick ) - portNVIC_SYSTICK_CURRENT_VALUE_REG;
-
-
/* How many complete tick periods passed while the processor
-
was waiting? */
-
ulCompleteTickPeriods = ulCompletedSysTickDecrements / ulTimerCountsForOneTick;
-
-
/* The reload value is set to whatever fraction of a single tick
-
period remains. */
-
portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods +
1UL ) * ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;
-
}
-
-
/* Restart SysTick so it runs from portNVIC_SYSTICK_LOAD_REG
-
again, then set portNVIC_SYSTICK_LOAD_REG back to its standard
-
value. */
-
portNVIC_SYSTICK_CURRENT_VALUE_REG =
0UL;
-
portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
-
vTaskStepTick( ulCompleteTickPeriods );
-
portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick -
1UL;
-
-
/* Exit with interrpts enabled. */
-
__enable_irq();
-
}
-
}
-
-
#endif /* #if configUSE_TICKLESS_IDLE */
内容不少,慢慢看即可:
0、入参是期望睡眠的 Tick 数目,这里不用这么抽象,比如配置的 Tick 周期为 1ms,那么这里就是 ms 数;
这里有 3 个全局变量需要说明一下:
-
/*
-
* The number of SysTick increments that make up one tick period.
-
*/
-
#if( configUSE_TICKLESS_IDLE == 1 )
-
static
uint32_t ulTimerCountsForOneTick =
0;
-
#endif /* configUSE_TICKLESS_IDLE */
-
-
/*
-
* The maximum number of tick periods that can be suppressed is limited by the
-
* 24 bit resolution of the SysTick timer.
-
*/
-
#if( configUSE_TICKLESS_IDLE == 1 )
-
static
uint32_t xMaximumPossibleSuppressedTicks =
0;
-
#endif /* configUSE_TICKLESS_IDLE */
-
-
/*
-
* Compensate for the CPU cycles that pass while the SysTick is stopped (low
-
* power functionality only.
-
*/
-
#if( configUSE_TICKLESS_IDLE == 1 )
-
static
uint32_t ulStoppedTimerCompensation =
0;
-
#endif /* configUSE_TICKLESS_IDLE */
在开启调度器,配置 SysTick 的时候,这 3 个全局变量被赋值初始化:
-
#if( configUSE_TICKLESS_IDLE == 1 )
-
{
-
ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
-
xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;
-
ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ );
-
}
-
#endif /* configUSE_TICKLESS_IDLE */
ulTimerCountsForOneTick :代表了一个 SysTick 配置到寄存器的 Tick 的 Count;换句话来说,就是产生 1ms 的 SysTick 中断,需要配置给寄存器的值;
xMaximumPossibleSuppressedTicks:代表了在溢出之前,硬件最大支持多少个 Tick;(因为 Cortex-M3 处理器配置给硬件的 Tick Count 最大是 24 bit 的,所以这里用 24bit 的全 1 除以 ulTimerCountsForOneTick );
ulStoppedTimerCompensation:代表了一个时钟补偿的因子,这里是固定的 45;
1、首先判断期望睡眠的值是否大于了处理器的 xMaximumPossibleSuppressedTicks,如果是,那么将睡眠的值限定在 xMaximumPossibleSuppressedTicks;
2、禁止 SysTick 模块;
3、计算新的 SysTick 的 Load 值,这里的原理是,因为需要让处理器进入 WFI (Waiting For Interrupt),进入 WFI 后,处理器可以被中断唤醒,并继续执行;其实这里的 Tickless 并不是真的一直关闭了 SysTick ,而是将睡眠的时间配置到了 SysTick 中,所以这里才会限制睡眠时间;
-
ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG + \
-
( ulTimerCountsForOneTick * ( xExpectedIdleTime -
1UL ) );
-
-
if( ulReloadValue > ulStoppedTimerCompensation )
-
{
-
ulReloadValue -= ulStoppedTimerCompensation;
-
}
使用当前的 SysTick 的值,加上睡眠的时间乘以每个 Tick 的 Count,计算出即将配置到 SysTick 硬件寄存器的值;然后对补偿因子做减法;
4、__disable_irq,关闭中断,刷指令和数据流水线;
5、判断是否还有需要被执行的任务,如果有,那么重新配置 SysTick 还是为 1ms,并使能 SysTick,开启中断,退出睡眠逻辑;
6、如果没有要被执行的任务,将计算出来最大的睡眠时间 ulReloadValue 配置进 SysTick 计数器寄存器,开启 SysTick,此刻的 SysTick 便是睡眠的时间;
7、进入 WFI 睡眠;
8、如果有中断,则对 WFI 原地唤醒,继续执行,这里可能是 SysTick 的 IRQ 唤醒,也可能是其他中断唤醒;
9、__enable_irq,开启中断,因为可能是被其他 IRQ 唤醒,这里需要立马执行 ISR;
10、__disable_irq,关闭中断,刷指令和数据流水线,因为下面的配置不允许被打断;
11、重新配置 SysTick 成为 OS 的心跳(也就是 1ms),并使能 SysTick;