VectoredInterrupts

SwordfishUser.VectoredInterrupts History

Hide minor edits - Show changes to output

Added lines 125-126:
Note: IVT v2.2 adds saving the TABLAT register to TBLPTR context operations.
Changed line 132 from:
The only important registers it does not save are the TBLPTRU/H/L, which you should save/restore if the ISR accesses const data arrays or strings. This can be done using the SAVE_CONTEXT() and RESTORE_CONTEXT() macros as shown below:
to:
The only important registers it does not save are the TBLPTRU/H/L and TABLAT, which you should save/restore if the ISR accesses const data arrays or strings. This can be done using the SAVE_CONTEXT() and RESTORE_CONTEXT() macros as shown below:
Changed lines 156-157 from:
These macros take the place of the compiler's equivalent built-in 'save(0)/restore' functions, and if enabled by the '#option _IVT_SAVE_CONTEXT' setting will also save the TBLPTRU/H/L registers.
to:

The SYSTEM_CONTEXT
macros take the place of the compiler's equivalent built-in 'save(0)/restore' functions, and if enabled by the '#option _IVT_SAVE_CONTEXT' setting will also save the TBLPTRU/H/L registers.
Changed line 149 from:
   SAVE_CONTEXT(priority)    // high or low... change 'priority' to match ISR INTR_PRIORITY... 0=low, 1=high
to:
   SAVE_CONTEXT(priority)    // high or low... change 'priority' to match ISR INTR_PRIORITY
Changed line 154 from:
   SAVE_SYSTEM_CONTEXT(priority)  // high or low... change 'priority' to match ISR INTR_PRIORITY... 0=low, 1=high
to:
   SAVE_SYSTEM_CONTEXT(priority)  // high or low... change 'priority' to match ISR INTR_PRIORITY
Changed line 158 from:
SAVE_SYSTEM_CONTEXT allows multiple isr handlers to share the context storage for SF system variables. Since ISR handlers are defined as events, using the traditional 'save(0)' statement in multiple handlers would result in each handler allocating an additional 31 bytes of ram for the system context and additional hdw registers which is unnecessary. SAVE_SYSTEM_CONTEXT is also slightly faster since it does not have to save the additional registers which are saved automatically as part of the hardware shadow registers.
to:
SAVE_SYSTEM_CONTEXT allows multiple isr handlers to share the context storage for SF system variables. Since ISR handlers are defined as events, using the traditional 'save(0)' statement in multiple handlers would result in each handler allocating an additional 31 bytes of ram for the system context and hdw registers which is unnecessary. SAVE_SYSTEM_CONTEXT is also slightly faster since it does not have to save the additional registers which are saved automatically as part of the hardware shadow registers.
Changed lines 179-181 from:
   'SAVE_CONTEXT(priority)            // same as above... change 'priority' to match IRQ priority setting
   'SAVE_SYSTEM_CONTEXT()              // saves SF system variables (and TABLEPTR registers if enabled)
    'SAVE_SYSTEM_CONTEXT(priority)      // same as above... change 'priority' to match IRQ priority setting
to:
   'SAVE_CONTEXT(priority)            // same as above... change 'priority' to match IRQ priority
   'SAVE_SYSTEM_CONTEXT()              // saves SF system variables (and TABLEPTR if enabled)
    'SAVE_SYSTEM_CONTEXT(priority)      // same as above... change 'priority' to match IRQ priority
Changed lines 191-193 from:
   'RESTORE_CONTEXT(priority)          // same as above... change 'priority' to match IRQ priority setting
   'RESTORE_SYSTEM_CONTEXT()          // restores SF system variables (and TABLEPTR registers if enabled)
    'RESTORE_SYSTEM_CONTEXT(priority)  // same as above... change 'priority' to match IRQ priority setting
to:
   'RESTORE_CONTEXT(priority)          // same as above... change 'priority' to match IRQ priority
   'RESTORE_SYSTEM_CONTEXT()          // restores SF system variables (and TABLEPTR if enabled)
    'RESTORE_SYSTEM_CONTEXT(priority)  // same as above... change 'priority' to match IRQ priority
Changed line 49 from:
The IVT is a table of up to 256 entries, one for each IRQ vector number. Each entry is a 16-bit word that contains the address of the interrupt handler for that vector number (more on this later).  The IVT resides in ROM so it must be fixed at compile-time, however you can set the base address of the IVT using the IVTBASEU/H/L registers at run-time. This allows the code to setup multiple IVT tables
to:
The IVT is a table of up to 256 entries, one for each IRQ vector number. Each entry is a 16-bit word that contains the address of the interrupt handler for that vector number (more on this later).  The IVT resides in program memory so it must be fixed at compile-time, however you can set the base address of the IVT using the IVTBASEU/H/L registers at run-time. This allows the code to setup multiple IVT tables
Changed lines 56-57 from:
The number of available interrupt vectors varies with the device, but it typically ranges from 82 to 128. The IVT must include entries for all interrupt vector numbers up to the highest vector used by your program. Since an IVT entry requires a word value, a complete table of 128 entries requires 128*2 = 256 bytes of ROM space. The max number of IRQs and the IRQ vector number definitions themselves can be found in the device .bas file located in the compiler 'Includes' folder.
to:
The number of available interrupt vectors varies with the device, but it typically ranges from 82 to 128. The IVT must include entries for all interrupt vector numbers up to the highest vector used by your program. Since an IVT entry requires a word value, a complete table of 128 entries requires 128*2 = 256 bytes of program memory space. The max number of IRQs and the IRQ vector number definitions themselves can be found in the device .bas file located in the compiler 'Includes' folder.
Changed lines 105-110 from:
The 18F interrupt mechanism can be set to use a single priority for all interrupts or to allow the use of high and low priority setting through the IPEN register flag. The default setting is IPEN = 0 (all interrupts are high-priority), but this can be set using the ENABLE_INTR_PRIORITY() and DISABLE_INTR_PRIORITY() macros. Unless you have the need to allow a high-priority interrupt
to be able
to interrupt a running low-priority one, it is recommended to leave the interrupt priority setting at its default setting and just use a single priority.

If interrupt priority is enabled then each interrupt can be assigned as either low (priority=0) or high (priority=1). The individual priorities for each IRQ are set using INTR_PRIORITY
(irq_no, priority), which defaults to high-priority if left unchanged. INTR_PRIORITY() sets/clears the respective bit in the IPRx register for the given irq_no.

to:
The 18F interrupt mechanism can be set to use a single priority for all interrupts or to allow the use of high and low priorities through the IPEN register flag. The default setting is IPEN = 0 (all interrupts are high-priority), but this can be set using the ENABLE_INTR_PRIORITY() and DISABLE_INTR_PRIORITY() macros or by setting '#option _IVT_DEFAULT_IPEN = 1'. Unless you have the need to allow a high-priority interrupt to be able to interrupt a running low-priority one, it is recommended to leave the interrupt priority setting at its default setting and just use a single priority.

If interrupt priority is enabled then each interrupt can be assigned as either low (priority=0) or high
(priority=1). The individual priorities for each IRQ are set using INTR_PRIORITY(irq_no, priority), which defaults to high-priority if left unchanged. INTR_PRIORITY() sets/clears the respective bit in the IPRx register for the given irq_no. Where used, priority settings can be specified using the values 0, 1, or the constants HIGH_PRIORITY/LOW_PRIORITY.

Changed lines 112-115 from:
There are two levels of hardware shadow registers for saving context: one for high-priority interrupts (priority=1) and one for low-priority interrupts (priority=0) In addition to the program counter (PC), the hardware context automatically saves the registers:
  STATUS, WREG, BSR, FSR0/1/2, PRODL/H and PCLATH/U

The only important registers it does not save are the TBLPTRU/H/L, which you should save/restore if accessing const data or strings in the ISR. This can be done using the SAVE_CONTEXT() and RESTORE_CONTEXT() macros as shown
below:
to:
New in IVT v2.1, '#option _IVT_SAVE_CONTEXT' allows you to tailor the level of context support. The option setting is a bit-field value with the meanings as shown below-
Added lines 114-131:
_IVT_SAVE_CONTEXT bit field values:
  %0000        // no context support (default setting)
  %0001        // ---1 high-priority TBLPTR
  %0010        // --1- high-priority SF SYSTEM variables
  %0100        // -1-- low priority TBLPTR (requires IPEN = 1)
  %1000        // 1--- low-priority SF SYSTEM variables (requires IPEN = 1)
  %0011        // high-priority TBLPTR + SYSTEM
  %1100        // low-priority TBLPTR + SYSTEM
  %1111        // high and low priority TBLPTR + SYSTEM
=]

_IVT_SAVE_CONTEXT must be set to the proper value to allow the module to include the required functionality, remove unneeded code, and minimize variable usage. The option may have multiple bits set. For example '#option _IVT_SAVE_CONTEXT = %0101' would enable saving the low priority TBLPTR and high-priority TBLPTR context.

There are two levels of hardware shadow registers for saving context: one for high-priority interrupts (priority=1) and one for low-priority interrupts (priority=0) In addition to the program counter (PC), the improved 18XV hardware context automatically saves all CPU registers:
  STATUS, WREG, BSR, FSR0/1/2, PRODL/H and PCLATH/U

The only important registers it does not save are the TBLPTRU/H/L, which you should save/restore if the ISR accesses const data arrays or strings. This can be done using the SAVE_CONTEXT() and RESTORE_CONTEXT() macros as shown below:
=code [=
Changed lines 137-138 from:
       SAVE_CONTEXT()                     // high-priority
to:
       SAVE_CONTEXT()                  // high-priority
Changed lines 147-157 from:
There are three different implementations of SAVE_CONTEXT/RESTORE_CONTEXT, and their usage depends on the global INTR_PRIORITY enable setting...
    SAVE_CONTEXT()            // high-priority
    SAVE_CONTEXT(priority)    // high or low... change 'priority' to match IRQ priority setting (0 or 1)
    SAVE_CONTEXT_ISS()        // use run-time Interrupt State Status from INTCON1 (uses more code)

SAVE_CONTEXT_ISS can determine the priority state at runtime, but it will generate more code and is slower
. This might be useful where the interrupt priority for a handler is dynamically changed and not fixed at compilation.

If the ISR uses system library functions or calls other routines you may also need to save the system library variables and/or the frame context. This can be done using 'save/restore' statement blocks just as with traditional interrupts
.

The only restriction with using a traditional save/restore block is that it must be used outside of any SAVE_CONTEXT/RESTORE_CONTEXT
macros.
to:
There are three different implementations of SAVE_CONTEXT/RESTORE_CONTEXT which save the hardware TBLPTRU/H/L registers, and usage depends on the INTR_PRIORITY setting of the ISR...
    SAVE_CONTEXT()            // high-priority only
   SAVE_CONTEXT(priority)    // high or low... change 'priority' to match ISR INTR_PRIORITY... 0=low, 1=high
    SAVE_CONTEXT_ISS()        // use run-time Interrupt State Status from INTCON1

In addition to the TBLPTR registers, you may also need to save/restore the state of the SF system variables
. This is required if the interrupt handler uses system library support functions that reference 'SB_SVxx' variables, such as multiply or divide
    SAVE_SYSTEM_CONTEXT()          // high-priority only
    SAVE_SYSTEM_CONTEXT(priority)  // high or low... change 'priority' to match ISR INTR_PRIORITY
... 0=low, 1=high
    SAVE_SYSTEM_CONTEXT_ISS()      // use run-time Interrupt State Status from INTCON1
These
macros take the place of the compiler's equivalent built-in 'save(0)/restore' functions, and if enabled by the '#option _IVT_SAVE_CONTEXT' setting will also save the TBLPTRU/H/L registers.

SAVE_SYSTEM_CONTEXT allows multiple isr handlers to share the context storage for SF system variables. Since ISR handlers are defined as events, using the traditional 'save(0)' statement in multiple handlers would result in each handler allocating an additional 31 bytes of ram for the system context and additional hdw registers which is unnecessary. SAVE_SYSTEM_CONTEXT is also slightly faster since it does not have to save the additional registers which are saved automatically as part of the hardware shadow registers.
The SAVE_xxxx_ISS() versions can determine the priority state at runtime, but will generate more code and are slower. They are for use in rare situations where an ISR has a dynamically changing priority and is not fixed at compile time.

Along with the above macros, you can also use the compiler's traditional built-in 'save()/restore' functions to save the state of any local variables used by subroutines and functions that are called by their IRQ event handler. If used, the order of save()/SAVE_SYSTEM_CONTEXT is unimportant, but the order of 'saves' must match the order of 'restores' otherwise a system error will occur.


Added lines 166-169:
// if priorities are used you may want to define a const that can be used wherever
// specifying the irq 'priority' is required
public const handler_name_PRIORITY = HIGH_PRIORITY  // or LOW_PRIORITY

Changed lines 173-181 from:
   // define interrupt entry point
    ENTER_INTR_HANDLER(handler_name)    // change 'handler_name' to match 'public event' name

   // save SF system variable context (if req'd, must be before SAVE_CONTEXT)
   save(0)

    // save TABLEPTR registers (if req'd, for const data/string access)
 
  SAVE_CONTEXT(priority)              // change 'priority' to match IRQ priority setting (0 or 1)
to:
   // define interrupt entry point (this MUST be the first executable statement)
    ENTER_INTR_HANDLER(handler_name     // ** change 'handler_name' to match 'public event' name

 
  // save context (optional, depends on user code)
    // select one or more of the following:
    'SAVE_CONTEXT()                   // save TABLEPTR registers (for const data array/string access)
   'SAVE_CONTEXT(priority)            // same as above... change 'priority' to match IRQ priority setting
    'SAVE_SYSTEM_CONTEXT()              // saves SF system variables (and TABLEPTR registers if enabled)
    'SAVE_SYSTEM_CONTEXT(priority)      // same as above... change 'priority' to match IRQ priority setting
    'save(mysub)                        // save local frame variables used by mysub

Changed lines 186-194 from:
   // clear IF
   INTR_REQ(irq_no, 0)                // chnage 'irq_no' to match intr vector number
   
 
  // restore TABLEPTR (if saved above)
    RESTORE_CONTEXT(priority)          // high/low... change 'priority' to match... 0=low, 1=high

    // restore SF system variable context (if saved above)
    restore
to:
   // clear IF interrupt request flag
   
INTR_REQ(irq_no, 0)                     // ** change 'irq_no' to match intr vector number

   // restore context (if saved above)
    'RESTORE_CONTEXT()                  // restore TABLEPTR registers
    'RESTORE_CONTEXT(priority)          // same as above... change 'priority' to match IRQ priority setting
    'RESTORE_SYSTEM_CONTEXT()          // restores SF system variables (and TABLEPTR registers if enabled)
    'RESTORE_SYSTEM_CONTEXT(priority)  // same as above... change 'priority' to match IRQ priority setting
    '
restore                           // restore local frame variables
Changed lines 236-238 from:
Due to the manner in which the IVT is setup, there must be an entry in the table for every IRQ number, even if the interrupt is unused. To simplify this, the IVT.bas module defines a default_interrupt_handler() routine which is used by CREATE_IVT when the vector table database
is initialized. The default_interrupt_handler() will perform a device reset for any unhandled interrupt requests. This is all done automatically, so nothing further needs to be done by the user.
to:
Due to the manner in which the IVT is setup, there must be an entry in the table for every IRQ number, even if the interrupt is unused. To simplify this, the IVT.bas module defines a default_interrupt_handler() routine which is used by CREATE_IVT when the vector table database is initialized. The default_interrupt_handler() will perform a device reset for any unhandled interrupt requests. This is all done automatically, so nothing further needs to be done by the user.
Changed lines 242-244 from:
// ivt.bas contains a default_intr_handler() routine which is defined by default
// set '#option _IVT_DEFAULT_HANDLER = false' to override the default routine in ivt.bas

// and use our own local routine
to:
// ivt.bas contains a default_interrupt_handler() routine
// if you wish to override that routine with your own handler, then:
//  - set '#option _IVT_DEFAULT_HANDLER = false'
//  - define a replacement event 'public event default_interrupt_handler()' routine
//  - add at least one reference to the new routine between CREATE_IVT/END_IVT using
//      SET_IVT_HANDLER(<unused irq_no>, default_interrupt_handler)
Changed line 252 from:
// our local default_intr_handler()
to:
// our local default_interrupt_handler()
Changed lines 285-286 from:
The PIC18 interrupt structure is managed using two settings: each peripheral has its own interrupt enable bit located in the PIEx registers, and the global interrupt enable bit GIE (or GIE and GIEL if using interrupt priorities).
to:
The PIC18 interrupt structure is managed using two settings: each peripheral has its own interrupt enable bit located in the PIEx registers, and the global interrupt enable bit GIE (or GIE and GIEL if using IPEN=1 and interrupt priorities).
Changed lines 389-390 from:
!!!Example 2 - two interrupts, low and high priority w/context save
to:
!!!Example 2 - two interrupts, high and low priority with save_context
Added lines 400-403:
// enable both priorities by default
#option _IVT_DEFAULT_IPEN = 1
// save_context support - low-priority TBLPTR + SYSTEM context, high-priority TBLPTR context
#option _IVT_SAVE_CONTEXT = %1101
Changed line 409 from:
// IRQ_TMR1
to:
// IRQ_TMR1 (high-priority w/TBLPTR context save example)
Added lines 414-416:
   // save TABLEPTR registers
    SAVE_CONTEXT(HIGH_PRIORITY)

Added lines 424-426:
   // restore
    RESTORE_CONTEXT(HIGH_PRIORITY)

Changed line 431 from:
// IRQ_TMR3 (low-priority w/context save example)
to:
// IRQ_TMR3 (low-priority w/system context save example)
Changed lines 436-440 from:
   // save SF system variable context (must be before SAVE_CONTEXT)
    save(0)
    // save TABLEPTR registers
    SAVE
_CONTEXT(LOW_PRIORITY)
to:
   // save SF system variables and TABLEPTR registers
   
SAVE_SYSTEM_CONTEXT(LOW_PRIORITY)
Changed lines 446-450 from:
   // restore TABLEPTR registers
   RESTORE_CONTEXT(LOW_PRIORITY)
   // restore SF system variable context
    restore

to:
   // restore
 
  RESTORE_SYSTEM_CONTEXT(LOW_PRIORITY)
Changed line 488 from:
ENABLE_INTR_PRIORITY()
to:
ENABLE_INTR_PRIORITY()     // not req'd if '#option _IVT_DEFAULT_IPEN = 1'
Changed lines 525-526 from:
// - two IVT tables
// - replace default_intr_handler
to:
// - two IVT tables swapped at runtime
// - replace
default_interrupt_handler
Changed lines 534-540 from:
// ivt.bas contains a default_intr_handler() routine which is enabled by default
// if you wish to override that
routine with your own handler, then:
//  - set '#option _IVT_DEFAULT_HANDLER = false'
//  - define a replacement event 'public event default_interrupt_handler()' routine
//  - add at least one reference to the new routine between CREATE_IVT/END_IVT using
//      SET_IVT_HANDLER(<unused irq_no>, default_interrupt_handler)
//    if you add it first then it doesn't matter what irq_no you use
to:
// ivt.bas contains a default_interrupt_handler() routine
// set '#option _IVT_DEFAULT_HANDLER = false' to override the default routine
// and use our own local routine
Changed line 540 from:
// set this option true to test using the default handler
to:
// set this option true to test using the default_interrupt_handler
Changed line 548 from:
// override the default_intr_handler() in ivt.bas with our own local routine
to:
// override the default_interrupt_handler() in ivt.bas with our own local routine
Changed line 584 from:
   LED = 1
to:
   LED = 1                // turn on LED
Changed line 600 from:
   LED = 0
to:
   LED = 0                // turn off LED
Changed line 610 from:
// common handler for both tmr1 and tmr3 (using vector table 2)
to:
// common handler for both tmr1 and tmr3 (using ivt table 2)
Changed line 617 from:
   // WREG contains the interrupt number
to:
   // on entry WREG contains the interrupt number
Changed line 682 from:
// enable and generate IRQ=UNUSED_IRQ
to:
// enable and generate an IRQ=UNUSED_IRQ
Changed line 684 from:
INTR_REQ(UNUSED_IRQ, 1)      // generate intr (should go to default_handler)
to:
INTR_REQ(UNUSED_IRQ, 1)      // generate intr (should go to default_interrupt_handler)
Changed line 690 from:
// test TMR1 and TMR3 interrupts
to:
// test TMR1 and TMR3 interrupts using ivt table1
Changed lines 694-695 from:
// - LED should go ON for 1/2 sec then OFF for 1 sec
// let it run for 10 secs
to:

// let it run for 10 secs... LED should go ON for 1/2 sec then OFF for 1 sec
Added lines 698-701:
//
// now stop everything and swap runtime IVT tables...
// both TMR1 and TMR3 interrupts should now be handled by common tmr_handler()
//
Changed line 720 from:
// repeat forever...
to:
// repeat forever... LED should go ON for 1 sec and then OFF for 1/2 sec
Changed line 260 from:
The 'enable()/disable()' keywords used in traditional interrupts are replaced by the ENABLE_INTERRUPT()/DISABLE_INTERRUPT() macros.
to:
The 'enable()/disable()' keywords used in traditional interrupts are replaced by the ENABLE_INTERRUPT() and DISABLE_INTERRUPT() macros.
Changed line 156 from:
   SAVE_CONTEXT(priority)              // change 'priority' to match IRQ priority setting... 0=low, 1=high
to:
   SAVE_CONTEXT(priority)              // change 'priority' to match IRQ priority setting (0 or 1)
Changed line 135 from:
   SAVE_CONTEXT(priority)    // high or low... change 'priority' to match IRQ priority setting... 0=low, 1=high
to:
   SAVE_CONTEXT(priority)    // high or low... change 'priority' to match IRQ priority setting (0 or 1)
Changed line 134 from:
   SAVE_CONTEXT()            // high-priority only
to:
   SAVE_CONTEXT()            // high-priority
Changed lines 3-4 from:
Many new 18F devices include a Vectored Interrupt Controller (VIC) module, available on the XV18 core devices. Vectored Interrupts bring new enhancements to the interrupt scheme used in current 18F designs, improving speed and flexibility.
to:
Many new 18F devices include a Vectored Interrupt Controller (VIC) module, available on the 18XV core devices in the K and Q families. Vectored Interrupts bring new enhancements to the interrupt scheme used in current 18F designs, improving speed and flexibility. You can identify the 18XV core devices from the device .bas file entry \\
'#define _xv18 = 1'

Changed lines 9-10 from:
These interrupt vectors are located in low memory at $0008 and $0018, and are fully supported in Swordfish
using the Enable, Disable, and Interrupt statements. See the Users Manual/online Help section on Interrupts for more details.
to:
These interrupt vectors are located in low memory at $0008 and $0018, and are fully supported in Swordfish using the Enable, Disable, and Interrupt statements. See the Users Manual/online Help section on Interrupts for more details.
Changed line 52 from:
When an interrupt occurs, the processor uses the interrupt vector number 0-255 as an index into the IVT. It retrieves the word address from the table, shifts it left by 2 (multiplying it by 4), and branches to that address. Why does it shift the address? The IVT holds a 16-bit word, which only allows a value up to 64K. Multiplying the word address from the table by 4 allows the vector to point to up to 256K bytes of code address space.
to:
When an interrupt occurs, the processor uses the interrupt vector number 0-255 as an index into the IVT. It retrieves the word address from the table, shifts it left by 2 (multiplying it by 4), and branches to that address. Why does it shift the address? The IVT holds a 16-bit word, which only allows a value up to 65535. Multiplying the word address from the table by 4 allows the vector to point to up to 256K bytes of code address space.
Changed lines 3-6 from:
Many new 18F devices include a Vectored Interrupt Controller (VIC) module, available on the XV18 core devices.
Vectored Interrupts bring new enhancements to the interrupt scheme used in current 18F designs, improving speed
and flexability.
to:
Many new 18F devices include a Vectored Interrupt Controller (VIC) module, available on the XV18 core devices. Vectored Interrupts bring new enhancements to the interrupt scheme used in current 18F designs, improving speed and flexibility.
Changed lines 10-13 from:
Also, check out the article "PIC 18F Interrupts in Swordfish" https://www.sfcompiler.co.uk/wiki/pmwiki.php?n=SwordfishUser.Interrupts by RadioT for some additional tips.

One of the issues with the two-vector approach is once you have more than
two interrupts you must begin to add code
to detect which peripheral is generating the interrupt request. This adds additional latency and complexity to
the ISR.
to:
Also, check out the article [[https://www.sfcompiler.co.uk/wiki/pmwiki.php?n=SwordfishUser.Interrupts | PIC 18F Interrupts in Swordfish]] by RadioT for some additional tips.

One of the issues with the
two-vector approach is once you have more than two interrupts you must begin to add code to detect which peripheral is generating the interrupt request. This adds additional latency and complexity to the ISR.
Changed lines 38-42 from:
In this case, saving and restoring the interrupt context becomes much more challenging. It also means that you have to move the
interrupt routine to outside the .bas module that uses it since you likely have to reference three different source modules,
breaking the modular approach used in existing Swordfish modules.

to:
In this case, saving and restoring the interrupt context becomes much more challenging. It also means that you have to move the interrupt routine to outside the .bas module that uses it since you likely have to reference three different source modules, breaking the modular approach used in existing Swordfish modules.

Changed lines 43-50 from:
Vectored interrupt mode departs from the traditional scheme. While it still provides for two interrupt priorities (high and low),
the interrupt priority no longer sets the vector address as it does in legacy mode. Instead, each interrupt source is assigned its own individual vector number, from 0 to 255.
This allows interrupts to have a unique ISR handler so you no longer need code like the above to check the IF and IE flags.
Since ISRs are no longer shared among modules, you can move the interrupt handler code back into the .bas module that uses it.
This is all accomplished through the Interrupt Vector Table (IVT). Vectored interrupts are enabled via the Multi-Vector Enable bit (MVECEN)
in the config setting. For compatability with existing Swordfish modules, the device include file for XV18 core devices sets MVECEN off
so by default interrupts operate in traditional legacy mode.
to:
Vectored interrupt mode departs from the traditional scheme. While it still provides for two interrupt priorities (high and low), the interrupt priority no longer sets the vector address as it does in legacy mode. Instead, each interrupt source is assigned its own individual vector number, from 0 to 255.
This allows interrupts to have a unique ISR handler so you no longer need code like the above to check the IF and IE flags. Since ISRs are no longer shared among modules, you can move the interrupt handler code back into the .bas module that uses it.

This is all accomplished through the Interrupt Vector Table (IVT). Vectored interrupts are enabled via the Multi-Vector Enable bit (MVECEN) in the config setting. For compatibility with existing Swordfish modules, the device include file for XV18 core devices sets MVECEN off so by default interrupts operate in traditional legacy mode.
Changed lines 49-51 from:
The IVT is a table of up to 256 entries, one for each IRQ vector number. Each entry is a 16-bit word that contains the address (more on this later)
of the interrupt handler for that vector number
.  The IVT resides in ROM so it must be fixed at compile-time, however you can
set the base address of the IVT using the IVTBASEU/H/L registers at run-time. This allows the code to setup mutiple IVT tables
to:
The IVT is a table of up to 256 entries, one for each IRQ vector number. Each entry is a 16-bit word that contains the address of the interrupt handler for that vector number (more on this later).  The IVT resides in ROM so it must be fixed at compile-time, however you can set the base address of the IVT using the IVTBASEU/H/L registers at run-time. This allows the code to setup multiple IVT tables
Changed lines 52-63 from:
When an interrupt occurs, the processor uses the interrupt vector number 0-255 as an index into the IVT. It retrieves the word address from the table,
shifts it left by 2 (multiplying by 4), and branches to that address. Why does it shift the address? The IVT holds a 16-bit word, which only allows a value up to 64K.
Multiplying the word address from the table by 4 allows the vector to point to up to 256K bytes of code address space.

The downside to this is that the ISR code MUST be located on a 4-byte address boundary, but Swordfish has no built-in mechanism to enforce this restriction.
The ENTER_INTR_HANDLER() macro will add padding to the start of the interrupt handler routine if needed to adjust the value used
to set the IVT entry point, making it compatible with the word-length address entry requirement.

The number of available interrupt vectors varies with the device, but it typically ranges from 82 to 128. The IVT must include entries
for all interrupt vector numbers up to the highest vector used by your program. Since an IVT entry requires a word value,
a complete table of 128 entries requires 128*2 = 256 bytes of ROM space. The max number of IRQs and the IRQ vector number definitions
themselves can be found in the device .bas file located in the compiler 'Includes' folder.
to:
When an interrupt occurs, the processor uses the interrupt vector number 0-255 as an index into the IVT. It retrieves the word address from the table, shifts it left by 2 (multiplying it by 4), and branches to that address. Why does it shift the address? The IVT holds a 16-bit word, which only allows a value up to 64K. Multiplying the word address from the table by 4 allows the vector to point to up to 256K bytes of code address space.

The downside to this is that the ISR code MUST be located on a 4-byte address boundary, but Swordfish has no built-in mechanism to enforce this restriction. The ENTER_INTR_HANDLER() macro will add padding to the start of the interrupt handler routine if needed to adjust the value used to set the IVT entry point, making it compatible with the word-length address entry requirement.

The number of available interrupt vectors varies with the device, but it typically ranges from 82 to 128. The IVT must include entries for all interrupt vector numbers up to the highest vector used by your program. Since an IVT entry requires a word value, a complete table of 128 entries requires 128*2 = 256 bytes of ROM space. The max number of IRQs and the IRQ vector number definitions themselves can be found in the device .bas file located in the compiler 'Includes' folder.
Changed lines 73-77 from:
This new module contains macros and routines that can be used to setup and manage the IVT and interrupts when using vectored interrupts.
Replacements for the standard Enable, Disable, and Interrupt statements are provided and must be used in place of these original functions when
operating in VIC mode. Including IVT.bas will automatically set the MVECEN config setting to ON, enabling multi-vectored mode.
Before we look at creating an IVT, let's look at the required format for an ISR interrupt handler.
to:
This new module contains macros and routines that can be used to setup and manage the IVT and interrupts when using vectored interrupts. Replacements for the standard Enable, Disable, and Interrupt statements are provided and must be used in place of these original functions when operating in VIC mode. Including IVT.bas will automatically set the MVECEN config setting to ON, enabling multi-vectored mode.

Before we look at creating an IVT, let's look at the required format for an interrupt handler ISR.
Changed lines 79-80 from:
Interrupt handlers replace the traditional 'interrupt' routines, and must be declared using the 'event' attribute.
They should follow the basic format:
to:
Interrupt handlers replace the traditional 'interrupt' routines, and must be declared using the 'event' attribute. They should follow the basic format:
Changed lines 95-103 from:
ENTER_INTR_HANDLER will make any required adjustments to the code offset and will set the interrupt entry point.
This marks the first executable statement in the handler, so you cannot use variable declarations that include an initializer since they would be skipped.

Before exiting the ISR you should clear the source of the interrupt request. This can be done using the INTR_REQ() macro as
shown above to set the IF flag to 0.

EXIT_INTR_HANDLER() will perform a 'RETFIE 1', restoring the hardware context and reenabling interrupts.

to:

ENTER_INTR_HANDLER will make any required adjustments to the code offset and will set the interrupt entry point. This marks the first executable statement in the handler, so you cannot use variable declarations that include an initializer since they would be skipped.

Before exiting the ISR you should clear the source of the interrupt request. This can be done using the INTR_REQ() macro as shown above to set the IF flag to 0.

EXIT_INTR_HANDLER() will perform a 'RETFIE 1', restoring the hardware context and re-enabling interrupts.

Changed lines 105-114 from:
The 18F interrupt mechanism can be set to use a single priority for all interrupts or to allow the use of high and low priority setting
through the IPEN register flag. The default setting is IPEN = 0 (all interrupts are high-priority), but this can be set
using the ENABLE_INTR_PRIORITY() and DISABLE_INTR_PRIORITY() macros. Unless you have the need to allow a high-priority interrupt
to be able to interrupt a running low-priority one, it is recommended to leave the interrupt priority setting at its default setting.

If interrupt priority is enabled then each
interrupt can be assigned as either low (priority=0) or high (priority=1).
The individual priorities are set using INTR_PRIORITY
(irq_no, priority), which defaults to high-priority if left unchanged.
INTR_PRIORITY() sets/clears the respective bit in the IPRx register for the given irq_no.

to:
The 18F interrupt mechanism can be set to use a single priority for all interrupts or to allow the use of high and low priority setting through the IPEN register flag. The default setting is IPEN = 0 (all interrupts are high-priority), but this can be set using the ENABLE_INTR_PRIORITY() and DISABLE_INTR_PRIORITY() macros. Unless you have the need to allow a high-priority interrupt
to be able to interrupt a running low-priority one, it is recommended to leave the interrupt priority setting at its default setting and just use a single priority.

If
interrupt priority is enabled then each interrupt can be assigned as either low (priority=0) or high (priority=1). The individual priorities for each IRQ are set using INTR_PRIORITY(irq_no, priority), which defaults to high-priority if left unchanged. INTR_PRIORITY() sets/clears the respective bit in the IPRx register for the given irq_no.

Changed lines 113-114 from:
There are two levels of hardware shadow registers for saving context, one for high-priority interrupts (priority 1) and one for low-priority interrupts (priority 0)
In addition to the program counter (PC), the hardware context automatically saves the registers:
to:
There are two levels of hardware shadow registers for saving context: one for high-priority interrupts (priority=1) and one for low-priority interrupts (priority=0) In addition to the program counter (PC), the hardware context automatically saves the registers:
Changed lines 116-117 from:
The only important registers it does not save are the TBLPTRU/H/L, which you should save/restore if accessing const data or strings in the ISR.
This can be done using the SAVE_CONTEXT() and RESTORE_CONTEXT() macros as shown below:
to:
The only important registers it does not save are the TBLPTRU/H/L, which you should save/restore if accessing const data or strings in the ISR. This can be done using the SAVE_CONTEXT() and RESTORE_CONTEXT() macros as shown below:
Changed lines 138-142 from:
SAVE_CONTEXT_ISS can determine the priority state at runtime, but it will generate more code and is slower.
This might be useful where the interrupt priority for a handler is dynamically changed and not fixed at compilation.

If the ISR uses system library functions or calls other routines you may also need to save the system library variables
and/or the frame context. This can be done using 'save/restore' statement blocks just as with traditional interrupts.
to:
SAVE_CONTEXT_ISS can determine the priority state at runtime, but it will generate more code and is slower. This might be useful where the interrupt priority for a handler is dynamically changed and not fixed at compilation.

If the ISR uses system library functions or calls other routines you may also need to save the system library variables and/or the frame context. This can be done using 'save/restore' statement blocks just as with traditional interrupts.
Changed lines 176-188 from:
To create an interrupt vector table in program memory you first use CREATE_IVT(), then add a series of SET_IVT_HANDLER() entries
for every IRQ used by your program, and finally mark the end of the table using END_IVT().

CREATE_IVT(table_name) is used to initialize the vector table database settings and assign a unique name to the table.

Assigning a name to the table does two things: it provides a table name which is used when programming the IVTBASE registers,
and also allows you to have multiple tables and switch between them at run-time. The SET_IVTBASE(table_name) macro will load the
registers with the address of the specified table_name.

SET_IVT_HANDLER() updates the vector table database for an individual IRQ with the address of an interrupt handler.
The format for adding a handler is SET_IVT_HANDLER(irq_no, event_handler), where 'irq_no' is 0-255 (or one of the IRQ_xxx consts
from the device file), and 'event_handler' is the name assigned to the event routine by ENTER_INTR_HANDLER (ie 'tmr1_handler').
to:
To create an interrupt vector table in program memory you first use CREATE_IVT(), then add a series of SET_IVT_HANDLER() entries for every IRQ used by your program, and finally mark the end of the table using END_IVT().

CREATE_IVT(table_name) is used to initialize the vector table database settings and assign a unique name to the table. Assigning a name to the table does two things: it provides a table name which is used when programming the IVTBASE registers, and also allows you to have multiple tables and switch between them at run-time. The SET_IVTBASE(table_name) macro will load the registers with the address of the specified table_name.

SET_IVT_HANDLER() updates the vector table database for an individual IRQ with the address of an interrupt handler. The format for adding a handler is SET_IVT_HANDLER(irq_no, event_handler), where 'irq_no' is 0-255 (or one of the IRQ_xxx consts from the device file), and 'event_handler' is the name assigned to the event routine by ENTER_INTR_HANDLER (ie 'tmr1_handler').
Changed line 191 from:
This example shows the creation of two tables. In the second table TMR1 and TMR3 share the same interrupt handler rouitne.
to:
This example shows the creation of two tables. In the second table TMR1 and TMR3 share the same interrupt handler routine.
Changed lines 209-217 from:
Due to the manner in which the IVT is setup, there must be an entry in the table for every IRQ number, even if the interrupt is unused.
To simplify this, the IVT.bas module defines a default_interrupt_handler() routine which is used by CREATE_IVT when the vector table database
is initialized. The default_interrupt_handler() will perform a device reset for any unhandled interrupt requests.
This is all done automatically, so nothing further needs to be done by the user.

You can override the default interrupt handler and provide your own routine if desired. To do this, set '#option _IVT_DEFAULT_HANDLER=false'
and create a public event routine named 'default_interrupt_handler()'. When you create the IVT, you must register the routine using SET_IVT_HANDLER.
If you list default_interrupt_handler first then the irq_no used is unimportant, and your routine will now be used for all unspecified interrupts.
to:
Due to the manner in which the IVT is setup, there must be an entry in the table for every IRQ number, even if the interrupt is unused. To simplify this, the IVT.bas module defines a default_interrupt_handler() routine which is used by CREATE_IVT when the vector table database
is initialized. The default_interrupt_handler() will perform a device reset for any unhandled interrupt requests. This is all done automatically, so nothing further needs to be done by the user.

You can override the default interrupt handler and provide your own routine if desired. To do this, set '#option _IVT_DEFAULT_HANDLER=false' and create a public event routine named 'default_interrupt_handler()'. When you create the IVT, you must register the routine using SET_IVT_HANDLER. Note: If you list the default_interrupt_handler first then the irq_no used is unimportant, and your routine will now be used for all unspecified interrupts.
Changed lines 256-261 from:
The PIC18 interrupt structure is managed using two settings: each peripheral has its own interrupt enable bit located in the PIEx registers,
and the global interrupt enable bit GIE (or GIE and GIEL if using interrupt priorities).

INTR_ENABLE(vector_no, val) is used to set/clear the individual PIEx interrupt enable mask register bit, where 'vector_no' is the number of the IRQ (can be one of the constants from the device file),
and 'val' is the PIRx bit setting... '0' disables the interrupt, and '1' enables the interrupt.
to:
The PIC18 interrupt structure is managed using two settings: each peripheral has its own interrupt enable bit located in the PIEx registers, and the global interrupt enable bit GIE (or GIE and GIEL if using interrupt priorities).

INTR_ENABLE(vector_no, val) is used to set/clear the individual PIEx interrupt enable mask register bit, where 'vector_no' is the number of the IRQ (can be one of the constants from the device file), and 'val' is the PIRx bit setting... '0' disables the interrupt, and '1' enables the interrupt.
Added line 261:
Changed lines 263-264 from:
while ENABLE_INTERRUPT(priority)/DISABLE_INTERRUPT(priority) can be used for either GIE or GIEL depending on the 'priority' value setting...
0=GIEL (low-priority) and 1=GIE (high-priority). You can also use the LOW_PRIORITY/HIGH_PRIORITY constant values.
to:
while ENABLE_INTERRUPT(priority)/DISABLE_INTERRUPT(priority) can be used for either GIE or GIEL depending on the 'priority' value setting... 0=GIEL (low-priority) and 1=GIE (high-priority). You can also use the LOW_PRIORITY/HIGH_PRIORITY constant values.
Added lines 1-746:
!!!Using Vectored Interrupts with Swordfish

Many new 18F devices include a Vectored Interrupt Controller (VIC) module, available on the XV18 core devices.
Vectored Interrupts bring new enhancements to the interrupt scheme used in current 18F designs, improving speed
and flexability.

!!!Traditional Interrupts

Traditional 18F devices support up to two interrupt vectors: a high priority and a low priority vector.
These interrupt vectors are located in low memory at $0008 and $0018, and are fully supported in Swordfish
using the Enable, Disable, and Interrupt statements. See the Users Manual/online Help section on Interrupts for more details.
Also, check out the article "PIC 18F Interrupts in Swordfish" https://www.sfcompiler.co.uk/wiki/pmwiki.php?n=SwordfishUser.Interrupts by RadioT for some additional tips.

One of the issues with the two-vector approach is once you have more than two interrupts you must begin to add code
to detect which peripheral is generating the interrupt request. This adds additional latency and complexity to the ISR.
Here's an example of what a traditional interrupt handler with three different interrupt sources might look like:
=code [=
interrupt HighPriorityISR()
    // save context
   
    if (INTCON.TMR0IF = 1) and (INTCON.TMR0IE = 1) then
        // TMR0 isr code
        INTCON.TMR0IF = 0
    endif
   
    if (PIR1.TMR1IF = 1) and (PIE1.TMR1IE = 1) then
        // TMR1 isr code
        PIR1.TMR1IF = 0
    endif
   
    if (PIR2.TMR3IF = 1) and (PIE2.TMR3IE = 1) then
        // TMR3 isr code
        PIR2.TMR3IF = 0
    endif
   
    // restore context
end interrupt
=]

In this case, saving and restoring the interrupt context becomes much more challenging. It also means that you have to move the
interrupt routine to outside the .bas module that uses it since you likely have to reference three different source modules,
breaking the modular approach used in existing Swordfish modules.


!!!Vectored Interrupts

Vectored interrupt mode departs from the traditional scheme. While it still provides for two interrupt priorities (high and low),
the interrupt priority no longer sets the vector address as it does in legacy mode. Instead, each interrupt source is assigned its own individual vector number, from 0 to 255.
This allows interrupts to have a unique ISR handler so you no longer need code like the above to check the IF and IE flags.
Since ISRs are no longer shared among modules, you can move the interrupt handler code back into the .bas module that uses it.
This is all accomplished through the Interrupt Vector Table (IVT). Vectored interrupts are enabled via the Multi-Vector Enable bit (MVECEN)
in the config setting. For compatability with existing Swordfish modules, the device include file for XV18 core devices sets MVECEN off
so by default interrupts operate in traditional legacy mode.

!!!IVT
The IVT is a table of up to 256 entries, one for each IRQ vector number. Each entry is a 16-bit word that contains the address (more on this later)
of the interrupt handler for that vector number.  The IVT resides in ROM so it must be fixed at compile-time, however you can
set the base address of the IVT using the IVTBASEU/H/L registers at run-time. This allows the code to setup mutiple IVT tables
and switch between them if desired.

When an interrupt occurs, the processor uses the interrupt vector number 0-255 as an index into the IVT. It retrieves the word address from the table,
shifts it left by 2 (multiplying by 4), and branches to that address. Why does it shift the address? The IVT holds a 16-bit word, which only allows a value up to 64K.
Multiplying the word address from the table by 4 allows the vector to point to up to 256K bytes of code address space.

The downside to this is that the ISR code MUST be located on a 4-byte address boundary, but Swordfish has no built-in mechanism to enforce this restriction.
The ENTER_INTR_HANDLER() macro will add padding to the start of the interrupt handler routine if needed to adjust the value used
to set the IVT entry point, making it compatible with the word-length address entry requirement.

The number of available interrupt vectors varies with the device, but it typically ranges from 82 to 128. The IVT must include entries
for all interrupt vector numbers up to the highest vector used by your program. Since an IVT entry requires a word value,
a complete table of 128 entries requires 128*2 = 256 bytes of ROM space. The max number of IRQs and the IRQ vector number definitions
themselves can be found in the device .bas file located in the compiler 'Includes' folder.
For example, here are the first few interrupt vector number entries from the 18F26K42.bas file:
=code [=
// interrupt vectors
#define _intvectors = 82              // interrupt vector table entries
public const
  IRQ_SWINT = 0,                        // irq 0 - Software Interrupt
  IRQ_HLVD = 1,                          // irq 1 - HLVD Interrupt
  IRQ_OSF = 2,                          // irq 2 - Oscillator Failure Interrupt
  IRQ_CSW = 3,                          // irq 3 - Clock Switch Interrupt
  IRQ_NVM = 4,                          // irq 4 - NVM Interrupt
=]


!!!IVT.bas

This new module contains macros and routines that can be used to setup and manage the IVT and interrupts when using vectored interrupts.
Replacements for the standard Enable, Disable, and Interrupt statements are provided and must be used in place of these original functions when
operating in VIC mode. Including IVT.bas will automatically set the MVECEN config setting to ON, enabling multi-vectored mode.
Before we look at creating an IVT, let's look at the required format for an ISR interrupt handler.

!!!Interrupt Handler

Interrupt handlers replace the traditional 'interrupt' routines, and must be declared using the 'event' attribute.
They should follow the basic format:
=code [=
    public event isr_XXX_handler()            // user-assigned interrupt event handler name
        ENTER_INTR_HANDLER(isr_XXX_handler)  // parameter must match assigned 'event' name above

        // user code goes here

        // set irq flag to 0 (clears IF)
        INTR_REQ(IRQ_XXX, 0)                  // IRQ_XXX = irq_no from device file list

        EXIT_INTR_HANDLER()
    end event
=]

ENTER_INTR_HANDLER() and EXIT_INTR_HANDLER() are required to mark the beginning and end of the ISR.
Each ISR event must have a unique name, and the event name ('isr_XXX_handler' in this example) must be used as the parameter for ENTER_INTR_HANDLER.
ENTER_INTR_HANDLER will make any required adjustments to the code offset and will set the interrupt entry point.
This marks the first executable statement in the handler, so you cannot use variable declarations that include an initializer since they would be skipped.

Before exiting the ISR you should clear the source of the interrupt request. This can be done using the INTR_REQ() macro as
shown above to set the IF flag to 0.

EXIT_INTR_HANDLER() will perform a 'RETFIE 1', restoring the hardware context and reenabling interrupts.


!!!Interrupt Priorities

The 18F interrupt mechanism can be set to use a single priority for all interrupts or to allow the use of high and low priority setting
through the IPEN register flag. The default setting is IPEN = 0 (all interrupts are high-priority), but this can be set
using the ENABLE_INTR_PRIORITY() and DISABLE_INTR_PRIORITY() macros. Unless you have the need to allow a high-priority interrupt
to be able to interrupt a running low-priority one, it is recommended to leave the interrupt priority setting at its default setting.

If interrupt priority is enabled then each interrupt can be assigned as either low (priority=0) or high (priority=1).
The individual priorities are set using INTR_PRIORITY(irq_no, priority), which defaults to high-priority if left unchanged.
INTR_PRIORITY() sets/clears the respective bit in the IPRx register for the given irq_no.


!!!Interrupt context

There are two levels of hardware shadow registers for saving context, one for high-priority interrupts (priority 1) and one for low-priority interrupts (priority 0)
In addition to the program counter (PC), the hardware context automatically saves the registers:
  STATUS, WREG, BSR, FSR0/1/2, PRODL/H and PCLATH/U

The only important registers it does not save are the TBLPTRU/H/L, which you should save/restore if accessing const data or strings in the ISR.
This can be done using the SAVE_CONTEXT() and RESTORE_CONTEXT() macros as shown below:
=code [=
    // IRQ_TMR1
    public event tmr1_handler()
        ENTER_INTR_HANDLER(tmr1_handler)

        // save TABLEPTR (for const data or string access)
        SAVE_CONTEXT()                    // high-priority

        // user code goes here
        INTR_REQ(IRQ_TMR1, 0)          // clear TMR1IF

        RESTORE_CONTEXT()
        EXIT_INTR_HANDLER()
    end event
=]

There are three different implementations of SAVE_CONTEXT/RESTORE_CONTEXT, and their usage depends on the global INTR_PRIORITY enable setting...
    SAVE_CONTEXT()            // high-priority only
    SAVE_CONTEXT(priority)    // high or low... change 'priority' to match IRQ priority setting... 0=low, 1=high
    SAVE_CONTEXT_ISS()        // use run-time Interrupt State Status from INTCON1 (uses more code)

SAVE_CONTEXT_ISS can determine the priority state at runtime, but it will generate more code and is slower.
This might be useful where the interrupt priority for a handler is dynamically changed and not fixed at compilation.

If the ISR uses system library functions or calls other routines you may also need to save the system library variables
and/or the frame context. This can be done using 'save/restore' statement blocks just as with traditional interrupts.
The only restriction with using a traditional save/restore block is that it must be used outside of any SAVE_CONTEXT/RESTORE_CONTEXT macros.

Here is a full template for a generic ISR event handler
=code [=
public event handler_name()
    // << variable declarations here... do NOT use initializers >>

    // define interrupt entry point
    ENTER_INTR_HANDLER(handler_name)    // change 'handler_name' to match 'public event' name

    // save SF system variable context (if req'd, must be before SAVE_CONTEXT)
    save(0)

    // save TABLEPTR registers (if req'd, for const data/string access)
    SAVE_CONTEXT(priority)              // change 'priority' to match IRQ priority setting... 0=low, 1=high

    // << user code goes here >>

    // clear IF
    INTR_REQ(irq_no, 0)                // chnage 'irq_no' to match intr vector number
   
    // restore TABLEPTR (if saved above)
    RESTORE_CONTEXT(priority)          // high/low... change 'priority' to match... 0=low, 1=high

    // restore SF system variable context (if saved above)
    restore

    // exit interrupt
    EXIT_INTR_HANDLER()
end event
=]

!!!Creating an IVT

To create an interrupt vector table in program memory you first use CREATE_IVT(), then add a series of SET_IVT_HANDLER() entries
for every IRQ used by your program, and finally mark the end of the table using END_IVT().

CREATE_IVT(table_name) is used to initialize the vector table database settings and assign a unique name to the table.

Assigning a name to the table does two things: it provides a table name which is used when programming the IVTBASE registers,
and also allows you to have multiple tables and switch between them at run-time. The SET_IVTBASE(table_name) macro will load the
registers with the address of the specified table_name.

SET_IVT_HANDLER() updates the vector table database for an individual IRQ with the address of an interrupt handler.
The format for adding a handler is SET_IVT_HANDLER(irq_no, event_handler), where 'irq_no' is 0-255 (or one of the IRQ_xxx consts
from the device file), and 'event_handler' is the name assigned to the event routine by ENTER_INTR_HANDLER (ie 'tmr1_handler').

END_IVT() takes the vector table database information and builds the IVT in program memory.

An example of a simple IVT is shown below:
=code [=
CREATE_IVT(ivt_table)
SET_IVT_HANDLER(IRQ_TMR1, tmr1_handler)
END_IVT()
=]

This example shows the creation of two tables. In the second table TMR1 and TMR3 share the same interrupt handler rouitne.
=code [=
CREATE_IVT(ivt_table1)
SET_IVT_HANDLER(IRQ_TMR1, tmr1_handler)
SET_IVT_HANDLER(IRQ_TMR3, tmr3_handler)
END_IVT()

CREATE_IVT(ivt_table2)
SET_IVT_HANDLER(IRQ_TMR1, tmr_handler)
SET_IVT_HANDLER(IRQ_TMR3, tmr_handler)
END_IVT()
=]

You MUST have a call to SET_IVTBASE() as part of your initialization code to set the IVTBASEU/H/L registers before enabling any interrupts.


!!!Assigning a Default Interrupt Handler

Due to the manner in which the IVT is setup, there must be an entry in the table for every IRQ number, even if the interrupt is unused.
To simplify this, the IVT.bas module defines a default_interrupt_handler() routine which is used by CREATE_IVT when the vector table database
is initialized. The default_interrupt_handler() will perform a device reset for any unhandled interrupt requests.
This is all done automatically, so nothing further needs to be done by the user.

You can override the default interrupt handler and provide your own routine if desired. To do this, set '#option _IVT_DEFAULT_HANDLER=false'
and create a public event routine named 'default_interrupt_handler()'. When you create the IVT, you must register the routine using SET_IVT_HANDLER.
If you list default_interrupt_handler first then the irq_no used is unimportant, and your routine will now be used for all unspecified interrupts.

Example of overriding the Default Interrupt handler
=code [=
// ivt.bas contains a default_intr_handler() routine which is defined by default
// set '#option _IVT_DEFAULT_HANDLER = false' to override the default routine in ivt.bas
// and use our own local routine
#option _IVT_DEFAULT_HANDLER = false
include "ivt.bas"


// our local default_intr_handler()
#if (_IVT_DEFAULT_HANDLER = false)
public event default_interrupt_handler()
    dim irq_no as byte
   
    // define interrupt entry point
    ENTER_INTR_HANDLER(default_interrupt_handler)

    // on entry, WREG contains the interrupt number
    irq_no = WREG

    // clear IF in PIRx
    // this is an example of using 'irq_no' to determine
    // which PIRx register and bit to clear
    FSR0 = addressof(PIR0)
    FSR0 = FSR0 + (irq_no >> 3)            // get PIRx reg addr
    PRODL = byte(1 << ((irq_no) mod 8))    // create bit mask
    INDF0 = INDF0 and not(PRODL)            // and clear the bit

    // exit interrupt
    EXIT_INTR_HANDLER()
end event
#endif      // _IVT_DEFAULT_HANDLER


CREATE_IVT(ivt_table)
SET_IVT_HANDLER(0, default_interrupt_handler)        // register our default handler
SET_IVT_HANDLER(IRQ_TMR1, tmr1_handler)
END_IVT()
=]

!!!Enabling and Disabling Interrupts

The PIC18 interrupt structure is managed using two settings: each peripheral has its own interrupt enable bit located in the PIEx registers,
and the global interrupt enable bit GIE (or GIE and GIEL if using interrupt priorities).

INTR_ENABLE(vector_no, val) is used to set/clear the individual PIEx interrupt enable mask register bit, where 'vector_no' is the number of the IRQ (can be one of the constants from the device file),
and 'val' is the PIRx bit setting... '0' disables the interrupt, and '1' enables the interrupt.

The 'enable()/disable()' keywords used in traditional interrupts are replaced by the ENABLE_INTERRUPT()/DISABLE_INTERRUPT() macros.
The global/high-priority GIE bit can be set/cleared using ENABLE_INTERRUPT()/DISABLE_INTERRUPT(),
while ENABLE_INTERRUPT(priority)/DISABLE_INTERRUPT(priority) can be used for either GIE or GIEL depending on the 'priority' value setting...
0=GIEL (low-priority) and 1=GIE (high-priority). You can also use the LOW_PRIORITY/HIGH_PRIORITY constant values.

If using interrupt priorities you must enable both GIE and GIEL, and note that setting GIE=0 will disable both priorities.
=code [=
ENABLE_INTERRUPT(0)        // sets GIEL=1 to enable low-priority interrupts
ENABLE_INTERRUPT(1)        // sets GIE=1, enabling both high and low priorities since GIEL is set

DISABLE_INTERRUPT()    // sets GIE=0, disabling all interrupts
=]

!!!Wrapping it up - Some examples

!!!Example 1 - single interrupt


=code [=
// simple VIC example using TMR1
program example1

device = 18F27K42
clock = 64

include "intosc.bas"

include "ivt.bas"

dim LED as PORTC.3        // user LED, active high

//----------------------------------------------------------------------------
// IRQ_TMR1
//----------------------------------------------------------------------------
public event tmr1_handler()
    ENTER_INTR_HANDLER(tmr1_handler)

    // user code goes here
    toggle(LED)

    INTR_REQ(IRQ_TMR1, 0)  // clear TMR1IF

    EXIT_INTR_HANDLER()
end event

//----------------------------------------------------------------------------
// setup TMR1 to use 500KHz MFINTOSC
//----------------------------------------------------------------------------
sub init_timer()
    // OSCEN register MFINTOSC bit
    const MFOEN = 5

    // enable MFINTOSC 500khz for TMR1 and TMR3
    OSCEN.bits(MFOEN) = 1

    // setup TMR1
    // at 500KHz w/1:8 prescaler TMR wraps in 2us * 8 * 65536 = 1048576us (approx 1 sec) 
    T1CON = %00110010      // 1:8, RD16
    T1CLK = %00000101      // MFINTOSC (500khz)
    TMR1H = 0
    TMR1L = 0
end sub

//----------------------------------------------------------------------------
// main program entry begins here
//----------------------------------------------------------------------------
main:
   
low(LED)
init_timer()

// set IVTBASE registers
SET_IVTBASE(ivt_table)

// enable PIE bits
INTR_ENABLE(IRQ_TMR1, 1)

// enable high-priority interrupts
ENABLE_INTERRUPT()

// start TMR1
T1CON.bits(0) = 1

// repeat forever... LED should toggle every sec
while (true)
end while

//----------------------------------------------------------------------------
// IVT table
//----------------------------------------------------------------------------

// create the primary IVT and add interrupt handlers to the table
CREATE_IVT(ivt_table)
SET_IVT_HANDLER(IRQ_TMR1, tmr1_handler)
END_IVT()

end program
=]


!!!Example 2 - two interrupts, low and high priority w/context save

=code [=
// VIC example using TMR1 and TMR3, low and high priority
program example2

device = 18F27K42
clock = 64

include "intosc.bas"

include "ivt.bas"

dim LED as PORTC.3        // user LED, active high

//----------------------------------------------------------------------------
// IRQ_TMR1
//----------------------------------------------------------------------------
public event tmr1_handler()
    ENTER_INTR_HANDLER(tmr1_handler)

    // user code goes here
    LED = 1
    T1CON.bits(0) = 0      // stop TMR1
    T3CON.bits(0) = 1      // start TMR3
   
    INTR_REQ(IRQ_TMR1, 0)  // clear TMR1IF

    EXIT_INTR_HANDLER()
end event

//----------------------------------------------------------------------------
// IRQ_TMR3 (low-priority w/context save example)
//----------------------------------------------------------------------------
public event tmr3_handler()
    ENTER_INTR_HANDLER(tmr3_handler)

    // save SF system variable context (must be before SAVE_CONTEXT)
    save(0)
    // save TABLEPTR registers
    SAVE_CONTEXT(LOW_PRIORITY)

    // user code goes here
    LED = 0
    T3CON.bits(0) = 0      // stop TMR3
    T1CON.bits(0) = 1      // start TMR1
   
    INTR_REQ(IRQ_TMR3, 0)  // clear TMR3IF

    // restore TABLEPTR registers
    RESTORE_CONTEXT(LOW_PRIORITY)
    // restore SF system variable context
    restore

    EXIT_INTR_HANDLER()
end event

//----------------------------------------------------------------------------
// setup TMR1 and TMR to use 500KHz MFINTOSC
//----------------------------------------------------------------------------
sub init_timers()
    // OSCEN register MFINTOSC bit
    const MFOEN = 5

    // enable MFINTOSC 500khz for TMR1 and TMR3
    OSCEN.bits(MFOEN) = 1

    // setup TMR1
    // at 500KHz w/1:8 prescaler TMR wraps in 2us * 8 * 65536 = 1048576us (approx 1 sec) 
    T1CON = %00110010      // 1:8, RD16
    T1CLK = %00000101      // MFINTOSC (500khz)
    TMR1H = 0
    TMR1L = 0

    // setup TMR3
    // at 500KHz w/1:4 prescaler TMR wraps in 2us * 4 * 65536 = 524288us (approx 1/2 sec) 
    T3CON = %00100010      // 1:4, RD16
    T3CLK = %00000101      // MFINTOSC (500khz)
    TMR3H = 0
    TMR3L = 0
end sub

//----------------------------------------------------------------------------
// main program entry begins here
//----------------------------------------------------------------------------
main:
   
low(LED)
init_timers()

// set IVTBASE registers
SET_IVTBASE(ivt_table)

ENABLE_INTR_PRIORITY()
INTR_PRIORITY(IRQ_TMR1, HIGH_PRIORITY)
INTR_PRIORITY(IRQ_TMR3, LOW_PRIORITY)

// enable PIE bits
INTR_ENABLE(IRQ_TMR1, 1)
INTR_ENABLE(IRQ_TMR3, 1)

// enable interrupts
ENABLE_INTERRUPT(LOW_PRIORITY)
ENABLE_INTERRUPT()

// start TMR1
T1CON.bits(0) = 1

// repeat forever...
while (true)
end while

//----------------------------------------------------------------------------
// IVT table
//----------------------------------------------------------------------------

// create the primary IVT and add interrupt handlers to the table
CREATE_IVT(ivt_table)
SET_IVT_HANDLER(IRQ_TMR1, tmr1_handler)
SET_IVT_HANDLER(IRQ_TMR3, tmr3_handler)
END_IVT()

end program
=]


!!!Example 3 - two interrupts, two IVT tables, local default interrupt handler

=code [=
// VIC example using TMR1 and TMR3
// - two IVT tables
// - replace default_intr_handler
program example3

device = 18F27K42
clock = 64

include "intosc.bas"

// ivt.bas contains a default_intr_handler() routine which is enabled by default
// if you wish to override that routine with your own handler, then:
//  - set '#option _IVT_DEFAULT_HANDLER = false'
//  - define a replacement event 'public event default_interrupt_handler()' routine
//  - add at least one reference to the new routine between CREATE_IVT/END_IVT using
//      SET_IVT_HANDLER(<unused irq_no>, default_interrupt_handler)
//    if you add it first then it doesn't matter what irq_no you use
#option _IVT_DEFAULT_HANDLER = false
include "ivt.bas"

// set this option true to test using the default handler
#option TEST_DEFAULT_HANDLER = true
const UNUSED_IRQ = NUM_INT_VECTORS-1    // set an unused IRQ number


dim LED as PORTC.3        // user LED, active high

//----------------------------------------------------------------------------
// override the default_intr_handler() in ivt.bas with our own local routine
//----------------------------------------------------------------------------
#if (_IVT_DEFAULT_HANDLER = false)
// default interrupt handler
public event default_interrupt_handler()
    dim irq_no as byte
   
    // define interrupt entry point
    ENTER_INTR_HANDLER(default_interrupt_handler)

    // on entry, WREG contains the interrupt number
    irq_no = WREG

    // clear IF in PIRx
    // this is an example of using 'irq_no' to determine
    // which PIRx register and bit to clear
    FSR0 = addressof(PIR0)
    FSR0 = FSR0 + (irq_no >> 3)            // get PIRx reg addr
    PRODL = byte(1 << ((irq_no) mod 8))    // create bit mask
    INDF0 = INDF0 and not(PRODL)            // and clear the bit

    // turn on led
    LED = 1
   
    // exit interrupt
    EXIT_INTR_HANDLER()
end event
#endif      // _IVT_DEFAULT_HANDLER

//----------------------------------------------------------------------------
// IRQ_TMR1
//----------------------------------------------------------------------------
public event tmr1_handler()
    ENTER_INTR_HANDLER(tmr1_handler)

    // user code goes here
    LED = 1
    T1CON.bits(0) = 0      // stop TMR1
    T3CON.bits(0) = 1      // start TMR3
   
    INTR_REQ(IRQ_TMR1, 0)  // clear TMR1IF

    EXIT_INTR_HANDLER()
end event

//----------------------------------------------------------------------------
// IRQ_TMR3
//----------------------------------------------------------------------------
public event tmr3_handler()
    ENTER_INTR_HANDLER(tmr3_handler)

    // user code goes here
    LED = 0
    T3CON.bits(0) = 0      // stop TMR3
    T1CON.bits(0) = 1      // start TMR1
   
    INTR_REQ(IRQ_TMR3, 0)  // clear TMR3IF

    EXIT_INTR_HANDLER()
end event

//----------------------------------------------------------------------------
// common handler for both tmr1 and tmr3 (using vector table 2)
//----------------------------------------------------------------------------
public event tmr_handler()
    dim irq_no as byte

    ENTER_INTR_HANDLER(tmr_handler)

    // WREG contains the interrupt number
    irq_no = WREG

    if (irq_no = IRQ_TMR1) then
        INTR_REQ(IRQ_TMR1, 0)  // clear TMR1IF

        LED = 0                // turn off led
        T1CON.bits(0) = 0      // stop TMR1
        T3CON.bits(0) = 1      // start TMR3
    elseif (irq_no = IRQ_TMR3) then       
        INTR_REQ(IRQ_TMR3, 0)  // clear TMR3IF

        LED = 1                // turn on led
        T3CON.bits(0) = 0      // stop TMR3
        T1CON.bits(0) = 1      // start TMR1
    endif
   
    EXIT_INTR_HANDLER()
end event

//----------------------------------------------------------------------------
// setup TMR1 and TMR to use 500KHz MFINTOSC
//----------------------------------------------------------------------------
sub init_timers()
    // OSCEN register MFINTOSC bit
    const MFOEN = 5

    // enable MFINTOSC 500khz for TMR1 and TMR3
    OSCEN.bits(MFOEN) = 1

    // setup TMR1
    // at 500KHz w/1:8 prescaler TMR wraps in 2us * 8 * 65536 = 1048576us (approx 1 sec) 
    T1CON = %00110010      // 1:8, RD16
    T1CLK = %00000101      // MFINTOSC (500khz)
    TMR1H = 0
    TMR1L = 0

    // setup TMR3
    // at 500KHz w/1:4 prescaler TMR wraps in 2us * 4 * 65536 = 524288us (approx 1/2 sec) 
    T3CON = %00100010      // 1:4, RD16
    T3CLK = %00000101      // MFINTOSC (500khz)
    TMR3H = 0
    TMR3L = 0
end sub

//----------------------------------------------------------------------------
// main program entry begins here
//----------------------------------------------------------------------------
main:
   
low(LED)
init_timers()

// set IVTBASE registers
SET_IVTBASE(ivt_table)

// enable PIE bits
INTR_ENABLE(IRQ_TMR1, 1)
INTR_ENABLE(IRQ_TMR3, 1)

// enable high-priority interrupts
ENABLE_INTERRUPT()

// test default handler
#if (TEST_DEFAULT_HANDLER)
// enable and generate IRQ=UNUSED_IRQ
INTR_ENABLE(UNUSED_IRQ, 1)  // enable IRQ = UNUSED_IRQ
INTR_REQ(UNUSED_IRQ, 1)      // generate intr (should go to default_handler)
#endif

// start TMR1
T1CON.bits(0) = 1

// test TMR1 and TMR3 interrupts
// - start TMR1 (1 sec rollover)
// - TMR1 intr turns on LED and starts TMR3 (1/2 sec rollover)
// - TMR3 intr turns off LED and restarts TMR1
// - LED should go ON for 1/2 sec then OFF for 1 sec
// let it run for 10 secs
delayms(10000)

DISABLE_INTERRUPT()
// stop timers
T1CON.bits(0) = 0
T3CON.bits(0) = 0

// set TMR1 and TMR3 to use a single common handler
SET_IVTBASE(ivt_table2)

// clear IF bits
INTR_REQ(IRQ_TMR1, 0)
INTR_REQ(IRQ_TMR3, 0)

// enable high-priority interrupts
ENABLE_INTERRUPT()

// start TMR1
T1CON.bits(0) = 1

// repeat forever...
while (true)
end while

//----------------------------------------------------------------------------
// IVT table
//----------------------------------------------------------------------------

// create the primary IVT and add interrupt handlers to the table
CREATE_IVT(ivt_table)
SET_IVT_HANDLER(0, default_interrupt_handler)  // assign local default handler
SET_IVT_HANDLER(IRQ_TMR1, tmr1_handler)
SET_IVT_HANDLER(IRQ_TMR3, tmr3_handler)
END_IVT()

// create a second ivt table
// this one uses a single handler to alternate TMR1 and TMR3
CREATE_IVT(ivt_table2)
SET_IVT_HANDLER(IRQ_TMR1, tmr_handler)
SET_IVT_HANDLER(IRQ_TMR3, tmr_handler)
END_IVT()

end program
=]