2019年3月25日 星期一

STM32duino delay power saving.

STM32duino delay power saving.

STM32duino delay power saving.

如果使用STM32duino平台寫code,又需要使用low power的功能做省電時,若使用delay()語法做延遲,MCU會用全速的速度等待delay()設定的時間

例如

  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(5000);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(5000);

然後,delay這個function內部長這個樣子

void delay( uint32_t ms )
{
  if (ms == 0)
      return;
  uint32_t start = GetCurrentMilli();
  do {
      yield();
  } while (GetCurrentMilli() - start < ms);
}

所以當我們從外部呼叫delay(5000)時,MCU會以全速在do-while之間,繞圈不做任何事。

以STM32L051@32Mhz來說,核心的消耗電流為
140uA * 32 = 4.48 mA,這五秒間就白白把這些電消耗掉不做任何事。

如果需要優化,則需要在do-while之間增加省電機制,讓MCU掉入睡眠模式。

讓我們看看,在do-while迴圈間的yield()在做什麼事情

/**
 * Empty yield() hook.
 *
 * This function is intended to be used by library writers to build
 * libraries or sketches that supports cooperative threads.
 *
 * Its defined as a weak symbol and it can be redefined to implement a
 * real cooperative scheduler.
 */
static void __empty() {
	// Empty
}
void yield(void) __attribute__ ((weak, alias("__empty")));

這function讓使用者根據函式庫或者開發環境來自定義,因為有使用__attribute__ 前綴,所以只要在code內寫另一yield的函式,GCC會取代現有的yield函式(也就是目前無任何功能的"空"函式)。

所以,只要將yield()的函式設計成,讓系統進入sleep mode即可。

參考AN4445,STM32L0xx ultra-low power features overview

Sleep mode,只有將CPU進入Sleep,其餘皆正常運作,不用擔心外部中斷,或者正在通訊的UART會有收不到資料的情況。

但是,即使這樣,CPU停止後仍需要中斷,將CPU重新啟動,若是沒有外部中斷,或者UART能喚醒怎麼辦?

這時,就得了解STM32duino,其系統預設會使用SYSTICK做為系統時鐘,該時鐘會1ms中斷一次,所以即使進入睡眠,系統也會在1ms後醒來。

/**
  * @brief This function configures the source of the time base. 
  *        The time source is configured  to have 1ms time base with a dedicated 
  *        Tick interrupt priority.
  * @note This function is called  automatically at the beginning of program after
  *       reset by HAL_Init() or at any time when clock is reconfigured  by HAL_RCC_ClockConfig(). 
  * @note In the default implementation, SysTick timer is the source of time base. 
  *       It is used to generate interrupts at regular time intervals. 
  *       Care must be taken if HAL_Delay() is called from a peripheral ISR process, 
  *       The the SysTick interrupt must have higher priority (numerically lower) 
  *       than the peripheral interrupt. Otherwise the caller ISR process will be blocked.
  *       The function is declared as __Weak  to be overwritten  in case of other
  *       implementation  in user file.
  * @param TickPriority Tick interrupt priority.
  * @retval HAL status
  */
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
  /*Configure the SysTick to have interrupt in 1ms time basis*/
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000U);

  /*Configure the SysTick IRQ priority */
  HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority ,0U);

   /* Return function status */
  return HAL_OK;
}

這樣的設定,搭配sleep mode,MCU會在delay()迴圈時這樣跑

sleep() => SysTick ISR => do-while loop check => sleep()…

理論上,99%的時間都在sleep()睡覺,這樣就可以達到省電的目的。

那到底要怎樣實做呢?其實非常簡單,只要在STM32duino的Sketch內,內新增

void yield(void){
  HAL_PWR_EnterSLEEPMode(PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFI);
}

並於setup()內,Initial LowPower_init函式

LowPower_init();

這樣就能於delay時啟動省電機制了,簡單吧!