Analogue to Digital Conversion Interrupts on an ATmega168

Back in February, we wrote a post on Analogue to Digital Conversion. Many people mentioned that it was a bit light and they would like a more advanced tutorial. Well here it is…

In this tutorial we add a second analogue input and use the ADC Conversion Complete interrupt. The circuit we are using is similar to what we used last time but has an extra trimpot and uses an ATmega168A microcontroller. The ATmega168 is now obsolete, but its replacement (ATmega168A) is almost identical.

Circuit

The code is pretty simple as you can see below.

  1. #include <avr/io.h>
  2. #include <avr/interrupt.h>
  3. #include "hd44780.h"
  4.  
  5. volatile uint16_t adc[]={0,0};
  6. uint8_t  requested_adc_channel=0;
  7.  
  8. void ReadADC_Request(uint8_t __channel)
  9. {
  10.         if (bit_is_set(ADCSRA,ADSC))
  11.         {
  12.                 //If another ADC read is in progress, display a message and halt processing
  13.                 lcd_goto(0);
  14.                 lcd_puts("ADC Error");
  15.                 abort();
  16.         }
  17.         else
  18.         {
  19.                 requested_adc_channel = __channel;        //We’ll need this when reading the value
  20.        
  21.                 ADMUX = (ADMUX & 0×11110000) | __channel; //Channel selection
  22.                 ADCSRA |= _BV(ADSC);                      //Start conversion
  23.         }
  24. }
  25.  
  26. void adc_init()
  27. {
  28.         //Enable ADC, set 128 prescale and enable the interrupt
  29.         ADCSRA = _BV(ADEN) | _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0) | _BV(ADIE);
  30.         sei();  //Enable interrupts
  31. }
  32.  
  33. ISR(ADC_vect,ISR_NOBLOCK)
  34. {
  35.         adc[requested_adc_channel]=ADC;    // Store the resulting value to the adc results array
  36. }
  37.  
  38.  
  39. int main (void)
  40. {
  41.         lcd_init();
  42.         adc_init();
  43.  
  44.         unsigned long count=0;
  45.         char buffer[16];
  46.         while (1)
  47.         {
  48.                 ReadADC_Request(count % 2);
  49.                 //Uncomment the line below to generate an error in ReadADC_Request
  50.                 //ReadADC_Request(count % 2);
  51.  
  52.                 count++;
  53.                 sprintf(buffer,"%u   ", count);
  54.                 lcd_goto(40);
  55.                 lcd_puts(buffer);
  56.  
  57.                 //The lcd_puts command is pretty slow (>1ms) so by the time we get
  58.                 //here the AD conversion would have already occurred and the
  59.                 //interrupt service routine called.
  60.  
  61.                 sprintf(buffer,"%4u  %4u", adc[1], adc[0]);
  62.                 lcd_goto(0);
  63.                 lcd_puts(buffer);
  64.         }
  65.  
  66.         return(0);
  67. }

Because we set the prescaler to 128, it takes 1664-3200 clock cycles to read the analogue input. Whilst this is occuring we could be doing other things.

On line 48 we initiate an AD Converter read. Because we can only read one analogue input at a time we pass “count % 2″ as the channel number. This evaluates to a 0 or 1 depending on the odd or even value of count. Whilst the analogue value is being read, we output count to the LCD display. The reason for this is to simulate the system being busy doing something. In a real system you would be doing something much more interesting than outputting a count.

The interrupt service routine reads in the ADC value and stores it in adc[0] or adc[1]. By the time we get to line 61, one of these values has been updated and we are ready to output it to the LCD display.

Return the favour

We work hard to write tutorials that we hope you like. If you do like this post, please share it it on twitter, Facebook or your own blog. Your support is very much appreciated.

Code Download

More Information

ATmega48A/48PA/88A/88PA/168A/168PA/328/328 Datasheet
AVR libc <avr/interrupt.h> Interrupts Documentation