Analogue to Digital Conversion on an ATmega168

Many AVR microcontrollers are capable of doing Analogue to Digital Conversion. The ATmega168 has 6 ports (8 ports on the SMD packages) that can be used for analogue input. This tutorial shows you how.

The circuit

We’ll be building the following circuit on a breadboard.

AD Converter Circuit

The Breadboard layout is based on the Atmega8 breadboard circuit which is described in Atmega8 breadboard circuit Part 1 and Atmega8 breadboard circuit Part 2. Breadboard Circuit

AVCC

The Atmega168 has 2 digital supply voltage pins, VCC and AVCC. AVCC supplies power to PC0-PC5. When these pins are used as analogue inputs, the AVCC power needs to be very stable. This is achieved by using a low pass filter comprising of an inductor and capacitor.

Low Pass Filter

AREF

The AREF pin is used to set the voltage that corresponds to a 100% value (1024) in the AD converter. In this example we tie it to AVCC.

Analogue Input

The Atmega168 has has 6 pins (8 for the SMD packages) that can be used for analogue input. These are PC0 to PC5. These are normally used for digital I/O but can be used for analogue input as well. In our example we are using a trimpot as the analog input device and connecting it to PC0.

Analogue Input

LCD display

We’ve connected an LCD display to pins PD0 to PD5 and is used to display analogue readings. For more information on using LCD displays, please read Character LCD Displays – Part 2.

Registers

The Atmega168 uses 6 different registers when performing analogue to digital conversion. These are:

Register Description
ADMUX ADC Multiplexer Selection Register
ADCSRA ADC Control and Status Register A
ADCSRB ADC Control and Status Register B
DIDR0 Digital Input Disable Register 0
ADCL ADC Data Register – Low
ADCH ADC Data Register – High

The ADMUX register allows you to control:

  • The Reference Voltage
  • Left adjustment of results (used for 8 bit results)
  • Selection of input channel
bit 7 6 5 4 3 2 1 0
ADMUX REFS1 REFS0 ADLAR - MUX3 MUX2 MUX1 MUX0
Read/Write R/W R/W R/W R R/W R/W R/W R/W
Initial Value 0 0 0 0 0 0 0 0

The reference voltage is controlled by REFS1 and REFS0. The default is to use AREF, but other options are available.

ADLAR is used for left shifting the converted data. This is useful when reading 8 bit values due to the fact that reading a 16 bit value is not an atomic operation.

MUX0 to MUX3 are used to select which input channel you wish to read. The values 0000 to 0101 allow you to select ports PC0 to PC5, while the reserved values of 1110 and 1111 allow you to select 1.1V and 0V.

ADCSRA and ADCSRB are used to control the AD conversion process.

bit 7 6 5 4 3 2 1 0
ADCSRA ADEN ADSC ADATE ADIF ADIE ADPS2 ADPS1 ADPS0
Read/Write R/W R/W R/W R/W R/W R/W R/W R/W
Initial Value 0 0 0 0 0 0 0 0
bit 7 6 5 4 3 2 1 0
ADCSRB - ACME - - - ADTS2 ADTS1 ADTS0
Read/Write R R/W R R R R/W R/W R/W
Initial Value 0 0 0 0 0 0 0 0

These 2 registers provide for many different options and we will look at a subset in this tutorial.

ADEN enables the AD converter subsystem. This bit needs to be set before any conversion takes place.

ADSC is set when you want to start an AD conversion process. When the conversion is finished, the value reverts back to 0.

ADIF is set when the conversion is complete and the data is written to the result registers (ADCL/ADCH). To clear it back to 0 you need to write a 1 to it.

When an analog sample is read, a timeslice is used. The width of that timeslice is determined by the input clock. ADPS0 to ADPS2 sets the division factor between the system clock and the input clock.

DIDR0 is used to disable the digital input buffers on PC0 to PC5. When set, the corresponding PINC value will be set to 0.

bit 7 6 5 4 3 2 1 0
DIDR0 - - ADC5D ADC4D ADC3D ADC2D ADC1D ADC0D
Read/Write R R R/W R/W R/W R/W R/W R/W
Initial Value 0 0 0 0 0 0 0 0

Lastly we have the ADC Data Registers, ADCL and ADCH. The AD converter returns a 10 bit value which is stored in these 2 registers. The structure of these registers depends on the ADLAR value.

ADLAR=0

bit 7 6 5 4 3 2 1 0
ADCH - - - - - - ADC9 ADC8
Read/Write R R R R R R R R
Initial Value 0 0 0 0 0 0 0 0
bit 7 6 5 4 3 2 1 0
ADCL ADC7 ADC6 ADC5 ADC4 ADC3 ADC2 ADC1 ADC0
Read/Write R R R R R R R R
Initial Value 0 0 0 0 0 0 0 0

ADLAR=1

bit 7 6 5 4 3 2 1 0
ADCH ADC9 ADC8 ADC7 ADC6 ADC5 ADC4 ADC3 ADC2
Read/Write R R R R R R R R
Initial Value 0 0 0 0 0 0 0 0
bit 7 6 5 4 3 2 1 0
ADCL ADC1 ADC0 - - - - - -
Read/Write R R R R R R R R
Initial Value 0 0 0 0 0 0 0 0

To make things easier, the AVR libc library returns the values of these registers as a single 16 bit value, ADC.

Putting it all together

The following code example shows how to read an analogue value and output it to the LCD panel.

  1. #include <avr/io.h>
  2. #include "hd44780.h"
  3.  
  4. uint16_t ReadADC(uint8_t __channel)
  5. {
  6.    ADMUX = (ADMUX & 0xf0) | __channel; // Channel selection
  7.    ADCSRA |= _BV(ADSC);                // Start conversion
  8.    while(!bit_is_set(ADCSRA,ADIF));    // Loop until conversion is complete
  9.    ADCSRA |= _BV(ADIF);                // Clear ADIF by writing a 1 (this sets the value to 0)
  10.  
  11.    return(ADC);
  12. }
  13.  
  14. void adc_init()
  15. {
  16.         ADCSRA = _BV(ADEN) | _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); //Enable ADC and set 128 prescale
  17. }
  18.  
  19. int main (void)
  20. {
  21.     lcd_init();
  22.         adc_init();
  23.  
  24.         while (1)
  25.         {
  26.                 char buffer[16];
  27.                 sprintf(buffer,"%d   ", ReadADC(0));
  28.                 lcd_goto(0);
  29.                 lcd_puts(buffer);
  30.         }
  31.  
  32.         return(0);
  33. }

Sharing is Caring

If you like this post, please post it on twitter, Facebook or your blog. Your support is very much appreciated.

Code Download

More Information

ATmega48/88/168/328 datasheet