Wednesday, January 7, 2015

Maximizing Arduino’s ADC Resolution and Accuracy Part 1

In part 1, of this 3 to 4 part series, we will look at what ADC measurement resolution is and how to maximize it on the Arduino. We will also look at a simple hint to increase Arduino's ADC measurement accuracy. In later parts we will get much deeper into accuracy and how to increase it.




Arduino Code using analogreference()*************************************************
/*This example sketch shows how to change the reference for Arduino's ADC to default or internal 1.1V.
This sketch was used to show the measurement resolution advantage of scaling Arduino's ADC reference to 
the voltage range or scale being measured. This was shown in an ADC tutorial on the ForceTronics YouTube Channel.
This code is free and open for anybody to use at their own risk. 1/6/15
*/
float const aVCC = 5.049; //Actual measured voltage value at AVCC pin. This is im
float const iREF = 1.084; //Actual measured voltage value of the internal 1.1V reference
//Note that these values can be measured at the AREF pin

void setup() {
  Serial.begin(9600); //setup serial connection
}

void loop() {
  
  delay(1000); //delay 1 second between each ADC measurement
  analogReference(DEFAULT); //set the ADC reference to default which is AVCC (uses power supply voltage or VCC as reference)
  burn8Readings(); //make 8 readings but don't use them to ensure good reading after reference change
  Serial.println("Default (AVCC) Ref:");
  Serial.println(convertToVolt(aVCC,analogRead(A0)),3); //Make ADC measurement at A0, convert it to a voltage value, write value to serial with 3 decimal places 
  Serial.println(" ");
  delay(1000); //delay 1 second between each ADC measurement
  analogReference(INTERNAL); //set the ADC reference to internal 1.1V reference
  burn8Readings(); //make 8 readings but don't use them to ensure good reading after reference change
  Serial.println("Internal 1.1V Ref:");
  Serial.println(convertToVolt(iREF,analogRead(A0)),3); //Make ADC measurement at A0, convert it to a voltage value, write value to serial with 3 decimal places
  Serial.println(" ");
}

//This function makes 8 ADC measurements but does nothing with them
//Since after a reference change the ADC can return bad readings at first. This function is used to get rid of the first 
//8 readings to ensure an accurate one is displayed
void burn8Readings() {
  for(int i=0; i<8; i++) {
    analogRead(A0);
    delay(1);
  }
}

//This function convers the ADC level integer value into a useful voltage value.
//The inputs are the measured ADC value and the ADC reference voltage level
//The formula used was obtained from the data sheet: (ADC value / 1024) x ref voltage
float convertToVolt(float refVal, int aVAL) {
  return (((float)aVAL/1024)*refVal);
}

Arduino Code using Registers*************************************************
/*This example sketch shows how to change the reference for Arduino's ADC to default or internal 1.1V using registers.
This sketch was used to show the measurement resolution advantage of scaling Arduino's ADC reference to 
the voltage range or scale being measured. This was shown in an ADC tutorial on the ForceTronics YouTube Channel.
This code is free and open for anybody to use at their own risk. 1/6/15
*/

int const aVCC = 0b01000000; //variable to set ADC to use VCC as reference
int const iREF = 0b11000000; //variable to set ADC to use internal 1.1V as reference
int const aREF = 0b00000000; //variable to set ADC to use VCC as reference
//The three zeros at the end of the above binary register values sets the analog channel to A0

void setup() {
  Serial.begin(9600);
  ADCSRA |= 1<<ADEN; //Turn on ADC
  ADCSRA |= ((1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0));    //Prescaler at 128 so we have an 125Khz clock source
}

void loop() {
  delay(1000);
  Serial.println("Internal Ref:");
  burn8Readings(iREF);
  Serial.println(measADC(iREF),3);
  Serial.println(" ");
  delay(1000);
  Serial.println("AVCC Ref:");
  burn8Readings(aVCC);
  Serial.println(measADC(aVCC),3);
  Serial.println(" ");
}

//This function makes an ADC measurement using registers
float measADC(int ref) {
  ADMUX =  ref; //bits 7&6 select reference, bit 5 R or L adjust (0 R), bit 4 unused, 3:0 is channel select (0000 is ADC0)
  //ADMUX |=  0b00000000; //bits 7&6 select reference, bit 5 R or L adjust (0 R), bit 4 unused, 3:0 is channel select (0000 is ADC0)
  ADCSRA |= 1<<ADSC; //this bit starts conversion. It will remain high until conversion is complete
  
  while(ADCSRA & (1<<ADSC)) { delay(1);} //ADIF bit goes high when conversion is complete and result is in ADCL and ADCH
  return convertToVolt(ref, ADC);
}

//This function takes the ADC integer value and turns it into a voltage level. The inputs are
//the selected reference source and the measured ADC value.
float convertToVolt(int refSet, int aVAL) {
  float refVal;
  if (refSet == iREF) { refVal = 1.08; } //hard coded in reference value for internal
  else { refVal = 5.05; } //hard coded in AVCC value
  
  return (((float)aVAL/1023)*refVal);
}

//This function makes 8 ADC measurements but does nothing with them
//Since after a reference change the ADC can return bad readings at first. 
//This function is used to get rid of the first 
//8 readings to ensure an accurate one is displayed
void burn8Readings(int ref) {
 for(int i = 0; i<8; i++) {
  measADC(ref);
  delay(1);
 } 
}