Monitoring LiPo battery voltage with Wemos D1 minibattery shield and Thingspeak

There are a million reasons why you would want to monitor the Battery voltage of  your Battery fed ESP8266. I will illustrate it with a Wemos D1 mini and the Battery shield

Wemos D1 Mini Battery shield

I am using a small 720 mAh LiPo cel. If I just leave the Wemos access the internet continuously it will last 6.5 hours, but for this example I will put the Wemos in Deepsleep for a minute, then read the battery voltage and upload that to Thingspeak.
You only need to make a few connections:
First, connect RST with GPIO16 (that is D0 on the Wemos D1 mini). This is needed to let the chip awake from sleep.
Then connect the Vbat  through a 100k resistor to A0.

So why a 100 k resistor?

Well the Wemos D1 mini already has an internal voltage divider  that connects the A0 pin to the ADC of the ESP8266 chip. This is a 220 k resistor over a 100 k resistor

Wemos D1 Internal Voltage divider
Wemos D1 Internal Voltage divider

By adding a 100k , it will in fact be a total resistance  of 100k+220k+100k=420k.
So if the Voltage of a fully loaded Cell would be 4.2 Volt, the ADC of the ESP8266 would get 4.2 * 100/420= 1 Volt

1 Volt is the max input to the ADC and will give a Raw reading of 1023.

The True voltage  then can be calculated by:
raw = AnalogRead(A0);voltage =raw/1023;
voltage =4.2*voltage;
Ofcourse you could also do that in one step, but I like to keep it easy to follow.

Wemos Battery monitoring
Wemos Battery monitoring

If you do use this possibility, do realise that the resistors drain the battery as well with a constant 10uA (4.2V/420 000ohm). The powerconsumption of an ESP8266 in deepsleep is about 77uA. With the battery monitor this would be 87uA, which is a sizeable increase. A solution could be to close off the Vbat to the A0 with a transistor, controlled from an ESP8266 pin

A program could look like this:


 * Wemos battery shield, measure Vbat
 * add 100k between Vbat and ADC
 * Voltage divider of 100k+220k over 100k
 * gives 100/420k
 * ergo 4.2V -> 1Volt
 * Max input on A0=1Volt ->1023
 * 4.2*(Raw/1023)=Vbat

// Connect RST en gpio16 (RST and D0 on Wemos)
#include <ESP8266WiFi.h>
unsigned int raw=0;
float volt=0.0;
// Time to sleep (in seconds):
const int sleepTimeS = 60;

void setup() {
  Serial.println("ESP8266 in normal mode");
  const char* ssid     = "YourSSID";
  const char* password = "YourPW";
  const char* host = "";
  const char* writeAPIKey="YourAPIkey";
  // put your setup code here, to run once:
  pinMode(A0, INPUT);
  raw = analogRead(A0);
//  Connect to WiFi network
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
  String v=String(volt);// change float into string
  // make TCP connections
  WiFiClient client;
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
  String url = "/update?key=";
  url += writeAPIKey;
  url += "&field6=";// I had field 6 still free that's why
  url += String(volt);
  url += "\r\n";
// Send request to the server
  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
                 "Host: " + host + "\r\n" +
                 "Connection: close\r\n\r\n");

   Serial.println("ESP8266 in sleep mode");
   ESP.deepSleep(sleepTimeS * 1000000);              

void loop() {
  //all code is in the Setup


33 thoughts on “Monitoring LiPo battery voltage with Wemos D1 minibattery shield and Thingspeak”

  1. Depending of course on what your project is required to do, my advice would be to put the thing in the deepest possible sleep once it hits the low end of the battery voltage, maybe only to sound a piezo buzzer for say 200 ms when it shortly wakes up. That will be the safest bet to keep your LiPo alive as long as possible. The cell shown probably has a low voltage protection circuit build in though. Still, the warning is helpful.

    Sidenote: The other day I accidentally miswired a cell like that (600 mA version), reversed polarity. In a few ms, the magic smoke appeared and the cell seemed to have lost quite a bit of it’s capacity. Hard to believe things going south that fast. Turned out the protection circuit had fried a bit. Without it, the cell runs fine with 550 mAh betweem 3.0 and 4.2 volt.

    1. I agree. It is quite easy to do that. My intention ofcourse is that it will not get too low because of a solar cell that is to provide enough input.
      esp.deepsleep is as far as i know the deepest sleep, but when the battery voltage hits 3.0 it might be wise to let the deepsleep last as long as possible and do no uploads anymore.
      I am not sure at what voltage the esp stops working, but it does still work at 3Volt, and will proceed to discharge the cell.
      After 3.5 days my cell has discharged till 3.7 Volt. Obviously that still can be better, but the battery shield is not the most efficient. I will see how long it will take for the last 0.7Volt. Will repeat it with a bare ESP8266-12

      Yes, some components really dont like reversed potential, but seems you still had some luck

  2. From “the guy with the Swiss accent” I understood there is a setting that the ADC on the ESP8266 reports Vcc without any external components. Might save two resistors and a few uA. I think the episode where he experimented with a Li-Ion button cell.

    1. that is true, but that reports the Vcc on the board, which under normal circumstances would be 5 Volt, whereas I wanted to measure the direct battery voltage that even when lower, ideally should still give 5 Volts on the board. But I will check his video to make sure. Thanks
      Having said that… “the guy with the Swiss accent” is a hero

      1. Are you sure? AFAIK it reports the Vvv on the bare ESP module (so shoud be 3.3-ish). Now I know you want the battery voltage, and after boosting and then dropping to 3.3, all is lost. This is IMHO very usable if you hook up an ESP to a Lipo with just a good LDO. Agreed about “the guy”!!

      2. my mistake. I mean the VCC on the esp indeed.
        Needless to state the battery shield is not a good low power option at all.

  3. Hi,

    i added the 100k resistor like explained above, but there is some deviation between the readings from the esp8266 and by manually checking the voltage with the multimeter. In my setup a 18650 battery with 3000mAh is used, powering a wemos d1 mini with dht22-shield and battery shield. The 18650 had a voltage of 3.53V when i measured with the multimeter, the reading from A0 was 3.24V. Currently i am charging, at 4.09V (Multimeter) A0 reports 3.88V. So there is some inaccuracy betwen 0.21V and 0.27V. While i could “fix” that via adding an offset, i’m interested in the reason behind this.

    1. could be many reasons. Your multimeter might not be accurate and then again there might be a tolerance in the resistor(s)
      What the software and hardware does is based on calculation. The A0 needs 1 Volt. So ideally a 100k resistor on top of the internal voltage divider should give 1 V at A0 when the resistor is fed with 4.2 Volt. However if any of these resistors is not the exact value it is labelled for, the outcome is ofcourse different

    2. I put a 100K from the 3.3v pin to A0 and I am getting within .05v. That can be easily adjusted in the program. There will always be slight variations.

    1. The connector on MY lipo battery is the standard lipocell connector. I think it is called a JST-PH. The shield however has a different connector, I think that is a JST-XH

  4. Hi, I see that Wemos has new battery shield V1.2 where is possible to connect resistor 130K to A0 via jumper 2 (J2). How the calculation will be looks like if we will have 1.3V not 1V in this case?

  5. Has anyone got any tips for calibrating the ADC values against the battery capacity?

    I have a WeMos D1 mini. I’m not convinced its genuine but it seems to work fine. I measured across A0 and GND with two separate multimeters and got a reading of 314K and 316k respectively, so somewhat short of the intended 320k voltage divider (unless there is another path to ground via the ADC itself even when the wemos is powered off in which case that could lower the resistance value, right?). I don’t know how to work out the value for each of R1 and R2 without identifying exactly which tiny SMD resistors they are and trying to scratch away at the lacquer than may be coating the exposed contacts.

    With an added 100k resistor between A0 and the battery positive terminal (carbon film, actually reads at 97k on the multimeters) and an 18650 LiPo battery as a power supply for the WeMos, charged to 4.16V I get a reading of 917 on the ADC. This seems quite low, even if I play around with the numbers for R1 and R2 quite significantly.

    I just wondered if anyone has a good way to baseline the ADC so that I can make the battery monitor as precise as possible. I’m not looking for exacting +/- 0.01V accuracy or anything, just a way to tune the readings I get to be as accurate as possible.

    thanks for reading

    1. There are several ways to do that and I think you may be making it yourself a bit unnecessarily difficult.
      For one you could just measure r1 between A0 and ADC and see what r2 gives you by measuring between ADC and mass. R1 should give you an accurate reading and the r2 reading might be influenced by the gate itself, but I presume if you switch off the Wemos it should be pretty accurate.

      Another way is to use a known voltage and apply that to the A0. Now the ADC reads a max of 1 Volt, so, if your resistors were correct, applying 3.2Volt, should put 1 Volt on the ADC and your reading should be 1023.
      If it isnt, you can calculate the ratio of the resistors, but it would be simpler to just use a correction factor in your software.
      As you seem mainly interested in measuring the battery, obviously you would need an extra series resistor and you can play around with that value, or use a correction factor again in your software. Be sure though that you are not measuring more than 1 Volt on your ADC, it will not immediately destroy your Wemos, but it will clip the top value readings.
      A reading of 917 is in fact 917/1023 Volt =0.896 Volt =0.9Volt,
      Suppose that carbon resistor is indeed 97k, you get two equations:
      You can calculate r1 and r2 from that by substitution.
      This ofcourse all under the premises that the readings you gave were correct.
      I suggest you apply some other voltages and recalculate to see if it gives the same values
      But as I suggested earlier you could just measure r1 between A0 and ADC and see what r2 gives you by measuring between ADC and mass
      All those findings should be very close

      You may make it even easier by just using r1 and r2 and put a voltage of 3Volt on it and see what reading you then get. That way you eliminate the 97k from the equation.

      ofcourse you could just use a trimpot for the extra series resistor and trim that till you get the required output on the ADC

      1. Thank you, that is excellent advice and I really appreciate you taking the time to reply. For about 30 minutes I couldn’t work out what you meant by “just measure between A0 and the ADC”, then I realised that the ADC pin of the ESP12-E board that is soldered on top of the WeMos board is available to be probed with my multimeter. Now I feel a little silly. I’ll let you know how I get on with it, as you say I will probably just end up using a correction factor in my software but I wanted to check I wasn’t missing a trick elsewhere

  6. So I managed to solder a piece of wire to the ADC pin of the ESP-12E chip. It seemed that just touching it with the multimeter lead yielded no reading. I guess there is some sort of flux/lacquer covering the contacts to prevent oxidisation.

    I got a reading of 216k between the A0 pin of the WeMos and the ADC pin of the ESP and a reading of 98.0k between the ADC pin and the GND pin of the WeMos. So putting those numbers into the voltage divider calculations (along with the additional 100k resistor that reads as 97k on the multimeter) yields the following:

    Vout = (Vin x R2) / (R1 + R2)
    Vout = (4.16 x 98) / (98+313) = 0.99V

    So the *expected* ADC value given an input of 4.16V should be:
    ADC = (Max-mV / Max-ADC-value) * mV-in
    (that is the max ADC voltage in mV divided by the maximum ADC reading, multiplied by the input voltage in mV)
    ADC = (1000 / 1023) * 990 = 967

    This is still a long way above the ADC value of 917 that I get when I construct the circuit and flash the WeMos with my code.

    Then last night it dawned on me. All the calculations I had done (and I’ve ended up doing quite a lot of calculations), pointed to a maximum input voltage with my setup of around 4.63V, which equates to a maximum voltage on the ADC pin of 1.1V

    This makes sense when I plug any of the ADC readings that I then manually verified with my multimeter:

    4.16V @ 917
    mV-max = (mV-in * ADC-max) / Actual-ADC-reading
    = (4160 * 1023) / 917 = 4640 mV

    4.0V @ 883
    = (4000 * 1023) / 883 = 4634 mV

    3.86V @ 848
    = (3860 * 1023) / 848 = 4656 mV

    Taking the average of these and plugging it back into the voltage divider calculation yields:
    Vout = (4.643 * 98) / (98 + 313) = 1.10V

    I’m glad i’ve got to the bottom of the discrepancy. I hope this explanation of my analysis and calculations may prove useful to others.

    Does anyone have any thoughts on why my ADC seems to hold 1.1V as it’s reference voltage instead of 1.0?

    1. Well for one thing, your meter might be off 10% 🙂
      But do you mean that when you put 1.1V on yr adc you get a reading of 1023 and when you put 1 volt on it you get a lesser reading?

  7. Until your comment, no I hadn’t applied 1.0V directly to the ADC pin; that was worked out by reading ADC values of higher voltages through the voltage divider (after I had measured more precisely the resistor readings of R1 and R2).

    Since then I have applied 1.0V directly to the ADC pin and it gives me a reading of 980. When I apply 1.1V the reading goes off the scale, but when I apply 1.077V I get the max reading of 1023.

    Ps. Both multimeters agree on the voltage of 1.0V when on the 20V scale, different brands bought at different times. So unless they’re both off by the same amount I have a reasonable confidence in their accuracy 🙂

    1. As Jeroen also remarks: the esp8266 ADC is uncalibrated and what you describe is typical. The ADC can measure ‘1 Volt’ max. It is just that that 1 Volt isnt always precisely 1 Volt. In your case it is 1.077Volt.So that gives you yr calibration factor, that no doubr would be different for another ESP8266

  8. The ESP8266’s ADC is massively uncalibrated. 1024 is “about” 1V, but the calibration is pretty different per chip. A good practice, assuming you really need this, is to store the calibration in EEPROM. It also unfortunately means that you cannot build a big bunch of hardware where you reply on a calibrated ADC. It’s a nasty omission.

    1. Very true Jeroen. Especially as it only has one ADC channel, I use a separate 4channel ADC anyway. Just a simple PCF8591 with 8bit accuracy. For more accuracy an ADS1115

  9. Would a BC457 transistor be fine to switch the voltage monitor on/off? It would have been great if you could provide a scheme for that setting, too.
    Thank you for this awesome blog entry!

    1. Yes it would, but depending on circumstances maybe a Logic level FET would be better as that I think has a lower voltage drop than a regular transistor.
      But in fact I have used a BC547 to Switch off voltage to Soil-moisture probe (voltage less of an issue there).
      You may want to have a look here:
      or google for “High side switching”
      Obviously you would have to recalibrate the series resistor and formula used for the transfer of Raw analog to Voltage

  10. I used your code, but found a mistake in it.
    The right line is:
    “client.print(String(“GET “) + url + “&headers=false” + ” HTTP/1.1\r\n” + “Host: ” + host + “\r\n” + “Connection: close\r\n\r\n”);”
    I used a Nodemcu v3 Lol1n board, the 3.3 V power supply give 1010 ADC Value for me.
    So I must calculate the correct value to measure correct voltage.
    For 1010 ADC Value the correct number is 3.34685598.
    In code:
    pinMode(A0, INPUT);
    raw = analogRead(A0);

    So works ok everything!
    Thanks for sharing!

    1. Thank you. I didn’t experience any mistake in the code but as WordPress isn’t really ‘ friendly’ in reproducing code it is possible an error was introduced

  11. Hi there .. great project .. Would you mind explainging how to do this with a ‘barebones’ ESP8266 12F ..AiThinker type ?

    1. You do that in the same way but you need to construct your own voltage divider as the esp8266-12 does not have one. Voltage divider needs to bring 4.2volt down to 1 volt.
      Although you could use the wemos shield in this case as well, I wouldn’t but rather get one of those cheap lipo loader modules, forget about generating 5volt like the shield dies but focus on just 3.3volt

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.