ESP32 Deep Sleep & Its Wake-up Sources

Have you ever wanted your IoT project to last on batteries for almost 5 years? Wait… What? 5 years? Yes. It might sound ridiculous, but this is possible with the ESP32’s deep sleep feature.

Why do ESP32 need deep sleep?

Depending on which state it’s in, the ESP32 can be a relatively power-hungry device. It usually pulls about 75mA in normal operation and hits about 240mA while transmitting data over WiFi.

When your IoT project is powered by a plug in the wall, you tend not to care too much about power consumption. But if you are going to power your project by batteries, every mA counts. The solution here is to cut back ESP32’s power usage by leveraging Deep Sleep Mode.

To know more about other sleep modes of ESP32 and their power consumption, please visit below tutorial.

Tutorial - ESP32 Sleep Modes - Modem Sleep, Light Sleep, Deep Sleep, Hibernation & Their Power Consumption
Insight Into ESP32 Sleep Modes & Their Power Consumption
There is no question that ESP32 is a worthy competitor to many WiFi/MCU SoCs out there, often beating it on both performance and price. But,...

ESP32 Deep Sleep

In deep sleep mode, the CPU, most of the RAM and all the digital peripherals are powered off. The only parts of the chip that remains powered on are: RTC controller, RTC peripherals (including ULP co-processor), and RTC memories (slow and fast).

The chip consumes around 0.15 mA(if ULP co-processor is powered on) to 10µA.

ESP32 Deep Sleep Functional Block Diagram & Current Consumption

During deep sleep mode, the main CPU is powered down, while the ULP co-processor does sensor measurements and wakes up the main system, based on the measured data from sensors. This sleep pattern is known as ULP sensor-monitored pattern.

Along with the CPU, the main memory of the chip is also disabled. So, everything stored in that memory is wiped out and cannot be accessed.

However, the RTC memory is kept powered on. So, its contents are preserved during deep sleep and can be retrieved after we wake the chip up. That’s the reason, the chip stores Wi-Fi and Bluetooth connection data in RTC memory before disabling them.

So, if you want to use the data over reboot, store it into the RTC memory by defining a global variable with RTC_DATA_ATTR attribute. For example, RTC_DATA_ATTR int bootCount = 0;

In Deep sleep mode, power is shut off to the entire chip except RTC module. So, any data that is not in the RTC recovery memory is lost, and the chip will thus restart with a reset. This means program execution starts from the beginning once again.

ESP32 supports running a deep sleep wake stub when coming out of deep sleep. This function runs immediately as soon as the chip wakes up – before any normal initialization, bootloader code has run. After the wake stub runs, the chip can go back to sleep or continue to start normally.

Unlike the other sleep modes, the system cannot go into Deep-sleep mode automatically. esp_deep_sleep_start() function can be used to immediately enter deep sleep once wake-up sources are configured.

By default, ESP32 will automatically power down the peripherals not needed by the wake-up source. But you can optionally decide what all peripherals to shut down/keep on. For more information, check out API docs.

ESP32 Deep Sleep Wake-up sources

Wake up from deep sleep mode can be done using several sources. These sources are:

  • Timer
  • Touch pad
  • External wakeup(ext0 & ext1)

Wake-up sources can be combined, in this case the chip will wake up when any one of the sources is triggered.

These sources can be configured at any moment before entering in to sleep mode.

Warning:

It is also possible to go into deep sleep with no wake-up sources configured, in this case the chip will be in deep sleep mode indefinitely, until external reset is applied.

ESP32 Wake-up Source : Timer

RTC controller has a built in timer which can be used to wake up the chip after a predefined amount of time.

Time is specified at microsecond precision, but the actual resolution depends on the clock source selected.

esp_sleep_enable_timer_wakeup() function can be used to enable deep sleep wake up using a timer.

Here’s a code that demonstrates the most basic deep sleep example with a timer as wake-up source and how to store data in RTC memory to use it over reboots.

#define uS_TO_S_FACTOR 1000000  //Conversion factor for micro seconds to seconds
#define TIME_TO_SLEEP  5        //Time ESP32 will go to sleep (in seconds)

RTC_DATA_ATTR int bootCount = 0;

void setup(){
	Serial.begin(115200);
	delay(1000); //Take some time to open up the Serial Monitor

	//Increment boot number and print it every reboot
	++bootCount;
	Serial.println("Boot number: " + String(bootCount));

	//Print the wakeup reason for ESP32
	print_wakeup_reason();

	//Set timer to 5 seconds
	esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
	Serial.println("Setup ESP32 to sleep for every " + String(TIME_TO_SLEEP) +
	" Seconds");

	//Go to sleep now
	esp_deep_sleep_start();
}

void loop(){}

//Function that prints the reason by which ESP32 has been awaken from sleep
void print_wakeup_reason(){
	esp_sleep_wakeup_cause_t wakeup_reason;
	wakeup_reason = esp_sleep_get_wakeup_cause();
	switch(wakeup_reason)
	{
		case 1  : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
		case 2  : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
		case 3  : Serial.println("Wakeup caused by timer"); break;
		case 4  : Serial.println("Wakeup caused by touchpad"); break;
		case 5  : Serial.println("Wakeup caused by ULP program"); break;
		default : Serial.println("Wakeup was not caused by deep sleep"); break;
	}
}

ESP32 Wake-up Source : Touch Pad

RTC IO module contains logic to trigger wake up when a touch sensor interrupt occurs.

You need to configure the touch pad interrupt before the chip starts deep sleep.

esp_sleep_enable_touchpad_wakeup() function can be used to enable this wake-up source.

Here’s a code that demonstrates the most basic deep sleep example with a touch as wake-up source and how to store data in RTC memory to use it over reboots.

//Define touch sensitivity. Greater the value, more the sensitivity.
#define Threshold 40

RTC_DATA_ATTR int bootCount = 0;
touch_pad_t touchPin;

void callback(){
  //placeholder callback function
}

void setup(){
  Serial.begin(115200);
  delay(1000);

  //Increment boot number and print it every reboot
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));

  //Print the wakeup reason for ESP32 and touchpad too
  print_wakeup_reason();
  print_wakeup_touchpad();

  //Setup interrupt on Touch Pad 3 (GPIO15)
  touchAttachInterrupt(T3, callback, Threshold);

  //Configure Touchpad as wakeup source
  esp_sleep_enable_touchpad_wakeup();

  //Go to sleep now
  esp_deep_sleep_start();
}

void loop(){}

//Function that prints the reason by which ESP32 has been awaken from sleep
void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  switch(wakeup_reason)
  {
    case 1  : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case 2  : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case 3  : Serial.println("Wakeup caused by timer"); break;
    case 4  : Serial.println("Wakeup caused by touchpad"); break;
    case 5  : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.println("Wakeup was not caused by deep sleep"); break;
  }
}


//Function that prints the touchpad by which ESP32 has been awaken from sleep
void print_wakeup_touchpad(){
  touch_pad_t pin;
  touchPin = esp_sleep_get_touchpad_wakeup_status();
  switch(touchPin)
  {
    case 0  : Serial.println("Touch detected on GPIO 4"); break;
    case 1  : Serial.println("Touch detected on GPIO 0"); break;
    case 2  : Serial.println("Touch detected on GPIO 2"); break;
    case 3  : Serial.println("Touch detected on GPIO 15"); break;
    case 4  : Serial.println("Touch detected on GPIO 13"); break;
    case 5  : Serial.println("Touch detected on GPIO 12"); break;
    case 6  : Serial.println("Touch detected on GPIO 14"); break;
    case 7  : Serial.println("Touch detected on GPIO 27"); break;
    case 8  : Serial.println("Touch detected on GPIO 33"); break;
    case 9  : Serial.println("Touch detected on GPIO 32"); break;
    default : Serial.println("Wakeup not by touchpad"); break;
  }
}

ESP32 Wake-up Source : External Wake-up

There are two types of external triggers to wake ESP32 up from deep sleep.

  • ext0 – Use it when you want to wake-up the chip by one particular pin only.
  • ext1 – Use it when you have several buttons for the wake-up.

ext0 External Wake-up Source

RTC controller contains logic to trigger wake-up when one particular pin is set to a predefined logic level. That pin can be one of RTC GPIOs 0,2,4,12-15,25-27,32-39.

esp_sleep_enable_ext0_wakeup(GPIO_PIN,LOGIC_LEVEL) function can be used to enable this wake-up source. The function takes two parameters. First one is a pin number to which button is connected and second one decides if we want to trigger the wake up by a LOW or a HIGH state of the pin.

ext0 uses RTC IO to wake up, so RTC peripherals will be kept powered ON during deep sleep if this wake-up source is requested.

Because RTC IO module is enabled in this mode, internal pullup or pulldown resistors can also be used. They need to be configured by the application using rtc_gpio_pullup_en() and rtc_gpio_pulldown_en() functions, before calling esp_sleep_start().

Below schematic shows how to connect a push button to ESP32 GPIO that serves as a ext0 external wake-up source.

Connecting Button to ESP32 for ext0 External Wakeup Source

Here’s a code that demonstrates the most basic deep sleep example with a ext0 as wake-up source.

RTC_DATA_ATTR int bootCount = 0;

void setup(){
  Serial.begin(115200);
  delay(1000); 

  //Increment boot number and print it every reboot
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));

  //Print the wakeup reason for ESP32
  print_wakeup_reason();

  //Configure GPIO33 as ext0 wake up source for HIGH logic level
  esp_sleep_enable_ext0_wakeup(GPIO_NUM_33,1);

  //Go to sleep now
  esp_deep_sleep_start();
}

void loop(){}

//Function that prints the reason by which ESP32 has been awaken from sleep
void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  switch(wakeup_reason)
  {
    case 1  : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case 2  : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case 3  : Serial.println("Wakeup caused by timer"); break;
    case 4  : Serial.println("Wakeup caused by touchpad"); break;
    case 5  : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.println("Wakeup was not caused by deep sleep"); break;
  }
}

ext1 External Wake-up Source

ESP32 can be woken up from deep sleep using multiple GPIO pins. Those pins can be one of RTC GPIOs 32-39.

As ext1 wake-up source uses RTC controller, it does’t need RTC peripherals and RTC memories to be powered ON. In this case internal pullup and pulldown resistors will not be available.

To use internal pullup or pulldown resistors, we need to request RTC peripherals to be kept on during sleep, and configure pullup/pulldown resistors using rtc_gpio_ functions, before entering sleep.

esp_sleep_enable_ext1_wakeup(BUTTON_PIN_MASK,LOGIC_LEVEL) function can be used to enable this wake-up source. The function takes two parameters. First one is a pin mask to let ESP32 know which all pins we are going to use

The second parameter can be one of the two logic functions can be used to trigger wake-up:

  • Wake up if any of the selected pins is HIGH ( ESP_EXT1_WAKEUP_ANY_HIGH )
  • Wake up if all the selected pins are LOW ( ESP_EXT1_WAKEUP_ALL_LOW )

The easiest way to understand this pin masking technique is to write it in a binary format.

ext1 External Wakeup Source Pin Bit Mask Representation in Binary
  • 0 represents masked pins
  • 1 represents pins that will be enabled as a wake-up source

The bit numbering is so simple and based on a normal GPIO numbering. The least significant bit(LSB) represents GPIO0 and the most significant bit(MSB) represents GPIO39.

As the the first pin available is GPIO32, the mask contains 32 times 0 on the right. And then for each enabled pin we write a 1.

If you don’t want to enable any GPIO for wake up you have to write a 0 at its corresponding place.

Once you are done, you need to convert it into HEX before using it as a parameter.

Below schematic shows how to connect multiple push buttons to ESP32 GPIOs that serves as a ext1 external wake-up source.

Connecting Multiple Buttons to ESP32 for ext1 External Wakeup Source

Here’s a code that demonstrates the most basic deep sleep example with a ext1 as wake-up source.

//Pushbuttons connected to GPIO32 & GPIO33
#define BUTTON_PIN_BITMASK 0x300000000

RTC_DATA_ATTR int bootCount = 0;

void setup(){
  Serial.begin(115200);
  delay(1000); 

  //Increment boot number and print it every reboot
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));

  //Print the wakeup reason for ESP32
  print_wakeup_reason();

  //Configure GPIO32 & GPIO33 as ext1 wake up source for HIGH logic level
  esp_sleep_enable_ext1_wakeup(BUTTON_PIN_BITMASK,ESP_EXT1_WAKEUP_ANY_HIGH);

  //Go to sleep now
  esp_deep_sleep_start();
}

void loop(){}

//Function that prints the reason by which ESP32 has been awaken from sleep
void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  switch(wakeup_reason)
  {
    case 1  : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case 2  : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case 3  : Serial.println("Wakeup caused by timer"); break;
    case 4  : Serial.println("Wakeup caused by touchpad"); break;
    case 5  : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.println("Wakeup was not caused by deep sleep"); break;
  }
}