-
Notifications
You must be signed in to change notification settings - Fork 13.3k
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
Forced Light Sleep Mode not working #6642
Comments
@downsider7 try this pinMode(WAKE_UP_PIN, INPUT_PULLUP);
// ...
WiFi.mode(WIFI_OFF);
delay(100);
Serial.println("Going to sleep, pull WAKE_UP_PIN low to wake me");
delay(3); // needs a brief delay or the whole message above doesn't print
wifi_fpm_set_sleep_type(LIGHT_SLEEP_T);
gpio_pin_wakeup_enable(GPIO_ID_PIN(WAKE_UP_PIN), GPIO_PIN_INTR_LOLEVEL);
// or LOLEVEL or HILEVEL interrupt, no edge allowed, sorry, that's the SDK limitation
wifi_fpm_open();
wifi_fpm_do_sleep(0xFFFFFFF);
delay(100);
Serial.println("Woke up!"); I'm not using the callback, but the code above puts it in Forced Light Sleep for me. After the GPIO interrupt it falls through to the 'Woke up!' message. Forced Light Sleep only works with wifi_fpm_do_sleep(0xFFFFFFF); any other value and it doesn't sleep. I tried the WiFi disconnect, and it wouldn't go to Forced Light Sleep, only the WiFi.mode(WIFI_OFF); worked for me. Try that little chunk first to verify for yourself that Forced Light Sleep is working before you start adding your callback and other things. It's very sensitive to what's going on with WiFi. If you're running a 12E DevKit, there's a minimum current of around 6 to 15mA due to the 3.3V voltage regulator and whatever USB <> serial chip you have on your board. You might be able to power it by feeding 3.3V into the 3V3 pin, although I'm unsure what state that leave the USB chip in. The CH340G might go into 'suspend' mode, I can't tell from the datasheet. I'll try it this weekend. |
Here's an expanded set of low power tests with every power-saving mode I could think of or read about. If I've missed any, please let me know and I'll expand it a bit further. 😄 This should work with any build from 2.5 onward, unknown with builds before that but probably not. /* This example demonstrates the different low-power modes of the ESP8266
The initial setup was a WeMos D1 Mini with 3.3V connected to the 3V3 pin through a meter
so that it bypassed the on-board voltage regulator and USB chip. There's still about
0.3 mA worth of leakage amperage due to the unpowered chips. These tests should work with
any module, although on-board components will affect the actual current measurement.
While the modem is turned on the amperage is > 67 mA or changing with a minimum value.
To verify the 20 uA Deep Sleep amperage the voltage regulator and USB chip were removed.
This test series requires an active WiFi connection to illustrate two tests. If you
have problems with WiFi, uncomment the #define DEBUG for additional WiFi error messages.
The test requires a pushbutton switch connected between D3 and GND to advance the tests.
You'll also need to connect D0/GPIO16 to RST for the Deep Sleep tests. If you forget to
connect D0 to RST it will hang after the first Deep Sleep test. Additionally, you can
connect an LED from any free pin through a 1K ohm resistor to the 3.3V supply, though
preferably not the 3V3 pin on the module or it adds to the measured amperage. When the
LED blinks you can proceed to the next test. When the LED is lit continuously it's
connecting WiFi, and when it's off the CPU is asleep. The LED blinks slowly when the
tests are complete. Test progress can also be shown on the serial monitor.
WiFi connections will be made over twice as fast if you can use a static IP address.
This example 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 example 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 example; if not, write to the Free Software Foundation, Inc.,
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#include <ESP8266WiFi.h>
#include <coredecls.h> // crc32()
#include <PolledTimeout.h>
#include <include/WiFiState.h> // WiFiState structure details
//#define DEBUG // prints WiFi connection info to serial, uncomment if you want WiFi messages
#ifdef DEBUG
#define DEBUG_PRINTLN(x) Serial.println(x)
#define DEBUG_PRINT(x) Serial.print(x)
#else
#define DEBUG_PRINTLN(x)
#define DEBUG_PRINT(x)
#endif
#define WAKE_UP_PIN 0 // D3/GPIO0, can also force a serial flash upload with RESET
// you can use any pin for WAKE_UP_PIN except for D0/GPIO16 as it doesn't support interrupts
// uncomment one of the two lines below for your LED connection, if used
#define LED 5 // D1/GPIO5 external LED for modules with built-in LEDs so it doesn't add amperage
//#define LED 2 // D4/GPIO2 LED for ESP-01,07 modules; D4 is LED_BUILTIN on most other modules
// you can use LED_BUILTIN, but it adds to the measured amperage by 0.3mA to 6mA.
ADC_MODE(ADC_VCC); // allows you to monitor the internal VCC level; it varies with WiFi load
// don't connect anything to the analog input pin(s)!
// enter your WiFi configuration below
const char* AP_SSID = "SSID"; // your router's SSID here
const char* AP_PASS = "password"; // your router's password here
IPAddress staticIP(0, 0, 0, 0); // parameters below are for your static IP address, if used
IPAddress gateway(0, 0, 0, 0);
IPAddress subnet(0, 0, 0, 0);
IPAddress dns1(0, 0, 0, 0);
IPAddress dns2(0, 0, 0, 0);
uint32_t wifiTimeout = 30E3; // 30 second timeout on the WiFi connection
//#define testPoint 4 // D2/GPIO4 used to track the timing of several test cycles, optional
// This structure is stored in RTC memory to save the WiFi state and reset count (number of Deep Sleeps).
// non volatile data
struct nv_s {
WiFiState wss; // core's wifi save state
struct {
uint32_t crc32;
uint32_t rstCount; // stores the Deep Sleep reset count
} rtcData;
};
static nv_s* nv = (nv_s*)RTC_USER_MEM; // user RTC RAM area
uint32_t resetCount = 0; // keeps track of the number of Deep Sleep tests / resets
const uint32_t blinkDelay = 100; // fast blink rate for the LED when waiting for the user
esp8266::polledTimeout::periodicMs blinkLED(blinkDelay); // LED blink delay without delay()
esp8266::polledTimeout::oneShotFastMs altDelay(blinkDelay); // tight loop to simulate user code
// use fully qualified type and avoid importing all ::esp8266 namespace to the global namespace
void wakeupCallback() { // unlike ISRs, you can do a print() from a callback function
#ifdef testPoint
digitalWrite(testPoint, LOW); // testPoint tracks latency from WAKE_UP_PIN LOW to testPoint LOW
#endif
Serial.print(F("millis() = ")); // show that RTC / millis() is stopped in Forced Light Sleep
Serial.println(millis()); // although the CPU may run for up to 1000 mS before fully stopping
Serial.println(F("Woke from Forced Light Sleep - this is the callback"));
}
void preinit() {
ESP8266WiFiClass::preinitWiFiOff();
}
void setup() {
#ifdef testPoint
pinMode(testPoint, OUTPUT); // test point for Forced Light Sleep and Deep Sleep tests
digitalWrite(testPoint, LOW); // Deep Sleep reset doesn't clear GPIOs, testPoint LOW shows boot time
#endif
pinMode(LED, OUTPUT); // activity and status indicator
digitalWrite(LED, LOW); // turn on the LED
pinMode(WAKE_UP_PIN, INPUT_PULLUP); // polled to advance tests, interrupt for Forced Light Sleep
Serial.begin(115200);
Serial.print(F("\nReset reason = "));
String resetCause = ESP.getResetReason();
Serial.println(resetCause);
resetCount = 0;
if ((resetCause == "External System") || (resetCause == "Power on")) {
Serial.println(F("I'm awake and starting the Low Power tests"));
}
// Read CRC from RTC memory
uint32_t crcOfData = crc32((uint8_t*) &nv->rtcData.rstCount, sizeof(nv->rtcData.rstCount));
if ((crcOfData = nv->rtcData.crc32) && (resetCause == "Deep-Sleep Wake")) {
resetCount = nv->rtcData.rstCount; // read the previous reset count
resetCount++;
}
nv->rtcData.rstCount = resetCount; // update the reset count & CRC
updateRTC();
if (resetCount == 1) { // show that millis() is cleared across Deep Sleep reset
Serial.print(F("millis() = "));
Serial.println(millis());
}
} // end of setup()
void loop() {
if (resetCount == 0) { // if first loop() since power on or external reset
runTest1();
runTest2();
runTest3();
runTest4();
runTest5();
runTest6(); // first Deep Sleep test, all these end with a RESET
}
if (resetCount < 4) {
initWiFi();
}
if (resetCount == 1) {
runTest7();
} else if (resetCount == 2) {
runTest8();
} else if (resetCount == 3) {
runTest9();
} else if (resetCount == 4) {
resetTests();
}
} //end of loop()
// 1st test - running with WiFi unconfigured, reads ~67 mA minimum
void runTest1() {
Serial.println(F("\n1st test - running with WiFi unconfigured"));
float volts = ESP.getVcc();
Serial.printf("The internal VCC reads %1.3f volts\n", volts / 1000);
Serial.println(F("press the switch to continue"));
waitPushbutton(false, blinkDelay);
}
// 2nd test - Automatic Modem Sleep 7 seconds after WiFi is connected (LED flashes)
void runTest2() {
Serial.println(F("\n2nd test - Automatic Modem Sleep"));
Serial.println(F("connecting WiFi, please wait until the LED blinks"));
initWiFi();
if (WiFi.localIP()) { // won't go into Automatic Sleep without an active WiFi connection
Serial.println(F("The amperage will drop in 7 seconds."));
float volts = ESP.getVcc();
Serial.printf("The internal VCC reads %1.3f volts\n", volts / 1000);
Serial.println(F("press the switch to continue"));
waitPushbutton(true, 90); /* This is using a special feature: below 100 mS blink delay,
the LED blink delay is padding 100 mS time with 'program cycles' to fill the 100 mS.
At 90 mS delay, 90% of the blink time is delay(), and 10% is 'your program running'.
Below 90% you'll see a difference in the average amperage: less delay() = more amperage.
At 100 mS and above it's essentially all delay() time. On an oscilloscope you'll see the
time between beacons at > 67 mA more often with less delay() percentage. You can change
the '90' mS to other values to see the effect it has on Automatic Modem Sleep. */
} else {
Serial.println(F("no WiFi connection, test skipped"));
}
}
// 3rd test - Forced Modem Sleep
void runTest3() {
Serial.println(F("\n3rd test - Forced Modem Sleep"));
WiFi.mode(WIFI_SHUTDOWN, &nv->wss); // shut the modem down and save the WiFi state
// WiFi.forceSleepBegin(); // alternate method of Forced Modem Sleep without saving WiFi state
// delay(10); // it doesn't always go to sleep unless you delay(10); yield() wasn't reliable
float volts = ESP.getVcc();
Serial.printf("The internal VCC reads %1.3f volts\n", volts / 1000);
Serial.println(F("press the switch to continue"));
waitPushbutton(true, 99); /* Using the same < 100 mS feature. If you drop the delay below 100, you
will see the effect of program time vs. delay() time on minimum amperage. Above ~ 97 (97% of the
time in delay) there is little change in amperage, so you need to spend maximum time in delay()
to get minimum amperage. At a high percentage of delay() you will see minimum amperage. */
}
// 4th test - Automatic Light Sleep
void runTest4() {
Serial.println(F("\n4th test - Automatic Light Sleep"));
Serial.println(F("reconnecting WiFi"));
Serial.println(F("it will be in Automatic Light Sleep when WiFi connects (LED blinks)"));
digitalWrite(LED, LOW); // visual cue that we're reconnecting
WiFi.setSleepMode(WIFI_LIGHT_SLEEP, 3); // Automatic Light Sleep, DTIM listen interval = 3
// at higher beacon intervals you'll have a hard time establishing and maintaining a connection
WiFi.forceSleepWake(); // reconnect with previous STA mode and connection settings
uint32_t wifiStart = millis();
while ((!WiFi.localIP()) && (millis() - wifiStart < wifiTimeout)) {
yield();
}
if (WiFi.localIP()) { // won't go into Automatic Sleep without an active WiFi connection
float volts = ESP.getVcc();
Serial.printf("The internal VCC reads %1.3f volts\n", volts / 1000);
Serial.println(F("long press of the switch to continue"));
waitPushbutton(true, 350); /* Below 100 mS delay it only goes into 'Automatic Modem Sleep',
and below ~ 350 mS delay() the 'Automatic Light Sleep' is less frequent. Above 500 mS
delay() doesn't make much improvement in power savings. */
} else {
Serial.println(F("no WiFi connection, test skipped"));
}
}
// 5th test - Forced Light Sleep using Non-OS SDK calls
void runTest5() {
Serial.println(F("\n5th test - Forced Light Sleep using Non-OS SDK calls"));
Serial.flush();
WiFi.mode(WIFI_OFF); // you must turn the modem off; using disconnect won't work
delay(10);
digitalWrite(LED, HIGH); // turn the LED off so they know the CPU isn't running
#ifdef testPoint
digitalWrite(testPoint, HIGH);
// testPoint LOW in callback tracks latency from WAKE_UP_PIN LOW to testPoint LOW
#endif
float volts = ESP.getVcc();
Serial.printf("The internal VCC reads %1.3f volts\n", volts / 1000);
Serial.println(F("CPU going to sleep, pull WAKE_UP_PIN low to wake it (press the switch)"));
Serial.print(F("millis() = "));
Serial.println(millis());
Serial.flush(); // needs a delay(100) or Serial.flush() else it doesn't print the whole message
wifi_fpm_set_sleep_type(LIGHT_SLEEP_T);
gpio_pin_wakeup_enable(GPIO_ID_PIN(WAKE_UP_PIN), GPIO_PIN_INTR_LOLEVEL);
// only LOLEVEL or HILEVEL interrupts work, no edge, that's an SDK or CPU limitation
wifi_fpm_set_wakeup_cb(wakeupCallback); // Set wakeup callback (optional)
wifi_fpm_open();
wifi_fpm_do_sleep(0xFFFFFFF); // only 0xFFFFFFF works; any other value and it won't sleep
delay(10); // it goes to sleep some time during this delay() and waits for an interrupt
Serial.println(F("Woke up!")); // the interrupt callback hits before this is executed
}
// 6th test - Deep Sleep for 10 seconds, wake with RF_DEFAULT
void runTest6() {
Serial.println(F("\n6th test - Deep Sleep for 10 seconds, reset and wake with RF_DEFAULT"));
initWiFi(); // initialize WiFi since we turned it off in the last test
float volts = ESP.getVcc();
Serial.printf("The internal VCC reads %1.3f volts\n", volts / 1000);
Serial.println(F("press the switch to continue"));
while (!digitalRead(WAKE_UP_PIN)) { // wait for them to release the switch from the previous test
delay(10);
}
delay(50); // debounce time for the switch, pushbutton released
waitPushbutton(true, blinkDelay); // set true if you want to see Automatic Modem Sleep
digitalWrite(LED, LOW); // turn the LED on, at least briefly
//WiFi.mode(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..."));
Serial.print(F("millis() = "));
Serial.println(millis());
Serial.flush(); // needs a delay(10) or Serial.flush() else it doesn't print the whole message
#ifdef testPoint
digitalWrite(testPoint, HIGH); // testPoint set HIGH to track Deep Sleep period, cleared at startup()
#endif
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 off)
// maximum timed Deep Sleep interval = 71.58 minutes with 0xFFFFFFFF
// 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
Serial.println(F("What... I'm not asleep?!?")); // it will never get here
}
// 7th test - Deep Sleep for 10 seconds, wake with RFCAL
void runTest7() {
Serial.println(F("\n7th test - in RF_DEFAULT, Deep Sleep for 10 seconds, reset and wake with RFCAL"));
float volts = ESP.getVcc();
Serial.printf("The internal VCC reads %1.3f volts\n", volts / 1000);
Serial.println(F("press the switch to continue"));
waitPushbutton(false, blinkDelay); // set true if you want to see Automatic Modem Sleep
//WiFi.mode(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..."));
Serial.flush(); // needs a delay(10) or Serial.flush() else it doesn't print the whole message
#ifdef testPoint
digitalWrite(testPoint, HIGH); // testPoint set HIGH to track Deep Sleep period, cleared at startup()
#endif
ESP.deepSleep(10E6, WAKE_RFCAL); // good night! D0 fires a reset in 10 seconds...
Serial.println(F("What... I'm not asleep?!?")); // it will never get here
}
// 8th test - Deep Sleep Instant for 10 seconds, wake with NO_RFCAL
void runTest8() {
Serial.println(F("\n8th test - in RFCAL, Deep Sleep Instant for 10 seconds, reset and wake with NO_RFCAL"));
float volts = ESP.getVcc();
Serial.printf("The internal VCC reads %1.3f volts\n", volts / 1000);
Serial.println(F("press the switch to continue"));
waitPushbutton(false, blinkDelay); // set true if you want to see Automatic Modem Sleep
WiFi.mode(WIFI_SHUTDOWN, &nv->wss); // Forced Modem Sleep for a more Instant Deep Sleep
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
#ifdef testPoint
digitalWrite(testPoint, HIGH); // testPoint set HIGH to track Deep Sleep period, cleared at startup()
#endif
ESP.deepSleepInstant(10E6, WAKE_NO_RFCAL); // good night! D0 fires a reset in 10 seconds...
Serial.println(F("What... I'm not asleep?!?")); // it will never get here
}
// 9th test - Deep Sleep Instant for 10 seconds, wake with RF_DISABLED
void runTest9() {
Serial.println(F("\n9th test - in NO_RFCAL, Deep Sleep Instant for 10 seconds, reset and wake with RF_DISABLED"));
float volts = ESP.getVcc();
Serial.printf("The internal VCC reads %1.3f volts\n", volts / 1000);
Serial.println(F("press the switch to continue"));
waitPushbutton(false, blinkDelay); // set true if you want to see Automatic Modem Sleep
//WiFi.mode(WIFI_SHUTDOWN); // Forced Modem Sleep for a more Instant Deep Sleep
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
#ifdef testPoint
digitalWrite(testPoint, HIGH); // testPoint set HIGH to track Deep Sleep period, cleared at startup()
#endif
ESP.deepSleepInstant(10E6, WAKE_RF_DISABLED); // good night! D0 fires a reset in 10 seconds...
Serial.println(F("What... I'm not asleep?!?")); // it will never get here
}
void resetTests() {
float volts = ESP.getVcc();
Serial.printf("The internal VCC reads %1.3f volts\n", volts / 1000);
Serial.println(F("\nTests completed, in RF_DISABLED, press the switch to do an ESP.restart()"));
waitPushbutton(false, 1000);
ESP.restart();
}
void waitPushbutton(bool usesDelay, unsigned int delayTime) { // loop until they press the switch
// note: 2 different modes, as 3 of the power saving modes need a delay() to activate fully
if (!usesDelay) { // quick interception of pushbutton press, no delay() used
blinkLED.reset(delayTime);
while (digitalRead(WAKE_UP_PIN)) { // wait for a pushbutton press
if (blinkLED) {
digitalWrite(LED, !digitalRead(LED)); // toggle the activity LED
}
yield(); // this would be a good place for ArduinoOTA.handle();
}
} else { // long delay() for the 3 modes that need it, but it misses quick switch presses
while (digitalRead(WAKE_UP_PIN)) { // wait for a pushbutton press
digitalWrite(LED, !digitalRead(LED)); // toggle the activity LED
delay(delayTime); // another good place for ArduinoOTA.handle();
if (delayTime < 100) {
altDelay.reset(100 - delayTime); // pad the time < 100 mS with some real CPU cycles
while (!altDelay) { // this simulates 'your program running', not delay() time
}
}
}
}
delay(50); // debounce time for the switch, pushbutton pressed
while (!digitalRead(WAKE_UP_PIN)) { // now wait for them to release the pushbutton
delay(10);
}
delay(50); // debounce time for the switch, pushbutton released
}
void updateRTC() { // updates the reset count CRC
nv->rtcData.crc32 = crc32((uint8_t*) &nv->rtcData.rstCount, sizeof(nv->rtcData.rstCount));
}
void initWiFi() {
digitalWrite(LED, LOW); // give a visual indication that we're alive but busy with WiFi
uint32_t wifiBegin = millis(); // how long does it take to connect
if (WiFi.shutdownValidCRC(&nv->wss)) { // if we have a valid WiFi saved state
Serial.println(F("attempting to resume WiFi"));
if (!(WiFi.mode(WIFI_RESUME, &nv->wss))) { // try to resume it
Serial.println(F("issue resuming WiFi\n"));
}
} else {
/* Explicitly set the ESP8266 as a WiFi-client (STAtion mode), otherwise by default it
would try to act as both a client and an access-point and could cause network issues
with other WiFi devices on your network. */
WiFi.persistent(false); // don't store the connection each time to save wear on the flash
WiFi.mode(WIFI_STA);
WiFi.setOutputPower(10); // reduce RF output power, increase if it won't connect
WiFi.config(staticIP, gateway, subnet); // if using static IP, enter parameters at the top
WiFi.begin(AP_SSID, AP_PASS);
Serial.print(F("connecting to WiFi "));
Serial.println(AP_SSID);
DEBUG_PRINT(F("my MAC: "));
DEBUG_PRINTLN(WiFi.macAddress());
}
uint32_t wifiStart = millis();
while ((WiFi.status() != WL_CONNECTED) && (millis() - wifiStart < wifiTimeout)) {
yield();
}
if (WiFi.status() == WL_CONNECTED) {
DEBUG_PRINTLN(F("WiFi connected"));
} else {
Serial.println(F("WiFi timed out and didn't connect"));
}
wifiStart = millis(); // timeout if we don't get the IP addresses from DHCP
while ((!WiFi.localIP() && (WiFi.status() == WL_CONNECTED)) && (millis() - wifiStart < wifiTimeout)) {
yield();
}
WiFi.setAutoReconnect(true);
if (WiFi.localIP()) {
Serial.print(F("WiFi connect time = "));
float reConn = (millis() - wifiBegin);
Serial.printf("%1.3f seconds\n", reConn / 1000);
DEBUG_PRINT(F("WiFi Gateway IP: "));
DEBUG_PRINTLN(WiFi.gatewayIP());
DEBUG_PRINT(F("my IP address: "));
DEBUG_PRINTLN(WiFi.localIP());
} else {
DEBUG_PRINTLN(F("IP addresses not acquired from DHCP"));
}
} If you're connected through USB, this is what the serial monitor will show, plus my current measurements in BOLD with the extra chips cut off of the D1 Mini: {boot message} 1st test - running with WiFi unconfigured 2nd test - Automatic Modem Sleep 3rd test - Forced Modem Sleep 4th test - Automatic Light Sleep 5th test - Forced Light Sleep using NONOS SDK calls 6th test - Deep Sleep for 10 seconds, wake with RF_DEFAULT 7th test - in RF_DEFAULT, Deep Sleep for 10 seconds, wake with RFCAL 8th test - in RFCAL, Deep Sleep for 10 seconds, wake with NO_RFCAL 9th test - in NO_RFCAL, Deep Sleep for 10 seconds, wake with RF_DISABLED Tests completed, in RF_DISABLED, press the button to do an ESP.restart() {end of tests, it restarts from here} The tests were all run with 80MHz CPU since most ICs use more power at higher speeds. The current for Forced Light Sleep and Deep Sleep was the same for both 80 and 160MHz as the clock was stopped. |
Thank you for this - I will start having a look at it as soon as I get home from work. :-) |
@Tech-TX Looking through your comments, it seems to confirm what I have found - you can only wake from light forced sleep with an I/O pin - you can't wake from that state with a sleep time OR a pin wiggle wake. I need to think through my application and see if I can work with that, or whether its possible to get a pin wiggle from the RTC and simply diode-OR that with my external wake-up signal. Thank you for confirming "Forced Light Sleep only works with wifi_fpm_do_sleep(0xFFFFFFF); any other value and it doesn't sleep." - that saves me a lot of heartache and frustration thinking I was doing something wrong. I don't want to use Deep Sleep + RTC to reset the chip as I want to preserve status (without having to write into the RTC memory). |
@downsider7 I edited the file a little to make it more user friendly, but it still works the same. I removed everything that wasn't absolutely needed to put the chip into the various power modes. There's a LOT of examples of people with different solutions, many now out of date. Everything works with the current core & lwIP, unknown if it works on anything before 2.5.0. The table in the Espressif 'Low Power Solutions' PDF is both out of date and incomplete or confusing on the various low-power modes. Here's my revision to it: I haven't re-verified the average current in the 3 DTIM modes as I don't yet have a good way to do that. It's hard to calculate average power by visually reading a Fluke meter. 😝 I'm guessing that the CPU clock is cycling slower periodically during Automatic Light Sleep, as I see the current drop to 5mA (see the scope trace below). I suspect that will mess with the system timer that tracks time since boot / power on, but haven't verified that yet. |
This is true. There is also #6356 which tries to address this issue, with its example. Would you have a chance to test it with your hardware tools, and possibly update the core functions with your findings, in #6989 ? |
@d-a-v I'm sorry David, I didn't notice your request until late last night. It looks like your example runs OK and does what you're expecting, although I'm having a hard time following what your state machine is doing on the scope as it's frequently changing states. Was there anything in particular you wanted me to look at? I get slightly different current readings on an unmodified clone D1 Mini: compared to your readings On the board I've cut the chips off, it's even lower: I agree on updating the core functions to force the selected sleep mode independent of what the user has done. That eliminates a couple of the extra steps I had to do in my example. I see you're doing a delay() after WIFI_OFF in one place. I've just verified that the minimum is either delay(5) or yield() to insure that it goes into modem shutdown, otherwise it's erratic whether it goes to sleep or not. The most reliable seems to be delay(10) across the range of boards I've tried today. By the way, I'm walking down a bug with the SDK calls to put the chip in Forced Modem Sleep. On one of my boards I was getting soft WDT resets. The delay(5) below seems to have fixed it. wifi_station_disconnect();
delay(10); // without at least delay(5) here I get soft WDT resets on the delay() at the bottom
wifi_set_opmode(NULL_MODE);
wifi_fpm_set_sleep_type(MODEM_SLEEP_T);
wifi_fpm_open();
wifi_fpm_do_sleep(0xFFFFFFF);
delay(10); // without this minimum delay it infrequently goes into Forced Modem Sleep I don't know if this is related to the WDT resets that were described in #6266 and #6172 edit: the Forced Modem Sleep current only gets to a low of 15mA when you're in a long delay(). When your code is running it's around 19-20mA. Looping on delay(350) the current bounces around between 15 and 16mA. |
David, the only thing I noted was that WiFi.mode(WIFI_RESUME) doesn't bring it out of Forced Modem Sleep. It turns the modem on (current goes up to 67mA) but there aren't any spikes from the WiFi trying to reconnect. Interestingly, WiFi.mode(WIFI_SHUTDOWN) doesn't require a delay(10) after it like WiFi.forceSleepBegin() does. I've been running yours all day without the delay() and never seen it fail once. I've probably hit it over 100 times on 6 different boards. With WiFi.forceSleepBegin() it would fail about 25-50% of the time and not go into Forced Modem Sleep without the delay(). |
@Tech-TX What do you think of adding in #6989 :
It may be too much to do at once but your feeling about that is interesting to know. |
David, I think you're taking offense where none was meant. I didn't say your example was unclear, only that I couldn't follow the state changes on the 'scope. It was changing too frequently to take more than a brief measurement, and I'd have needed to bring out a bunch of test points to the pins to add into my mixed-signal oscilloscope so I could identify each state and the result. That's why I stopped everything at each of the tests: it allows the user to take a breath and look at what it's doing in each sleep mode. If they're trying to integrate average current over time then it's simpler when it's only doing one thing. I don't yet have a way to monitor average current/amperage, but I have all the parts. It just needs time and code. My snarky comment in the README about 'poor examples' was what I'd found on arduino.cc, Espressif's BBS and esp8266.com. I hadn't seen your example until you mentioned it, as 'WiFiShutdown' doesn't immediately bring to mind 'sleep modes'; I knew how to turn WiFi off already. 😃 I'd read a lot of pages from other people trying to get different types of sleep to work, and many were failing. That's why I did a demonstrator of the minimum required to get into all of the sleep modes. From what I understand of your demo, it's a stress-test trying to see if repeated WiFi connects/resumes are OK across Deep Sleep and/or Forced Modem Sleep, and it illustrates the new SHUTDOWN/RESUME class. You don't need to do anything with #6356 as it works. I wasn't able to use the WiFi.mode(WIFI_RESUME) as I can't figure out how it works. I'm only barely functional at C and not competent at C++ so I don't understand much of ESP8266WiFiGeneric.cpp and the relevant parts of WiFiShutdown.ino. I presume it's C++ as I don't recognize the symbols in those positions. |
No offense taken :) I take for granted that some of my examples are not clear enough. Anyway I look forward your example to be merged so I can try and improve the internal API then propose another example based on yours but using the API in the usual arduino way (simple example so one help me until it is simple enough). |
Yes, I understand what you're doing and how... I got that quickly from simple inspection. The SHUTDOWN routine stores the details from the connection in the WiFiState struct (if it exists), and the RESUME then uses that struct to restore the connection more quickly. It's a nice arrangement, and essentially halves the reconnect time (or faster). That would be handy as my router is VERY slow at reconnects at times, taking over 20 seconds. The pointers are what's tripping me up, as I've only dealt with them from the other direction (writing assembly drivers that our C folks used). I never got into C until summer, so I'm going through the learning pains now that you sailed through years ago. ;-) |
I'm almost done integrating your new modes into my tests, just need to fix one pointer I goofed up last night. I'm getting about the same resume times you are, which is encouraging. Twice as fast on reconnect is great! |
@d-a-v am I correct in assuming that WiFi.mode(WIFI_RESUME, (struct)) invalidates the stored CRC / struct once it's done resuming? That's the effect I'm seeing, and I'm not sure if it's intended. I can resume once, but when trying to resume a second time the saved state is no longer valid. It only works once after doing WiFi.mode(WIFI_SHUTDOWN, (struct)). I could copy the whole struct to another area of RTC memory as it's only 152 bytes / 38 words long, and then copy it back if the saved state isn't valid. I've updated the demo above with my latest version, including pasting bits of your WiFiShutdown code in. It's all working OK. |
Yes, nvram data is invalidated on success on purpose.
Thanks for testing this api and putting it into a better example ! |
Not better, merely different. You were looking at other problems than I was. I was frustrated in summer trying to get the Sleep modes to work, and the documentation didn't help much. When I saw downsider7 struggling with the same issues I'd had, I needed to correct the docs or make them more clear. I was a teacher for a while (and still am occasionally) so I'm a bit more wordy than most people. I write for 14 year old kids, as that's about when I started microprocessors back in the Dark Ages with a CDP1802 as my first computer. The ESP8266 with the IDE is a much nicer 'first micro' trainer today. I think I have a final copy of the demo, just going through it line-by-line to make sure everything is correct and works where I give them easy options to change things. It's both a demo and an experimenter's kit. |
@d-a-v maybe you can suggest something here. I'm trying to get the preinitWiFiOff(); to work, as it apparently got broken sometime in the last (year?). I presume it worked at one point.
Here's the quick test I did, and what I attempted in ESP8266WiFiGeneric.cpp to fix it so that preinitWiFiOff() works again. The circled numbers relate to the pin wiggles further below so you can see what is happening, and when: Here's the result on the 'scope, first a wide view of the boot + setup(), then zooming in on the preinit(), zooming again, then scrolled over to setup() in the last screen. The top trace is my pin wiggle, the lower trace is CPU amps/current. The time per division for the 'scope is at the top of the display. The last two screens are 100us per division. wifi_set_opmode_current(NULL_MODE); is the same as wifi_set_opmode_current(WIFI_OFF); as both variables are zero. I'd copied my code (cloned and fixed from the Espressif example) in place of what's in ESP8266WiFiGeneric.cpp for the preinitWiFiOff() function during this investigation. The same sequence is used in setup() in the example. The delay() in preinitWiFiOff() never executes, nor does the one at the bottom of preinit(). Short of cloning the delay() code into preinitWiFiOff() so that I can force it to idle, I don't see a way to make this work. Here's the example I used above with GPIO4 used for the pin wiggles: #include <ESP8266WiFi.h>
void preinit() {
pinMode(4, OUTPUT);
digitalWrite(4, LOW); // pin wiggle (1)
ESP8266WiFiClass::preinitWiFiOff(); // currently not working
delay(10); // never executes
digitalWrite(4, LOW); // pin wiggle (4)
}
void setup() {
pinMode(4, OUTPUT);
digitalWrite(4, HIGH); // pin wiggle (5)
digitalWrite(4, LOW); // pin wiggle
digitalWrite(4, HIGH); // pin wiggle
digitalWrite(4, LOW); // pin wiggle
wifi_set_opmode_current(NULL_MODE); // set Wi-Fi working mode to unconfigured, don't save to flash
wifi_fpm_set_sleep_type(MODEM_SLEEP_T); // set the sleep type to modem sleep
wifi_fpm_open(); // enable Forced Modem Sleep
wifi_fpm_do_sleep(0xFFFFFFF); // force modem to enter sleep mode
delay(1); // without a delay() here it doesn't reliably enter sleep
digitalWrite(4, HIGH); // pin wiggle (6)
digitalWrite(4, LOW); // pin wiggle
digitalWrite(4, HIGH); // pin wiggle
}
void loop() {
} |
Never mind, I just re-found something that accomplishes nearly the same end that igrr did back in 2016. Tested, and it somewhat works with the current git. His old code from back then doesn't work any longer as-written. It won't sleep at all, but it does stop the RFCAL. First cut, turn off the RFCAL in boot, and put as much of the Forced Modem Sleep inside preinit() as possible, with the delay(1) the first thing in setup(), #include <ESP8266WiFi.h>
RF_PRE_INIT() {
pinMode(4, OUTPUT);
digitalWrite(4, HIGH); // pin wiggle (1)
system_phy_set_powerup_option(2); // shut down the RFCAL at boot
digitalWrite(4, LOW); // pin wiggle (2)
}
void preinit() {
pinMode(4, OUTPUT);
digitalWrite(4, HIGH); // pin wiggle (3)
wifi_set_opmode_current(NULL_MODE); // set Wi-Fi working mode to unconfigured, don't save to flash
wifi_fpm_set_sleep_type(MODEM_SLEEP_T); // set the sleep type to modem sleep
wifi_fpm_open(); // enable Forced Modem Sleep, can't put this in RF_PRE_INIT or it doesn't sleep
wifi_fpm_do_sleep(0xFFFFFFF); // force modem to enter sleep mode, in RF_PRE_INIT this causes exceptions
digitalWrite(4, LOW); // pin wiggle (4)
// won't go into Forced Modem Sleep until it sees the delay(1) in setup()
}
void setup() {
digitalWrite(4, HIGH); // pin wiggle (5)
delay(1); // G'night, John-Boy
digitalWrite(4, LOW); // pin wiggle (6)
}
void loop() {
} Second cut, move as much inside RF_PRE_INIT() as will work, saving another 600+us of that modem power burst, #include <ESP8266WiFi.h>
RF_PRE_INIT() {
pinMode(4, OUTPUT);
digitalWrite(4, HIGH); // pin wiggle (1)
system_phy_set_powerup_option(2); // shut the RFCAL at boot
wifi_set_opmode_current(NULL_MODE); // set Wi-Fi working mode to unconfigured, don't save to flash
wifi_fpm_set_sleep_type(MODEM_SLEEP_T); // set the sleep type to modem sleep
digitalWrite(4, LOW); // pin wiggle (2)
}
void preinit() {
pinMode(4, OUTPUT);
digitalWrite(4, HIGH); // pin wiggle (3)
wifi_fpm_open(); // enable Forced Modem Sleep, can't put this in RF_PRE_INIT or it doesn't sleep
wifi_fpm_do_sleep(0xFFFFFFF); // force modem to enter sleep mode, in RF_PRE_INIT this causes exceptions
digitalWrite(4, LOW); // pin wiggle (4)
// won't go into Forced Modem Sleep until it sees the delay(1) in setup()
}
void setup() {
digitalWrite(4, HIGH); // pin wiggle (5)
delay(1); // G'night, John-Boy
digitalWrite(4, LOW); // pin wiggle (6)
}
void loop() {
} Slightly better power management, but you still can't boot it with a 50mA power supply unless you have a small supercap to hold the power up during the modem turn-on, That's too long of a burst for a regular electrolytic capacitor to supply unless you have a huge capacitor. I used a 1 ohm shunt for the current measurements, so the scope readings here are 50mA per major vertical division. I don't think it's worth trying to move the delay() inside preinit() as that only saves another 150us of that 70mA modem burst, The effort would be better spent moving the modem turn-on after preinit(). Looks like the second cut above is the best we'll get today. Third and final cut, using a write to the system RTC area to sort-of sleep the modem for lower power, |
Thanks for the very fine detailed analysis and clear explanation.
|
@d-a-v Calling delay(0), delay(1) or esp_schedule() 1000 times inside preinit() adds about a millisecond to the preinit(), but doesn't turn off the modem. I also tried them with a 2000 count, adding 2ms: still no joy. When I individually move these out of preinit() into RF_PRE_INIT() wifi_fpm_open();
wifi_fpm_do_sleep(0xFFFFFFF); The wifi_fpm_open() apparently doesn't execute in RF_PRE_INIT(), as it won't sleep. It must need (unknown conditions) set up before it executes that aren't there by the time of RF_PRE_INIT(). When I also move wifi_fpm_do_sleep(0xFFFFFFF) inside RF_PRE_INIT() I get this in loop():
which looks like a software WDT every 4 seconds, but without the WDT message and no stack trace. From what I can glean from the API reference, 'idle' (delay) does something in the blob or CPU that allows the power management modes to execute. It needs the OS_timer running, and I'd guess it's not initialized before preinit() happens. I found delay() in core_esp8266_wiring.cpp but don't understand what happens there as I haven't played with the OS_timer. It sets & arms the timer, executes a yield(), then disarms the timer and falls through. I get lost in the yield() and esp_schedule() code, and don't see control passing to the blob anywhere there. I know from previous tests that yield() alone won't set the power management mode, it needs delay() (so whatever is underneath esp_schedule(), or yield() and then esp_schedule()). edit: I looked in an object dump for 'idle' and found it. Unfortunately I'm not that good at reading Xtensa assembly yet, so I can see some of what it's doing in idle but not why. Looks like the idle timer is nearly the same code as something called a 'noise timer' so it merged the two routines in the compiler, which confuses things a bit. |
@downsider7 I don't know if you've been following my pull request for the demo, but I got Timed Light Sleep running several days ago. It's solid. On the boards that don't have voltage regs or USB chips, I read ~ 360uA when it's in Timed or Forced Light Sleep. Since the Timed version is only using 7 nybbles for the time, the maximum timed sleep is 2^28-1 microseconds, ~ 268 seconds. I really wish they'd used a millisecond timebase for the timed sleep modes instead of microsecond. The microsecond 'accuracy' is a complete waste, due to the irregularity in restarting the CPU clock (baseband PLL). Having a sleep that would last over a day would have been more handy. |
Just curious, how do you measure those low currents? |
@TD-er I'm running a Wavetek DM27XT DVM in-line with the power to the chip. The DVM's minimum range is 200uA full-scale, and it goes up to a 10A range. It's repeatable and at least as accurate as my Fluke, but has a lower current range than the Fluke does (2mA full-scale minimum range). I'm also monitoring on the 'scope with a 1 ohm shunt, but the 'scope isn't nearly as accurate. |
ref: esp8266/Arduino#6642 Signed-off-by: freemandealer <[email protected]>
I've followed the steps in the ESP8266 Non-OS SDK API Reference manual (v2.0.0,2016) page 97 (just checked no change in v3.0.1 of the manual)
Running on SDK 2.2.1, Core 2.5.2
Board ESP-12E
What I see is the awake message 1 second after the going to sleep message. (I've tried with delays of 1,2,5 or 10s just incase it was taking time to sleep)
Deep sleep works, the current drops to almost 0.
Modem sleep works.
I can't make force Light sleep work at all. The RF should be off, the CPU unclocked.
Does force light sleep work with any time other than 0xFFFFFFF? am I doing something daft?
The text was updated successfully, but these errors were encountered: