ISRContext
There are a few excellent sources of information regarding the topic of interrupt context, and those are the Swordfish Manual / IDE help (see sections on Interrupts, Events, Frame Recycling, and Context Saving), and the PIC 18F Interrupts in Swordfish article by RadioT. If your device has a Vectored Interrupt Controller module (VIC) then you should also check out the article Using Vectored Interrupts with Swordfish for more details on vectored interrupts.
This article will discuss some additional information and hints to expand on those resources.
Note: To follow some of the the discussion below you may have to examine the asm output of the compiler. After compiling, open an IDE assembler view (F2 function key), or just open the .asm or .lst file with a text editor. Scroll down/search to find org 0x00, which is the program reset location. The statements following org 0x08 and org 0x18 show the vector locations for the high-priority and low-priority interrupt routines respectively.
ORG 0X00 ; program reset vector GOTO SBCDSTD ; main program startup ORG 0X08 ; high-priority intr vector BRA ISR_HIGHP_ISR_2 ; goto ipHigh handler ORG 0X18 ; low-priority intr vector BRA ISR_LOWP_ISR_5 ; goto ipLow handler
With regards to interrupts and context saving, there are a few topics to address. These are:
- processor registers
- save/restore blocks
- SF system variables/library functions
- subroutines and frame recycling
- events
- stack depth
Processor Registers
Interrupt handlers must save and restore all processor registers used in an ISR.
The standard 18F series devices have one level of hardware shadow registers for context saving. If a single ISR is declared in your program, the STATUS, WREG, and BSR are saved and restored in hardware shadow registers. If both high and low priority ISRs are declared within the same program, the high priority interrupt will save and restore STATUS, WREG, and BSR using the hardware shadow registers, but the low priority interrupt must save and restore STATUS, WREG, and BSR in software since there is only a single set of shadow registers.
The 18XV core devices (K42, K83, and most of the Q family) improve on this. In these devices there are two levels of hardware shadow registers, one each for high and low priority ISRs, and the shadow registers are greatly expanded to include: STATUS, WREG, BSR, FSR0/1/2, PRODL/H and PCLATH/U. You can identify the 18XV core devices from the device 18F*.bas include file entry #define _xv18 = 1.
The hardware shadow registers are restored at the end of an ISR via an asm RETFIE 1 instruction (aka RETFIE FAST), whereas an asm RETFIE instruction just re-enables interrupts and returns without restoring the shadow registers, which assumes that the registers were saved/restored in software.
ISR_SHADOW options
You can force the compiler to perform context saving in software for both high and low priority interrupts, rather than using the hardware shadow registers.
For standard 18F devices this is achieved via the ISR_SHADOW option:
// enable hardware shadow register usage... ipHIGH uses shadow registers, // ipLOW saved in software (this is the default setting if not specified) #option ISR_SHADOW = true // disable all hardware shadow register usage... ipHIGH and ipLOW saved in software #option ISR_SHADOW = false
For 18XV core devices, ISR_SHADOW has been augmented with two individual options- ISR_SHADOW_HIGH and ISR_SHADOW_LOW, which can be used to select the context save method for high and low priority interrupts respectively:
The default setting for these two options is the same as using #option ISR_SHADOW=true (ISR_SHADOW_HIGH=true, ISR_SHADOW_LOW=false), so for best performance with 18XV devices you should set both #option ISR_SHADOW_HIGH=true and #option ISR_SHADOW_LOW=true
#option ISR_SHADOW_HIGH = true // enables ipHIGH hardware shadow register usage #option ISR_SHADOW_LOW = true // enables ipLOW hardware shadow register usage
Here's an example of a typical asm output for a standard 18F low-priority intr using software context save. As you can see, it starts out with saving the STATUS, WREG, and BSR registers.
ISR_LOWP_ISR_5 MOVFF STATUS,F0_U08 MOVFF WREG,F1_U08 MOVFF BSR,F2_U08
Save/Restore blocks
The save statement can be used to save a variable, register, subroutine, or function context. Multiple items can be included as part of the save statement, and specifying a constant '0' will save the compilers system variables. Note that in addition to saving the system variables, save(0) will also save the FSR0, FSR1, and PRODH/PRODL processor registers. The save statement should typically be used on entry to the ISR, and restore called prior to exit. examples:
save(0) // save system registers, FSR0, FSR1, and PROD save(FSR2) // save FSR2 register (FSR2H/FSR2L) save(FSR0, FSR1, mysub) // save local variables for 'mysub' (see subroutine discussion below) save(0, FSR2, mysub) // save all of the above
SF Register Usage
PROD (PRODH/PRODL) - hardware multiply instruction result
- used by SF multiply statements (ie 'b = a * c'), but may also be used in computing the address of array elements, especially with arrays of structures (ie 'myStruct[ix].b = 3').
- some libraries may also use this as a temp variable.
- asm references: MULLW, MULWF instructions
FSR0/FSR1/FSR2 (FSRxH/FSRxL) - indirect RAM address pointer registers
- used with array, string, and structure variables located in RAM
- FSR0 and FSR1 may also be used when saving/restoring the interrupt context (system registers, subs/functions)
- these registers are also aliased as A0/A0H (FSR0), A1/A1H (FSR1), and A2/A2H (FSR2) by the compiler
- FSR2 is not typically used by the compiler itself, but it may be used by library modules.
- asm references: LFSR instruction and FSRxH/xL, INDFx, POSTINCx, POSTDECx, PREINCx, PLUSWx registers
TBLPTR (TBLPTRU/TBLPTRH/TBLPTRL) and TABLAT - table read/write address and data registers
- used to access const data/strings in PFM (program flash memory)
- lower 16-bits (TBLPTRH/TBLPTRL) aliased as TABLEPTR
- may also be used on some devices to access other special areas such as SAF and CONFIG data
- asm references: TBLRD, TBLWR instructions
PCLAT (PCLATH/PCLATU) - used by the SF event() mechanism to invoke/call an event handler
- PCLATH/PCLATU registers will automatically be saved in software if events are used, so these registers can be safely ignored.
The device include file defines aliases for the common 16-bit registers:
// sfr alias... public dim PROD as PRODL.AsWord, // PRODL, PRODH FSR0 as FSR0L.AsWord, // FSR0L, FSR0H FSR1 as FSR1L.AsWord, // FSR1L, FSR1H FSR2 as FSR2L.AsWord, // FSR2L, FSR2H TABLEPTR as TBLPTRL.AsWord // TBLPTRL, TBLPTRH public dim PRODW as word absolute $0FF3, FSR0W as word absolute $0FE9, FSR1W as word absolute $0FE1, FSR2W as word absolute $0FD9, TABLEPTRW as word absolute $0FF6
The table pointer registers (TBLPTRU/TBLPTRH/TBLPTRL) are used to access flash memory space. The 24-bit value can point to the full 16M address space of the 18F, although typically only the first 64K or 128K is used for the program memory space. Table read and write operations use the TABLAT register as the data register.
Normally, SF places const data and strings in the lower 64K of program memory and treats addresses as 16-bit values assuming TBLPTRU=0. Setting #option large_code_model=true can be used to extend this to the full 128K range, and the compiler will set TBLPTRU as appropriate. TABLEPTR is an alias for the lower 16-bits of the table pointer (TBLPTRH/TBLPTRL) and can be used accordingly.
For the larger 128K devices, or to access special areas of the flash memory space the full 24-bit address must be used. If #option large_code_model=true then you should use save(TBLPTRU, TBLPTRH, TBLPTRL, TABLAT), otherwise you can typically just use save(TABLEPTR, TABLAT) to save the TBLPTRH/TBLPTRL pointer and data registers.
Performing table operations is a two-step process, so it is important to save the TABLAT data register to avoid issues with being interrupted in the middle of a table read operation such as the following:
TBLRD* ; read into TABLAT MOVF TABLAT, W ; put TABLAT data previously read into WREG
For 16-bit registers such as FSRx and PROD you can specify either the individual 8-bit registers (ie 'PRODH, PRODL') or the 16-bit alias name ('PROD'), in which case you'll see references to the alias names ('PROD__PRODL#M0_U16H, PROD__PRODL#M0_U16') instead of the actual PRODH, PRODL registers.
To save ALL hardware registers, you can use save(FSR0, FSR1, FSR2, PROD, TBLPTRU, TBLPTRH, TBLPTRL, TABLAT). For the 18XV core devices this can be reduced to save(TBLPTRU, TBLPTRH, TBLPTRL, TABLAT) if using the hardware shadow registers.
SF system variables/library functions
SF reserves approx 25 bytes of ram as system variables for use by various internal library functions, such as multiply and divide routines. These variables are defined in the system.bas include file, and all begin with the prefix 'SB_SV'. System library functions themselves can be identified by the prefix 'SB_'. If your ISR uses any system libraries or calls routines that use them these system variables must be saved as part of the context save/restore via save(0).
Example of a system library call
dim a, b, c as longword // module-level static variables a = b * c // 32-bit multiply uses SF library call (SB_MULT32X32) // ASM output ?I000000_F000_000003_M000000 ; L#MK a = b * c MOVFF M4_U32HHH,SB_SV0HHH // 'SB_SVxxx' system library variable MOVFF M4_U32HH,SB_SV0HH MOVFF M4_U32H,SB_SV0H MOVFF M4_U32,SB_SV0 MOVFF M8_U32HHH,SB_SV2HHH MOVFF M8_U32HH,SB_SV2HH MOVFF M8_U32H,SB_SV2H MOVFF M8_U32,SB_SV2 RCALL SB_MULT32x32 // 'SB_xxxx' system library call MOVFF SB_SV0HHH,M0_U32HHH MOVFF SB_SV0HH,M0_U32HH MOVFF SB_SV0H,M0_U32H MOVFF SB_SV0,M0_U32
When viewing the asm output you will notice that the variable names do not seem to match up with the source code. The compiler changes variable names to ensure they are unique, and to allow frame variable placement. Variable names that begin with 'M' are module-level static variables, variable names beginning with 'F' are frame variables, and 'SB_SV' variables are SF system variables.
To aid in understanding some of the variable references, adding #option _showvar = true to your program will prefix the 'M' and 'F' names with your variable name, making it easier to see what's going on. This can, however, make it more difficult to distinguish module-level variables from frame variables.
#option _showvar = true // add variable name prefixes to the asm output dim a, b, c as longword // module-level static variables a = b * c // 32-bit multiply uses SF library call (SB_MULT32X32) // ASM output ?I000002_F000_000010_M000000 ; L#MK a = b * c // 32-bit multiply uses SF library call (SB_) MOVFF B_M4_U32HHH,SB_SV0HHH // variable names added as a prefix MOVFF B_M4_U32HH,SB_SV0HH MOVFF B_M4_U32H,SB_SV0H MOVFF B_M4_U32,SB_SV0 MOVFF C_M8_U32HHH,SB_SV2HHH MOVFF C_M8_U32HH,SB_SV2HH MOVFF C_M8_U32H,SB_SV2H MOVFF C_M8_U32,SB_SV2 RCALL SB_MULT32x32 MOVFF SB_SV0HHH,A_M0_U32HHH MOVFF SB_SV0HH,A_M0_U32HH MOVFF SB_SV0H,A_M0_U32H MOVFF SB_SV0,A_M0_U32
Module-level vs local variables (subroutines/functions) and frame recycling
Module-level variables (those declared outside any subroutine or function) are static in nature... they exist for the lifetime of the program, are not shared, and are safe to access in an ISR (ignoring the fact that you may still need to protect them from non-atomic access, but that's a different topic). These variables are identified by the 'M' prefix in their names.
Subroutine/function parameters, local, and temp variables are local to the routine and are part of the frame variables... they are accessible only while the procedure is executing. Frame recycling may result in these local variable locations being shared by other routines. If you call subroutines or functions in an ISR then these frame variables must be saved on entry to the ISR. To do this, add the name of the sub/function to the save list. You only need to specify the procedures that are called directly from the ISR... the compiler will account for any additional frame variables used by routines that they may call. However, if you have nested subroutine calls you must examine ALL the routines for processor register and/or system library usage and account for these in the save list. Look for any CALL or RCALL instructions in the ISR asm listing, or references to variable names beginning with 'F' (frame variables).
When adding a subroutine/function to the save list, you should also be aware that saving/restoring the local context of a procedure may involve the use the FSR0 and FSR1 registers. For small amounts of data (up to about 6 bytes) the context is saved directly. However, larger amounts of context data will use the FSR registers, which results in something like this to save the subroutine context:
LFSR 0,F11_U56 // FSR0 (dest) LFSR 1,F0_U56 // FSR1 (src) MOVLW 0X07 // number of bytes to copy MOVFF POSTINC1,POSTINC0 // loop copying src to dest DECFSZ WREG,1,0 BRA $ - 6
In this case, instead of just save(mysub) you would also have to save the FSR registers using either save(FSR0, FSR1, mysub) or save(0, mysub)
Events
SF events use an indirect call mechanism by modifying the PCLAT registers (PCLATU/PCLATH). If you use events in your program, SF will automatically add saving the PCLATU/PCLATH registers as part of the ISR entry so no further action is required.
ISR_LOWP_ISR_6 MOVFF STATUS,F0_U08 // software processor register context save MOVFF WREG,F1_U08 MOVFF BSR,F2_U08 MOVFF PCLATH,F3_U08 // PCLATx registers saved due to event usage MOVFF PCLATU,F4_U08
Any local variables defined in an event() routine are not shared with regular frame variables, even though they are identified as 'F'. They are effectively the same as a module-level static variable ('M').
Stack Depth
This isn't really related to context issues, but you should be aware that standard 18F devices limit the hardware stack to 31 entries, after which the stack 'overflows' and wraps (see device file #define _hwstackdepth). 18XV core devices extend this to a 127 level stack. If your ISR calls procedures, events, or uses system library calls you must account for this, especially if you have both high and low priority interrupts enabled. The sum total of the main code call depth + high_priority depth (+ low_priority depth) cannot exceed this, and the interrupt(s) counts as one additional level. The default device file setting CONFIG STVREN = ON will generate a reset if the stack overflows.
Conclusion
As you can see, saving the interrupt context can be a complex topic. When in doubt, the safest thing to do is just to save everything... save(0, FSR2, TBLPTRU, TBLPTRH, TBLPTRL, TABLAT, <list of all subroutines called>) However, this comes at a performance penalty that will impact interrupt latency. As a general rule of thumb, saving the system registers takes approx 6 cycles/byte, and saving the additional hdw registers could take another 32 cycles for a total of 182 instruction cycles. The restore is the same, so that's 364 cycles to save/restore the above, and that's not accounting for any user subroutines you might add to the list. Even at 64MHz that's an additional 22us penalty just for the context operations!
Code Examples
Here are some common operations and the typical assembler code output to demonstrate what resources might be used and what to watch out for. Note: in some of the examples I have removed asm output to make it easier to see the instructions of interest.
example 1 - array in ram using a variable as index
dim barray(32) as byte dim b, ix as byte ix = 1 b = barray(ix) // ASM output ?I000000_F000_000111_P000006 ; L#MK ix = 1 MOVLW 0X01 MOVWF IX_M33_U08,0 ?I000001_F000_000112_P000006 ; L#MK b = barray(ix) LFSR 1,M0_U32 // FSR1 MOVF IX_M33_U08,0,0 ADDWF A1,1,0 // A1 is an alias for FSR1 MOVLW 0X00 ADDWFC A1H,1,0 MOVFF INDF1,B_M32_U08 // INDF1 uses FSR1
Used in an ISR, the context would require save(FSR1)
example 2 - const data array in program memory using a variable as index
const carray(4) as byte = (1, 2, 3, 4) dim b, ix as byte ix = 1 b = carray(ix) // ASM output ?I000000_F000_000119_P000006 ; L#MK ix = 1 MOVLW 0X01 MOVWF IX_M1_U08,0 ?I000001_F000_000120_P000006 ; L#MK b = carray(ix) <code removed> MOVFF F0_ADRHH,TBLPTRU // const data access uses TBLPTR registers MOVFF F0_ADRH,TBLPTRH MOVFF F0_ADR,TBLPTRL TBLRD*+ // TBLRD use TBLPTR, TABLAT MOVFF TABLAT,B_M0_U08
Used in an ISR, the context would require save(TBLPTRU, TBLPTRH, TBLPTRL, TABLAT)
example 3 - string array in ram
dim sarray(10) as string dim ix as byte dim s as string ix = 1 s = sarray(ix) // ASM output ?I000000_F000_000130_P000006 ; L#MK ix = 1 MOVLW 0X01 MOVWF IX_M72_U08,1 ?I000001_F000_000131_P000006 ; L#MK s = sarray(ix) <code removed> MULLW 0X18 // MUL instruction uses PRODH, PRODL MOVFF PRODL,F28_U16 // PRODL MOVFF PRODH,F28_U16H // PRODH <code removed> MOVFF F24_ADRH,A0H // A0 is an alias for FSR0 MOVFF F24_ADR,A0 MOVFF F30_ADRH,A1H // A1 is an alias for FSR1 MOVFF F30_ADR,A1 MOVF POSTINC1,0,0 // POSTINC1 uses FSR1 BZ $ + 6 MOVWF POSTINC0,0 // POSTINC0 uses FSR0 BRA $ - 6 CLRF INDF0,0 // INDF0 uses FSR0
Used in an ISR, the context would require save(PRODH, PRODL, FSR0, FSR1)
example 4 - const string array in program memory
const csarray(5) as string = ("this ", "is ", "a ", "const ", "array") dim ix as byte dim s as string s = csarray(2) // fixed index ix = 1 s = csarray(ix) // variable index // ASM output ?I000000_F000_000141_P000006 ; L#MK s = csarray(2) // fixed index LFSR 0,M1_U192 // FSR0 MOVLW ((_DB_CSARRAY_0#16 >> 16) & 255) MOVWF TBLPTRU,0 // TBLPTRU, TBLPTRH, TBLPTRL MOVLW ((_DB_CSARRAY_0#16 >> 8) & 255) MOVWF TBLPTRH,0 MOVLW (_DB_CSARRAY_0#16 & 255) MOVWF TBLPTRL,0 RCALL SB_LOAD_MEMSTRL // system library call (uses TBLPTR, TABLAT and FSR0) CLRF INDF0,0 // INDF0 uses FSR0 ?I000001_F000_000142_P000006 ; L#MK ix = 1 MOVLW 0X01 MOVWF IX_M0_U08,0 ?I000002_F000_000143_P000006 ; L#MK s = csarray(ix) // variable index <code removed> MOVFF F24_ADRH,A0H // A0 is an alias for FSR0 MOVFF F24_ADR,A0 MOVFF F30_ADRHH,TBLPTRU // TBLPTRU, TBLPTRH, TBLPTRL MOVFF F30_ADRH,TBLPTRH MOVFF F30_ADR,TBLPTRL RCALL SB_LOAD_MEMSTRL // system library call (uses TBLPTR, TABLAT and FSR0) CLRF INDF0,0 // INDF0 uses FSR0
Used in an ISR, the context would require save(0, TBLPTRU, TBLPTRH, TBLPTRL, TABLAT)
example 5 - array of structures using a variable as index
structure mystruct_t b as byte w as word lw as longword end structure dim struct_array(10) as mystruct_t dim mystruct as mystruct_t dim ix as byte ix = 1 mystruct = struct_array(ix) mystruct.w = struct_array(ix).w // ASM output ?I000001_F000_000160_P000007 ; L#MK ix = 1 MOVLW 0X01 MOVWF IX_M81_U08,1 ?I000002_F000_000161_P000007 ; L#MK mystruct = struct_array(ix) MOVF IX_M81_U08,0,1 MULLW 0X07 // MUL instruction uses PRODH, PRODL MOVFF PRODL,F38_U16 // PRODL MOVFF PRODH,F38_U16H // PRODH LFSR 1,M4_U32 // FSR1 MOVF F38_U16,0,0 ADDWF A1,1,0 // A1 is an alias for FSR1 MOVF F38_U16H,0,0 ADDWFC A1H,1,0 LFSR 0,MYSTRUCT_M74_U56 // FSR0 MOVLW 0X07 MOVFF POSTINC1,POSTINC0 // POSTINC1 uses FSR1, POSTINC0 uses FSR0 DECFSZ WREG,1,0 BRA $ - 6 ?I000003_F000_000162_P000007 ; L#MK mystruct.w = struct_array(ix).w MOVF IX_M81_U08,0,1 MULLW 0X07 // MUL instruction uses PRODH, PRODL MOVFF PRODL,F38_U16 MOVFF PRODH,F38_U16H LFSR 1,M4_U32 // FSR1 MOVF F38_U16,0,0 ADDWF A1,1,0 // A1 is an alias for FSR1 MOVF F38_U16H,0,0 ADDWFC A1H,1,0 INFSNZ A1,1,0 INCF A1H,1,0 MOVFF POSTINC1,W_M75_U16 // POSTINC1 uses FSR1 MOVFF POSTINC1,W_M75_U16H
Used in an ISR, the context would require save(PRODH, PRODL, FSR0, FSR1)
example 6 - functions with local frame variables and system lib calls
function mult_lw(pA, pB as longword) as longword // 32-bit multiply uses SF library call (SB_MULT32X32) which uses the hdw MUL result = pA * pB // frame variables end function function mult_b(pA, pB as byte) as word // 8-bit multiply is typically done inline (no library call) result = pA * pB // frame variables end function // ASM output PROC_MULT_LW_0 ?I000000_F000_000006_P000001 ; L#MK result = pA * pB // frame variables MOVFF F0_U32HHH,SB_SV0HHH // 'SB_SVxxx' system library variable MOVFF F0_U32HH,SB_SV0HH MOVFF F0_U32H,SB_SV0H MOVFF F0_U32,SB_SV0 MOVFF F4_U32HHH,SB_SV2HHH MOVFF F4_U32HH,SB_SV2HH MOVFF F4_U32H,SB_SV2H MOVFF F4_U32,SB_SV2 RCALL SB_MULT32x32 // 'SB_xxxx' system library call (uses hdw MUL) MOVFF SB_SV0HHH,F8_U32HHH MOVFF SB_SV0HH,F8_U32HH MOVFF SB_SV0H,F8_U32H MOVFF SB_SV0,F8_U32 ?I000001_F000_000007_P000001 ; L#MK end function RETURN 0 PROC_MULT_B_2 ?I000002_F000_000011_P000002 ; L#MK result = pA * pB // frame variables MOVF F0_U08,0,0 MULWF F1_U08,0 // MUL instruction uses PRODH, PRODL MOVFF PRODL,F2_U16 MOVFF PRODH,F2_U16H ?I000003_F000_000012_P000002 ; L#MK end function RETURN 0
The function 'mult_lw()' uses a system library call and variables (SB_MULT32x32 and SB_SVxxx), a library call which uses the hdw MUL instruction (PRODH/PRODL registers), and local frame variables ('F'). If 'mult_lw' is called from an ISR, the context would require save(0, mult_lw)
The function 'mult_b()' does not use any system library calls or variables, but it does use hdw MUL instruction (PRODH/PRODL registers), and local frame variables ('F'). If 'mult_b' is called from an ISR, the context would require save(PRODH, PRODL, mult_b)