The ForceTronics blog provides tutorials on creating fun and unique electronic projects. The goal of each project will be to create a foundation or jumping off point for amateur, hobbyist, and professional engineers to build on and innovate. Please use the comments section for questions and go to forcetronics.com for information on forcetronics consulting services.
In part three we look at the three main sources of error in an ADC measurement. We will discuss how to reduce total ADC error and how to quantify the total error of an ADC measurement.
In part 2 we will look at how to increase the accuracy of our ADC measurements using the built-in Noise Reduction Mode
Arduino Code******************************************************************** /*This example sketch shows how to make ADC measurements via registers and how to use the low noise or noise cancellation ADC measurement capability. 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/9/15 */ #include <avr/sleep.h> int normADC[10]; //Create an arrray to hold the "normal" or non-low noise ADC measurements int lowNoiseADC[10]; //Create an array to hold the low noise ADC measurements 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 //bits 7 and 6 select the ADC reference source //The four zeros (bits 3 thru 0) at the end of the above binary register values sets the analog channel to A0 //Bit 5 selects right adjust for the result and bit 4 is not used //Setup code only run once void setup() { ADMUX = aVCC; //Configure the ADC via the ADMUX register for VCC as reference, result right adjust, and source A0 pin ADCSRA |= 1<<ADEN; //Turn the ADC on by setting the ADEN bit to 1 in the ADCSRA register ADCSRA |= 1<<ADIE; //Setting the ADIE bit to 1 means when the ADC is done a measurement it generates an interrupt, the interrupt will wake the chip up from low noise sleep ADCSRA |= ((1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)); //Prescaler at 128 so we have an 125Khz clock source //The ADC clock must be between 50kHz and 200kHz, since the Uno clock is 16MHz we want to divide it by 128 to get 125kHz. This may need to be adjusted for other Arduino boards //We do not have to use registers to set the sleep mode since we are using the avr/sleep.h library sleep_enable(); //enable the sleep capability set_sleep_mode(SLEEP_MODE_ADC); //set the type of sleep mode. We are using the ADC noise reduction sleep mode //Note that in this sleep mode the chip automatically starts an ADC measurement once the chip enters sleep mode //Note that the reason for making the ADC measurements and storing in arrays was to avoid using the arduino serial functions with sleep mode since serial uses interrupts for(int i=0; i<10; i++) { //Loop ten times and make ten ADC measurements in low noise mode sei(); //enable interrupts sleep_cpu(); //enter low ADC noise sleep mode, this action turns off certain clocks and other modules in the chip so they do not generate noise that affects the accuracy of the ADC measurement //The chip remains in sleep mode until the ADC measurement is complete and executes an interrupt which wakes the chip from sleep lowNoiseADC[i] = ADC; //get ADC result and store it in low noise measurement array } for(int k=0; k<10; k++) { //loop executes 10 times and makes 10 normal ADC measurements ADCSRA |= 1<<ADSC; //this bit starts conversion. It will remain high until conversion is complete while(ADCSRA & (1<<ADSC)) { delay(1);} //When ADSC bit goes low conversion is done normADC[k] = ADC; //get ADC result and store it in normal ADC measurement array } Serial.begin(9600); //Start serial comm to print out results to serial monitor while(!Serial.available()) { delay(1); } //wait to print out results until serial monitor is open and any character is sent Serial.println("Noise reduction:"); //labels the readings being printed for(int j=0; j<10;j++) { //loop ten times to print out all the low noise measurement in array Serial.println(convertToVolt(lowNoiseADC[j]),3); //Convert each low noise measurement to a voltage level and send it over serial with three decimal places precision } Serial.println(); //print a line feed Serial.println("No noise reduction:"); //print heading for normal ADC measurements for(int l=0; l<10;l++) { //loop ten times and print each ADC reading Serial.println(convertToVolt(normADC[l]),3); //Convert each normal ADC measurement to a voltage level and send it over serial with three decimal places precision } } void loop() { //do nothing in the loop } //This function takes the ADC integer value and turns it into a voltage level. The input is the measured ADC value. float convertToVolt(int aVAL) { float refVal = 5.01; //hard coded in AVCC value, measured the real value for higher accuracy return (((float)aVAL/1023)*refVal); //formula to convert ADC value to voltage reading } //this is the interrupt service routine for the ADC interrupt. This must be in the code for the interrupt to work correctly ISR(ADC_vect) { }
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