Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / IoT / Arduino

Power Reduction and Wake Up Techniques on ESP32 Board

4.50/5 (4 votes)
4 Jun 2021CPOL7 min read 14K   160  
A C++ script with functions for managing the power reduction and wake up techniques on ESP32 board
In this article, you will see the use of functions to reduce power consumption by putting the board into sleep and how it can be awakened. A part of the article is dedicated to illustrating the application that uses the aforementioned functions with particular reference to data preservation during the sleep period.

Introduction

This article shows the use of functions for reducing the power consumption by putting the board into sleep and how it can be awakened.

I found many articles on this subject and I am grateful to their authors who allowed me to develop the program that I am about to describe.

I also believe that this is an opportunity to solicit observation and criticism as I know that I am not an expert in this field; so I apologize if the reader finds imprecisions or errors.

Background

The ESP32Power.h script contains a set of functions for enter in so called sleep mode in order to reduce power consumption and functions to set how the board can be awakened. The sleeps modes considered here are essentially light and deep - they differ not only in the lesser or greater reduction in current but also in the behavior after waking up; in fact, waking up from deep sleep is an almost restart, i.e., the program is reinitialized and only data in the RTC (Real Time Clock) memory are preserved.

The following table summarizes the possible ways of reducing the power of the ESP32 board in relation to what is achieved by the script in question.

Modem Sleep Mode Is automatic when dealing with WIFI end/or Bluetooth
Light Sleep Mode Implemented
Deep Sleep Mode Implemented
Hibernation Mode (not full) Implemented

The wake up types are summarized in the below table:

Timer Implemented
Touch pad Implemented
External wakeup (ext0) Implemented
External wakeup (ext1) Implemented
ULP coprocessor wakeup Not implemented
GPIO wakeup Implemented
UART wakeup Implemented but not yet tested

Using the Code

The sketch uses a scheduler (see the Code Project article Arduino scheduler) with three events:

  • after 2 seconds of start, displays the help and the event is disabled
  • every two seconds, the event checks the serial port in order to manage the requests
  • every minute, a time is displayed

The command h shows the help, below are some examples of commands sleeping:

  • d7 deep sleep of 7 seconds
  • lg15 light sleep that can end by GPIO pin or after 15 seconds
  • di wakes up from deep sleep by Ext0 pin

The Hardware

The board is ESP32 DEVKIT TV1 with:

  • a connector Female Female connected to pin named VIM
  • a connector Female Female connected to pin named 3V3
  • a connector Female Male connected to pin named D15
  • a free connector Male Male

Wake Up Types

The ESP32Power.h script contains Functions, Variables and Constants for dealing with Power Reduction.

In particular, it contains:

  • functions for setting the wake up mode and entering in power reduction (these are explained in dedicated paragraphs)
  • a stub function that is a function called where the board wake up after a deep sleep
  • two functions for get the clock
  • a function that shows the type of wake up

All mode of wake up can be associated to a wake up by timer; in fact, the function pw_EnterSleep(dl, delayType) being called by all types of wake up allows also to insert the timer wake up (see below).

C++
void pw_EnterSleep(uint32_t dl,int delayType) {       // dl is milliseconds
  pw_sleepTime = dl;
  if (dl > 0) esp_sleep_enable_timer_wakeup(dl*1000); // enable also timer
  if (delayType == PW_LIGHT_SLEEP) esp_light_sleep_start();
  else if (delayType == PW_DEEP_SLEEP) {
    pw_Clock = pw_getTime2()/1000;
    esp_deep_sleep_start();
  } else
  delay(dl);  // -1 and unknown
}

In the following paragraphs, I will use some variables:

  • dl the sleep time in milliseconds
  • delayType the sleep mode, one of the constants PW_LIGHT_SLEEP, PW_DEEP_SLEEP
  • touchPin, touchPins the pin(s) that can work for touch
  • Threshold the "sensibility" of the touch
  • pin, pins the pin(s) of GPIO or the pin(s) of awakening by Ext1 interrupt
  • mode the change of state that causes awakening

touchPins and pins are array terminated by -1 like:

C++
...
int8_t GPIOpins[] = {GPIO_NUM_13,GPIO_NUM_4, -1};
int8_t touchPins[] = {T0,T3,-1};      // Touch pins GPIO 4 GPIO 15
int8_t Ext1Pins[] = {GPIO_NUM_13,GPIO_NUM_4, -1};
...

Below is a sample of function to set up the touch wake up:

C++
void pw_wakeByTouch(uint32_t dl,int delayType, int8_t touchPins[], 
     int Threshold = 40) {  // *** wakeup by touch pad ***
  for (int8_t i=0;touchPins[i] != -1;i++) 
       touchAttachInterrupt(touchPins[i], pw_callback, Threshold);
  esp_sleep_enable_touchpad_wakeup();
  pw_EnterSleep(dl,delayType);
  esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_TOUCHPAD);
}

Note that the last statement that disables the wake up source is executed only for light sleep.

Wake Up by Timer

There are two functions for setting a timer based interrupt:

  • pw_wakeByTimer(dl, delayType)
  • pw_EnterSleep(dl, delayType)

They are formally similar, the first is usable only for the wake up by timer and, if deep sleep is required, a (little) hibernation occurs. The second is also called by all types of wake up.

Wake Up by Touch Pad

The two functions:

  • pw_wakeByTouch(dl, delayType, touchPins, Threshold = 40) // pins array
  • pw_wakeByTouch(dl, delayType, touchPin, Threshold = 40) // single pin

enter the board in sleep; the board woke up when one of touchPin(s) is solicited or by timer if dl is greater than 0.

If Threshold is omitted, the default is 40.

Below are the GPIO pins associated with the touches, the first row contains the RTC pins that can be used for the Ext0 and Ext1 wake ups.

GPIO 0 2 4 12 13 14 15 25 26 27 32 33 34 35 36 37 38 39
Touch T1 T2 T0 T5 T4 T6 T3     T7 T9 T8            

To test: Remove a connector from one of the power sources (for example, from 3V3 named pin) and insert it into pin named D4, then insert the free connector to that connector; in this mode, we have two touch pins (T0 and T3).

Wake Up by GPIO

All pins can be used for wake up GPIO, but only for light sleep, the two functions:

  • pw_wakeByGPIO(dl, pins, mode = GPIO_INTR_HIGH_LEVEL)
  • pw_wakeByGPIO(dl, pin, mode = GPIO_INTR_HIGH_LEVEL)

Enter the board in sleep, the board woke up when the pin or one of pins assumes the state indicated by mode or by timer if dl is greater than 0.

The default mode is HIGH (when the pin is linked to power).

To test: To wake up, connect one of the power sources (for example, the 3V3 named pin) to one of the pins named D13 or D4.

Wake Up by Interrupt

This interrupt can be by a single pin that changes the status (Ext0):

C++
pw_wakeByExt0(dl, delayType, pin, mode = HIGH)

or a set of pins (Ext1):

C++
pw_wakeByExt1(dl, delayType, pins, mode = ESP_EXT1_WAKEUP_ANY_HIGH)

The mode can be ESP_EXT1_WAKEUP_ANY_HIGH (the default) that is the wake up occurs when one of pins go to HIGH or ESP_EXT1_WAKEUP_ALL_LOW that is all pins go to LOW.

The script contains a function for set the Ext1 pins:

C++
...
inline uint64_t setExt1(uint64_t a, int pin) {return bitSet(a, pin % 40);}
...
void pw_wakeByExt1(uint32_t dl,int delayType, int8_t pins[], 
     esp_sleep_ext1_wakeup_mode_t mode = ESP_EXT1_WAKEUP_ANY_HIGH) {
  uint64_t Ext1 = 0;
  for (int8_t i=0; pins[i] != -1;i++) 
       Ext1 = setExt1(Ext1, pins[i]); // 0,2,4,12-15,25-27,32-39
  esp_sleep_enable_ext1_wakeup(Ext1,mode);
  pw_EnterSleep(dl,delayType);
  esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_EXT1);
}

To test Ext0: To wake up connect one of the power source (for example, the 3V3 named pin) to the pin named D13.

To test Ext1: To wake up connect one of the power source (for example, the 3V3 named pin) to one of the pins named D13 or D4.

Wake Up From Deep Sleep

Unlike light sleep where the contents of the memory are preserved, in deep sleep mode, only the RTC memories are preserved and the CPU and all digital peripherals are powered off. RTC controller, RTC peripherals and ULP co-processor remains powered.

As a consequence of this, upon awakening, the program performs a quasi reset, i.e., it restarts from the setup() function, however what is present in the RTC memories is preserved and, through the function esp_sleep_get_wakeup_cause(), it is possible to know if there has been a hardware reset (as when the board connects to the power source) or a simple wake up.

C++
void setup() {
  Serial.begin(9600);
  delay(100);
  if ((int) esp_sleep_get_wakeup_cause() > 0) {
    Serial.println(pw_wakeup_reason());  
    events.begin(cron,pw_Clock);
  }
}

The Stub Function

MC++
void RTC_IRAM_ATTR esp_wake_deep_sleep(void) {
  esp_default_wake_deep_sleep();
  pw_Clock = pw_getTime()/1000 - pw_Clock; // milliseconds
}

Open or Unaddressed Problems

This paragraph partially reflects my lack of knowledge of the ESP32 card.

The script has a function for Uart wake up but it is not yet tried, the ULP wake up has not been implemented.

The call for wake up from deep sleep by timer has a partial hibernation obtained by turning off the RTC peripherals and the RTC Fast Memory. In this case, I was unable to access the RTC memory from the stub function, which however had retained its contents. Turning off the RTC Low Memory has not been implemented because it causes loss of the stored data.

The time sleep isn't checked; I found and checked that it is a maximum about 71 minutes for the counter is 32 bits and the values are microseconds, therefore it seems that the counter can be 64 bits, see this article.

Finally, when the deep wake up isn't by timer, I use the RTC controls timer with two identical functions (thanks to this post):

C++
...
#define RTC_CTNL_SLOWCLK_FREQ 160000
...
uint64_t pw_getTime2(void) {
    SET_PERI_REG_MASK(RTC_CNTL_TIME_UPDATE_REG, RTC_CNTL_TIME_UPDATE_M);
    while (GET_PERI_REG_MASK(RTC_CNTL_TIME_UPDATE_REG, RTC_CNTL_TIME_VALID_M) == 0) { }
    uint64_t now = READ_PERI_REG(RTC_CNTL_TIME0_REG);
    now |= ((uint64_t) READ_PERI_REG(RTC_CNTL_TIME1_REG)) << 32;
    return now * 100 / (RTC_CTNL_SLOWCLK_FREQ / 10000);    // scale RTC_CTNL_SLOWCLK_FREQ 
                                                           // to avoid overflow;
}
RTC_IRAM_ATTR uint64_t pw_getTime(void) {
    SET_PERI_REG_MASK(RTC_CNTL_TIME_UPDATE_REG, RTC_CNTL_TIME_UPDATE_M);
    while (GET_PERI_REG_MASK(RTC_CNTL_TIME_UPDATE_REG, RTC_CNTL_TIME_VALID_M) == 0) { }
    uint64_t now = READ_PERI_REG(RTC_CNTL_TIME0_REG);
    now |= ((uint64_t) READ_PERI_REG(RTC_CNTL_TIME1_REG)) << 32;
    return now * 100 / (RTC_CTNL_SLOWCLK_FREQ / 10000);    // scale RTC_CTNL_SLOWCLK_FREQ 
                                                           // to avoid overflow;
}

Why two functions? Because I am not able to call the function stored in the RTC memory from normal operation but only from the stub function. Another fact, which I am perplexed about, is the frequency I used is 160kHz that give a less deviation compared to 150kHz.

Known Issues

After the try of interrupt by touch, the GPIO wake up on the same pin used for touch doesn't works (also after a deep sleep).

After a light sleep terminated by a touch, the esp_sleep_get_touchpad_wakeup_status function crashes so I am not able to know which touch pin caused the interruption.

History

  • 30th May, 2021: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)