Skip to main content

Microcontrollers

Section 8.1 Interrupts on the ATmega328P

The ATmega328P has many different interrupts that can be utilized depending on the application. Some of these interrupts can be configured to take place at particular timed intervals using the timer/counter unit (as introduced in ChapterΒ 9). Other interrupts correspond to different microcontroller peripherals. For example, an ADC interrupt can be configured to trigger when the ADC on the ATmega328P completes a conversion.
When an ISR is invoked, the microcontroller completes the following operations.

Subsection 8.1.1 Interrupt Vector Table

The ATmega328P has 26 different interrupt sources, listed in TableΒ 8.1.1. This interrupt vector table defines the address in program memory where the pointer to each interrupt service routine will be stored. For example, if the PCINT0 interrupt service routine is stored in program memory at a starting address of 0x0AF2, that address value (0x0AF2) will be stored in program memory at location 0x0006. When that interrupt is triggered, the program counter will load the address stored in location 0x0006 and execute the instructions at that address. The end of an interrupt should always contain a return from interrupt (RETI) instruction, causing the program counter to go back to its previous location.
Table 8.1.1. ATmega328P interrupt vector table
Vector Address Source Description
1 0x0000 RESET
External pin, power-on reset
2 0x0002 INT0
External interrupt request 0
3 0x0004 INT1
External interrupt request 1
4 0x0006 PCINT0
Pin change interrupt request 0
5 0x0008 PCINT1
Pin change interrupt request 1
6 0x000A PCINT2
Pin change interrupt request 2
7 0x000C WDT
Watchdog time-out interrupt
8 0x000E TIMER2 COMPA
Timer/counter 2 compare match A
9 0x0010 TIMER2 COMPB
Timer/counter 2 compare match B
10 0x0012 TIMER2 OVF
Timer/counter 2 overflow
11 0x0014 TIMER1 CAPT
Timer/counter 1 capture event
12 0x0016 TIMER1 COMPA
Timer/counter 1 compare match A
13 0x0018 TIMER1 COMPB
Timer/counter 1 compare match B
14 0x001A TIMER1 OVF
Timer/counter 1 overflow
15 0x001C TIMER0 COMPA
Timer/counter 0 compare match A
16 0x001E TIMER0 COMPB
Timer/counter 0 compare match B
17 0x0020 TIMER0 OVF
Timer/counter 0 overflow
18 0x0022 SPI STC
SPI serial transfer complete
19 0x0024 USART RX
USART Rx complete
20 0x0026 USART UDRE
USART data register empty
21 0x0028 USART TX
USART Tx complete
22 0x002A ADC
ADC conversion complete
23 0x002C EE READY
EEPROM ready
24 0x002E ANALOG COMP
Analog comparator
25 0x0030 TWI
2-wire serial interface
26 0x0032 SPM READY
Store program memory ready
If an interrupt is enabled, but no ISR is defined, the program counter will still load the address stored in the corresponding memory location. It’s possible that the value stored in that memory location will lead to an area of program memory with no legitimate code to execute. This can cause a program to stop working once the interrupt is triggered. If an interrupt is enabled, the corresponding ISR must be defined in software.
The priority of interrupt servicing is defined by the interrupt vector table. If two or more interrupts are triggered at the exact same instant, the interrupt with the lowest vector number will be serviced first, followed by the next highest vector, and so on, until all interrupts have been serviced.

Subsection 8.1.2 The Reset Interrupt

Reset is a special category of interrupt on the ATmega328P. It is the highest priority ISR and cannot be disabled.
There are four different reset sources on the ATmega328P microcontroller. The source of the most recent reset can be determined by reading information from the MCU status register (MCUSR).
  • Power-on: this reset source occurs after power has been disconnected and then reconnected to the microcontroller.
  • External: this reset source occurs if the active-LOW reset pin (PC6) is asserted.
  • Watchdog: this reset source occurs if the watchdog timer (SectionΒ 9.8) times out.
  • Brownout: this reset source occurs if VCC dips below the brownout detection level established in the extended fuse byte (as introduced in SectionΒ 4.7).

Subsection 8.1.3 Masking (Disabling) and Unmasking (Enabling) Interrupts

At times, it is useful to mask (disable) or unmask (enable) interrupts. Interrupts can be globally masked by clearing the global interrupt enable flag (I) in SREG, as described in SubsectionΒ 4.3.1. (This process occurs automatically when an ISR is invoked on the ATmega328P to prohibit one interrupt from interrupting another.) Interrupts can be globally enabled by setting the global interrupt enable flag in SREG. (This occurs automatically after an ISR has concluded upon receiving the RETI instruction.)
Some interrupts are nonmaskable. The reset interrupt is nonmaskable; it cannot be masked even if the I flag in SREG is cleared.
It can be important to globally mask interrupts while performing particular tasks that cannot be interrupted. These tasks include configuring peripherals (such as the ADC, timer/counters, external interrupts, and pin-change interrupts) and when reading from or writing to the β€œ16-bit” registers on the ATmega328P (including TCNT1 and other registers associated with timer/counter 1). The following code demonstrates masking interrupts in C.
int main(void) {
    cli(); // globally mask interrupts
    // peripheral configuration code
    sei(); // globally enable interrupts

    while (1) {
        // program code
    }
}
Each individual interrupt can be enabled or disabled using its corresponding configuration or setup register. For example, enabling the ADC interrupt requires setting the ADC interrupt enable (ADIE) bit in the ADC control and status register A (ADCSRA).

Subsection 8.1.4 Interrupt Servicing

As previously mentioned, an ISR is serviced in order of priority, with lower vector locations being serviced first if two or more interrupts are invoked simultaneously. When the first ISR concludes, the ISR with the next level of priority will be invoked, and so on until all interrupts that were triggered have been serviced.
If an interrupt is triggered when another interrupt is being serviced, a status flag will be set to notify the microcontroller to service it once the current interrupt has concluded. Because the I flag in SREG is automatically cleared upon entering an ISR, that means that an interrupt will not be serviced while another interrupt is being serviced. (This can be manually overridden but is strongly discouraged.) If the same interrupt is triggered more than once when interrupts are globally masked (either because the cli() function was used or because an interrupt is currently being serviced), that interrupt will only be serviced once when interrupts are globally enabled again.
Because interrupts are typically used to temporarily halt normal operations to deal with an important event, and because it would be bad for the same interrupt to be triggered multiple times while interrupts are globally masked, it’s important to ensure that each ISR is written to execute as quickly as possible. It’s also important to ensure that interrupts will not be globally masked for long periods of time as program code executes. If one or more ISR requires extensive processing tasks, a backlog of interrupts can pile up, with repeated interrupts unable to be serviced at all. This can cause cascading errors with program operation.
It’s important to note that the value of SREG is not automatically stored when an ISR is invoked by an interrupt request. This means that any critical functionality of the microcontroller (for example, performing conditional and/or Boolean operations to execute control flow logic) will be affected. The value of SREG must therefore be stored upon executing an ISR and retrieved upon concluding an ISR.