低功耗模式
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 }
留白