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

Extending on the new WiFi-less startup and new power saving APIs in ESP class #7979

Open
wants to merge 44 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
810fed9
WiFi is off by default anyway.
dok-net Apr 13, 2021
a1ceef3
Set deep sleep resume and powerup RF modes to OFF.
dok-net Apr 15, 2021
9538ef9
Implement forced light sleep in Esp class
dok-net Apr 15, 2021
1ecdfc1
Add low power forced light sleep demo.
dok-net Apr 15, 2021
ef2bc95
Implement auto light sleep and auto modem sleep in Esp class
dok-net Apr 15, 2021
9891613
Add auto sleep mode demo.
dok-net Apr 15, 2021
ea94626
Refactoring MODEM forced sleep code from ESP8266WiFi to core ESP class.
dok-net Apr 16, 2021
7137d0f
Add new Esp class member functions to host test MockEsp.cpp
dok-net Apr 16, 2021
7fea539
API refactoring.
dok-net Apr 16, 2021
446aea7
Missed that there are different functions for auto and force sleep ty…
dok-net Apr 17, 2021
6a07829
DRY
dok-net Apr 17, 2021
49c2ac0
enable callback in forced light sleep example
dok-net Apr 17, 2021
e7864f1
instead of crude manual mangling and no prototypes, put the weak symb…
dok-net Apr 17, 2021
79a7ee8
Example for a forced modem sleep with timeout and callback.
dok-net Apr 18, 2021
788731e
Save 9ms for force MODEM sleep activation.
dok-net Apr 18, 2021
433cd17
Add timer_list debug dump to forced light sleep
dok-net Apr 19, 2021
24efea2
Auto light sleep is all about WiFi and requires a connection to an AP
dok-net Apr 22, 2021
83e70ee
Some comments from memory of what testing revealed. Independent verif…
dok-net Apr 24, 2021
c0326f0
Copyright notices and clarifying introductory comments in example code.
dok-net Apr 25, 2021
7c4879f
Increase delay time to let example actually show measurable times in …
dok-net Apr 25, 2021
01eb942
Remove unintended copy&paste artifact.
dok-net Apr 25, 2021
382f6fd
Reset ISR to edge triggered mode when back from sleep via timeout ins…
dok-net Apr 25, 2021
213a2e0
AttachInterrupt is not available in ISR (IRAM_ATTR), fix crash on stu…
dok-net Apr 25, 2021
56c40f3
SDK needs idle task to perform mode switching. Reset the callback poi…
dok-net Apr 25, 2021
affc7fc
Fix serial bitrate in example.
dok-net Apr 25, 2021
6015cec
Debounce the button GPIO in order to prevent crashes due to IRQ storms.
dok-net Apr 25, 2021
b6e14f6
Add documentation for the new ESP sleep modes to libraries.rst
dok-net Apr 26, 2021
ad454ea
Fix headline in documentation as well.
dok-net Apr 26, 2021
74951fd
By review comment.
dok-net Apr 26, 2021
0a70272
ESP.deepSleep[Instant] refactoring to match the setting of RF mode in…
dok-net Apr 27, 2021
df7e909
More comprehensive description of the usage of forcedLightSleep.
dok-net Apr 28, 2021
1af1380
Be more specific about ForcedLightSleepEnd.
dok-net Apr 28, 2021
a9d9cda
Describe forced light sleep initiation in more detail.
dok-net Apr 28, 2021
e8798ac
Add neverSleep and neverSleepOff to Esp class, use them in core and l…
dok-net May 17, 2021
3fc7d23
Apply suggested style fixes.
dok-net Mar 13, 2022
abd17a7
There may exist code expecting this macro definition to overwrite the…
dok-net Mar 12, 2023
a6d1f73
Merge branch 'master' into wifioff
dok-net Mar 31, 2023
5a2c2db
Merge branch 'master' into wifioff
dok-net Apr 23, 2023
2d1cb1c
Merge branch 'master' into wifioff
dok-net May 13, 2023
9847e1f
Merge branch 'master' into wifioff
dok-net Jun 17, 2023
b8a95de
Reviewer noticed that esp_suspend is part of the core for awhile now …
dok-net Jun 21, 2023
dd6ea93
Implement RAII-patterned token class for ForcedLightSleep begin()/end().
dok-net Jun 24, 2023
6252680
Code style fixes.
dok-net Jun 24, 2023
cd6d155
Merge branch 'master' into wifioff
dok-net Aug 5, 2023
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
184 changes: 176 additions & 8 deletions cores/esp8266/Esp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>(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<int>(mode));
system_deep_sleep_set_option(__get_rf_mode());
system_deep_sleep_instant(time_us);
esp_suspend();
}
Expand All @@ -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);
Copy link
Collaborator

Choose a reason for hiding this comment

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

fpm_wakeup_cb_func will be called after system wakes up only if the force sleep
time out (wifi_fpm_do_sleep and the parameter is not 0xFFFFFFF).
fpm_wakeup_cb_func will not be called if wake-up is caused by
wifi_fpm_do_wakeup from MODEM_SLEEP_T type force sleep.

Also, perhaps, not necessary to expose to user? Isn't return signal enough of a result?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If there's no hurt in exposing it as an option, let's keep it this way, please.

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;
}
Comment on lines +174 to +183
Copy link
Collaborator

Choose a reason for hiding this comment

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

Combine with begin() as a single operation?
If begin() called once and then off(), sort-of works (if fpm was not enabled externally)
If begin() called once and then again, sleep mode gets overwritten

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Any calls to the SDK or modifying sleep modes by sidelining this API is undefined behavior. Simple as that.
The saved_sleep_type, if that is what you are focused on, is rather a convenience than a full-blown requirement for this to be useful. I don't see any real malfunction if its used the way you described.


#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([]() {
mcspr marked this conversation as resolved.
Show resolved Hide resolved
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;
devyte marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Collaborator

Choose a reason for hiding this comment

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

-//- as modem - if we hide timer list, it should not be possible for user to overwrite this accidentally. Single operation instead of separate begin() -> end()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This way, it worked/works, other ways, it didn't work/does work, i.e. doesn't enter the power saving modes properly. It was all carefully measured by the other guy at the time. Whatever happened to him :-(

Single operation instead of separate begin() -> end()?

I don't understand that sentence, I am sorry.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This way, it worked/works, other ways, it didn't work/does work, i.e. doesn't enter the power saving modes properly. It was all carefully measured by the other guy at the time. Whatever happened to him :-(

Sadly, yeah, might be difficult to test at this point from our baseline of knowledge :/
As I understand it, we have issue where SDK processes timer list. So, that is what we try to avoid in code. If there is an ets_post to a task, does it still work? Are timers waking up early, or SDK never sleeps / idles to begin with?

I don't understand that sentence, I am sorry.

Something like InterruptLock. Imagine FpmThingy class that enables sleep on begin or construction, and disables on destruction. It may or may not be applicable to our internal usage, but seems like a better API for user side of things. Plus, it would not be necessary to expose too much methods to control it

Copy link
Contributor Author

@dok-net dok-net Jun 24, 2023

Choose a reason for hiding this comment

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

@mcspr I have added an RAII class and modified libraries\esp8266\examples\ForcedLightSleep\ForcedLightSleep.ino. Please review that and let me know if that matches to what you had in mind. Thanks for the suggestion!

Copy link
Collaborator

Choose a reason for hiding this comment

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

Repeating the q from above - what is the difference between GPIO-activated sleep and timed one here? Is CPU really sleeping or we just simulate that it does?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OMG, you are asking questions that I cannot remember the answer to, except that I am 100% certain that we literally did hours of repetitive tests to show that power consumption dropped to the expected levels, and it didn't or the MCU became unreliable or the tables got corrupted if it wasn't done in exactly this - documented by Espressif - way. We really should not change a single line.

Copy link
Collaborator

@mcspr mcspr Jun 23, 2023

Choose a reason for hiding this comment

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

Right, but now we have a half-understood feature that maybe works?
Reading our issues simply repeats the above - we copy nodemcu and test how it did. I'd have to read that yet again if you don't have link or an explanation :/
(nodemcu/nodemcu-firmware#1231 (comment) and below)
edit: link oops

Copy link
Contributor Author

@dok-net dok-net Jun 24, 2023

Choose a reason for hiding this comment

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

I'm not sure we are not talking past each other. The sleep isn't actived by the GPIO, but cancelled. Same for the timer. I'm sure that's what you meant. As far as it got measure two years ago, yes we were sure it did as advertised - sleeping to save power, resuming either by GPIO trigger - push of a button - or timeout.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mcspr Is this now stalled again due to to unrelated workload, or have my changes and explanations been unconvincing so far? I would be totally relieved if this finally got merged, I am sure it will help people a lot, at least it might allow more specific reports about power-saving mode issues?

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;
}

dok-net marked this conversation as resolved.
Show resolved Hide resolved
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)
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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<uint32_t *>(data), size) == SPI_FLASH_RESULT_OK;
};
Expand Down Expand Up @@ -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) {
Expand All @@ -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)
Expand Down
60 changes: 57 additions & 3 deletions cores/esp8266/Esp.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion cores/esp8266/Updater.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 1 addition & 4 deletions cores/esp8266/core_esp8266_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
dok-net marked this conversation as resolved.
Show resolved Hide resolved
}

#if FLASH_MAP_SUPPORT
Expand Down
Loading