5.9.1.3 Writing Software Interrupt Vector Table ISRs
If your project is using software interrupt vector tables (software IVTs), observe the following guidelines when writing interrupt service routines (ISRs).
Write each required ISR using the __interrupt()
specifier. Use void
as the ISR return type and for the parameter specification. For faster interrupt response times, write a dedicated function for each interrupt source, although multiple sources can be processed by one function, if desired. Consider implementing default interrupt functions to handle accidental triggering of unused interrupts, or use the -mundefints
option to provide a default action (see Undefints Option).
Specify at least one set of peripheral flag bits that need to be tested for the ISR to be executed, using either the flags()
or __flags()
argument to the __interrupt()
specifier. The __flags()
argument typically consists of a comma-separated set of the peripheral bits that need to be set for the interrupt code to be executed. This list can optionally be followed by a comma and numerical value, used to specify a process order, with the lowest numbered ISR being the first whose peripheral bits are checked when an interrupt of the appropriate priority occurs, followed by the next highest numbered ISR etc.
If your device supports interrupt priorities, specify the interrupt priority assigned to the function’s source using any one of low_priority
or __low_priority
; or high_priority
or __high_priority
. If an ISR does not specify a priority, it will default to being high priority. It is recommended that you always specify the ISR priority when required to ensure your code is readable.
If your device supports priorities, at the appropriate point in your main-line code, assign the required priority to each interrupt source by writing the appropriate bits in the SFRs. Additionally, enable the interrupt sources required and the global interrupt enable.
void __interrupt(__flags(TMR0IE,TMR0IF), __high_priority) tc0Int(void) {
TMR0IF=0;
++tick_count;
return;
}
void __interrupt(__flags(TMR1IE,TMR1IF), __high_priority) tc1Int(void) {
TMR1IF=0;
tick_count += 100;
return;
}
Although these are written as separate ISRs, they are still both linked to the one vector, in this case the high priority vector. The compiler will output code to test the specified peripheral bits to determine the interrupt source, rather than this having to be written explicitly inside the ISR. So in the above example, the peripheral flags TMR0IE
and TMR0IF
must both be set for tc0Int()
to be executed, and the peripheral flags TMR1IE
and TMR1IF
must both be set for tc1Int()
to be executed. Since each ISR contains the code to process only one interrupt source, the number of registers that need to be saved and restored for each ISR will typically be less than the total number of registers that would need to be saved had all these functions been combined into one interrupt function not using the software interrupt vector table.__flags()
argument can be used multiple times with the one __interrupt
specifier. The following example shows a single interrupt function for a mid-range device designated to handle multiple interrupt sources.void __interrupt(__flags(PEIE,RCIE,RCIF), __flags(PEIE,TXIE,TXIF)) isr_usart(void) {
// process both RX and TX USART interrupts here
}
Here, either the peripheral flags PEIE
, RCIE
, and RCIF
must all be set, or the peripheral flags PEIE
, TXIE
, and TXIF
must all be set for the ISR isr_uart()
to be executed.__flags()
argument. If there is more than one set beginning with the same peripheral flag, then the sort is based on the second flag, etc. To change this process order, you can specify the order using a decimal number after the peripheral flag list. The lower the number, the earlier the check is made for that interrupt source. For example, the following code defines two ISRs, each using a process order.void __interrupt(__flags(PEIE,LCDIE,LCDIF,30)) isr_lcd(void) {
// process LCD interrupt here
}
void __interrupt(__flags(PEIE,TMR1IE,TMR1IF,10)) isr_tmr1(void) {
// process Timer1 interrupt here
}
The code generated by the compiler will check to see if isr_tmr1()
(process order set to 10) should be executed before it checks to see if isr_lcd()
(process order set to 30) needs to be executed, since the process order of isr_tmr1()
is lower. The process order assigned to an ISR must be unique. An error will be generated if that is not the case. Where a program contains a mixture of unspecified and user-specified ISR ordering, then the interrupt sources for the ISRs specifying an order value will be checked first, and the remaining sources will be subsequently checked in default ordering.Within each check, the order in which the peripheral flags are tested is typically determined by the order in which the individual peripheral flags are listed in the __flags()
argument. This might not be true if the same peripheral flag is used in multiple ISRs. In that case, it will be factored out and tested before other flags. Changing the order of the peripheral flags, hence the order in which they are checked, might affect the response time of the interrupt.
isr_tmr1()
, with its lower process order value, were checked first, followed by checks for the sources relating to isr_lcd()
. Within each of these checks, the flags were tested in the order in which they were specified in __flags()
. Note also, that the tests for the PEIE
bit, required by both ISRs, were factored out into one code sequence executed by both checks.; entry 0 : common bit-test (PEIE)
movlb 0
btfss 11,6 ; PEIE
ljmp sivt_entry_end_2
sivt_entry_end_0:
; entry 1 : PEIE,TMR1IE,TMR1IF,10 => _isr_tmr1
; PEIE already tested
movlb 1
btfss 17,0 ; TMR1IE
ljmp sivt_entry_end_1
movlb 0
btfss 17,0 ; TMR1IF
ljmp sivt_entry_end_1
ljmp _isr_tmr1
sivt_entry_end_1:
; entry 2 : PEIE,LCDIE,LCDIF,30 => _isr_lcd
; PEIE already tested
movlb 1
btfss 18,2 ; LCDIE
ljmp sivt_entry_end_2
movlb 0
btfss 18,2 ; LCDIF
ljmp sivt_entry_end_2
ljmp _isr_lcd
sivt_entry_end_2:
sivt_end:
bcf int$flags,0 ;clear compiler interrupt flag (level 1)
retfie
__default
as the argument to __flags()
to have the ISR handle any interrupt sources not covered by other ISRs. The following PIC18 example shows an ISR written to handle the high priority USART source, with all other high priority sources handled by the default handler, isrhi()
, and all low priority sources handled by isrlo()
.void __interrupt(__flags(RC1IE,RC1IF), __high_priority) isr_usartrx(void) {
// process USART receive interrupt here
}
void __interrupt(__flags(__default), __high_priority) isrhi(void) {
// process extraneous high priority interrupts here
}
void __interrupt(__flags(__default), __low_priority) isrlo(void) {
// process extraneous low priority interrupts here
}
Any ISRs specifying __default
flags cannot also specify a process order. The interrupt sources for default ISRs are always checked last.In programs where interrupt functions have been defined using __flags()
, and hence are using the software IVT, and other interrupt functions have been defined without using __flags()
, then these other interrupt functions will assume the special default interrupt source, that is, it is assumed that they were defined using __flags(__default)
.