ATmega32 Bare Metal ADC

This example drives an LED with PWM using the output of the ADC in free running mode. The ADC is set to read a differential input on PORTA(0:1) with 200x amplification and 7-bit two's complement resolution. A 9V battery is connnected to the inputs via a set of 10kΩ current-limiting resistors and a variable voltage divider. When the ADC conversion completes, it triggers the ADC conversion complete interrupt handler, adc_int. The interrupt handler uses the left-adjusted result from ADCH to set the duty cycle of the LED.

Voltage divider/ADC and PWM LED on an ATmega32

;   Interrupt Handlers
jmp boot            ;   RESET
jmp ignore_int      ;   INT0
jmp ignore_int      ;   INT1
jmp ignore_int      ;   INT2
jmp ignore_int      ;   TIMER2 COMP
jmp ignore_int      ;   TIMER2 OVF
jmp ignore_int      ;   TIMER1 CAPT
jmp ignore_int      ;   TIMER1 COMPA
jmp ignore_int      ;   TIMER1 COMPB
jmp ignore_int      ;   TIMER1 OVF
jmp ignore_int      ;   TIMER0 COMP
jmp ignore_int      ;   TIMER0 OVF
jmp ignore_int      ;   SPI, STC
jmp ignore_int      ;   USART, RXC
jmp ignore_int      ;   USART, UDRE
jmp ignore_int      ;   USART, TXC
jmp adc_int         ;   ADC
jmp ignore_int      ;   EE_RDY
jmp ignore_int      ;   ANA_COMP
jmp ignore_int      ;   TWI
jmp ignore_int      ;   SPM_RDY

;   On RESET
boot:
    enable_stack:
        ldi r16, 0x08
        out 0x3E, r16
        ldi r16, 0x5F
        out 0x3D, r16
    call boot_finish
    call start

;   Dummy interrupt handler (should be the 1st thing after `boot`.)
ignore_int:
    reti

;   Additional boot chores post-stack-initialization, but before `start`
boot_finish:
    call enable_led
    call enable_adc
    call flash_led
    sei                         ;   Enable global interrupts
    ret

;   Main program
start:
    main_loop:
        rjmp main_loop

    ;   Returns to `boot`, which drops through to `ignore_int`, calls 
    ;   `reti` and resets the processor. This is never executed.
    ret

;   ADC driver for PORTA(0:1)
enable_adc:
    push r16

    ldi r16, 0x00   ;   PORTA is tristated input
    out 0x1A, r16
    out 0x1B, r16

    ldi r16, 0x6A   ;   Set ADMUX: VREF is AVCC with external cap on AREF, result left adjusted,
    out 0x07, r16   ;   ADC0 negative, ADC0 positive, differential input, 200x gain, 7-bit

    clr r16         ;   Clear SFIOR: Free running mode
    sts 0x50, r16

    ldi r16, 0xF8   ;   Set ADCSRA: Enable ADC, start conversion, auto trigger, clear ADC
    out 0x06, r16   ;   interrupt flag, enable ADC interrupt, prescalar division factor of 2

    pop r16
    ret

;   ADC conversion complete interrupt handler
adc_int:
    push r25

    in r25, 0x05        ;   Get the result from ADCH
    tst r25             ;   Check for a non-zero result
    breq adc_delay      ;   Zero
    brpl adc_display    ;   Positive
    clr r25             ;   Negative values are zeroed
    rjmp adc_delay

    adc_display:        ;   PWM output
        call led_on

    adc_delay:
        dec r25
        brne PC - 1

    adc_done:
        call led_off

    pop r25
    reti

;  LED driver (reserves r26) for PORTB, pin 0
enable_led:
    ldi r26, 0x01
    out 0x17, r26
    clr r26
    ret

toggle_led:
    cpi r26, 0x01
    breq PC + 4
    call led_on
    ret
    call led_off
    ret

led_on:
    ldi r26, 0x01
    out 0x18, r26
    ret

led_off:
    clr r26 
    out 0x18, r26
    ret

flash_led:
    push r16
    push r24
    push r25
    ldi r16, 0x07
        ldi r24, 0x7D
        ldi r25, 0x10
        call delay
        call toggle_led
        dec r16
        brne PC - 5
    pop r25
    pop r24
    pop r16
    ret

;   Delay is for r24 * r25 * 250 microseconds
;   r24 and r25 must be > 0 
delay:
    push r16
    push r17
    push r24
    push r25

    mov r17, r24
        ldi r16, 0x3E
            dec r16
            brne PC - 1
        dec r17
        brne PC - 4
    dec r25
    brne PC - 7

    pop r25
    pop r24
    pop r17
    pop r16
    ret

Sections