diff --git a/cores/esp8266/Esp.cpp b/cores/esp8266/Esp.cpp index 20da390008..2a2d655d4b 100644 --- a/cores/esp8266/Esp.cpp +++ b/cores/esp8266/Esp.cpp @@ -115,16 +115,16 @@ void EspClass::wdtFeed(void) system_soft_wdt_feed(); } -void EspClass::deepSleep(uint64_t time_us, WakeMode mode) +void EspClass::deepSleep(uint64_t time_us) { - system_deep_sleep_set_option(static_cast(mode)); + system_deep_sleep_set_option(__get_rf_mode()); system_deep_sleep(time_us); esp_suspend(); } -void EspClass::deepSleepInstant(uint64_t time_us, WakeMode mode) +void EspClass::deepSleepInstant(uint64_t time_us) { - system_deep_sleep_set_option(static_cast(mode)); + system_deep_sleep_set_option(__get_rf_mode()); system_deep_sleep_instant(time_us); esp_suspend(); } @@ -138,6 +138,174 @@ uint64_t EspClass::deepSleepMax() } +extern os_timer_t* timer_list; +namespace { + sleep_type_t saved_sleep_type = NONE_SLEEP_T; + os_timer_t* saved_timer_list = nullptr; + fpm_wakeup_cb saved_wakeupCb = nullptr; +} + +bool EspClass::forcedModemSleep(uint32_t duration_us, fpm_wakeup_cb wakeupCb) +{ + // Setting duration to 0xFFFFFFF, it disconnects the RTC timer + if (!duration_us || duration_us > 0xFFFFFFF) { + duration_us = 0xFFFFFFF; + } + wifi_fpm_close(); + saved_sleep_type = wifi_fpm_get_sleep_type(); + wifi_set_opmode(NULL_MODE); + wifi_fpm_set_sleep_type(MODEM_SLEEP_T); + wifi_fpm_open(); + saved_wakeupCb = nullptr; + if (wakeupCb) wifi_fpm_set_wakeup_cb(wakeupCb); + auto ret_do_sleep = wifi_fpm_do_sleep(duration_us); + if (ret_do_sleep != 0) + { +#ifdef DEBUG_SERIAL + DEBUG_SERIAL.printf("core: error %d with wifi_fpm_do_sleep: (-1=sleep status error, -2=force sleep not enabled)\n", ret_do_sleep); +#endif + return false; + } + // SDK turns on forced modem sleep in idle task + esp_delay(10); + return true; +} + +void EspClass::forcedModemSleepOff() +{ + const sleep_type_t sleepType = wifi_fpm_get_sleep_type(); + if (sleepType != NONE_SLEEP_T) { + if (sleepType == MODEM_SLEEP_T) wifi_fpm_do_wakeup(); + wifi_fpm_close(); + } + wifi_fpm_set_sleep_type(saved_sleep_type); + saved_sleep_type = NONE_SLEEP_T; +} + +#ifdef DEBUG_SERIAL +namespace { + void walk_timer_list() { + os_timer_t* timer_root; + { + esp8266::InterruptLock lock; + auto src = timer_list; + auto dest = src ? new os_timer_t : nullptr; + timer_root = dest; + while (dest) { + *dest = *src; + src = src->timer_next; + dest->timer_next = src ? new os_timer_t(*timer_list) : nullptr; + dest = dest->timer_next; + } + } + DEBUG_SERIAL.printf("=============\n"); + for (os_timer_t* timer_node = timer_root; nullptr != timer_node; timer_node = timer_node->timer_next) { + DEBUG_SERIAL.printf("timer_address = %p\n", timer_node); + DEBUG_SERIAL.printf("timer_expire = %u\n", timer_node->timer_expire); + DEBUG_SERIAL.printf("timer_period = %u\n", timer_node->timer_period); + DEBUG_SERIAL.printf("timer_func = %p\n", timer_node->timer_func); + DEBUG_SERIAL.printf("timer_next = %p\n", timer_node->timer_next); + if (timer_node->timer_next) DEBUG_SERIAL.printf("=============\n"); + } + DEBUG_SERIAL.printf("=============\n"); + DEBUG_SERIAL.flush(); + while (timer_root) { + auto next = timer_root->timer_next; + delete timer_root; + timer_root = next; + } + } +} +#endif + +bool EspClass::forcedLightSleepBegin(uint32_t duration_us, fpm_wakeup_cb wakeupCb) +{ + // Setting duration to 0xFFFFFFF, it disconnects the RTC timer + if (!duration_us || duration_us > 0xFFFFFFF) { + duration_us = 0xFFFFFFF; + } + wifi_fpm_close(); + saved_sleep_type = wifi_fpm_get_sleep_type(); + wifi_set_opmode(NULL_MODE); + wifi_fpm_set_sleep_type(LIGHT_SLEEP_T); + wifi_fpm_open(); + saved_wakeupCb = wakeupCb; + wifi_fpm_set_wakeup_cb([]() { + if (saved_wakeupCb) { + saved_wakeupCb(); + saved_wakeupCb = nullptr; + } + esp_schedule(); + }); +#ifdef DEBUG_SERIAL + walk_timer_list(); +#endif + { + esp8266::InterruptLock lock; + saved_timer_list = timer_list; + timer_list = nullptr; + } + return wifi_fpm_do_sleep(duration_us) == 0; +} + +void EspClass::forcedLightSleepEnd(bool cancel) +{ + if (!cancel) { + // SDK turns on forced light sleep in idle task + esp_suspend(); + } +#ifdef DEBUG_SERIAL + walk_timer_list(); +#endif + { + esp8266::InterruptLock lock; + timer_list = saved_timer_list; + } + saved_wakeupCb = nullptr; + wifi_fpm_close(); + wifi_fpm_set_sleep_type(saved_sleep_type); + saved_sleep_type = NONE_SLEEP_T; + if (cancel) { + // let the SDK catch up in idle task + esp_delay(10); + } +} + +void EspClass::autoModemSleep() { + wifi_fpm_close(); + saved_sleep_type = wifi_get_sleep_type(); + wifi_set_sleep_type(MODEM_SLEEP_T); +} + +void EspClass::autoLightSleep() { + wifi_fpm_close(); + saved_sleep_type = wifi_get_sleep_type(); + wifi_set_sleep_type(LIGHT_SLEEP_T); +} + +void EspClass::autoSleepOff() { + wifi_set_sleep_type(saved_sleep_type); + saved_sleep_type = NONE_SLEEP_T; +} + +void EspClass::neverSleep() { + const auto active_sleep_type = wifi_get_sleep_type(); + if (NONE_SLEEP_T == active_sleep_type) { + return; + } + wifi_fpm_close(); + saved_sleep_type = active_sleep_type; + wifi_set_sleep_type(NONE_SLEEP_T); +} + +void EspClass::neverSleepOff() { + if (NONE_SLEEP_T == saved_sleep_type) { + return; + } + wifi_set_sleep_type(saved_sleep_type); + saved_sleep_type = NONE_SLEEP_T; +} + /* Layout of RTC Memory is as follows: Ref: Espressif doc 2C-ESP8266_Non_OS_SDK_API_Reference, section 3.3.23 (system_rtc_mem_write) @@ -531,7 +699,7 @@ bool EspClass::eraseConfig(void) { return true; } -uint8_t *EspClass::random(uint8_t *resultArray, const size_t outputSizeBytes) +uint8_t* EspClass::random(uint8_t* resultArray, const size_t outputSizeBytes) { /** * The ESP32 Technical Reference Manual v4.1 chapter 24 has the following to say about random number generation (no information found for ESP8266): @@ -787,7 +955,7 @@ static bool isAlignedPointer(const uint8_t *ptr) { } -size_t EspClass::flashWriteUnalignedMemory(uint32_t address, const uint8_t *data, size_t size) { +size_t EspClass::flashWriteUnalignedMemory(uint32_t address, const uint8_t* data, size_t size) { auto flash_write = [](uint32_t address, uint8_t *data, size_t size) { return spi_flash_write(address, reinterpret_cast(data), size) == SPI_FLASH_RESULT_OK; }; @@ -857,7 +1025,7 @@ size_t EspClass::flashWriteUnalignedMemory(uint32_t address, const uint8_t *data return written; } -bool EspClass::flashWrite(uint32_t address, const uint32_t *data, size_t size) { +bool EspClass::flashWrite(uint32_t address, const uint32_t* data, size_t size) { SpiFlashOpResult result; #if PUYA_SUPPORT if (getFlashChipVendorId() == SPI_FLASH_VENDOR_PUYA) { @@ -871,7 +1039,7 @@ bool EspClass::flashWrite(uint32_t address, const uint32_t *data, size_t size) { return result == SPI_FLASH_RESULT_OK; } -bool EspClass::flashWrite(uint32_t address, const uint8_t *data, size_t size) { +bool EspClass::flashWrite(uint32_t address, const uint8_t* data, size_t size) { if (data && size) { if (!isAlignedAddress(address) || !isAlignedPointer(data) diff --git a/cores/esp8266/Esp.h b/cores/esp8266/Esp.h index 9f97175c29..4d50f6eaa1 100644 --- a/cores/esp8266/Esp.h +++ b/cores/esp8266/Esp.h @@ -57,7 +57,6 @@ enum RFMode { RF_DISABLED = 4 // disable RF after deep-sleep wake up, just like modem sleep, there will be the smallest current. }; -#define RF_MODE(mode) int __get_rf_mode() { return mode; } #define RF_PRE_INIT() void __run_user_rf_pre_init() // compatibility definitions @@ -94,10 +93,35 @@ class EspClass { static void wdtDisable(); static void wdtFeed(); - static void deepSleep(uint64_t time_us, RFMode mode = RF_DEFAULT); - static void deepSleepInstant(uint64_t time_us, RFMode mode = RF_DEFAULT); + static void deepSleep(uint64_t time_us); + static void deepSleepInstant(uint64_t time_us); static uint64_t deepSleepMax(); + static bool forcedModemSleep(uint32_t duration_us = 0, void (*wakeupCb)() = nullptr); + /// The prior sleep type is restored, but only as automatic. + /// If any forced sleep mode was effective before forcedModemSleep, + /// it would have to be restored explicitly. + static void forcedModemSleepOff(); + + static bool forcedLightSleepBegin(uint32_t duration_us = 0, void (*wakeupCb)() = nullptr); + /// The prior sleep type is restored, but only as automatic. + /// If any forced sleep mode was effective before forcedLightSleepBegin, + /// it would have to be restored explicitly. + static void forcedLightSleepEnd(bool cancel = false); + + static void autoModemSleep(); + static void autoLightSleep(); + /// The prior sleep type is restored, but only as automatic. + /// If any forced sleep mode was effective before auto{Modem,Light}Sleep, + /// it would have to be restored explicitly. + static void autoSleepOff(); + + static void neverSleep(); + /// Any prior sleep type is restored, but only as automatic. + /// If any forced sleep mode was effective before neverSleep, + /// it would have to be restored explicitly. + static void neverSleepOff(); + static bool rtcUserMemoryRead(uint32_t offset, uint32_t *data, size_t size); static bool rtcUserMemoryWrite(uint32_t offset, uint32_t *data, size_t size); @@ -269,4 +293,34 @@ class EspClass { extern EspClass ESP; +/// RAII helper token class for forcedLightSleepBegin()/End(). +/// Cast to bool to check that forced light sleep can commence. +/// The forced light sleep is entered when the token goes out of scope. +/// Call cancel() to prevent sleeping. +class ESPForcedLightSleepToken { +private: + ESPForcedLightSleepToken() = delete; + ESPForcedLightSleepToken(const ESPForcedLightSleepToken&) = delete; + ESPForcedLightSleepToken& operator=(const ESPForcedLightSleepToken&) = delete; + + bool isArmed = false; + +public: + ESPForcedLightSleepToken(uint32_t duration_us, void (*wakeupCb)()) { + isArmed = ESP.forcedLightSleepBegin(10 * 1000 * 1000, wakeupCb); + } + ~ESPForcedLightSleepToken() { + if (isArmed) ESP.forcedLightSleepEnd(false); + } + operator bool() { + return isArmed; + } + void cancel() { + if (isArmed) { + ESP.forcedLightSleepEnd(true); + isArmed = false; + } + } +}; + #endif //ESP_H diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index 91f2a9e6ae..5599f6ce08 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -109,7 +109,7 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) { _md5 = MD5Builder(); #ifndef HOST_MOCK - wifi_set_sleep_type(NONE_SLEEP_T); + ESP.neverSleep(); #endif //address where we will start writing the update diff --git a/cores/esp8266/core_esp8266_main.cpp b/cores/esp8266/core_esp8266_main.cpp index d0309cc71f..580b9c9e21 100644 --- a/cores/esp8266/core_esp8266_main.cpp +++ b/cores/esp8266/core_esp8266_main.cpp @@ -408,10 +408,7 @@ extern "C" void __disableWiFiAtBootTime (void) { // Starting from arduino core v3: wifi is disabled at boot time // WiFi.begin() or WiFi.softAP() will wake WiFi up - wifi_set_opmode_current(0/*WIFI_OFF*/); - wifi_fpm_set_sleep_type(MODEM_SLEEP_T); - wifi_fpm_open(); - wifi_fpm_do_sleep(0xFFFFFFF); + ESP.forcedModemSleep(); } #if FLASH_MAP_SUPPORT diff --git a/cores/esp8266/core_esp8266_phy.cpp b/cores/esp8266/core_esp8266_phy.cpp index 4657bb0081..7b0b77d4ee 100644 --- a/cores/esp8266/core_esp8266_phy.cpp +++ b/cores/esp8266/core_esp8266_phy.cpp @@ -27,6 +27,7 @@ #include "ets_sys.h" #include "spi_flash.h" #include "user_interface.h" +#include "coredecls.h" extern "C" { @@ -288,20 +289,10 @@ static const uint8_t ICACHE_FLASH_ATTR phy_init_data[128] = /*[114] =*/ 1 }; - -// These functions will be overridden from C++ code. -// Unfortunately, we can't use extern "C" because Arduino preprocessor -// doesn't generate forward declarations for extern "C" functions correctly, -// so we use mangled names here. -#define __get_adc_mode _Z14__get_adc_modev -#define __get_rf_mode _Z13__get_rf_modev -#define __run_user_rf_pre_init _Z22__run_user_rf_pre_initv - static bool spoof_init_data = false; extern int __real_spi_flash_read(uint32_t addr, uint32_t* dst, size_t size); extern int IRAM_ATTR __wrap_spi_flash_read(uint32_t addr, uint32_t* dst, size_t size); -extern int __get_adc_mode(); /* Verified that the wide filtering of all 128 byte flash reads during @@ -328,19 +319,43 @@ extern int IRAM_ATTR __wrap_spi_flash_read(uint32_t addr, uint32_t* dst, size_t return 0; } -extern int __get_rf_mode(void) __attribute__((weak)); +extern int __get_rf_disable_mode(void) __attribute__((noinline, weak)); +extern int __get_rf_disable_mode(void) +{ + // Starting from arduino core v3: wifi is disabled at boot time + // mode == 4: Disable RF after deep-sleep wake up, just like modem sleep; this has the least current + // consumption; the device is not able to transmit or receive data after wake up. + return 4; +} + +extern int __get_rf_powerup_disable_mode(void) __attribute__((noinline, weak)); +extern int __get_rf_powerup_disable_mode(void) +{ + // Starting from arduino core v3: wifi is disabled at boot time + // mode == 2: RF initialization only calibrates VDD33 which will take about 2 ms; + // this has the least current consumption. + return 2; +} + +extern int __get_rf_mode(void) __attribute__((noinline, weak)); extern int __get_rf_mode(void) { - return -1; // mode not set + return __get_rf_disable_mode(); +} + +extern int __get_rf_powerup_mode(void) __attribute__((noinline, weak)); +extern int __get_rf_powerup_mode(void) +{ + return __get_rf_powerup_disable_mode(); } -extern int __get_adc_mode(void) __attribute__((weak)); +extern int __get_adc_mode(void) __attribute__((noinline, weak)); extern int __get_adc_mode(void) { return 33; // default ADC mode } -extern void __run_user_rf_pre_init(void) __attribute__((weak)); +extern void __run_user_rf_pre_init(void) __attribute__((noinline, weak)); extern void __run_user_rf_pre_init(void) { return; // default do nothing @@ -370,6 +385,10 @@ void user_rf_pre_init() if (rf_mode >= 0) { system_phy_set_rfoption(rf_mode); } + rf_mode = __get_rf_powerup_mode(); + if (rf_mode >= 0) { + system_phy_set_powerup_option(rf_mode); + } __run_user_rf_pre_init(); } diff --git a/cores/esp8266/coredecls.h b/cores/esp8266/coredecls.h index 73af6d8cf1..967edd1bc3 100644 --- a/cores/esp8266/coredecls.h +++ b/cores/esp8266/coredecls.h @@ -25,6 +25,10 @@ void disable_extra4k_at_link_time (void) __attribute__((noinline)); void enable_wifi_enterprise_patch(void) __attribute__((noinline)); void __disableWiFiAtBootTime (void) __attribute__((noinline)); void __real_system_restart_local() __attribute__((noreturn)); +int __get_adc_mode(); +int __get_rf_mode(); +int __get_rf_powerup_mode(); +void __run_user_rf_pre_init(); uint32_t sqrt32(uint32_t n); diff --git a/doc/libraries.rst b/doc/libraries.rst index 625935554b..9c7358087e 100644 --- a/doc/libraries.rst +++ b/doc/libraries.rst @@ -71,12 +71,30 @@ An ESP8266 port of SoftwareSerial library done by Peter Lerup (@plerup) supports ESP-specific APIs ----------------- -Some ESP-specific APIs related to deep sleep, RTC and flash memories are available in the ``ESP`` object. +Some ESP-specific APIs related to the deep, modem, and light sleep modes, RTC and flash memory are available in the ``ESP`` object. ``ESP.deepSleep(microseconds, mode)`` will put the chip into deep sleep. ``mode`` is one of ``WAKE_RF_DEFAULT``, ``WAKE_RFCAL``, ``WAKE_NO_RFCAL``, ``WAKE_RF_DISABLED``. (GPIO16 needs to be tied to RST to wake from deepSleep.) The chip can sleep for at most ``ESP.deepSleepMax()`` microseconds. If you implement deep sleep with ``WAKE_RF_DISABLED`` and require WiFi functionality on wake up, you will need to implement an additional ``WAKE_RF_DEFAULT`` before WiFi functionality is available. ``ESP.deepSleepInstant(microseconds, mode)`` works similarly to ``ESP.deepSleep`` but sleeps instantly without waiting for WiFi to shutdown. +``ESP.forcedModemSleep(microseconds, callback)`` immediately puts the chip into forced ``MODEM_SLEEP``. A microseconds duration after which the sleep mode returns to the automatic sleep mode that was effective before this call can be given, a value of 0 or 0xFFFFFFF turns off that timeout. The optional callback function will be invoked when the forced modem sleep ends. + +``ESP.forcedModemSleepOff()`` immediately returns the chip to the automatic sleep mode in effect before the preceeding call to ``ESP.forcedModemSleep``. + +``ESP.forcedLightSleepBegin(microseconds, callback)`` works in tandem with ``ESP.forcedLightSleepEnd(cancel)`` to put the chip into forced ``LIGHT_SLEEP``. A microseconds duration after which the sleep mode returns can be given, a value of 0 or 0xFFFFFFF turns off that timeout. The optional callback function will be invoked when the forced light sleep ends. Forced light sleep halts the CPU, in addition to the timeout, it can be awakened via GPIO input. Between the calls to ``ESP.forcedLightSleepBegin`` and ``ESP.forcedLightSleepEnd`` any GPIOs except GPIO16 to use for wakeup can be set up. Care must be taken not to allow the chip to enter the idle task before the call to ``ESP.forcedLightSleepEnd(cancel)``, so for instance no direct or indirect calls to ``delay()`` are possible. Otherwise the forced light sleep may engange too early, breaking the required logic of the tandem calls. + +``ESP.forcedLightSleepEnd(cancel)`` causes the chip to enter the forced light sleep mode that was prepared by the preceeding ``ESP.forcedLightSleepBegin``. The optional cancel argument, if true, prevents the sleep mode transition from occuring. This can be used, for instance, to return immediately if setting up the level-triggered GPIO interrupts for wakeup fails. Otherwise, it returns after waking up from forced light sleep. On return, the automatic sleep mode that was effective before the call to ``ESP.forcedLightSleepBegin`` is activated. + +``ESP.autoModemSleep()`` immediately puts the chip into automatic ``MODEM_SLEEP``. + +``ESP.autoLightSleep()`` immediately puts the chip into automatic ``LIGHT_SLEEP``. + +``ESP.autoSleepOff()`` returns the chip to the automatic sleep mode that was effective before the preceding call to either ``ESP.autoModemSleep`` or ``ESP.autoLightSleep``. + +``ESP.neverSleep()`` immediately puts the chip into ``NONE_SLEEP`` mode. + +``ESP.neverSleepOff()`` returns the chip to any automatic sleep mode that was effective before the preceding call to ``ESP.neverSleep``. + ``ESP.rtcUserMemoryWrite(offset, &data, sizeof(data))`` and ``ESP.rtcUserMemoryRead(offset, &data, sizeof(data))`` allow data to be stored in and retrieved from the RTC user memory of the chip respectively. ``offset`` is measured in blocks of 4 bytes and can range from 0 to 127 blocks (total size of RTC memory is 512 bytes). ``data`` should be 4-byte aligned. The stored data can be retained between deep sleep cycles, but might be lost after power cycling the chip. Data stored in the first 32 blocks will be lost after performing an OTA update, because they are used by the Core internals. ``ESP.restart()`` restarts the CPU. diff --git a/libraries/ESP8266WiFi/examples/WiFiShutdown/WiFiShutdown.ino b/libraries/ESP8266WiFi/examples/WiFiShutdown/WiFiShutdown.ino index 69502de862..213453371c 100644 --- a/libraries/ESP8266WiFi/examples/WiFiShutdown/WiFiShutdown.ino +++ b/libraries/ESP8266WiFi/examples/WiFiShutdown/WiFiShutdown.ino @@ -47,7 +47,7 @@ void setup() { WiFi.mode(WIFI_OFF); Serial.println("Cannot connect!"); Serial.flush(); - ESP.deepSleep(10e6, RF_DISABLED); + ESP.deepSleep(10e6); return; } } @@ -69,7 +69,7 @@ void setup() { Serial.println("Done."); Serial.flush(); - ESP.deepSleep(10e6, RF_DISABLED); + ESP.deepSleep(10e6); } void loop() { diff --git a/libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.cpp b/libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.cpp index f64e6d3ac5..7d3d2350e3 100644 --- a/libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.cpp +++ b/libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.cpp @@ -348,6 +348,18 @@ bool ESP8266WiFiGenericClass::setSleepMode(WiFiSleepType_t type, uint8_t listenI return ret; } +bool ESP8266WiFiGenericClass::setSleep(bool enable) { + if (enable) + { + ESP.neverSleepOff(); + } + else + { + ESP.neverSleep(); + } + return true; +} + /** * get Sleep mode * @return sleep_type_t @@ -427,11 +439,9 @@ bool ESP8266WiFiGenericClass::mode(WiFiMode_t m) { char backup_hostname [33] { 0 }; // hostname is 32 chars long (RFC) - if (m != WIFI_OFF && wifi_fpm_get_sleep_type() != NONE_SLEEP_T) { + if (m != WIFI_OFF) { memcpy(backup_hostname, wifi_station_hostname, sizeof(backup_hostname)); - // wifi starts asleep by default - wifi_fpm_do_wakeup(); - wifi_fpm_close(); + ESP.forcedModemSleepOff(); } bool ret = false; @@ -526,26 +536,7 @@ bool ESP8266WiFiGenericClass::forceSleepBegin(uint32 sleepUs) { DEBUG_WIFI("core: error with mode(WIFI_OFF)\n"); return false; } - - if(sleepUs == 0 || sleepUs > 0xFFFFFFF) { - sleepUs = 0xFFFFFFF; - } - - wifi_fpm_set_sleep_type(MODEM_SLEEP_T); - esp_yield(); - wifi_fpm_open(); - esp_yield(); - auto ret = wifi_fpm_do_sleep(sleepUs); - if (ret != 0) - { - DEBUG_WIFI("core: error %d with wifi_fpm_do_sleep: (-1=sleep status error, -2=force sleep not enabled)\n", ret); - return false; - } - // fpm_is_open() is always 1 here, with or without delay - // wifi_fpm_set_wakeup_cb(cb): callback is never called - // no power reduction without this delay - delay(10); - return true; + return ESP.forcedModemSleep(sleepUs); } /** @@ -553,12 +544,8 @@ bool ESP8266WiFiGenericClass::forceSleepBegin(uint32 sleepUs) { * @return ok */ bool ESP8266WiFiGenericClass::forceSleepWake() { - if (wifi_fpm_get_sleep_type() != NONE_SLEEP_T) { - wifi_fpm_do_wakeup(); - wifi_fpm_close(); - } - // restore last mode + ESP.forcedModemSleepOff(); if(mode(_forceSleepLastMode)) { if((_forceSleepLastMode & WIFI_STA) != 0){ wifi_station_connect(); @@ -794,10 +781,7 @@ bool ESP8266WiFiGenericClass::shutdown (WiFiState& state) { bool ESP8266WiFiGenericClass::resumeFromShutdown (WiFiState& state) { - if (wifi_fpm_get_sleep_type() != NONE_SLEEP_T) { - wifi_fpm_do_wakeup(); - wifi_fpm_close(); - } + ESP.forcedModemSleepOff(); if (shutdownCRC(state) != state.crc) { diff --git a/libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.h b/libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.h index d0525176bd..3307a35f82 100644 --- a/libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.h +++ b/libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.h @@ -85,21 +85,11 @@ class ESP8266WiFiGenericClass { bool setSleepMode(WiFiSleepType_t type, uint8_t listenInterval = 0); /** - * Set modem sleep mode (ESP32 compatibility) + * Set ESP866 to never sleep or return to previous mode (ESP32 compatibility) * @param enable true to enable * @return true if succeeded */ - bool setSleep(bool enable) - { - if (enable) - { - return setSleepMode(WIFI_MODEM_SLEEP); - } - else - { - return setSleepMode(WIFI_NONE_SLEEP); - } - } + bool setSleep(bool enable); /** * Set sleep mode (ESP32 compatibility) * @param mode wifi_ps_type_t diff --git a/libraries/ESP8266WiFi/src/enable_wifi_at_boot_time.cpp b/libraries/ESP8266WiFi/src/enable_wifi_at_boot_time.cpp index 5eb62eec15..997f48b50f 100644 --- a/libraries/ESP8266WiFi/src/enable_wifi_at_boot_time.cpp +++ b/libraries/ESP8266WiFi/src/enable_wifi_at_boot_time.cpp @@ -26,3 +26,15 @@ extern "C" void __disableWiFiAtBootTime() // (note: c++ ctors not called yet at this point) ESP8266WiFiClass::persistent(true); } + +extern "C" int __get_rf_disable_mode(void) +{ + // overrides the default __get_rf_disable_mode + return -1; // mode not set +} + +extern "C" int __get_rf_powerup_disable_mode(void) +{ + // overrides the default __get_rf__powerup_disable_mode + return -1; // mode not set +} diff --git a/libraries/esp8266/examples/AutoSleepDemo/AutoSleepDemo.ino b/libraries/esp8266/examples/AutoSleepDemo/AutoSleepDemo.ino new file mode 100644 index 0000000000..13f158ae09 --- /dev/null +++ b/libraries/esp8266/examples/AutoSleepDemo/AutoSleepDemo.ino @@ -0,0 +1,128 @@ +/* + ESP8266 auto sleep mode with webserver example + + Copyright (c) 2021 Dirk O. Kaar. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + + This example starts in the default sleep mode, which would be auto modem sleep. + Use a web browser to open http://.../sleep to let it switch into auto light sleep, + open http://.../nosleep to revert to the previous auto modem sleep. +*/ + +#include +#include +#include +#include + +#ifndef D5 +#define D5 (14) +#endif + +// enter your WiFi configuration below +const char* AP_SSID = "SSID"; // your router's SSID here +const char* AP_PASS = "PSK"; // your router's password here + +uint32_t timeout = 30E3; // 30 second timeout on the WiFi connection +esp8266::polledTimeout::oneShotMs wifiTimeout(timeout); // 30 second timeout on WiFi connection + +ESP8266WebServer server(80); + +void handleRoot() { + server.send(200, "text/plain", "hello from esp8266!\r\n"); +} + +void handleNotFound() { + String message = "File Not Found\n\n"; + message += "URI: "; + message += server.uri(); + message += "\nMethod: "; + message += (server.method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += server.args(); + message += "\n"; + for (uint8_t i = 0; i < server.args(); i++) { + message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; + } + server.send(404, "text/plain", message); +} + +void setup() { + Serial.begin(74880); + while (!Serial) + ; + delay(100); + Serial.println(); + WiFi.mode(WIFI_STA); + WiFi.begin(AP_SSID, AP_PASS); + Serial.print(F("connecting to WiFi ")); + Serial.println(WiFi.SSID()); + + wifiTimeout.reset(timeout); + while (((!WiFi.localIP()) || (WiFi.status() != WL_CONNECTED)) && (!wifiTimeout)) { + yield(); + } + if ((WiFi.status() != WL_CONNECTED) || !WiFi.localIP()) { + Serial.println(F("WiFi timed out and didn't connect")); + } else { + Serial.println(F("IP address: ")); + Serial.println(WiFi.localIP()); + } + WiFi.setAutoReconnect(true); + + // if (MDNS.begin("esp8266")) { + // Serial.println("MDNS responder started"); + // } + + server.on("/", handleRoot); + + server.on("/on", []() { + tone(D5, 440); + server.send(200, "text/plain", "tone on"); + }); + + server.on("/off", []() { + noTone(D5); + server.send(200, "text/plain", "tone off"); + }); + + server.on("/sleep", []() { + ESP.autoLightSleep(); + server.send(200, "text/plain", "auto light sleep on"); + }); + + server.on("/nosleep", []() { + ESP.autoSleepOff(); + server.send(200, "text/plain", "sleep off"); + }); + + server.onNotFound(handleNotFound); + + server.begin(); + + Serial.println("HTTP server started"); + pinMode(LED_BUILTIN, OUTPUT); +} + +void loop() { + digitalWrite(LED_BUILTIN, LOW); + server.handleClient(); + digitalWrite(LED_BUILTIN, HIGH); + // in order to see relevant durations when the CPU is auto light sleeping + // in this example, regardless of the added latency for web requests: + delay(300); +} diff --git a/libraries/esp8266/examples/ForcedLightSleep/ForcedLightSleep.ino b/libraries/esp8266/examples/ForcedLightSleep/ForcedLightSleep.ino new file mode 100644 index 0000000000..91a1e285f3 --- /dev/null +++ b/libraries/esp8266/examples/ForcedLightSleep/ForcedLightSleep.ino @@ -0,0 +1,102 @@ +/* + ESP8266 forced light sleep mode example + + Copyright (c) 2021 Dirk O. Kaar. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include + +#define WAKE_UP_PIN 0 // D3/GPIO0, can also force a serial flash upload with RESET +// you can use any GPIO for WAKE_UP_PIN except for D0/GPIO16 as it doesn't support interrupts + +void IRAM_ATTR wakeupPinIsr() { + // For edge-triggered IRQ. + detachInterrupt(WAKE_UP_PIN); + schedule_function([]() { + Serial.println("GPIO went from HI to LO"); + }); +} + +void IRAM_ATTR wakeupPinIsrWE() { + // Wakeup IRQs are available as level-triggered only. + detachInterrupt(WAKE_UP_PIN); + schedule_function([]() { + Serial.println("GPIO wakeup IRQ"); + }); + wakeupPinIsr(); + // reattach falling edge IRQ in loop +} + +void wakeupCallback() { + schedule_function([]() { + Serial.println("wakeup callback was performed"); + }); + // return to falling edge IRQ, otherwise level-triggered IRQ with wakeup + // would get called unexpectedly while awake. + attachInterrupt(WAKE_UP_PIN, wakeupPinIsr, FALLING); +} + +void setup() { + Serial.begin(74880); + while (!Serial) + ; + delay(100); + pinMode(LED_BUILTIN, OUTPUT); // activity and status indicator + digitalWrite(LED_BUILTIN, LOW); // turn on the LED + pinMode(WAKE_UP_PIN, INPUT_PULLUP); // polled to advance tests, interrupt for Forced Light Sleep + attachInterrupt(WAKE_UP_PIN, wakeupPinIsr, FALLING); +} + +using oneShotYieldMs = esp8266::polledTimeout::timeoutTemplate; +oneShotYieldMs gotoSleep(2000); + +void loop() { + if (gotoSleep) { + // No new timers, no delay(), while RAII ForcedLightSleepToken exists. + // Only ONLOW_WE or ONHIGH_WE interrupts work, no edge, that's an SDK or CPU limitation. + // If the GPIO is in the state that will cause a wakeup on attaching the interrupt, + // it cannot trigger a wakeup later, but any sleep duration will be honored. + bool wakeupPinIsHigh = digitalRead(WAKE_UP_PIN); + { + ESPForcedLightSleepToken token(10 * 1000 * 1000, wakeupCallback); + if (token) { // if true, run user code to set up forced light sleep details + // debouncing the wake up pin + delayMicroseconds(5000); + wakeupPinIsHigh &= digitalRead(WAKE_UP_PIN); + delayMicroseconds(5000); + wakeupPinIsHigh &= digitalRead(WAKE_UP_PIN); + // the GPIO might still bounce to LOW after this but before sleep is full engaged, + // disabling wakeup after all + if (wakeupPinIsHigh) { + attachInterrupt(WAKE_UP_PIN, wakeupPinIsrWE, ONLOW_WE); + } + digitalWrite(LED_BUILTIN, HIGH); // turn the LED off so they know the CPU isn't running + if (!wakeupPinIsHigh) token.cancel(); + } + // RAII token gets destructed, going to sleep if all went well + } + digitalWrite(LED_BUILTIN, LOW); // turn on the LED + // retry immediately if the GPIO was found not ready for entering sleep + if (wakeupPinIsHigh) { + gotoSleep.reset(); + } + // restore falling edge IRQ + attachInterrupt(WAKE_UP_PIN, wakeupPinIsr, FALLING); + } +} diff --git a/libraries/esp8266/examples/ForcedModemSleep/ForcedModemSleep.ino b/libraries/esp8266/examples/ForcedModemSleep/ForcedModemSleep.ino new file mode 100644 index 0000000000..6a99bd0860 --- /dev/null +++ b/libraries/esp8266/examples/ForcedModemSleep/ForcedModemSleep.ino @@ -0,0 +1,50 @@ +/* + ESP8266 forced modem sleep mode example + + Copyright (c) 2021 Dirk O. Kaar. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include + +using oneShotYieldMs = esp8266::polledTimeout::timeoutTemplate; +oneShotYieldMs gotoSleep(2000); + +void wakeupCallback() { + digitalWrite(LED_BUILTIN, LOW); // turn on the LED + schedule_function([]() { + Serial.println("wakeup callback was performed"); + gotoSleep.reset(2000); + }); +} + +void setup() { + Serial.begin(74880); + while (!Serial) + ; + delay(100); + pinMode(LED_BUILTIN, OUTPUT); // activity and status indicator + digitalWrite(LED_BUILTIN, LOW); // turn on the LED +} + +void loop() { + if (gotoSleep && ESP.forcedModemSleep(10 * 1000 * 1000, wakeupCallback)) { + digitalWrite(LED_BUILTIN, HIGH); // turn the LED off so they know the modem isn't running + gotoSleep.resetToNeverExpires(); + } +} diff --git a/libraries/esp8266/examples/I2SInput/I2SInput.ino b/libraries/esp8266/examples/I2SInput/I2SInput.ino index 6e2e11b07b..8729c0e6a6 100644 --- a/libraries/esp8266/examples/I2SInput/I2SInput.ino +++ b/libraries/esp8266/examples/I2SInput/I2SInput.ino @@ -27,12 +27,10 @@ course be sure to wire up VCC(3.3V) and GND. */ -#include #include void setup() { Serial.begin(115200); - WiFi.forceSleepBegin(); delay(500); i2s_rxtx_begin(true, false); // Enable I2S RX diff --git a/libraries/esp8266/examples/LowPowerDemo/LowPowerDemo.ino b/libraries/esp8266/examples/LowPowerDemo/LowPowerDemo.ino index 5d1fb2fb44..5bc076a4a0 100644 --- a/libraries/esp8266/examples/LowPowerDemo/LowPowerDemo.ino +++ b/libraries/esp8266/examples/LowPowerDemo/LowPowerDemo.ino @@ -289,10 +289,10 @@ void runTest7() { // WiFi.shutdown(nv->wss); // Forced Modem Sleep for a more Instant Deep Sleep, // and no extended RFCAL as it goes into Deep Sleep Serial.println(F("going into Deep Sleep now...")); - printMillis(); // show time difference across sleep - testPoint_HIGH; // testPoint set HIGH to track Deep Sleep period, cleared at startup() - ESP.deepSleep(10E6, WAKE_RF_DEFAULT); // good night! D0 fires a reset in 10 seconds... - // if you do ESP.deepSleep(0, mode); it needs a RESET to come out of sleep (RTC is disconnected) + printMillis(); // show time difference across sleep + testPoint_HIGH; // testPoint set HIGH to track Deep Sleep period, cleared at startup() + ESP.deepSleep(10E6); // good night! D0 fires a reset in 10 seconds... + // if you do ESP.deepSleep(0); it needs a RESET to come out of sleep (RTC is disconnected) // maximum timed Deep Sleep interval ~ 3 to 4 hours depending on the RTC timer, see the README // the 2 uA GPIO amperage during Deep Sleep can't drive the LED so it's not lit now, although // depending on the LED used, you might see it very dimly lit in a dark room during this test @@ -309,7 +309,7 @@ void runTest8() { Serial.println(F("going into Deep Sleep now...")); Serial.flush(); // needs a delay(10) or Serial.flush() else it doesn't print the whole message testPoint_HIGH; // testPoint set HIGH to track Deep Sleep period, cleared at startup() - ESP.deepSleep(10E6, WAKE_RFCAL); // good night! D0 fires a reset in 10 seconds... + ESP.deepSleep(10E6); // good night! D0 fires a reset in 10 seconds... Serial.println(F("What... I'm not asleep?!?")); // it will never get here } @@ -322,7 +322,7 @@ void runTest9() { Serial.println(F("going into Deep Sleep now...")); Serial.flush(); // needs a delay(10) or Serial.flush() else it doesn't print the whole message testPoint_HIGH; // testPoint set HIGH to track Deep Sleep period, cleared at startup() - ESP.deepSleepInstant(10E6, WAKE_NO_RFCAL); // good night! D0 fires a reset in 10 seconds... + ESP.deepSleepInstant(10E6); // good night! D0 fires a reset in 10 seconds... Serial.println(F("What... I'm not asleep?!?")); // it will never get here } @@ -335,7 +335,7 @@ void runTest10() { Serial.println(F("going into Deep Sleep now...")); Serial.flush(); // needs a delay(10) or Serial.flush() else it doesn't print the whole message testPoint_HIGH; // testPoint set HIGH to track Deep Sleep period, cleared at startup() - ESP.deepSleepInstant(10E6, WAKE_RF_DISABLED); // good night! D0 fires a reset in 10 seconds... + ESP.deepSleepInstant(10E6); // good night! D0 fires a reset in 10 seconds... Serial.println(F("What... I'm not asleep?!?")); // it will never get here } diff --git a/libraries/esp8266/examples/LowPowerDemo/README.md b/libraries/esp8266/examples/LowPowerDemo/README.md index f080348116..8d02fdab81 100644 --- a/libraries/esp8266/examples/LowPowerDemo/README.md +++ b/libraries/esp8266/examples/LowPowerDemo/README.md @@ -71,13 +71,13 @@ Similar to timed Deep Sleep, but it wakes with an interrupt at the next line in Similar to ESP.deepSleep(0). The chip sleeps at 0.4 mA amperage until it is woken by an external interrupt. The only allowed interrupts are high level and low level; edge interrupts won't work. If you have a design that needs to be woken more often than every 2 seconds then you should consider using Forced Light Sleep. For sleep periods longer than 2 seconds, Deep Sleep will be more energy efficient. The chip wakes after an interrupt in about 3 to 5.5 mS (regardless of CPU speed), but WiFi was turned off to enter Forced Light Sleep so you will need to re-initialize it if you are using WiFi. Any user timers (including PWM) will keep the chip from going fully into Forced Light Sleep, and amperage will be ~ 2 mA with timers enabled. -### Test 7 - Deep Sleep, wake with RF_DEFAULT +### Test 7 - Deep Sleep -In Deep Sleep almost everything is turned off, and the chip draws ~ 20 uA. If you have D0/GPIO16 connected to RST, you can use the RTC timer to wake the chip up at a timed interval. You can also wake it solely with an external RESET with ESP.deepSleep(0, wake option), which disconnects the timer. Waking with RF_DEFAULT means it will do an RFCAL if it needs to. Doing **ESP.deepSleep(time)** without the mode variable uses this wake mode. These first two Deep Sleep tests use the standard Deep Sleep function, so the WiFi connection is closed and the modem turned off, which takes up to 270 mS before Deep Sleep begins. Deep Sleep ends with a RESET, and the boot time after that is ~ 130 mS. Any Deep Sleep less than 2 seconds is wasting energy due to the modem shut-off and boot times, and Forced Light Sleep will be a better choice as it recovers in < 5.5 mS from the previous program state. The Deep Sleep tests will not go into Automatic Modem Sleep because delay() is not used. +In Deep Sleep almost everything is turned off, and the chip draws ~ 20 uA. If you have D0/GPIO16 connected to RST, you can use the RTC timer to wake the chip up at a timed interval. You can also wake it solely with an external RESET with ESP.deepSleep(0), which disconnects the timer. Doing **ESP.deepSleep(time)** without the mode variable uses this wake mode. These first two Deep Sleep tests use the standard Deep Sleep function, so the WiFi connection is closed and the modem turned off, which takes up to 270 mS before Deep Sleep begins. Deep Sleep ends with a RESET, and the boot time after that is ~ 130 mS. Any Deep Sleep less than 2 seconds is wasting energy due to the modem shut-off and boot times, and Forced Light Sleep will be a better choice as it recovers in < 5.5 mS from the previous program state. The Deep Sleep tests will not go into Automatic Modem Sleep because delay() is not used. Note that a RESET during Deep Sleep (either external or from D0/GPIO16) does not clear the GPIO pins; some of them hold their previous state. It's unknown how much else survives a reset, as it's not well documented. -### Test 8 - Deep Sleep, wake with RFCAL +### Test 8 - Deep Sleep, wake with RFCAL - BREAKING CHANGE: MUST use __get_rf_mode overrides to alter WAKE modes Identical to the test above, but the modem always does an RF power calibration when booting. In normal use, most people would do WAKE_RF_DEFAULT instead to avoid the extra RFCAL power burst coming out of Deep Sleep if it's not needed. Note that most of the time both of these modes (WAKE_RF_DEFAULT and WAKE_RFCAL) do a 100 mS long RFCAL *before* going into Deep Sleep (the RFCAL after Deep Sleep is much shorter). If the modem is shut down, this long RFCAL doesn't happen. diff --git a/tests/host/common/MockEsp.cpp b/tests/host/common/MockEsp.cpp index 18e7be6c83..4b6afa907f 100644 --- a/tests/host/common/MockEsp.cpp +++ b/tests/host/common/MockEsp.cpp @@ -93,6 +93,37 @@ void eboot_command_write(struct eboot_command* cmd) EspClass ESP; +bool EspClass::forcedModemSleep(uint32_t duration_us, void (*wakeupCb)()) +{ + (void)duration_us; + (void)wakeupCb; + return true; +} + +void EspClass::forcedModemSleepOff() { } + +bool EspClass::forcedLightSleepBegin(uint32_t duration_us, void (*wakeupCb)()) +{ + (void)duration_us; + (void)wakeupCb; + return true; +} + +void EspClass::forcedLightSleepEnd(bool cancel) +{ + (void)cancel; +} + +void EspClass::autoModemSleep() { } + +void EspClass::autoLightSleep() { } + +void EspClass::autoSleepOff() { } + +void EspClass::neverSleep() { } + +void EspClass::neverSleepOff() { } + void EspClass::restart() { mockverbose("Esp.restart(): exiting\n");