|
| 1 | +/* This example demonstrates the different low-power modes of the ESP8266 |
| 2 | +
|
| 3 | + My initial setup was a WeMos D1 Mini with 3.3V connected to the 3V3 pin through a meter |
| 4 | + so that it bypassed the on-board voltage regulator and USB chip. There's still about |
| 5 | + 0.3 mA worth of leakage current due to the unpowered chips, so an ESP-01 will show lower |
| 6 | + current readings than what I could achieve. These tests should work with any module. |
| 7 | + While the modem is on the current is 67 mA or jumping around with a listed minimum. |
| 8 | + To verify the 20 uA Deep Sleep current I removed the voltage regulator and USB chip. |
| 9 | +
|
| 10 | + Since I'm now missing the USB chip, I've included OTA upload. You'll need to upload |
| 11 | + from USB or a USB-to-TTL converter the first time, then you can disconnect and use OTA |
| 12 | + afterwards during any test if the WiFi is connected. Some tests disconnect or sleep WiFi |
| 13 | + so OTA won't go through. If you want OTA upload, hit RESET & press the test button once. |
| 14 | +
|
| 15 | + This test assumes you have a pushbutton switch connected between D3 and GND to advance |
| 16 | + the tests. You'll also need to connect D0/GPIO16 to RST for the Deep Sleep tests. |
| 17 | + If you forget to connect D0 to RST it will hang after the first Deep Sleep test. |
| 18 | + Connect an LED from any free pin through a 330 ohm resistor to the 3.3V supply, NOT the 3V3 |
| 19 | + pin on the module or it adds to the measured current. When it blinks you can proceed. |
| 20 | + When the LED is lit continuously it's connecting WiFi, when it's off the CPU is asleep. |
| 21 | + The LED blinks slowly when the tests are complete. |
| 22 | +
|
| 23 | + WiFi connections will be made over twice as fast if you can use a static IP address. |
| 24 | +
|
| 25 | + This example code is in the public domain, and was inspired by code from numerous sources */ |
| 26 | + |
| 27 | +#include <ESP8266WiFi.h> |
| 28 | +#include <ESP8266mDNS.h> |
| 29 | +#include <WiFiUdp.h> |
| 30 | +#include <ArduinoOTA.h> |
| 31 | +#include <PolledTimeout.h> |
| 32 | + |
| 33 | +//#define DEBUG // prints WiFi connection info to serial, uncomment if you want WiFi messages |
| 34 | +#ifdef DEBUG |
| 35 | +#define DEBUG_PRINTLN(x) Serial.println(x) |
| 36 | +#define DEBUG_PRINT(x) Serial.print(x) |
| 37 | +#else |
| 38 | +#define DEBUG_PRINTLN(x) |
| 39 | +#define DEBUG_PRINT(x) |
| 40 | +#endif |
| 41 | + |
| 42 | +#define WAKE_UP_PIN D3 // GPIO0, can also force a serial flash upload with RESET |
| 43 | + |
| 44 | +// un-comment one of the two lines below for your LED connection |
| 45 | +#define LED D1 // external LED for modules with built-in LEDs so it doesn't add to the current |
| 46 | +//#define LED D4 // GPIO2 LED for ESP-01,07 modules; D4 is LED_BUILTIN on most other modules |
| 47 | + |
| 48 | +ADC_MODE(ADC_VCC); // allows us to monitor the internal VCC level; it varies with WiFi load |
| 49 | +// don't connect anything to the analog input pin(s)! |
| 50 | + |
| 51 | +// enter your WiFi configuration below |
| 52 | +const char* AP_SSID = "SSID"; // your router's SSID here |
| 53 | +const char* AP_PASS = "password"; // your router's password here |
| 54 | +IPAddress staticIP(0, 0, 0, 0); // parameters below are for your static IP address, if used |
| 55 | +IPAddress gateway(0, 0, 0, 0); |
| 56 | +IPAddress subnet(0, 0, 0, 0); |
| 57 | +IPAddress dns1(0, 0, 0, 0); |
| 58 | +IPAddress dns2(0, 0, 0, 0); |
| 59 | + |
| 60 | + |
| 61 | +// CRC function used to ensure data validity of RTC User Memory |
| 62 | +uint32_t calculateCRC32(const uint8_t *data, size_t length); |
| 63 | + |
| 64 | +// This structure will be stored in RTC memory to remember the reset loop count. |
| 65 | +// First field is CRC32, which is calculated based on the rest of the structure contents. |
| 66 | +// Any fields can go after the CRC32. The structure must be 4-byte aligned. |
| 67 | +struct { |
| 68 | + uint32_t crc32; |
| 69 | + byte data[4]; // the last byte stores the reset count |
| 70 | +} rtcData; |
| 71 | + |
| 72 | +byte resetLoop = 0; // keeps track of the number of Deep Sleep tests / resets |
| 73 | +String resetCause = ""; |
| 74 | + |
| 75 | +const unsigned int blinkDelay = 100; // fast blink rate for the LED when waiting for the user |
| 76 | +const unsigned int longDelay = 350; // longer delay() for the two AUTOMATIC modes |
| 77 | +esp8266::polledTimeout::periodicFastMs blinkLED(blinkDelay); |
| 78 | +// use fully qualified type and avoid importing all ::esp8266 namespace to the global namespace |
| 79 | + |
| 80 | +void wakeupCallback() { // unlike ISRs, you can do a print() from a callback function |
| 81 | + Serial.println(F("Woke from Forced Light Sleep - this is the callback")); |
| 82 | +} |
| 83 | + |
| 84 | +void setup() { |
| 85 | + pinMode(LED, OUTPUT); // Activity and Status indicator |
| 86 | + digitalWrite(LED, LOW); // turn on the LED |
| 87 | + pinMode(WAKE_UP_PIN, INPUT_PULLUP); // polled to advance tests, INTR for Forced Light Sleep |
| 88 | + Serial.begin(115200); |
| 89 | + Serial.print(F("\nReset reason = ")); |
| 90 | + String resetCause = ESP.getResetReason(); |
| 91 | + Serial.println(resetCause); |
| 92 | + if (resetCause == "External System") { |
| 93 | + Serial.println(F("I'm awake and starting the low power tests")); |
| 94 | + resetLoop = 5; |
| 95 | + updateRTC(); // if external reset, wipe the RTC memory and start all over |
| 96 | + } |
| 97 | + |
| 98 | + // Read struct from RTC memory |
| 99 | + if (ESP.rtcUserMemoryRead(64, (uint32_t*) &rtcData, sizeof(rtcData))) { |
| 100 | + uint32_t crcOfData = calculateCRC32((uint8_t*) &rtcData.data[0], sizeof(rtcData.data)); |
| 101 | + if (crcOfData != rtcData.crc32) { // if the CRC is invalid |
| 102 | + resetLoop = 0; // set first test loop since power on or external reset |
| 103 | + } else { |
| 104 | + resetLoop = rtcData.data[3]; // read the previous reset count |
| 105 | + } |
| 106 | + } |
| 107 | +} // end of Setup() |
| 108 | + |
| 109 | +void loop() { |
| 110 | + if (resetLoop == 0) { |
| 111 | + // 1st test - running with WiFi unconfigured, reads ~67 mA minimum |
| 112 | + Serial.println(F("\n1st test - running with WiFi unconfigured")); |
| 113 | + float volts = ESP.getVcc(); |
| 114 | + Serial.printf("The internal VCC reads %1.3f volts\n", volts / 1000 ); |
| 115 | + Serial.println(F("press the button to continue")); |
| 116 | + waitPushbutton(false, blinkDelay); |
| 117 | + |
| 118 | + // 2nd test - Automatic Modem Sleep 7 seconds after WiFi is connected (LED flashes) |
| 119 | + Serial.println(F("\n2nd test - Automatic Modem Sleep")); |
| 120 | + Serial.println(F("connecting WiFi, please wait until the LED blinks")); |
| 121 | + init_WiFi(); |
| 122 | + init_OTA(); |
| 123 | + Serial.println(F("The current will drop in 7 seconds.")); |
| 124 | + volts = ESP.getVcc(); |
| 125 | + Serial.printf("The internal VCC reads %1.3f volts\n", volts / 1000 ); |
| 126 | + Serial.println(F("press the button to continue")); |
| 127 | + waitPushbutton(true, longDelay); |
| 128 | + |
| 129 | + // 3rd test - Forced Modem Sleep |
| 130 | + Serial.println(F("\n3rd test - Forced Modem Sleep")); |
| 131 | + WiFi.forceSleepBegin(); |
| 132 | + delay(10); // it doesn't always go to sleep unless you delay(10) |
| 133 | + volts = ESP.getVcc(); |
| 134 | + Serial.printf("The internal VCC reads %1.3f volts\n", volts / 1000 ); |
| 135 | + Serial.println(F("press the button to continue")); |
| 136 | + waitPushbutton(false, blinkDelay); |
| 137 | + |
| 138 | + // 4th test - Automatic Light Sleep |
| 139 | + Serial.println(F("\n4th test - Automatic Light Sleep")); |
| 140 | + Serial.println(F("reconnecting WiFi")); |
| 141 | + Serial.println(F("it will be in Automatic Light Sleep once WiFi connects (LED blinks)")); |
| 142 | + digitalWrite(LED, LOW); // visual cue that we're reconnecting |
| 143 | + WiFi.setSleepMode(WIFI_LIGHT_SLEEP, 5); // Automatic Light Sleep |
| 144 | + WiFi.forceSleepWake(); // reconnect with previous STA mode and connection settings |
| 145 | + while (!WiFi.localIP()) |
| 146 | + delay(50); |
| 147 | + volts = ESP.getVcc(); |
| 148 | + Serial.printf("The internal VCC reads %1.3f volts\n", volts / 1000 ); |
| 149 | + Serial.println(F("press the button to continue")); |
| 150 | + waitPushbutton(true, longDelay); |
| 151 | + |
| 152 | + // 5th test - Forced Light Sleep using Non-OS SDK calls |
| 153 | + Serial.println(F("\n5th test - Forced Light Sleep using Non-OS SDK calls")); |
| 154 | + WiFi.mode(WIFI_OFF); // you must turn the modem off; using disconnect won't work |
| 155 | + yield(); |
| 156 | + digitalWrite(LED, HIGH); // turn the LED off so they know the CPU isn't running |
| 157 | + volts = ESP.getVcc(); |
| 158 | + Serial.printf("The internal VCC reads %1.3f volts\n", volts / 1000 ); |
| 159 | + Serial.println(F("CPU going to sleep, pull WAKE_UP_PIN low to wake it (press the button)")); |
| 160 | + delay(100); // needs a brief delay after the print or it may print the whole message |
| 161 | + wifi_fpm_set_sleep_type(LIGHT_SLEEP_T); |
| 162 | + gpio_pin_wakeup_enable(GPIO_ID_PIN(WAKE_UP_PIN), GPIO_PIN_INTR_LOLEVEL); |
| 163 | + // only LOLEVEL or HILEVEL interrupts work, no edge, that's an SDK or CPU limitation |
| 164 | + wifi_fpm_set_wakeup_cb(wakeupCallback); // Set wakeup callback (optional) |
| 165 | + wifi_fpm_open(); |
| 166 | + wifi_fpm_do_sleep(0xFFFFFFF); // only 0xFFFFFFF allowed; any other value and it won't sleep |
| 167 | + delay(10); // it goes to sleep some time during this delay() and waits for an interrupt |
| 168 | + Serial.println(F("Woke up!")); // the interrupt callback hits before this is executed |
| 169 | + |
| 170 | + // 6th test - Deep Sleep for 10 seconds, wake with RF_DEFAULT |
| 171 | + Serial.println(F("\n6th test - Deep Sleep for 10 seconds, wake with RF_DEFAULT")); |
| 172 | + init_WiFi(); // initialize WiFi since we turned it off in the last test |
| 173 | + init_OTA(); |
| 174 | + resetLoop = 1; // advance to the next Deep Sleep test after the reset |
| 175 | + updateRTC(); // save the current test state in RTC memory |
| 176 | + volts = ESP.getVcc(); |
| 177 | + Serial.printf("The internal VCC reads %1.3f volts\n", volts / 1000 ); |
| 178 | + Serial.println(F("press the button to continue")); |
| 179 | + while (!digitalRead(WAKE_UP_PIN)) // wait for them to release the button from the last test |
| 180 | + delay(10); |
| 181 | + delay(50); // debounce time for the switch, button released |
| 182 | + waitPushbutton(false, blinkDelay); |
| 183 | + digitalWrite(LED, LOW); // turn the LED on, at least briefly |
| 184 | + Serial.println(F("going into Deep Sleep now...")); |
| 185 | + delay(10); // sometimes the \n isn't printed without a short delay |
| 186 | + ESP.deepSleep(10E6, WAKE_RF_DEFAULT); // good night! D0 fires a reset in 10 seconds... |
| 187 | + delay(10); |
| 188 | + // if you do ESP.deepSleep(0, mode); it needs a RESET to come out of sleep (RTC is off) |
| 189 | + // maximum timed Deep Sleep interval = 71.58 minutes with 0xFFFFFFFF |
| 190 | + // the 2 uA GPIO current during Deep Sleep can't drive the LED so it's off now |
| 191 | + Serial.println(F("What... I'm not asleep?!?")); // it will never get here |
| 192 | + } |
| 193 | + |
| 194 | + // 7th test - Deep Sleep for 10 seconds, wake with RFCAL |
| 195 | + if (resetLoop < 4) { |
| 196 | + init_WiFi(); // need to reinitialize WiFi & OTA due to Deep Sleep resets |
| 197 | + init_OTA(); // since we didn't do it in setup() because of the first test |
| 198 | + } |
| 199 | + if (resetLoop == 1) { // second reset loop since power on |
| 200 | + resetLoop = 2; // advance to the next Deep Sleep test after the reset |
| 201 | + updateRTC(); // save the current test state in RTC memory |
| 202 | + Serial.println(F("\n7th test - in RF_DEFAULT, Deep Sleep for 10 seconds, wake with RFCAL")); |
| 203 | + float volts = ESP.getVcc(); |
| 204 | + Serial.printf("The internal VCC reads %1.3f volts\n", volts / 1000 ); |
| 205 | + Serial.println(F("press the button to continue")); |
| 206 | + waitPushbutton(false, blinkDelay); |
| 207 | + Serial.println(F("going into Deep Sleep now...")); |
| 208 | + delay(10); // sometimes the \n isn't printed without a short delay |
| 209 | + ESP.deepSleep(10E6, WAKE_RFCAL); // good night! D0 fires a reset in 10 seconds... |
| 210 | + delay(10); |
| 211 | + Serial.println(F("What... I'm not asleep?!?")); // it will never get here |
| 212 | + } |
| 213 | + |
| 214 | + // 8th test - Deep Sleep Instant for 10 seconds, wake with NO_RFCAL |
| 215 | + if (resetLoop == 2) { // third reset loop since power on |
| 216 | + resetLoop = 3; // advance to the next Deep Sleep test after the reset |
| 217 | + updateRTC(); // save the current test state in RTC memory |
| 218 | + Serial.println(F("\n8th test - in RFCAL, Deep Sleep Instant for 10 seconds, wake with NO_RFCAL")); |
| 219 | + float volts = ESP.getVcc(); |
| 220 | + Serial.printf("The internal VCC reads %1.3f volts\n", volts / 1000 ); |
| 221 | + Serial.println(F("press the button to continue")); |
| 222 | + waitPushbutton(false, blinkDelay); |
| 223 | + Serial.println(F("going into Deep Sleep now...")); |
| 224 | + delay(10); // sometimes the \n isn't printed without a short delay |
| 225 | + ESP.deepSleepInstant(10E6, WAKE_NO_RFCAL); // good night! D0 fires a reset in 10 seconds... |
| 226 | + delay(10); |
| 227 | + Serial.println(F("What... I'm not asleep?!?")); // it will never get here |
| 228 | + } |
| 229 | + |
| 230 | + // 9th test - Deep Sleep Instant for 10 seconds, wake with RF_DISABLED |
| 231 | + if (resetLoop == 3) { // fourth reset loop since power on |
| 232 | + resetLoop = 4; // advance to the next Deep Sleep test after the reset |
| 233 | + updateRTC(); // save the current test state in RTC memory |
| 234 | + Serial.println(F("\n9th test - in NO_RFCAL, Deep Sleep Instant for 10 seconds, wake with RF_DISABLED")); |
| 235 | + float volts = ESP.getVcc(); |
| 236 | + Serial.printf("The internal VCC reads %1.3f volts\n", volts / 1000 ); |
| 237 | + Serial.println(F("press the button to continue")); |
| 238 | + waitPushbutton(false, blinkDelay); |
| 239 | + Serial.println(F("going into Deep Sleep now...")); |
| 240 | + delay(10); // sometimes the \n isn't printed without a short delay |
| 241 | + ESP.deepSleepInstant(10E6, WAKE_RF_DISABLED); // good night! D0 fires a reset in 10 seconds... |
| 242 | + delay(10); |
| 243 | + Serial.println(F("What... I'm not asleep?!?")); // it will never get here |
| 244 | + } |
| 245 | + |
| 246 | + if (resetLoop == 4) { |
| 247 | + resetLoop = 5; // start all over |
| 248 | + updateRTC(); // save the current test state in RTC memory |
| 249 | + float volts = ESP.getVcc(); |
| 250 | + Serial.printf("The internal VCC reads %1.3f volts\n", volts / 1000 ); |
| 251 | + Serial.println(F("\nTests completed, in RF_DISABLED, press the button to do an ESP.restart()")); |
| 252 | + waitPushbutton(false, 1000); |
| 253 | + ESP.restart(); |
| 254 | + } |
| 255 | +} |
| 256 | + |
| 257 | +void waitPushbutton(bool usesDelay, unsigned int delayTime) { // loop until they press the button |
| 258 | + // note: 2 different modes, as both of the AUTOMATIC power saving modes need a long delay() |
| 259 | + if (!usesDelay) { // quick interception of button press, no delay() |
| 260 | + blinkLED.reset(delayTime); |
| 261 | + while (digitalRead(WAKE_UP_PIN)) { // wait for a button press |
| 262 | + if (blinkLED) { |
| 263 | + digitalWrite(LED, !digitalRead(LED)); // toggle the activity LED |
| 264 | + if (WiFi.localIP()) // don't check OTA if WiFi isn't connected |
| 265 | + ArduinoOTA.handle(); //see if we need to reflash |
| 266 | + } |
| 267 | + yield(); |
| 268 | + } |
| 269 | + } else { // long delay() for the 2 AUTOMATIC modes, but it misses quick button presses |
| 270 | + while (digitalRead(WAKE_UP_PIN)) { // wait for a button press |
| 271 | + digitalWrite(LED, !digitalRead(LED)); // toggle the activity LED |
| 272 | + if (WiFi.localIP()) // don't check OTA if WiFi isn't connected |
| 273 | + ArduinoOTA.handle(); //see if we need to reflash |
| 274 | + delay(delayTime); |
| 275 | + } |
| 276 | + } |
| 277 | + delay(50); // debounce time for the switch, button pressed |
| 278 | + while (!digitalRead(WAKE_UP_PIN)) // now wait for them to release the button |
| 279 | + delay(10); |
| 280 | + delay(50); // debounce time for the switch, button released |
| 281 | +} |
| 282 | + |
| 283 | +uint32_t calculateCRC32(const uint8_t *data, size_t length) { |
| 284 | + uint32_t crc = 0xffffffff; |
| 285 | + while (length--) { |
| 286 | + uint8_t c = *data++; |
| 287 | + for (uint32_t i = 0x80; i > 0; i >>= 1) { |
| 288 | + bool bit = crc & 0x80000000; |
| 289 | + if (c & i) { |
| 290 | + bit = !bit; |
| 291 | + } |
| 292 | + crc <<= 1; |
| 293 | + if (bit) { |
| 294 | + crc ^= 0x04c11db7; |
| 295 | + } |
| 296 | + } |
| 297 | + } |
| 298 | + return crc; |
| 299 | +} |
| 300 | + |
| 301 | +void updateRTC() { |
| 302 | + rtcData.data[3] = resetLoop; // save the loop count for the next reset |
| 303 | + // Update CRC32 of data |
| 304 | + rtcData.crc32 = calculateCRC32((uint8_t*) &rtcData.data[0], sizeof(rtcData.data)); |
| 305 | + if (resetLoop == 5) // wipe the CRC in RTC memory when we're done with all tests |
| 306 | + rtcData.crc32 = 0; |
| 307 | + // Write struct to RTC memory |
| 308 | + ESP.rtcUserMemoryWrite(64, (uint32_t*) &rtcData, sizeof(rtcData)); |
| 309 | +} |
| 310 | + |
| 311 | +void init_WiFi() { |
| 312 | + /* Explicitly set the ESP8266 as a WiFi-client (STAtion mode), otherwise by default it |
| 313 | + would try to act as both a client and an access-point and could cause network issues |
| 314 | + with other WiFi devices on your network. */ |
| 315 | + digitalWrite(LED, LOW); // give a visual indication that we're alive but busy |
| 316 | + WiFi.persistent(false); // don't store the connection each time to save wear on the flash |
| 317 | + WiFi.mode(WIFI_STA); |
| 318 | + WiFi.config(staticIP, gateway, subnet); // if using static IP, enter parameters at the top |
| 319 | + WiFi.begin(AP_SSID, AP_PASS); |
| 320 | + Serial.print(F("connecting to WiFi ")); |
| 321 | + Serial.println(AP_SSID); |
| 322 | + DEBUG_PRINT(F("my MAC: ")); |
| 323 | + DEBUG_PRINTLN(WiFi.macAddress()); |
| 324 | + while (WiFi.status() != WL_CONNECTED) |
| 325 | + delay(50); |
| 326 | + DEBUG_PRINTLN(F("WiFi connected")); |
| 327 | + while (!WiFi.localIP()) |
| 328 | + delay(50); |
| 329 | + WiFi.setAutoReconnect(true); |
| 330 | + DEBUG_PRINT(F("WiFi Gateway IP: ")); |
| 331 | + DEBUG_PRINTLN(WiFi.gatewayIP()); |
| 332 | + DEBUG_PRINT(F("my IP address: ")); |
| 333 | + DEBUG_PRINTLN(WiFi.localIP()); |
| 334 | +} |
| 335 | + |
| 336 | +void init_OTA() { |
| 337 | + // Port defaults to 8266 |
| 338 | + // ArduinoOTA.setPort(8266); |
| 339 | + |
| 340 | + // Hostname defaults to esp8266-[ChipID] |
| 341 | + // ArduinoOTA.setHostname("myesp8266")); |
| 342 | + |
| 343 | + // No authentication by default |
| 344 | + // ArduinoOTA.setPassword((const char *)"123")); |
| 345 | + |
| 346 | + ArduinoOTA.onStart([]() { |
| 347 | + Serial.println(F("Start")); |
| 348 | + }); |
| 349 | + ArduinoOTA.onEnd([]() { |
| 350 | + Serial.println(F("\nEnd")); |
| 351 | + }); |
| 352 | + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { |
| 353 | + Serial.printf("Progress: %u%%\r", (progress / (total / 100))); |
| 354 | + }); |
| 355 | + ArduinoOTA.onError([](ota_error_t error) { |
| 356 | + Serial.printf("Error[%u]: ", error); |
| 357 | + if (error == OTA_AUTH_ERROR) Serial.println(F("Auth Failed")); |
| 358 | + else if (error == OTA_BEGIN_ERROR) Serial.println(F("Begin Failed")); |
| 359 | + else if (error == OTA_CONNECT_ERROR) Serial.println(F("Connect Failed")); |
| 360 | + else if (error == OTA_RECEIVE_ERROR) Serial.println(F("Receive Failed")); |
| 361 | + else if (error == OTA_END_ERROR) Serial.println(F("End Failed")); |
| 362 | + }); |
| 363 | + ArduinoOTA.begin(); |
| 364 | + yield(); |
| 365 | +} |
0 commit comments