Lecture: 14
Objective: Understand the pieces of code that are required to construct an interrupt.
Code: basicInterrupt.c

Interrupts

In the coming discussion I will make references to hardware subsystems of the PIC. Hardware subsystems are computational units inside the PIC that are not directly related to the fetching and executing of instructions. Examples of hardware subsystems include timers and capture/compare/PWM modules. Each of these hardware subsystems has an interrupt flag. Our programs have polled the interrupt flag, waiting until the flag has been set (by the hardware subsystem) indicating that the subsystem has completed some important task. It is now time to start using the interrupt flags in a much more significant way, to give them the capability to call a special function.

An interrupt service routine (ISR) is a function called by hardware. Almost all the hardware subsystems in the PIC have the ability to call an ISR. To do this the subsystem must be configured so that the subsystems interrupt flag becomes the signal to stop running main and start running the ISR. This is accomplished by setting the interrupt enable bit associated with the hardware subsystem. Since an ISR is called by hardware and not your program, your main program has no idea when the ISR will be called, and cannot pass parameters to the ISR. At first you may find the concept of interrupts complex and unintuitive, but they represent one of the most powerful capabilities of a microcontroller and your embedded systems toolbox.

Why should we bother with these odd functions? Having an ISR is like having a second program independent from main. These little programs can handle mundane tasks only when mundane tasks need worked on. This allows main to focus on the important command, control, and communications tasks. In fact there are many different hardware subsystems inside the PIC that can generate interrupts, take a look at page 107 of the PIC18(L)F2X/4XK22 Data Sheet found on the class main web page.

In order to use interrupts in your programs you will need the following 6 components somewhere in your program.

Example program design

In order to better understand how you can use interrupts to perform useful tasks, the example program generates a 50% duty cycle square wave with a frequency of 352 Hz. We will use timer 0 as the hardware source to keep track of time and generate the interrupt signal. Note that this program, basicInterrupt.c, is linked at the top of this web page. Please download and run it to verify what we are doing here.


Let's take a minute to verify to check that the prescaler for timer 0 and the starting value for timer 0 are correct. This starting value needs to be for half the wave length of the 352 Hz wave because, as we will saw, the ISR toggles the pin that generates the waveform (2 toggles are required for a complete wave).

A 352 Hz waveform toggles once every 1.42ms. The smallest timer 0 prescaler that accommodates this duration is 1:1. Using a 1:1 prescaler, let's use units factoring to determine the number of timer counts that go by in ½ of a 352Hz wave.
16*10^6 clk   1 cnt   roll over  1 pin toggle      wave           352 waves
----------- * ----- * -------- * ------------ * -------------  =  ---------
  1 sec       1 clk   x counts   1 roll over    2 pin toggles        sec
Solving for x yields 22,727. We will be using the roll-over flag for timer 0 as the interrupt signal, so we need to determine the initial value for timer 0 so that it rolls over in 22,727 counts. Starting timer 0 at 216

Example program analysis

Let's take a moment and examine how the basicInterrupt program runs. To do this I like to ask myself the question, "Through time, what function is running?" The answer to this question is either main or the interrupt service routine; always one and never both. In the image below, time flows from the left to right and the we see the green bar indicating when main is in control of the CPU and red bar when the tmr0_isr is running. Below this a blue time line showing the timer 0 count value. Initially main (through the INIT_PIC function) initializes the timer to 0xA739. The timer slowly counts up, making its way to 0xFFFF and then rolls-over to 0x0000. The moment that the timer rolls over to 0x0000, the INTCONbits.TMR0IF flag is set, causing main to stop running and the tmr0_isr function to start running. Inside the tmr0_isr function, the timer is set to 0xA739, pin RC0 is toggled, and the INTCONbits.TMR0IF flag is cleared. The tmr0_isr function then exits and main resumes running. This corresponds to the red bar ending and the green bar starting. The process of timer 0 counting-up, rolling over, stopping main, running the tmr0_isr repeats indefinitely.


Subsystem interrupt interface

In order to enable the interrupt feature of the PIC, the hardware must be configured to generate the appropriate signal to the MCU. Each interrupt signal has three bits associated with it.

ISR guidelines

Advanced topics

Our PIC has 33 different interrupt sources. These interrupts are split into three categories, resets, core and peripheral. Reset interrupts correspond to events which should cause the MCU to restart. Core interrupts are configured using the INTCONx registers and generally correspond to legacy features (features found on older generation PIC processors). Peripheral interrupts are configured using the PIRx, PIEx and IPRx registers and correspond to more contemporary features of the 18F family of MCUs.

While there are up to 33 distinct sources of interrupts, there are only three interrupt service routines, main, HIGH_ISR, and LOW_ISR as shown in the following figure. It may seem odd to call main an ISR, but its a subroutine that's executed in response to an interrupt event.


This figure shows the interrupt sources on the left and what action is taken when each occurs. When a reset event occurs, the MCU executes the "program" at address 0, which is most cases is a GOTO instruction pointing to main. When one of the other interrupts occurs, the MCU will execute either the program at address 0x08 or 0x18 depending on how the interrupt priority was configured.

Since their may be several different interrupt sources activating an ISR, the first thing that an ISR must do is to poll all the potential interrupt sources to determine which is responsible for activating it. The guts of the ISR should be simple and should be limited to moving the information collected by the hardware to internal storage for further processing by main. In some cases it may be appropriate to perform basic data processing on this data. In many cases, the ISR will alert main that it has collected new data by means of a flag. All communication between the ISR and main will require the use of global variables. The last thing that an ISR needs to do is to return from the interrupt using the RETFIE. This instruction is automatically included in your C code whenever you have an "interrupt" type function.

The Big Picture




  1. MCU powers up, jumps to RESET vector
  2. MCU starts execution of main
  3. Dynamic configuration
    • configure hardware
    • clear hardware interrupt flag
    • enable hardware interrupt
    • enables global interrupts
  4. Event occurs which sets interrupt flag
  5. MCU stops running main
  6. MCU saves W, STATUS, BSR, PC
  7. MCU disables global interrupts
  8. Executes "GOTO ISR" at interrupt vector address
  9. ISR: Poll interrupt flags
  10. ISR: Execute appropriate code in ISR
  11. ISR: Clears interrupt flag
  12. ISR: executes RETFIE
  13. global interrupts are enabled
  14. W, STATUS, BSR, and PC are restored
  15. MCU resumes running main

Test your understanding

You can find the solutions embedded in the "source code" for this web page by right mouse clicking on this web page and selecting "view source". The solutions are in HTML comments.
  1. Adapt basicInterrupt.c to generate a 38kHz waveform with a 50% duty cycle.
  2. Adapt basicInterrupt.c to generate a 38kHz waveform with a 10% duty cycle.