I'm working on a couple of projects that use interrupts, specifically interrupt on change Port B. Many projects can be broken down into what I call "push a button/do something". Often times, the "somethings" have nothing do with each other (as was the case with this project). Those familiar with using IOC know that its a matter of trapping the interrupt, copying the IOCBF register, clearing the flags and getting out of the ISR. Once out of the ISR, review the resulting flags and do "whatever". For "real-time" response one then has to poll the IOCBF copy often enough to make the response time as short (or reasonable) as possible. Often times, one is left with the decision, do I just poll the port, or use interrupts? After consulting a friend (who's an expert coder) and digging in the data sheet (particularly the section dealing with the Stack) we came to the conclusion that as long as one "knows" exactly what the response to the interrupt is then there is no reason to exit the ISR with a RETFIE. This flies in the face of conventional programming, but if implemented correctly can dramatically change the response of the system. To demonstrate this I've attached a short piece of code. Be aware; this uses a combination of assembly programming and Pic Basic. It is rather straightforward and even if you don't "know" assembly you can look up the few commands used in the data sheet. I use "enhanced core" devices when possible; the benefit of the devices usually outweighs the additional cost. In this case I am using a PIC 16F1939. One of the benefits to using these devices is automatic context saving. If you use one of the baseline devices this will still apply (and work); you are responsible for context saving. This is all described in the Pic Basic manual so I wont go into detail here. One note; this can theoretically apply to other interrupts as well, however in those cases you would be responsible for handling the stack pointer (STKPTR) yourself. I may work out an example of that at a later date. Keep in mind; this really isn't for the faint of heart, and if you tend to do sloppy (spaghetti) programming this could turn into a disaster very quickly.

The code here is very simple: PortB.0-3 are setup as inputs with internal pullups. PortD.0-3 are outputs blinking some leds at different rates. I use a 4 line OLED (available at www.seetron.com) as a diagnostic tool. It is amazingly handy to have when developing; you can "SEROUT2" anywhere in a program to keep track of things - comment them out when done. When a pushbutton is pressed the interrupt is called, a bit test is done and "GOTO's" the appropriate routine. Each routine (in this case) is it's own little entity doing some specific function. As such, the code is very modular and if maintained diligently one section should have no bearing on any other. Each time a button is pushed the system responds "instantly". The code:
Code:
'******************************************************************************
'* Name                                                   *
'* Compiler: PICBASIC PRO Compiler microEngineering Labs                      *
'* Author  : Mike Tripoli                                                     *
'* Notes: Interrupt on Change (PORTB) ISR Routine                                                                   *
'* Date    : 04/02/2013                                                       *
'******************************************************************************


; DEVICE USED = PIC16F1939


#config
    __config _CONFIG1, _FOSC_INTOSC &_WDTE_OFF &_PWRTE_OFF &_MCLRE_ON &_CP_OFF &_CPD_OFF &_BOREN_OFF &_CLKOUTEN_ON &_IESO_OFF &_FCMEN_OFF
    __config _CONFIG2, _WRT_OFF &_VCAPEN_OFF &_LVP_OFF &_STVREN_OFF &_PLLEN_OFF
#endconfig


DEFINE  OSC  16             ; 16 mhz.
OSCCON  =   111000       ; 16MHz, CLKOUT = 2MHz
;            1-------   bit 7 = 1 PLL is on, bit7 = 0 PLL is off - i.e. bit7=1 then OSC is 4x WITH PLLEN_OFF
;             -0010--- = 31.25 kHz MF 0011 =31.25 kHz HF(1) bit 6-3     IRCF<3:0>: Internal Oscillator Frequency Select bits 000x =31 kHz LF       
;             -0100--- = 62.5 kHz MF      
;             -0101--- = 125 kHz MF      
;             -0110--- = 250 kHz MF      
;             -0111--- = 500 kHz MF (default upon Reset) 1000 =125 kHz HF(1) 1001 =250 kHz HF(1) 1010 =500 kHz HF(1)      
;             -1011--- = 1 MHz HF      
;             -1100--- = 2 MHz HF      
;             -1101--- = 4 MHz HF      
;             -1110--- = 8 MHz or 32 MHz HF(see Section 5.2.2.1 “HFINTOSC”) 1111 = 16 MHz HF      
;            -1111--- = 16 MHz HF 
;            -xxxx0-- = bit 2    Unimplemented: Read as ‘0’      
;            ------1x = Internal oscillator block bit 1-0     SCS<1:0>: System Clock Select bits      
;            ------01 = Timer1 oscillator      
;            ------00 = Clock determined by FOSC<2:0> in Configuration Word 1      


#HEADER
  errorlevel -303  ; suppress Program word too large
#ENDHEADER


;VARIABLES
RB_COPY     VAR     BYTE
 
;ALIASES


;PORT CONTROL REGISTERS
;PORTA
TRISA   =   111111        ; Set PORTA to all input
ANSELA =    000111       ; Set AN0-AN7 to digital with the exception of AN0-2


;PORTB
TRISB   =   001111       ; Set PORTB.0-3 to all INPUTS
ANSELB  =   000000       ; Set AN8 and higher channels to digital operation
IOCBN   =   001111       ; TRIGGER ON NEGATIVE GOING EDGE 
WPUB    =   001111       ; RB.0-3 PULLUP - WPUB7 WPUB6 WPUB5 WPUB4 WPUB3 WPUB2 WPUB1 WPUB0 


;PORTC
TRISC   =   000000        ' Set PORTC to all output


;PORTD
TRISD   =   000000
ANSELD  =   000000       ; PortD outputs


;PORTE
TRISE   =   111111
ANSELE =    000000


;ADC CONTROL REGISTERS       
ADCON0 =    000000       ' ADC disabled, AN0 selected 
ADCON1  =   010000       ' Clock Fosc/16 Should be 1uS
;            X-------        ADFM bit 7 1 = Right justified. 0 = Left justified, use ADRESH for data     
;            -XXX----        bit 6-4     ADCS<2:0>: A/D Conversion Clock Select bits      
;             000 =FOSC/2 
;             001 =FOSC/8 
;             010 =FOSC/32 
;             011 =FRC (clock supplied from a dedicated RC oscillator) 
;             100 =FOSC/4 
;             101 =FOSC/16 
;             110 =FOSC/64 
;             111 =FRC (clock supplied from a dedicated RC oscillator)      
;            ----X---   bit 3     Unimplemented: Read as ‘0’      
;            -----X--   bit 2 ADNREF: A/D Negative Voltage Reference Configuration bit
;                       1 = VREF- is connected to external VREF- pin(1)
;                       0 = VREF- is connected to VSS
;            ------XX   ADPREF<1:0>: Voltage Reference Configuration bits 1-0     
;                   11 = VREF+ is connected to internal Fixed Voltage Reference (FVR) module(1)
;                  10 = VREF+ is connected to external VREF+ pin(1)
;                  01 = Reserved
;                  00 = VREF+ is connected to VDD


INTCON = 001000          ; INTERRUPTS ON, INTERRUPT ON CHANGE PORT RB
;bit 7 GIE: Global Interrupt Enable bit
;1 = Enables all active interrupts
;0 = Disables all interrupts


;bit 6 PEIE: Peripheral Interrupt Enable bit
;1 = Enables all active peripheral interrupts
;0 = Disables all peripheral interrupts


;bit 5 TMR0IE: Timer0 Overflow Interrupt Enable bit
;1 = Enables the Timer0 interrupt
;0 = Disables the Timer0 interrupt


;bit 4 INTE: INT External Interrupt Enable bit
;1 = Enables the INT external interrupt
;0 = Disables the INT external interrupt


;bit 3 IOCIE: Interrupt-on-Change Enable bit
;1 = Enables the interrupt-on-change
;0 = Disables the interrupt-on-change


;bit 2 TMR0IF: Timer0 Overflow Interrupt Flag bit
;1 = TMR0 register has overflowed
;0 = TMR0 register did not overflow


;bit 1 INTF: INT External Interrupt Flag bit
;1 = The INT external interrupt occurred
;0 = The INT external interrupt did not occur


;bit 0 IOCIF: Interrupt-on-Change Interrupt Flag bit
;1 = When at least one of the interrupt-on-change pins changed state
;0 = None of the interrupt-on-change pins have changed stat


;The IOCIF Flag bit is read-only and cleared when all the Interrupt-on-Change flags in the IOCBF register
;have been cleared by software.


    OPTION_REG   =  000011
;                    X-------   bit 7     WPUEN: Weak Pull-up Enable bit 1 = All weak pull-ups are disabled (except MCLR, if it is enabled) 0 = Weak pull-ups are enabled by individual WPUx latch values     
;                    -X------   bit 6     INTEDG: Interrupt Edge Select bit 1 = Interrupt on rising edge of INT pin 0 = Interrupt on falling edge of INT pin      
;                    --X-----   bit 5     TMR0CS: Timer0 Clock Source Select bit 1 = Transition on T0CKI pin 0 = Internal instruction cycle clock (FOSC/4)     
;                    ---X----   bit 4     TMR0SE: Timer0 Source Edge Select bit 1 = Increment on high-to-low transition on T0CKI pin 0 = Increment on low-to-high transition on T0CKI pin      
;                    ----X---   bit 3     PSA: Prescaler Assignment bit 1 = Prescaler is not assigned to the Timer0 module 0 = Prescaler is assigned to the Timer0 module      
;                    -----XXX   bit 2-0     PS<2:0>: Prescaler Rate Select bits      
;                         000 1 : 2 Bit Value Timer0 Rate      
;                         001 1 : 4      
;                         010 1 : 8      
;                         011 1 : 16      
;                         100 1 : 32      
;                         101 1 : 64      
;                         110 1 : 128      
;                         111 1 : 256      


'Initialize registers and variables
        rb_copy =   0           ; clear register to begin
        PORTD   =   0           ; ALL LEDS OFF TO START
        goto    start           ; jump over interrupt


DEFINE  INTHAND DO_RBPORT       ; INTERRUPT HANDLER        
 
        asm
; BIG NOTE! THIS WORKS WITH "ENHANCED" PIC16F1xxx DEVICES. IF USED WITH OTHER DEVICES THEN
; YOU !!!MUST!!! DO CONTEXT SAVING YOURSELF!
DO_RBPORT                          
        banksel INTCON          ; SELECT INTCON BANK
        bcf     INTCON,7        ; clear INTCON GIE, disables interrupts
        banksel IOCBF           ; change to IOCBF bank
        movf    IOCBF,w         ; copy IOCBF to W
        movwf   _RB_COPY        ; copy W to RB_COPY (makes a copy of IOCBF)
        banksel IOCBF
        clrf    IOCBF           ; clear register to reset flags for new interrupts
        btfsc   _RB_COPY,0      ; TEST EACH BIT TO SEE WHICH BUTTON WAS PUSHED
        goto    _jump_0
        btfsc   _RB_COPY,1
        goto    _jump_1
        btfsc   _RB_COPY,2
        goto    _jump_2
        btfsc   _RB_COPY,3
        goto    _jump_3        
;        banksel INTCON         ; switch to INTCON bank -NEVER GET HERE          
;        bsf     INTCON,7       ; reset interrupts
        endasm
       




start: 
; INITIALIZE STUFF HERE IF NEEDED
mainloop:
        Serout2 PORTD.7,84,[12]             ; CLEAR THE OLED DISPLAY (www.seetron.com) AT BEGINNING      
        Serout2 PORTD.7,84,["MAINLOOP"]     ; WRITE TEST TO OLED
inLoop:
        high    portd.0                     ; do something main code body
        pause   300
        low     portd.0
        pause   300
        goto    inloop


; EACH "JUMP_x" REPRESENTS SEPERATE ROUTINES THAT RUN DEPENDING ON WHICH PUSHBUTTON IS PUSHED
jump_0:
        pause   30                          ; 30mS switch debounce - really crappy pushbuttons
        PORTD = 0                           ; TURN OFF ALL LEDS ON PORTD
        INTCON.7 = 1                        ; IMPORTANT: TURN INTERRUPTS BACK ON - NEVER DID IT IN ISR
        Serout2 PORTD.7,84,[12]             ; CLEAR THE OLED DISPLAY       
        Serout2 PORTD.7,84,["JUMP 0"]       ;
j_0_loop:                         
        high    PORTD.0                     ; TURN LED ON           
        pause   50                          ; 50mS delay
        low     PORTD.0
        pause   50
        goto    j_0_loop                    ; DO IT OVER AGAIN; COULD "GOTO" ANYWHERE


jump_1:
        pause   30                          ; 
        PORTD = 0
        INTCON.7 = 1                        ; 
        Serout2 PORTD.7,84,[12]             ;        
        Serout2 PORTD.7,84,["JUMP 1"]       ;
j_1_loop:                         
        high    PORTD.1                                
        pause   100                          ; 
        low     PORTD.1
        pause   100
        goto    j_1_loop


jump_2:
        pause   30                          ; 
        PORTD = 0
        INTCON.7 = 1                        ; 
        Serout2 PORTD.7,84,[12]             ;        
        Serout2 PORTD.7,84,["JUMP 2"]       ;
j_2_loop:                         
        high    PORTD.2                                
        pause   150                          ; 
        low     PORTD.2
        pause   150
        goto    j_2_loop


jump_3:
        pause   30                          ; 
        PORTD = 0
        INTCON.7 = 1                        ; 
        Serout2 PORTD.7,84,[12]             ;       
        Serout2 PORTD.7,84,["JUMP 3"]       ;
j_3_loop:                         
        high    PORTD.3                                
        pause   200                          ; 100mS delay
        low     PORTD.3
        pause   200
        goto    j_3_loop


        end


  
; W_OLED:
;        HIGH    PORTD.1
;        INTCON.7 = 0          ; TURN INTERRUPTS OFF, WRITE TO OLED
;        Serout2 PORTB.3,84,[12]     ; CLEAR THE OLED DISPLAY AT BEGINNING 40.5mS TO DISPLAY THREE POT VALUES       
;        Serout2 PORTB.3,84,["ADC_0=",DEC3 ADC_0,13]   ; Display adc channel 0
;        Serout2 PORTB.3,84,["ADC_1=",DEC3 ADC_1,13]   ; Display adc channel 0
;        Serout2 PORTB.3,84,["ADC_2=",DEC3 ADC_2,13]   ; Display adc channel 0
;        Serout2 PORTD.0,84,[12]     ; CLEAR THE OLED DISPLAY AT BEGINNING 40.5mS TO DISPLAY THREE POT VALUES       
;        Serout2 PORTD.0,84,["UPPER LIMIT=",DEC3 UPPER_LIMIT,13]   ; Display adc channel 0
;        Serout2 PORTD.0,84,["LOWER LIMIT=",DEC3 LOWER_LIMIT,13]   ; Display adc channel 0
;        PAUSE   5
;        INTCON.7 = 1                                             ; TURN INTERRUPTS BACK ON
;        LOW     PORTD.1
;        return