I’ve just published a small, open source, Arduino library to sense battery level in Arduino projects and, while writing the readme file, I found myself trapped explaining the reasons behind some decisions. While I believe those might be valuable, I believe they deserve a post as they have a broader range of application than the library itself.
I’ll try to keep the explanations as simple as possible, mainly for two reasons: keep the thing simple for beginners and avoid saying stupid things myself.
The Arduino microcontroller (commonly an Atmega328 integrated circuit) is capable to convert analog readings into a digital value through its internal ADC (Analog to Digital Converter). This conversion is never perfect and it’s precision is determined by the ADC resolution, in our case being 10 bits: we get a value between 0 and 1023 representing usually a voltage between 0V and 5V.
This means our readings have a resolution of about 5mV ad we will not be able to determine the difference between 1.000V and 1.004V.
This is not bad for most projects, but definitely something to be aware of. You can’t get around the 10 bit resolution limit (well, you can using averaging techniques, but that’s out of scope for now), but you can increase your resolution by reducing the reference voltage: a scale of 1024 applied to 1V means an increment every approximately 1mV, five times better.
Impedance and capacitance
The internal conversion circuit stores the voltage to be measured into a small capacitor (14pF in most cases) through a resistor, in the order of 10k Ohm. This means the ADC is not reading the voltage at the pin, but the voltage at the capacitor plates! This might be higher or lower than the voltage you are trying to measure, due to ADC multiplexing (more on this later).
If you try to measure the voltage of a low impedance circuit this has no side effects: the capacitor will charge/discharge very quickly due to it’s very small capacitance and your readings will be accurate.
But if you try to measure a high impedance circuit (like an LM35 temperature sensor) , you must take into account the time it requires to allow the capacitor to charge/discharge. A common workaround for this is to perform two or more readings in rapid succession: each reading will give the capacitor a chance to equalize to the pin voltage, eventually bringing it to the desired voltage.
You have many analog pins on your board, but there is only 1 ADC in your microcontroller.
This means your analog readings will be multiplexed or, if you prefer, they will share the same ADC circuit. This condition limits the speed of the analogRead function and, in conjunction to the circuit capacitance and impedance, can lead to wrong readings: if you try to measure a 5V signal on pin A0 and then try to measure a 0V signal on pin A1, the internal ADC capacitor needs to fully discharge before an accurate conversion can be performed.
If you need to perform multiple, fast, accurate readings, you might want to consider using an external ADC.
It is very often underestimated, but it’s of the highest importance: all the values returned by the ADC are determined by comparing the internal capacitor voltage to a reference voltage.
Unless you provide a specific voltage to the AREF pin, the ADC uses Vcc or 5V as reference voltage, meaning a value of 1023 means approximately 5V. Yes, you read it right: approximately.
The voltage at the Vcc or 5V pin is not guaranteed to be exactly 5V: it will depend on your voltage source circuit precision and calibration. For a battery powered project using a voltage booster to generate 5V out of a LiPo/LiIon battery, it will probably change with time, while an Arduino powered from a 9V battery through the VIN pin or the barrel jack, it will depend on the on-board voltage regulator.
As an example, using my medium priced multimeter, using a completely charged 9V battery, I measure 4.9V at the 5V pin on one board and 5.2V on another one. Those are cheap chinese Arduino clones.
Fortunately the microcontroller provides an internal reference voltage which is independent from Vcc, but it is not the default reference voltage and it is not as precise as we wish. If we want to get accurate, voltage source independent analog readings we need to compensate the internal reference voltage.
Without trying to get into the details, I wrote a nice, simple and open source library to do it: please refer to VoltageReference Github project.
The reference voltage can be used to improve your readings accuracy, if you can trade off on the voltage range: if you provide exactly 1V to the AREF pin each reading you will make will have about 1mV precision (977uV, to be precise), but it can only range between 0V and 1V. If you are dealing with low voltage measurements it’s best to use the internal 1.1V voltage reference, possibly having it calibrated for best accuracy.