How to handle interrupts on a PIC microcontroller

Most of the time, embedded systems have time sensitive inputs that need to be handled as soon as they are triggered, with minimal delay. This is usually done by using interrupts. Interrupts are pieces of code that are run when an event occurs on one of the microcontroller’s ports, or internal components such as a timer. The execution of the main program is paused, and the microcontroller jumps to the interrupt code (in reality, it jumps to a given address in the program memory, reserved to the interrupt instructions). Once it  finishes executing the interrupt code, it will go back to where it stopped in the main program.

In this tutorial we’re going to learn how to implement an interrupt when a user pushes on a button.

Bill of material

I’ll use a PIC16F88 in this tutorial. To make it easy to see if the interrupt is working correctly, we’ll use a counter that will be displayed on a 7 segment display. Here is a reminder on how to use a 7 segment display. Because my microcontroller doesn’t have a lot of pins, I’ll use a 74HC595 shift register to increase the number of ports I can control. This is very easy to do, as we learned in this previous tutorial. We’ll also need a push button, a 10K and a 100K resistors, and a 0.1uF capacitor.

Build the circuit

Let’s start by building the circuit we’ll be using. As mentioned above, I use a shift register to control 8 outputs with only 3 pins from the PIC. Feel free to use pins on your microcontroller; this will work the same.

Connect the PIC and the 74HC595 shift register as showed in this tutorial. Then, connect the outputs QA-H of the shift register, to the segments A to G and DP of the 7 segment display.

For the push button, we’ll use hardware debouncing to avoid any unwanted noise when pressing on it. If you use the simple non-debounced circuit shown below, you’ll end up with multiple rising/falling edges when pushing the button, because the push buttons always create contact multiple times. That’s totally normal, as the mechanical pieces don’t just touch and stop touching when you push on the button, they always touch several times within a few milliseconds. And that’s enough to trigger the interrupt multiple times on a microcontroller! In our case, this would lead to the counter being increased by more than one every time we push the button.

button_no_debouncing
Button without debouncing

This problem can be fixed by debouncing the button. There are two ways to do this, either in software, or hardware. As microcontrollers have limited resources, I personally prefer doing it in hardware.

To debounce our button with hardware, we’ll only need an extra 0.1uF capacitor, that we’ll place between the pin of the button tied to the 5V rail, and the input of the PIC, like shown below.

button_debounced
Button with a debouncing capacitor

With all the components included, here is the final circuit:

push_button_interrupt
Circuit to implement an interrupt on the push button

Software

Configuration of the interrupt

We’ll use the port RB0/INT of the PIC, which can be used to trigger interrupts INT0. Let’s first set up the interrupt registers to the correct values to enable this interrupt. The datasheet for the PIC16F88 explains that there are two register that are used for INT0:

  • INTCON is used to configure interrupts
  • OPTION_REG contains a bit INTEDG that specifies the edge that will trigger INT0 (either rising or falling edge)
intcon_reg
Register INTCON, used to configure interrupts
INTEDG_config
Register OPTION_REG, used to configure optional settings

We first set the pin RB0 to be an input. Then, we enable the global interrupts (GIE). Without this bit set to 1, all interrupts would be disabled. Next, set INT0 to be triggered by a rising edge on the pin, by setting the bit INTEDG to 1 in the register OPTION_REG. Finally, we clear the INT0 flag, and enable INT0 interrupts by setting INT0IE to 1. With interrupts, the flag (which name usually ends with IF) is set to 1 when the interrupt is triggered. It must be reset to 0 when handling the interrupt, so it can trigger it next time the event happens.

Interrupt handler

We now need to write the code that will be executed when the interrupt occurs. Interrupt handlers should always be kept to a minimal code, as they are time sensitive. Any user interface update, and any code that is not absolutely needed in the interrupt should be kept out of it. Here, we’ll simply clear the INT0 flag, and increment our counter. Note that we don’t output the counter’s value on the 7 segment; this will be done in the main loop. We only increment the counter, that will be read by the main loop next time it iterates.

With the XC8 compiler, and PIC16F devices, writing the interrupt handler is as simple as giving the specifier  interrupt  to a function. This function should be of type void  and doesn’t take any argument. As a side note, on some higher end microcontrollers, there are high and low priority interrupt vectors, and the low priority vector should be declared with the keyword low_priority . In our case we only have one priority, so we don’t need to specify it.

In the interrupt vector, we need to check which interrupt was triggered; here, we only need to check INT0, as we don’t have any other interrupt implemented.

As we’ve seen before, we keep the code of this function to the strict minimum. This is always the best practice when working with interrupts.

Controlling the shift register

If you’re using a 74HC595 shift register like I am, you can refer to this tutorial for more details, and to my Github page for a shift register library.

Display a number on the 7 segment

Similarly to what we did in this tutorial on how to control a 7 segment display, we’ll declare an array of 8-bit values, representing the state of the segments for each number between 0 and 15. We’ll declare it as static const  so that it is constant, and only instantiated once. Then, we’ll just pick the value at the index of the number that we want to display, and send it to the shift register.

Main loop

The main loop will display the value of the counter at regular intervals. Note that we declared the counter with the keyword volatile. This tells the compiler that this variable can change value outside of the normal flow of execution of the program, preventing it from doing any optimization on this variable.

Full code

Here is the full code for the main file. The code for the 74HC595 library can be found on this Github page.

Result

The result of this program is that the value displayed on the 7 segment is increased every time you press on the button. As you can see, with the hardware debouncing we added, it is only increased by one every time. If you want to confirm the need for debouncing, you can try removing the capacitor, and it will be increased by more than one every time!

 

Be the first to comment

%d bloggers like this: