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:
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 IRQpriority setting
'SAVE_SYSTEM_CONTEXT(priority) // same as above... change 'priority' to match IRQ
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
'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 IRQpriority setting
'RESTORE_SYSTEM_CONTEXT(priority) // same as above... change 'priority' to match IRQ
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
'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 be able
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
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.
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:
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
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 [=
%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.
SAVE_CONTEXT() // high-priority
SAVE_CONTEXT_ISS can determine the priority state at runtime, but it will generate more code and is slower
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
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.
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
// 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)
// save TABLEPTR registers (if req'd, for const data/string access)
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
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
RESTORE_CONTEXT(
// restore SF system variable context (if saved above)
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
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.
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
// set '#option _IVT_DEFAULT_HANDLER = false' to override the default routine in ivt.bas
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)
// 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
#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)
SAVE_CONTEXT(HIGH_PRIORITY)
Added lines 424-426:
// restore
RESTORE_CONTEXT(HIGH_PRIORITY)
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)
save(0)
// save TABLEPTR registers
SAVE
to:
// save SF system variables and TABLEPTR registers
SAVE_SYSTEM_CONTEXT(LOW_PRIORITY)
SAVE_SYSTEM_CONTEXT(LOW_PRIORITY)
Changed lines 446-450 from:
// restore TABLEPTR registers
RESTORE_CONTEXT(LOW_PRIORITY)
// restore SF system variable context
restore
restore
to:
// restore
RESTORE_SYSTEM_CONTEXT(LOW_PRIORITY)
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
// - 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 thatroutine 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
// if you wish to override that
// - 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
// 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()
//
// 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'
'#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.
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 thantwo interrupts you must begin to add code
to detect which peripheral is generating the interrupt request. This adds additional latency and complexity tothe ISR.
One of the issues with the two-vector approach is once you have more than
to detect which peripheral is generating the interrupt request. This adds additional latency and complexity to
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.
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.
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.
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)
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.
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
of the interrupt handler for that vector number
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.
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 number of available interrupt vectors varies with the device, but it typically ranges from 82 to 128. The IVT must include entries
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.
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 anISR interrupt handler.
Before we look at creating an IVT, let's look at the required format for an
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.
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:
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 andreenabling interrupts.
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
EXIT_INTR_HANDLER() will perform a 'RETFIE 1', restoring the hardware context and
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 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
The individual priorities are set using INTR_PRIORITY
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.
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:
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:
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.
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.
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').
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,
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').
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.
is initialized. The default_interrupt_handler() will perform a device reset for any unhandled interrupt requests.
You can override the default interrupt handler and provide your own routine if desired. To do this, set '#option _IVT_DEFAULT_HANDLER=false'
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.
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.
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.
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.
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
=]
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
=]