1. Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.
    Dismiss Notice
Ian Rogers

Simple Interrupts on mid range PIC's....

How to implement interrupts in your project.

  1. Ian Rogers
    Interrupts are quite easy to use.. They help your code run smoothly and free up time to do other processing.
    The simplest interrupt is the little timer available in MOST pic micro's

    Every interrupt available to you has certain flags/ bits that must be set up to use.
    Timer0 has all these flag / bits....
    The INTCON register on the majority of mid range pic's looks like this..

    | GIE | PEIE | T0IE | INTE | RBIE | T0IF | INTF| RBIF |

    GIE = Global interrupt enable.. Each interrupt can be individually enabled / disabled, or you can use this flag to enable / disable ALL interrupts.

    PEIE = Peripheral interrupt enable. All interrupts NOT under the control of the INTCON register. Again can ALL be enabled / disabled.

    T0IE also TMR0IE = Timer 0 interrupt enable. The one we'll be using..

    INTE = INT enable... Enables transactions on RB0, Transaction direction is set in the OPTION_REG... Transactions being either HIGH to LOW or visa versa..

    RBIE = Port change transactions on the top half of PORTB pins 4 through 7.

    T0IF, INTF and RBIF are used by the interrupt system to notify a interrupt has occured... Each must be cleared or the interrupt will continue to fire..

    Okay... I have the smallest code so we can see what happens.

    Code (c):

    #include <xc.h>            // header
    #pragma config  WRT = OFF, FOSC = XT, WDTE = OFF, LVP = OFF, PWRTE = OFF
    void interrupt ISR()    // ISR...
        {
        RB0 = ~RB0;            // toggle pin
        T0IF = 0;            // clear interrupt
        }

    void main()
        {
        TRISB0 = 0;
        OPTION_REG = 0b00000111;// prescaler to max
        T0IE = 1;                // enable timer 0 interrupts
        GIE = 1;                // enable all interrupts

        while(1)                // Forever wait!!
            {
            }
        }
     
    And the same in assembler
    Code (asm):

    list        p=16F877a
        #include    <p16F877a.inc>
        __CONFIG(0x3f71)
        org    0x0000        ; Start vector
        goto    Start

        org    0x0004        ; ISR vector
        btfss    PORTB,0        ; Test condition
        goto    unset        ; either set or
        bcf    PORTB,0        ; unset
        goto    reset
    unset    bsf    PORTB,0
    reset    bcf    INTCON,T0IF    ;clear the interrupt flag
        retfie

    Start
        banksel    TRISB        ; Select the SFR bank that contains TRISB ( bank 1)
        bcf    TRISB,0
        movlw    b'00000111'    ; Pre-scaler to max
        movwf    OPTION_REG
        bsf    INTCON,T0IE    ; T0IE on
        bsf    INTCON,GIE    ; Global interrupts on
        banksel    PORTB        ; Put back into bank 0
        bcf    PORTB,0

    FIN    goto     FIN        ; wait here forever

        end
     
    First!! One of the questions I hear is...

    "When / what calls the interrupt"

    One misconception is that people often think that they need to call the interrupt from somewhere!! The truth is that interrupts, once set up, are automatically called when the trigger has occurred, be it an external event or some internal event..

    Now we look at the examples I have posted... Both do exactly the same job.. First we see that both have an initialization part where the timer parameters are set.. We need to switch on the interrupt... T0IE or TMR0IE ( both are correct as they are both defined ) is the Timer 0 Interrupt Enable bit... this will now set the interrupt flag T0IF / TMR0IF when the timer spills over... It counts 0 - 255 and as soon as it overflows this flag indicates that it has occurred..

    We have the ability to slow the timer down using a pre-scaler.. A "divide by" register that slows the timer increment by a predetermined amount.

    They are set in the OPTION_REG... the last 4 bits in this register..PSA,PS2,PS1 and PS0... PS0,1,2 are the pre-scaler and PSA assigns the pre-scaler to the watchdog or timer....

    There are 8 combinations of the pre-scaler when associated with timer 0... 1:2, 1:4, 1:8, 1:16, 1:32, 1:64, 128 and 1:256... There is one more setting for timer 0... If the pre-scaler is assigned to the watchdog at 1:1... Timer 0 has a 1:1 ( no pre-scale ).. So we have 9 settings...

    If we run the micro at 1Mhz the clock cycle will take 4uS to complete, so at 1:1 the timer will overflow every 1.024mS, at 1:2 it will be 2.48mS through to 1:256 which yields 262.144mS...

    So we can see that the two examples the LED connected to PORTB pin 0 will flash at a rate of two flashes per second.... 262mS on 262mS off...

    I have purposely placed the code in a "Forever loop" so it can wait for the timer to overflow and change the LED status.

    To use the INT pin on the chip a couple of easy steps and you will be able to configure the interrupt on the PORTB pin 0.

    To use INTE / INT0IE just replace the line in the code ( Move the status LED to another pin and change the TRIS setting

    Code (c):

    #include <xc.h>            // header
    #pragma config  WRT = OFF, FOSC = XT, WDTE = OFF, LVP = OFF, PWRTE = OFF
    void interrupt ISR()    // ISR...
        {
        RB1 = ~RB1;            // toggle pin
        INTF = 0;            // clear interrupt
        }

    void main()
        {
        TRISB1 = 0;
        TRISB0 = 1;
        OPTION_REG = 0b00000111;// prescaler to max
        INTE = 1;               // enable INT interrupts
        GIE = 1;                // enable all interrupts

        while(1)                // Forever wait!!
            {
            }
        }
     
    And again, the same in assembler
    Code (asm):

    list        p=16F877a
        #include    <p16F877a.inc>
        __CONFIG(0x3f71)
        org    0x0000        ; Start vector
        goto    Start

        org    0x0004        ; ISR vector
        btfss    PORTB,1        ; Test condition
        goto    unset        ; either set or
        bcf    PORTB,1        ; unset
        goto    reset
    unset    bsf    PORTB,0
    reset    bcf    INTCON,INTF    ;clear the interrupt flag
        retfie

    Start
        banksel    TRISB        ; Select the SFR bank that contains TRISB ( bank 1)
        bcf    TRISB,1
        movlw    b'00000111'    ; Pre-scaler to max
        movwf    OPTION_REG
        bsf    INTCON,INTE    ; INTE on
        bsf    INTCON,GIE    ; Global interrupts on
        banksel    PORTB        ; Put back into bank 0
        bcf    PORTB,1

    FIN    goto     FIN        ; wait here forever

        end
     
    Now a toggle happens when an external button is pressed.. I have not allowed for the debounce of a button... In reality the interrupt will fire several times and possible not do what is expected.... There are many ways to debounce,,,, The best would be to set a variable within the ISR to track if the button was falsely triggered..

    There is a facility to interrupt on both rising or falling edge of a external event....
    If the INTEDG bit 6 of the OPTION_REG is set it will fire the interrupt on a rising edge, otherwise if the bit is cleared, then the interrupt will fire on the falling edge.
    You can toggle this pin in the interrupt if you want to catch each transaction.

    You can also connect an external event on PORTB pins.. This is exactly the same as before, but we set RBIE interrupt flag instead, we do not clear the RBIF interrupt flag... We have to physically read the port to clear the ports mismatched situation...

    You can also use the flags to determine which interrupt has been fired.

    Code (c):

    #include <xc.h>            // header
    #pragma config  WRT = OFF, FOSC = XT, WDTE = OFF, LVP = OFF, PWRTE = OFF
    void interrupt ISR()    // ISR...
        {
        if(INTF)    // Int occurred.
           {
           RB1 = ~RB1;            // toggle pin
           INTF = 0;            // clear interrupt
           }
        else   // timer overflowed
          {
          RB2 = ~RB2;
          T0IF = 0;
          }
        }

    void main()
        {
        TRISB1 = 0;
        OPTION_REG = 0b00000111;// prescaler to max
        INTE = 1;                // enable INT  interrupts
        T0IE = 1;                // enable timer 0 interrupts
        GIE = 1;                // enable all interrupts
        RB1 = 0;
        RB2 = 0;
        while(1)                // Forever wait!!
            {
            }
        }
     
    And yet again, the same in assembler
    Code (asm):

        list    p=16F877a    ; header / list
        #include<p16F877a.inc>
        __CONFIG(0x3f71)    ; config bits
        org    0x0000        ; strat vector
        goto    Main

        org    0x0004        ; ISR vector
        btfss    INTCON,INTF    ; was it INT or timer
        goto    tim
        btfss    PORTB,1        ; LED condition
        goto    unset1
        bcf    PORTB,1        ; clear
        goto    reset1
    unset1    bsf    PORTB,1        ; or set
    reset1    bcf    INTCON,INTF
            goto    ISRfin
    tim                ; it was timer overflow
        btfss    PORTB,2        ; LED condition
        goto    unset2
        bcf    PORTB,2        ; clear
        goto    reset2
    unset2    bsf    PORTB,2        ; or set
    reset2    bcf    INTCON,T0IF
    ISRfin
        retfie

    Main
        banksel    TRISB
        bcf    TRISB,1
        bcf    TRISB,2
        movlw    b'00000111'
        movwf    OPTION_REG
        bsf    INTCON,T0IE    ; Timer interrupt on
        bsf    INTCON,INTE    ; external interrupt on
        bsf    INTCON,GIE    ; global on
        banksel    PORTB
        bcf    PORTB,1        ; set LED's off
        bcf    PORTB,2
    Wait    goto    Wait         ; forever wait

        end
     
    Of course, you DON'T have to use the interrupts... You can poll the flags. If you set an interrupt flag but don't enable the global interrupts.... Then when an event happens, the corresponding flag is still set...

    The other little thing is the T0CKI... On the pic16f877a is RA4 ( pin 6 ) You can redirect the clock input to this pin so the pulses arriving on this pin drives the timer... You have a basic frequency counter... There are two flags in the OPTION_REG for this.. T0CS ( timer 0 clock select ) and T0SE ( timer 0 source edge )..
    Cribcat, MCU88, yugal and 5 others like this.

Recent Reviews

  1. jpanhalt
    jpanhalt
    5/5,
    Nice to have both C and Assembly code side by side.
    1. Ian Rogers
      Author's Response
      Thanks John!! I'm going to do a few more articles in the same way... This way I hope the transition from ASM to C can clearly be seen..