0% found this document useful (0 votes)
5 views24 pages

Atmega32 Problems Recovered

The document provides a comprehensive guide on register-level programming for the ATmega32 microcontroller in C, covering I/O ports, interrupts, and ADC functionalities. It includes practical problems with full solutions, key registers, and example code snippets for various applications such as LED blinking, push-button control, and timer interrupts. The content is designed for self-study and utilizes AVR-GCC for compilation and flashing of programs.

Uploaded by

Rubayet Sikder
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views24 pages

Atmega32 Problems Recovered

The document provides a comprehensive guide on register-level programming for the ATmega32 microcontroller in C, covering I/O ports, interrupts, and ADC functionalities. It includes practical problems with full solutions, key registers, and example code snippets for various applications such as LED blinking, push-button control, and timer interrupts. The content is designed for self-study and utilizes AVR-GCC for compilation and flashing of programs.

Uploaded by

Rubayet Sikder
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

ATmega32 Register-Level Programming in C IO • Interrupts • ADC

ewcommand[1]1
ewcommand[1]1
ewcommand[1]1
ewcounterproblem
ewenvironmentproblem[1]

Problem : 1

ewenvironmentsolution[1]

Solution : 1

—1—
ATmega32 Register-Level
Programming in C

20 Practical Problems with Full Solutions


IO Ports • External & Timer Interrupts • ADC

Quick Reference: Key Registers

Register Purpose
DDRx Data Direction Register (0=Input, 1=Output)
PORTx Output / Pull-up control
PINx Read digital input
MCUCR INT0/INT1 sense control
GICR Global Interrupt Control Register
GIFR Global Interrupt Flag Register
TIMSK Timer Interrupt Mask
TCCR0/1/2 Timer/Counter Control
ADMUX ADC Multiplexer / Reference
ADCSRA ADC Control & Status
ADCH/L ADC Data Registers
SREG Status Register (bit 7 = Global IE)
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

Environment: AVR-GCC • #include <avr/io.h> • #include


<avr/interrupt.h> • FCPU = 8 MHz (unless stated otherwise)

Compiled for self-study purposes — ATmega32 Datasheet reference: Atmel/Microchip doc 2503

—2—
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

Contents

ewpage

1. Introduction

The ATmega32 is an 8-bit AVR RISC microcontroller with 32 KB Flash, 2 KB SRAM,


1 KB EEPROM, 32 I/O pins (Ports A–D), a 10-bit ADC (8 channels), two 8-bit timers,
one 16-bit timer, and a flexible interrupt system.
Register-level (bare-metal) C programming means manipulating hardware registers directly
via the macros provided in avr/io.h rather than using a hardware-abstraction layer.

Compile & Flash


avr - gcc - mmcu = atmega32 - DF_CPU =8000000 UL - Os -o main . elf main . c
avr - objcopy -O ihex main . elf main . hex
avrdude -c usbasp -p m32 -U flash : w : main . hex

Common Bit-manipulation Macros


# define SET_BIT ( reg , bit ) (( reg ) |= (1 << ( bit ) ) )
# define CLR_BIT ( reg , bit ) (( reg ) &= ~(1 << ( bit ) ) )
# define TOG_BIT ( reg , bit ) (( reg ) ^= (1 << ( bit ) ) )
# define GET_BIT ( reg , bit ) ((( reg ) >> ( bit ) ) & 1)

ewpage

2. I/O Port Problems

LED Blink on PORTB Pin 0 Configure PB0 as output and blink an LED at approximately
1 Hz using a software delay loop. No external libraries; pure register-level delay.
Registers used: DDRB, PORTB
LED Blink on PORTB Pin 0
1 # define F_CPU 8000000 UL
2 # include < avr / io .h >
3
4 /* Software delay ~ 1 second at 8 MHz */
5 void delay_ms ( uint16_t ms ) {
6 uint32_t i ;

—1—
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

7 for (; ms > 0; ms - -)
8 for ( i = 0; i < 800; i ++) /* ~1 ms loop */
9 __asm__ __volatile__ ( " nop " ) ;
10
11 int main ( void ) {
12 DDRB |= (1 << PB0 ) ; /* PB0 = output */
13 PORTB &= ~(1 << PB0 ) ; /* LED off initially */
14
15 while (1) {
16 PORTB |= (1 << PB0 ) ; /* LED ON */
17 delay_ms (500) ;
18 PORTB &= ~(1 << PB0 ) ; /* LED OFF */
19 delay_ms (500) ;
20 }
21 }

Note: The inner loop count (800) is an approximation; calibrate with an oscilloscope for
exact timing. Use util/delay.h when precision matters.

Push-Button Controlled LED Read a push-button connected to PD2 (with internal pull-up
enabled). Light an LED on PB1 while the button is pressed.
Registers used: DDRD, PORTD, PIND, DDRB, PORTB
Push-Button Controlled LED
1 # include < avr / io .h >
2
3 int main ( void ) {
4 /* PD2 = input with internal pull - up */
5 DDRD &= ~(1 << PD2 ) ;
6 PORTD |= (1 << PD2 ) ;
7

8 /* PB1 = output */
9 DDRB |= (1 << PB1 ) ;
10
11 while (1) {
12 /* Button active - low : pressed = > PD2 reads 0 */
13 if (!( PIND & (1 << PD2 ) ) )
14 PORTB |= (1 << PB1 ) ; /* LED ON */
15 else
16 PORTB &= ~(1 << PB1 ) ; /* LED OFF */
17 }

8-bit Binary Counter on PORTA Display an incrementing 8-bit counter on all 8 pins of
PORTA (each pin drives an LED). The counter increments every 200 ms.
Registers used: DDRA, PORTA
8-bit Binary Counter on PORTA
1 # define F_CPU 8000000 UL
2 # include < avr / io .h >
3 # include < util / delay .h >
4
5 int main ( void ) {
6 DDRA = 0 xFF ; /* All PORTA pins = output */

—2—
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

7 PORTA = 0 x00 ;
8
9 uint8_t count = 0;
10
11 while (1) {
12 PORTA = count ++; /* Write counter value directly */
13 _delay_ms (200) ;
14 }

Note: After 255 the counter wraps back to 0 automatically due to uint8_t overflow —
perfect for a cycling display.

Shift Register: LED Chaser on PORTC Create a Knight-Rider style LED chaser on all 8
pins of PORTC. A single lit LED bounces left and right with a 100 ms step.
Registers used: DDRC, PORTC
Shift Register: LED Chaser on PORTC
1 # define F_CPU 8000000 UL
2 # include < avr / io .h >
3 # include < util / delay .h >
4
5 int main ( void ) {
6 DDRC = 0 xFF ;
7 PORTC = 0 x00 ;
8
9 uint8_t pos = 0;
10 int8_t dir = 1; /* +1 = left shift , -1 = right shift */
11
12 while (1) {
13 PORTC = (1 << pos ) ;
14 _delay_ms (100) ;
15
16 /* Bounce at edges */
17 if ( pos == 7) dir = -1;
18 if ( pos == 0) dir = 1;
19 pos += dir ;
20 }

ewpage

3. External Interrupt Problems

Toggle LED on INT0 Falling Edge Each time the button on INT0 (PD2) is pressed (falling
edge), toggle an LED on PB0.
Registers used: MCUCR, GICR, SREG, DDRB, PORTB
Toggle LED on INT0 Falling Edge
1 # include < avr / io .h >
2 # include < avr / interrupt .h >

—3—
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

3
4 ISR ( INT0_vect ) {
5 PORTB ^= (1 << PB0 ) ; /* Toggle LED */
6
7 int main ( void ) {
8 /* PB0 = output */
9 DDRB |= (1 << PB0 ) ;
10
11 /* INT0 = falling edge ( ISC01 =1 , ISC00 =0) */
12 MCUCR |= (1 << ISC01 ) ;
13 MCUCR &= ~(1 << ISC00 ) ;
14

15 GICR |= (1 << INT0 ) ; /* Enable INT0 */


16 sei () ; /* Global interrupt enable */
17
18 while (1) { /* main loop free */ }
19 }

MCUCR ISCxx truth table: ISC01=0,ISC00=0 → Low level ISC01=0,ISC00=1 →


Any change
ISC01=1,ISC00=0 → Falling edge ISC01=1,ISC00=1 → Rising edge

Event Counter Using INT1 Count button presses on INT1 (PD3). Display the 8-bit count
on PORTA (LEDs). Reset the count when it reaches 10.
Registers used: MCUCR, GICR, DDRA, PORTA
Event Counter Using INT1
1 # include < avr / io .h >
2 # include < avr / interrupt .h >
3

4 volatile uint8_t count = 0;


5
6 ISR ( INT1_vect ) {
7 count ++;
8 if ( count >= 10)
9 count = 0;
10 PORTA = count ; /* Update display immediately */
11
12 int main ( void ) {
13 DDRA = 0 xFF ; /* PORTA all output */
14 PORTA = 0 x00 ;
15

16 /* PD3 input + pull - up */


17 DDRD &= ~(1 << PD3 ) ;
18 PORTD |= (1 << PD3 ) ;
19
20 /* INT1 falling edge */
21 MCUCR |= (1 << ISC11 ) ;
22 MCUCR &= ~(1 << ISC10 ) ;
23
24 GICR |= (1 << INT1 ) ;
25 sei () ;
26
27 while (1) {}
28 }

—4—
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

Dual Interrupt Priority: INT0 and INT1 INT0 turns ON an LED on PB2; INT1 turns it
OFF. Both react on rising edge. Demonstrate that both interrupts operate independently.
Registers used: MCUCR, GICR
Dual Interrupt Priority: INT0 and INT1
1 # include < avr / io .h >
2 # include < avr / interrupt .h >
3
4 ISR ( INT0_vect ) {
5 PORTB |= (1 << PB2 ) ; /* LED ON */
6

7 ISR ( INT1_vect ) {
8 PORTB &= ~(1 << PB2 ) ; /* LED OFF */
9 }
10
11 int main ( void ) {
12 DDRB |= (1 << PB2 ) ; /* PB2 output */
13
14 /* PD2 , PD3 inputs with pull - ups */
15 DDRD &= ~((1 << PD2 ) | (1 << PD3 ) ) ;
16 PORTD |= ((1 << PD2 ) | (1 << PD3 ) ) ;
17
18 /* Both on rising edge : ISCx1 =1 , ISCx0 =1 */
19 MCUCR |= (1 << ISC01 ) | (1 << ISC00 ) /* INT0 */
20 | (1 << ISC11 ) | (1 << ISC10 ) ; /* INT1 */
21
22 GICR |= (1 << INT0 ) | (1 << INT1 ) ;
23 sei () ;
24

25 while (1) {}
26 }

ewpage

4. Timer Interrupt Problems

Timer0 Overflow — 1 Hz LED Blink Use Timer0 overflow interrupt (prescaler 1024) to
blink an LED on PB3 every 1 second. FCPU = 8 MHz.
Registers: TCCR0, TIMSK, TCNT0
256×1024
Calculation: Toverf low = 8 000 000
= 32.768 ms, so count 30 overflows ≈ 1 s.
Timer0 Overflow — 1 Hz LED Blink
1 # include < avr / io .h >
2 # include < avr / interrupt .h >
3

4 volatile uint8_t ovf_count = 0;


5
6 ISR ( TIMER0_OVF_vect ) {
7 ovf_count ++;

—5—
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

8 if ( ovf_count >= 30) { /* ~1 second */


9 ovf_count = 0;
10 PORTB ^= (1 << PB3 ) ; /* Toggle LED */
11 }
12
13 int main ( void ) {
14 DDRB |= (1 << PB3 ) ;
15
16 /* Timer0 : Normal mode , prescaler = 1024 */
17 TCCR0 = (1 << CS02 ) | (1 << CS00 ) ;
18
19 /* Enable Timer0 overflow interrupt */
20 TIMSK |= (1 << TOIE0 ) ;
21 sei () ;
22
23 while (1) {}
24 }

Timer0 CTC Mode — Precise 500 Hz Toggle Configure Timer0 in CTC mode to generate
an exact 500 Hz square wave on OC0 (PB3).
FCP U 8 000 000
Calculation: OCR0 = 2×prescaler×f
−1= 2×64×500
− 1 = 124
Timer0 CTC Mode — Precise 500 Hz Toggle
1 # include < avr / io .h >
2

3 int main ( void ) {


4 /* PB3 ( OC0 ) = output */
5 DDRB |= (1 << PB3 ) ;
6
7 /* CTC mode : WGM01 =1 , WGM00 =0
8 Toggle OC0 on compare : COM01 =0 , COM00 =1
9 Prescaler 64: CS01 =1 , CS00 =1 */
10 TCCR0 = (1 << WGM01 ) | (1 << COM00 )
11 | (1 << CS01 ) | (1 << CS00 ) ;
12
13 OCR0 = 124; /* Compare value for 500 Hz */
14

15 while (1) {} /* Hardware toggles OC0 automatically */

In CTC mode the hardware toggles OC0 each compare match with no ISR needed — zero
CPU overhead.

Timer1 — Measure Pulse Width (Input Capture) Use Timer1 Input Capture to measure
the width of a pulse on ICP1 (PD6). Store rising-edge and falling-edge timestamps; output
result via a global variable.
Registers: TCCR1A, TCCR1B, TIMSK, ICR1
Timer1 — Measure Pulse Width (Input Capture)
1 # include < avr / io .h >
2 # include < avr / interrupt .h >
3
4 volatile uint16_t rise_time = 0;
5 volatile uint16_t pulse_width = 0;

—6—
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

6 volatile uint8_t capturing = 0;


7
8 ISR ( TIMER1_CAPT_vect ) {
9 if (! capturing ) {
10 rise_time = ICR1 ;
11 /* Switch to falling - edge capture */
12 TCCR1B &= ~(1 << ICES1 ) ;
13 capturing = 1;
14 } else {
15 pulse_width = ICR1 - rise_time ; /* ticks */
16 /* Switch back to rising - edge */
17 TCCR1B |= (1 << ICES1 ) ;
18 capturing = 0;
19 }
20
21 int main ( void ) {
22 /* ICP1 ( PD6 ) input */
23 DDRD &= ~(1 << PD6 ) ;
24
25 /* Timer1 : no prescaler , rising - edge capture , noise canceler on
*/
26 TCCR1A = 0;
27 TCCR1B = (1 << ICNC1 ) | (1 << ICES1 ) | (1 << CS10 ) ;
28

29 /* Enable Input Capture interrupt */


30 TIMSK |= (1 << TICIE1 ) ;
31 sei () ;
32
33 while (1) {
34 /* pulse_width in timer ticks (125 ns each @ 8 MHz , no
prescale ) */
35 }
36 }

ewpage Timer2 PWM — LED Brightness Control Generate an 8-bit Fast PWM signal
on OC2 (PD7) to control LED brightness. Fade from 0% to 100% and back continuously.
Registers: TCCR2, OCR2, DDRD
Timer2 PWM — LED Brightness Control
1 # define F_CPU 8000000 UL
2 # include < avr / io .h >
3 # include < util / delay .h >
4

5 int main ( void ) {


6 /* OC2 ( PD7 ) = output */
7 DDRD |= (1 << PD7 ) ;
8
9 /* Fast PWM , non - inverting , prescaler 64
10 WGM21 =1 , WGM20 =1 , COM21 =1 , COM20 =0 , CS22 =1 */
11 TCCR2 = (1 << WGM21 ) | (1 << WGM20 )
12 | (1 << COM21 )
13 | (1 << CS22 ) ;
14
15 uint8_t duty = 0;
16 int8_t step = 1;
17

—7—
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

18 while (1) {
19 OCR2 = duty ; /* Set duty cycle (0 -255) */
20 _delay_ms (10) ;
21
22 if ( duty == 255) step = -1;
23 if ( duty == 0) step = 1;
24 duty += step ;
25 }

ewpage

5. ADC Problems

Single-Channel ADC Read (Polling) Read a potentiometer on ADC0 (PA0) using polling
mode. Display the 8 MSBs of the 10-bit result on PORTB.
Registers: ADMUX, ADCSRA, ADCH, ADCL
Single-Channel ADC Read (Polling)
1 # include < avr / io .h >
2
3 /* Blocking ADC read on given channel (0 -7) ; returns 10 - bit result */
4 uint16_t adc_read ( uint8_t ch ) {
5 /* Select channel , AVCC reference , left - adjust off */
6 ADMUX = (1 << REFS0 ) | ( ch & 0 x07 ) ;
7

8 /* Start conversion */
9 ADCSRA |= (1 << ADSC ) ;
10
11 /* Wait until conversion complete ( ADSC goes low ) */
12 while ( ADCSRA & (1 << ADSC ) ) ;
13

14 return ADC ; /* 10 - bit value in ADCL : ADCH */


15
16 int main ( void ) {
17 DDRB = 0 xFF ; /* PORTB all output */
18
19 /* Enable ADC , prescaler = 64 (125 kHz @ 8 MHz ) */
20 ADCSRA = (1 << ADEN ) | (1 << ADPS2 ) | (1 << ADPS1 ) ;
21
22 while (1) {
23 uint16_t val = adc_read (0) ;
24 PORTB = ( uint8_t ) ( val >> 2) ; /* Display 8 MSBs */
25 }
26 }

ADC clock requirement: 50–200 kHz for full 10-bit accuracy. At 8 MHz: prescaler 64
gives 125 kHz — ideal.

ADC with Interrupt — Non-Blocking Read Trigger a new ADC conversion every 10 ms
using Timer0 overflow. Store results in a circular buffer of 8 samples using the ADC

—8—
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

complete interrupt.
Registers: ADCSRA, ADMUX, TIMSK
ADC with Interrupt — Non-Blocking Read
1 # include < avr / io .h >
2 # include < avr / interrupt .h >
3
4 # define BUF_SIZE 8
5 volatile uint16_t adc_buf [ BUF_SIZE ];
6 volatile uint8_t buf_idx = 0;
7
8 /* ADC conversion complete */
9 ISR ( ADC_vect ) {
10 adc_buf [ buf_idx ] = ADC ;
11 buf_idx = ( buf_idx + 1) % BUF_SIZE ;
12
13 /* Timer0 overflow -- triggers a new ADC conversion */
14 ISR ( TIMER0_OVF_vect ) {
15 ADCSRA |= (1 << ADSC ) ; /* Start next conversion */
16 }
17

18 int main ( void ) {


19 /* ADC : AVCC ref , channel 0 , prescaler 64 , interrupt enabled */
20 ADMUX = (1 << REFS0 ) ;
21 ADCSRA = (1 << ADEN ) | (1 << ADIE )
22 | (1 << ADPS2 ) | (1 << ADPS1 ) ;
23

24 /* Timer0 : overflow every ~8 ms ( prescaler 256) */


25 TCCR0 = (1 << CS02 ) ; /* prescaler 256 */
26 TIMSK |= (1 << TOIE0 ) ;
27
28 sei () ;
29
30 while (1) {
31 /* Process adc_buf [] here when needed */
32 }
33 }

ADC Multi-Channel Scan Scan 4 ADC channels (ADC0–ADC3) in round-robin fashion


using the ADC interrupt. Store each channel result in a dedicated array.
Registers: ADMUX, ADCSRA
ADC Multi-Channel Scan
1 # include < avr / io .h >
2 # include < avr / interrupt .h >
3

4 # define NUM_CH 4
5 volatile uint16_t adc_result [ NUM_CH ];
6 volatile uint8_t cur_ch = 0;
7
8 ISR ( ADC_vect ) {
9 adc_result [ cur_ch ] = ADC ; /* Save result */
10 cur_ch = ( cur_ch + 1) % NUM_CH ; /* Next channel */
11
12 /* Select next channel */

—9—
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

13 ADMUX = ( ADMUX & 0 xF8 ) | cur_ch ;


14
15 /* Start next conversion */
16 ADCSRA |= (1 << ADSC ) ;
17
18 int main ( void ) {
19 /* AVCC reference , start on channel 0 */
20 ADMUX = (1 << REFS0 ) ;
21 ADCSRA = (1 << ADEN ) | (1 << ADIE )
22 | (1 << ADPS2 ) | (1 << ADPS1 ) ;
23
24 sei () ;
25 ADCSRA |= (1 << ADSC ) ; /* Kick off first conversion */
26
27 while (1) {
28 /* adc_result [0..3] always hold latest readings */
29 }
30 }

ewpage Light-Dependent Resistor (LDR) Threshold Alert Read an LDR on ADC1. If the
ADC value falls below 200 (darkness threshold), light an LED on PB5; otherwise keep it
off.
Registers: ADMUX, ADCSRA, DDRB, PORTB
Light-Dependent Resistor (LDR) Threshold Alert
1 # include < avr / io .h >
2

3 uint16_t adc_read ( uint8_t ch ) {


4 ADMUX = (1 << REFS0 ) | ( ch & 0 x07 ) ;
5 ADCSRA |= (1 << ADSC ) ;
6 while ( ADCSRA & (1 << ADSC ) ) ;
7 return ADC ;
8

9 int main ( void ) {


10 DDRB |= (1 << PB5 ) ; /* LED output */
11 ADCSRA = (1 << ADEN ) | (1 << ADPS2 ) | (1 << ADPS1 ) ;
12
13 while (1) {
14 uint16_t light = adc_read (1) ; /* ADC1 = LDR */
15
16 if ( light < 200)
17 PORTB |= (1 << PB5 ) ; /* Dark : LED ON */
18 else
19 PORTB &= ~(1 << PB5 ) ; /* Light : LED OFF */
20 }
21 }

ewpage

6. Mixed Problems (IO + Interrupts + ADC)

— 10 —
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

Interrupt-Driven PWM Brightness via ADC Read a potentiometer on ADC0 every 50 ms


(via Timer1 Compare interrupt). Feed the ADC value into OCR2 to control LED brightness
through Timer2 Fast-PWM on OC2 (PD7).
Interrupt-Driven PWM Brightness via ADC
1 # include < avr / io .h >
2 # include < avr / interrupt .h >
3
4 volatile uint8_t new_reading = 0;
5

6 /* Timer1 Compare A fires every ~50 ms */


7 ISR ( TIMER 1_COMP A_vect ) {
8 ADMUX = (1 << REFS0 ) ; /* Channel 0 , AVCC ref */
9 ADCSRA |= (1 << ADSC ) ; /* Start ADC */
10
11 ISR ( ADC_vect ) {
12 OCR2 = ( uint8_t ) ( ADC >> 2) ; /* 10 - bit -> 8 - bit duty */
13 }
14
15 int main ( void ) {
16 /* OC2 ( PD7 ) output */
17 DDRD |= (1 << PD7 ) ;
18
19 /* Timer2 Fast PWM , non - inverting , prescaler 64 */
20 TCCR2 = (1 << WGM21 ) | (1 << WGM20 )
21 | (1 << COM21 ) | (1 << CS22 ) ;
22
23 /* Timer1 CTC , prescaler 256: OCR1A for 50 ms
24 OCR1A = F_CPU / ( prescaler * freq ) - 1
25 = 8 e6 / (256 * 20) - 1 = 1562 */
26 TCCR1A = 0;
27 TCCR1B = (1 << WGM12 ) | (1 << CS12 ) ; /* CTC , /256 */
28 OCR1A = 1562;
29 TIMSK |= (1 << OCIE1A ) ;
30
31 /* ADC : AVCC ref , ADC interrupt , prescaler 64 */
32 ADCSRA = (1 << ADEN ) | (1 << ADIE )
33 | (1 << ADPS2 ) | (1 << ADPS1 ) ;
34 sei () ;
35

36 while (1) {}
37 }

Emergency Stop with INT0 and ADC Motor Simulation Simulate a motor: PB4 is the
“motor running” LED toggled by Timer0 overflow. Pressing INT0 immediately stops the
motor (disables Timer0). An ADC on ADC2 reads speed; if ADC > 900 (over-speed), also
stop.
Emergency Stop with INT0 and ADC Motor Simulation
1 # include < avr / io .h >
2 # include < avr / interrupt .h >
3
4 volatile uint8_t motor_on = 1;
5
6 /* INT0 emergency stop */
7 ISR ( INT0_vect ) {

— 11 —
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

8 motor_on = 0;
9 TCCR0 = 0; /* Stop Timer0 */
10 PORTB &= ~(1 << PB4 ) ; /* Motor LED off */
11
12 /* Timer0 overflow : simulate motor running */
13 ISR ( TIMER0_OVF_vect ) {
14 PORTB ^= (1 << PB4 ) ; /* Blink = motor pulse */
15 }
16
17 uint16_t adc_read ( uint8_t ch ) {
18 ADMUX = (1 << REFS0 ) | ch ;
19 ADCSRA |= (1 << ADSC ) ;
20 while ( ADCSRA & (1 << ADSC ) ) ;
21 return ADC ;
22 }
23
24 int main ( void ) {
25 DDRB |= (1 << PB4 ) ;
26
27 /* INT0 falling edge */
28 MCUCR |= (1 << ISC01 ) ;
29 GICR |= (1 << INT0 ) ;
30
31 /* Timer0 prescaler 1024 */
32 TCCR0 = (1 << CS02 ) | (1 << CS00 ) ;
33 TIMSK |= (1 << TOIE0 ) ;
34
35 /* ADC setup */
36 ADCSRA = (1 << ADEN ) | (1 << ADPS2 ) | (1 << ADPS1 ) ;
37

38 sei () ;
39
40 while (1) {
41 if ( motor_on ) {
42 uint16_t speed = adc_read (2) ;
43 if ( speed > 900) { /* Over - speed check */
44 motor_on = 0;
45 TCCR0 = 0;
46 PORTB &= ~(1 << PB4 ) ;
47 }
48 }
49 }
50 }

Temperature Alarm System Read an NTC thermistor ADC value on ADC3. Use a lookup
or threshold: if ADC value > 700 (hot), blink a red LED on PB6 at 2 Hz (Timer2
overflow); otherwise keep a green LED on PB7 steady. A button on INT1 silences the
blink for 5 timer ticks.
Temperature Alarm System
1 # include < avr / io .h >
2 # include < avr / interrupt .h >
3
4 volatile uint8_t alarm_active = 0;
5 volatile uint8_t silence_count = 0;
6 volatile uint16_t temp_adc = 0;
7

— 12 —
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

8 ISR ( TIMER2_OVF_vect ) {
9 /* ~122 Hz overflow at prescaler 1024. Blink every ~61 ticks =
0.5 s */
10 static uint8_t ticks = 0;
11 ticks ++;
12

13 if ( silence_count > 0) {
14 silence_count - -;
15 PORTB &= ~(1 << PB6 ) ; /* Alarm LED off while silenced */
16 return ;
17 }
18

19 if ( alarm_active && ( ticks & 0 x20 ) ) { /* bit 5 toggle = ~0.5 s */


20 PORTB ^= (1 << PB6 ) ;
21 } else if (! alarm_active ) {
22 PORTB &= ~(1 << PB6 ) ;
23 }
24

25 /* INT1 : silence alarm for ~5 timer periods */


26 ISR ( INT1_vect ) {
27 silence_count = 5;
28 }
29
30 uint16_t adc_read ( uint8_t ch ) {
31 ADMUX = (1 << REFS0 ) | ch ;
32 ADCSRA |= (1 << ADSC ) ;
33 while ( ADCSRA & (1 << ADSC ) ) ;
34 return ADC ;
35 }
36

37 int main ( void ) {


38 DDRB |= (1 << PB6 ) | (1 << PB7 ) ;
39
40 /* Timer2 prescaler 1024 for overflow interrupt */
41 TCCR2 = (1 << CS22 ) | (1 << CS21 ) | (1 << CS20 ) ;
42 TIMSK |= (1 << TOIE2 ) ;
43
44 /* INT1 falling edge */
45 MCUCR |= (1 << ISC11 ) ;
46 GICR |= (1 << INT1 ) ;
47
48 ADCSRA = (1 << ADEN ) | (1 << ADPS2 ) | (1 << ADPS1 ) ;
49 sei () ;
50
51 while (1) {
52 temp_adc = adc_read (3) ;
53
54 if ( temp_adc > 700) {
55 alarm_active = 1;
56 PORTB &= ~(1 << PB7 ) ; /* Green LED off */
57 } else {
58 alarm_active = 0;
59 PORTB &= ~(1 << PB6 ) ; /* Red LED off */
60 PORTB |= (1 << PB7 ) ; /* Green LED on */
61 }
62 }
63 }

— 13 —
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

ADC-Controlled Timer Period Read a potentiometer on ADC0. Use the result to dynami-
cally change Timer1 OCR1A compare value, making an LED on PB0 blink faster or slower
according to the potentiometer position.
ADC-Controlled Timer Period
1 # include < avr / io .h >
2 # include < avr / interrupt .h >
3
4 volatile uint16_t adc_val = 512;
5

6 ISR ( TIMER 1_COMP A_vect ) {


7 PORTB ^= (1 << PB0 ) ; /* Toggle LED */
8 /* Update period from ADC : range 100..5000 ticks */
9 OCR1A = 100 + ( adc_val * 19) ; /* pot maps 0 -1023 -> 100 -19537 */
10
11 uint16_t adc_read ( uint8_t ch ) {
12 ADMUX = (1 << REFS0 ) | ch ;
13 ADCSRA |= (1 << ADSC ) ;
14 while ( ADCSRA & (1 << ADSC ) ) ;
15 return ADC ;
16 }
17

18 int main ( void ) {


19 DDRB |= (1 << PB0 ) ;
20
21 /* Timer1 CTC mode , prescaler 256 */
22 TCCR1A = 0;
23 TCCR1B = (1 << WGM12 ) | (1 << CS12 ) ;
24 OCR1A = 5000;
25 TIMSK |= (1 << OCIE1A ) ;
26
27 ADCSRA = (1 << ADEN ) | (1 << ADPS2 ) | (1 << ADPS1 ) ;
28 sei () ;
29

30 while (1) {
31 adc_val = adc_read (0) ; /* Read pot continuously */
32 }
33 }

Debounced Button Counter with ADC Display Use INT0 with a 20 ms software debounce
(via Timer0) to count button presses. Display count proportionally on a 3-LED bar graph
(PB0–PB2): 0 presses = no LEDs, 1–3 = one LED, 4–6 = two LEDs, 7+ = all three LEDs.
Also read ADC0 and display raw MSB nibble on PORTA.
Debounced Button Counter with ADC Display
1 # include < avr / io .h >
2 # include < avr / interrupt .h >
3
4 volatile uint8_t press_count = 0;
5 volatile uint8_t debouncing = 0;
6
7 /* INT0 : record press , start debounce timer */
8 ISR ( INT0_vect ) {
9 if (! debouncing ) {
10 press_count ++;
11 debouncing = 1;

— 14 —
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

12 TCCR0 = (1 << CS02 ) | (1 << CS00 ) ; /* Start Timer0 1024 */


13 TCNT0 = 100; /* ~20 ms until
overflow */
14 }
15
16 /* Timer0 overflow : debounce window expired */
17 ISR ( TIMER0_OVF_vect ) {
18 debouncing = 0;
19 TCCR0 = 0; /* Stop timer */
20 }
21
22 void update_bar ( uint8_t count ) {
23 PORTB &= 0 xF8 ; /* Clear PB0 - PB2 */
24 if ( count >= 7) PORTB |= 0 x07 ; /* All 3 LEDs */
25 else if ( count >= 4) PORTB |= 0 x03 ; /* 2 LEDs */
26 else if ( count >= 1) PORTB |= 0 x01 ; /* 1 LED */
27 }
28

29 uint16_t adc_read ( uint8_t ch ) {


30 ADMUX = (1 << REFS0 ) | ch ;
31 ADCSRA |= (1 << ADSC ) ;
32 while ( ADCSRA & (1 << ADSC ) ) ;
33 return ADC ;
34 }
35
36 int main ( void ) {
37 DDRB |= 0 x07 ; /* PB0 - PB2 output */
38 DDRA = 0 xFF ;
39
40 DDRD &= ~(1 << PD2 ) ; /* INT0 input */
41 PORTD |= (1 << PD2 ) ; /* Pull - up */
42
43 /* INT0 falling edge */
44 MCUCR |= (1 << ISC01 ) ;
45 GICR |= (1 << INT0 ) ;
46

47 TIMSK |= (1 << TOIE0 ) ;


48 ADCSRA = (1 << ADEN ) | (1 << ADPS2 ) | (1 << ADPS1 ) ;
49 sei () ;
50
51 while (1) {
52 update_bar ( press_count ) ;
53 uint16_t a = adc_read (0) ;
54 PORTA = ( uint8_t ) ( a >> 6) & 0 x0F ; /* 4 MSBs */
55 }
56 }

ADC Free-Running Mode with Watchdog Overflow Alert Configure the ADC in free-
running mode on ADC0. Use Timer1 overflow as a 1-second heartbeat: if no ADC
conversion has completed within the heartbeat window, blink an error LED on PB7 rapidly
(potential sensor failure detection).
ADC Free-Running Mode with Watchdog Overflow Alert
1 # include < avr / io .h >
2 # include < avr / interrupt .h >
3
4 volatile uint8_t adc_fresh = 0;

— 15 —
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

5 volatile uint16_t last_adc = 0;


6 volatile uint8_t error_state = 0;
7
8 ISR ( ADC_vect ) {
9 last_adc = ADC ;
10 adc_fresh = 1; /* Mark data as new */
11
12 /* Timer1 overflow ~1 s ( prescaler 1024 , 16 - bit wraps at 65535)
13 65535 * 1024 / 8 e6 = 8.39 s ; use prescaler 256 for ~2 s */
14 ISR ( TIMER1_OVF_vect ) {
15 if (! adc_fresh ) {
16 error_state = 1; /* No ADC data : sensor fail */
17 } else {
18 error_state = 0;
19 adc_fresh = 0;
20 }
21 }
22

23 int main ( void ) {


24 DDRB |= (1 << PB7 ) ;
25
26 /* ADC : free - running , AVCC ref , channel 0 , interrupt , prescaler
64 */
27 ADMUX = (1 << REFS0 ) ;
28 ADCSRA = (1 << ADEN ) | (1 << ADIE ) | (1 << ADATE )
29 | (1 << ADPS2 ) | (1 << ADPS1 ) ;
30
31 /* Timer1 overflow interrupt , prescaler 256 */
32 TCCR1A = 0;
33 TCCR1B = (1 << CS12 ) ;
34 TIMSK |= (1 << TOIE1 ) ;
35
36 sei () ;
37 ADCSRA |= (1 << ADSC ) ; /* Start first conversion */
38
39 while (1) {
40 if ( error_state )
41 PORTB ^= (1 << PB7 ) ; /* Rapid blink on error */
42 else
43 PORTB &= ~(1 << PB7 ) ;
44 }
45 }

Servo Motor Control via ADC Control a servo motor using Timer1 in Fast PWM mode
(50 Hz, 20 ms period). Read ADC0 to set the servo angle. Map ADC (0–1023) to PWM
pulse width (1 ms to 2 ms ≡ 0° to 180°).
ICR1 sets the period; OCR1A sets the pulse width.
Servo Motor Control via ADC
1 # include < avr / io .h >
2 # include < avr / interrupt .h >
3
4 volatile uint16_t servo_pos = 1000; /* 1 ms default (0 deg ) */
5
6 ISR ( ADC_vect ) {
7 /* Map 0 -1023 -> 625 -1250 (1 ms -2 ms at prescaler 8 , 16 - bit )

— 16 —
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

8 At F_CPU =8 MHz , prescaler =8: 1 tick = 1 us


9 1 ms = 1000 ticks , 2 ms = 2000 ticks */
10 servo_pos = 1000 + ( ADC >> 1) ; /* 0..1023/2 -> 0..511 added */
11 OCR1A = servo_pos ;
12
13 /* Restart next conversion via free - running isn ’t used ;
14 manually restart for controlled rate */
15 ADCSRA |= (1 << ADSC ) ;
16
17 int main ( void ) {
18 /* PB1 ( OC1A ) output */
19 DDRB |= (1 << PB1 ) ;
20
21 /* ADC channel 0 , AVCC , interrupt */
22 ADMUX = (1 << REFS0 ) ;
23 ADCSRA = (1 << ADEN ) | (1 << ADIE )
24 | (1 << ADPS2 ) | (1 << ADPS1 ) ;
25

26 /* Timer1 Fast PWM , ICR1 top , prescaler 8


27 Period = ( ICR1 +1) *8/8 e6 = 20000*8/8 e6 = 20 ms = 50 Hz */
28 ICR1 = 19999;
29 OCR1A = 1000; /* Initial : 1 ms = 0 degrees */
30 TCCR1A = (1 << WGM11 ) | (1 << COM1A1 ) ;
31 TCCR1B = (1 << WGM13 ) | (1 << WGM12 ) | (1 << CS11 ) ;
32
33 sei () ;
34 ADCSRA |= (1 << ADSC ) ; /* Kick off first ADC */
35
36 while (1) {}
37 }

Servo pulse widths: 1 ms = 0°, 1.5 ms = 90°, 2 ms = 180° (typical). Always verify with
your specific servo datasheet.

ewpage UART-less ADC Data Logger (store to EEPROM) Sample ADC0 every second
(Timer1 CTC) and write 8-bit samples to EEPROM starting at address 0x00. After 50
samples, stop. A button on INT0 can restart logging (clears index).
Registers: EEAR, EEDR, EECR
UART-less ADC Data Logger (store to EEPROM)
1 # include < avr / io .h >
2 # include < avr / interrupt .h >
3 # include < avr / eeprom .h >
4
5 # define MAX_SAMPLES 50
6 volatile uint8_t sample_idx = 0;
7 volatile uint8_t logging = 1;
8
9 void e e p r o m _ w r i t e _ b y t e _ r e g ( uint16_t addr , uint8_t data ) {
10 while ( EECR & (1 << EEWE ) ) ; /* Wait for previous write */
11 EEAR = addr ;
12 EEDR = data ;
13 EECR |= (1 << EEMWE ) ; /* Master write enable */
14 EECR |= (1 << EEWE ) ; /* Start write */
15

— 17 —
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

16 ISR ( TIMER 1_COMP A_vect ) {


17 if (! logging || sample_idx >= MAX_SAMPLES ) {
18 logging = 0;
19 return ;
20 }
21 /* Start ADC conversion */
22 ADCSRA |= (1 << ADSC ) ;
23 }
24
25 ISR ( ADC_vect ) {
26 uint8_t val = ( uint8_t ) ( ADC >> 2) ; /* 10 - bit -> 8 - bit */
27 e e p r o m _ w r i t e _ b y t e _ r e g ( sample_idx , val ) ;
28 sample_idx ++;
29 if ( sample_idx >= MAX_SAMPLES ) logging = 0;
30 }
31
32 ISR ( INT0_vect ) {
33 sample_idx = 0; /* Reset index */
34 logging = 1;
35 }
36
37 int main ( void ) {
38 /* INT0 falling edge ( restart logging ) */
39 DDRD &= ~(1 << PD2 ) ;
40 PORTD |= (1 << PD2 ) ;
41 MCUCR |= (1 << ISC01 ) ;
42 GICR |= (1 << INT0 ) ;
43
44 /* ADC : AVCC ref , channel 0 , interrupt , prescaler 64 */
45 ADMUX = (1 << REFS0 ) ;
46 ADCSRA = (1 << ADEN ) | (1 << ADIE )
47 | (1 << ADPS2 ) | (1 << ADPS1 ) ;
48
49 /* Timer1 CTC , prescaler 1024 , 1 Hz
50 OCR1A = 8 e6 /1024 - 1 = 7811 */
51 TCCR1A = 0;
52 TCCR1B = (1 << WGM12 ) | (1 << CS12 ) | (1 << CS10 ) ;
53 OCR1A = 7811;
54 TIMSK |= (1 << OCIE1A ) ;
55
56 sei () ;
57

58 while (1) {
59 /* Low - power or other work can go here */
60 }
61 }

Full System: Smart Fan Controller Combine everything:


• ADC0: temperature sensor (NTC / LM35 equivalent).
• Timer2 Fast PWM (OC2): controls fan speed (duty = ADC/4).
• Timer0 overflow: 1-second display refresh.
• INT0: toggle fan ON/OFF override.
• PORTB LEDs: show speed zone (low/med/high).
Full System: Smart Fan Controller
1 # include < avr / io .h >

— 18 —
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

2 # include < avr / interrupt .h >


3
4 volatile uint16_t temp_val = 0;
5 volatile uint8_t fan_override = 0; /* 0= auto , 1= off */
6 volatile uint8_t refresh = 0;
7

8 /* ---- ISRs ---- */


9 ISR ( INT0_vect ) {
10 fan_override ^= 1; /* Toggle override */
11 if ( fan_override ) OCR2 = 0; /* Force fan off */
12
13 ISR ( TIMER0_OVF_vect ) {
14 static uint8_t ticks = 0;
15 if (++ ticks >= 30) { /* ~1 second at prescaler 1024 */
16 ticks = 0;
17 refresh = 1;
18 }
19 }
20
21 ISR ( ADC_vect ) {
22 temp_val = ADC ;
23 }
24
25 /* ---- Helpers ---- */
26 void update_leds ( uint16_t val ) {
27 PORTB &= 0 xF8 ; /* Clear PB0 - PB2 */
28 if ( val > 750) PORTB |= 0 x07 ; /* High : 3 LEDs */
29 else if ( val > 400) PORTB |= 0 x03 ; /* Med : 2 LEDs */
30 else if ( val > 150) PORTB |= 0 x01 ; /* Low : 1 LED */
31 }
32
33 int main ( void ) {
34 /* I / O setup */
35 DDRB |= 0 x07 ; /* PB0 - PB2 : LEDs */
36 DDRD |= (1 << PD7 ) ; /* OC2 ( fan PWM ) output */
37 DDRD &= ~(1 << PD2 ) ; /* PD2 : INT0 input */
38 PORTD |= (1 << PD2 ) ; /* Pull - up */
39
40 /* Timer2 Fast PWM , non - inv , prescaler 8 */
41 TCCR2 = (1 << WGM21 ) | (1 << WGM20 )
42 | (1 << COM21 )
43 | (1 << CS21 ) ;
44 OCR2 = 0;
45
46 /* Timer0 prescaler 1024 , overflow interrupt */
47 TCCR0 = (1 << CS02 ) | (1 << CS00 ) ;
48 TIMSK |= (1 << TOIE0 ) ;
49

50 /* INT0 falling edge */


51 MCUCR |= (1 << ISC01 ) ;
52 GICR |= (1 << INT0 ) ;
53
54 /* ADC free - running , channel 0 , AVCC , interrupt , /64 */
55 ADMUX = (1 << REFS0 ) ;
56 ADCSRA = (1 << ADEN ) | (1 << ADIE ) | (1 << ADATE )
57 | (1 << ADPS2 ) | (1 << ADPS1 ) ;
58
59 sei () ;

— 19 —
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

60 ADCSRA |= (1 << ADSC ) ; /* Start ADC */


61
62 while (1) {
63 if ( refresh ) {
64 refresh = 0;
65 if (! fan_override ) {
66 OCR2 = ( uint8_t ) ( temp_val >> 2) ; /* Auto speed */
67 }
68 update_leds ( temp_val ) ;
69 }
70 }
71 }

This capstone problem integrates: free-running ADC, dual timer interrupts, external interrupt
with override logic, PWM output, and multi-LED display — all operating concurrently
through the interrupt system.

ewpage

7. Quick Reference Summary

Prescaler vs. Timer Overflow Period (FCPU = 8 MHz)


Prescaler Tick period Timer0/2 OVF Timer1 OVF
1 125 ns 32 µs 8.19 ms
8 1 µs 256 µs 65.5 ms
64 8 µs 2.05 ms 524 ms
256 32 µs 8.19 ms 2.10 s
1024 128 µs 32.8 ms 8.39 s

CTC Compare Value Formula


FCP U
OCR = − 1 (for toggle on compare)
2 × prescaler × ftarget

ADC Voltage Calculation


ADC_result
Vin = × Vref (with 10-bit result, Vref = 5 V typically)
1024

— 20 —
ATmega32 Register-Level Programming in C IO • Interrupts • ADC

Interrupt Vector Names (AVR-GCC)


Interrupt ISR Vector Name
INT0 external INT0_vect
INT1 external INT1_vect
Timer0 overflow TIMER0_OVF_vect
Timer0 compare A TIMER0_COMP_vect
Timer1 overflow TIMER1_OVF_vect
Timer1 compare A TIMER1_COMPA_vect
Timer1 capture TIMER1_CAPT_vect
Timer2 overflow TIMER2_OVF_vect
ADC complete ADC_vect

Happy hacking with your ATmega32!


“The best way to learn embedded systems is to break something and fix it.”

— 21 —

You might also like