Pages Menu
TwitterRssFacebook
Categories Menu

Posted by on Jun 20, 2011 in Atmel AVR, Microcontrollers | 318 comments

The ADC of the AVR

The ADC of the AVR

Analog to Digital Conversion

AVR SeriesMost real world data is analog. Whether it be temperature, pressure, voltage, etc, their variation is always analog in nature. For example, the temperature inside a boiler is around 800°C. During its light-up, the temperature never approaches directly to 800°C. If the ambient temperature is 400°C, it will start increasing gradually to 450°C, 500°C and thus reaches 800°C over a period of time. This is an analog data.

Signal Acquisition Process

Signal Acquisition Process

Now, we must process the data that we have received. But analog signal processing is quite inefficient in terms of accuracy, speed and desired output. Hence, we convert them to digital form using an Analog to Digital Converter (ADC).

Signal Acquisition Process

In general, the signal (or data) acquisition process has 3 steps.

  • In the Real World, a sensor senses any physical parameter and converts into an equivalent analog electrical signal.
  • For efficient and ease of signal processing, this analog signal is converted into a digital signal using an Analog to Digital Converter (ADC).
  • This digital signal is then fed to the Microcontroller (MCU) and is processed accordingly.
ADC Pins - ATMEGA16/32

ADC Pins – ATMEGA16/32

Interfacing Sensors

In general, sensors provide with analog output, but a MCU is a digital one. Hence we need to use ADC. For simple circuits, comparator op-amps can be used. But even this won’t be required if we use a MCU. We can straightaway use the inbuilt ADC of the MCU. In ATMEGA16/32, PORTA contains the ADC pins.

The ADC of the AVR

The AVR features inbuilt ADC in almost all its MCU. In ATMEGA16/32, PORTA contains the ADC pins. Some other features of the ADC are as follows:

ADC Features - ATMEGA16/32

ADC Features – ATMEGA16/32

Right now, we are concerned about the 8 channel 10 bit resolution feature.

  • 8 channel implies that there are 8 ADC pins are multiplexed together. You can easily see that these pins are located across PORTA (PA0…PA7).
  • 10 bit resolution implies that there are 2^10 = 1024 steps (as described below).
8 channel 10 bit ADC

8 channel 10 bit ADC

Suppose we use a 5V reference. In this case, any analog value in between 0 and 5V is converted into its equivalent ADC value as shown above. The 0-5V range is divided into 2^10 = 1024 steps. Thus, a 0V input will give an ADC output of 0, 5V input will give an ADC output of 1023, whereas a 2.5V input will give an ADC output of around 512. This is the basic concept of ADC.

To those whom it might concern, the type of ADC implemented inside the AVR MCU is of Successive Approximation type.

Apart from this, the other things that we need to know about the AVR ADC are:

  • ADC Prescaler
  • ADC Registers – ADMUX, ADCSRA, ADCH, ADCL and SFIOR

ADC Prescaler

The ADC of the AVR converts analog signal into digital signal at some regular interval. This interval is determined by the clock frequency. In general, the ADC operates within a frequency range of 50kHz to 200kHz. But the CPU clock frequency is much higher (in the order of MHz). So to achieve it, frequency division must take place. The prescaler acts as this division factor. It produces desired frequency from the external higher frequency. There are some predefined division factors – 2, 4, 8, 16, 32, 64, and 128. For example, a prescaler of 64 implies F_ADC = F_CPU/64. For F_CPU = 16MHz, F_ADC = 16M/64 = 250kHz.

Now, the major question is… which frequency to select? Out of the 50kHz-200kHz range of frequencies, which one do we need? Well, the answer lies in your need. There is a trade-off between frequency and accuracy. Greater the frequency, lesser the accuracy and vice-versa. So, if your application is not sophisticated and doesn’t require much accuracy, you could go for higher frequencies.

ADC Registers

We will discuss the registers one by one.

ADMUX – ADC Multiplexer Selection Register

The ADMUX register is as follows.

ADMUX

ADMUX Register

The bits that are highlighted are of interest to us. In any case, we will discuss all the bits one by one.

  • Bits 7:6 – REFS1:0 – Reference Selection Bits – These bits are used to choose the reference voltage. The following combinations are used.
Reference Voltage Selection

Reference Voltage Selection

ADC Voltage Reference Pins

ADC Voltage Reference Pins

The ADC needs a reference voltage to work upon. For this we have a three pins AREF, AVCC and GND. We can supply our own reference voltage across AREF and GND. For this, choose the first option. Apart from this case, you can either connect a capacitor across AREF pin and ground it to prevent from noise, or you may choose to leave it unconnected. If you want to use the VCC (+5V), choose the second option. Or else, choose the last option for internal Vref.

Let’s choose the second option for Vcc = 5V.

  • Bit 5 – ADLAR – ADC Left Adjust Result – Make it ‘1’ to Left Adjust the ADC Result. We will discuss about this a bit later.
  • Bits 4:0 – MUX4:0 – Analog Channel and Gain Selection Bits – There are 8 ADC channels (PA0…PA7). Which one do we choose? Choose any one! It doesn’t matter. How to choose? You can choose it by setting these bits. Since there are 5 bits, it consists of 2^5 = 32 different conditions as follows. However, we are concerned only with the first 8 conditions. Initially, all the bits are set to zero.
Input Channel and Gain Selections

Input Channel and Gain Selections

Thus, to initialize ADMUX, we write

ADMUX = (1<<REFS0);

ADCSRA – ADC Control and Status Register A

The ADCSRA register is as follows.

ADCSRA Register

ADCSRA Register

The bits that are highlighted are of interest to us. In any case, we will discuss all the bits one by one.

  • Bit 7 – ADEN – ADC Enable – As the name says, it enables the ADC feature. Unless this is enabled, ADC operations cannot take place across PORTA i.e. PORTA will behave as GPIO pins.
  • Bit 6 – ADSC – ADC Start Conversion – Write this to ‘1’ before starting any conversion. This 1 is written as long as the conversion is in progress, after which it returns to zero. Normally it takes 13 ADC clock pulses for this operation. But when you call it for the first time, it takes 25 as it performs the initialization together with it.
  • Bit 5 – ADATE – ADC Auto Trigger Enable – Setting it to ‘1’ enables auto-triggering of ADC. ADC is triggered automatically at every rising edge of clock pulse. View the SFIOR register for more details.
  • Bit 4 – ADIF – ADC Interrupt Flag – Whenever a conversion is finished and the registers are updated, this bit is set to ‘1’ automatically. Thus, this is used to check whether the conversion is complete or not.
  • Bit 3 – ADIE – ADC Interrupt Enable – When this bit is set to ‘1’, the ADC interrupt is enabled. This is used in the case of interrupt-driven ADC.
  • Bits 2:0 – ADPS2:0 – ADC Prescaler Select Bits – The prescaler (division factor between XTAL frequency and the ADC clock frequency) is determined by selecting the proper combination from the following.
ADC Prescaler Selections

ADC Prescaler Selections

Assuming XTAL frequency of 16MHz and the frequency range of 50kHz-200kHz, we choose a prescaler of 128.

Thus, F_ADC = 16M/128 = 125kHz.

Thus, we initialize ADCSRA as follows.

ADCSRA = (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
// prescaler = 128

ADCL and ADCH – ADC Data Registers

The result of the ADC conversion is stored here. Since the ADC has a resolution of 10 bits, it requires 10 bits to store the result. Hence one single 8 bit register is not sufficient. We need two registers – ADCL and ADCH (ADC Low byte and ADC High byte) as follows. The two can be called together as ADC.

ADC Data Registers (ADLAR = 0)

ADC Data Registers (ADLAR = 0)

ADC Data Registers (ADLAR = 1)

ADC Data Registers (ADLAR = 1)

You can very well see the the effect of ADLAR bit (in ADMUX register). Upon setting ADLAR = 1, the conversion result is left adjusted.

SFIOR – Special Function I/O Register

In normal operation, we do not use this register. This register comes into play whenever ADATE (in ADCSRA) is set to ‘1’. The register goes like this.

SFIOR Register

SFIOR Register

The bits highlighted in yellow will be discussed as they are related to ADATE. Other bits are reserved bits.

  • Bits 7:5 – ADC Auto Trigger Source – Whenever ADATE is set to ‘1’, these bits determine the trigger source for ADC conversion. There are 8 possible trigger sources.
ADC Auto Trigerring Source Selections

ADC Auto Triggering Source Selections

These options are will be discussed in the posts related to timers. Those who have prior knowledge of timers can use it. The rest can leave it for now, we won’t be using this anyway.

ADC Initialization

The following code segment initializes the ADC.

void adc_init()
{
    // AREF = AVcc
    ADMUX = (1<<REFS0);

    // ADC Enable and prescaler of 128
    // 16000000/128 = 125000
    ADCSRA = (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
}

Reading ADC Value

The following code segment reads the value of the ADC. Always refer to the register description above for every line of code.

uint16_t adc_read(uint8_t ch)
{
  // select the corresponding channel 0~7
  // ANDing with ’7′ will always keep the value
  // of ‘ch’ between 0 and 7
  ch &= 0b00000111;  // AND operation with 7
  ADMUX = (ADMUX & 0xF8)|ch; // clears the bottom 3 bits before ORing

  // start single convertion
  // write ’1′ to ADSC
  ADCSRA |= (1<<ADSC);

  // wait for conversion to complete
  // ADSC becomes ’0′ again
  // till then, run loop continuously
  while(ADCSRA & (1<<ADSC));

  return (ADC);
}

Physical Connections

Let’s connect two LDRs (Light Dependent Resistors) to pins PA0 and PA1 respectively. The connection is as follows. The function of potentiometers is explained in a later section, Sensor Calibration. You can scroll down to it. ;)

LDR Connections

LDR Connections

Now suppose we want to display the corresponding ADC values in an LCD. So, we also need to connect an LCD to our MCU. Read this post to know about LCD interfacing.

Since it is an LDR, it senses the intensity of light and accordingly change its resistance. The resistance decreases exponentially as the light intensity increases. Suppose we also want to light up an LED whenever the light level decreases. So, we can connect the LED to any one of the GPIO pins, say PC0.

Note that since the ADC returns values in between 0 and 1023, for dark conditions, the value should be low (below 100 or 150) whereas for bright conditions, the value should be quite high (above 900).

Now let’s write the complete code.

Example Code

To learn about LCD interfacing, view this post. You can type, compile and build it in AVR Studio 5. View this page to know how. To know about the I/O port operations in AVR, view this page.

#include <avr/io.h>
#include <util/delay.h>

#include "lcd.h"

#define LTHRES 500
#define RTHRES 500

// initialize adc
void adc_init()
{
    // AREF = AVcc
    ADMUX = (1<<REFS0);

    // ADC Enable and prescaler of 128
    // 16000000/128 = 125000
    ADCSRA = (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
}

// read adc value
uint16_t adc_read(uint8_t ch)
{
    // select the corresponding channel 0~7
    // ANDing with '7' will always keep the value
    // of 'ch' between 0 and 7
    ch &= 0b00000111;  // AND operation with 7
    ADMUX = (ADMUX & 0xF8)|ch;     // clears the bottom 3 bits before ORing

    // start single conversion
    // write '1' to ADSC
    ADCSRA |= (1<<ADSC);

    // wait for conversion to complete
    // ADSC becomes '0' again
    // till then, run loop continuously
    while(ADCSRA & (1<<ADSC));

    return (ADC);
}

int main()
{
    uint16_t adc_result0, adc_result1;
    char int_buffer[10];
    DDRC = 0x01;           // to connect led to PC0

    // initialize adc and lcd
    adc_init();
    lcd_init(LCD_DISP_ON_CURSOR);

    // display the labels on LCD
    lcd_puts("left  ADC = ");
    lcd_gotoxy(0,1);
    lcd_puts("right ADC = ");

    _delay_ms(50);

    while(1)
    {
        adc_result0 = adc_read(0);      // read adc value at PA0
        adc_result1 = adc_read(1);      // read adc value at PA1

        // condition for led to glow
        if (adc_result0 < LTHRES && adc_result1 < RTHRES)
            PORTC = 0x01;
        else
            PORTC = 0x00;

        // now display on lcd
        itoa(adc_result0, int_buffer, 10);
        lcd_gotoxy(12,0);
        lcd_puts(int_buffer);

        itoa(adc_result1, int_buffer, 10);
        lcd_gotoxy(12,1);
        lcd_puts(int_buffer);
        _delay_ms(50);
    }
}

Sensor Calibration

Calibration means linking your real world data with the virtual data. In the problem statement given earlier, I have mentioned that the LED should glow if the light intensity reduces. But when should it start to glow? The MCU/code doesn’t know by itself. You get the readings from the sensor continuously in between 0 and 1023. So, the question is how do we know that below ‘such and such’ level the LED should glow?

This is achieved by calibration. You need to physically set this value. What you do is that you run the sensor for all the lighting conditions. You have the ADC values for all these levels. Now, you need to physically see and check the conditions yourself and then apply a threshold. Below this threshold, the light intensity goes sufficiently down enough for the LED to glow.

The potentiometer connected in the circuit is also for the same reason. Now, by the basic knowledge of electronics, you could easily say that upon changing the pot value the ADC value changes. Thus, for various reasons (like poor lighting conditions, you are unable to distinguish between bright and dark conditions, etc), you can vary the pot to get desired results.

This is why I have given the two thresholds (RTHRES anf LTHRES) in the beginning of the code.

So, this is all with the ADC. I hope you enjoyed reading this. Please post the comments below for any suggestion, doubt, clarification, etc.

318 Comments

  1. I’m trying to get successive samples of an analog voltage signal. I’m using atmega16 with 4MHz crystal oscillator.
    1) Do I need to use an additional adc for my purpose?
    2) does the delay between two successive sample depend upon any other factors other than adc conversion time in atmega?
    NB: Actually this is for a power system over voltage relay

    Thanks in advance

    • Hi nankuniyil!

      1) No you don’t. The ATmega16 internal ADC Unit is enough.
      2) No, it depends on how much delay you introduce, between sampling of two successive inputs, or the sampling frequency in free running mode.

      Hope this helps!

  2. Very helpful demonstration .. also i have one additional question how i can map a higher resolution(e.g 10 bit) adc value to lower resolution(e.g 6 bit )adc value

    • You basically truncate the lower significant bits.

      • sir i have to make multimeter assigned by teacher that would measure current and voltage only using atmega16,i have no circuit diagram and code also,,can you provide me circuit diagram and programming code???i relly need this…i’ii be grateful to you..

        • Sorry, but we do not do people’s assignments for them!

  3. plz sir , i wanna monitor 4 analog signal and sending them to lcd i found 3 channels is the like the four channel (the last one).when i was trying to monitor one and deactivate the others each on was working.
    i hope i hear from you soon.

    • thanks sir ,
      i checked the code and i found i didn’t clear the bits
      ADMUX|=ADCport;
      instead of
      ADMUX = (ADMUX & 0xF8)|ch; // clears the bottom 3 bits before ORing

      everything is working perfectly .

      • Great you could make it work!

    • Hi Alaa

      We couldn’t really understand your problem. Could you please elaborate? Thanks!

  4. Thank you very much for your clear explanations of the avr adc. Very helpful and useful. I’ve spent lots of time reading datasheet and lots of other sources in order to get good understanding of the topic, but your explanation is just really nice, short and easy to understand. I really appreciate your work and time spent for educating us

    • Hi vstrulev

      It’s great to hear that we could be of help to you! Keep reading and sharing the knowledge! Cheers!

      • since it is a comparator, would it damage the pin if I apply a signal that varies by more than 5 volts on the pin or should I use some form of circuitry to scale down the signal?

  5. Hi Max,

    I have a problem:
    Atmega128 at 16 MHz (Also tried with 1 MHz internal clock.)
    Using only 1 channel
    interrupt enable with free running mode (tried different mode too)
    LC low pass filter at Vcc and all the inputs line.
    Vref de coupled with external RC filter
    Vref = Vcc (internal) selected.
    my output is not stable (noise type is AC, For constant input analog signal it vary +- 7.)
    I also tried sleep mode with ADC noise canceler
    Also tried at 50k to 500k all different sample rate.

    I have 2 hardware and tried on both of them and got the same result.

    Do you faced same problem practically?
    If you think there is any way to find it out….. please suggest.

    Thanks,
    Zalavadia

  6. hello sir please tell me can i use the above ADC concept for my project. My project is to display the pressure on lcd using atmega 16.
    pls help me sir

  7. why we need voltage reference?. how to select the reference voltages

  8. sir i want design temp. sensor program for ATtiny45 micro-controller then what to be changes required in this code

  9. i am getting an error
    missing ‘ ( ‘
    at
    uint16_t adc_read(uint8_t ch)

    can u help me sir?

  10. can anybody help me ?

  11. Hello sir,
    The tutorial is an excellent for a beginners like me. I have a simple doubt regarding the tradeoff between F_ADC frequency and accuracy of digital value. Since, the F_ADC is the frequency at which the ADC will sample the incoming analog signal right. So in one case, let my F_ADC be 100KHz then I get 100K Samples per second, In other case let my F_ADC be 150KHz then I get 150K Samples per second. The samples accuracy i get is more in second case is n’t it !!??

  12. Sir,
    can u guide me on using differential adc on atmega 16 to find the difference between two adc pins, with some gain.
    there is no decent tutorials on the internet regarding differential adc
    And ur tutorial on adc helped me the most in learning adc

  13. hi, first thanks for your tutorials
    i have some points i can’t understand in the coded
    the ch is variable or register? cause it’s not decleared and what its used for?
    ch &= 0b00000111;
    ADMUX = (ADMUX & 0xF8)|ch; // what is this command for?

  14. Sir, please help me with this: I know what this does

    while(ADCSRA & (1<<ADSC))

    but not sure how it functions. I am familiar with bitwise operations but don't know about how to use them for condition check. Please explain or give me link of some source for it's explanation. Thank you

    • Also help me with this
      return (ADC)
      how does it read ADCL & ADCH registers?

  15. Hi MAX
    Very useful
    Thanks.

  16. Sir,
    According to the datasheet,there is an ISR for ADC..but here you not using the ISR the way you used in TIMERS and instead are using return (ADC);
    does the statement return(ADC); refer to the ISR specified to the ADC and if not,can i use ISR in this case ..
    using Atmega 2560.
    please help..

  17. I am having a serious problem with atmega 16…I used its 8 channel with 8 LDRs. But I came to know that ADC is only taking 2 channel as input….So your method is okay with 3 or more sensors? Connections are same as shown above just i used 8 sensors…..

  18. Do you know what the operation is with ADATE set to 0?

    According to the logic diagram, it looks like this would stop the prescaler running, and that can’t be riight.

    What I want to do is to reset the precaler before starting single conversion by setting ADSC to 1, so that the timing is not quantized to the next frree-running prescaler output cycle, but rather to the finer prescaler in put clock

    So I thought of writing ADATE to 0 then back again simulataneoulsy with setting ADSC to 1.

Trackbacks/Pingbacks

  1. ADC (Analog to Digital Converter) Mikrokontroler - […] Referensi :1. maxembedded.com, […]

Leave a Reply

%d bloggers like this: