Arduino: Analog measurements

An Arduino has a number of analog inputs, which enables us to measure parameters in the analog domain. This could be a voltage, current, resistance, temperature, light and so on. This article explores the usage and performance of the analog to digital converter (ADC) on the Arduino. The tests are performed on an Arduino Nano v3.0 which is very similar to the Arduino Uno with the most notable difference its size. Also the microcontroller ATMEGA328 on board is the same and runs at a clock frequency of 16 MHz.
AnalogRead
The conversion time of the standard analogRead() function is examined with the code below. To detect the start and end of the conversion a marker output is created on pen 12. The level on this pin is toggled before the analogRead() is executed. The execution of bitSet() and bitClear() to toggle the output lasts 125 ns.
int marker = 12; // marker output pin
int analogPin = 3; // analog input pin
int aval = 0; // analog value
void setup() {
pinMode(marker, OUTPUT); // pin = output
}
void loop() {
bitSet(PORTB, 4); // marker high
aval = analogRead(analogPin); // sample signal
bitClear(PORTB, 4); // marker low
aval = analogRead(analogPin); // sample signal
}

During the analog to digital conversion the level on the marker pin stays high for 111 μs. The execution time (125 ns) for changing the marker signal is therefore negligible. The maximum sample speed with the analogRead() function is therefore 9.1 kHz. A drawback of using analogRead() is that the Arduino can't execute other code while waiting for the conversion result.
Using ADC interrupts
As described above, the Arduino can't execute other code while executing the analogRead() function. Since the ATMEGA328 doesn't use the CPU-core for the acquisition of the analog signals it's a waste of processing capabilities. Especially when a signal must be sampled continuously. This can be done more sophisticated by using interrupts. Because there are no default functions to setup an analog conversion with interrupts, the registers associated with the ADC must be manipulated manually.
Single shot sampling
A single shot sampling is in fact what the Arduino does when calling the analogRead() function. There is not much to gain advantage when this is done by other means. Because before the ADC can be started, first the ADC-ready flag has to be checked. And this means polling the flag in a loop, and is not different than the Arduino does.
Free running sampling
When a signal must be sampled continuously, it's a good idea to set this up with interrupts. The ATMEGA328 can be configured into a free running mode. In this mode the ADC conversion starts automatically after the previous one. Every time a conversion is finished an interrupt is generated that calls the interrupt routine ISR(ADC_vect) wherein the ADC-result can be read and processed.
To setup the free running mode three registers must be configured: ADMUX, ADCSRA and ADCSRB. A detailed description of these registers can be found in the ATMEGA328 datasheet. In this case the internal 1.1 volt reference voltage and the analog input channel ADC3 is selected with ADMUX. The clock frequency is selected in ADCSRA and is set at ÷16 in this example. A single analog conversion lasts 13 clock cycles. The sample rate can be calculated from this setting and the CPU clock frequency: 16 MHz/(16*13) ≈ 77kHz. By making bit 6 in ADCSRA high, the free run conversion starts.
int marker = 12; // marker output pin
int aval = 0; // analog value
void setup() {
pinMode(marker, OUTPUT); // pin = output
DIDR0 = 0x3F; // digital inputs disabled
ADMUX = 0xC3; // measuring on ADC3, use the internal 1.1 reference
ADCSRA = 0xAC; // AD-converter on, interrupt enabled, prescaler = 16
ADCSRB = 0x40; // AD channels MUX on, free running mode
bitWrite(ADCSRA, 6, 1); // Start the conversion by setting bit 6 (=ADSC) in ADCSRA
sei(); // set interrupt flag
}
void loop() {
}
/*** Interrupt routine ADC ready ***/
ISR(ADC_vect) {
bitClear(PORTB, 4); // marker low
aval = ADCL; // store lower byte ADC
aval += ADCH << 8; // store higher bytes ADC
bitSet(PORTB, 4); // marker high
}
The result of the AD-conversion is read within the interrupt routine ISR(ADC_vect). Because the result of the analog to digital conversion is 10 bits long it's divided over two bytes wide registers: ADCL and ADCH. For a correct readout it's important that first the ADCL is read and only then the ADCH register. In the example code the ADC-result is copied in the int variable "aval".

To measure the execution time the marker is made low before reading the ADC and is made high again after it. Note that the total execution time of the interrupt routine is longer.
Notice that the "loop" now is totally free and can be used to execute other code.
Which reference?
To measure an analog signal there has to be a voltage level to compare it with. This voltage is called the reference. In the ATMEGA328 of the Arduino this reference voltage is also the maximum voltage that can be measured. The voltages are always measured relative to the ground. The Arduino has three reference voltage options: AVcc which is connected to the digital 5 V power line, the internal 1.1 V and the option to use an external voltage reference. Because the measurements of the input voltages are made relative to the reference voltage, fluctuations of the reference voltage will influence the result.
The reference voltage can be set with the function analogReference() or with the REFS[1:0] bits in the ADMUX register.


AVcc reference
The AVcc is the default reference voltage and is only useful when measuring voltages that are directly dependent on the supply voltage. This is the case where the voltage of a half resistor bridge must be measured as shown in figure 4. If for some reason the supply voltage drops, the voltage on the junction of the two resistors will drop in proportion. Because now the reference and input voltage change in proportion, the ADC-result will be still the same.
Internal 1.1 V reference
Use the internal 1.1 V reference for exact measurements of external voltages. The 1.1 V reference is more stable and hardly depends on the supply voltage or temperature. Therefore absolute measurements can be made. The example in figure 5 uses the 1.1 V reference and a 10:1 resistor divider to measure external voltages between 0 and 11 V.
Accuracy
According to the ATMEGA328 datasheet the reference voltage is 1.1 ±0.1 V. This is quite a large tolerance. The measured reference voltage of the tested Arduino Nano was 1.089 V at an ambient temperature of 21 °C and the package temperature was 29 °C. When the package of the ATMEGA328 is cooled down with freeze spray to -18 °C the reference voltage measures 1.084 V. The temperature drift is therefore 100 ppm/°C.
int analogPin = 3; // analog input pin
void setup() {
analogReference(INTERNAL); // select internal 1.1 volt reference
Serial.begin(9600);
}
void loop() {
int aval = analogRead(analogPin); // sample analog input
Serial.println(aval);
delay(300);
}
A test with the code beside confirms a slight result drift due to the temperature change. The analog pin 3 is connected to a 0.545 V source. At a case temperature of 29 °C the result must be: (0.545/1.089)*1024 = 512 (511 read), and at a temperature of -18 °C it will become: (0.545/1.084)*1024 = 515 (515 read)
However the temperature drift is low, for an exact measurement an Arduino application must be calibrated due to its large overall uncertainty of 10 %.
Noise

One way to measure the noise level is to look at the spread in values from the ADC. To do this a stable DC-level is offered to one of the analog inputs and the converted values are binned to create a histogram.
Test circuit
The circuit in figure 6 delivers the test voltage to the Arduino. The source is a highly stable low noise adjustable power supply set at a voltage of 0.55 V, halve the 1.1 V reference voltage. The signal is extra filtered by R1, C1,2 and connected via a 100 Ω resistor R2 to the analog input A3 of the Arduino. The ground is connected to GND pin at the same row.
Binning
The ATMEGA328 ADC has a resolution of 210 = 1024 bits. The idea of binning is to count how often a certain value occurs. Therefore an array is created with 1024 places called bins, which represents each of the possible ADC values. Because the available memory is limited, only byte size bins can be created. The number of counts is therefore limited to 255.
Two programs
The noise is tested by using the analogRead function as well as with the interrupt method. The two programs do in essence the same thing: An array is defined with 1024 bins. In the setup all the bins are set at 0 and the internal 1.1 V reference voltage is selected.
Both programs use a run-up of 10000 dummy readings. After this the binning starts and on each ADC result the corresponding bin is increased by one. If one of the 1024 bins reaches the maximum of 255 counts the acquisition stops and all the 1024 bin values are send to the computer.
int analogPin = 3; // analog input pin
int sendStatus = 0; // send status
int startDelay = 0;
byte valueBin[1024]; // value bins
void setup() {
analogReference(INTERNAL); // select internal 1.1 volt reference
for (int i=0; i<=1023; i++) { // clear bins
valueBin[i] = 0;
}
Serial.begin(9600);
Serial.println("Start");
}
void loop() {
int aval = analogRead(analogPin); // sample analog input
if (sendStatus == 0) { // do nothing the first x samples
if (startDelay < 10000) {
startDelay++;
}
else {
valueBin[aval] += 1; // increase value bin
if (valueBin[aval] == 255) { // stop if a bin is full
sendStatus = 1;
}
}
}
if (sendStatus == 1) {
for (int i=0; i<=1023; i++) { // output bin values
Serial.print(i);
Serial.print("\t");
Serial.println(valueBin[i]);
}
Serial.println("Done");
sendStatus = 2;
}
}
int sendStatus = 0; // send status
int startDelay = 0;
byte valueBin[1024]; // value bins
void setup() {
TIMSK0 = 0x00; // disable timer (causes anoying interrupts)
DIDR0 = 0x3F; // digital inputs disabled
ADMUX = 0xC3; // measuring on ADC3, right adjust, internal 1.1V ref
ADCSRA = 0xAC; // AD-converter on, interrupt enabled, prescaler = 128
ADCSRB = 0x40; // AD channels MUX on, free running mode
bitWrite(ADCSRA, 6, 1); // Start the conversion by setting bit 6 (=ADSC) in ADCSRA
sei(); // Set global interrupt flag
for (int i=0; i<=1023; i++) { // clear bins
valueBin[i] = 0;
}
Serial.begin(9600);
Serial.println("Start");
}
void loop() {
if (sendStatus == 1) {
for (int i=0; i<=1023; i++) { // output bin values
Serial.print(i);
Serial.print("\t");
Serial.println(valueBin[i]);
}
Serial.println("Done");
sendStatus = 2;
}
}
/*** Interrupt routine ADC ready ***/
ISR(ADC_vect) {
int aval = ADCL; // store lower byte ADC
aval += ADCH << 8; // store higher bytes ADC
if (sendStatus == 0) {
if (startDelay < 10000) { // do nothing the first x samples
startDelay++;
}
else {
valueBin[aval] += 1; // increase value bin
if (valueBin[aval] == 255) { // stop if a bin is full
sendStatus = 1;
}
}
}
}
Test frequencies
The test is performed with the analogRead function and with the continues sampling method. Since the sample frequency in the last case is adjustable, four different frequencies are tested by changing the value in the line ADCSRA = 0xAC;. These are: 9.6 kHz (clk÷128), 19.2 kHz (clk÷64), 38.4 kHz (clk÷32) and 76.9 kHz (clk÷16). The sample frequency with analogRead is approximately 9.1 kHz.
Results
The results of both methods and all the sample frequencies are similar. The samples are divided over two adjacent bins and exceptionally some samples can be found in a third bin. This means that the noise levels in all cases are acceptably low.

Switching between inputs and reference voltages
The Arduino Nano has 8 analog inputs. Since the ADC can only digitize one signal at once, the analog input to be converted must be selected before the ADC starts. Also the required reference voltage must be selected first.
Selecting analog inputs
The selection of the analog input is done by analogPin = n; where n is the analog pin number, or by changing the Analog Channel Selection Bits MUX[3:0] in the ADMUX register. Particular attention should be paid if the free running mode is used: The analog channel must be selected before a new analog conversion starts. In the interrupt routine an analog input is selected whereof the result is read at the following interrupt.
To test the noise levels and accuracy when inputs are switched, the additional codes below are included in the codes 4 and 5. A second voltage is applied to the analog input 5. Also now the measured values are binned just as in the noise test.
void loop() {
if (analogPin == 3) {
analogPin = 5;}
else {
analogPin = 3;}
int aval = analogRead(analogPin);
...
ISR(ADC_vect) {
if (ADMUX == 0xC3) {
ADMUX = 0xC5;}
else {
ADMUX = 0xC3;}
int aval = ADCL;
...
Results
Both measured voltages are visible as two spikes in the histograms. Figure 8 shows the histograms of the five tests: AnalogRead function, free running at ÷128, ÷64, ÷32 and ÷16. The measured values of the first voltage (ADC value 511) doesn't deviate from the previous noise test. So the measurement are still accurate. The surrounding bins are very small what means that the noise level didn't increase.

Changing reference voltage
Changing the reference voltage can't be done so quickly as switching between the analog inputs. This is due to the 100 nF capacitor connected to the AREF pin of the ATMEGA328 on the Arduino board. This capacitor needs time to charge and discharge. Figure 9 shows an oscilloscope plot of these settle delays. The time it takes to change the reference voltage from 1.1 V to 5 V is reasonably fast: 15 μs, but the change in the opposite direction from 5 V to 1.1 V takes at least 5 ms.
void loop() {
if (swr == true) {
analogReference(DEFAULT);
swr = false;
}
else {
analogReference(INTERNAL);
swr = true;
}
delay(8);
int aval = analogRead(analogPin);
...

Changing between reference voltages must be avoided when using the continues sampling method. When there are mixed inputs: measuring external voltages and resistor bridges like the one in figure 4, it's preferable to supply the resistor bridge from the 1.1 reference voltage. Be aware that the reference voltage can only deliver small currents. A load of a 4.7 kΩ resistor let the reference voltage drop from 1.0880 to 1.0857. In this case it's the best to buffer the AREF voltage with an opamp and connect the resistor loads to the opamp output.
Sample frequency vs. resolution

The same two binning codes 4 and 5 are used to analyze the resolution versus the sampling frequency. For this test a function generator is connected to the Arduino analog input as shown in figure 10. The function generator delivers a triangle waveshape with a top-top voltage of 25 mV and an offset voltage (= mean value) of 0.55 V. With every measurement the waveform frequency is chosen so that the sample frequency is 163 times higher.
A triangle waveform is chosen because each value (when quantized) is equally common. When such a signal is binned each bin value within the minimum and maximum voltage should have the same number.
Results
As the results in figure 11 shows the low frequency sampling analogRead and continues sampling at clk÷128 have a reasonable flat top: eg, all the values within the range occur with the same number. But at higher sampling frequencies (clk÷64, clk÷32 and clk÷16) gaps appear in the binning block that get worse with the frequency.
The datasheet of the ATMEGA328 warns about this phenomenon: At an ADC-clock above 200 kHz the resolution becomes lower. At a clock frequency of 16 MHz and a division factor of 64 the ADC clock becomes 250 kHz, just above the threshold where the resolution becomes less as confirmed by the measurements.
