freeRTOS 低功耗模式 和 空闲任务

低功耗模式

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 }

 

 

 

 

 

 

 

留白

posted @ 2017-11-24 16:53  为民除害  阅读(6465)  评论(0编辑  收藏  举报