diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 2f14a6fb62f..6bb2ff6a8c0 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -248,6 +248,8 @@ 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 + echo CONFIG_FREERTOS_USE_TICKLESS_IDLE=y >> test/sdkconfig.defaults idf.py -C test -DEXTRA_COMPONENT_DIRS=$PWD/components build # Save artifacts to gh-pages diff --git a/cores/esp32/esp32-hal-cpu.c b/cores/esp32/esp32-hal-cpu.c index 7027c7cad9d..4fcd2086f0f 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" @@ -291,3 +292,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 59806b460ae..d9d8d34386a 100644 --- a/cores/esp32/esp32-hal-cpu.h +++ b/cores/esp32/esp32-hal-cpu.h @@ -44,6 +44,15 @@ uint32_t getCpuFrequencyMhz(); // In MHz uint32_t getXtalFrequencyMhz(); // In MHz uint32_t getApbFrequency(); // In Hz +/** + * @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 + * - ESP_ERR_NOT_SUPPORTED if CONFIG_PM_ENABLE is not enabled in sdkconfig + */ +bool setAutomaticLightSleep(bool enabled); + #ifdef __cplusplus } #endif 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/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 new file mode 100644 index 00000000000..9aba5264b8c --- /dev/null +++ b/libraries/ESP32/examples/PowerSave/WiFiModemSleep/WiFiModemSleep.ino @@ -0,0 +1,95 @@ +/* +WiFi 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 = ""; +const char *wifi_password = ""; + +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.cpp b/libraries/WiFi/src/WiFiGeneric.cpp index 75a01d1362f..c6746a41d33 100644 --- a/libraries/WiFi/src/WiFiGeneric.cpp +++ b/libraries/WiFi/src/WiFiGeneric.cpp @@ -621,6 +621,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 3cb1515b324..3e610eec485 100644 --- a/libraries/WiFi/src/WiFiGeneric.h +++ b/libraries/WiFi/src/WiFiGeneric.h @@ -103,6 +103,16 @@ class WiFiGenericClass { bool setSleep(wifi_ps_type_t sleepType); wifi_ps_type_t getSleep(); + /** + * @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); + bool setTxPower(wifi_power_t power); wifi_power_t getTxPower();