4.8.1 Writing an Interrupt Service Routine
Observe the following guidelines when writing an ordinary ISR.
- Write each ISR prototype by enabling the CCI (3.6.3.4 Ext Option) and using the
__interrupt()
specifier. This will create a function with the appropriate name, prototype, and attributes. - If necessary, clear the relevant interrupt flag once the source has been processed, although typically this is not required.
- Only re-enable interrupts inside the ISR body if absolutely necessary. Interrupt are re-enabled automatically when the ISR returns.
- Keep the ISR as short and as simple as possible. Complex code will typically use more registers that will increase the size of the context switch code.
The compiler will process interrupt functions differently to other functions, generating code to save and restore any registers used by the function and using a special instruction to return.
The hardware globally disables interrupts when an interrupt is executed.
Usually, each interrupt source has a corresponding interrupt flag bit, accessible in a control register. When set, these flags indicate that the specified interrupt condition has been met. Interrupt flags are often cleared in the course of processing the interrupt, either when the handler is invoked or by reading a particular hardware register; however, there are a few instances when the flag must be cleared manually by code. Failure to do so might result in the interrupt triggering again as soon as the current ISR returns.
TIFR = _BV(TOV0);
which is guaranteed to clear the TOV0 bit and leave the remaining bits untouched.
void __interrupt(SPI_STC_vect_num) spi_Isr(void) {
process(SPI_ClientReceive());
return;
}
Note that the argument to the __interrupt()
specifier is
a vector number, which are available as macros ending with vect_num
once
you have included <xc.h>
in your program.
More complex interrupt function definitions can be created using macros
and attributes defined by <avr/interrupt.h>
, which are shown in the
following examples.
EMPTY_INTERRUPT()
macro and an interrupt source
argument.#include <avr/interrupt.h>
EMPTY_INTERRUPT(INT2_vect);
BADISR_vect
, can be
used with the ISR()
macro to define a function that can process any
otherwise undefined interrupts. Without this function defined, an undefined interrupt will
trigger a device reset. For
example:ISR(BADISR_vect) {
// place code to process undefined interrupts here
return;
}
sei
instruction to your ISR to re-enable the global interrupt flag;
however, there is an argument you can use with the ISR()
macro to have
this instruction added by the compiler to the beginning of the interrupt routine. For
example:ISR(IO_PINS_vect, ISR_NOBLOCK)
{ ... }
The
ISR_NOBLOCK
expands to __attribute__((interrupt))
,
which can be used instead.__interrupt()
) for one vector then
reuse that ISR for other vector definitions using the ISR_ALIASOF()
argument.void __interrupt(PCINT0_vect_num)
{ ... }
ISR(PCINT1_vect, ISR_ALIASOF(PCINT0_vect));
ISR_NAKED
argument, as shown in this
example.ISR(TIMER1_OVF_vect, ISR_NAKED)
{
PORTB |= _BV(0); // results in SBI which does not affect SREG
reti();
}
Note that the compiler will not generate any context switch code,
including the final return from interrupt instruction, so you must write any relevant
switching code and the reti
instruction. The SREG register must be
manually saved if it is modified by the ISR, and the compiler-implied assumption of
__zero_reg__
always being 0 could be wrong, for example when an
interrupt occurs right after a mul
instruction.