Close

Converting Delays to Sleep

A project log for Coin Cell Powered Temperature/Humidity Display

Using an ePaper Display, investigate getting the lowest possible current usage from an STM32L0.

Kevin KesslerKevin Kessler 04/25/2018 at 04:300 Comments

The Waveshare ePaper drivers spend a good bit of time in HAL_Delay. When it initially resets the display, it drives the RESET pin low and waits 200ms, then drives it high, and waits another 200 ms. When it updates the display, it kind of loops around waiting on the BUSY pin for seconds until the display is ready to receive another command. All of this wait time is a perfect opportunity to put the uControler to sleep.

Implementing sleeping during the delays had some effect on the energy usage of the device during the update of the ePaper display. The uController alone draws .632mA when executing HAL_Delay, but only .324mA using sleepDelay, shown below. Overall, the energy usage for one display update dropped from 0.0093 mAH to 0.0086 mAH. This corresponds to an extra 2180 display updates on one CR2032 battery.

I re-implemented the delay in the epdif.c file from a simple HAL_Delay, to one that puts the STM32 into STOP mode, and uses the Real Time Clock to wake the controller after the delay:

void sleepDelay(uint16_t delaytime){

    if(delaytime < 25) {
        HAL_Delay(delaytime);
        return;
    }
    else {
        if(HAL_RTCEx_DeactivateWakeUpTimer(&hrtc) != HAL_OK) {
                    HAL_Delay(delaytime);
                    return;
                }
        __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
        uint32_t counter = (uint32_t)(delaytime * 1000) /432;
        if(HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, (uint16_t)counter, RTC_WAKEUPCLOCK_RTCCLK_DIV16)!=HAL_OK) {
                    HAL_Delay(delaytime);
                    return;
                }

        HAL_SuspendTick();
        HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON, PWR_STOPENTRY_WFI);
        HAL_ResumeTick();

    }

}

This code just does a regular HAL_Delay is the delay is for less than 25ms and goes to sleep for longer delays. The normal pattern for RTC Wakeup power savings modes is to deactivate any RTC timer currently running, clear the Wake Up flag, set a new timer, and enter the selected low power mode. Since the uC will wake up on any interrupt, the SYSTick clock, which generates an interrupt every 1ms, has to be disabled before entering STOP mode with HAL_SuspendTick(), and re-enabled once it leaves STOP mode.  To calculate the delay, the 37kHz clock divided by 16 has a 432us period. To avoid floating point arithmetic, I convert the delay time to microseconds, to figure out how many 432us counts I need to wait to get the delay.

A very time consuming part of the display update procedure is waiting for the BUSY line on the ePaper display to go high indicating it is ready to do something else. It is implemented in the Waveshare library like:

void EPD_WaitUntilIdle(EPD* epd) {
  while(EPD_DigitalRead(epd, epd->busy_pin) == 0) {      //0: busy, 1: idle
    EPD_DelayMs(epd, 100);
  }
}

While the EPD_DelayMs call will now go the sleepDelay code, this can be further improved by converting the BUSY line to an interrupt, and putting the STM32 to sleep as it wait for the BUSY line to go high and generate an interrupt. This is implement in the sleepWait function in epdcontroller.c, and is basically a simpler sleep procedure compare to the sleepDelay above, because the RTC is not set. The code can be found in the GitHub repo under the tag STOPMode.

I spent at least a day trying to track down an issue where the device would hang once it returned from STANDBY mode and start to do another ePaper display update. It turned out that when it entered STOP mode in the sleepDelay function, it never returned, and that seemed to be related to the Wakeup Timer not functioning properly (the BUSY interrupt wake up worked fine). After banging my head against a wall for hours, I found the problem was in the STM32Cube generated code. The offending code was this:

static void MX_RTC_Init(void)
{

  RTC_TimeTypeDef sTime;
  RTC_DateTypeDef sDate;

    /**Initialize RTC Only 
    */
  hrtc.Instance = RTC;
if(HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0) != 0x32F2){
  hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
  hrtc.Init.AsynchPrediv = 124;

/* Cut out a lot of code here for brevity */

  }

    HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR0,0x32F2);
  }

}

The initialization routine for the Real Time Clock would first check for the magic number 0x32F2 in the first backup register (DR0). If it wasn't there, there it would initialize the RTC and write that 0x32F2 number to the register. If it was, it would skip the whole initialization routine. Since the backup registers keep their values even in STANDBY mode, when the STM32 returned from STANDBY, the DR0 register retained its value, and it skipped the RTC initialization. Why the code is generated like this, I do not know, but the only fix I had was to cut and paste this initialization routine to a function called MX_RTC_Init_fixed, and call it in the user code of main.c. See the STOPMode tag in the repo for the complete code. This bug was introduced in STM32CubeMX version 4.25, because code generated with version 4.23 does not have this rouge if statement in it.

Update 5/2/2018: I put a message on the ST Community explained the problem the RTC Initialization bug. Apparently ST Engineers were able to reproduce the problem, because I got a message that it will be fixed in the next release.

Discussions