Why Use Deep Sleep in the ESP32

When IoT devices are plugged in, power consumption is often not a primary engineering consideration. However, in remote, battery or solar-powered solutions the power consumption of your devices is often the first requirement.

 

In this post, we provide code to put your ESP32 into Deep Sleep. The ESP32 from Espressif is extremely popular in IoT applications, and by leveraging its first-class support for Deep Sleep (as well as other sleep modes) you can:
  • Lower maintenance costs by extending time between battery replacements
  • Lower deployment costs by using a small solar panel, mounting bracket, battery and enclosure

What is Deep Sleep?

The ESP32 Datasheet explains the different sleep modes available, and provides a great table explaining the power consumption of those different modes. The different sleep modes have different functions. RTC below stands for “Real Time Clock” and ULP for “Ultra Low Power.”

Active mode: The chip radio is powered on. The chip can receive, transmit, or listen.
Modem-­sleep mode: The CPU is operational and the clock is configurable. The Wi-Fi/Bluetooth baseband and radio are disabled.
Light-­sleep mode: The CPU is paused. The RTC memory and RTC peripherals, as well as the ULP co-processor are running. Any wake-up events (MAC host, RTC timer, or external interrupts) will wake up the chip.
Deep-­sleep mode: Only the RTC memory and RTC peripherals are powered on. Wi-Fi and Bluetooth connection data are stored in the RTC memory. The ULP co-processor is functional.
Hibernation mode: The internal 8-MHz oscillator and ULP co-processor are disabled. The RTC recovery memory is powered down. Only one RTC timer on the slow clock and certain RTC GPIOs are active. The RTC timer or the RTC GPIOs can wake up the chip from the Hibernation mode.

Here’s a chart provided by Espressif that covers the power consumption of the ESP32 chipset in different sleep modes: 
Now that we know the difference between the sleep modes available to the ESP32, let’s take a look at how to put an ESP32 in Deep Sleep.

 

How to Deep Sleep the ESP32

For this example, we’ll be using the ESP-IDF. The ESP-IDF is the Integrated Development Environment provided by Espressif (the makers of the ESP32) for development with the ESP32. If you need guidance on getting the ESP-IDF set up and working with your ESP32 Device, you can check out this guide.

 

Getting the ESP32 into Deep Sleep mode is relatively easy. Here’s the code that puts the device to sleep:

[code lang=”arduino”]#include "esp_sleep.h"
const int wakeup_time_sec = 900; // 900 seconds is 15 minutes
esp_sleep_enable_timer_wakeup(wakeup_time_sec * 1000000);
esp_deep_sleep_start();[/code]
 

That’s it! These three lines, along with the esp_sleep.h header file, and ta-da, your ESP32 is sleeping deeply! So, after 900 seconds, or 15 minutes, the board restarts and the main() function is called again, restarting the process as if the board was just powered on.

In the current configuration using the breakout board, the system went from using 40mA in standby mode to 6.4mA in Deep Sleep. Given the pulses to send data only last 0.5s, this system change drive the power consumption per day down by 84%. In a solar powered example, you could switch from a 3 Watt solar panel down to a 0.6 or 1 Watt solar panel.

We would expect to go far lower in power consumption in a production application where we could shut down the voltage regulator, UART controller, etc.

No Deep Sleep Deep Sleep
Standard Current 40mA 6.4mA
Pulse Current 100mA 100mA
Pulse Duration 500ms 500ms
Duty Cycle 0.0056% 0.0056%
Watt Hours Per Day 3.6Wh 0.57Wh

Send IoT Data to an MQTT Broker

In solar-powered use cases, it is often important to know the current status of your battery. If the battery reaches a low capacity threshold, you can reduce the measurement and transmission duty cycle or identify potential problems in the field. Customers have used battery measurements to have end users re-orient solar panels to the South and increase power production.

Connecting and maintaining a WiFi or cellular connection, an MQTT connection, and sending messages are power-intensive processes. By having a device make its connections, send its data, and then go to sleep, you can greatly reduce the total power consumption for your IoT solution and extend battery life.

So, for this example, we’ll check the voltage of a battery and send it to an MQTT Broker. In this example, we’ll be using the MQTT Broker provided by the Losant IoT Platform

Here’s what you’ll need:

The Huzzah32 has both a JST connector for LiPo batteries, and a Mirco-USB port. This means that are two ways to power the board. When the USB is powered, the board will automatically switch over to USB to power the board, as well as start charging the battery. You can read more from Adafruit on this automatic power switching. This means we can plug a solar panel from Voltaic Systems into the Micro-USB port that will both power the board and charge the battery. So, when the solar panel isn’t receiving any sunlight, the board is still powered.
Here are the steps we’ll be following for this example: 
  1. Connect to WiFi & Losant
  2. Read battery voltage
  3. Report to the Losant MQTT Broker
  4. Go to sleep for 15 minutes
You can grab our example code in the Github Repository, but there are 3 specific parts of this code that you should pay special attention. 
First, the very top:

[code lang=”arduino”]
#define LOSANT_DEVICE_ID ""
#define LOSANT_ACCESS_KEY ""
#define LOSANT_ACCESS_SECRET ""

#define EXAMPLE_ESP_WIFI_SSID ""
#define EXAMPLE_ESP_WIFI_PASS ""
[/code]
These are #defines that will be used throughout the code. The first three are your Losant Device credentials. You’ll need to create a device and an access key

The next two lines are for your WiFi Connection. You’ll input your WiFi SSID and password so that the device can connect to the internet.

[code lang=”arduino”]
void read_bat_and_publish(void *client)
{
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_GPIO35_CHANNEL, ADC_ATTEN_11db);

adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t));
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_11db, ADC_WIDTH_BIT_12, DEFAULT_VREF, adc_chars);
print_char_val_type(val_type);

//build state topic
char state_topic[128];
sprintf(state_topic, "losant/%s/state", LOSANT_DEVICE_ID);

int adc_reading = adc1_get_raw(ADC1_GPIO35_CHANNEL);
ESP_LOGI(TAG, "Raw: %d", adc_reading);

// This board has voltage divider, so need to multiply by 2.
uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, adc_chars) * 2;
ESP_LOGI(TAG, "Voltage: %d", voltage);

cJSON *payload = cJSON_CreateObject();
cJSON *data = cJSON_AddObjectToObject(payload, "data");
cJSON_AddNumberToObject(data, "battery_voltage", voltage);

esp_mqtt_client_publish(client, state_topic, cJSON_Print(payload), 0, 0, 0);
}[/code]

This function reads the battery voltage, multiples it by two (since the Huzzah32 has a voltage divider built in), and publishes the value to Losant via the MQTT state topic.  This all happens within the app_main() function when the device is powered on or wakes up from sleep. 

 
[code lang=”arduino”]
void app_main(void)
{
ESP_LOGI(TAG, "[APP] Startup..");
ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size());
ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());

esp_log_level_set("*", ESP_LOG_INFO);
esp_log_level_set("MQTT_CLIENT", ESP_LOG_VERBOSE);
esp_log_level_set("MQTT_EXAMPLE", ESP_LOG_VERBOSE);
esp_log_level_set("TRANSPORT_TCP", ESP_LOG_VERBOSE);
esp_log_level_set("TRANSPORT_SSL", ESP_LOG_VERBOSE);
esp_log_level_set("TRANSPORT", ESP_LOG_VERBOSE);
esp_log_level_set("OUTBOX", ESP_LOG_VERBOSE);

ESP_ERROR_CHECK(nvs_flash_init());

// start wifi
ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
wifi_init_sta();

esp_mqtt_client_handle_t client = mqtt_app_start();

if (client) {
read_bat_and_publish(client);
vTaskDelay(pdMS_TO_TICKS(5000)); // wait 5 seconds
// disconnect from MQTT client
esp_mqtt_client_disconnect(client);
vTaskDelay(pdMS_TO_TICKS(5000)); // wait 5 seconds
}

const int wakeup_time_sec = 900; // 900 seconds = 15 minutes
ESP_LOGI(TAG, "Enabling timer wakeup, %ds\n", wakeup_time_sec);
esp_sleep_enable_timer_wakeup(wakeup_time_sec * 1000000);

ESP_LOGI(TAG, "Entering deep sleep\n");
esp_deep_sleep_start();
}[/code]

In this main function, we are outputting some information about the device we are using, and then setting the log levels.

Next we start WiFi, then start the MQTT connection, and then call the read_bat_and_publish function to read the battery voltage and publishing the value to an MQTT topic.

Finally we set the length for how long we want the Huzzah32 to sleep, and then go to sleep with esp_deep_sleep_start()