ESP8266 — running on battery power

May 15, 2019 · 7 min read
Image for post
Image for post

The issue of running ESP8266 on battery power came when I was working on a somewhat bigger project, an IoT sensor node which has to collect some sensor data and upload the sensor values to cloud services.

What I wanted is to be able to run the sensor node for extended periods with no human intervention. I have found a 9800mAh LiPo battery that should provide me enough juice to run for several months. Still, to achieve good results I have to reduce the power draw of the ESP8266 to the minimum possible, and I also have to provide a means to protect the LiPo battery when its voltage drops below-given value (typically 3V).

By itself, this proves to be a challenging thing — enough to become a separate article. So this is a tutorial on implementing some strategies to reduce the power consumption of ESP8266 modules and of protecting LiPo batteries from over-discharging.

The first and the simplest way to cut down a few milliamps is to remove the power LED. Some ESP8266 boards have a trace that has to be cut, such as the ESP8266 Thing Dev I’m using in this tutorial. On other boards, one has to physically remove the LED — SMD hot tweezers are a must. Either way, you can shed 8–10 mA.

Then, we go deep into the ESP8266 sleep modes. There are three sleep modes, as detailed in the table below:

|                       | Modem sleep  | Light sleep  | Seep sleep |
| WiFi | OFF | OFF | OFF |
| System clock | ON | OFF | OFF |
| RTC | ON | ON | ON |
| CPU | ON | Pending | OFF |
| Substrate current | 15 mA | 0.4 mA | ~ 20µA |
| Avg. Current DTIM = 1 | 16.2 mA | 1.8 mA | – |
| Avg. Current DTIM = 3 | 15.4 mA | 0.9 mA | – |
| Avg. Current DTIM = 10| 15.2 mA | 0.55 mA | – |
Note: on some routers you cannot change the DTIM value

ESP8266 deep sleep

For a sensor node that wakes up and sends data from time to time, the most interesting is the deep sleep mode. On Arduino IDE one can put the ESP8266 in deep sleep mode by using ESP.deepSleep(sleepTimeSeconds * 1000000);

#include <ESP8266WiFi.h>

/* ESP8266 Deep-sleep mode*/
void setup() {
// Wait for serial to initialize.
while(!Serial) { }
Serial.println("I'm awake.");
Serial.println("Going into deep sleep for 20 seconds");
ESP.deepSleep(20e6); // 20e6 is 20 microseconds

void loop() {}

However, there’s a catch: to wake up the ESP8266 one has to connect the RST pin to GPIO16 (WAKE). On the ESP8266 Thing Dev you do this by closing the SJ2 jumper. On other boards, one has to use a wire to connect the RST and GPIO16 pins.

Another aspect is that the ESP8266 will lose everything in its memory, and it will run the code just as it does when it’s powered on for the first time.

Of course, one can save some context variables in the EEPROM. But, if a node wakes up every five minutes, this will result in having 288 writes to the EEPROM per day. The AT25SF041 used by ESP8266 Thing Dev is rated for 100,000 Program/Erase Cycles. That means that the EEPROM will wear in less than one year.

ESP8266 — turning off the modem

This is a neat trick I found in the Arduino examples, in ESP8266 board version 2.5.0 (or higher). It allows the ESP8266 to start with the modem off, then to start the modem at the desired moment in the code. When dealing with slow sensors such as the DGS-H2S sensor from SPEC sensors, one can use this trick to keep the modem off while the sensor data is gathered and to turn on the modem just before uploading to online services.

#include <ESP8266WiFi.h>

#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"

// preinit() is called before system startup
// from nonos-sdk's user entry point user_init()

void preinit() {
// Global WiFi constructors are not called yet
// (global class instances like WiFi, Serial...
// are not yet initialized)..
// No global object methods or C++ exceptions
// can be called in here!
// The code line below is a static class method,
// which is similar to a function, so it's ok.

void setup() {
Serial.println("sleeping 5s");

// during this period, a simple amp meter shows
// an average of 20mA with a Wemos D1 mini

Serial.println("waking WiFi up, sleeping 5s");

// amp meter raises to 75mA
Serial.println("connecting to AP " STASSID);
// amp meter cycles within 75-80 mA


void loop() {

This is where things become interesting. By the way most ESP8266 boards are designed, one cannot read the battery voltage Vin without external components. But there is a method to detect a discharged battery indirectly, by measuring the input voltage of ESP8266 — that would be V3.3.

// Load Wi-Fi library
#include <ESP8266WiFi.h>

// Replace with your network credentials
const char* ssid = "myssid";
const char* password = "mypassword";

int Batt;

// Set web server port number to 80
WiFiServer server(80);

// Variable to store the HTTP request
String header;

void setup() {

// Connect to Wi-Fi network with SSID and password
Serial.print("Connecting to ");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
// Print local IP address and start web server
Serial.println("WiFi connected.");
Serial.println("IP address: ");

void loop(){
// Listen for incoming clients
WiFiClient client = server.available();

if (client) {
Serial.println("new client");
// an http request ends with a blank line
boolean currentLineIsBlank = true;
while (client.connected()) {
if (client.available()) {
char c =;
// if you've gotten to the end of the line (received a newline
// character) and the line is blank, the http request has ended,
// so you can send a reply
if (c == '\n' && currentLineIsBlank) {
// send a standard http response header
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close");
// the connection will be closed after completion
// of the response
client.println("Refresh: 5");
// refresh the page automatically every 5 sec
client.println("<!DOCTYPE HTML>");

// output the battery value
Batt = ESP.getVcc();
client.println("Battery voltage is: ");
// refresh the page automatically every 5 sec

if (c == '\n') {
// you're starting a new line
currentLineIsBlank = true;
} else if (c != '\r') {
// you've gotten a character on the current line
currentLineIsBlank = false;
// give the web browser time to receive the data
// close the connection:
Serial.println("client disconnected");


In the code example above, the ESP8266 is configured to read the V3.3 voltage by placing ADC_MODE(ADC_VCC); on top of the sketch.

Then, the battery voltage in mV is read as in uint32_t getVcc = ESP.getVcc();

In my experiments, I found the following relation between Vin, V3.3 and the Vmeasured by ESP.getVcc(); This applies to the ESP8266 Thing Dev, which uses AP2112K-3.3V LDO regulator. Some other boards might behave differently so you will have to redo my experiment.

Image for post
Image for post
|       Vin       |         V3.3       |      Vmeasured/1000    |
| 5 | 3.2904 | 3.475 |
| 4.9 | 3.2905 | 3.475 |
| 4.8 | 3.2905 | 3.475 |
| 4.7 | 3.2904 | 3.474 |
| 4.6 | 3.2905 | 3.475 |
| 4.5 | 3.2904 | 3.474 |
| 4.4 | 3.2905 | 3.475 |
| 4.3 | 3.2905 | 3.475 |
| 4.2 | 3.2906 | 3.475 |
| 4.1 | 3.2905 | 3.475 |
| 4 | 3.29 | 3.474 |
| 3.9 | 3.2899 | 3.474 |
| 3.8 | 3.2899 | 3.474 |
| 3.7 | 3.2898 | 3.474 |
| 3.6 | 3.2897 | 3.473 |
| 3.5 | 3.2897 | 3.473 |
| 3.4 | 3.2895 | 3.472 |
| 3.3 | 3.2575 | 3.44 |
| 3.2 | 3.153 | 3.331 |
| 3.1 | 3.0555 | 3.226 |
| 3 | 2.947 | 3.106 |
| 2.9 | 2.775 | 2.936 |
| 2.8 | 2.7 | 2.87 |

First of all, we see there’s an offset of about 0.18V between the Vmeasured and the actual V3.3. One can leave it like this or can compensate in the software.

Then we notice that, when the input voltage Vin = 3.3V, we have V3.3 = 3.2575 (it becomes to drop). The ESP8266 senses this small voltage drop, and it measures 3.44V.

Further, when the battery voltage drops to 3V (which is the safe margin to discharge LiPo batteries), the readout of the ESP.getVcc() is 3.106V. We can use this value to trigger a deep sleep to keep the battery from discharging, as in the code below:

// Low voltage detection
// Note that we read Vin, and not the battery voltage,
// as the battery voltage is not accessible to be measured
// ESP8266 thing uses AP2112K-3.3V
// Low DropoutVoltage (3.3V): 250mV (Typ.) @IOUT=600mA
int Batt;

// do other things here

void setup() {
// Check battery status
// Reads Vin (not battery voltage!!!)
// But Vin = battery voltage if battery_voltage < 3.3V
Batt = ESP.getVcc();
// If the battery is discharged don't go any further!!!
if(Batt < 3100){
// Deep sleep for as long as you can
// your code goes here

void loop() {
// your code goes here

What I did here is read the battery voltage one the ESP8266 module starts, and if the value returned by the ESP.getVcc() is below the safe threshold, the ESP8266 goes into deep sleep for as long as it can. Then it will wake up and go back to deep sleep until the battery is recharged.

Please observe that there will be other components (sensors, etc.) that will still drain power from the battery, but the overall discharge rate is considerably slowed down.

Originally published at on May 15, 2019. Moved on Medium on April 23, 2020


DIY electronics projects and more

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store