Assignment title: Information
64
8 Interrupts (part 2)
8.1 Aim
This laboratory practical will extend the knowledge on interrupts that you built through carrying out
the exercises in laboratory practical 5. We will be considering the peripheral interrupts sources
available for use on the PIC16F877A which include those for Timer1 and Timer2 modules. You will also
acquire the knowledge to be able implement code with other potentially useful interrupt sources such
as the ADC conversion complete interrupt.
8.2 Learning Outcomes
After this laboratory practical you will:
x Understand the full range of interrupts available on the PIC16F877A.
x Be able to configure the PIC16F877A utilise the peripheral interrupt set.
x Be able to create useful code based upon interrupts on all three Timer modules.
x Be able to implement a library file of useful functions.
8.3 Background
Note: You are strongly advised to re‐read the background information on interrupts found in section
5.3 prior to starting this laboratory practical.
8.3.1 Introduction
In laboratory practical 5 we considered the three primary interrupt sources and associated flags:
x Timer0 overflow (TMRIF)
x State change on pins RB4:7 (RBIF)
x External interrupt (state change) on pin RB0 (INTF)
These interrupts are entirely configurable using the interrupt control (INTCON) SFR which contains
both the interrupt enable bits and associated flags (see figure 7‐2). Please re‐read section 5.3.4
detailed breakdown of the operation of the INTCON SFR.
Figure 8‐1 INTCON SFR overview
However there are a total of 15 interrupts available on the PIC16F877A, the other 12 are organised in
SFRs as peripheral interrupts. There are 4 SFRs associated with peripheral interrupt sources; two for
interrupt enable bits (PIE1, PIE2) and two for their respective flags (PIR1, PIR2). The bit positions for
each enable bit and associated flag bit in the relevant PIE and PIR SFRs respectively, map onto each
other for the sake of simplicity. This should be clear if you study the PIE1 (figure 8‐2) and PIR1 (figure
8‐3) SFR contents.65
Figure 8‐2 Overview of the PIE1 SFR
Figure 8‐3 Overview of the PIR1 SFR66
For the purposes of this laboratory practical we will be concerning ourselves only with configuration
of the Timer1 and Timer2 interrupts which are configured using the PIE1/PIR1 SFRs. However you
should also be aware that there are 4 further interrupts configured from the PIE2/PIR2 SFRs, details
of which can be found in the PIC16F877A datasheet.
8.3.2 Peripheral interrupt configuration
In order to access the peripheral interrupts it is necessary to set the peripheral interrupt enable bit
(PEIE) in the INTCON SFR. The global interrupt enable bit (GIE) also needs to be set, as is the case
whenever interrupts are required. Figure 8‐4 is a useful aid to visualising how the various configuration
bits operate in the PIC16F877A. Each AND gate has an enable bit as an input which can be used prevent
all interrupt sources reaching the CPU. The GIE bit is the input to the rightmost AND gate and has the
effect of enabling disabling all interrupt sources from reaching the CPU. The PEIE bit can be seen to
enable/disable the peripheral interrupts sources (flags) while leaving the TMR0IF, INTF and RBIF
interrupt sources able to interrupt the CPU.
Figure 8‐4 Schematic showing the logical relationship between interrupt configuration bits on the PIC16F877A
Other than setting the PEIE bit to enable peripheral interrupts, all other aspects of peripheral interrupt
configuration and usage are identical to the non‐peripheral interrupts we studied in laboratory
practical 5.
8.3.3 Configuration example
If we wish to configure the PIC16F877A to respond to the Timer1 overflow interrupt we first need to
configure the PIE1 register. Reference to figure 8‐2 shows that we need to set the least significant bit
only to enable the Timer1 overflow interrupt. All other bits should be cleared to ensure those
interrupts are disabled.
PSPIE ADIE RCIE TXIE SSPIE CCPIE TMR2IE TMR1IE
0 0 0 0 0 0 0 1
In this case we need to right 0x01 to the PIE1 register. In addition to setting up the PIE1 SFR we also
need to enable the GIE and PEIE bits in the INTCON SFR in order to make the peripheral interrupts67
operational. All other bits in the INTCON SFR should be cleared as we are not making use of any of the
interrupts controlled by this register.
GIE PEIE TMR0IE INTE RBIE TMR0IF INTF RBIF
1 1 0 0 0 0 0 0
In this case, writing 0xC0 to the INTCON register configures the interrupts as required. In MPLABX /
XC8 we can write the following code to carry out the required SFR configuration as follows,
PIE1 = 0x01;
INTCON = 0xC0;
Alternatively if we wish to only modify the relevant SFRs rather than carry out a one‐shot configuration
we can set the relevant bits individually as shown below,
PIE1bits.TMR1IE = 1;
INTCONbits.PEIE = 1;
INTCONbits.GIE = 1;
This method is useful if it is necessary to turn individual interrupt sources on or off at specific points
during code execution. Note that GIE should always be the last bit to be enabled to ensure that
undefined behaviour cannot happen as a result of interrupts occurring during the interrupt
configuration process.
8.3.4 Coding ISRs for peripheral interrupts in the MPLAB X and the XC8 compiler
Writing interrupt service routines (ISRs) for peripheral interrupts is exactly the same process we used
previously in section 5.3.6 where a non‐standard, compiler specific keyword interrupt to is used define
the ISR. By way of reminder, the syntax for defining an ISR is as follows:
void interrupt functionName (void)
{
// ISR code goes here
}
The ISR needs to check for peripheral interrupts in exactly the same manner as we have used
previously. It is a case of simply checking the relevant interrupt flags as part of an if‐else‐if chain. In
the following code the Timer0 and Timer 1 overflow interrupt flags are checked in the ISR. Code
specific to each interrupt is only executed if the relevant interrupt flag is set (which happens only when
that interrupt has occurred.
void interrupt myISR(void)
{
if(INTCONbits.TMR0IF)
{
// Execute Timer0 overflow interrupt specific code
INTCONbits.TMR0IF = 0; // Clear flag
}
else if(PIR1bits.TMR1IF)
{
// Execute Timer1 overflow specific code
PIR1bits.TMR1IF = 0; // Clear flag
}
}68
Remember that it is essential that you remember to clear the relevant interrupt flag after you have
executed the interrupt specific code.
8.4 Procedure
8.5 Exercise 1 – Implementing the Timer1 overflow interrupt
Create a new project in the MPLAB X IDE, create an empty source file, and populate it with
the code listing below.
// Filename: Lab8Ex1.c
// Version: 1.0
// Date:
// Author:
//
// Description: Implements a Timer1 peripheral interrupt and uses it to
// toggle RB7 at a rate of 2 Hz
#include // Required for all MPLAB XC8 source files
void config(void); // Configuration function
void short_delay (void); // Produces a short delay
void main(void)
{
config(); // Call configuration function
while(1) // Endless loop
{
PORTB = PORTB^0x40; // Toggle RD6
short_delay();
}
}
// Function to carry out configuration routines. Is called once at the start of
// execution and helps to keep the main() function clean and tidy.
void config(void)
{
INTCONbits.GIE = 0; // Disable interrupts during configuration
TRISB = 0x00; // Configure PORTB
PORTB = 0x00;
TRISD = 0x00; // Configure PORTD
PORTD = 0x00;
T1CON = 0x30; // Timer1 OFF, Prescaler 1:8
PIE1 = 0x01; // Enable TIMER1 interrupt ONLY
PIR1bits.TMR1IF = 0; // Clear Timer1 interrupt flag
T1CONbits.TMR1ON = 0; // Turn Timer 1 OFF
TMR1H = 0x38; // Load high byte
TMR1L = 0x00; // Load low byte
T1CONbits.TMR1ON = 1; // Turn Timer 1 ON
INTCON = 0xC0; // Enable PEIE and GIE ONLY
}
// Function to provide a short delay using a simple 'do nothing' loop.
void short_delay(void)69
{
unsigned int i;
for(i=0;i<20000;i++);
}
// Interrupt service routine (ISR)
void interrupt myISR(void)
{
if(PIR1bits.TMR1IF)
{
T1CONbits.TMR1ON = 0; // Turn Timer 1 OFF
TMR1H = 0x38; // Load high byte
TMR1L = 0x00; // Load low byte
T1CONbits.TMR1ON = 1; // Turn Timer 1 ON
PORTB=PORTB^0x80; // Flash RB7 led
PIR1bits.TMR1IF = 0; // Clear interrupt flag (IMPORTANT)
}
}
Study the code listing carefully and identify the foreground and background code. Draw a
low‐level flowchart for both the foreground and background code. Keep your flowchart clear
and simple by using the subroutine call flowchart symbol to represent the config() and
short_delay() functions.
Figure 8‐5 Flowchart symbol for a subroutine call
Refer to section 6.4.3 to see an example of the usage of the subroutine flowchart symbol.
8.6 Exercise 2 – Servicing multiple peripheral interrupts
In this exercise we will be adding a second interrupt service to the ISR so that Timer2 interrupt can be
serviced in addition to the Timer1 interrupt.
Modify the config() function in the code listing above to set up Timer2 so it generates an
interrupt every 50 ms.
Modify the ISR in the code listing to increment PORTD with each Timer2 interrupt. Confirm
the count interval is 50 ms by counting the time taken for a full binary count up to 256 using
a stopwatch.
Further modify the ISR to provide a clear function when RB0 is pressed. This resets the count
on PORTD to zero and should be implemented using the RB0 external interrupt.
8.7 Exercise 3 – Creating a function library
In this exercise we are going to package the short_delay() function from the code developed in the
previous exercise into a library.
Create a new project in the MPLAB X IDE, create an empty source file, called Lab8Ex3_main.c
or similar. This will be the main source file for your code which will contain the main()
function. Populate this source file with the code listing you produced in the previous exercise.70
Create two new empty files called myfunctions.h (header file) and myfunctions.c (source file).
These files will form a library in which you will be able to store useful functions for use in the
future. Make sure both files reside in the current project directory.
The library header file (myfunctions.h) must contain the function prototypes for your new
library functions. Cut the function prototype for the short_delay() function from the main file
and paste it into the library header file.
The library source file (myfunctions.c) must contain the function definition itself. This code
will be linked, after compilation, during the project build process. Cut the function definition
for the short_delay() function from the main file and paste it into your library source file.
You have now created your library files, but we now need to make the stored function
accessible from the main source file. This is done by adding an include directive to link the
function prototypes in your library header file. Place the required directive (shown in bold
below) below the other include directive(s) in the main file.
#include // Required for all MPLAB XC8 source files
#include "myfunctions.h" // Required to access myfunctions library
Use the Add Existing Item dialog in the project navigator window to add your library source
file. Do this by right‐clicking on the Source Files icon then select Add Existing Item… from the
context menu. Select the file myfunction.c which should be stored in the current project
directory and click Select. You should see the file represented under the Source Files icon in
the project navigator window which will look something like Error! Reference source not
found..
Figure 8‐6 Project navigator window showing attached source files
Build your project and test it is operating correctly. You have now successfully stored a simple
but useful function, short_delay() in a separate library. You can utilise this library by copying
the library header and source files into the current project directory. You must the link the
files to the project correctly using the method outlined in steps 5) and 6).
You are encouraged to develop and add functions to your library so you make use of them in
the future. Try and keep functions as generic as possible, to make them applicable to a broad
range of circumstances. Configurable delay functions, like those you have developed in some
of the previous laboratory practicals, are a good example of generic functions. Code re‐use is
an important concept in all software engineering, it leads to shorter development time and
more robust software.