Having Trouble With Inconsistent analogRead Values

I'm working on a sketch / circuit to tell me when the source voltage has dropped below some value. The source voltage will be in the range of 10v DC to 15v DC. The circuit runs the source voltage through a voltage divider (two resistors, R1=3.3 MOhms, R2=1.5 MOhms) which yields an output voltage from the voltage divider in the range of 2.75v to 4.43v when the source is from my bench power supply.

The output from the voltage divider is being fed into analog pin A0 on a Arduino Pro Mini.

When reading pin A0 these are the results I'm seeing:

Source Voltage    Divider Output               A0 Reading
11v                     2.75v                     444 - 454
12v                     3.05v                     492 - 503
13v                     3.60v                     578 - 592
14v                     3.86v                     624 - 638
16v                     4.43v                     713 - 729

The source voltage is constant, the Divider Output voltage doesn't vary, either. However, the reading on pin A0 varies quite a bit.

The sketch itself should read analog pin A0 every couple of seconds and calculate the source voltage (in the sketch as currently written, that calculation isn't working correctly). It should also read a reference voltage from analog pin A2 which is fed by a 10k linear potentiometer (this part of the sketch hasn't been worked and at the moment, pin A2 is grounded). If the source voltage falls below the reference voltage, then an LED should light and a buzzer sound.

The part of the sketch that's concerning me is where the sketch reads analog pin A0 (about line 55) and even though the voltage to the pin is constant, the analogRead varies quite a bit. Further, I'd expect as the source voltage approaches 16v (and the voltage divider output approaches 5v) that the reading from analog pin A0 should approach 1023. It doesn't.

Is this what should be happening? Or (more likely) what am I doing wrong?

Here's the source code for the sketch as currently written.

#include <SPI.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// Define LCD pinout
const int  en = 2, rw = 1, rs = 0, d4 = 4, d5 = 5, d6 = 6, d7 = 7, bl = 3;

// Define I2C Address - change if reqiuired
const int i2c_addr = 0x27;

LiquidCrystal_I2C lcd(i2c_addr, en, rw, rs, d4, d5, d6, d7, bl, POSITIVE);

int LEDPin = 9;
int BUZZERPin = 7;
int LEDOk = 13;

// int vIn = 16;
// float r1 = 3294.0;
// float r2 = 1482.0;
// float voltConvert = r2/(r1+r2);

// calibrating voltage (Start at 5v because that is the arduino pro mini base voltage)

int readDelay = 10;

int voltagePin = A0;
int triggerPin = A2;

void setup() {

  Serial.begin(9600);
  while (!Serial) 
  {
  }
  lcd.begin(16,2);
  delay(2000);

  // setup the pin for the low voltage alert
  pinMode(LEDPin, OUTPUT);
  pinMode(BUZZERPin, OUTPUT);
   

  // Clear the buffer
  lcd.clear();
  digitalWrite(LEDOk, HIGH);
}

void loop() {
  float limitValue = analogRead(triggerPin);
//  Serial.print("limitValue: ");
//  Serial.println(limitValue);
  float limitVoltage = convertSlope(0.0,16.0,0.0,1023.0,limitValue);
//  Serial.print("calcLimit: ");
//  Serial.println(limitVoltage);
  
  float voltageValue = analogRead(voltagePin);
  Serial.print("voltageValue: ");
  Serial.println(voltageValue);
  float realVolts = convertSlope(0.0,17.5,0.0,1023.0,voltageValue);
  Serial.print("realVolts: ");
  Serial.println(realVolts);

  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Voltage: ");
  lcd.print(realVolts);
  lcd.setCursor(0,1);
  lcd.print("Trigger: ");
  lcd.print(limitVoltage);

  if (realVolts <= limitVoltage) {
    LowVoltageAlert();
  }
  delay(2000);
}

void LowVoltageAlert(){
  digitalWrite(LEDPin, HIGH);         // turn the LED on (HIGH is the voltage level)
  digitalWrite(BUZZERPin, HIGH);
  digitalWrite(LEDOk, LOW);
  delay(250);
  digitalWrite(BUZZERPin, LOW);
  delay(1000);                        // wait for a second
  digitalWrite(LEDPin, LOW);          // turn the LED off by making the voltage LOW
  digitalWrite(LEDOk, HIGH);
  delay(1000);                        // wait for a second
}

// Convert a float range from 0-1023 to 0-16
float convertSlope(float outputStart,float outputEnd,float inputStart,float inputEnd,float inputValue) {
  float slope = (outputEnd-outputStart) / (inputEnd - inputStart);
  float outputValue = outputStart + slope * (inputValue - inputStart);
  return outputValue;
  
}

Use lower resistors. The analog inputs are designed for a 10k or lower impedance. Instead of R1=3.3 MOhms and R2=1.5 MOhms), try R1=3.3 kOhms and R2=1.5 kOhms. Yes, that will be a 3.125 mAmp draw at 15V (about 0.05 W).

when the source is from my bench power supply.

What's the "real source" going to be? i.e. Is it a power supply so current through the voltage divider is not a problem, or is it a high-impedance source? or a battery where you want to minimize current?

two resistors, R1=3.3 MOhms, R2=1.5 MOhms)

High impedances are more sensitive to noise pickup. 3.3K & 1.5K would probably give you more stable readings.

Your meter is high impedance but the readings are filtered/smoothed.

A capacitor across the "bottom" resistor (between the analog input and ground) will also help to filter-out the noise.

And/or see the [u]Smoothing Example[/u].

. Further, I'd expect as the source voltage approaches 16v (and the voltage divider output approaches 5v) that the reading from analog pin A0 should approach 1023.

That is correct. Assuming your reference is truly 5V, 4.43V should read 1023 x 4.43/5 = 906. Did you check the 5V power supply?

BTW - Your reference potentiometer is also subject to noise so it may need some filtering too.

Johnwasser: Thanks for the advice on resistor values. Changing to 3.3kΩ and 1.5kΩ brought the A0 reading much closer to what I was expecting.

There is still quite some variation in the reading, though. At 13v source (voltage divider ouput: 3.94v) the A0 reading varied from 803 - 850. That still seems quite a wide variation. If that's normal, then I'll program around it, but I'm thinking it isn't normal.

DVDdoug: The reference voltage is 5.2v. I'm assuming that I'll take that difference into account when converting the A0 reference number to the representative source voltage.

I'll add a capacitor as you suggested to help filter out the noise.

Thanks!
Roland

The project, when deployed, would be reading voltage from a lead-acid battery. That battery will be supplying power to an amateur radio transmitter as well as to this monitor device. I want to protect the rather expensive radio from source voltage dropping below about 11.2v ... and would set the reference voltage somewhat higher.

@johnwasser @DVDDoug Thanks for your help! I added a .01pf capacitor from A0 to ground and the analogRead values settled down. I also added a routine to average 5 readings which almost completely removed the variation. I think I'm good to go. I appreciate your prompt assistance!