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.
-
Finish executing the current instruction.
-
Save the address of the next instruction in the stack (SectionΒ 13.5).
-
Jump to interrupt vector address (TableΒ 8.1.1).
-
Load memory address of the associated ISR into the program counter.
-
Run the ISR subroutine until its end (
RETIinstruction). -
Load the memory address to return to from the stack into the program counter
-
Execute the next instruction.
-
Then, either continue to execute regular program flow instructions (if no interrupts were triggered while the interrupt was being serviced), or service the next interrupt.
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.
| 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.
