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.