Speeding up the ADC on an Arduino ATMega 328P

ADC steps
courtesy Spinningspark at Wikipedia

This is a continuation of the performance enhancements for reading the ADC on Arduino.  Similar steps apply to many non Arduino/ATMega processors.

That other article is here: Arduino Library Functions & Macros and introduces startSample(), sampleDone(), and getSampleResult() as a way of avoiding blocking when doing an analogRead.  Here we will update the second two functions and introduce a new function: startFreeRunningADC().

This time we delve a little deeper into the ATMega328 and 168, we implement the free running mode and increase the clock rate to achieve 38,462 samples per second compared to the 8,966 samples per second that analogRead() can do.   Other than the example, none of this is Arduino dependent.  It works on any microcontroller in the same series as the ATMega328 and similar steps can be done on just about any microcontroller with a successive approximation ADC.

The first step to improve performance is to have the ADC operate in Free Running mode, constantly sampling and updating the ADC results registers.  The first conversion is started by calling startFreeRunningADC() from setup().  In this mode the ADC will perform successive conversions every 13 ADC clocks indefinitely instead of doing one conversion in 25 ADC clocks and then waiting to be restarted.  Note, the sampleDone() and getSampleResult() are also slightly updated.  This alone more than doubles performance.

The remaining step is to increase the ADC clock speed from Arduino’s default of 125KHz to 500KHz.  This has a side effect of reducing the sample time which reduces resolution.  To minimize the impact of the reduced sample time, avoid high value resistor dividers and use the lowest value that your signal source can accept, or buffer the signal with an opamp.  There is a compromise speed commented out if you’d prefer the accuracy of remaining close to the 200KHz required for best resolution.  With Free Running mode and a 500KHz ADC clock, the ADC runs at 38,462 samples per second.

Finally, not implemented here, you can trigger an interrupt on conversion complete to avoid polling for results as is implemented here.  In many interrupt driven programs, the ADC results are simply stored and then the stored result is polled, leading to no real improvements in speed.

Here are the functions:

#include "wiring_private.h"
#include "pins_arduino.h"

void startFreeRunningADC(uint8_t pin){
 ADMUX=(1<<REFS0)|((pin-14)&0x07); //select AVCC as reference and set MUX to pin
 ADCSRB = 0; //set free running mode
ADCSRA=(1<<ADEN)|(1<< ADATE)|(1<<ADPS2)|(1<<ADPS0); //set adc clock to 500Khz, enable, free running mode
//ADCSRA=(1<<ADEN)|(1<< ADATE)|(1<<ADPS2)|(1<<ADPS1); //set adc clock to 250Khz, enable, free running mode
 sbi(ADCSRA, ADSC); //start the ADC
}

inline uint8_t sampleDone(){
 // ADIF is set when the conversion finishes
 return bit_is_set(ADCSRA, ADIF);
}

inline uint16_t getSampleResult(){
 uint8_t low, high;
 low = ADCL; //make sure to read L value first
 high = ADCH;
 sbi(ADCSRA, ADIF); //clear conversion complete flag
 return (high << 8) | low;
}

Here is a sample program using these functions:

#define ADCport A4
void setup() {
  Serial.begin(230400); //the ADC is so fast that we need faster serial!
  startFreeRunningADC(ADCport); //free running mode, only needs to start once.
}

void loop() {
  if( sampleDone()){
    Serial.println(getSampleResult());
  }
}
Advertisements

One thought on “Speeding up the ADC on an Arduino ATMega 328P

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s