Using Interrupts in C

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:

  1. Write an ISR (interrupt service routine).
  2. Initialize the appropriate jump vector to jump to the ISR.
  3. Enable the desired interrupt.
  4. 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 0×60 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 0×60).
  • 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 0×60).
  • 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:
    1. Reading the current value of portVal from location 0×60 in SRAM.
    2. Writing the value to PORTC.
  • This ensures that PORTC will get the most current value of portVal.
ce2810/interrupts.txt · Last modified: 2009/06/03 11:22 (external edit)
 

This website is not owned or managed by the Milwaukee School of Engineering.

© 2003-2010 Dr. Christopher C. Taylor, et. al. • Office: L-343 • Phone: 277-7339 • npǝ˙ǝosɯ@ɹolʎɐʇ • -> RSS <-