FreeRTOS Low Power Support

1. Introduction

在 FreeRTOS 裡, Power Saving 相關的工作通常會放在 idle task 裡面作, 它的作法是限制periodic tick interrupt, 這樣可以減少 period task的耗電

實際作法是, 它停止 periodic tick interrupt, 然後在 restart periodic tick interrupt實調整 RTOS tick count

在 tick interrupt 停止的時候, microcontroller 就可以進 deep power saving state, 直到有interrupt發生

2. Idle Task

idle task 放在 task.c
static portTASK_FUNCTION( prvIdleTask, pvParameters )

裡面有 infinite for loop 不停地執行一些工作, 包括:

  1. 檢查有沒有 task 已經被刪除
  2. 檢查 preemption 相關的工作
  3. 呼叫 idle hook funtion
  4. Power Saving Task

其中有兩個 hook function 會因為 power saving 的機制受到影響

  • vApplicationIdleHook : 在 idle task 裡每次呼叫
  • vApplicationTickHook : 在 idle task 裡, 並且只有在 sytem tick 增加時才呼叫
這兩個 hook function 都是直接 hard code 實作, 預設是空的, 為了避免影響其它task, 這兩個hook function 裡面不可以擺可能會 blocking 的 task

3. Power Saving

底下是 idle task 裡的 power saving code:
/* This conditional compilation should use inequality to 0, not equality
to 1.  This is to ensure portSUPPRESS_TICKS_AND_SLEEP() is called when
user defined low power mode implementations require
configUSE_TICKLESS_IDLE to be set to a value other than 1. */
#if ( configUSE_TICKLESS_IDLE != 0 )
TickType_t xExpectedIdleTime;

 /* It is not desirable to suspend then resume the scheduler on
 each iteration of the idle task.  Therefore, a preliminary
 test of the expected idle time is performed without the
 scheduler suspended.  The result here is not necessarily
 valid. */
 xExpectedIdleTime = prvGetExpectedIdleTime();

 if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
   /* Now the scheduler is suspended, the expected idle
   time can be sampled again, and this time its value can
   be used. */
   configASSERT( xNextTaskUnblockTime >= xTickCount );
   xExpectedIdleTime = prvGetExpectedIdleTime();

   if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
    portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
  ( void ) xTaskResumeAll();
#endif /* configUSE_TICKLESS_IDLE */

它第一步先計算預計會idle的時間 xExpectedIdleTime, 如果這個時間大於 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 才真的 sleep, 不然的話, idle時間太短又跑去sleep會造成額外的 effort

接著 vTaskSuspendAll(), 這裡將 uxSchedulerSuspended 加 1, 於是task scheduler就被停止

然後重新計算 idle time, 接著進入sleep
portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );

最後 xTaskResumeAll(), 將 uxSchedulerSuspended 減 1, 將所有應該要切入 ready state 的 task 喚醒, 最後將 system tick 加回來


portSUPPRESS_TICKS_AND_SLEEP() 這個macro根據不同的MCU而有不同的實作, 在GCC, IAR, Keil都有default的實作

通常 SysTick 跟 Cortex-M Microcontrollers Clock 是一樣的 frequency, 這種情況下, 這兩個值是一樣的, configSYSTICK_CLOCK_HZ 以及 configCPU_CLOCK_HZ

如果 SysTick 比 Core Clock還慢的話, 就要定義 configSYSTICK_CLOCK_HZ 這個值

其中sleep的機制會呼叫到Cortex-M的 function call
/* Sleep until something happens.  configPRE_SLEEP_PROCESSING() can
set its parameter to 0 to indicate that its implementation contains
its own wait for interrupt or wait for event instruction, and so wfi
should not be executed again.  However, the original expected idle
time variable must remain unmodified, so a copy is taken. */
xModifiableIdleTime = xExpectedIdleTime;
configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
if( xModifiableIdleTime > 0 )
configPOST_SLEEP_PROCESSING( xExpectedIdleTime );

其中會有個 pre & post processing, 中間sleep的部份取決於不同MCU而有不同的code

DSP 表示 Data Synchronization Barrier, 它將 processor目前做到一半的工作做完
WFI 表示 Wait For Interrupt, 它讓 processor 進 suspend, 直到底下其中一個發生:

  • non-masked interrupt 發生
  • 被 PRIMASK mask 的 interrupt 被 pending
  • Debug Entery 的 request

ISB 表示 Instruction Synchronization Barrier, 它將 processor 的pipe line清空, 這樣才不會拿到之前未執行的 code 來執行