top of page

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.




©2020 by Pulin Global

bottom of page