Reducing WiFi power consumption on ESP8266, part 3

Welcome to part 3 of this series on reducing WiFi power consumption on ESP8266 chips.

Earlier, I have established the baseline power consumption and shown how to reduce this a bit by disabling the radio when it is not needed.

This time, I’ll take it a step further by showing how to make sure the radio is needed for a shorter period of time.

In the first part I showed that in my case, where the ESP8266 wakes up every 5 minutes to read some sensors and transmit the results to the server.  This whole process takes 8.3 seconds, and a whole 6.45 seconds of this is taken up by the ESP8266 establishing a connection to the WiFi network and configuring itself by DHCP.

Disabling network persistence

I mentioned before that the ESP8266 will persist the network connection information to flash, and then read this back when it next starts the WiFi function.  It does this every time, and in my experiments I have found that this takes at least 1.2 seconds.  There are cases where the WiFi function would crash the chip, and the WiFi would never connect.

The chip also does this even when you pass connection information to WiFi.begin(), i.e. even in the case below:

WiFi.begin( WLAN_SSID, WLAN_PASSWD );

This will actually load the connection information from flash, promptly ignore it and use the values you specify instead, connect to the WiFi and then finally write your values back to flash.

This starts wearing out the flash memory after a while.  Exactly how quickly or slowly will depend on the quality of the flash memory connected to your ESP8266 chip.

The good news is that we can disable or enable this persistence by calling WiFi.persistent().  Our code to enable the WiFi network will then look like this:

WiFi.forceSleepWake();
delay( 1 );

// Disable the WiFi persistence.  The ESP8266 will not load and save WiFi settings in the flash memory.
WiFi.persistent( false );

WiFi.mode( WIFI_STA );
WiFi.begin( WLAN_SSID, WLAN_PASSWD );

Let’s see how this affects the power consumption:

The cycle has become longer, but on closer inspection of the graph and network packets the main reason for that is that DHCP took longer this time.  Unfortunately, the DHCP time is quite variable on my network, probably something I need to look into at some stage but not today.  (Ah, the noble art of procrastination…)

What is missing this time, though, is the 1.2 seconds between AP association and the start of DHCP negotiation.  That’s 1200 ms at 71 mA, or 0.023 mAh saved.  Combined with the 0.024 mAh saved so far, we’ve saved 0.047 mAh in total.  And we’ve most likely increased the life time of the flash chip as well.

Configuring static IP

As the DHCP negotiation is taking quite a while, the next step will be to disable DHCP and configure the WiFi connection statically instead.

Of course, depending on your network setup this may not be a practical solution, but if your goal is to reduce the power consumption as far as we can go, it’s worth looking at this as well.

Assuming that the sensor network operates with an IP range of 192.168.0.1 – 192.168.0.255, network mask 255.255.255.0 and 192.168.0.254 as a gateway, we can configure it as follows:

IPAddress ip( 192, 168, 0, 1 );
IPAddress gateway( 192, 168, 0, 254 );
IPAddress subnet( 255, 255, 255, 0 );

WiFi.forceSleepWake();
delay( 1 );
WiFi.persistent( false );
WiFi.mode( WIFI_STA );
WiFi.config( ip, gateway, subnet );
WiFi.begin( WLAN_SSID, WLAN_PASSWD );

The time taken to negotiate a DHCP lease is now completely gone, and along with it the variability in the wake/sleep cycle.

The total power consumption is now down to 0.07 mAh per cycle, less than half of the 0.164 mAh we started with before optimising.

There may be other areas to target, so I will continue researching the impact different access points and interleaving operations.

10 thoughts on “Reducing WiFi power consumption on ESP8266, part 3”

  1. Thank You, man!
    Your article was very helpfull for me. I wanted use for powered ESP from garden solar light. I had problem with using only one solar cell. When I applicated your solution, rasantelly fall consumed current, and now enough for powering!
    Again thanks, many success in future projects!

    1. Happy to help. I’m just about to start a solar powered project on one of these chips myself. The idea is to let the solar cell charge my batteries enough while the ESP sleeps to both power the device when it wakes up as well as through the night.
      Hopefully I will have enough sun during the day to get results as good as yours 🙂

  2. Hello,

    Thanks for these tutos … but unfortunately, I missed something 🙁

    Here my code :

    #include
    #include /* https://pubsubclient.knolleary.net/api.html */

    #include “Maison.h” /* WiFi settings */
    #define LED_BUILTIN 2

    #define MQTT_PUBTOPIC “TestPub”
    #define MQTT_CLIENT “TestWiFiDS”

    IPAddress adr_ip(192, 168, 0, 17);
    IPAddress adr_gateway(192, 168, 0, 10);
    IPAddress adr_dns(192, 168, 0, 3);

    WiFiClient clientWiFi;
    PubSubClient clientMQTT(clientWiFi);

    void connexion_WiFi(){
    WiFi.forceSleepWake(); // Réveil le WiFi
    delay(10);
    Serial.println(“Connexion WiFi”);

    WiFi.persistent( false );

    WiFi.mode(WIFI_STA);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    while(WiFi.status() != WL_CONNECTED){
    delay(500);
    Serial.print(“.”);
    }

    Serial.print(“ok : adresse “);
    Serial.println(WiFi.localIP());
    }

    void Connexion_MQTT(){
    digitalWrite(LED_BUILTIN, LOW);
    Serial.println(“Connexion MQTT”);
    while(!clientMQTT.connected()){
    if(clientMQTT.connect(MQTT_CLIENT)){
    Serial.println(“connecté”);
    break;
    } else {
    Serial.print(“Echec, rc:”);
    Serial.println(clientMQTT.state());
    delay(1000); // Test dans 1 seconde
    }
    }
    digitalWrite(LED_BUILTIN, HIGH);
    }

    void setup() {
    long debut = millis(); // Calcul du temps de connexion

    /*
    * Init hardward
    */
    Serial.begin(115200); // debug
    pinMode(LED_BUILTIN, OUTPUT); // La LED est allumée pendant la recherche de WiFi

    /*
    * Init WiFi
    */
    WiFi.config(adr_ip, adr_gateway, adr_dns); // Service de base du réseau
    connexion_WiFi();

    /*
    * Envoi du message MQTT
    */
    clientMQTT.setServer(BROKER_HOST, BROKER_PORT);
    Connexion_MQTT();

    long q=millis();
    clientMQTT.publish(MQTT_PUBTOPIC, String( q-debut ).c_str());
    Serial.println( String( q-debut ).c_str() );

    WiFi.disconnect( true ); // Explicitement désactive le WiFi
    delay( 1 );
    ESP.deepSleep(10e6, WAKE_RF_DISABLED);
    }

    void loop() {
    }

    But at wakeup, my ESP-12 stays forever in loop
    while(WiFi.status() != WL_CONNECTED){
    delay(500);
    Serial.print(“.”);
    }

    So it seems the WiFi is never connected.

    I think it’s a stupid mistake … but it drives me nuts and I can’t where I’m wrong.
    Any idea ?

    Thanks

    1. Bonjour.

      I’ve compiled your program for one of my own modules here and was able to reproduce exactly the same problem here.
      It is easily fixed, though, by adding the following lines to the beginning of your setup() function:

      WiFi.mode( WIFI_OFF );
      WiFi.forceSleepBegin();
      delay( 1 );

      It looks like my code does not initialise the WiFi radio properly, otherwise. My process was quite iterative, so each optimisation step was building on what came before, and it looks like the WiFi.forceSleepWake() and WiFi.mode() calls depend on this initialisation to happen first.

      One other thing that I usually do is to place a timeout on the WiFi connection, so that if a connection has not been established by a certain number of loops, I will reset the module. (Or send it back to sleep, depending on what is most suitable for the situation)

      1. Thanks for your informations!
        I will try it on my esp, but i don’t know if it’s possible to activate and deactivate the wifi in a loop like a timer during only a period of time like…each 2 minutes between my first action and my last action, i want to use the wifi to retrieve data from a mqtt only during a period of time

        1. Hi Rudy.
          THat should be possible. I have an example sketch here which keeps the WiFi disabled until I need it, then I enable the WiFi, do what needs to be done and then disable it again, without going back to deep sleep. After a minute, the loop repeats, the WiFi is enabled again, and so on.

          I’m posting it below, so you can see what I am talking about.

          #include "test.h"

          #include
          #include

          #include

          const char* WLAN_SSID = "XXXXXXXX";
          const char* WLAN_PASSWD = "????????";

          int loops = 0;

          void setup() {
          WiFi.mode( WIFI_OFF );
          WiFi.forceSleepBegin();
          delay( 1 );

          Serial.begin( 115200 );

          while ( !Serial );

          Serial.println( "Booted, and have disabled WiFi radio" );
          }

          void loop() {
          loops++;
          Serial.println ( "Entering loop()" );

          Serial.println ( "Enabling WiFi" );
          WiFi.forceSleepWake();
          delay( 1 );
          WiFi.persistent( true );
          WiFi.mode( WIFI_STA );

          WiFi.begin( WLAN_SSID, WLAN_PASSWD );

          int retries = 0;
          int wifiStatus = WiFi.status();
          while ( wifiStatus != WL_CONNECTED )
          {
          retries++;
          if( retries == 300 )
          {
          Serial.println( "No connection after 300 steps, powercycling the WiFi radio. I have seen this work when the connection is unstable" );
          WiFi.disconnect();
          delay( 10 );
          WiFi.forceSleepBegin();
          delay( 10 );
          WiFi.forceSleepWake();
          delay( 10 );
          WiFi.begin( WLAN_SSID, WLAN_PASSWD );
          }
          if ( retries == 600 )
          {
          WiFi.disconnect( true );
          delay( 1 );
          WiFi.mode( WIFI_OFF );
          WiFi.forceSleepBegin();
          delay( 10 );

          if( loops == 3 )
          {
          Serial.println( "That was 3 loops, still no connection so let's go to deep sleep for 2 minutes" );
          Serial.flush();
          ESP.deepSleep( 120000000, WAKE_RF_DISABLED );
          }
          else
          {
          Serial.println( "No connection after 600 steps. WiFi connection failed, disabled WiFi and waiting for a minute" );
          }

          delay( 60000 );
          return;
          }
          delay( 50 );
          wifiStatus = WiFi.status();
          }
          Serial.print( "Connected to " );
          Serial.println( WLAN_SSID );
          Serial.print( "Assigned IP address: " );
          Serial.println( WiFi.localIP() );
          Serial.println( "WiFi connection successful, waiting for 10 seconds" );
          delay( 10000 );

          MQTT_uploadToServer();

          WiFi.disconnect( true );
          delay( 1 );
          WiFi.mode( WIFI_OFF );
          WiFi.forceSleepBegin();
          delay( 5 );

          if( loops == 3 )
          {
          Serial.println( "That was 3 loops, so let's go to deep sleep for 2 minutes" );
          Serial.flush();
          ESP.deepSleep( 120000000, WAKE_RF_DISABLED );
          }
          else
          {
          Serial.println( "All done, disabled WiFi and will wait for a minute." );
          delay( 60000 );
          }
          }

          void MQTT_uploadToServer()
          {
          WiFiClient client;
          float temp = -500; // Use this as a flag, as -500 degrees is physically impossible, it is below absolute zero
          int8_t ret;

          Adafruit_MQTT_Client mqtt( &client, MQTT_SERVER, MQTT_SERVERPORT, MQTT_USERNAME, MQTT_PASSWD );
          Adafruit_MQTT_Publish publish_temp = Adafruit_MQTT_Publish( &mqtt, MQTT_USERNAME "/feeds/" MQTT_NAME "/temp", MQTT_QOS_0, 1 );

          Serial.println("Connecting to MQTT... ");

          uint8_t retries = 3;
          while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected
          Serial.println(mqtt.connectErrorString(ret));
          Serial.println("Retrying MQTT connection in 5 seconds...");

          mqtt.disconnect();
          delay(5000); // wait 5 seconds
          retries--;
          if (retries == 0) {
          // basically just hang around and wait for the watchdog to come and eat us.
          while (1);
          }
          }
          Serial.println( "MQTT Connected!" );
          Serial.println( "Sending MQTT message" );
          publish_temp.publish( temp );

          delay( 50 );
          mqtt.disconnect();
          Serial.println("Disconnected from MQTT");
          }

          1. Hi,

            I see the source code above has a delay( 50 ); after doing publish_temp.publish( temp );
            I am curious as to why it has to be there?
            Im publishing 3 messages in a row before disonnecting and going to deep sleep and if i dont use a delay most of the time only the first message gets sent. This is when not reading the serial port, if I read the serial port usally most of the messages gets sent. Probably because the time before disconnect and deep sleep is longer I guess.

            snippet:
            ………..
            client.publish(“/esp12f/ds18b20”, String(ds18b20Temp).c_str());
            client.publish(“/esp12f/dht22-temp”, String(dhtTemp).c_str());
            client.publish(“/esp12f/dht22-hum”, String(dhtHum).c_str());
            Serial.println(“Publish complete”);
            delay(50);
            unsigned long uploadTime = millis()-start;

            Serial.print(“Upload took: “);
            Serial.println(uploadTime);
            client.disconnect();
            delay(1);
            WiFi.disconnect( true );
            delay( 1 );
            ESP.deepSleep(sleepTime, WAKE_RF_DISABLED );

            ps. Thanks for an awsome guide!

          2. In my case, I had problems with the chip not going into full deep sleep mode without a bit of a delay before sleeping.
            There were two reasons for this. The first is that after instructing the WiFi function to go to sleep, control needs to return to the WiFi runtime in order for this to actually happen. That’s why I put delay(1) after any calls to change the WiFi state.

            The second reason, and I don’t have the tools to debug into the WiFi runtime itself to confirm this, but from what I could gather, is that if there is any commands sent to the WiFi module after instructing it to go to sleep, then it will not go into full deep sleep. It appears that there is some activity happening as part of the publishing and/or MQTT disconnection that means a bit longer delay is needed before shutting down the WiFi. The delay() solved that for me, but I don’t know if that’s the only reason it got solved.

            I hope that makes sense. I know it took a long time before it started making sense to me when I was working on this.

  3. Hi ebakke,
    I am doing pretty the same, designing a WIFI temperature / humidity sensor to report to a server (another ESP system)
    I followed basically your advice and get a wake time of unbelivable 200ms to connect to the server, read out the HTU21 and send the data via UDP. Reading the sensor is done while connecting.

    However, I go to deep sleep with WAKE_RFCAL and omit the forceSleep calls.
    The 200ms are measured from the begin of setup to the point where I go back to sleep.

    The ESP needs another 300ms or so from reset until it enters setup, so I have an overall on-time of about 500ms consuming an average of 70mA -> tis makes 10µAh per measurement.
    I also tried your version with going to sleept with WAKE_RF_DISABLED and enabling WIFI again in setup so the first 300ms only consume about 13mA. Then however the connection takes 400ms, so there is no advantage in this.

    I have no idea, why my connection is so much faster – probably my router is connecting faster (a AVM fritz!box) or the ESP I am using has some newer firmware or what ever. When sleeping I measure about 18µA including the HTU21 – so I am sure the device is really in deep sleep.

    As a board I am using a modified Wemos D1 mini with a split up 3.3V power source so the USB system does not draw any power when not active.

    Rainer

    1. Yes, the WiFi AP has a significant impact on how long it takes to connect, as does the DHCP server implementation. (Typically in the same unit as the AP/router, but not necessarily so in more complex networks.)

      I have added code in a more recent post to reduce the time taken to connect, and also to use static IPs. Static IPs are not a problem in my network, as I don’t add and remove devices too often. Every couple of months, at most. The devices also have their own, dedicated network, which is not connected to anything else, except through a management gateway server (Raspberry Pi) which is dual-homed.

      I’ll run some more experiments with WAKE_RFCAL and see how the total power consumption works out now with the quick connect function. It stands to reason that subsequent optimisations can obviate the need for other optimisations or indeed open up further optimisations elsewhere.

      Your deep sleep current of 18µA is pretty good, not much board overhead there. I’m getting close to that, the Adafruit Huzzah breakout board doesn’t have a USB connection, one of the reasons I like it for this kind of work, so there is very little overhead. Some, but not much.

      Thanks for your suggestions.

Leave a Reply

Your email address will not be published. Required fields are marked *