Interrupts and 16-bit Timer/Counter 1: ATmega Interrupts

READING

The AVR Microcontroller and Embedded Systems using Assembly and C
by Muhammad Ali Mazidi, Sarmad Naimi, and Sepehr Naimi

Sections: 10.1, 10.4

Table of Contents

Interrupt Basics

Figure 1: Outline of Instructions

  • A microcontroller normally executes instructions in an orderly fetch-execute sequence as dictated by a user-written program.
  • However, a microcontroller must also be ready to handle unscheduled, events that might occur inside or outside the microcontroller.
  • The interrupt system onboard a microcontroller allows it to respond to these internally and externally generated events. By definition we do not know when these events will occur.
  • When an interrupt event occurs, the microcontroller will normally complete the instruction it is currently executing and then transition program control to an Interrupt Service Routine (ISR). These ISR, which handles the interrupt.
  • Once the ISR is complete, the microcontroller will resume processing where it left off before the interrupt event occurred.

Figure 2: Outline of Instructions with an Interrupt Service Routine

The Main Reasons You Might Use Interrupts

  • To detect pin changes (eg. rotary encoders, button presses)
  • Watchdog timer (eg. if nothing happens after 8 seconds, interrupt me)
  • Timer interrupts – used for comparing/overflowing timers
  • SPI data transfers
  • I2C data transfers
  • USART data transfers
  • ADC conversions (analog to digital)
  • EEPROM ready for use
  • Flash memory ready

ATmega328P Interrupt Vector Table

  • The ATmega328P provides support for 25 different interrupt sources. These interrupts and the separate Reset Vector each have a separate program vector located at the lowest addresses in the Flash program memory space.
  • The complete list of vectors is shown in Table 11-6 “Reset and Interrupt Vectors in ATMega328P. Each Interrupt Vector occupies two instruction words.
  • The list also determines the priority levels of the different interrupts. The lower the address the higher is the priority level. RESET has the highest priority, and next is INT0 – the External Interrupt Request 0.

ATmega328P Interrupt Vector Table

Vector No Program Address Source Interrupt Definition Arduino/C++ ISR() Macro Vector Name Assembly Name
1 0x0000 RESET Reset
2 0x0002 INT0 External Interrupt Request 0 (pin D2) (INT0_vect) INT0addr
3 0x0004 INT1 External Interrupt Request 1 (pin D3) (INT1_vect) INT1addr
4 0x0006 PCINT0 Pin Change Interrupt Request 0 (pins D8 to D13) (PCINT0_vect) PCI0addr
5 0x0008 PCINT1 Pin Change Interrupt Request 1 (pins A0 to A5) (PCINT1_vect) PCI1addr
6 0x000A PCINT2 Pin Change Interrupt Request 2 (pins D0 to D7) (PCINT2_vect) PCI2addr
7 0x000C WDT Watchdog Time-out Interrupt (WDT_vect) WDTaddr
8 0x000E TIMER2 COMPA Timer/Counter2 Compare Match A (TIMER2_COMPA_vect) OC2Aaddr
9 0x0010 TIMER2 COMPB Timer/Counter2 Compare Match B (TIMER2_COMPB_vect) OC2Baddr
10 0x0012 TIMER2 OVF Timer/Counter2 Overflow (TIMER2_OVF_vect) OVF2addr
11 0x0014 TIMER1 CAPT Timer/Counter1 Capture Event (TIMER1_CAPT_vect) ICP1addr
12 0x0016 TIMER1 COMPA Timer/Counter1 Compare Match A (TIMER1_COMPA_vect) OC1Aaddr
13 0x0018 TIMER1 COMPB Timer/Counter1 Compare Match B (TIMER1_COMPB_vect) OC1Baddr
14 0x001A TIMER1 OVF Timer/Counter1 Overflow (TIMER1_OVF_vect) OVF1addr
15 0x001C TIMER0 COMPA Timer/Counter0 Compare Match A (TIMER0_COMPA_vect) OC0Aaddr
16 0x001E TIMER0 COMPB Timer/Counter0 Compare Match B (TIMER0_COMPB_vect) OC0Baddr
17 0x0020 TIMER0 OVF Timer/Counter0 Overflow (TIMER0_OVF_vect) OVF0addr
18 0x0022 SPI, STC SPI Serial Transfer Complete (SPI_STC_vect) SPIaddr
19 0x0024 USART, RX USART, Rx Complete (USART_RX_vect) URXCaddr
20 0x0026 USART, UDRE USART, Data Register Empty (USART_UDRE_vect) UDREaddr
21 0x0028 USART, TX USART, Tx Complete (USART_TX_vect) UTXCaddr
22 0x002A ADC ADC Conversion Complete (ADC_vect) ADCCaddr
23 0x002C EE READY EEPROM Ready (EE_READY_vect) ERDYaddr
24 0x002E ANALOG COMP Analog Comparator (ANALOG_COMP_vect) ACIaddr
25 0x0030 TWI 2-wire Serial Interface (I2C) (TWI_vect) TWIaddr
26 0x0032 SPM READY Store Program Memory Ready (SPM_READY_vect) SPMRaddr

ATmega328P Interrupt Processing

  • (1) When an interrupt occurs, (2) the microcontroller completes the current instruction and (3) stores the address of the next instruction on the stack
  • It also turns off the interrupt system to prevent further interrupts while one is in progress. This is done by (4) clearing the SREG Global Interrupt Enable I-bit.

    Figure 3: Status Register

  • The (5) Interrupt flag bit is cleared for Type 1 Interrupts only (see the next page for Type definitions).
  • The execution of the ISR is performed by (6) loading the beginning address of the ISR specific for that interrupt into the program counter. The AVR processor starts running the ISR.
  • (7) Execution of the ISR continues until the return from interrupt instruction (reti) is encountered. The (8) SREG I-bit is automatically set when the reti instruction is executed (i.e., Interrupts enabled).
  • When the AVR exits from an interrupt, it will always (9) return to the interrupted program and (10) execute one more instruction before any pending interrupt is served.
  • The Status Register is not automatically stored when entering an interrupt routine, nor restored when returning from an interrupt routine. This must be handled by software.

push reg_F
in reg_F,SREG
:
out SREG,reg_F
pop reg_F

By The Numbers

Figure 4: Interrupt Process Diagram

Type 1

  • The user software can write logic one to the I-bit to enable nested interrupts. All enabled interrupts can then interrupt the current interrupt routine.
    • The SREG I-bit is automatically set to logic one when a Return from Interrupt instruction – RETI – is executed.
  • There are basically two types of interrupts…
    • The first type (Type 1) is triggered by an event that sets the Interrupt Flag. For these interrupts, the Program Counter is vectored to the actual Interrupt Vector in order to execute the interrupt handling routine, and hardware clears the corresponding Interrupt Flag.
      • If the same interrupt condition occurs while the corresponding interrupt enable bit is cleared, the Interrupt Flag will be set and remembered until the interrupt is enabled, or the flag is cleared by software (interrupt cancelled).
      • Interrupt Flag can be cleared by writing a logic one to the flag bit position(s) to be cleared.
    • If one or more interrupt conditions occur while the Global Interrupt Enable (SREG I) bit is cleared, the corresponding Interrupt Flag(s) will be set and remembered until the Global Interrupt Enable bit is set on return (reti), and will then be executed by order of priority.

Type 2

  • The second type (Type 2) of interrupts will trigger as long as the interrupt condition is present. These interrupts do not necessarily have Interrupt Flags. If the interrupt condition disappears before the interrupt is enabled, the interrupt will not be triggered.

Figure 5: Type 2 Interrupt Process

When Writing an Interrupt Service Routine (ISR)

  • As a general rule get in and out of ISRs as quickly as possible. For example do not include timing loops inside of an ISR.
  • If you are writing an Arduino program
    • Don’t add delay loops or use function delay()
    • Don’t use function Serial.print(val)
    • Make variables shared with the main code volatile
    • Variables shared with main code may need to be protected by “critical sections” (see below)
    • Toggling interrupts off and on is not recommended. The default in the Arduino is for interrupts to be enabled. Don’t disable them for long periods or things like timers won’t work properly.

Program Initialization and the Interrupt Vector Table (IVT)

  • Start by jumping over the Interrupt Vector Table

RST_VECT:

rjmp reset

  • Add jumps in the IVT to your ISR routines

.ORG INT0addr // 0x0002 External Interrupt 0

jmp INT0_ISR

.ORG OVF1addr

jmp TOVF1_ISR

  • Initialize Variables, Configure I/O Registers, and Set Local Interrupt Flag Bits

reset:

lds r16, EICRA // EICRA Memory Mapped Address 0x69
sbr r16, 0b000000010
cbr r16, 0b000000001
sts EICRA, r16 // ISC0=[10] (falling edge)
sbi EIMSK, INT0 // Enable INT0 interrupts

  • Enable interrupts at the end of the initialization section of your code.

sei // Global Interrupt Enable

loop:

The Interrupt Service Routine (ISR)

; — Interrupt Service Routine —
INT0_ISR:

push reg_F
in reg_F,SREG
push r16
; Load
; Do Something
; Store
pop r16
out SREG,reg_F
pop reg_F
reti

; ——————————————————-

Predefined Arduino IDE Interrupts

  • When you push the reset button the ATmega328P automatically runs an Arduino Boot program located in a separate Boot Flash section at the top of program memory. If compiled within the Arduino IDE, the Boot program loads your compiled program with these interrupts enabled.
17 0x0020 TIMER0 OVF Timer/Counter0 Overflow (TIMER0_OVF_vect)
  • The millis() and micros() function calls make use of the “timer overflow” feature utilize timer 0. The ISR runs roughly 1000 times a second, and increments an internal counter which effectively becomes the millis() counter (see On your own question).
19 0x0024 USART, RX USART Rx Complete (USART_RX_vect)
21 0x0028 USART, TX USART, Tx Complete (USART_TX_vect)
  • The hardware serial library uses interrupts to handle incoming and outgoing serial data. Your program can now be doing other things while data in an SRAM buffer is sent or received. You can check the status of the buffer by calling the Serial.available() function.
  • On your own. Given that you are using 8-bit Timer/Counter 0, you have set TCCR0B bits CS02:CS01:CS00 = 0b011 (clkI/O/64), and the system clock fclk = 16 MHz, what value would you preload into the Timer/Counter Register TCNT0 to get a interrupt 1000 times a second.

Source: Gammon Software Solutions forumthis blog also covers how to work with all the interrupts in C++ and the Arduino scripting language.

Appendix

Programming the Arduino to Handle External Interrupts

  • Variables shared between ISRs and normal functions should be declared “volatile“. This tells the compiler that such variables might change at any time, and thus the compiler should not “optimize” the code by placing a copy of the variable in one of the general purpose processor registers (R31..R0). Specifically, the processor must reload the variable from SRAM whenever it is referenced.

int pin = 13;
volatile int state = LOW;

  • Add jumps in the IVT to ISR routine, configure External Interrupt Control Register A (EICRA), and enable local and global Interrupt Flag Bits.

void setup()
{
pinMode(pin, OUTPUT);
attachInterrupt(0, blink, CHANGE);
}

  • Write Interrupt Service Routine (ISR)

void blink()
{
state = !state;
}

  • To disable interrupts globally (clear the I bit in SREG) call the noInterrupts() function. To once again enable interrupts (set the I bit in SREG) call the interrupts() function.
  • Again – Toggling interrupts ON and OFF is not recommended. For a discussion of when you may want to turn interrupts off, read Gammon Software Solutions forum – Why disable Interrupts?