Microcontroller Tutorials

Arduino, Raspberry Pi, PIC, Beaglebone Resources

PIC Interrupt with PIC16F84A

So far we have discussed how to code microcontrollers sequentially, i.e., the device performs the code from top to bottom and may branch out to labels (via goto) or subroutines (via call). Now we'll be looking at PIC interrupt.

What is a PIC Interrupt?

Interrupts, from the word itself, are events that the microcontroller has to acknowledge even if it is currently executing something.

An analogy of interrupts may be useful. Imagine you reading a book. When your phone rings, you will pause reading, place a bookmark, and then answer the phone. If the number who is calling you is unknown, you would ask for the name of the caller. You will then finish the phone conversation, and return to where you stopped reading.

A similar thing happens with microcontrollers and interrupts: the device is busy with a main process (reading a book) when an interrupt occurs (phone ringing). The device will save the program counter value to the stack (bookmark the page), know which interrupt is triggered (identify the caller) and execute the interrupt routine (talk to the caller). When the interrupt routine is finished, the device will read the program counter from the stack so that it can go back to where it stopped on the main process (pickup the bookmark and go back to reading).

If the interrupts are really important and should not be ignored, we call them non-maskable interrupts. Examples of these are internal memory errors, data corruption and other very important things that would directly affect the operation of the microcontroller. The PIC16F84A has no non-maskable interrupt so all its interrupts can be disabled or enabled via code. I will discuss all of the interrupt types for this microcontroller with example codes in assembly. Codes for CCS C and PBP will also be given in a separate section. Here's a rundown of the interrupts for the PIC16F84A (click to jump to the specified section):

The interrupts are enabled/disable by using the INTCON register (address 0x0B or 0x8B):

PIC16F84A INTCON Register

 

To enable any interrupt, the GIE (bit 7, Global Interrupt Enable) bit should be set. Bit 3 to bit 0 are flag bits which are bits that would set whenever a specific interrupt is triggered.

The advantage of using interrupts over traditional, "linear" microcontroller programming is that you can trigger events almost instantly. For example, if you want to read the state of a button on a pin, you would normally do this:

In order for the device to detect the pin state change, line 1 must be the current line executed. If the device is already on line 4, pressing the button on RB0 wouldn't trigger anything!  However if you use interrupt, it won't matter which line is currently being executed. The interrupt will trigger anytime as long as it is enabled.

External Pin Interrupt

The external pin interrupt is triggered whenever there is a "change of state" on the RB0/INT (pin #6 on the PIC16F84A). The change of state means the pin went from high to low or low to high.

To enable this interrupt, you have to set the INTE (bit 4, External Interrupt Enable) bit of the INTCON register. An example program below illustrates this:

What this code does is to clear RA.0 whenever there is a change in the state of RB.0. A LED connected to RA.0, for example, will stay on unless the button on RB.0 is pressed.

The first thing you'll notice is the addition of the interrupt vector:

This points to the line where the code should jump whenever an interrupt is triggered. In this case, the code jumps to the section with the label ISR (which stands for Interrupt Service Routine,  the formal name of the routine to execute when an interrupt occurs, but you can use whatever label that you like). For PIC devices, the interrupt vector is non-programmable (meaning, it can't be changed).

You'll need to disable all interrupts inside the ISR via clearing the GIE bit. This ensures that no other interrupt is triggered when the code is currently processing an interrupt. If you remove this one, the code will be trapped in an endless loop! You should also clear the INTF bit which was set as the interrupt is triggered. As you exit the isr, don't forget to re-enable all interrupts.

Timer Overflow Interrupt

A timer overflow is another source of interrupt for the PIC16F84A. All microcontrollers have timers in them. They can be used to count events and introduce delays in the code. Read about using the timers on the PIC16F84A here.

In this article, a delay subroutine was used to introduce waiting time before proceeding to the next line. That routine however is limited as to how much time you want delayed. The better option is to use the built-in timer.

The PIC16F84A has one 8-bit timer (TMR0) and is found at address 0x01. Since it is 8-bits wide, it can only count from 0 to 255 (28 = 256, o included). What happens when it reaches 255? The next count would be 0. This is when the timer overflow interrupt will trigger.

Each count of the timer depends on the instruction cycle speed. If, for example, a 4 MHz crystal is used, the instruction cycle speed will be:

This means that the timer will overflow after 255 uS. That's still too fast! Thankfully, there's the prescaler option. This will extend the timer beyond its default counting time. The prescale value is set via the OPTION register:

PIC16F84A OPTION register

 

The PSA (bit 3, Prescaler Assignment) should be cleared in order to enable the prescale for TMR0. Setting this would assign the prescale to the watchdog timer (which is discussed in the timers article). You might noticed the INTEDG (bit 6, Interrupt Edge Select) bit which allows you to choose between rising edge or falling edge change in state on the RB0 pin (discussed in the previous section).

Bits 2 to 0 are the prescaler select bits:

PIC16F84A Timer0 Prescaler

 

Giving bits 2 to 0 gives you the maximum prescale value of 1:256. This means that TMR0 will now be counting every 256 uS instead of every 1 uS. That's 65280 uS before TMR0 would overflow.

Look at this LED blink code that uses the TMR0 overflow PIC interrupt:

When the timer is yet to overflow, the code loops inside the main routine which continually sets the RB0 pin. When the timer overflows and the interrupt triggers, the isr routine clears the RB0 pin. This effectively "blinks" a LED if it's connected to RB0. Since maximum value of prescale is set, the blinking speed would be roughly 65 mS.

RB Change Interrupt

You can also attach a PIC interrupt to the pins RB4, RB5, RB6 and RB7 just like how you can with RB0/INT.  The RB change interrupt is configured via setting RBIE (bit 3, RB Change Interrupt Enable) of the INTCON register. When any change of state occurs for any of the mentioned pins, the RBIF (bit 0, RB Change Interrupt Flag) of the INTCON register sets. Note that the time it took to change the state must be at least equal to the instruction cycle time.

The example sets RA0 then clears it when a change of state in any of the pins RB.4 to RB.7 occurs.

EEPROM Write Interrupt

Writing to the EEPROM location of the PIC takes around 10 ms, a long wait for microcontrollers. This is practically why this PIC interrupt source exist: so that the PIC can do other useful things while writing data to the EEPROM. Setting EEIE (bit-6) enables this interrupt and EEIF (bit-4) is the corresponding flag bit.

The example code below clears RA0, writes data to the EEPROM then sets RA0 when the EEPROM write is complete.

Interrupts in XC8

In XC8, interrupts are given their own interrupt data type. This means you can create ISRs as functions with interrupt as the return type. For example, this is how you would implement the same external interrupt code above in XC8:

As you see, there is a need to check the external interrupt flag INTF to check if that is indeed the interrupt that triggered because any interrupt can trigger the isr() function.

The timer interrupt would be coded like this:

How would you code RB change and EEPROM write complete interrupts in XC8?

Next >>Using PIC Timer with PIC16F84A

3 thoughts on “PIC Interrupt with PIC16F84A

  1. Apart from leaving a few labels off (main) in the foregoing sample programs, i find your tutorials good news.
    I am currently wondering about paying for an on-line course (gooligum) but i am finding your tutorials highly comprehensible.

    Many thanks for your efforts.

    Paddy Magill

  2. This part just before the RB CHANGE INTERRUPT secton.

    START
    BSF STATUS, RP0
    CLRF TRISB ;PORTB all output
    MOVLW b'10100000'
    MOVWF INTCON ;enable timer-overflow interrupt
    MOVLW b'00000111'
    MOVWF OPTION_REG ;instruction clock,falling edge,prescaler set to maximum
    BCF STATUS, RP0 ;switch to Bank 0
    CLRF TMR0 ;initialize timer
    GOTO MAIN

    Just reading this, it looks like the TMRO will never reach beyond b,00000001' because the CLRF TMR0 statement being inside the loop from START to GOTO MAIN. This needs to be at the end of the interrupt routine if at all since it will go to 0x00H when it overflows anyway. Or, am I just reading this wrong?

    1. No, the CLRF TMR0 is inside the START routine and not the MAIN routine so it is not part of the MAIN loop routine.

Leave a Reply

Your email address will not be published. Required fields are marked *