Voltage divider problem

oric_dan(333):
From dhenry's comments, I don't understand what his "specific" objections are.

In general, straightforward ADC readings using a microcontroller like Arduino will have
probably 2-5% error, at best. For 5V, that's as much as 0.25V.

First off, the v.reg voltages are not spot-on 5V, but will have up to 5% error, depending
upon the specific v.regs being used. Secondly, whenever you use a voltage divider, the
resistors also have their own tolerances, ranging from below 1% for high-precision parts
[and who uses these] to 5% for most discrete parts. Therefore, any ADC measurements
will be off from the get-go. You can use a precision voltage-reference, but still must
use precision resistors in the voltage dividers for best results.

It seems to me that the majenko page has some good info to help deal with the first
problem, of v.reg tolerance, so I don't see d-h's trouble.

Well there are pretty simple methods to deal with both the 'problems' you addressed. First it's possible with proper coding to have an arduino measure it's own exact Vcc voltage to gain the proper value to convert counts to voltage readings. The second problem is easily deal with by using a 10 turn pot (say 5K ohms) for the voltage divider and set it for the division ratio you desire using a DMM to verify that it's accurately set.

Now if either or both those are really needed or even desired depends on the application and it's accuracy requirements or assumptions.

Lefty

// Function created to obtain chip's actual Vcc voltage value, using internal bandgap reference
// This demonstrates ability to read processors Vcc voltage and the ability to maintain A/D calibration with changing Vcc
// Now works for 168/328 and mega boards.
// Thanks to "Coding Badly" for direct register control for A/D mux
// 1/9/10 "retrolefty"

int battVolts;   // made global for wider avaliblity throughout a sketch if needed, example a low voltage alarm, etc

void setup(void)
    {
     Serial.begin(38400);
     Serial.print("volts X 100");
     Serial.println( "\r\n\r\n" );
     delay(100);
    }
    
void loop(void)
    {
     battVolts=getBandgap();  //Determins what actual Vcc is, (X 100), based on known bandgap voltage
     Serial.print("Battery Vcc volts =  ");
     Serial.println(battVolts);
     Serial.print("Analog pin 0 voltage = ");
     Serial.println(map(analogRead(0), 0, 1023, 0, battVolts));
     Serial.println();    
     delay(1000);
    }

int getBandgap(void) // Returns actual value of Vcc (x 100)
    {
        
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
     // For mega boards
     const long InternalReferenceVoltage = 1115L;  // Adjust this value to your boards specific internal BG voltage x1000
        // REFS1 REFS0          --> 0 1, AVcc internal ref. -Selects AVcc reference
        // MUX4 MUX3 MUX2 MUX1 MUX0  --> 11110 1.1V (VBG)         -Selects channel 30, bandgap voltage, to measure
     ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR)| (0<<MUX5) | (1<<MUX4) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (0<<MUX0);
  
#else
     // For 168/328 boards
     const long InternalReferenceVoltage = 1056L;  // Adjust this value to your boards specific internal BG voltage x1000
        // REFS1 REFS0          --> 0 1, AVcc internal ref. -Selects AVcc external reference
        // MUX3 MUX2 MUX1 MUX0  --> 1110 1.1V (VBG)         -Selects channel 14, bandgap voltage, to measure
     ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (0<<MUX0);
       
#endif
     delay(50);  // Let mux settle a little to get a more stable A/D conversion
        // Start a conversion  
     ADCSRA |= _BV( ADSC );
        // Wait for it to complete
     while( ( (ADCSRA & (1<<ADSC)) != 0 ) );
        // Scale the value
     int results = (((InternalReferenceVoltage * 1024L) / ADC) + 5L) / 10L; // calculates for straight line value 
     return results;
 
    }

Thanks, that code looks very interesting.

Also a question regarding resistor tolerances. These are manufacturing tolerances right? The resistors themselves does not "change" their resistance over time, right? If so then this problem should be overcome by just measuring them and using that exact value in my code. The projects I make is for myself and will not be mass produced hence it's ok for me to measure each resistor manually.

Also I have some 1% and some 5% resistors, the former is much closer to their specified value, of course, but is there any other benefits to use these over the ones with lower tolerances? As I said I do measure them prior to installation.

Resistors will change their values with temperature changes.

measure it's own exact Vcc voltage

What you mean by "exact"? I've seen nothing "exact" in this analog world. Also, on
further looking, the bandgap reference in the ATmega 328P datasheet is only specified
to within 10% <-- gakk!

Using that code I got the following when powered over the USB:

USB
Battery Vcc volts = 5.03 (serial printer)
Analog pin 0 voltage = 1.09 (serial printer)
Analog pin 0 voltage = 1.11 (using my meter)

3S external power
Battery Vcc volts = 4.94 (serial printer)
Analog pin 0 voltage = 2.84 (serial printer)
Analog pin 0 voltage = 2.89 (using my meter)

That's pretty good, next I'll try to use these values and update my code to see what I'll get. If I get a consistent difference <2% I guess I can live with that.

No, someone already pointed this out (couldn't find this to quote).
Tolerances depend on multiple factors.
I guess the most known is temperature.
Having the resistors in the circuit, might for instance have them pick up ambient temperature as well as temperature possible generated by your cicuit (one could argue that this is also ambient temperature).
The temperature generated by the circuit probably differs after an hour running compared to right after starting.
You should take that in consideration.
Ofcourse you could try to compensate that by using an other sensor, measuring the resistor's temperature, or heat up the resistors to a known fixed temperature.
But you also need to know that these methods themselves will also have their own tolerances.

Ofcourse for the sake of learning this is all worth trying, but it's questionable if this will ever get you to read with very high accuracy.

oric_dan(333):
Resistors will change their values with temperature changes.

measure it's own exact Vcc voltage

What you mean by "exact"? I've seen nothing "exact" in this analog world. Also, on
further looking, the bandgap reference in the ATmega 328P datasheet is only specified
to within 10% <-- gakk!

Well 'exact' means measuring the actual Vcc relative to the bandgap voltage. And as you said even the bandgap voltage has a tolerance but it usually a steady value within that tolerance range for each specific chip, and that is one parameter in the sketch that does have to be 'tweeked' to account for one's specific chips bandgap value. Once that is done then the sketch (see the comments) will measure any variation of the Vcc that can be used to compensate for the standard analogRead commands rather then just assume that Vcc is always an exact 5.000 which it presently does and we all know it's not necessarily so. I've seen .5vdc differences of Vcc on a standard arduino board just switching from USB power to external power and that is what my sketch was designed to address, a way to measure the real world Vcc the chip has applied to it to come up with a compensation value to use with analogRead statements.
Lefty

Ofcourse for the sake of learning this is all worth trying, but it's questionable if this will ever get you to read with very high accuracy.

And that is particularly true when you are working with the limited 10 bit ADC of the arduino. If one jumps into using one of the many 12,14,16, or even 24 bit external I2C or SPI ADC chips then applying principles and practices of good design and calibration will give dividends in trying for the best possible accuracy. The arduino ADC is very useful and simple to use but one shouldn't mistake if for instrumentation quality measurements.

Lefty

Ok, I'll better use the ones I have with lower tolerances then.

Measurement doesn't need to be super exact. I'll use the arduino to measure battery voltage in my UAV ground station. So it's mostly to make sure I don't run out of battery. Because of that I'll try to keep it as simple as possible but also not introduce any unneccessary errors. I'll also measure a bunch of other stuff such a current sensor, clock, timer, signal strength and buzzers and lights to indicate warnings etc. Information will be presented mostly on a 20x4 LCD.

As you probably figured out I'm an arduino newbie and this is my first project, I'm learning as I go along and really appreciate all input.

All in all, seems like too much variability going on here for OP to read really accurate
values. Especially, considering powering via USB versus using Vin and the v.reg. Probably
best to go with a good external voltage reference chip and 1% Rs, and call it a day.

Yes, maybe. Do you have some ideas on such chips that I could use with the Arduino?

Put a small capacitor on the analog input pin. Anything from 0.1n to 1000n will work.

The issue is that your input resistance is too high.

Boopidoo:
Yes, maybe. Do you have some ideas on such chips that I could use with the Arduino?

Here are two nice ones that adafruit sells:

12 bit ADC ADS1015 12-Bit ADC - 4 Channel with Programmable Gain Amplifier [STEMMA QT / Qwiic] : ID 1083 : $9.95 : Adafruit Industries, Unique & fun DIY electronics and kits

16 bit ADC ADS1115 16-Bit ADC - 4 Channel with Programmable Gain Amplifier [STEMMA QT / Qwiic] : ID 1085 : $14.95 : Adafruit Industries, Unique & fun DIY electronics and kits

I've got a ADS1115 module from an e-bay firm and was quite impressed with it's features and capabilities.

Lefty

What are the resistor tolerances on those adafruit devices?

Where they say 12 or 16-bit "precision", they actually mean resolution, and not accuracy.
You might be able to discriminate 1 part in 4096 or 65536, but the absolute value may
still be off by 5%, or 204 parts in 4096, or 3276 parts in 65536.

oric_dan(333):
What are the resistor tolerances on those adafruit devices?

Where they say 12 or 16-bit "precision", they actually mean resolution, and not accuracy.
You might be able to discriminate 1 part in 4096 or 65536, but the absolute value may
still be off by 5%, or 204 parts in 4096, or 3276 parts in 65536.

The total word size (bits) determine the steps of resolution possible, the accuracy depends on the reference voltage used and of course the quality of the internal design of the chip. Both those models have an internal voltage reference as well as programmable gain settings, 4 single ended input channels or 2 differential input channels. The datasheet shows all the possible error tolerances and are impressive if you study ADC chips in the past. It's not a cheap chip but certainly a very powerful feature filled chip. Study the datasheet at your leisure.

Ah so, I misinterpreted the ckt, and thought those smt resistors had to do
with gain-setting or voltage dividers. However, the d/s uses the word resolution,
and doesn't confuse with precision.

I guess the accuracy would be wrapped up in the values of,

Integral nonlinearity DR = 8SPS, FS = ±2.048V, best fit(2) 1 LSB
FS = ±2.048V, differential inputs ±1 ±3 LSB
Offset error
FS = ±2.048V, single-ended inputs ±3 LSB
Offset drift FS = ±2.048V 0.005 LSB/°C
Offset power-supply rejection FS = ±2.048V 1 LSB/V
Gain error(3) FS = ±2.048V at 25°C 0.01 0.15 %
FS = ±0.256V 7 ppm/°C
Gain drift(3) FS = ±2.048V 5 40 ppm/°C
FS = ±6.144V(1) 5 ppm/°C
Gain power-supply rejection 80 ppm/V
PGA gain match(3) Match between any two PGA gains 0.02 0.1 %
Gain match Match between any two inputs 0.05 0.1 %
Offset match Match between any two inputs 3 LSB
At dc and FS = ±0.256V 105 dB
At dc and FS = ±2.048V 100 dB
Common-mode rejection At dc and FS = ±6.144V(1) 90 dB

At the refinery I worked at before retirement we dealt with a lot of calibration issues and training. We tried to teach two different concepts to new instrumentation techs that started work there about what a quality measurement was and was not.


If a sensor can be proved to have good 'repeatablity' within it's rated 'accuracy' over it's full measurement range then you have a good sensor, stop fussing with it. Absolute accuracy is all about standards and what you are using as a reference to compare all other readings with. We stressed about repairs and adjustments that ended up with good repeatablity, rather then the circular fool's path of 'proving' that a given measurement is 'accurate'. So we used the word repeatablity to mean precision and didn't pretend to claim anything about accuracy.

On a few legal compliance measurements that local government required for us to have official calibration standards traceable to an approved 3rd party lab standards. For those we had to send out a few of our "bench standards instruments" such as a couple of bench DMM, bench deadweight tester (used for pressure measurements), bench electronic pressure sensor, etc to an 'approved' calibration laboratory which would test our standards, publish accuracy specs for them and put a dated seal on them good for one year. With these 'bench standards' we could then use them to compare our other measurement equipment when dealing with compliance measurement issues.

The word accuracy is a very overloaded word that can mean many different things to many different people. In principal it would seem to be a simple word, how close is a specific measurement to it's 'true' value. The problem is defining 'true' and trying to implement it in a meaningful, useful, and practical matter. Metrology can be an incredibly complex field. It's also incredibly expensive. :smiley:

Well, now I've tried to try a few things in my code and have some interesting results. However I'm not done and will test this in various temperatures next.

In the following code I use three methods. All of them uses the same voltage divider so changes due to resistor tolerances and temperature change will be the same for all.

Method 1 is using the code from retrolefty.

Method 2 is using the code from Scott Daniels. This one suggests a method of calibrating which I've done: http://provideyourown.com/2012/secret-arduino-voltmeter-measure-battery-voltage/

Method 3 is just using 5V without bothering to know about reference voltage etc.

Powering using USB:
4.52V (measured using my meter)
4.44V (method 1) diff 1.8%
4.51V (method 2) diff 0.2%
4.45V (method 3) diff 1.5%

Powering usning 3S:
11.62V (measured using my meter)
11.46V (method 1) diff 1.5%
11.62V (method 2) diff 0.0%
11.61V (method 3) diff 0.1%

// Boopidoo Ground Station

#include <LiquidCrystal.h>

//LCD display pinout - YM2004A & OV1604A
//VSS   LCD pin 1     -  Connect to ground
//VDD   LCD pin 2     -  Connect to +5V
//V0    LCD pin 3     -  Connect to potentiometer
//RS    LCD pin 4     -  Arduino pin D07  
//RW    LCD pin 5     -  Connect to ground
//EN    LCD pin 6     -  Arduino pin D08
//DB4   LCD pin 11    -  Arduino pin D09
//DB5   LCD pin 12    -  Arduino pin D10
//DB6   LCD pin 13    -  Arduino pin D11
//DB7   LCD pin 14    -  Arduino pin D12
//ELA   LCD pin 15    -  Arduino pin D13
//ELK   LCD pin 16    -  Connect to ground
//LiquidCrystal lcd(7, NULL, 8, 9, 10, 11, 12);
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);

int screen_backlight = 13;                       //pin D13 will control the backlight
float voltage_battery1 = 0.0;                    //voltage from pin A0
float voltage_reference1 = 0.0;                  //reference voltage on the arduino 5V-rail
float voltage_battery2 = 0.0;                    //voltage from pin A0
float voltage_reference2 = 0.0;                  //reference voltage on the arduino 5V-rail
float voltage_divider = (6780.0+2720.0)/2720.0;  //((R1+R2)/R2)*voltage for voltage divider before pin A0

void setup() {
  pinMode(screen_backlight, OUTPUT);       //LCD Setup
  digitalWrite(screen_backlight, HIGH);    // turn backlight on. Replace 'HIGH' with 'LOW' to turn it off.
  lcd.begin(20,4);                         // columns, rows.  use 16,2 for a 16x2 LCD, etc.
  lcd.clear();                             // start with a blank screen
}

//method 1
int getBandgap(void) // Returns actual value of Vcc  (x100)
    {    
      #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
        // For mega boards
        const long InternalReferenceVoltage = 1115L;  // Adjust this value to your boards specific internal BG voltage x1000
        // REFS1 REFS0          --> 0 1, AVcc internal ref. -Selects AVcc reference
        // MUX4 MUX3 MUX2 MUX1 MUX0  --> 11110 1.1V (VBG)         -Selects channel 30, bandgap voltage, to measure
        ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR)| (0<<MUX5) | (1<<MUX4) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (0<<MUX0);
      #else
        // For 168/328 boards
        const long InternalReferenceVoltage = 1056L;  // Adjust this value to your boards specific internal BG voltage x1000
        // REFS1 REFS0          --> 0 1, AVcc internal ref. -Selects AVcc external reference
        // MUX3 MUX2 MUX1 MUX0  --> 1110 1.1V (VBG)         -Selects channel 14, bandgap voltage, to measure
        ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (0<<MUX0);
      #endif
      delay(50);  // Let mux settle a little to get a more stable A/D conversion
      // Start a conversion  
      ADCSRA |= _BV( ADSC );
      // Wait for it to complete
      while( ( (ADCSRA & (1<<ADSC)) != 0 ) );
        // Scale the value
        int results = (((InternalReferenceVoltage * 1024L) / ADC) + 5L) / 10L; // calculates for straight line value 
        return results;
    }

//method 2
long readVcc() {
  // Read 1.1V reference against AVcc
  // set the reference to Vcc and the measurement to the internal 1.1V reference
  #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
    ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
    ADMUX = _BV(MUX5) | _BV(MUX0);
  #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
    ADMUX = _BV(MUX3) | _BV(MUX2);
  #else
    ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #endif  
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA,ADSC)); // measuring
  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH  
  uint8_t high = ADCH; // unlocks both
  long result = (high<<8) | low;
  result = 1.1 * (5.00/5.13) * 1023 * 1000 / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
  //result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
  return result; // Vcc in millivolts
}

void read_voltage() {
  //method 1 using internal reference voltage
  voltage_reference1=(float)getBandgap();
  voltage_battery1=map(analogRead(0),0,1023.0,0.0,voltage_reference1) * voltage_divider;
  
  //method 2
  voltage_reference2 = readVcc();
  voltage_battery2 = map(analogRead(0),0,1023.0,0.0,voltage_reference2) * voltage_divider;

}

void screen_print() {
  //printing method 1
  lcd.setCursor(0,0);
  lcd.print(voltage_reference1 / 100); 
  lcd.setCursor(0,1);
  lcd.print(voltage_battery1 / 100);
  
  //printing method 2
  lcd.setCursor(10,0);
  lcd.print(voltage_reference2 / 1000); 
  lcd.setCursor(10,1);
  lcd.print(voltage_battery2 / 1000);
  
  //printing original method not bothering with voltage reference
  lcd.setCursor(0,3);
  lcd.print(analogRead(0)*voltage_divider/1023*5);
}

void loop() {  
  read_voltage();
  screen_print();
}

Also, another issue is that some of the values seem to fluctuate on my LCD screen. This is especially apparent for method 2. Any suggestions on how to reduce this and make the value more stable? I've added a 470n capacitor between A0 and ground.

Use smoothing:

 volt_avg = alpha * read_volt() + (1-alpha) * volt_avg;

If you pick alpha to be 1/2^n, you can greatly simplify the math above.

How do you mean? Sorry if I'm a little slow. :slight_smile:

I tried testing in just below 0°C and now I just bothered to take notes on method 2 and 3 since 1 was too far off.

Powering using USB:
4.47V (measured using my meter)
4.45V (method 2) diff 0.4%
4.39V (method 3) diff 1.8%

Powering usning 3S:
11.55V (measured using my meter)
11.56V (method 2) diff 0.1%
11.54V (method 3) diff 0.1%

Now if it gets even colder I will try again but for now I pretty happy with method 2, it seems to be very consistent with my meter which was what I strived for in the start.

dhenry:
Put a small capacitor on the analog input pin. Anything from 0.1n to 1000n will work.

When should I use a capacitor like this? On all analog inputs? I have some inputs that goes straight from a 0-5V source and thus doesn't need a voltage divider but do I still need the capacitor?

Also should I route both ground and + from this source? I also monitor its voltage so I guess there's a risk of ground loops.