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.

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

  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

Comments are closed.