From 298b7a3e4ddba8e9cf68d06083602dce519e3c5c Mon Sep 17 00:00:00 2001 From: Taras Shcherban Date: Tue, 9 Jan 2024 13:17:23 +0200 Subject: [PATCH 1/6] ci(esp32): Added PM_ENABLE to sdkconfig.defaults --- .github/workflows/push.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 9a0561c92cd..9a2752dcd68 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -109,4 +109,5 @@ jobs: . ${IDF_PATH}/export.sh idf.py create-project test echo CONFIG_FREERTOS_HZ=1000 > test/sdkconfig.defaults + echo CONFIG_PM_ENABLE=y >> test/sdkconfig.defaults idf.py -C test -DEXTRA_COMPONENT_DIRS=$PWD/components build From 8e5c9575664244f91c3f8dd75dff690b91abf6e8 Mon Sep 17 00:00:00 2001 From: Taras Shcherban Date: Tue, 9 Jan 2024 13:26:26 +0200 Subject: [PATCH 2/6] ci(esp32): Added CONFIG_FREERTOS_USE_TICKLESS_IDLE to sdkconfig.defaults --- .github/workflows/push.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 9a2752dcd68..90cc8fdd680 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -110,4 +110,5 @@ jobs: idf.py create-project test echo CONFIG_FREERTOS_HZ=1000 > test/sdkconfig.defaults echo CONFIG_PM_ENABLE=y >> test/sdkconfig.defaults + echo CONFIG_FREERTOS_USE_TICKLESS_IDLE=y >> test/sdkconfig.defaults idf.py -C test -DEXTRA_COMPONENT_DIRS=$PWD/components build From 15115527cde91c618d3baf3e4dc1127cc8a78623 Mon Sep 17 00:00:00 2001 From: Taras Shcherban Date: Tue, 9 Jan 2024 17:33:03 +0200 Subject: [PATCH 3/6] feat(esp32): Add automatic lightSleep and WiFi listenInterval APIs --- cores/esp32/esp32-hal-cpu.c | 14 ++++++++++++++ cores/esp32/esp32-hal-cpu.h | 9 +++++++++ libraries/WiFi/src/WiFiGeneric.cpp | 21 +++++++++++++++++++++ libraries/WiFi/src/WiFiGeneric.h | 10 ++++++++++ 4 files changed, 54 insertions(+) diff --git a/cores/esp32/esp32-hal-cpu.c b/cores/esp32/esp32-hal-cpu.c index 4819ce1190b..8fdb9c4bd50 100644 --- a/cores/esp32/esp32-hal-cpu.c +++ b/cores/esp32/esp32-hal-cpu.c @@ -18,6 +18,7 @@ #include "freertos/task.h" #include "esp_attr.h" #include "esp_log.h" +#include "esp_pm.h" #include "soc/rtc.h" #if !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(CONFIG_IDF_TARGET_ESP32H2) #include "soc/rtc_cntl_reg.h" @@ -278,3 +279,16 @@ uint32_t getApbFrequency(){ rtc_clk_cpu_freq_get_config(&conf); return calculateApb(&conf); } + +bool setAutomaticLightSleep(bool enabled) +{ + uint32_t cpuFreq = getCpuFrequencyMhz(); + + esp_pm_config_t pm_config = { + .max_freq_mhz = cpuFreq, + .min_freq_mhz = cpuFreq, + .light_sleep_enable = enabled, + }; + + return esp_pm_configure(&pm_config) == ESP_OK; +} diff --git a/cores/esp32/esp32-hal-cpu.h b/cores/esp32/esp32-hal-cpu.h index 646b59858f5..3f71fd644b4 100644 --- a/cores/esp32/esp32-hal-cpu.h +++ b/cores/esp32/esp32-hal-cpu.h @@ -41,6 +41,15 @@ uint32_t getCpuFrequencyMhz(); // In MHz uint32_t getXtalFrequencyMhz(); // In MHz uint32_t getApbFrequency(); // In Hz +/** + * @brief Set automatic light sleep state. CPU will fo into light sleep if no ongoing activity (active task, peripheral activity etc.) + * @param enabled true to enable automatic lightSleep + * @return + * - ESP_OK on success + * - ESP_ERR_NOT_SUPPORTED if CONFIG_PM_ENABLE is not enabled in sdkconfig + */ +bool setAutomaticLightSleep(bool enabled); + #ifdef __cplusplus } #endif diff --git a/libraries/WiFi/src/WiFiGeneric.cpp b/libraries/WiFi/src/WiFiGeneric.cpp index 8ef803064c4..27b38f63026 100644 --- a/libraries/WiFi/src/WiFiGeneric.cpp +++ b/libraries/WiFi/src/WiFiGeneric.cpp @@ -1393,6 +1393,27 @@ bool WiFiGenericClass::setSleep(wifi_ps_type_t sleepType) return false; } +bool WiFiGenericClass::setSleep(wifi_ps_type_t sleepType, uint16_t listenInterval) +{ + if (!setSleep(sleepType)) + return false; + + wifi_config_t current_conf; + if(esp_wifi_get_config((wifi_interface_t)ESP_IF_WIFI_STA, ¤t_conf) != ESP_OK){ + log_e("setSleep(wifi_ps_type_t sleepType, uint16_t listenInterval) failed: get current config failed!"); + return false; + } + + current_conf.sta.listen_interval = listenInterval; + + if(esp_wifi_set_config((wifi_interface_t)ESP_IF_WIFI_STA, ¤t_conf) != ESP_OK){ + log_e("setSleep(wifi_ps_type_t sleepType, uint16_t listenInterval) failed: set wifi config failed!"); + return false; + } + + return true; +} + /** * get modem sleep enabled * @return true if modem sleep is enabled diff --git a/libraries/WiFi/src/WiFiGeneric.h b/libraries/WiFi/src/WiFiGeneric.h index 38396f5a72e..de390d6e417 100644 --- a/libraries/WiFi/src/WiFiGeneric.h +++ b/libraries/WiFi/src/WiFiGeneric.h @@ -182,6 +182,16 @@ class WiFiGenericClass bool setSleep(bool enabled); bool setSleep(wifi_ps_type_t sleepType); + + /** + * @brief Set modem sleep state + * @param sleepType Modem sleep type, one of WIFI_PS_NONE, WIFI_PS_MIN_MODEM, WIFI_PS_MAX_MODEM + * @param listenInterval Listen interval for ESP32 station to receive beacon when WIFI_PS_MAX_MODEM is set. Units: AP beacon intervals. Defaults to 3 if set to 0. + * @return + * - true on success + * - false on internal error when parameters combination is not valid + */ + bool setSleep(wifi_ps_type_t sleepType, uint16_t listenInterval); wifi_ps_type_t getSleep(); bool setTxPower(wifi_power_t power); From 3277229cde894f5fe70206cec489e4a5993cb638 Mon Sep 17 00:00:00 2001 From: Taras Shcherban Date: Thu, 11 Jan 2024 15:45:15 +0200 Subject: [PATCH 4/6] feat(wifi): Add WiFi automatic LightSleep example --- cores/esp32/esp32-hal-cpu.h | 2 +- .../PowerSave/CpuLightSleep/CpuLightSleep.ino | 45 +++++++++ .../WiFiModemSleep/WiFiModemSleep.ino | 95 +++++++++++++++++++ libraries/WiFi/src/WiFiGeneric.h | 2 +- 4 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 libraries/ESP32/examples/PowerSave/CpuLightSleep/CpuLightSleep.ino create mode 100644 libraries/ESP32/examples/PowerSave/WiFiModemSleep/WiFiModemSleep.ino diff --git a/cores/esp32/esp32-hal-cpu.h b/cores/esp32/esp32-hal-cpu.h index 3f71fd644b4..5a93b72f2f3 100644 --- a/cores/esp32/esp32-hal-cpu.h +++ b/cores/esp32/esp32-hal-cpu.h @@ -42,7 +42,7 @@ uint32_t getXtalFrequencyMhz(); // In MHz uint32_t getApbFrequency(); // In Hz /** - * @brief Set automatic light sleep state. CPU will fo into light sleep if no ongoing activity (active task, peripheral activity etc.) + * @brief Set automatic light sleep state. CPU will go into light sleep if no ongoing activity (active task, peripheral activity etc.) * @param enabled true to enable automatic lightSleep * @return * - ESP_OK on success diff --git a/libraries/ESP32/examples/PowerSave/CpuLightSleep/CpuLightSleep.ino b/libraries/ESP32/examples/PowerSave/CpuLightSleep/CpuLightSleep.ino new file mode 100644 index 00000000000..77dfa18aba3 --- /dev/null +++ b/libraries/ESP32/examples/PowerSave/CpuLightSleep/CpuLightSleep.ino @@ -0,0 +1,45 @@ +/* +Cpu automatic LightSleep +===================================== +This code displays how to use automatic light sleep +and demonstrates a difference between busywait and +sleep-aware wait using delay(). +Another examples of a 'sleep-friendly' blocking might be +FreeRTOS mutexes/semaphores, queues. + +This code is under Public Domain License. + +Hardware Connections (optional) +====================== +Use an ammeter/scope connected in series with a CPU/DevKit board to measure power consumption + +Author: +Taras Shcherban +*/ + +#include "Arduino.h" + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; // wait for serial port to connect + + // CPU will automatically go into light sleep if no ongoing activity (active task, peripheral activity etc.) + setAutomaticLightSleep(true); +} + +void loop() +{ + Serial.printf("Time since boot: %ld ms; going to block for a 5 seconds...\n", millis()); + + // Serial output is being suspended during sleeping, so without a Flush call logs + // will be printed to the terminal with a delay depending on how much CPU spends in a sleep state + Serial.flush(); + + // This is a sleep-aware waiting using delay(). Blocking in this manner + // allows CPU to go into light sleep mode until there is some task to do. + // if you remove that delay completely - CPU will have to call loop() function constantly, + // so no power saving will be available + delay(5000); +} diff --git a/libraries/ESP32/examples/PowerSave/WiFiModemSleep/WiFiModemSleep.ino b/libraries/ESP32/examples/PowerSave/WiFiModemSleep/WiFiModemSleep.ino new file mode 100644 index 00000000000..ceca1d5e269 --- /dev/null +++ b/libraries/ESP32/examples/PowerSave/WiFiModemSleep/WiFiModemSleep.ino @@ -0,0 +1,95 @@ +/* +Cpu automatic LightSleep +===================================== +This code displays how to use automatic light sleep with an active WiFi connection +and tune WiFi modem sleep modes + +This code is under Public Domain License. + +Hardware Connections (optional) +====================== +Use an ammeter/scope connected in series with a CPU/DevKit board to measure power consumption + +Author: +Taras Shcherban +*/ + +#include "Arduino.h" + +#include +#include + +const char *wifi_ssid = "kriegsmaschine"; +const char *wifi_password = "ap3NhxtcmszTdok36ijka"; + +void doHttpRequest(); + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; // wait for serial port to connect + + // CPU will automatically go into light sleep if no ongoing activity (active task, peripheral activity etc.) + setAutomaticLightSleep(true); + + WiFi.begin(wifi_ssid, wifi_password); + + // Additionally to automatic CPU sleep a modem can also be setup for a power saving. + // If a WiFi is active - selected modem sleep mode will determine how much CPU will be sleeping. + // There are two functions available:setSleep(mode) and setSleep(mode, listenInterval) + // mode - supports one of three values: + // * WIFI_PS_NONE - No power save + // * WIFI_PS_MIN_MODEM - Minimum modem power saving. In this mode, station wakes up to receive beacon every DTIM period + // * WIFI_PS_MAX_MODEM - Maximum modem power saving. In this mode, interval to receive beacons is determined by the listenInterval parameter + // listenInterval - effective only with a WIFI_PS_MAX_MODEM mode. determines how often will modem (and consequently CPU) wakeup to catch WiFi beacon packets. + // The larger the interval - the less power needed for WiFi connection support. However that comes at a cost of increased networking latency, i.e. + // If your device responds to some external requests (web-server, ping etc.) - larger listenInterval means device takes more to respond. + // Reasonable range is 2..9, going larger would not benefit too much in terms of power consumption. Too large value might result in WiFi connection drop. + // listenInterval is measured in DTIM intervals, which is determined by your access point (typically ~100ms). + WiFi.setSleep(WIFI_PS_MAX_MODEM, 4); +} + +void loop() +{ + doHttpRequest(); + + // Serial output is being suspended during sleeping, so without a Flush call logs + // will be printed to the terminal with a delay depending on how much CPU spends in a sleep state + Serial.flush(); + + // This is a sleep-aware waiting using delay(). Blocking in this manner + // allows CPU to go into light sleep mode until there is some task to do. + // if you remove that delay completely - CPU will have to call loop() function constantly, + // so no power saving will be available + delay(5000); +} + +// simulate some job - make an HTTP GET request +void doHttpRequest() +{ + if (!WiFi.isConnected()) + return; + + HTTPClient http; + + Serial.print("[HTTP] request...\n"); + http.begin("http://example.com/index.html"); + + int httpCode = http.GET(); + if (httpCode > 0) + { + Serial.printf("[HTTP] GET... code: %d\n", httpCode); + + if (httpCode == HTTP_CODE_OK) + { + Serial.printf("[HTTP] GET... payload size: %d\n", http.getSize()); + } + } + else + { + Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); + } + + http.end(); +} diff --git a/libraries/WiFi/src/WiFiGeneric.h b/libraries/WiFi/src/WiFiGeneric.h index de390d6e417..2e6fcf4c22a 100644 --- a/libraries/WiFi/src/WiFiGeneric.h +++ b/libraries/WiFi/src/WiFiGeneric.h @@ -186,7 +186,7 @@ class WiFiGenericClass /** * @brief Set modem sleep state * @param sleepType Modem sleep type, one of WIFI_PS_NONE, WIFI_PS_MIN_MODEM, WIFI_PS_MAX_MODEM - * @param listenInterval Listen interval for ESP32 station to receive beacon when WIFI_PS_MAX_MODEM is set. Units: AP beacon intervals. Defaults to 3 if set to 0. + * @param listenInterval Listen interval for ESP32 station to receive beacon when WIFI_PS_MAX_MODEM is set. Units: AP beacon intervals. Defaults to 3 if set to 0. * @return * - true on success * - false on internal error when parameters combination is not valid From 1518743559e503c943b9c1d87b10ebadbbbdc195 Mon Sep 17 00:00:00 2001 From: Taras Shcherban Date: Thu, 11 Jan 2024 15:47:00 +0200 Subject: [PATCH 5/6] feat(wifi): wifi credentials remove --- .../examples/PowerSave/WiFiModemSleep/WiFiModemSleep.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP32/examples/PowerSave/WiFiModemSleep/WiFiModemSleep.ino b/libraries/ESP32/examples/PowerSave/WiFiModemSleep/WiFiModemSleep.ino index ceca1d5e269..d714b0acddd 100644 --- a/libraries/ESP32/examples/PowerSave/WiFiModemSleep/WiFiModemSleep.ino +++ b/libraries/ESP32/examples/PowerSave/WiFiModemSleep/WiFiModemSleep.ino @@ -19,8 +19,8 @@ Taras Shcherban #include #include -const char *wifi_ssid = "kriegsmaschine"; -const char *wifi_password = "ap3NhxtcmszTdok36ijka"; +const char *wifi_ssid = ""; +const char *wifi_password = ""; void doHttpRequest(); From c2af5fd52c14ddf00e6410fbc1ab892166d4a111 Mon Sep 17 00:00:00 2001 From: Taras Shcherban Date: Thu, 11 Jan 2024 19:09:14 +0200 Subject: [PATCH 6/6] feat(esp32): add GPIO interrupts with automatic LightSleep enabled --- .../GpioInterrupts/GpioInterrupts.ino | 85 +++++++++++++++++++ .../WiFiModemSleep/WiFiModemSleep.ino | 2 +- 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 libraries/ESP32/examples/PowerSave/GpioInterrupts/GpioInterrupts.ino diff --git a/libraries/ESP32/examples/PowerSave/GpioInterrupts/GpioInterrupts.ino b/libraries/ESP32/examples/PowerSave/GpioInterrupts/GpioInterrupts.ino new file mode 100644 index 00000000000..330a9d06b7c --- /dev/null +++ b/libraries/ESP32/examples/PowerSave/GpioInterrupts/GpioInterrupts.ino @@ -0,0 +1,85 @@ +/* +Gpio Interrupts with sleep mode +===================================== +This code displays how to use automatic light sleep with a GPIO interrupts. + +With a light sleep mode the only available interrupts are ONLOW_WE and ONHIGH_WE. +RISING/FALLING/CHANGE/ONLOW/ONHIGH would be not fired. +Keep in mind that the interrupt ONLOW_WE/ONLOW would be fired repetitively as long as +the input is held in a LOW state, so a single button press by a fraction of second can +easily trigger your interrupt handler a few hundreds times. +The same is valid for ONHIGH_WE/ONHIGH and HIGH level. +To catch every button press only once - we are going to change the interrupt level +(from ONHIGH_WE to ONLOW_WE and vice versa). +Since ONHIGH_WE interrupt handler is detached right on the first execution - it can be +also treated as a RISING interrupt handler. +The same way ONLOW_WE can be treated as a FALLING interrupt handler. +If CHANGE interrupt is needed - just put your logic in both ONHIGH_WE and ONLOW_WE handlers. + +This code is under Public Domain License. + +Hardware Connections +====================== +A button from IO10 to ground (or a jumper wire to mimic that button). +Optionally - an ammeter/scope connected in series with a CPU/DevKit board to measure power consumption. + +Author: +Taras Shcherban +*/ + +#include "Arduino.h" +#include + +std::atomic_int interruptsCounter = 0; + +#define BTN_INPUT 10 + +void lowIsrHandler(); + +void IRAM_ATTR highIsrHandler() +{ + // button was released - attach button press interrupt back + attachInterrupt(BTN_INPUT, lowIsrHandler, ONLOW_WE); +} + +void IRAM_ATTR lowIsrHandler() +{ + // button is pressed - count it + interruptsCounter++; + + // attach interrupt to catch an event of button releasing + // implicitly detaches previous interrupt and stops this function from being called + // while the input is held in a LOW state + attachInterrupt(BTN_INPUT, highIsrHandler, ONHIGH_WE); +} + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; // wait for serial port to connect + + // CPU will automatically go into light sleep if no ongoing activity (active task, peripheral activity etc.) + setAutomaticLightSleep(true); + + pinMode(BTN_INPUT, INPUT_PULLUP); + attachInterrupt(BTN_INPUT, lowIsrHandler, ONLOW_WE); + + // this function is required for GPIO to be able to wakeup CPU from a lightSleep mode + esp_sleep_enable_gpio_wakeup(); +} + +void loop() +{ + Serial.printf("Button press count: %d\n", (int)interruptsCounter); + + // Serial output is being suspended during sleeping, so without a Flush call logs + // will be printed to the terminal with a delay depending on how much CPU spends in a sleep state + Serial.flush(); + + // This is a sleep-aware waiting using delay(). Blocking in this manner + // allows CPU to go into light sleep mode until there is some task to do. + // if you remove that delay completely - CPU will have to call loop() function constantly, + // so no power saving will be available + delay(5000); +} diff --git a/libraries/ESP32/examples/PowerSave/WiFiModemSleep/WiFiModemSleep.ino b/libraries/ESP32/examples/PowerSave/WiFiModemSleep/WiFiModemSleep.ino index d714b0acddd..9aba5264b8c 100644 --- a/libraries/ESP32/examples/PowerSave/WiFiModemSleep/WiFiModemSleep.ino +++ b/libraries/ESP32/examples/PowerSave/WiFiModemSleep/WiFiModemSleep.ino @@ -1,5 +1,5 @@ /* -Cpu automatic LightSleep +WiFi automatic LightSleep ===================================== This code displays how to use automatic light sleep with an active WiFi connection and tune WiFi modem sleep modes