8.1 Interrupt Code (PIC18)

Much of the discussion in section Interrupt Code (Mid-range) also applies to PIC18 interrupt code, and that information is not repeated here. In this section, PIC18-specific details concerning interrupts are discussed.

Some PIC18 devices, such as the PIC18F47K42 device used in this example, can be configured to use a table of interrupt vectors rather than two fixed interrupt vector locations. If your PIC18 device does not support vector tables or you are using it in legacy mode, you will need to define one or two interrupt service routines (one for each interrupt priority being used), whose entry points are linked to the interrupt vector addresses. This approach is similar to that shown in the Mid-range example; however, Mid-range devices only support one interrupt vector location.

The example in this chapter assumes the vectored interrupt controller is being used. Turning on the MVECEN configuration bit enables this mode of operation.

When using vector tables, one stand-alone interrupt service routine (ISR) is written for each interrupt source to be handled. A vector table containing the addresses of these ISRs is created and located in memory. The position of the vector within the table determines which source it handles. The base address of the table must be stored in the IVTBASE registers so the device will load the correct interrupt vector when an interrupt occurs. The content of the IVTBASE registers can be changed at runtime, meaning that a program can define more than one interrupt vector table, hence different ISRs can be selected at different points in the program.

The interrupt code in this example, and which is repeated here, shows one ISR (tmr0Isr) and its corresponding address in the vector table, whose base address is ivtbase.
;vector table
PSECT ivt,class=CODE,reloc=2,ovrld
ivtbase:
    ORG         31*2            ;timer 0 vector position
    DW          tmr0Isr shr 2   ;timer 0 ISR address shifted right

;one ISR to handle timer0
PSECT textISR,class=CODE,reloc=4
tmr0Isr:
    bcf         TMR0IF	    ;clear the timer interrupt flag
    ;toggle the desired LED state
    movlw       1 shl (LEDState&7)
    xorwf       LEDState/(0+8),c
    
    retfie      f

The ISR to handle the timer 0 in this example uses the label tmr0Isr. Although the psect in which this symbol was placed can be linked anywhere in the program memory, the ISR entry point must be at an address that is a multiple of 4. This is due to the PIC18 device left shifting the value stored in the vector table by two bits to form the ISR address it will jump to. To ensure the ISR is located correctly, the psect it is placed in has a reloc value set to 4, which ensure it will begin on an address with a multiple of 4.

A retfie f instruction was used to return from the interrupt. Like many modern devices, the PIC18F47K42 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, 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 f operand to the retfie instruction indicates the fast form of a return from interrupt, which will copy the content of the shadow registers back to the original registers.

The vector table was placed into a user-defined psect called ivt, which can then be linked independently to other psects in the program memory, as shown in section Building the Example. The reloc=2 flag was used to ensure that the psect's starting address is word aligned for correct operation, as described in the device data sheet. The use of the ovrld psect flag is explained later.

The assembler's DW directive was used to place the address of the ISR, tmr0Isr, into the vector table. The device expects this address to be shifted right by 2 bits, so the assembler's shr operator was used to obtain this value. The timer 0 interrupt vector must be located at position #31 in the table; however, the table itself can be moved around in program memory, so there is no absolute location for this (or any other) vector. To make it easier to modify the program, an ORG directive was used to locate the vector. This is one instance where the use of this directive is recommended. Note that the ORG directive moves the location counter relative to the start of the current psect, not to an absolute location, but that is exactly what is required in this situation. Provided the psect holding the vector table is linked to the required address, the ORG directive will ensure that the vector is at the correct offset within that the table. The vector is offset by the amount 31 * 2, since each vector is two bytes wide.

As the program is developed and other interrupts are required, additional ISRs can be written and the addresses of these routines added to the vector table. The psect holding the vector table can be built up in sections and across multiple files. You might prefer to supply one ISR and its vector table contribution together in a source file of their own.

To ensure that the vectors in the psect appear in the correct order regardless of where and when each vector is added, the ovrld flag was used with the ivt psect. This flag tells the linker to overlay, rather than concatenate, each contribution to the psect. For example, the following additional code, which could be located in a different source file, places the vector for a UART1 receive interrupt routine.
PSECT ivt,class=CODE,reloc=2,ovrld
    ORG         27*2          ;UART1 receive vector position
    DW          uart1Isr shr 2

PSECT textISR,class=CODE,reloc=4
uart1Isr:
    ;code to process uart1 receive goes here
    retfie      f

Note that this vector is placed in the same psect that was used to hold the vector for the timer interrupt (ivt). Since the ovrld flag has been used with this psect, the ORG directive will again move the address that follows relative to the start address of the psect, even if the content of ivt psect from the first example has already been processed. In this way, you do not need to ensure that the code that defines the vectors are processed in any particular order.

If you are checking the vector table psect in the map file, you should see the Link address indicate the address at which it was placed (in the example below, address 8) and the length will include any gaps introduced by the ORG directive, or directives. Even though there is only one vector in the table in this example, the size has been shown as 0x40, due to the fact that the timer 0 vector is located at an offset of 31 * 2 from the beginning of the psect and is 2 bytes long.

TOTAL         Name                     Link     Load   Length     Space
  CLASS   CODE
              ivt                         8        8       40         0

Even if your project does not need interrupts from other sources, it is advisable to consider what will happen should any of these interrupt sources inadvertently trigger. You might want to populate the unused vector table locations with the address of a default ISR that can handle these cases.

The vector table can be positioned (and repositioned) in program memory by writing the appropriate table address to the IVTBASE registers. To avoid having to modify your source code should you decide to link the vector table to a different address, you can load the IVTBASE registers with special symbols that are defined by the linker. The following extract from the example shows the IVTBASE unlock sequence (described in the device data sheet) and all three IVTBASE registers being loaded.
GLOBAL __Livt                 ;defined by the linker but used in this code
...
    movlw       0x55
    movwf       BANKMASK(IVTLOCK),c
    movlw       0xAA
    movwf       BANKMASK(IVTLOCK),c
    bcf         IVTLOCKED
    movlw       low highword __Livt
    movwf       BANKMASK(IVTBASEU),c
    movlw       high __Livt
    movwf       BANKMASK(IVTBASEH),c
    movlw       low __Livt
    movwf       BANKMASK(IVTBASEL),c

For any psect allocated memory using a -p linker option, the linker defines special symbols that represent where in memory those psects were linked. The starting address of such psects are represented by a symbol with the form __Lpsectname (note the two leading underscore characters). (The linker also defines __Hpsectname and __Bpsectname symbols.) In this example, the __Livt symbol (the lower bound of the ivt psect) has been used to load the IVTBASE registers. The low, high, and highword operators have been used to obtain the appropriate byte of this full address for each of the registers. Such code does not need to be modified if at a later time you wish to change the location of the vector table; simply relink the program with a different ivt address.