I am very new to micro controller and circuits. I am not at all with this field. But trying to create a home project where I have a device image attached below.

enter image description here

This is 18650 Lipo battery powered device. Here I want to calculate the battery level. After doing some research on google I found that I need to have Voltage Divider which I think I already included in this with 220k ohms and 100k ohms

enter image description here

People are using different ways to calculate it. Which I found in few examples. Which I am not able to understand at all, that what formula or values they are referring to calculate it.

if someone can help to understand that will be helpful. Here is how I am coding it which I saw somewhere on internet itself.

      #include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
//#include "DHTesp.h"

#define DHT_PIN       16

//SSID and Password of your WiFi router
const char* ssid = "Asus";
const char* password = "Xmv02488!!**";

ESP8266WebServer server(80); //Server on port 80

void setup(void){

  WiFi.begin(ssid, password);     //Connect to your WiFi router

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {

  //If connection successful show IP address in serial monitor
  Serial.print("Connected to ");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());  //IP address assigned to your ESP

  server.on("/", handleRoot);      //Which routine to handle at root location

  server.begin();                  //Start server
  Serial.println("HTTP server started");

  pinMode(A0, INPUT); 

void loop(void){
  server.handleClient();          //Handle client requests


 * This function converts IPAddress struct to a String
String IpAddress2String(const IPAddress& ipAddress)
  return String(ipAddress[0]) + String(".") +\
  String(ipAddress[1]) + String(".") +\
  String(ipAddress[2]) + String(".") +\
  String(ipAddress[3])  ;

 * This rutine is exicuted when you open its IP in browser
 void handleRoot() {
  IPAddress ip_address = WiFi.localIP();
  String ip_str = IpAddress2String(ip_address);

  int nVoltageRaw = analogRead(A0);
  float fVoltage = (float)nVoltageRaw * 0.00486;

  float fVoltageMatrix[22][2] = {
    {4.2,  100},
    {4.15, 95},
    {4.11, 90},
    {4.08, 85},
    {4.02, 80},
    {3.98, 75},
    {3.95, 70},
    {3.91, 65},
    {3.87, 60},
    {3.85, 55},
    {3.84, 50},
    {3.82, 45},
    {3.80, 40},
    {3.79, 35},
    {3.77, 30},
    {3.75, 25},
    {3.73, 20},
    {3.71, 15},
    {3.69, 10},
    {3.61, 5},
    {3.27, 0},
    {0, 0}

  int i, perc;

  perc = 100;

  for(i=20; i>=0; i--) {
    if(fVoltageMatrix[i][0] >= fVoltage) {
      perc = fVoltageMatrix[i + 1][1];

  server.send(200, "text/plain", "Hello from esp8266!\n\rIP: " + ip_str + 
    ".\n\rTemp: " + "NO" + 
    ", Hum: " + "NO" + 
    "\n\r" + "NO" + 
    "\n\r" + "Voltage: " + fVoltage + 
    "\n\r" + "Charge: " + perc + '%');

This is a copied code. Where I am not sure about where they got the this formula and how they got the value of 0.00486

I copied this code from different source references. But I hope this will help you to understand the issue.

Any suggestions will be helpful!

Thank you! (In advance)

  • 1
    The biggest problem with the internet is the number of people that think they know what they are doing, but in reality have no idea, but still feel the need to post tutorials about that which they know nothing of .. chances are 0.00486 was either the result of "trial and error", or a case of "chinese whispers" .. code copied and copied and copied again, and somewhere along the line, there was a typo, or two, or three ... and you end up with purple monkey dishwasher (Simpsons reference) – Jaromanda X Jan 7 at 22:36

The problem with LiPo batteries is that the discharge curve is very flat. Here's an example:

enter image description here

As you can see, for about 95% of the life of the battery there is very little change in voltage. You can't just take the voltage between "full" and "empty" and get a percentage from that. Instead you have to map points along that curve to different percentages.

The simplest way is to just have a set of percentages that you are interested in - every 5% as in the code you have above. Then you say "If the voltage is above this value then it's 100%. If it's above this next value down it's 95%. If it's above the next value down then it's 90%". etc.

The array fVoltageMatrix contains that mapping. For 100% the voltage must be 4.2V or greater. For 95% it must be 4.15V or greater. And so on.

If we take those voltage values from the array and plot them on a graph they look like this:

enter image description here

As you see that looks kind of similar. Quite flat, with a sudden drop off.

The code that gets the percentage charge simply reads the value from the ADC and converts it to a voltage by multiplying it by 0.004861, then it steps through the array one element at a time. The first time it finds one that fails the test "This percentage's threshold is less than the voltage" makes it take the previous one in the list as the percentage. Personally, I think that's backwards and a bad way of doing it. Instead, it should be the first one that passes the test "The voltage is greater than or equal to this percentage's threshold" that should be taken. I'd rewrite the loop like this:

perc = 0;

for(i=0; fVoltageMatrix[i][0] > 0; i++) {
  if(fVoltage >= fVoltageMatrix[i][0]) {
    perc = fVoltageMatrix[i][1];

1 The number 0.00486 is the number of volts applied to the input to give a 1 from the ADC. Incidentally, if I am using the right figures, I calculate it should be 0.003125. 1 * (1/1024) gives 0.000976563 (that's 1 volt maximum range on the ADC, multiplied by one bit - a 1024th since it's 10 bit resolution on the ESP8266). Multiply that by the ratio of the resistors ((R1 + R2)/R2) which is 3.2, and that gives 0.003125. To compress it, you get: x = (R1 + R2) / R2 * 1/1024 = 320000 / 100000 * 0.000976563 = 0.003125

However, my numbers could be wrong...

  • Thank you so much for such a detailed explanation. It helped to understand a lot. Here if I use this float fVoltage = (float)nVoltageRaw * 0.0041015625; and I get a voltage reading of 4.2 which is awesome. I added a 100k ohms resister to the A0 to positive of my battery. Which makes a lot of sense now! :) – user3201500 Jan 8 at 10:06

Another problem you can run into is that a lot of the A/D converters use the VCC as a voltage reference. Which means that as your input voltage drops (what you are trying to measure) the reference voltage also drops which means the values you are recording will not appear to change or at least will not give an accurate reference.


One problem I have with the ESP8266 is the crappy results from the A/D converter. There are a number of hoops that you need to go through for the planets to align in order to get consistent readings. See Fluctuating ADC with stabilized source https://github.com/esp8266/Arduino/issues/2070

The other issue I have is with the assumption that the range of operation of the A/D is 0-1.0 volts and that certainly is not the case.

I calculate it should be 0.003125. 1 * (1/1024) gives 0.000976563 (that's 1 volt maximum range on the ADC

The above shows how precise our calculations can be but unfortunately the six digits of precision gets thrown out the window if the assumed 1 volt top end has variations of 5%. Unfortunately there are no specifications given as far as the A/D converter in the miniscule spec sheet.

Yes the ESP8266 A/D can be calibrated, and measurements taken under controlled conditions. But make sure that you do the required work and verify the results under all operating conditions before you trust your results.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.