FreeRTOS tickless低功耗 内部函数处理详解

低功耗模式

1. 芯片原本就支持的硬件低功耗

2. freeRTOS提供的软件低功耗,Tickless模式!

 

当用户将宏定义 configUSE_TICKLESS_IDLE 配置为 1 且系统运行满足以下两个条件时,

系统内核会自动的调用,低功耗宏定义函数 portSUPPRESS_TICKS_AND_SLEEP():
-------------------------------
## 当前空闲任务正在运行,所有其它的任务处在挂起状态或者阻塞状态。

## 根据用户配置 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 的大小,

只有当系统可运行于低功耗模式的时钟节拍数大于等于这个参数时,系统才可以进入到低功耗模式。

-------------------------------

复制代码

#ifndef configEXPECTED_IDLE_TIME_BEFORE_SLEEP
    #define configEXPECTED_IDLE_TIME_BEFORE_SLEEP 2
#endif
#if configEXPECTED_IDLE_TIME_BEFORE_SLEEP < 2
    #error configEXPECTED_IDLE_TIME_BEFORE_SLEEP must not be less than 2
#endif
默认定义的大小是 2 个系统时钟节拍,且用户自定义的话,必须大于 2 个系统时钟节拍。 

复制代码

 

 

函数 portSUPPRESS_TICKS_AND_SLEEP 是 FreeRTOS 实现 tickles 模式的关键,此函数被空闲任务调用,

其定义是在 portmacro.h 文件中:

/* Tickless idle/low power functionality. */
#ifndef portSUPPRESS_TICKS_AND_SLEEP
    extern void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime );
    #define portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ) vPortSuppressTicksAndSleep( xExpectedIdleTime )
#endif

 

 

其中函数 vPortSuppressTicksAndSleep 是实际的低功耗执行代码,在 port.c 文件中定义,

参数xExpectedIdleTime 就是系统可以处于低功耗模式的系统时钟节拍数。

复制代码

  1 #if configUSE_TICKLESS_IDLE == 1
  2 
  3     __weak void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
  4     {
  5     uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements, ulSysTickCTRL;
  6     TickType_t xModifiableIdleTime;
  7 
  8         /* Make sure the SysTick reload value does not overflow the counter. */
            确保滴答定时器的reload值不会溢出,也就是不能超过滴答定时器最大计数值。
  9         if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks )  【1】见后
 10         {
 11             xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
 12         }
 13 
 14         /* Stop the SysTick momentarily.  The time the SysTick is stopped for
 15         is accounted for as best it can be, but using the tickless mode will
 16         inevitably result in some tiny drift of the time maintained by the
 17         kernel with respect to calendar time. */
            停止滴答定时器
 18         portNVIC_SYSTICK_CTRL_REG &= ~portNVIC_SYSTICK_ENABLE_BIT;
 19 
 20         /* Calculate the reload value required to wait xExpectedIdleTime
 21         tick periods.  -1 is used because this code will execute part way
 22         through one of the tick periods. */
 23         根据参数xExpectIdleTime来计算滴答定时器的重载值,进入低功耗之后,计时由滴答定时器计算。
            ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG(寄存器) 
                                + ( ulTimerCountsForOneTick(一个节拍多少个时钟) * ( xExpectedIdleTime - 1UL ) );

 24         if( ulReloadValue > ulStoppedTimerCompensation )   【2】补偿时间,见后
 25         {
 26             ulReloadValue -= ulStoppedTimerCompensation;
 27         }
 28 
 29         /* Enter a critical section but don't use the taskENTER_CRITICAL()
 30         method as that will mask interrupts that should exit sleep mode. */
 31         __disable_irq();  【3】设置PRIMASK关闭中断
 32         __dsb( portSY_FULL_READ_WRITE );
 33         __isb( portSY_FULL_READ_WRITE );
 34 
 35         /* If a context switch is pending or a task is waiting for the scheduler
 36         to be unsuspended then abandon the low power entry. */
            确认是否可以进入低功耗模式
 37         if( eTaskConfirmSleepModeStatus() == eAbortSleep )  【4】函数见后
 38         {
 39             /* Restart from whatever is left in the count register to complete
 40             this tick period. */
                不能进入低功耗模式,重启滴答定时器,恢复滴答运行
 41             portNVIC_SYSTICK_LOAD_REG = portNVIC_SYSTICK_CURRENT_VALUE_REG;
 42 
 43             /* Restart SysTick. */
 44             portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
 45 
 46             /* Reset the reload register to the value required for normal tick
 47             periods. */
 48             portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
 49 
 50             /* Re-enable interrupts - see comments above __disable_irq() call
 51             above. */
 52             __enable_irq();  恢复中断设置
 53         }
 54         else
 55         {
                可以进入低功耗模式,设置滴答定时器
 56             /* Set the new reload value. */
 57             portNVIC_SYSTICK_LOAD_REG = ulReloadValue;    刚刚在【2】处算的时间值,赋给滴答定时器
 58 
 59             /* Clear the SysTick count flag and set the count value back to
 60             zero. */
 61             portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
 62 
 63             /* Restart SysTick. */
 64             portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
 65 
 66             /* Sleep until something happens.  configPRE_SLEEP_PROCESSING() can
 67             set its parameter to 0 to indicate that its implementation contains
 68             its own wait for interrupt or wait for event instruction, and so wfi
 69             should not be executed again.  However, the original expected idle
 70             time variable must remain unmodified, so a copy is taken. */
 71             xModifiableIdleTime = xExpectedIdleTime;
 72             configPRE_SLEEP_PROCESSING( xModifiableIdleTime );   【5】见后
 73             if( xModifiableIdleTime > 0 )
 74             {
 75                 __dsb( portSY_FULL_READ_WRITE );
 76                 __wfi();                          使用__WFI指令,进入睡眠模式。http://www.keil.com/support/man/docs/armcc/armcc_chr1359125004400.htm
 77                 __isb( portSY_FULL_READ_WRITE );
 78             }
                
                当代码执行到这里,说明已经退出了低功耗模式!!!
 79             configPOST_SLEEP_PROCESSING( xExpectedIdleTime );    【5】见后
 80 
 81             /* Stop SysTick.  Again, the time the SysTick is stopped for is
 82             accounted for as best it can be, but using the tickless mode will
 83             inevitably result in some tiny drift of the time maintained by the
 84             kernel with respect to calendar time. */
                停止滴答定时器
 85             ulSysTickCTRL = portNVIC_SYSTICK_CTRL_REG;       读取滴答定时器控制和状态寄存器
 86             portNVIC_SYSTICK_CTRL_REG = ( ulSysTickCTRL & ~portNVIC_SYSTICK_ENABLE_BIT );
 87 
 88             /* Re-enable interrupts - see comments above __disable_irq() call
 89             above. */
 90             __enable_irq();
 91 
                判断导致退出低功耗的是,外部中断,还是滴答定时器计时时间到了
 92             if( ( ulSysTickCTRL & portNVIC_SYSTICK_COUNT_FLAG_BIT ) != 0 )   不同的唤醒方式,对应的“系统时间补偿值”(单位是时钟节拍)是不同的。
 93             {
 94                 uint32_t ulCalculatedLoadValue;
 95 
 96                 /* The tick interrupt has already executed, and the SysTick
 97                 count reloaded with ulReloadValue.  Reset the
 98                 portNVIC_SYSTICK_LOAD_REG with whatever remains of this tick
 99                 period. */
100                 ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) - ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG );
101 
102                 /* Don't allow a tiny value, or values that have somehow
103                 underflowed because the post sleep hook did something
104                 that took too long. */
105                 if( ( ulCalculatedLoadValue < ulStoppedTimerCompensation ) || ( ulCalculatedLoadValue > ulTimerCountsForOneTick ) )
106                 {
107                     ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL );
108                 }
109 
110                 portNVIC_SYSTICK_LOAD_REG = ulCalculatedLoadValue;
111 
112                 /* The tick interrupt handler will already have pended the tick
113                 processing in the kernel.  As the pending tick will be
114                 processed as soon as this function exits, the tick value
115                 maintained by the tick is stepped forward by one less than the
116                 time spent waiting. */
117                 ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
118             }
119             else   外部中断唤醒的,需要进行时间补偿
120             {
121                 /* Something other than the tick interrupt ended the sleep.
122                 Work out how long the sleep lasted rounded to complete tick
123                 periods (not the ulReload value which accounted for part
124                 ticks). */
125                 ulCompletedSysTickDecrements = ( xExpectedIdleTime * ulTimerCountsForOneTick ) - portNVIC_SYSTICK_CURRENT_VALUE_REG;
126 
127                 /* How many complete tick periods passed while the processor
128                 was waiting? */
129                 ulCompleteTickPeriods = ulCompletedSysTickDecrements / ulTimerCountsForOneTick;
130 
131                 /* The reload value is set to whatever fraction of a single tick
132                 period remains. */
133                 portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1UL ) * ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;
134             }
135 
136             /* Restart SysTick so it runs from portNVIC_SYSTICK_LOAD_REG
137             again, then set portNVIC_SYSTICK_LOAD_REG back to its standard
138             value.  The critical section is used to ensure the tick interrupt
139             can only execute once in the case that the reload register is near
140             zero. */
                重新启动滴答定时器,重载值设置为正常值。
141             portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
142             portENTER_CRITICAL();
143             {
144                 portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
145                 vTaskStepTick( ulCompleteTickPeriods );    【6】给系统时钟节拍进行补偿,函数见后
146                 portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
147             }
148             portEXIT_CRITICAL();
149         }
150     }
151 
152 #endif /* #if configUSE_TICKLESS_IDLE */

复制代码

【1】参数 xExpectedIdleTime 表示处理器将要在低功耗模式运行的时长(单位为时钟节拍数),

这个时间会使用滴答定时器来计时,

但是滴答定时器的计数寄存器是 24 位的,因此这个时间值不能超过滴答定时器的最大计数值。

xMaximumPossibleSuppressedTicks 是个静态全局变量,此变量会在函数 vPortSetupTimerInterrupt()中被重新赋值,代码如下:

ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );    多少个时钟计时是一个节拍
xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;

经过计算 xMaximumPossibleSuppressedTicks=0xFFFF_FF/(180000000/1000)≈93,因此可以得出进入低功耗模式的最大时长为 93 个时钟节拍,

注意!这个值要根据自己所使用的平台以及FreeRTOS 的实际配置情况来计算。

 

【2】从滴答定时器停止运行,到把统计得到的低功耗模式运行时间补偿给 FreeRTOS系统时钟,也是需要时间的,这期间也是有程序在运行的。

这段程序运行的时间我们要留出来,具体的时间没法去统计,因为平台不同、编译器的代码优化水平不同导致了程序的执行时间也不同。

这里只能大概的留出一个时间值,这个时间值由变量 ulStoppedTimerCompensation 来确定,这是一个全局变量。

此变量也会在函数 vPortSetupTimerInterrupt()中被重新赋值,代码如下:

#define portMISSED_COUNTS_FACTOR ( 45UL )

ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ );

由上面的公式可以得出: ulStoppedTimerCompensation=45/(180000000/180000000)=45。

如果要修改这个时间值的话直接修改宏 portMISSED_COUNTS_FACTOR 即可。

 

【3】三个中断屏蔽寄存:

PRIMASK禁止除NMI和HardFault以外的所有异常和中断。

使用CPS(修改寄存器状态指令)

CPSIE  I    清除PRIMASK(使能中断)

CPSID  I    设置PRIMASK(禁止中断)

 

FAULTMASK 连HardFault也屏蔽了。【只有NMI】

CPSIE   F    清除指令,FAULTmask会在异常退出时自动清零。

CPSID   F

 

BASEPRI 用于屏蔽优先级 大于等于 某个阈值的中断。(M3内核优先级大则低,freRTOS大则高)

MOV    R0,       #0x60

MSR     BASEPRI,   R0

 

## 程序中写的__disable_irq()这个函数找不到定义的地方,在网上搜到人家直接是MDK的内部指令:(链接失效的话,直接上MDK官方搜__disable_irq intrinsic 即可)

http://www.keil.com/support/man/docs/armcc/armcc_chr1359124995648.htm

 

在以上代码中的含义:

在执行 WFI 前设置寄存器 PRIMASK 的话处理器可以由中断唤醒,但是不会处理这些中断,

退出低功耗模式以后,通过清除寄存器 PRIMASK 来使 ISR 得到执行,其实就是利用PRIMASK 来延迟 ISR 的执行。 

 

【4】返回eAbortSleep就不能进入低功耗模式了。

注意区别:

PendingReadyList 任务进入就绪状态,但是没有放入readylist链表。这种情况发生在调度器被停止时,有些任务进入到ready状态,这时就将任务加入到xPendingReadyList,等待调度器开始时,从新进行一次调度。 

SuspendTaskList 是任务调用"挂起任务"的API,导致被挂起的任务列表。

复制代码

 1 #if( configUSE_TICKLESS_IDLE != 0 )
 2 
 3     eSleepModeStatus eTaskConfirmSleepModeStatus( void )
 4     {
 5     /* The idle task exists in addition to the application tasks. */
 6     const UBaseType_t uxNonApplicationTasks = 1;
 7     eSleepModeStatus eReturn = eStandardSleep;
 8 
 9         if( listCURRENT_LIST_LENGTH( &xPendingReadyList ) != 0 )  是否有就绪任务
10         {
11             /* A task was made ready while the scheduler was suspended. */
12             eReturn = eAbortSleep;
13         }
14         else if( xYieldPending != pdFALSE )  是否产生了调度请求
15         {
16             /* A yield was pended while the scheduler was suspended. */
17             eReturn = eAbortSleep;
18         }
19         else
20         {
21             /* If all the tasks are in the suspended list (which might mean they
22             have an infinite block time rather than actually being suspended)
23             then it is safe to turn all clocks off and just wait for external
24             interrupts. */
25             if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) == ( uxCurrentNumberOfTasks - uxNonApplicationTasks ) )  只有一个IdleTask活着
26             {
27                 eReturn = eNoTasksWaitingTimeout;
28             }
29             else
30             {
31                 mtCOVERAGE_TEST_MARKER();
32             }
33         }
34 
35         return eReturn;
36     }
37 
38 #endif /* configUSE_TICKLESS_IDLE */

复制代码

 

 

【5】宏 configPRE_SLEEP_PROCESSING ()和 configPOST_SLEEP_PROCESSING()
在真正的低功耗设计中不仅仅是将处理器设置到低功耗模式就行了,还需要做一些其他的处理,比如:

● 将处理器降低到合适的频率,因为频率越低功耗越小,甚至可以在进入低功耗模式以后关闭系统时钟。
● 修改时钟源,晶振的功耗肯定比处理器内部的时钟源高,进入低功耗模式以后可以切换到内部时钟源,比如 STM32 的内部 RC 振荡器。
● 关闭其他外设时钟,比如 IO 口的时钟。
● 关闭板子上其他功能模块电源,这个需要在产品硬件设计的时候就要处理好,比如可以通过 MOS 管来控制某个模块电源的开关,在处理器进入低功耗模式之前关闭这些模块的电源。

 

FreeRTOS 为我们提供了一个宏来完成这些操作,它就是 configPRE_SLEEP_PROCESSING(),这个宏的具体实现内容需要用户去编写。

如果在进入低功耗模式之前我们降低了处理器频率、关闭了某些外设时钟等的话,那在退出低功耗模式以后就需要恢复处理器频率、重新打开外设时钟等,

这个操作在宏configPOST_SLEEP_PROCESSING()中完成,同样的这个宏的具体内容也需要用户去编写。

 

这两个宏会被函数 vPortSuppressTicksAndSleep()调用,我们可以在 FreeRTOSConfig.h 定义这两个宏,如下:

复制代码

/********************************************************************************/
/* FreeRTOS 与低功耗管理相关配置 */
/********************************************************************************/
extern void PreSleepProcessing(uint32_t ulExpectedIdleTime);
extern void PostSleepProcessing(uint32_t ulExpectedIdleTime);
//进入低功耗模式前要做的处理
#define configPRE_SLEEP_PROCESSING PreSleepProcessing
//退出低功耗模式后要做的处理
#define configPOST_SLEEP_PROCESSING PostSleepProcessing

复制代码

函数 PreSleepProcessing()和 PostSleepProcessing()可以在任意一个 C 文件中编写,例子:

复制代码

//进入低功耗模式前需要处理的事情
//ulExpectedIdleTime:低功耗模式运行时间
void PreSleepProcessing(uint32_t ulExpectedIdleTime)
{
  //关闭某些低功耗模式下不使用的外设时钟
  __HAL_RCC_GPIOB_CLK_DISABLE(); (1)
  __HAL_RCC_GPIOC_CLK_DISABLE();
  __HAL_RCC_GPIOD_CLK_DISABLE();
  __HAL_RCC_GPIOE_CLK_DISABLE();
  __HAL_RCC_GPIOF_CLK_DISABLE();
  __HAL_RCC_GPIOG_CLK_DISABLE();
  __HAL_RCC_GPIOH_CLK_DISABLE();
}


void PostSleepProcessing(uint32_t ulExpectedIdleTime)
{
  //退出低功耗模式以后打开那些被关闭的外设时钟
  __HAL_RCC_GPIOB_CLK_ENABLE(); (2)
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOE_CLK_ENABLE();
  __HAL_RCC_GPIOF_CLK_ENABLE();
  __HAL_RCC_GPIOG_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();
}
(1)、进入低功耗模式以后关闭那些低功耗模式中不用的外设时钟,USART1 和 GPIOA 的时钟没有关闭。
(2)、退出低功耗模式以后需要打开函数 PreSleepProcessing()中关闭的那些外设的时钟。

复制代码

 

 

 

【6】给系统时钟节拍,加个补偿值。

复制代码

    void vTaskStepTick( const TickType_t xTicksToJump )
    {
        /* Correct the tick count value after a period during which the tick
        was suppressed.  Note this does *not* call the tick hook function for
        each stepped tick. */
        configASSERT( ( xTickCount + xTicksToJump ) <= xNextTaskUnblockTime );
        xTickCount += xTicksToJump;
        traceINCREASE_TICK_COUNT( xTicksToJump );
    }

复制代码

 

 

 

======================================================================

======================================================================

空闲任务

优先级最低,

空闲任务有一个重要的职责:

如果某个任务要调用函数 vTaskDelete()删除自身,

那么这个任务的任务控制块 TCB 和 任务堆栈 等这些由 FreeRTOS 系统自动分配的内存,需要在空闲任务中释放掉。

 

空闲任务的创建:启动任务调度器的时候自动创建。

大部分功能已经在“ 开启关闭调度器、挂起恢复调度器、vTaskStepTick ”这章节解释过了。这里看IdleTask的创建部分就行。

复制代码

  1 void vTaskStartScheduler( void )
  2 {
  3 BaseType_t xReturn;
  4 
  5     
  6     /* Add the idle task at the lowest priority. */
  7     #if( configSUPPORT_STATIC_ALLOCATION == 1 )   静态方式创建IdleTask
  8     {
  9         StaticTask_t *pxIdleTaskTCBBuffer = NULL;
 10         StackType_t *pxIdleTaskStackBuffer = NULL;
 11         uint32_t ulIdleTaskStackSize;
 12 
 13         /* The Idle task is created using user provided RAM - obtain the
 14         address of the RAM then create the idle task. */
 15         vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );    这几个空间由用户来定义
 16         xIdleTaskHandle = xTaskCreateStatic(    prvIdleTask,
 17                                                 "IDLE",
 18                                                 ulIdleTaskStackSize,
 19                                                 ( void * ) NULL,
 20                                                 ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
 21                                                 pxIdleTaskStackBuffer,
 22                                                 pxIdleTaskTCBBuffer ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
 23 
 24         if( xIdleTaskHandle != NULL )
 25         {
 26             xReturn = pdPASS;
 27         }
 28         else
 29         {
 30             xReturn = pdFAIL;
 31         }
 32     }
 33     #else   动态方式创建IdleTask
 34     {
 35         /* The Idle task is being created using dynamically allocated RAM. */
 36         xReturn = xTaskCreate(    prvIdleTask,
 37                                 "IDLE", configMINIMAL_STACK_SIZE,    堆栈大小可以改~
 38                                 ( void * ) NULL,
 39                                 ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
 40                                 &xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
 41     }
 42     #endif /* configSUPPORT_STATIC_ALLOCATION */
 43 


        /* 下边的不属于IdleTask方面的 */

 44     #if ( configUSE_TIMERS == 1 )  【略】
 45     {
 46         if( xReturn == pdPASS )
 47         {
 48             xReturn = xTimerCreateTimerTask();
 49         }
 50         else
 51         {
 52             mtCOVERAGE_TEST_MARKER();
 53         }
 54     }
 55     #endif /* configUSE_TIMERS */
 56 
 57     if( xReturn == pdPASS )
 58     {
 59         /* Interrupts are turned off here, to ensure a tick does not occur
 60         before or during the call to xPortStartScheduler().  The stacks of
 61         the created tasks contain a status word with interrupts switched on
 62         so interrupts will automatically get re-enabled when the first task
 63         starts to run. */
 64         portDISABLE_INTERRUPTS();
 65 
 66         #if ( configUSE_NEWLIB_REENTRANT == 1 )
 67         {
 68             /* Switch Newlib's _impure_ptr variable to point to the _reent
 69             structure specific to the task that will run first. */
 70             _impure_ptr = &( pxCurrentTCB->xNewLib_reent );
 71         }
 72         #endif /* configUSE_NEWLIB_REENTRANT */
 73 
 74         xNextTaskUnblockTime = portMAX_DELAY;
 75         xSchedulerRunning = pdTRUE;
 76         xTickCount = ( TickType_t ) 0U;
 77 
 78         /* If configGENERATE_RUN_TIME_STATS is defined then the following
 79         macro must be defined to configure the timer/counter used to generate
 80         the run time counter time base. */
 81         portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
 82 
 83         /* Setting up the timer tick is hardware specific and thus in the
 84         portable interface. */
 85         if( xPortStartScheduler() != pdFALSE )
 86         {
 87             /* Should not reach here as if the scheduler is running the
 88             function will not return. */
 89         }
 90         else
 91         {
 92             /* Should only reach here if a task calls xTaskEndScheduler(). */
 93         }
 94     }
 95     else
 96     {
 97         /* This line will only be reached if the kernel could not be started,
 98         because there was not enough FreeRTOS heap to create the idle task
 99         or the timer task. */
100         configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
101     }
102 
103     /* Prevent compiler warnings if INCLUDE_xTaskGetIdleTaskHandle is set to 0,
104     meaning xIdleTaskHandle is not used anywhere else. */
105     ( void ) xIdleTaskHandle;
106 }

复制代码

 

IdleTask任务函数:

复制代码

  1 /*
  2  * -----------------------------------------------------------
  3  * The Idle task.
  4  * ----------------------------------------------------------
  5  *
  6  * The portTASK_FUNCTION() macro is used to allow port/compiler specific
  7  * language extensions.  The equivalent prototype for this function is:
  8  *
  9  * void prvIdleTask( void *pvParameters );
 10  *
 11  */
 12 static portTASK_FUNCTION( prvIdleTask, pvParameters )
 13 {
 14     /* Stop warnings. */
 15     ( void ) pvParameters;
 16 
 17     /** THIS IS THE RTOS IDLE TASK - WHICH IS CREATED AUTOMATICALLY WHEN THE
 18     SCHEDULER IS STARTED. **/
 19 
 20     for( ;; )
 21     {
 22         /* See if any tasks have deleted themselves - if so then the idle task
 23         is responsible for freeing the deleted task's TCB and stack. */
 24         prvCheckTasksWaitingTermination();  检查是否有任务删除自己,有的话,会添加到xTaskWaitingTermination列表,扫描这个列表,并进行清理工作。
 25 
 26         #if ( configUSE_PREEMPTION == 0 )
 27         {
 28             /* If we are not using preemption we keep forcing a task switch to
 29             see if any other task has become available.  If we are using
 30             preemption we don't need to do this as any task becoming available
 31             will automatically get the processor anyway. */
                如果没有使用抢占式内核,就强制执行任务切换,查看是否有其他任务有效。
                抢占式内核,只要有优先级高的任务,自动就会抢占,不用这一步。
 32             taskYIELD();
 33         }
 34         #endif /* configUSE_PREEMPTION */
 35 
 36         #if ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) )
 37         {
 38             /* When using preemption tasks of equal priority will be
 39             timesliced.  If a task that is sharing the idle priority is ready
 40             to run then the idle task should yield before the end of the
 41             timeslice.
 42             如果使用抢占式内核,并且使能时间片调度,当有任务和空闲任务共享一个优先级的时,此任务就绪,空闲任务就应该放弃本时间片
                将本时间片剩余的时间让给这个就绪任务。

 43             A critical region is not required here as we are just reading from
 44             the list, and an occasional incorrect value will not matter.  If
 45             the ready list at the idle priority contains more than one task
 46             then a task other than the idle task is ready to execute. */
 47             if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > ( UBaseType_t ) 1 )
 48             {
 49                 taskYIELD();  检查空闲任务优先级的就绪任务列表,不为空,则进行任务切换。
 50             }
 51             else
 52             {
 53                 mtCOVERAGE_TEST_MARKER();
 54             }
 55         }
 56         #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) */
 57 
 58         #if ( configUSE_IDLE_HOOK == 1 )
 59         {
 60             extern void vApplicationIdleHook( void );
 61 
 62             /* Call the user defined function from within the idle task.  This
 63             allows the application designer to add background functionality
 64             without the overhead of a separate task.
 65             NOTE: vApplicationIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES,
 66             CALL A FUNCTION THAT MIGHT BLOCK. */
 67             vApplicationIdleHook();  执行空闲任务钩子函数,钩子函数不能使用任何可以引起阻塞的API.
 68         }
 69         #endif /* configUSE_IDLE_HOOK */
 70 
 71         /* This conditional compilation should use inequality to 0, not equality
 72         to 1.  This is to ensure portSUPPRESS_TICKS_AND_SLEEP() is called when
 73         user defined low power mode    implementations require
 74         configUSE_TICKLESS_IDLE to be set to a value other than 1. */
            如果使能了Tickless模式,就执行相关处理代码
 75         #if ( configUSE_TICKLESS_IDLE != 0 )      使能了Tickless模式
 76         {
 77         TickType_t xExpectedIdleTime;
 78 
 79             /* It is not desirable to suspend then resume the scheduler on
 80             each iteration of the idle task.  Therefore, a preliminary
 81             test of the expected idle time is performed without the
 82             scheduler suspended.  The result here is not necessarily
 83             valid. */
 84             xExpectedIdleTime = prvGetExpectedIdleTime();  获取处理器进入低功耗模式的时长,变量ExceptionidleTime单位是时钟节拍数。
 85 
 86             if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP ) ExcptIdleTime要大于这个宏,原因在后边再说。⭐
 87             {
 88                 vTaskSuspendAll();  相当于临界段代码保护功能。
 89                 {
 90                     /* Now the scheduler is suspended, the expected idle
 91                     time can be sampled again, and this time its value can
 92                     be used. */
                        调度器已被挂起,重新采集一次时间值,这次的时间值可以使用。
 93                     configASSERT( xNextTaskUnblockTime >= xTickCount );
 94                     xExpectedIdleTime = prvGetExpectedIdleTime();  重新获取,这次可以直接用于Suppress_Tick_And_Sleep.
 95 
 96                     if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
 97                     {
 98                         traceLOW_POWER_IDLE_BEGIN();
 99                         portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );   调用宏,进入低功耗模式。
100                         traceLOW_POWER_IDLE_END();
101                     }
102                     else
103                     {
104                         mtCOVERAGE_TEST_MARKER();
105                     }
106                 }
107                 ( void ) xTaskResumeAll();  恢复任务调度器
108             }
109             else
110             {
111                 mtCOVERAGE_TEST_MARKER();
112             }
113         }
114         #endif /* configUSE_TICKLESS_IDLE */
115     }
116

 

转载地址:https://www.cnblogs.com/WMCH/articles/7891399.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值