Activity 2 Counters & Interrupts


Objectives

In this chapter you will create a more complex program that is designed to act as an interruptible counter, you will use the C programming language throughout. Your objectives are:

  1. Create a new C project using the MPLABX software and the XC8 compiler

  2. Build a circuit using the seven-segment display and push button switches

  3. Create a counter that slowly counts automatically from zero to ten and then resets with the value displayed on the seven segment

  4. Read the state of some push-buttons to allow you to control your counter using interrupts

Hardware

The hardware for this activity will consist of the seven-segment display and some push-buttons. You are free to connect the display and buttons to any pins on any port you desire. It is your role as the embedded engineer to design both the hardware and software for your counter. Remember to use pull-up or pull-down resistors for your switches as necessary.

Software

You will need to create your own software that operates as a free running counter, the counter should count from zero to ten and then automatically reset back to zero. Your software will need to use interrupts to detect a button press that performs a stop action. A further set of buttons (not connected to the interrupt) should perform the following actions after the stop button has been pressed: start and reset to zero.

You will need a function that can convert a decimal number into the correct binary pin values for the seven-segment display (as in the Assembly laboratories). A prototype of such a function is given in .

Interrupts

In order to complete this activity you will need to use interrupts, this section provides a short overview of interrupts as they apply to the PIC16F84A and the C programming language. Interrupts allow us to respond to certain events by interrupting the currently running code, this is a very useful feature in a microcontroller. This section will cover interrupts in general but the concepts will apply to many of the built in peripherals such as timers.

One way to trigger an interrupt is to use the external interrupt pin, on the PIC16F84A this is pin RB0 (INT). This allows you to interrupt code execution when either a rising or falling edge is detected on the pin, you can select the edge type in the interrupt configuration registers. One application of this would be a circuit that responds to a push button, instead of periodically checking the status of an input to see if a button was pressed, you can use the INT pin (RB0) to interrupt the code as soon as the button is pressed.

Another interrupt can be triggered by pins RB4 - RB7 using the Interrupt-On-Change feature. This feature is similar to the external interrupt pin, except that a port change interrupt will be triggered by any change on any of the pins for which it is enabled. This makes it more flexible (being available on more pins), but also more difficult to deal with correctly.

The code that is run when an interrupt occurs is called an Interrupt Service Routine (ISR). If you want to use interrupts then you must tell XC8 which function in your code is the ISR. You do this when you declare the function by adding "interrupt" to the declaration. In Assembler we did this by forcibly locating our ISR to memory address $0$x$04$.Note that although the ISR is a function it cannot take any arguments and does not return anything. This makes sense because you do not call the ISR manually, it is called automatically when an interrupt occurs.

Each interrupt has an interrupt enable bit in one of several registers. For example, the INT pin interrupt enable bit (INTE) is found in the Interrupt Control (INTCON) register. Also, before any interrupt can occur, the Global Interrupt Enable (GIE) bit must be set. The GIE is also found in the INTCON register. PEIE is the Peripheral Interrupt Enable bit and it must be set before a peripheral interrupt can occur. However, the INT pin interrupt is not a peripheral interrupt so this does not affect us right now.

The last thing we must decide for the external interrupt is if the interrupt will occur on the rising or falling edge of the input signal. This is controlled by the INTEDG bit of the OPTION_REG register. A $1$ means interrupt on the rising edge, a $0$ for falling edge. Refer to the PIC datasheet for a further explanation of the interrupt control registers.

In we will use an interrupt to flip the state of pin RB2 every time a rising edge is detected on the INT pin. Meanwhile, pin RB1 will continue to toggle as the main program loop runs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Config omitted for display purposes
// Create meaningful names for our pins
#define LED1        PORTBbits.RB1       
#define LED1_TRIS   TRISBbits.TRISB1
#define LED2        PORTBbits.RB2
#define LED2_TRIS   TRISBbits.TRISB2

// Our ISR
void __interrupt() isr()
{
    // Reset the interrupt flag
    INTCONbits.INTF = 0;
    // Toggle the state of LED2, ~ is the complement
    LED2 = ~LED2;  
}

void main()
{
    // Set all of PortA to inputs
    TRISA = 0xFF; 
    // LED1 and LED2 are outputs
    LED1_TRIS = 0;
    LED2_TRIS = 0;
    // Reset the external interrupt flag
    INTCONbits.INTF = 0;    
    // Interrupt on the rising edge
    OPTION_REGbits.INTEDG = 1;
    // Enable the external interrupt
    INTCONbits.INTE = 1;
    // Global interrupt enable
    INTCONbits.GIE = 1; 

    while(1)
    {
        LED1 = 1;
        __delay_ms(500);
        LED1 = 0;
        __delay_ms(500);
    }
}
Interrupt Example

Note that the tilde operator (~) simply inverts the current pin value.

Exercises

Exercise 1: Write a function that converts an integer number into the required pin values for the seven-segment display, see .

Exercise 2: Build a circuit with the seven-segment display connected to the pins (and port) of your choice.

Exercise 3: Design a simple counter in your main while loop, by simply adding 1 to an integer on each pass of the loop. Design an ISR that allows you to stop, resume and reset this counter using some push buttons at any time. You may use the Interrupt-On-Change ports if you wish. (Hint: consider using global variables to enable/disable your counter).

Exercise 4: Display the value of your counter on the seven segment-display as it counts.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Function to convert an integer to a bit pattern suitable for driving a seven segment display
int convertSevenSeg(int input)
{
    if (input == 0)
    {
        return 0b0101010;
    }
    else if (input == 1)
    {
        return 0b1010101;
    }
    // TODO: Add other numbers...
}

// Main function
void main()
{
    while(1)
    {
        PORTB = convertSevenSeg(5);
    }
}
7-Segment Conversion Function