This is a tutorial on handling interrupts. It is an introduction with a primary focus on using either postive or negative edge triggered external transistions on input RB.0/INT to cause an interrupt. Future discussions will include discussions and examples of "wake-up" on change interrupts and timer/counter interrupts.
Overview of PIC Interrupts.
An interrupt is an event which forces a call to a subroutine which is usually referred to as an interrupt service routine. Typical sources of interrupts on the PIC include a positive or negative transition on the RB.0/INT input, a change on any of the inputs RB4 - RB7 or a timer / counter overflow from a value of FFH to 00H.
On interrupt, the processor saves the return address on the stack and program control is redirected to the interrupt service routine. It is important to note that aside from the return address no registers are saved. However the user may save such important registers as W and STATUS and this is discussed below.
On interrupt, program flow is directed to program location 004H. As a reset directs program flow to 000H, a program using interrupts is usually structured as follows;
ORG 000H ; a reset redirects program to this point GOTO MAIN ORG 004H ; an interrupt redirects the program to here GOTO INT_SERV MAIN: ; Your main program ; end of main INT_SERV: ; your interupt service routine
The user may control the sources of interrupts. For example;
BSF INTCON, INTE ; enable interupts on RB0/INT BSF INTCON, RBIE ; enable change in RB4 - RB7 interrupt BSF INTCON, TOIE ; enable timer interrupt
Any of these may be used alone, or several sources may be enabled, depending on your application.
At any time in the program, the user may turn any of the sources off by clearing these mask bits;
BCF INTCON, INTE BCF INTCON, RBIE BCF INTCON, TOIE
Note that on RESET, these bits are cleared.
On interrupt, a flag bit associated with the type of interrupt is set by the processor. If dealing with multiple interrupt sources, these flag bits may be used to determine the source of the interrupt and perform the appropriate action. For the moment, let's confine the discussion to a single interrupt source.
Finally, all interrupts may be enabled;
BSF INTCON, GIE ; global interrupt enable
BCF INTCON, GIE
I usually present my confused students with a analogy of a switch array;
|------ INTE ---- | Processor A --- GIE --- |------ RBIE ---- | |------ TOIE ----
Current can travel from A to the processor only if GIE is "closed" and at least one of the mask bits is closed. Mask bits may be closed, but if GIE is open, no current flows. Along the same line, the processor can only b interrupted if one of the mask bits is set and the GIE bit is set.
Thus, at some point in your routine, you are ready to accept interrupts. Set the appropriate mask bit (INTE, RBIE or TOIE) and set GIE. When no further interrupts are desired, either clear GIE or the mask bits or both.
Assume GIE is set and a mask bit is set. If an event then occurs, a flag bit assocociated with that interrupt is automatically set. This allows you to determine the source of the interrupt. Let's defer this for the moment.
However, it is important that you clear the appropriate flag bit in your interrupt service routine or the processor will fail to recognize that the interrupt request was serviced.
BCF INTCON, INTF ; clear external INT flag BCF INTCON, RBIF ; clear RB4 - RB7 interrupt flag BCF INTCON, TOIF ; clear timeout flag
Note that the occurrence of an interrupt event sets the flag bit (if GIE and the mask bit are set). It is the user's responsibility to clear the flag bit.
Failure to clear the flag bit is interpreted by the processor as "a previous interrupt occurred, but the user has yet to service the interrupt". Thus, although an interrupt event may occur, GIE may be enabled and a mask bit may be set, the actual interrupt will not occur unless the corresponding flag bit is clear.
On interrupt, the processor clears the GIE bit thus inhibiting any further interrupts. The bit is again set on execution of the return from interrupts (RETFIE). Note that the programmer need not perform this book keeping.
By now, you are undoubtedly hopelessly confused and the best advice I can give is to study examples with a constant referral back to these ground rules.
This program counts the number of positive transitions on input RB.0/INT and displays the current "count" on four LEDs on outputs RB.4 - RB.7.
The MAIN sets TRISB such that RB.0/INT is an input and all other bits on PORTB are outputs.
Note that the INTEDG bit in the OPTION_REG may be set such that the external interrupt occurs on the positive edge of a signal on RB.0 or cleared such that the interrupt occurs on the negative edge.
The INTE mask bit is set. Although the processor resets with all of the flag bits cleared, I decided to clear the flag bit associated with the external interrupt. (Be aware that not all processors boot as cleanly as Microchip's). Finally the GIE bit is set, enabling interrupts, and the program goes into a sleep mode. Note that the program counter now contains the address of the next instruction; the GOTO.
On interrupt, the program counter is saved and program execution is redirected to location 004H which jumps to the interrupt service routine. Note that the GIE bit is now cleared by the hardware, preventing any interrupts while in the interrupt service routine. (This might be a disadvantage as one might well miss an interrupt event while executing the interrupt service routine).
In the interrupt service routine, the counter is incremented and displayed on the LEDs. I opted to use a rather lengthy technique of setting or clearing each bit as I planned to use RB.3 for another purpose in the next example.
The flag bit associated with the external interrupt is cleared to let the processor know this interrupt request has been serviced.
The RETFIE does several things. It again enables the GIE bit and sets the program counter to its value prior to the interrupt; that is, the GOTO. This is all done by the hardware.
The processor again goes into a sleep mode until another positive transition occurs on input RB.0/INT.
; Program EXT_INT1.ASM ; ; Counts the number of positive transitions on input RB0/INT and ; displays on 4 LEDS on RB.4 - RB.7. ; ; Intended to illustrate how to use the external interrupt feature ; of the PIC. ; ; Copyright, Peter H. Anderson, H Paul Roach, August 29, '97 LIST p=16c84 #include <c:\mplab\p16c84.inc> __CONFIG 11H CONSTANT BASE_VAR=0CH COUNTER EQU BASE_VAR+0 ORG 000H GOTO MAIN ORG 004H GOTO INT_SERV MAIN: BSF STATUS, RP0 ; bank 1 MOVLW 1 MOVWF TRISB BCF STATUS, RP0 ; back to bank 0 CLRF COUNTER ; zero the counter BCF PORTB, 4 ; zero the LEDs BCF PORTB, 5 BCF PORTB, 6 BCF PORTB, 7 BSF OPTION_REG, INTEDG ; interrupt on positive BCF INTCON, INTF ; clear interrupt flag BSF INTCON, INTE ; mask for external interrupts BSF INTCON, GIE ; enable interrupts PT1: SLEEP GOTO PT1 INT_SERV: INCF COUNTER, F BTFSS COUNTER, 0 ; light the appropriate LEDs BCF PORTB, 4 BTFSC COUNTER, 0 BSF PORTB, 4 BTFSS COUNTER, 1 BCF PORTB, 5 BTFSC COUNTER, 1 BSF PORTB, 5 BTFSS COUNTER, 2 BCF PORTB, 6 BTFSC COUNTER, 2 BSF PORTB, 6 BTFSS COUNTER, 3 BCF PORTB, 7 BTFSC COUNTER, 3 BSF PORTB, 7 BCF INTCON, INTF ; clear the appropriate flag RETFIE ; this also set global interrupt enable END
This program extends the above to add a flashing LED on output RB.3. That is, rather than sleeping, the processor continually flashes this LED, all the time counting the number of positive transitions on input RB.0 and displaying the count on the four LEDs on outputs RB.4 - RB.7.
The MAIN is exactly the same, except that the SLEEP has been replaced with a routine to turn the LED on output RB.3 on and off at two pulses per second. While performing this, interrupts are enabled.
On interrupt, the processor finishes the instruction it is currently performing and the content of the program counter is saved and the program vectors location 004H and then to the interrupt service routine which is conceptually the same as that discussed above.
However, unlike the previous SLEEP, the flashing of the LEDs is representative of a process which might well use both the W register and the STATUS register. (Actually, my example is flawed as the STATUS register is not used, but I hope you get my point).
But, a typical interrupt service routine is quite likely to use both of these registers as well. (Again, my example is flawed!). Please bear with me.
We could have a potential problem in that the interrupt service routine might use both W and STATUS. Unfortunately, in executing RETFIE, the program will pick up wherever it left off, but the values of W and STATUS will not be the same as they were prior to interrupt. With the PIC, nothing other than the program counter is saved on interrupt.
Thus, it is the users responsibility to save W and STATUS on entering the interrupt service routine and to restore them prior to exiting the ISR.
This is trickier than it might appear. Note that instruction MOVWF does not affect any status bits. Thus, the W register is easily saved;
But, saving STATUS involves moving it to W and unfortunately, MOVF STATUS, W will change status bits. However, SWAPF STATUS, W doesn't. Thus, the STATUS register may be saved;
SWAPF STATUS, F SWAPF STATUS, W MOVWF STATUS_SAVE
Microchip sure is clever!
Prior to exiting, the registers are restored;
SWAPF STATUS_SAVE, F ; restore STATUS SWAPF STATUS_SAVE, W MOVWF STATUS SWAPF W_SAVE, F ; restore W SWAPF W_SAVE, W
Note that in my code, STATUS is actually saved with the high and low nibbles interchanged. This saves two SWAPF statements.
There is one other subtlety to be considered. At the time of the interrupt, the RP0 bit in STATUS may be configured for Register Bank 1. (Again, this is not true of my example). Thus, on entering the interrupt service routine, it is good practice to set the register bank as required. For example;
BCF STATUS, RP0
Note that on exiting the interrupt service routine, the appropriate register bank is restored with the restoring of STATUS.
; Program EXT_INT2.ASM ; ; Continually flashes LED on RB.3 250 msecs on and 250 msecs off. ; ; Counts the number of positive transitions on input RB0/INT and ; displays on 4 LEDS on RB.4 - RB.7. ; ; Intended to illustrate how to use the external interrupt feature ; of the PIC. ; ; Copyright, Peter H. Anderson, H. Pual Roach, August 29, '97 LIST p=16c84 #include <c:\mplab\p16c84.inc> __CONFIG 11H CONSTANT BASE_VAR=0CH COUNTER EQU BASE_VAR+0 LOOP1 EQU BASE_VAR+1 LOOP2 EQU BASE_VAR+2 W_SAVE EQU BASE_VAR+3 STATUS_SAVE EQU BASE_VAR+4 ORG 000H GOTO MAIN ORG 004H GOTO INT_SERV MAIN: BSF STATUS, RP0 ; bank 1 MOVLW 1 MOVWF TRISB BCF STATUS, RP0 ; back to bank 0 CLRF COUNTER ; zero the counter and the LEDs BCF PORTB, 4 BCF PORTB, 5 BCF PORTB, 6 BCF PORTB, 7 BSF OPTION_REG, INTEDG ; interupt on positive BCF INTCON, INTF ; clear interrupt flag BSF INTCON, INTE ; mask for external interrupts BSF INTCON, GIE ; enable interrupts FLASH: BCF PORTB, 3 ; turn off flashing LED CALL DELAY_250 BSF PORTB, 3 ; turn it on CALL DELAY_250 GOTO FLASH ; End of Main DELAY_250: ; 250 msec delay MOVLW .250 MOVWF LOOP2 DELAY_1: MOVLW .110 MOVWF LOOP1 INNER: NOP NOP NOP NOP NOP NOP NOP DECFSZ LOOP1, F GOTO INNER DECFSZ LOOP2, F GOTO DELAY_1 RETURN INT_SERV: MOVWF W_SAVE ; save W SWAPF STATUS, W ; save STATUS MOVWF STATUS_SAVE BCF STATUS, RP0 ; be sure we are in bank 0 INCF COUNTER, F BTFSS COUNTER, 0 ; light the appropriate LEDs BCF PORTB, 4 BTFSC COUNTER, 0 BSF PORTB, 4 BTFSS COUNTER, 1 BCF PORTB, 5 BTFSC COUNTER, 1 BSF PORTB, 5 BTFSS COUNTER, 2 BCF PORTB, 6 BTFSC COUNTER, 2 BSF PORTB, 6 BTFSS COUNTER, 3 BCF PORTB, 7 BTFSC COUNTER, 3 BSF PORTB, 7 BCF INTCON, INTF ; indicate that interrupt has been serviced SWAPF STATUS_SAVE, W MOVWF STATUS ; restore W and STATUS SWAPF W_SAVE, F SWAPF W_SAVE, W RETFIE END