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.
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.
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.
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.