Advanced Arduino ADC – Faster analogRead()

Most AVR cores have an 8 channel 10 bit analog to digital converter (ADC). Those familiar with the Arduino may have used the analogRead() function call to interact with the ADC. In this tutorial, I am going to take a closer look at the AVR ADC.

I am going to focus on the Atmel ATmega328P. There are two datasheets available from Atmel. Also, an application note, AVR120:Characterization and Calibration of the ADC on an AVR, is of interest. Following are links to the documents.

What is an ADC

An ADC is used to convert an analog voltage that is continuously varying within a known range into a digital value.The analog value often represents some real world measurement. Most values in the real world, be they air pressure or temperature or speed are inherently analog in nature.

The continues steam of analog information is normally referred to as a signal. Unlike the analog signal, the digital values that are converted from them will not be continues. They will be read or sampled a certain number of times per second, which is referred to as the sampling frequency.

Signal
Signal

The green line of the graph above might be an analog signal rising and falling between -5 volts and 5 volts. Most examples that you encounter will be fluctuation between 0 volts and 5 volts or level shifting circuitry will be needed. Level shifting is beyond the scope of this tutorial.

The blue dots are points in time when the ADC is used to convert the current point of the analog signal into a digital value. For example, the first blue dot might convert to 3.25 volts and the second might convert to 5 volts. The number of times that we take a reading (create blue dots) per second is the frequency of the sampling.

The AVR ADC

There are different types of ADC architecture. All AVRs that contain an ADC have a successive approximation ADC. Below is a simplified diagram of the AVR ADC.

The AVR has an 8 channel analog multiplexer. This multiplexes the 8 analog pins into the single 10 bit ADC. Only one ADC operation can be carried out at a time. If you are using more than one ADC channel (pin), the readings from the channels are handled by the single ADC unit. If analog pin 0 is being converted, analog pin 1 needs to wait for that to complete before it can be converted.

The AVR is an 8 bit microcontroller. The ADC has 10 bit resolution. The result of the ADC conversion needs to be stored in two registers. These are ADCH & ADCL (ADC high & low).

ADC
ADC

The input (VIN) of the ADC must be between 0 volts and smaller than the ADC’s reference voltage VREF. Typically this is the same as VCC, and on most AVR and Arduino circuits, it is 5 volts. VIN is the measurement from your equipment (perhaps a thermometer) that is attached to one of the ADC input channels.

The formula to compute the ADC conversion value is round((VIN/VREF)*1023). If VIN is 0 volts the converted value will be 0. If VIN = VREF the converted value will be 1023.

Operating Modes

The AVR ADC has two operating modes. These are single conversion & free running. We will discuss both modes of operation in this tutorial.

Single Conversion Mode

In single conversion mode you have to initiate each conversion. When the conversion is complete, the results are placed into the ADCH & ADCL registers and ADIF is set. No other conversion is started.

You initiate a conversion by setting the ADSC (ADC start conversion) bit in the ADCSR. ADSC will stay high while the conversion is in progress and will automatically be cleared when the conversion completes. You can select which channel you want to convert by setting the ADCMUX register before starting the conversion. Notification of conversion completion event can be obtained via the ADC Conversion Complete interrupt service routine (ISR).

Free Running Mode

In free running mode, you initiate the first conversion and then the ADC will automatically start the next conversions, as soon as the previous one is complete. You tell the ADC which channel to convert by setting the ADMUX register.

If a new channel is to be selected, it must be set in ADMUX before the next conversion begins. When using an ISR for getting the ADC results and setting the ADC channel, be careful not to change the ADMUX just after a conversion starts. This can lead to unspecified behaviour.

Clock

The AVR ADC has a recommended clock speed of between 50 kHz and 200 kHz when 10 bit resolution is desired. This is 50,000 to 200,000 cycles per second. Any faster and the resolution will start to degrade. There is a prescaler to control the ADC clock. This means that it is prescaled down by some factor from the core clock speed.

To set the prescaling, the ADPS bits (2..0) in the ADCSRA register are set. Prescaler values of 2, 4, 8, 16, 32, 64 & 128 are provided. A typical core clock speed is 16 MHz, although the core can run up to 20 MHz. So if you are running your Arduino or other AVR board at 16Mhz, you can set the ADC clock to one of the following values:

  • 16 MHz / 2 = 8 MHz
  • 16 MHz / 4 = 4 MHz
  • 16 MHz / 8 = 2 MHz
  • 16 MHz / 16 = 1 MHz
  • 16 MHz / 32 = 500 kHz
  • 16 MHz / 64 = 250 kHz
  • 16 MHz / 128 = 125 kHz

Since the ADC clock needs to be between 50 kHz & 200 kHz for 10 bit accuracy, we can only use the 128 prescaler and can achieve a 125 kHz ADC clock. If our core was at 20 Mhz and we use the 128 prescaler our ADC would be running at about 156 kHz.

To achieve a higher ADC clock, we could slow down the core. A core running at 12 Mhz with a prescaler of 64 will achieve an ADC clock of around 187 kHz. These are the tradeoffs we make when designing a system.

A normal conversion in the ADC takes 13 ADC clock cycles. The first conversion takes 25 clock cycles. So our ADC clock speed needs to be factored down by 13 to figure out how many samples we will be able to achieve per second.

If you are using the Arduino libraries, you can search the file wiring.c to find where the ADC is being configured. On my installation it appears starting on line 276. Here is the excerpt.

#if defined(ADCSRA)
    // set a2d prescaled factor to 128
    // 16 MHz / 128 = 125 KHz, inside the desired 50-200 KHz range.
    // XXX: this will not work properly for other clock speeds, and
    // this code should use F_CPU to determine the prescaled factor.
    sbi(ADCSRA, ADPS2);
    sbi(ADCSRA, ADPS1);
    sbi(ADCSRA, ADPS0);

    // enable a2d conversions
    sbi(ADCSRA, ADEN);
#endif

As expected, the Arduino library is enabling the ADC with a 128 prescaler, giving a 125 kHz ADC clock speed when the core clock is 16 Mhz. A 125 kHz clock speed will result in 125 kHz / 13 = 9600 Hz sample speed. The hardware limit is 9,600 samples per second or 96 samples per millisecond. We cannot physically do better than that at the desired 10 bit resolution.

If faster ADC clock and sample rates are required with high accuracy, then the ATmega328P might not be the correct chip for the job. The Xmega series from Atmel includes part numbers with ADCs 12 bit accuracy and much higher ADC clock speeds. Another alternative is an external ADC IC.

If must use the ATmega328P, but have the option of changing the core clock down to 12 Mhz, we can achieve 187 kHz ADC clock speed which will result in 187 kHz / 13 = 1.4 kHz sample speed or 14,384 samples per second. A bit better than the 9,600 sample achieve before. Finally, if we could achieve ADC clock maximum speed of 200 kHz, it would result in 200 kHz / 13 = 1.5 kHz sample speed or 15,384 samples per second. That is the best the ATmega328P ADC can do at 10 bit resolution.

Resolution

The ADC has a 10 bit resolution. This translates to values between 0 and 1023. If the reference voltage is 5 volts, this means that the smallest detectable change in voltage on the input pin is 5V / 1023 = 0.0049 volts or 4.9 mV.

The ADC has a recommended maximum ADC clock speed of 200 kHz. If you read the application note AVR120:Characterization and Calibration of the ADC on an AVR you will find the following statement:

The ADC accuracy also depends on the ADC clock. The recommended maximum ADC clock frequency is 
limited by the internal DAC in the conversion circuitry. For optimum performance, the ADC clock 
should not exceed 200 kHz. However, frequencies up to 1 MHz do not reduce the ADC resolution 
significantly.

Operating the ADC with frequencies greater than 1 MHz is not characterized.

This means that we can push up the clock form the recommended maximum of 200 kHz to 1 MHz without seeing too much degradation in accuracy. Lets give it a try.

Coding the ADC

We are going to write a test harness that allows us to sample the ADC and record the duration of the activity, so that we can compute samples per second. After that we are going to add code to allow the ADC clock prescaler to be changed so we can vary the clock speed. Remember, when adjusting the clock over 200 kHz, we are degrading the accuracy. Values up to 1 Mhz should not see too bad a degradation.

Lets start with an unmodified Ardino setup and the test harness that will do 100 analog reads, computing the duration of each read. It will then print out the results on the serial console. I have a potentiometer attached to analog pin 2.

// Arrays to save our results in
unsigned long start_times[100];
unsigned long stop_times[100];
unsigned long values[100];

// Setup the serial port and pin 2
void setup() {
  Serial.begin(9600);
  pinMode(2, INPUT);
}


void loop() {  
  unsigned int i;

  // capture the values to memory
  for(i=0;i<100;i++) {
    start_times[i] = micros();
    values[i] = analogRead(2);
    stop_times[i] = micros();
  }

  // print out the results
  Serial.println("\n\n--- Results ---"); 
  for(i=0;i<100;i++) {
    Serial.print(values[i]);
    Serial.print(" elapse = ");
    Serial.print(stop_times[i] - start_times[i]);
    Serial.print(" us\n");
  }
  delay(6000);
}

As we can see, our results are in the region of 116 us to 124 us. What does that translate to as samples per second? 116 us = 0.116 ms per sample. That equates to 8,620.689 samples per second. Pretty close to the 9,600 that we calculated earlier.

Console
Console

Lets change the ADC clock prescaler from 128 to 64. That will set the ADC clock to 250 kHz (up from 125 kHz), if we have a 16 Mhz core clock speed. Here is the full source code listing. We have added some prescaler definitions at the top of the source file and have set the ADC prescaler to 64 in the setup function. Lets see how it performs.

// Arrays to save our results in
unsigned long start_times[100];
unsigned long stop_times[100];
unsigned long values[100];


// Define various ADC prescaler
const unsigned char PS_16 = (1 << ADPS2);
const unsigned char PS_32 = (1 << ADPS2) | (1 << ADPS0);
const unsigned char PS_64 = (1 << ADPS2) | (1 << ADPS1);
const unsigned char PS_128 = (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);

// Setup the serial port and pin 2
void setup() {
  Serial.begin(9600);
  pinMode(2, INPUT);

  // set up the ADC
  ADCSRA &= ~PS_128;  // remove bits set by Arduino library

  // you can choose a prescaler from above.
  // PS_16, PS_32, PS_64 or PS_128
  ADCSRA |= PS_64;    // set our own prescaler to 64 

}


void loop() {  
  unsigned int i;

  // capture the values to memory
  for(i=0;i<100;i++) {
    start_times[i] = micros();
    values[i] = analogRead(2);
    stop_times[i] = micros();
  }

  // print out the results
  Serial.println("\n\n--- Results ---"); 
  for(i=0;i<100;i++) {
    Serial.print(values[i]);
    Serial.print(" elapse = ");
    Serial.print(stop_times[i] - start_times[i]);
    Serial.print(" us\n");
  }
  delay(6000);
}

Our results are now in the region of 60 us to 64 us per sampling. What does this now translate to in terms of samples per second? 60 us = 0.06 ms per sample. That now equates to 16,666.666 samples per second. Makes sense. We doubled our ADC clock speed and are getting around twice as many samples per second.

Console
Console

Playing around with the potentiometer, I am still getting back values in the 0 to 1023 range. Resolution seems to be ok.

Setting the prescaler to 32 the sample time seems to be in the region of 32 us to 36 us. The ADC clock is now running at 500 kHz. I am getting 31,250 samples per second.

Setting the prescaler to 16 gives me a 1 Mhz ADC clock speed. Samples are taking between 20 us and 24 us to complete. That is 50,000 samples per second. Resolution still seems ok, but I leave it to the implementor to verify that.

Console
Console

This is how I changed the prescaler values.

 // To change the prescaler modify setup() 

 ADCSRA |= PS_64; // 64 prescaler
 ADCSRA |= PS_32; // 32 prescaler
 ADCSRA |= PS_16; // 16 prescaler

Wrap Up

I discussed the architecture of the AVR ADC. It was shown that the Arduino library is setting the ADC clock speed to 125 kHz. Changing the ADC clock speed was demonstrated. A clock speed of 1 Mhz changed our sampling rate from 9,600 samples per second to 50,000 samples per second.

Atmel recommends no more that 200 kHz clock speed to maintain 10 bit accuracy. They do however state that clock speeds of up to 1 Mhz do not significantly impact the accuracy.

When we turned the ADC clock to 1 Mhz accuracy seemed ok. I leave it to the implementor to validate that the accuracy is within acceptable bounds for their application.