• 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.
Resource icon

Simple Interrupts on mid range PIC's.... 2013-10-28

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.

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:
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

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:
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.

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:
    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 )..

Latest reviews

Nice to have both C and Assembly code side by side.
Ian Rogers
Ian Rogers
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..

Latest threads

EE World Online Articles

Loading

 
Top