ESP8266 (ESP-12F) deep sleep and light sleep with Arduino

02 Apr 2021 | all notes

Tutorials on the ESP8266’s sleep modes typically focus on the differences in chip activity states and power consumption between modes, but I found them lacking when it came to documenting their respective support/usage of external wake-up vs. timer-based sleep. This document summarizes my empirical findings/insights into less well-documented aspects of deep sleep and (so-called ‘forced’) light sleep, with code examples.

Contents

  1. Code continuation after wake-up
  2. Output pin states during sleep
  3. External (hardware) wake-up
  4. Timer-based wake-up
  5. Determining the reason for waking
  6. Time-keeping during/across sleep
  7. Sources & links

Code continuation after wake-up

The Wi-Fi modem is turned off in all sleep modes, so it is necessary to restart/reconnect the WiFi when waking up from any sleep mode.

// registering a on-wakeup callback
void fpm_wakup_cb_func1() {
  // ok to use blocking functions in the callback, but not
  // delay(), which appears to cause a reset
  Serial.println("Light sleep is over");
}

wifi_fpm_set_wakeup_cb(fpm_wakup_cb_func1);

Output pin states during sleep

External (hardware) wake-up

// minimal example for entering interrupt-based deep sleep
// no #include necessary

// enter deep sleep
ESP.deepSleep(0);

// enter deep sleep, specifying the desired state of the WiFi chip
// on wakeup (not usually necessary, for options see link above)
//ESP.deepSleep(0, RF_DEFAULT);

// ESP.deepSleep() waits for WiFi chip shutdown before going to sleep.
// use ESP.deepSleepInstant() to go to sleep without waiting for shutdown
//ESP.deepSleepInstant(0);
//ESP.deepSleepInstant(0, RF_DEFAULT);
// minimal example for entering interrupt-based light sleep

// include required for LIGHT_SLEEP_T, among others
#include "user_interface.h"

// enable light sleep
wifi_fpm_set_sleep_type(LIGHT_SLEEP_T);
wifi_fpm_open();

// register one or more wake-up interrupts
gpio_pin_wakeup_enable(D2, GPIO_PIN_INTR_HILEVEL);
//gpio_pin_wakeup_enable(D3, GPIO_PIN_INTR_LOLEVEL);
// ...

// function for clearing all previously set wake interrupts:
//gpio_pin_wakeup_disable();

// optionally, can register a callback function using
//wifi_fpm_set_wakeup_cb(function_name);

// actually enter light sleep:
// the special timeout value of 0xFFFFFFF triggers indefinite
// light sleep (until any of the GPIO interrupts above is triggered)
wifi_fpm_do_sleep(0xFFFFFFF);
// the CPU will only enter light sleep on the next idle cycle, which
// can be triggered by a short delay()
delay(10);

// code will continue here after the interrupt

Timer-based wake-up

Both sleep modes support timeout-based wakeup, and in both cases timeout-based wakeup and external wakeup are not mutually exclusive: sleep mode is ended by the specified timeout or an external wakeup signal, whichever occurs first. (Determining the cause of waking up is a different cup of tea, see further below.)

// minimal example for entering timer-based deep sleep
// no #include necessary

// ESP.deepSleep() requires a timeout argument, but unless D0/GPIO16 is
// physically connected to the RESET pin, the chip will actually remain
// in deep sleep indefinitely
uint64_t sleepTimeMicroSeconds = 10e6;

// could use up to this, but caution is warranted, see:
// https://github.com/esp8266/Arduino/tree/master/libraries/esp8266/examples/LowPowerDemo#test-10---deep-sleep-instant-wake-with-rf_disabled
//uint64_t sleepTimeMicroSeconds = ESP.deepSleepMax();

// enter deep sleep
ESP.deepSleep(sleepTimeMicroSeconds);

// enter deep sleep, specifying the desired state of the WiFi chip
// on wakeup (not usually necessary, for options see link above)
//ESP.deepSleep(sleepTimeMicroSeconds, RF_DEFAULT);

// ESP.deepSleep() waits for WiFi chip shutdown before going to sleep.
// use ESP.deepSleepInstant() to go to sleep without waiting for shutdown
//ESP.deepSleepInstant(sleepTimeMicroSeconds);
//ESP.deepSleepInstant(sleepTimeMicroSeconds, RF_DEFAULT);
// minimal example for entering timer-based light sleep

// include required for LIGHT_SLEEP_T, among others
#include "user_interface.h"

// for timer-based light sleep to work, the os timers need to be disconnected
extern os_timer_t *timer_list;
timer_list = nullptr;

// enable light sleep
wifi_fpm_set_sleep_type(LIGHT_SLEEP_T);
wifi_fpm_open();

void fpm_wakup_cb_func(void) {
  Serial.println("Light sleep is over, either because timeout or external interrupt");
  Serial.flush();
}

wifi_fpm_set_wakeup_cb(fpm_wakup_cb_func);

// optional: register one or more wake-up interrupts. the chip
// will wake from whichever (timeout or interrupt) occurs earlier
//gpio_pin_wakeup_enable(D2, GPIO_PIN_INTR_HILEVEL);

// sleep for 10 seconds
long sleepTimeMilliSeconds = 10e3;
// light sleep function requires microseconds
wifi_fpm_do_sleep(sleepTimeMilliSeconds * 1000);

// timed light sleep is only entered when the sleep command is
// followed by a delay() that is at least 1ms longer than the sleep
delay(sleepTimeMilliSeconds + 1);

// code will continue here after the time-out (or interrupt)

Determining the reason for waking

Time-keeping during/across sleep

As the CPU is turned off in all sleep modes, the value of millis() is not advanced while the chip is in sleep. Time information can only be based on the (somewhat inaccurate) RTC chip which stays active at all times (as it is also the basis of timer-based waking up).

// required include
#include "user_interface.h"

uint32_t RTCmillis() {
  // system_get_rtc_time() is in us (but very inaccurate anyway)
  return (system_get_rtc_time() * (system_rtc_clock_cali_proc() >> 12)) / 1000;
}

Power consumption during different modes (according to official documentation):

Table showing the differences between different sleep modes of the ESP8266in in terms of chip states and power consumption

Comments