Whenever a hardware interrupt occurs on the ATmega32, the appropriate instruction in the jump vector is executed. Recall that the C compiler places jumps to _bad_interrupt in all of the jump vectors for unused interrupts. In order to use interrupts in C we must:
- Write an ISR (interrupt service routine).
- Initialize the appropriate jump vector to jump to the ISR.
- Enable the desired interrupt.
- Setting the global interrupt flag.
Setting the global interrupt flag is done with the sei() "function" defined in <avr/interrupt.h>. Enabling the desired interrupt is accomplished by setting the appropriate flag in the control register for the desired interrupt. The syntax for defining the ISR and initializing the appropriate jump vector is described below.
Defining ISR and Initializing Jump Vector
The <avr/interrupt.h> header file defines a macro, ISR(interrupt_vector), for initializing the jump vector and specifying the code to be executed when the interrupt occurs.
The interrupt_vector must be one of the following (defined in avr/iom32.h, which is included by avr/io.h):
Interrupt type | Vector Name | Vector Number |
---|---|---|
External Interrupt Request 0 | INT0_vect | 1 |
External Interrupt Request 1 | INT1_vect | 2 |
External Interrupt Request 2 | INT2_vect | 3 |
Timer/Counter2 Compare Match | TIMER2_COMP_vect | 4 |
Timer/Counter2 Overflow | TIMER2_OVF_vect | 5 |
Timer/Counter1 Capture Event | TIMER1_CAPT_vect | 6 |
Timer/Counter1 Compare Match A | TIMER1_COMPA_vect | 7 |
Timer/Counter1 Compare Match B | TIMER1_COMPB_vect | 8 |
Timer/Counter1 Overflow | TIMER1_OVF_vect | 9 |
Timer/Counter0 Compare Match | TIMER0_COMP_vect | 10 |
Timer/Counter0 Overflow | TIMER0_OVF_vect | 11 |
Serial Transfer Complete | SPI_STC_vect | 12 |
USART, Rx Complete | USART_RXC_vect | 13 |
USART Data Register Empty | USART_UDRE_vect | 14 |
USART, Tx Complete | USART_TXC_vect | 15 |
ADC Conversion Complete | ADC_vect | 16 |
EEPROM Ready | EE_RDY_vect | 17 |
Analog Comparator | ANA_COMP_vect | 18 |
2-wire Serial Interface | TWI_vect | 19 |
Store Program Memory Ready | SPM_RDY_vect | 20 |
Example Code
Consider the following example program.
#include <avr/io.h> #include <avr/interrupt.h> uint8_t portVal = 128; int main() { sei(); DDRB = 0x00; DDRC = 0xff; while(1) { PORTC = portVal; } return 0; } ISR(TIMER0_OVF_vect) { portVal = PINB; }
- This code won't really do anything useful because we didn't set up the Timer/Counter0 subsystem.
- If the Timer/Counter0 subsystem was set up correctly, this program would loop forever in main constantly sending the value of portVal to PORTC.
- The value of portVal should be updated every time the Timer/Counter0 subsystem overflows.
- Here are relevant pieces of the .lss file:
00000000 <__vectors>: 0: 0c 94 2a 00 jmp 0x54 ; 0x54 <__ctors_end> 4: 0c 94 47 00 jmp 0x8e ; 0x8e <__bad_interrupt> 8: 0c 94 47 00 jmp 0x8e ; 0x8e <__bad_interrupt> c: 0c 94 47 00 jmp 0x8e ; 0x8e <__bad_interrupt> 10: 0c 94 47 00 jmp 0x8e ; 0x8e <__bad_interrupt> 14: 0c 94 47 00 jmp 0x8e ; 0x8e <__bad_interrupt> 18: 0c 94 47 00 jmp 0x8e ; 0x8e <__bad_interrupt> 1c: 0c 94 47 00 jmp 0x8e ; 0x8e <__bad_interrupt> 20: 0c 94 47 00 jmp 0x8e ; 0x8e <__bad_interrupt> 24: 0c 94 47 00 jmp 0x8e ; 0x8e <__bad_interrupt> 28: 0c 94 47 00 jmp 0x8e ; 0x8e <__bad_interrupt> 2c: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__vector_11> 30: 0c 94 47 00 jmp 0x8e ; 0x8e <__bad_interrupt> 34: 0c 94 47 00 jmp 0x8e ; 0x8e <__bad_interrupt> 38: 0c 94 47 00 jmp 0x8e ; 0x8e <__bad_interrupt> 3c: 0c 94 47 00 jmp 0x8e ; 0x8e <__bad_interrupt> 40: 0c 94 47 00 jmp 0x8e ; 0x8e <__bad_interrupt> 44: 0c 94 47 00 jmp 0x8e ; 0x8e <__bad_interrupt> 48: 0c 94 47 00 jmp 0x8e ; 0x8e <__bad_interrupt> 4c: 0c 94 47 00 jmp 0x8e ; 0x8e <__bad_interrupt> 50: 0c 94 47 00 jmp 0x8e ; 0x8e <__bad_interrupt> <snip> 00000092 <main>: 92: 78 94 sei 94: 17 ba out 0x17, r1 ; 23 96: 8f ef ldi r24, 0xFF ; 255 98: 84 bb out 0x14, r24 ; 20 9a: 80 91 60 00 lds r24, 0x0060 9e: 85 bb out 0x15, r24 ; 21 a0: fe cf rjmp .-4 ; 0x9e <main+0xc> 000000a2 <__vector_11>: a2: 1f 92 push r1 a4: 0f 92 push r0 a6: 0f b6 in r0, 0x3f ; 63 a8: 0f 92 push r0 aa: 11 24 eor r1, r1 ac: 8f 93 push r24 ae: 86 b3 in r24, 0x16 ; 22 b0: 80 93 60 00 sts 0x0060, r24 b4: 8f 91 pop r24 b6: 0f 90 pop r0 b8: 0f be out 0x3f, r0 ; 63 ba: 0f 90 pop r0 bc: 1f 90 pop r1 be: 18 95 reti
- The jump vector (#11) for the timer/counter0 overflow interrupt has been initialized to jump to the ISR (called _vector_11).
- The main function sends 0 to DDRB and 0xFF to DDRC, reads the value of portVal from SRAM location 0x60 and then continuously sends it out to PORTC.
- The ISR reads the value on PINB into R24 and then writes it to portVal (SRAM location 0x60).
- Unfortunately, this program will always send the same value to PORTC regardless of the value found on PINB and how frequently the ISR runs. Can you see why?
Declaring a Variable as Volatile
- We have already seen situations where the compiler chooses to optimize our code by elimating variables that have an unchanging value and replacing them with the constant value.
- When the compiler generates code for the main function, it is not considering the possibility that the main function may be interrupted and an ISR run.
- In the above example, the compiler has no reason to believe that the value of portVal would change inside the while loop.
- Therefore, the code generated for the loop consists of continuously writing the value stored in R24 to PORTC.
- However, the ISR may change the value of portVal (storing a new value in SRAM location 0x60).
- We can tell the compiler not to "cache" a variables value by making use of the keyword volatile.
- Declaring a variable as volatile tells the compiler that it must read the actual value of the variable every time the variable is referenced.
- By declaring portVal as volatile in the previous example, we ensure that we get the most up-to-date value.
Example Code Revisited
Changing the declaration of portVal in the previous example to this:
volatile uint8_t portVal = 128;
Results in a minor change to the machine code generated for main:
00000092 <main>: 92: 78 94 sei 94: 17 ba out 0x17, r1 ; 23 96: 8f ef ldi r24, 0xFF ; 255 98: 84 bb out 0x14, r24 ; 20 9a: 80 91 60 00 lds r24, 0x0060 9e: 85 bb out 0x15, r24 ; 21 a0: fc cf rjmp .-8 ; 0x9a <main+0x8>
- Notice that the rjmp now jumps back 8 bytes instead of 4.
- As a result, the while loop now consists of:
- Reading the current value of portVal from location 0x60 in SRAM.
- Writing the value to PORTC.
- This ensures that PORTC will get the most current value of portVal.