Interrupts can be a scary and useful way to make your program really difficult to debug.
Interrupts are triggered by hardware events, either an I/O pin changing state or a timer timing out and so forth. If enabled (which by default they aren=t), an interrupt causes the processor to stop whatever it is doing and jump to a specific routine in the microcontroller called an interrupt handler.
Interrupts are not for the faint of heart. They can be very tricky to implement properly, but at the same time they can provide very useful functions. For example, an interrupt could be used to buffer serial input data behind the scenes while the main PICBASIC PRO™ program is off doing something else. (This particular usage would require a microcontroller with a hardware serial port.)
There are many ways to avoid using interrupts. Quickly polling a pin or register bit instead is usually fast enough to get the job done. Or you can check the value of an interrupt flag without actually enabling interrupts.
However, if you just gotta do it, here are some hints on how to go about it.
The PICBASIC PRO™ Compiler has two different mechanisms to handle interrupts. The first is simply to write the interrupt handler in assembler and tack it onto the front of a PBP program. The second method is to use the PICBASIC PRO™ statementON INTERRUPT. Each method will be covered separately, after we talk about interrupts in general.
9.1. Interrupts in General
When an interrupt occurs, the PICmicro stores the address of the next instruction it was supposed to execute on the stack and jumps to location 4. The first thing this means is that you need an extra location on the hardware stack, which is only 8 deep to begin with.
The PICBASIC PRO™ library routines can use up to 4 stack locations themselves. The remaining 4 (12 for PIC17Cxxx and 27 for PIC18Xxxx) are reserved forCALLs and nested BASIC GOSUBs. You must make sure that your GOSUBs are only nested 3 (11 for PIC17Cxxx and 26 for PIC18Xxxx) deep at most with no CALLs within them in order to have a stack location available for the return address. If your interrupt handler uses the stack (by doing a Call or GOSUB itself for example), you=ll need to have additional stack space available.
Once you have dealt with the stack issues, you need to enable the appropriate interrupts. This usually means setting the INTCON register. Set the necessary enable bits along with Global Interrupt Enable. For example:
INTCON = %10010000
enables the interrupt for RB0/INT. Depending on the actual interrupt desired, you may also need to set the PIE register.
Refer to the Microchip PICmicro data books for additional information on how to use interrupts. They give examples of storing processor context as well as all the necessary information to enable a particular interrupt. This data is invaluable to your success.
Finally, select the best technique with which to handle your particular interrupts.
9.2. Interrupts in BASIC
The easiest way to write an interrupt handler is to write it in PICBASIC PRO™ in conjunction with theON INTERRUPT statement. ON INTERRUPT tells PBP to activate its internal interrupt handling and to jump to your BASIC interrupt handler as soon as it can after receiving an interrupt. Which brings us the first issue.
UsingON INTERRUPT, when an interrupt occurs PBP simply flags the event and immediately goes back to what it was doing. It does not immediately vector to your interrupt handler. Since PBP statements are not re-entrant (PBP must finish the statement that is being executed before it can begin a new one) there could be considerable delay (latency) before the interrupt is handled.
As an example, lets say that the PICBASIC PRO™ program just started execution of aPause 10000 when an interrupt occurs. PBP will flag the interrupt and continue with the PAUSE. It could be up to 10 seconds later before the interrupt handler is executed. If it is buffering characters from a serial port, many characters will be missed.
To minimize the problem, use only statements that don=t take very long to execute. For example, instead of Pause 10000, use Pause 1 in a long FOR..NEXT loop. This will allow PBP to complete each statement more quickly and handle any pending interrupts.
If interrupt processing needs to occur more quicky than can be provided byON INTERRUPT, interrupts in assembly language should be used.
Exactly what happens whenON INTERRUPT is used is this: A short interrupt handler is placed at location 4 in the PICmicro. This interrupt handler is simply a Return. What this does is send the program back to what it was doing before the interrupt occurred. It doesn=t require any processor context saving. What it doesn=t do is re-enable Global Interrupts as happens using an Retfie.
A Call to a short subroutine is placed after each statement in the PICBASIC PRO™ program once anON INTERRUPT is encountered. This short subroutine checks the state of the Global Interrupt Enable bit. If it is off, an interrupt is pending so it vectors to the users interrupt handler. If it is still set, the program continues with the next BASIC statement, after which, the GIE bit is checked again, and so forth.
When theRESUME statement is encountered at the end of the BASIC interrupt handler, it sets the GIE bit to re-enable interrupts and returns to where the program was before the interrupt occurred. If RESUME is given a label to jump to, execution will continue at that location instead. All previous return addresses will be lost in this case.
DISABLEstops PBP from inserting the Call to the interrupt checker after each statement. This allows sections of code to execute without the possibility of being interrupted. ENABLE allows the insertion to continue.
ADISABLE should be placed before the interrupt handler so that it will not keep getting restarted every time the GIE bit is checked.
If it is desired to turn off interrupts for some reason afterON INTERRUPT is encountered, you must not turn off the GIE bit. Turning off this bit tells PBP an interrupt has happened and it will execute the interrupt handler forever. Instead set:
INTCON = $80
This disables all the individual interrupts but leaves the Global Interrupt Enable bit set.
9.3. Interrupts in Assembler
Interrupts in assembly language are a little trickier.
Since you have no idea of what the processor was doing when it was interrupted, you have no idea of the state of the W register, the STATUS flags, PCLATH or even what register page you are pointing to. If you need to alter any of these, and you probably will, you must save the current values so that you can restore them before allowing the processor to go back to what it was doing before it was so rudely interrupted. This is called saving and restoring the processor context.
If the processor context, upon return from the interrupt, is not left exactly the way you found it, all kinds of subtle bugs and even major system crashes can and will occur.
This of course means that you cannot even safely use the compiler=s internal variables for storing the processor context. You cannot tell which variables are in use by the library routines at any given time.
You should create variables in the PICBASIC PRO™ program for the express purpose of saving W, the STATUS register and any other register that may need to be altered by the interrupt handler. These variables should not be otherwise used in the BASIC program.
While it seems a simple matter to save W in any RAM register, it is actually somewhat more complicated. The problem occurs in that you have no way of knowing what register bank you are pointing to when the interrupt happens. If you have reserved a location in Bank0 and the current register pointers are set to Bank1, for example, you could overwrite an unintended location. Therefore you must reserve a RAM register location in each bank of the device at the same offset.
As an example, let's choose the 16C74(A). It has 2 banks of RAM registers starting at $20 and $A0 respectively. To be safe, we need to reserve the same location in each bank. In this case we will choose the first location in each bank. A special construct has been added to theVAR command to allow this:
wsave var byte $20 system
wsave1 var byte $a0 system
This instructs the compiler to place the variable at a particular location in RAM. In this manner, if the save of W "punches through" to another bank, it will not corrupt other data.
The interrupt routine should be as short and fast as you can possibly make it. If it takes too long to execute, the Watchdog Timer could timeout and really make a mess of things.
The routine should end with an Retfie instruction to return from the interrupt and allow the processor to pick up where it left off in your PICBASIC PRO™ program.
The best place to put the assembly language interrupt handler is probably at the very beginning of your PICBASIC PRO™ program. This should ensure that it is in the first 2K to minimize boundary issues. AGOTO needs to be inserted before it to make sure it won=t be executed when the program starts. See the example below for a demonstration of this.
If the PICmicro has more than 2K of code space, an interrupt stub is automatically added that saves the W, STATUS and PCLATH registers into the variables wsave, ssave and psave, before going to your interrupt handler. Storage for these variables must be allocated in the BASIC program:
wsave var byte $20 system wsave1 var byte $a0 system ' If device has RAM in bank1 wsave2 var byte $120 system ' If device has RAM in bank2 wsave3 var byte $1a0 system ' If device has RAM in bank3 ssave var byte bank0 system psave var byte bank0 system
You must restore these registers at the end of your assembler interrupt handler. If the PICmicro has 2K or less of code space, the registers are not saved. Your interrupt handler must save and restore any used registers.
Finally, you need to tell PBP that you are using an assembly language interrupt handler and where to find it. This is accomplished with aDEFINE:
DEFINE INTHAND Label
Labelis the beginning of your interrupt routine. PBP will place a jump to this Label at location 4 in the PICmicro.
' Assembly language interrupt example led var PORTB.1 wsave var byte $20 system ssave var byte bank0 system psave var byte bank0 system Goto start ' Skip around interrupt handler ' Define interrupt handler define INTHAND myint ' Assembly language interrupt handler asm ; Save W, STATUS and PCLATH registers myint movwf wsave swapf STATUS, W clrf STATUS movwf ssave movf PCLATH, W movwf psave ; Insert interrupt code here ; Save and restore FSR if used bsf _led ; Turn on LED (for example) ; Restore PCLATH, STATUS and W registers movf psave, W movwf PCLATH swapf ssave, W movwf STATUS swapf wsave, F swapf wsave, W retfie endasm ' PICBASIC PRO™ program starts here start: Low led ' Turn LED off ' Enable interrupt on PORTB.0 INTCON = %10010000 loop: Goto loop ' Wait here till interrupted