Skip to main content
Analogue to Digital Conversion Interrupts on an ATmega168

Analogue to Digital Conversion Interrupts on an ATmega168A

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 Diagram

The code is pretty simple as you can see below.

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

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

Related News

HD44780 Character LCD Displays – Part 1

Introduction LCD character displays can be found in espresso machines, laser printers, children's toys and...

Debouncing a switch

Mechanical switches do not make or break a connection cleanly due to microscopic conditions on...

AVR Eclipse Environment on Windows

In this tutorial we will show you how to setup an AVR Eclipse development environment...

4 Comments

  1. Tom

    Hello !

    Great tutorial, however according to the Mega88 datasheet, you don’t need to clear the ADIF flag in the interrupt routine, since it “is cleared by hardware when executing the corresponding interrupt handling vector”.

    Best regards,
    Tom

  2. Etienne

    Just a small thing, as per ATMega168 datasheet conversion takes 13 ADC clock cycles, which is generated from mcu clock through the prescaler, so the conversion will take 13 x 128 mcu clock cycles with a prescaler set to 128 (and 25 x 128 mcu clock cycles for the first conversion).

  3. Daid

    It doesn’t take 128 clock cycles to take a ADC sample, it takes 13 ADC cycles to get the sample. Which is 128*13 clock cycles. On the 128 divider.

  4. protostackadmin Author

    Well spotted. I’ve updated the post now.

Leave a reply

Shopping Cart