Measuring voltage, again

This is driving me nuts.

I am trying to measure battery voltage. I have a voltage divider with 47K to source, and 20K to ground.

I have a 2.2uF cap to ground to filter any ripple.

What I am seeing is very high non-linearity.

If I set the max voltage to 16.75 (which is what is calculated from the divider) I get voltages that are too low. I get readings of 706 from the pin, which then multiplies out to 11.54V, for source voltages of 13.04.

If I change the max voltage to somewhere around 18.90 in order to get a correct reading at 13V, I get readings that are too low at 12V. The discrepancies can be as high as 0.5V, and the discrepancies don't seem to be constant.

If I disconnect everything, I get a reading of 0, which is correct.

I've tried reading the pin once, many times and averaging, and none of it makes any difference.

Here's the code, boiled down to just the essentials.

What am I doing wrong?

#include <inttypes.h>

unsigned int readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = 1125300L / result; // Back-calculate AVcc in mV
  return result;
}

unsigned int
analogRead100 (byte reg)
{
  unsigned long analog = 0;
  static int skip=1;  // I've tried various values, up to 8
  static long samples=1;  // I've tried various values, up to 128
  static int msec=2;

// throw away the first few readings
  for (byte ai = 0; ai < skip; ai++)
    analogRead (reg);

  if(msec > 0) delay(msec);

  for (byte ai = 0; ai < samples; ai++)
    {
      analog += analogRead (reg);
    }
  return analog / samples;
}


unsigned int getVoltage (unsigned int raw)
{
// 1675 is the max voltage from the divider
// 20K to ground
// 47K to source
  return (1675l * raw) / 1024l;
}

void
setup(void) {
  Serial.begin (9600);
}

void
loop(void) {
  unsigned int V, Vraw;


  delay(1000);

  Vraw = analogRead100 (A0);
  V = getVoltage(Vraw);
  Serial.print("Voltage ");
  Serial.print(Vraw);
  Serial.print(" ");
  Serial.println(V);

  Serial.print("Vcc ");
  Serial.println(readVcc());
  }

The capacitor has leak current.
Use 1nF to 100nF.
You could take the average of 3 ... 100 samples.

After reading Vcc against the internal voltage regulator, you have set things normal again with analogReference(), and perhaps the first analogRead() is not valid.

The normal time to wait after changing the reference voltage is 20ms (assuming 100nF connected to it).

I suggest to make a new minimal sketch that reads the voltage of the voltage divider.

Sometimes I have to adjust the linearity with an offset in my calculation. This offset could be less than 1mV.

Using 'float' numbers makes the calculation easier.

EDIT: I didn't understand you want both the Vcc and the voltage of the voltage divider. That gives for me different results on different Arduino boards. The analog value of the Arduino Mega 2560 seems unstable after measuring the Vcc. I still don't know why.

Are you using the Vin pin? That might have a diode between it and the battery, which can account for a difference.

This behavior was there before I added the routine to read Vcc. I thought that perhaps I had issues with Vcc fluctuations (not unreasonable as the regulated supply is quite far from the unit reading the voltage.)

As it turns out Vcc is stable to +/- 1/2% so it's not the source of the trouble so I can drop that.

I like the cap leakage current theory. I'll read up on that as it would explain why I am seeing what I am seeing. My problem is that the voltage I am trying to measure has noise at 1k5 and 3k Hz from PWM power supplies. I can remove the cap altogether and go to software averaging if that would make things better.

Speed is not critical; I need one accurate reading every 5 seconds.

To test a problem, it is better to write a minimal sketch to isolate the problem. Once the problem is solved, you can combine it with the other things.

You could measure the voltage with the internal reference, that way the Vcc has no influence.

What about ground currents ?
When using motors is can hard to have a 'clean' measurement of a voltage.
I mentioned an offset in my previous post, that was to compensate the ground current.
When the ground current of the motors would lift the ground level of the Arduino, you could get that kind of non-linearity.
When the motors are using PWM and different speeds, it is very hard to compensate that.

How much ripple is on the voltage you want to measure ?
Less than 1V ?
It is possible to filter that, but your resistors are too high for the leak current of the capacitor. Either use 100nF for the filter, or resistors in the range of 1k...4k7.

Erdin:
To test a problem, it is better to write a minimal sketch to isolate the problem. Once the problem is solved, you can combine it with the other things.

I bread boarded the setup first. Wrote the software based on that. It all worked. Then it started doing this in the field.

The only difference is that in the field, I have two PWM charging sources for a battery. In the test rig, I have an analog charger.

The field setup is this:

A fairly large battery (225 Ah, 12VDC) is charged by one of 3 sources:

Solar power with a PWM charger. The solar panels develop up to 21V. The PWM charger slices this into PWM pulses based on battery voltage and some internal programming. The base PWM frequency is 1k5 Hz. The voltage can range from about 15 to probably 19 (assuming 90% efficiency in the charger.)

Three stage charger off generator or mains. This uses a 3kHz filtered signal of some sort at between 13.2 and 14.4VDC. The company is not very open about what it actually provides, but it claims that the signal is good enough to use without a battery.

Vehicle alternator at between 13.9 and 14.4VDC, depending on engine RPM.

None of these is particularly clean. All of them rely to some extent on a large battery to suck up the voltage peaks. Still, there is a lot of ripple that gets through based on the interference I get on video gear using unfiltered power supplies.

I need to rig up a scope to see what's going on, but I used the large cap to try and filter some of the larger peaks.

Erdin:
You could measure the voltage with the internal reference, that way the Vcc has no influence.

Good idea.

Erdin:
What about ground currents ?

I went through that. I ended up running using an existing ground to the arduino to eliminate ground currents. I'm still not 100% satisfied that ground is clean; I'll probably run a new lead just for the arduino.

Erdin:
How much ripple is on the voltage you want to measure ?
Less than 1V ?
It is possible to filter that, but your resistors are too high for the leak current of the capacitor. Either use 100nF for the filter, or resistors in the range of 1k...4k7.

The source is up to 19V, but the battery absorbs a lot of that. Not sure what the actual ripple measures out at.

I have to keep the resistors in the 10k range to have acceptable current use through the meter, so it looks like I need a smaller cap and some digital filtering.

Erdin:
To test a problem, it is better to write a minimal sketch to isolate the problem. Once the problem is solved, you can combine it with the other things.

I bread boarded the setup first. Wrote the software based on that. It all worked. Then it started doing this in the field.

The only difference is that in the test rig, I have an analog charger. In the field, I have two PWM charging sources for a battery, so I added this big cap to take up the ripple.

The field setup is this:

A fairly large battery (225 Ah, 12VDC) is charged by one of 3 sources:

Solar power with a PWM charger. The solar panels develop up to 21V. The PWM charger slices this into PWM pulses based on battery voltage and some internal programming. The base PWM frequency is 1k5 Hz. The voltage can range from about 15 to probably 19 (assuming 90% efficiency in the charger.)

Three stage charger off generator or mains. This uses a 3kHz filtered signal of some sort at between 13.2 and 14.4VDC. The company is not very open about what it actually provides, but it claims that the signal is good enough to use without a battery.

Vehicle alternator at between 13.9 and 14.4VDC, depending on engine RPM.

None of these is particularly clean. All of them rely to some extent on a large battery to suck up the voltage peaks. Still, there is a lot of ripple that gets through based on the interference I get on video gear using unfiltered power supplies.

I need to rig up a scope to see what's going on, but I used the large cap to try and filter some of the larger peaks.

Erdin:
You could measure the voltage with the internal reference, that way the Vcc has no influence.

Good idea.

Erdin:
What about ground currents ?

I went through that. I ended up running using an existing tested-good ground to the arduino to eliminate ground currents. I'm still not 100% satisfied that ground is clean; I'll probably run a new lead just for the arduino.

Erdin:
How much ripple is on the voltage you want to measure ?
Less than 1V ?
It is possible to filter that, but your resistors are too high for the leak current of the capacitor. Either use 100nF for the filter, or resistors in the range of 1k...4k7.

The source is up to 19V, but the battery absorbs a lot of that. Not sure what the actual ripple measures out at.

I have to keep the resistors in the 10k range to have acceptable current use through the meter, so it looks like I need a smaller cap and some digital filtering.

I'm guessing: a gel lead acid battery.

With a little filter and some averaging, the Arduino should have no problem reading an accurate voltage of an accuracy of 0.1V. Perhaps even 0.05V or better.
When you use the internal reference, you have to measure the voltage with a good multimeter to 'tune' the calculation. The internal reference is not precisely 1.1V or 2.56V.

This is my voltage measurement with the Arduino Mega 2560 and a voltage divider with resistors.
You see how much tuning I have used to get it linear. The result is 0.02V (20mV) accurate.

float getPowerVoltage()
{
  int i, n;
  float v;
  
  // Calculate during 20ms, to reduce 50Hz mains noise.
  // This is not precisely done, it could even increase the 50Hz influence.
  // 20 analog reads will fit into an integer. So 'n' can be an integer.
  n = 0;
  for( i=0; i<20; i++)
  {
    n += analogRead( pinPowerVoltage);
    delay( 1);
  }
  
  // Calculate the voltage at the Arduino analog input pin.
  // The reference voltage is 1.074 (it should be 1.1).
  // 20.0 : number of added analog reads.
  // 1023.0 : resolution of Arduino analog input.
  // 1.074 : actual voltage reference of my Mega board at room temperature.
  // 0.006 : offset, due to ground path caused by shield.
  v = (((float) n / 20.0) / 1023.0 * 1.074) + 0.006;
  
  // I use 27k and 1k to measure the voltage.
  // So I have to multiply the voltage at the pin times 28.
  // It turned out, that the factor was 28.13
  // 28.13 : tuned value for resistor divider.
  return( v * 28.13);
}