Today I received a cheap chinese clone of Arduino Nano v 3.0 marked as Iduino and using a red PCB. While I was prototyping an idea on my breadboard I decided it was time to start having an idea of the supply battery voltage to have a rough idea of battery capacity and I stumbled upon what I believe is a great post Measurement of Bandgap voltage - Development - Arduino Forum (BTW, why the core library still lacks the getBandgap() or readBandgap() function?)
I was getting wrong readings with the default function value, so I decided to try calibration, but I stumbled upon another issue: it seems I'm unable to get a stable AREF reading.
The process I followed was to place a 0.1uF capacitor between AREF and GND with all other pins disconnected and then running the simple sketch reported there
897
912
920
921
922
923
924
...
after a couple of minutes
...
891
891
893
892
894
894
So here is my question: am I doing something wrong? Is it possible the cheap board I bought has some issues? I did put the breadboard out of the equation by using direct wiring, so that's definitely not the source of my issue...
The sketch is reading analog input #0. What is connected to that pin? If the answer is nothing, then varying output would be expected, due to picking up stray fields. This sketch is not about evaluating the stability of AREF.
Could also just be a noisy board voltage source or lack of bypass filtering cap on the Avcc pin. When using the INTERNAL reference the actual voltage used as the ADC reference is that wired to the Avcc pin which is usually just the same as the Vcc pin. The AVR datasheets recommend adding an inductor filter for the Avcc pin but few board designs use that, most just place a .1ufd cap from Avcc to ground.
Many people do some 'software filtering' to try and smooth out analogRead values, like taking 4 readings, add them together and divide by 4, or other methods.
Bottom line is that the AVR analog input pins are very useful but should not be considered instrumentation quality. There are many inexpensive I2C or SPI ADC modules available today if your readings for your project(s) requires it.
I believe this sketch reads A0 just to obtain the internal reference voltage value: during setup the analogReference is switched to INTERNAL, which refers to the internal 1.1V voltage reference within the 328 chip.
Anyway, to answer your question, yes, A0 is floating (not connected).
retrolefty:
Could also just be a noisy board voltage source or lack of bypass filtering cap on the Avcc pin. When using the INTERNAL reference the actual voltage used as the ADC reference is that wired to the Avcc pin which is usually just the same as the Vcc pin. The AVR datasheets recommend adding an inductor filter for the Avcc pin but few board designs use that, most just place a .1ufd cap from Avcc to ground.
At this stage the board is powered by my laptop USB while the laptop is powered by mains, I hardly believe this can be considered anyhow unstable.
I did place a .1uF cap and I can't see any inductor whatsoever on the PCB.
retrolefty:
Bottom line is that the AVR analog input pins are very useful but should not be considered instrumentation quality. There are many inexpensive I2C or SPI ADC modules available today if your readings for your project(s) requires it.
I'm not looking for a three decimals precision, a rough estimate is more than enough, but with the values I'm reading I'll have 200mV variation in the calibration, which will then be multiplied when reading the battery value by a factor of 4.7... about 1V error is unacceptable
rlogiacco:
I believe this sketch reads A0 just to obtain the internal reference voltage value: during setup the analogReference is switched to INTERNAL, which refers to the internal 1.1V voltage reference within the 328 chip.
That's not how it works. It's reading the voltage on A0, using the selected reference to scale the returned value. See post above.
From the ATmega328P datasheet, the value returned by the ADC is (Vin * 1024) / Vref.
Shame on you, even after you went through the whole mega1284P update process, of posting a sketch that is device specific. What about running it on a mega, micro, etc?
Here is one I'm always playing with. Wrote it on top of blink just for grins:
/*
Blink
Turns on an LED on for one second, then off for one second, repeatedly.
This example code is in the public domain.
*/
// Pin 13 has an LED connected on most Arduino boards.
// give it a name:
int led = 13;
long battVolts;
// the setup routine runs once when you press reset:
void setup() {
// initialize the digital pin as an output.
pinMode(led, OUTPUT);
Serial.begin(9600);
Serial.print("millivolts = ");
}
// the loop routine runs over and over again forever:
void loop() {
digitalWrite(led, HIGH); // turn the LED on (HIGH is the voltage level)
delay(500); // wait for a second
battVolts = readVcc();
Serial.println(battVolts);
digitalWrite(led, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
Serial.print("millivolts = ");
}
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__) || defined(__AVR_ATmega1284P__)
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 = 1110500L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000 tweek to allow for 1.1 variation
return result; // Vcc in millivolts
}
Humm... SO where should I stick that A0 pin considering I'm just trying to find the calibration value to have an idea of the voltage of the voltage source that will power my arduino?
[quote author=Jack Christensen link=topic=240254.msg1723595#msg1723595 date=1400106623]
That's not how it works. It's reading the voltage on A0, using the selected reference to scale the returned value. See post above.
From the ATmega328P datasheet, the value returned by the ADC is (Vin * 1024) / Vref.
Humm... SO where should I stick that A0 pin considering I'm just trying to find the calibration value to have an idea of the voltage of the voltage source that will power my arduino?
[/quote]
First tell us how you are planning on powering the board, USB, on-board regulator, direct battery voltage, external regulated +5 vdc supply?
The plan is to use three AAA NiMh batteries connected to 5Vpin and GND, subsequently replaced by a LiIon or LiPo stepped up to 5V and again connected to 5Vpin and GND.
At this stage the board is powered from USB though.
BTW, by using your sketch I'm reading a stable 4551mV... which doesn't look too much right to me as it is connected to a mains powered laptop USB port...
rlogiacco:
The plan is to use three AAA NiMh batteries connected to 5Vpin and GND, subsequently replaced by a LiIon or LiPo stepped up to 5V and again connected to 5Vpin and GND.
At this stage the board is powered from USB though.
BTW, by using your sketch I'm reading a stable 4551mV... which doesn't look too much right to me as it is connected to a mains powered laptop USB port...
On AVR chips the 1.10000000 band-gap reference is very stable but has a device tolerance of like form 1.0 to 1.2 or so. That is the reason the line:
result = 1110500L / result; // Calculate Vcc (in mV); 1125300 = 1.110231000 tweek to allow for 1.1 variation
That 1110500L number has to be tweeked up or down until the results equal what you read on the Vcc pin with a decent digital multimeter. There is no getting around using something as your local reference for this kind of work, a good DMM at least.
Every board I own has a slightly different 1.1 vdc band-gap value, no getting around that either. Once you have that 'tweeked' you will have a number that the function returns in millivolts, lets call it calVolts. This can be then used to compensate for any analogRead(pin#) you make as follows:
int rawValue = analogRead(A0);
int compensatedValue = map(rawValue, 0,1023,0,calVots); // value in millivolts
You can choose to how often to call the readVcc function, either just once in setup in the case of a regulated supply or before every analogRead() for direct battery power that changes over time.
retrolefty:
Shame on you, even after you went through the whole mega1284P update process, of posting a sketch that is device specific. What about running it on a mega, micro, etc?
I know, I'm soooo embarrassed
Here is one I'm always playing with. Wrote it on top of blink just for grins:
Works very nice, on a 328P or on a 1284P. The internal reference voltage does indeed have about a 9% tolerance according to the datasheet. But like a lot of things with these MCUs, I find that they're usually a lot closer than the advertised worst-case scenario. I did a little experiment to compare (see results below), without tweaking the constant in the calculation. If I assume the Fluke meter is correct, then I have a 328P with a very close internal reference voltage, and a 1284P that is not as close, but not too bad either. 2% or 3% error is probably good enough for a lot of battery monitoring applications. Still probably worth checking and maybe calibrating because there are no guarantees.
rlogiacco:
Humm... SO where should I stick that A0 pin considering I'm just trying to find the calibration value to have an idea of the voltage of the voltage source that will power my arduino?
Connect it to the battery to be measured. Then, use the technique above to measure Vcc. That then gets used in the calculation to make the battery reading more accurate. Calibrate the internal voltage reference as lefty described for the best accuracy.
Code might be something like this.
void setup(void)
{
Serial.begin(115200);
}
void loop(void)
{
long Vcc = readVcc(); //read Vcc against the internal 1.1V reference
analogReference(DEFAULT); //ensure we switch back to use Vcc as the reference voltage
long adc = analogRead( 0 ); //read the ADC input value
long vA0 = adc * Vcc / 1024; //convert to volts, adjust for Vcc
Serial.print( "Vcc = ");
Serial.print( Vcc );
Serial.print( " mV, A0 input = ");
Serial.print( vA0 );
Serial.println( " mV");
delay(1000);
}
//read 1.1V reference against AVcc
//from http://code.google.com/p/tinkerit/wiki/SecretVoltmeter
int readVcc(void)
{
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
delay(2); //Vref settling time
ADCSRA |= _BV(ADSC); //start conversion
loop_until_bit_is_clear(ADCSRA, ADSC); //wait for it to complete
return 1126400L / ADC; //calculate AVcc in mV (1.1 * 1000 * 1024)
}
retrolefty:
...
uint8_t low = ADCL; // must read ADCL first - it then locks ADCH
uint8_t high = ADCH; // unlocks both
long result = (high<<8) | low;
result = 1110500L / result; // Calculate Vcc (in mV); 1125300 = 1.110231000 tweek to allow for 1.1 variation
return result; // Vcc in millivolts
}
Two things...
Reading the ADC registers separately has the potential to fail. As long as the semantics are preserved, the optimizer is allowed to reorder however it pleases. In other words, the compiler could actually do this...
uint8_t high = ADCH; // unlocks both
uint8_t low = ADCL; // must read ADCL first - it then locks ADCH
The solution to the problem is simple. Use ADC...
return 1110500L / ADC; // Calculate Vcc (in mV); 1125300 = 1.110231000 tweek to allow for 1.1 variation
}
The correct value is 1024. I sincerely apologize. When we first worked through the code I mistakenly used 1023. Sadly, that mistake has been duplicated many many times over the internet.
So, the section above should be replaced with this...
return 1126400L / ADC; // Calculate Vcc (in mV); 1126400 = 1.110241000 tweek to allow for 1.1 variation
}
[quote author=Jack Christensen link=topic=240254.msg1723595#msg1723595 date=1400106623]
That's not how it works. It's reading the voltage on A0, using the selected reference to scale the returned value. See post above.
From the ATmega328P datasheet, the value returned by the ADC is (Vin * 1024) / Vref.
Humm... SO where should I stick that A0 pin considering I'm just trying to find the calibration value to have an idea of the voltage of the voltage source that will power my arduino?
[/quote]
A precision 1.024V reference
So, I've put the code inside a library, just because I'm a fan of, here the source as a Gist: VoltageReference · GitHub
I'm now facing another doubt.
I've measured the output voltage of my laptop USB with no load and it is precisely 5200mV.
I've connected my original Arduino Micro and measured the voltage at it's pins, and no surprise it's 5200mV.
I've connected my cheap Arduino Nano clone and the voltage measured between GND and 5V pins is 4800mV while the USB input voltage is still 5200mV. Now, I believe there's a diode somewhere which is causing the voltage drop, which is still acceptable, but which value should I use for calibrating the library? I believe it should be the voltage measured between the pins as that should be the voltage powering the MCP...