Trying to measure input voltage... Doing something wrong.

I am trying to measure the "Vin" voltage into my 3V3 Arduino on an analog input pin. This is to monitor how the battery is doing. Because the Vin voltage is higher than the Vcc voltage, there is a voltage divider circuit to cut the voltage down by 1.5x. There is a 1M resistor from A5 to ground, and a 500k from A5 to Vin.

This actually works reasonably well as long as the ONLY thing being read is the analog pin connected to the voltage divider.

The problem comes when reading ANOTHER analog pin. In this case there is a a temp sensor which is really the point of the application, so that MUST get read. Introducing the reading of the temp sensor into the mix hoses the values I get back on my voltage reading.

Here is some sample code

const int voltage_pin = A5;
const int temp_pin = A4;

void setup(void)
{
  Serial.begin(57600);
  Serial.println("adc_test");
}

void loop(void)
{
  Serial.print("temp=");
  Serial.println(analogRead(temp_pin),DEC);
  Serial.print("volt=");
  Serial.println(analogRead(voltage_pin),DEC);
  delay(1000);
}

Running it, I get the following:

adc_test
temp=221
volt=883
temp=221
volt=869
temp=223
volt=885
temp=223
volt=900
temp=225
volt=870
temp=224
volt=924
temp=223
volt=881
temp=223
volt=869
temp=224

But if I comment out the temp reading, I get more reasonable values:

adc_test
volt=980
volt=985
volt=985
volt=986
volt=985

Anyone have thoughts on what needs to be done to get this right? Thanks!

The ADC circuit has a Sample and Hold capacitor that charges up and is then converted.
Your large resistor values don't let much current in for that to happen, just a few microamps.
Put a little delay between the 2 read types, should help the 800+ numbers look more like the 900+ numbers.
Or, lower the 500K/1M values.

The spec says the ADC wants signals with a source impedance of 10k or less for reliable measurements.

Change your voltage divider to use 10k and 4k7 resistors.

MarkT:
The spec says the ADC wants signals with a source impedance of 10k or less for reliable measurements.

Change your voltage divider to use 10k and 4k7 resistors.

Oooh.. Wow, ok, thanks. Reading through the ADC part of the data sheet, I just could not figure that out.

That's rough, though. So at 10k impedance, that circuit is going to burn 0.33mA, right? As it stands now, this unit uses 0.12mA on average, so this would quadruple my current usage just to measure current usage... Taking 20 months out of my battery life! Yikes.

The thing is, when I read the values by themselves, I get better readings. They're still a little off from what I'd expect, but not by 100+.

Tonight I'll run some tests against CR's suggestion too.

That's rough, though. So at 10k impedance, that circuit is going to burn 0.33mA, right? As it stands now, this unit uses 0.12mA on average, so this would quadruple my current usage just to measure current usage... Taking 20 months out of my battery life! Yikes.

Time for a "low side switch"? You can then "turn off" the voltage divider when you are not measuring the battery voltage (you should only need to take a measurement every few minutes, hours, or possibly days).

CrossRoads:
Put a little delay between the 2 read types, should help the 800+ numbers look more like the 900+ numbers.

Tried that, to no avail. Here's the new sample...

const int voltage_pin = A5;
const int temp_pin = A4;

unsigned long interval = 250; // ms
const unsigned long increase = 250; // ms

void setup(void)
{
  Serial.begin(57600);
  Serial.println("adc_test");
}

void loop(void)
{
  Serial.print("delay=");
  Serial.print(interval,DEC);
  Serial.print(" temp=");
  Serial.print(analogRead(temp_pin),DEC);
  delay(interval);
  Serial.print(" volt=");
  Serial.println(analogRead(voltage_pin),DEC);
  delay(interval);
  
  interval += increase;
}

...and the output:

adc_test
delay=250 temp=217 volt=908
delay=500 temp=218 volt=901
delay=750 temp=220 volt=882
delay=1000 temp=220 volt=899
delay=1250 temp=220 volt=867
delay=1500 temp=220 volt=886
delay=1750 temp=220 volt=874
delay=2000 temp=220 volt=875
delay=2250 temp=220 volt=886
delay=2500 temp=213 volt=879
delay=2750 temp=220 volt=883
delay=3000 temp=220 volt=874
delay=3250 temp=219 volt=900
delay=3500 temp=220 volt=909
delay=3750 temp=220 volt=870
delay=4000 temp=220 volt=909
delay=4250 temp=213 volt=871
delay=4500 temp=220 volt=883
delay=4750 temp=220 volt=920
delay=5000 temp=220 volt=894
delay=5250 temp=219 volt=889
delay=5500 temp=220 volt=879
delay=5750 temp=220 volt=879
delay=6000 temp=220 volt=893
delay=6250 temp=220 volt=896

Time for a "low side switch"? You can then "turn off" the voltage divider when you are not measuring the battery voltage (you should only need to take a measurement every few minutes, hours, or possibly days).

Ok, thanks, I will check those out. Never heard of it before. Yes, that's right in fact the whole unit is only awake for 40ms every minute to take and send a temp reading. And yes, a voltage reading once a day is probably even overkill. It sounds like that would do the job.

For the moment, it seems to work just living with the low reading. It's off by a reliable factor, so as long as I calibrate it, I can obtain the true voltage across the range of operating voltages.

What is connected to AREF?

IEdit: I removed the stuff with dividers, needs to be measured again..

Edit: when you use a high impedance dividers on the input ain5 and low impedance source on ain4 and the voltage difference between the channels is high and you are scanning the both analog channels fast in a loop, then you will get such difference
as depicted in the first topic (charging/discharging high potential on the S/H capacitor via two different impedancies)..
If the voltage difference between the two channels will be low, then you may use an higher impedance divider on one channel and lower impedance on the next as the S/H capacitor will be held on a "constant" potential level and therefore will charge/discharge a smaller potential/current..

Your last readings seems ~ok for me, as a) you use Vcc as the reference ( a lot of niose) and b) there is a lot of noise comming from poor decoupling maybe , c) or from other grounding loops. Try to put 10nF-100nF cpapcitor between analog input and ground and you will get better numbers, hopefully.
P.

Thanks for the responses, folks!

What is connected to AREF?

Not connected. Tried tying it to VCC just now. No appreciable difference.

Your last readings seems ~ok for me

All those things about the vcc and noise, etc, still apply when I was getting the reading of only the voltage, I was getting 980's which was more right. Only the addition of testing the other analog pin changes the other reading to <900, which is definitely wrong.

Try to put 10nF-100nF cpapcitor between analog input and ground and you will get better numbers, hopefully.

Aha! Yes, a 104 cap did the job. Now the values are fantastic. Thanks!!

Doh... I already sent off the first set to the fab. Guess I have some blue-wire in my future :slight_smile:

What is connected to AREF? Not connected. Tried tying it to VCC just now. No appreciable difference.

Try a 0.1 uF capacitor between AREF and positive voltage.

..and try this:

analogReference(INTERNAL);

..

pito:
..and try this:

analogReference(INTERNAL);

From analogReference() - Arduino Reference ...

analogReference(type)
Description

Configures the reference voltage used for analog input (i.e. the value used as the top of the input range). The options are:

INTERNAL: an built-in reference, equal to 1.1 volts on the ATmega168 or ATmega328 and 2.56 volts on the ATmega8 (not available on the Arduino Mega)

1.1 volts?! Maybe you mean 'EXTERNAL'?

Ok, I tried this, and the results were just bizarre. After adding that cap and setting 'analogReference(EXTERNAL)', which from the docs looks like the right thing, I was getting readings of 1024.

In any case, the cap across A5/GND seems to do the trick.

Thanks again!

The code:-
Serial.print(" temp=");
Serial.print(analogRead(temp_pin),DEC);
delay(interval);
Serial.print(" volt=");
Is not what was meant by inserting a delay it should be like this:-
Serial.print(" temp=");
temp = analogRead(temp_pin);
delay(interval);
Serial.print(analogRead(temp_pin),DEC);
Serial.print(" volt=");

The delay goes between the first read of the input channel and the second read. You throw away the first read as the capacitor will not have had time to charge up. You do this every time you change channels. Read it, delay, read it again.

Is AVCC connected to power?

..maybe I am missing something but normally I would go with the internal ref source (1.1V or 2.56V) when available..
I would consider Vcc as a ref source as a last option, though. P.

maybe I am missing something

You are missing the fact that this is meant to be a simple introductory system, in a world where 5V is the norm and simple to use.
Adding extra complication for accuracy is not normally worth it.
Of course you can use it when you want, if you know how but it is more complex and 95% of the time not necessary.

This is all good stuff, though. I learned a ton on this thread. Also realized that this temp sensor (MCP9700) doesn't exceed 1.1V until way outside the range of temps it will actually operate in, so I could consider INTERNAL.

The interesting thing about Arduino is that it IS made simple, BUT right under the covers is all the power that the AVRFreaks guys have available, all you have to do is figure it out :slight_smile: