1. 程式人生 > 其它 >FreeRTOS tickless低功耗 內部函式處理詳解

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

轉載地址:https://www.cnblogs.com/WMCH/articles/7891399.html