Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PowerManagement support #9083

Draft
wants to merge 28 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
298b7a3
ci(esp32): Added PM_ENABLE to sdkconfig.defaults
tshcherban Jan 9, 2024
8e5c957
ci(esp32): Added CONFIG_FREERTOS_USE_TICKLESS_IDLE to sdkconfig.defaults
tshcherban Jan 9, 2024
1511552
feat(esp32): Add automatic lightSleep and WiFi listenInterval APIs
tshcherban Jan 9, 2024
3277229
feat(wifi): Add WiFi automatic LightSleep example
tshcherban Jan 11, 2024
1518743
feat(wifi): wifi credentials remove
tshcherban Jan 11, 2024
c2af5fd
feat(esp32): add GPIO interrupts with automatic LightSleep enabled
tshcherban Jan 11, 2024
82eb3d5
Merge branch 'master' into power-management
tshcherban Jan 15, 2024
ffae7e9
Merge branch 'master' into power-management
tshcherban Jan 16, 2024
9374554
Merge branch 'master' into power-management
tshcherban Jan 17, 2024
5a7576f
Merge branch 'master' into power-management
tshcherban Jan 19, 2024
eb86e50
Merge branch 'master' into power-management
tshcherban Jan 21, 2024
25fd651
Merge branch 'master' into power-management
tshcherban Jan 24, 2024
1575558
Merge branch 'master' into power-management
tshcherban Feb 1, 2024
4375f4c
Merge branch 'master' into power-management
tshcherban Feb 2, 2024
1b9474a
Merge branch 'master' into power-management
tshcherban Feb 6, 2024
0b0698a
Merge branch 'master' into power-management
tshcherban Feb 7, 2024
1ea4eea
Merge branch 'master' into power-management
tshcherban Feb 8, 2024
d3c132b
Merge branch 'master' into power-management
tshcherban Feb 9, 2024
ea7400f
Merge branch 'master' into power-management
SuGlider Feb 14, 2024
096e5cb
Merge branch 'master' into power-management
lucasssvaz Feb 23, 2024
45577bd
Merge branch 'master' into power-management
SuGlider Feb 25, 2024
c58bc28
Merge branch 'master' into power-management
tshcherban Mar 26, 2024
9166bc0
Merge branch 'master' into power-management
lucasssvaz Mar 26, 2024
40702b2
Merge branch 'master' into power-management
tshcherban Apr 17, 2024
80bcb52
Merge branch 'master' into power-management
tshcherban Jun 6, 2024
205db45
Merge branch 'master' into power-management
tshcherban Jun 17, 2024
9b93762
Merge branch 'master' into power-management
tshcherban Aug 2, 2024
07046f8
Merge branch 'master' into power-management
tshcherban Aug 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions cores/esp32/esp32-hal-cpu.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from

uint32_t cpuFreq = getCpuFrequencyMhz();

to

const 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;
}
9 changes: 9 additions & 0 deletions cores/esp32/esp32-hal-cpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 45 additions & 0 deletions libraries/ESP32/examples/PowerSave/CpuLightSleep/CpuLightSleep.ino
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
*/

#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);
}
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
*/

#include "Arduino.h"
#include <atomic>

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);
}
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
*/

#include "Arduino.h"

#include <WiFi.h>
#include <HTTPClient.h>

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();
}
21 changes: 21 additions & 0 deletions libraries/WiFi/src/WiFiGeneric.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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, &current_conf) != ESP_OK){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from
(wifi_interface_t)ESP_IF_WIFI_STA
to
wifi_interface_t::WIFI_IF_STA
?

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, &current_conf) != ESP_OK){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from
(wifi_interface_t)ESP_IF_WIFI_STA
to
wifi_interface_t::WIFI_IF_STA
?

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
Expand Down
10 changes: 10 additions & 0 deletions libraries/WiFi/src/WiFiGeneric.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be good to indicate the units of this parameter. As written it could be interpreted as milliseconds, seconds, minutes, etc.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unit is specified here (AP beacon intervals). It is determined by the access point parameters, I've often seen a 102400us value during testing with different routers, however it can differ.

* @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();

Expand Down