7.1 Interrupt Code (Mid-range)

The example shown in this chapter shows how to write an interrupt service routine (ISR).

An ISR is written in the same as any other routine, except it must:
  • Have its entry point linked to the address of the relevant interrupt vector,
  • Ensure that the content of any registers it uses and which are also used by main-line code are preserved,
  • Ensure that only the relevant code is executed for each interrupt source, and
  • Execute a special return-from-interrupt instruction to end the routine and interrupt processing.
These points are discussed below for the ISR in this example, which has been repeated below.
PSECT isrVec,class=CODE,delta=2
isr:
    ;no context save required in software for this device
    PAGESEL     $           ;select this page for the following goto
    BANKSEL     PIE0        ;for TMR0IE and TMR0IF
    ;for timer interrupts, set the required LED state
    btfsc       TMR0IE
    btfss       TMR0IF
    goto        notTimerInt ;not a timer interrupt
    bcf         TMR0IF
    ;toggle the desired bit state
    movlw       1 shl (LEDState&7)
    BANKSEL     LEDState/8
    xorwf       BANKMASK(LEDState/8),f
notTimerInt:
    ;code to handle other interrupts could be added here
exitISR:
    ;no context restore required in software
    retfie

The entry point to an ISR, like that of the code executed after Reset, must be linked to the appropriate interrupt vector address for the target device. To do this, the ISR, or at least the code containing the entry point to the ISR, should be placed in a unique psect that can be linked to the required address. In this example the isrVec psect holds the entire interrupt routine. Some PIC18 devices can employ an interrupt vector table and have different linking requirements for ISRs, as described in section Interrupts and Bits Example For PIC18 Devices.

Any registers that are modified by an ISR (and any routines they call) and that are also used in main-line code must be saved on entry to the ISR and then restored on exit. For devices that support more than one interrupt vector (e.g. PIC18 devices), an ISR will need to save any registers that are modified but not saved by other ISRs. The code that saves and restores registers is often called context switch code.

Like many modern PIC devices, the 16F18446 automatically saves the state of core registers into shadow registers when an interrupt occurs, so context switching does not usually need to be performed in software for these devices, unless there are other registers or objects that the ISR should not leave modified. The example code above includes no context switch code at all, instead it immediately process the interrupt. If you need to write context switch code, see section Manual Context Switch.

The ISR shown here toggles the desired state of the LED when a timer 0 interrupt occurs. It performs no action for any other interrupt source, but you could add code to this ISR to process as many interrupt sources as required. The btfss TMR0IF instruction checks the relevant timer interrupt flag to ensure that the timer 0 has triggered, but it does this in conjunction with the btfsc TMR0IE instruction, which checks the timer 0 interrupt enable bit. Only when both of these bits are set can you be sure that timer 0 is the source of the interrupt.

To terminate execution of the interrupt code and return to main-line code, use a retfie instruction, as shown. A regular return instruction will not return the state of the device to that when the interrupt occurred and will lead to code failure.

Since this ISR contains a goto instruction, a PAGESEL directive was added to ensure that the page that contains the ISR is selected and that the goto will reach the intended destination.