9. Interrupts

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™ statement ON 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 for CALLs 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 the ON 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.

Using ON 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 a Pause 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 by ON INTERRUPT, interrupts in assembly language should be used.

Exactly what happens when ON 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 an ON 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 the RESUME 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.

DISABLE stops 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.

A DISABLE 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 after ON 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 the VAR 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. A GOTO 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 a DEFINE:


Label is 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
	; 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

	' 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