FreeRTOS tickless低功耗 內部函式處理詳解
技術標籤:RTOS
低功耗模式
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_irqintrinsic即可)
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