longchute

about

ATmega32 Bare Metal ADC

05 Nov 2013

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

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
   ;   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