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.
And the same in assembler
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
And again, the same in assembler
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.
And yet again, the same in assembler
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 )..
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 )..