ESP32 deep Sleep (RTC) GPIO Output

In the course of developing an ESP32 battery based application I was stumped along with others, on how to maintain a GPIO output in a known state while in deep sleep. My plan was to hold an external peripheral in a reset state to minimize it’s power. The reset signal was an active low signal and the external peripheral had a 10K pullup resistor on the signal. I program the ESP32 in the Arduino environment so I had to find a solution that supported this IDE. I performed a bunch of searches and finally found a working example by Xabi Alaez.

The ESP32 has several “sleep” modes that are listed below. A great article about ESP32 sleep modes is found on the Last Minute Engineers web page.

  • Active keeps everything running including WiFi, Bluetooth, and processing core at all times. This mode consumes more than 240mA with some power spikes as high as 790mA.
  • Modem Sleep is Active minus the WiFi, Bluetooth, and radio. Dependent on the clock speed, which is programmable, consumes between 3mA (low clock speed) to 20mA (high clock speed).
  • Light Sleep the CPU(s) clock is gated while the RTC and ULP co-processor are active. This sleep mode has full RAM retention. Once entering Light Sleep a wake-up source is needed to resume processing. This power consumption is less than 1mA.
  • Deep Sleep the CPU(s), most RAM, and all digital peripherals are powered off while the ULP co-processor and RTC components remain operational. The power consumption is about 0.15mA with the ULP co-processor running.
  • Hibernation only has one RTC timer running and a few RTC GPIOs needed to wake up the chip from hibernation mode. Power consumption is about 2.5uA.

I was most interested in Deep Sleep and Hibernation modes. It turns out you control the power domains before entering Deep Sleep to create Hibernation mode (no API call for hibernation sleep). This also means you can be selective in which power domains are enabled to create a “custom” sleep mode that has less current consumption than Deep Sleep, but not as good as Hibernation.

The ESP power domains are controlled by calling esp_deep_sleep_pd_config() with the power domain and options that include ESP_PD_OPTION_OFF, ESP_PD_OPTION_ON, and ESP_PD_OPTION_AUTO. The auto option powers down the power domain unless it is needed by one of the wakeup options. The different ESP power domains are:

  • ESP_PD_DOMAIN_RTC_PERIPH for RTC IO, sensors and ULP co-processor.
  • ESP_PD_DOMAIN_RTC_SLOW_MEM for RTC slow memory.
  • ESP_PD_DOMAIN_RTC_FAST_MEM for RTC fast memory.
  • ESP_PD_DOMAIN_XTAL for XTAL oscillator.
  • ESP_PD_DOMAIN_RTC8M for internal 8M oscillator.
  • ESP_PD_DOMAIN_VDDSDIO for VDD_SDIO.

During Deep Sleep the only GPIO pins that are available are the RTC IO (RTC_GPIO), which are part of the ESP_PD_DOMAIN_RTC_PERIPH power domain. The ULP co-processor is also active in this power domain. The table below shows the mapping of GPIO pins to analog and RTC functions available through the RTC Mux. The table information is from the Espressif Systems ESP32 Technical Reference Manual Version 4.2, section 5.11, table 21.

GPIO NumRTC GPIO NumAnalog
1
Analog
2
Analog
3
RTC
1
RTC
2
360ADC_HADC1_CH0RTC_GPIO0
371ADC_HADC1_CH1RTC_GPIO1
382ADC_HADC1_CH2RTC_GPIO2
393ADC_HADC1_CH3RTC_GPIO3
344ADC1_CH6RTC_GPIO4I2C_SCL
355ADC1_CH7RTC_GPIO5I2C_SDA
256DAC_1ADC2_CH8RTC_GPIO6I2C_SCL
267DAC_2ADC2_CH9RTC_GPIO7I2C_SDA
338XTAL_32K_NADC1_CH5TOUCH8RTC_GPIO8
329XTAL_32K_PADC1_CH4TOUCH9RTC_GPIO9
410ADC2_CH0TOUCH0RTC_GPIO10
011ADC2_CH1TOUCH1RTC_GPIO11
212ADC2_CH2TOUCH2RTC_GPIO12
1513ADC2_CH3TOUCH3RTC_GPIO13
1314ADC2_CH4TOUCH4RTC_GPIO14
1215ADC2_CH5TOUCH5RTC_GPIO15
1416ADC2_CH6TOUCH6RTC_GPIO16
2717ADC2_CH7TOUCH7RTC_GPIO17
GPIO Pin Function via RTC Mux Selection

To setup the RTC Mux and control the RTC GPIOs in the Arduino environment, calls are made to rtc_gpio_* functions. These functions allow you to initialize the pin as RTC GPIO and set it’s direction and level. My test code adapted from Xabi Alaez’s is shown below.

#include "driver/rtc_io.h"
gpio_num_t RST_PIN = GPIO_NUM_14;

void setup() {
  rtc_gpio_init(RST_PIN);
  rtc_gpio_set_direction(RST_PIN,RTC_GPIO_MODE_OUTPUT_ONLY);
  // toggle reset pin
  rtc_gpio_set_level(RST_PIN,1); //GPIO high
  rtc_gpio_set_level(RST_PIN,0); //GPIO LOW
  rtc_gpio_set_level(RST_PIN,1); //GPIO high
  delay(5000);  // wait
  rtc_gpio_set_level(RST_PIN,0); //GPIO hold low
  esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
  esp_sleep_enable_timer_wakeup(60*1000*1000);  // 60 seconds
  esp_deep_sleep_start();
}

void loop() {
  // none
}

The first step calls rtc_gpio_init(gpio_num_t gpio_num) with the GPIO pin number to the initialize the pad for an analog/RTC function. Next, rtc_gpio_set_direction(gpio_num_t gpio_num, rtc_gpio_mode_t mode) is used to set the pin as output. Finally rtc_gpio_set_level(gpio_num_t gpio_num, uint32_t level level) is used to set the pin output as either HIGH (1) or LOW (0).

After the pin setup and toggled, a delay of 5 seconds is introduced before setting the pin to the value needed during Deep Sleep. Note that the ESP_PD_DOMAIN_RTC_PERIPH is enabled prior to setting the wake up timer and entering Deep Sleep. After waking from Deep Sleep, setup() is repeated again.

Although this approach worked well, I was trying to save 5uA on an external peripheral. By enabling the RTC peripheral that includes the ULP co-processor, I added about 100uA+ to the ESP power. So in the end I didn’t use this approach.

The key to setting an ESP32 output to a known state during Deep Sleep is to select an RTC_GPIO, initialize and operate the pin using RTC_GPIO_* calls, and enabling the RTC peripheral power domain prior to entering Deep Sleep.

Leave a Reply