ATmega8 ADC Example

This is a very simple example you probably won't see in any serious design, but it's straight forward and not hard to troubleshoot. We want to read the voltage output of a pot and show the most significant 8 bits of the conversion result on port D (the ADC has a resolution of 10 bits!).

Choosing ADC Mode And Clock

The mega8 ADC offers 2 modes of operation, the Single Conversion Mode and the Free-Running Mode. Both have advantages and disadvantages and I will try to discuss them both well enough now.

In Free-Running Mode, a new conversion is started when a conversion finishes. The new conversion is done for the ADC channel set in ADMUX. If a new channel has to be converted, it's number has to be set *before* the new conversion starts. If an ISR is used to process the ADC results and update ADMUX, care has to be taken, as a change of ADMUX just after the conversion start can have unpredictable results (read pages 196/197 of the mega8 datasheet for more). As we only want to work with one ADC channel, this is no problem for us. Only the very first conversion has to be started by setting the ADSC bit!

In Single Conversion Mode, every conversion has to be started by setting the ADSC (ADC Start Conversion) bit in ADCSR. The advantage of this is that a new channel can be selected before the conversion is started without watching out for timing problems or unpredictable results. If the ADC Conversion Complete ISR is used for this, the loss in speed is quite small.

In this case we can use the Free-Running Mode - we don't need to change ADMUX (just one channel is used).

The recommended ADC clock range is 50 kHz to 200 kHz. If a faster ADC clock is used, the resolution will go down. The ADC clock is prescaled from the main clock by a seperate ADC prescaler. The division factor is selected with the ADPS2..0 bits in ADCSR (see pages 203/204 of the datasheet). At 4 MHz, appropriate values are 32 (resulting ADC clock is 125 kHz) and 64 (resulting ADC clock is 62.5 kHz). We'll use 32.

What The ISR Does...

The mega8 interrupt vector for the ADC Conversion Complete ISR doesn't have to do much. When it is called, a fresh ADC result is available in ADCL and ADCH. It is converted to 8 bits (the two LSBs aren't used) and written to PortD. On the STK500 the LEDs are active low, so inverting the result before writing it to PortD is a good idea.

The ISR is, of course, only called if the ADIE (ADC Interrupt Enble) bit in ADCSR is set and if global interrupts are enabled.

The Example Code!

.org 0x0000
rjmp reset
 
.org 0x000E
rjmp ADC_ISR
 
reset:
ldi r16, low(RAMEND)
out SPL, r16
ldi r16, high(RAMEND)
out SPH, r16
 
ldi r16, 0xFF
out DDRD, r16
 
ldi r16, 0
out ADMUX, r16
ldi r16, 0b11101101
out ADCSR, r16
 
sei
 
loop:
rjmp loop
 
ADC_ISR:
push r16
in r16, SREG
push r16
push r17
 
in r16, ADCL
in r17, ADCH
lsr r17
ror r16
lsr r17
ror r16

com r16
out PortD, r16

pop r17
pop r16
out SREG, r16
pop r16
reti
; reset vector
; jump to "reset"
;
; ADC Conversion Complete Interrupt vector:
; jump the "ADC_ISR"
;
; the reset code:
; stack setup; set SPH:SPL to
; RAMEND
;
;
;
; set all PortD pins to output
;
;
; write zero
; to ADMUX (select channel 0)
; from left to right: ADC Enable, Start Conversion, Free-Running Mode, write
; zero to ADC Int flag, enable int, prescaler: 101 for XTAL/32
;
; enable interrupts
;
; and loop
; forever
;
; Here it is, our ISR!
; save r16
; use r16 16 to save SREG
; (push both on stack)
; also save r17
;
; get the last ADC result, low byte first,
; then high byte
; shift ADC result right (2 bits)
; by first shifting out bit 0 of r16, then shifting it into r17
;
; (twice)
;
; now invert result
; and write to PortD
;
; restore r17,
; SREG
;
; and r16
; and return