Strange analogRead(A0) result on NodeMCU ESP8266-12E

I'm building a driveway gate monitor, and am getting some readings on the A0 pin which I'm having a hard time understanding.

The gate is operated by a 12v battery, which I also want to use to power the monitor I am building. But while developing the code, I've been using a USB cable from my computer to power the NodeMCU.

I connected leads from a 12v battery to a voltage divider (100k and 10k resistors) and fed it into A0 on the NodeMCU. This is a 12E development board, so the A0 pin can accept up to 3.3v input. It then has an internal voltage divider using 100k and 220k resistors, to bring it down to the 1v limitation of the ESP8266 chip.

When using USB to power the board, this equation produces a good reading of the 12v battery's charge:

(((analogRead(A0) / 1024) / 0.3125) / (R2/(R1+R2)))

But when I switch to powering the NodeMCU through a 3v3 pin, from a 3.3v power supply connected to the same 12v battery I'm trying to monitor ..... the reading of A0 changes significantly.

Why would the A0 reading be different if powering on 5v USB vs. 3v3 pin? Is it due to the ADC's reference voltage maybe?

I've attached a picture of my breadboard setup, as well as a drawing of what I think is going on and how I came to the equation I am using.

Here is the code I'm using to try and figure this out.

#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>
WiFiServer server(80);                // Define web server name and port

// ********************************************************* DEFINITIONS *********************************************************
// PIN DEFINITIONS -----------------------------------------------
  int Battery_PIN = A0;                 //Define pin to connect the output of the voltage divider from the gate controller's 12v battery (using a 100k and a 10k resistor to divide down to 1.x volts

// Variables for Monitoring Gate Controller's 12v Battery -----------------------
  String buf = "";                    // Define string to hold web response from client
  int v = 0;                          //v counter
  int t = 0;                          //t counter
  float volt = 0.0;                   //Variable to add up the 1000 readings of the gate controller's 12v battery
  float raw = 0.0;                    //Debug variable to display A0 raw value on web portal
  float reading = 0.0;                //Variable to store A0 reading
  float vin = 0.0;                    //Varibale to store the result of averaging the reading's of the gate controller's 12v battery
  float R1 = 96900.0;                 //Actual value of R1 used in the voltage divider, to bring the 12v gate controller battery down to a reasonable voltage 
  float R2 = 9970.0;                  //Actual value of R2 used in the voltage divider, to bring the 12v gate controller battery down to a reasonable voltage 
  
// ********************************************************* INITIALIZE *********************************************************
void setup() {
// Pin Modes -----------------------------------------------
  pinMode (Battery_PIN, INPUT);                         //Set the pin for commecnting to the voltage divider output from the gate controller's 12v battery, to an Input

// Initialize Serial and WiFi -------------------------------
  Serial.begin(115200);                                 // initialize serial for debugging
  WiFiManager wifiManager;                              // Start WiFi Manager and connect to previous WiFi if found
//  wifiManager.resetSettings();                          // Uncomment and run it once, if you want to erase all the stored information
  wifiManager.autoConnect("MySetupAP");                 // If no previsous WiFi connection found, start configuration AP
  Serial.println("<Controller is ready>");              // Success message printed to serial monitor
  server.begin();                                       // Start the web server  
}

// ********************************************************* MAIN LOOP *********************************************************
void loop() {
  HandleClient();                   //Handle any requests coming in over WiFi
  ReadBatteryVoltage();             //Check the voltage of the gate controller's 12v battery  
}

// *********************************************************  FUNCTION DEFINITIONS *********************************************************
// Check Gate Controller's Battery Voltage Level  --------------------------------------------------------
void ReadBatteryVoltage() {
  if (v == 100) {                                                             //If v has reached 100 cycles, meaning 100 voltage readings of the gate controller's 12v battery have been taken
   vin = volt / v;                                                            //Set voltage variable (vin) to the average of the 100 readings
   v = 0;                                                                     //reset v counter
   volt = 0;                                                                  //reset volt to 0
  }
  if (t == 1000) {                                                            //Read the A0 value every 2000 cycles
    raw = analogRead(Battery_PIN);
  }
  if (t == 2000) {                                                            //Read the A0 value every 2000 cycles
    reading = analogRead(Battery_PIN);                                    
    volt += (((reading / 1024) / 0.3125) / (R2/(R1+R2)));                          //Convert reading to 12v battery voltage
    v++;                                                                      //Increment v counter    
    t = 0;                                                                    //Reset t counter
  }
  t++;                                                                        //Increment t counter
}

// Handle Client Connections ------------------------------------------------------------
void HandleClient() {                         
  WiFiClient client = server.available();               // listen for incoming clients
    if (!client) {                                      // If no client ...
    return;                                             // ... Return  
  }
    while (client.connected()) {                        // loop while the client's connected
      if (client.available()) {                         // if there's data to read from the client
        char c = client.read();                         // read a byte
        buf += c;

        if (buf.endsWith("\r\n\r\n")) {                 // If two newline characters in a row
          sendHttpResponse(client);                     // Call HTTP response function
          break;                                        // Exit function
        }
      }
    }
   buf = "";
}

// SEND HTTP REQUEST TO THE CLIENT ------------------------------------------------------------
void sendHttpResponse(WiFiClient client) {
  client.println("HTTP/1.1 200 OK");
  client.println("Content-type:text/html");
  client.println("Connnection: close");
  client.println();
  client.println("<!DOCTYPE html>");                     
  client.println("<html>");
  client.println("<head>");
  client.println("<title>Gate Monitor</title>");
  client.println("<style type=\"text/css\">");
  client.println(".tftable {width:950px;border-collapse: collapse; table-layout: fixed;}");
  client.println(".tftable tr {background-color:#d4e3e5;}");
  client.println(".tftable td {font-size:40px;}");
  client.println("h1 {font-size:45px;}");
  client.println("h2 {font-size: 45px; Line-Height:0px;}");
  client.println("h4 {Line-Height:0px;}");
  client.println("</style>");
  client.println("</head>");
  client.println("<body>");  

  client.print("<h4>Battery Voltage: ");
  client.print(vin);
  client.println("v</h4>");

  client.print("<h4>Battery A0 Raw: ");
  client.print(raw);
  client.println("v</h4>");
  
  client.println("</body>");
  client.println("</html>");
  client.println();
}

That internal reference should be fixed, independent of Vcc.

For this kind of testing you best use the simplest possible sketch: something that just prints the raw value of the ADC reading to the Serial monitor to see what you actually get. If you have an FDTI cable or external USB/TTL adapter you can do so by just connecting Tx, Rx, GND and not power the NodeMCU over USB.

As you're using a solderless breadboard, one thing you always have to check, re-check and double check are the electrical connections. Wiggle and wobble all your resistors and connectors and see if that makes a difference.

wvmarle:
That internal reference should be fixed, independent of Vcc.

That's what I would think too, but the variance made me question it.

wvmarle:
For this kind of testing you best use the simplest possible sketch: something that just prints the raw value of the ADC reading to the Serial monitor to see what you actually get. If you have an FDTI cable or external USB/TTL adapter you can do so by just connecting Tx, Rx, GND and not power the NodeMCU over USB.

Ya, I could tear open a USB cable to clip the power wire; or grab my ESP-01 programmer and just use the Tx, Rx, GND pins ...

wvmarle:
As you're using a solderless breadboard, one thing you always have to check, re-check and double check are the electrical connections. Wiggle and wobble all your resistors and connectors and see if that makes a difference.

Certainly, wiggling does affect it. But what I've been doing is using my meter to measure the voltage seen at pin A0, and comparing that to the analogRead(A0) result. I confirm the voltage at A0 doesn't change much between powering over USB vs. 3.3 from the supply, but the A0 reading changes a lot.

As an example:
Powering over USB: The voltage at A0 is 1.117v, the raw analogRead value is 381, and the result of my equation is 12.77

Powering over 3.3v power supply: 1.099v, the raw analogRead value is 339, and the result of my equation is 11.49.

The battery is measuring about 12.60 at the terminals right now. I realize the variance of the onboard voltage divider resistors has an impact here, which is why I'm not getting exactly 12.60, and I can dial in the tolerance with some trial and error ... but the bigger concern I have is why the wide difference in A0 reading?

So it seems that analogRead is producing different results depending where the board is being powered.

Notice I do see a small drop in voltage (like 0.02vdc) at A0 when powering the NodeMCU off of the 3.3v power supply. I imagine this is due to the 3.3v power supply being hooked up to the 12v battery I'm measuring. So there's a bit of extra current being pulled from it.

The 12v battery is a 35AH SLA, so the the NodeMCU doesn't put much of a drain on it.

So I tried changing the voltage divider to tighten up the scale. With 100k and 10k resistors, I'd be safe up to a 30+vdc battery. Since I only have a 12vdc battery, I swapped out the 10k for a 22k. This should be good for up to an 18v battery.

After updating the code with actual readings of the 100k and 22k resistors, here are the results:

Running on USB power:

  • Voltage at A0 = 2.155v
  • analogRead of A0 = 728
  • Equation result = 12.51
  • Actual battery voltage = 12.64

Running on the 3.3 power supply:

  • Voltage at A0 = 2.119v
  • analogRead of A0 = 685
  • Equation result = 11.74v
  • Actual battery voltage = 12.66v

Maybe just using "map" would be better than working the math for both voltage dividers? Since an 18vdc source would produce 3.3v through my voltage divider, and 3.3v into A0 would result in 1v on the 8266 chip (due to the internal voltage divider) ... and 1v on the 8266 chip results in an ADC reading of 1024 .... so I should be able to map 1 - 1024 to 100 - 1800 , and then divide by 100 for a scale of 1v - 18v with two decimal place granularity ... I'll have to play with it a bit.

If the issue is the 5.0 to 3.3 voltage converter is sliding the reference point, then I should be able to adjust fairly easily by moving the maping ends. (ie: instead of 100 - 1800, do 130 - 1830)[/list][/list]

Just as an update to this ... the mapping is working quite well.

Here's the full function I ended up with

void ReadBatteryVoltage() {
  if (v == 100) {                                                             //If v has reached 100 cycles, meaning 100 voltage readings of the gate controller's 12v battery have been taken
   vin = volt / v;                                                            //Set voltage variable (vin) to the average of the 100 readings
   v = 0;                                                                     //reset v counter
   volt = 0;                                                                  //reset volt to 0
  }
 if (t == 2000) {                                                            //Read the A0 value every 2000 cycles
    reading = analogRead(Battery_PIN);                                    
    reading = map(reading, 1, 1024, 0, 1800);
    volt += (reading / 100);
    v++;                                                                      //Increment v counter    
    t = 0;                                                                    //Reset t counter
  }
  t++;                                                                        //Increment t counter
}

I tested it against my 12v battery, and it's within a couple hundreds of accurate. I also tested against a low 9v battery I have here, and it was only .2v off (battery read 7.4 on my DMM and 7.2 in the code).

So I'm happy with this. The purpose for this is to alert me if the gate controller's battery gets close to being too low to operate the gate effectively. So this should work well.