Focused on Your Needs
5. Analog-to-digital Converter (ADC)
This tutorial is to introduce ADC and explain how it works
The ADC pins read the analog signal and convert the signal to an 8-bit or 10-bit number. For example, if the top value is 5V and an 8-bit ADC pin reads 2.5V, then the MCU will convert 2.5V to 127 since the total count of 8-bit is 255 (counts from 0).
Example 1: Start the ADC function
ADCSRA |= 1<<ADEN; //Enable ADC
By default, the ADC input clock frequency should be between 50 kHz and 200 kHz to get maximum resolution. The input clock frequency can be higher than 200 kHz if the resolution is lower than 10-bit.
Example 2: Enable the prescaler to determine the ADC input clock when the MCU clock frequency is 1MHz
ADCSRA |= 1<<ADPS2; //Set Prescaler to 16, 1,000,000/16 = 62.5 kHz
The reference voltage needs to be set to determine the highest reading voltage. Here are 4 options:
REFS1 REFS0 Voltage Reference Selection
0 0 AREF, Internal Vref turned OFF
0 1 AVCC with external capacitor at AREF pin
1 0 Reserved
1 1 Internal 1.1V voltage reference with external capacitor at AREF pin
Example 3: Set the reference voltage
ADMUX |= 1<<REFS0; //Set the reference voltage at AVcc (input voltage)
ADCH and ADCL registers are used to store the 10-bit value. When ADLAR = 0 (default), the result is right adjusted; when ADLAR = 1, the result is left adjusted.
ADLAR = 0 (right adjusted)
ADCH: - - - - - - ADC9 ADC8
ADCL: ADC7 ADC6 ADC5 ADC4 ADC3 ADC2 ADC1 ADC0
ADLAR = 1 (left adjusted)
ADCH: ADC9 ADC8 ADC7 ADC6 ADC5 ADC4 ADC3 ADC2
ADCL: ADC1 ADC0 - - - - - -
If the result is left adjusted and no more than 8-bit, it is sufficient to read ADCH. Otherwise, ADCL must be read first, then ADCH.
Example 4: Set the result left adjusted
ADMUX |= 1<<ADLAR; //Left adjust the result
Example 5: Enable the ADC conversion complete interrupt and global interrupt
ADCSRA |= 1<<ADIE; //Enable the interrupt
sei(); //Enable the global interrupt
Example 6: Start the ADC conversion
ADCSRA |= 1<<ADSC; //Start the ADC conversion
Example 7: Start an Interrupt Service Routing
ISR(ADC_vect) //Interrupt Service Routing is call after 1<<ADSC
{
// Analog values will be automatically stored in ADCH
….
ADCSRA |= 1<<ADSC; //Start another ADC conversion after the previous one is done
}
The ADC input pin is determined by setting MUX[3:0] in the ADMUS register. If the pin is not specified, the ADC input pin is ADC0 by default.
MUX[3:0] Single Ended Input
0000 ADC0
0001 ADC1
0010 ADC2
0011 ADC3
0100 ADC4
0101 ADC5
0110 ADC6
0111 ADC7
1000 Temperature sensor
Example 8: Full code to have PD0 pin output HIGH when ACD0 reads 2.5V (8-bit) or higher when the reference voltage is 5V at AVcc
#include <avr/io.h>
#include <avr/interrupt.h>
int main(void)
{
DDRD |= 1 << 0; //Set PD0 as an output
PORTD &=~ (1<<0); //Set PD0 to LOW
ADCSRA |= 1<<ADPS2; //Set Prescaler to 16, 1,000,000/16 = 62.5 kHz
ADMUX |= 1<<REFS0; //Set the reference voltage at AVcc (input voltage)
ADMUX |= 1<<ADLAR; //Left adjust the result
ADCSRA |= 1<<ADIE; //Enable the interrupt
ADCSRA |= 1<<ADEN; //Enable ADC
sei(); //Enable the global interrupt
ADCSRA |= 1<<ADSC; //Start the ADC conversion
while (1) {}
}
ISR(ADC_vect) //Interrupt Service Routing is called after 1<<ADSC
{
if (ADCH >= 127) //127 represents 2.5V in 8-bit data
PORTD |= 1<<0;
else
PORTD &=~ (1<<0);
ADCSRA |= 1<<ADSC; //Start another ADC conversion after the previous one is done
}
Example 9 (extended): Full code to have PD0 pin output HIGH when ADC0 reads 2.5V (10-bit) or higher (reference voltage is 5V at AVcc)
#include <avr/io.h>
#include <avr/interrupt.h>
int main(void)
{
DDRD |= 1 << 0; //Set PD0 as an output
PORTD &=~ (1<<0); //Set PD0 to LOW
ADCSRA |= 1<<ADPS2; //Set Prescaler to 16, 1,000,000/16 = 62.5 kHz
ADMUX |= 1<<REFS0; //Set the reference voltage at AVcc (input voltage)
ADCSRA |= 1<<ADIE; //Enable the interrupt
ADCSRA |= 1<<ADEN; //Enable ADC
sei(); //Enable the global interrupt
ADCSRA |= 1<<ADSC; //Start the ADC conversion
while (1) {}
}
ISR(ADC_vect) //Interrupt Service Routing is called after 1<<ADSC
{
uint8_t LowBits = ADCL; //Need to create this variable because ADCL is read first
uint16_t TenBitResult = ADCH<<8 | LowBits; //Shift ADCH to the right by 8 bits and put ADCL to lower bits
if (TenBitResult >= 511) //511 represents 2.5V in 10-bit data
PORTD |= 1<<0;
else
PORTD &=~ (1<<0);
ADCSRA |= 1<<ADSC; //Start another ADC conversion after the previous one is done
}
Example 10 (extended): Full code to have PD0 pin output HIGH when ADC0 reads 2.5V (10-bit) or higher, and to have PD1 pin output LOW when ACD1 reads 2.5V (10-bit) or higher (reference voltage is 5V at AVcc)
#include <avr/io.h>
#include <avr/interrupt.h>
int main(void)
{
DDRD |= 0b00000011; //Set PD0 and PD1 as outputs
ADCSRA |= 1<<ADPS2; //Set Prescaler to 16, 1,000,000/16 = 62.5 kHz
ADMUX = 0b01000000; //Set the reference voltage at AVcc, right shifted, and ADC0 as an input
ADCSRA |= 1<<ADIE; //Enable the interrupt
ADCSRA |= 1<<ADEN; //Enable ADC
sei(); //Enable the global interrupt
ADCSRA |= 1<<ADSC; //Start the ADC conversion
while (1) {}
}
ISR(ADC_vect) //Interrupt Service Routing is called after 1<<ADSC
{
uint8_t LowBits = ADCL; //Need to create this variable because ADCL is read first
uint16_t TenBitResult = ADCH<<8 | LowBits; //Shift ADCH to the right by 8 bits and put ADCL to lower bits
switch (ADMUX) //switch the ADC ports
{
case 0b01000000: //when ADC0 is reading, PD0 is HIGH when ADC0 >= 2.5V, PD0 is LOW when ADC0 < 2.5V
if (TenBitResult >= 511) //511 represents 2.5V in 10-bit data
PORTD |= 1<<0;
else
PORTD &=~ (1<<0);
ADMUX = 0b01000001; //switch to ADC1 next time;
break;
case 0b01000001: //when ADC1 is reading, PD1 is LOW when ADC1 >= 2.5V, PD1 is HIGH when ADC1 < 2.5V
if (TenBitResult >= 511)
PORTD &=~ (1<<1);
else
PORTD |= (1<<1);
ADMUX = 0b01000000; //switch to ADC0 next time
break;
default: //no default condition
break;
}
ADCSRA |= 1<<ADSC; //Start another ADC conversion after the previous one is done
}
Based on the test, the ADC0 and ADC1 has to constantly read signals to ensure the corresponding outputs show correct values. In other words, ADC0 and ADC1 cannot be in tri state.