07. Interrupts

Interrupts

The idea of an interrupt is both cool and weird at the same time. If you remember from the old says of Sound Blaster 16 audio cards, I’m sure you’ve had the unfortunate experience of setting up interrupts, and getting a headache in the process. If you still don’t know what they are, don’t fret. It is a concept rarely used outside of computing. Then again, if I’m assuming that you’re reading this tutorial because you’re some sort of an engineer, you should already know what they are.

Pardon the…

Interrupts are exactly what the word means: interruptions in the soon-to-be-executed thread of instructions to the controller. It is basically lines of code that is executed on command when an interrupt signal occurs. These lines of codes, executed after an interrupt is called the “interrupt service routine” (ISR). This interruption signal can come from a variety of sources. It can come from an external signal, generated internally, generated from an internal timer, or turned on manually through code.

The reason that interrupts are important is because there are times when a set of code absolutely must be executed on queue. Since microcontrollers keep relative time with clocks, and it is impossible to know exactly how many clocks it takes to get to a certain portion of the code. In other words, time critical execution of code is impossible to do with sequential code. What this means is that if I want my microcontroller to toggle an LED on and off at exactly every second, it is impossible for me to write a loop in the main() function. I need another way to do the job. For this particular LED application, I need a timer to keep track of time, which will interrupt the microcontroller at exactly every second, which will start the executing of the code which will toggle the LED. In this manner, I can assure the turning on and off of the LED at exactly the desired frequency.

Of course, interrupts are not limited to external timer generated signals. Any TTL signal can be used as an external interrupt. There are several ways for the microcontroller to detect internally generated interrupts such as when a piece of data is received on the UART, or when an internal timer has reached its preset count. We will revisit the LED example on the next portion of the tutorial. For now, we will deal exclusively with external interrupts.

On to the PIC

There are usually 3 external interrupts on a PIC24. They are aptly named External Interrupt 0, External Interrupt 1 and External Interrupt 2. For the sake of simplicity, I will name them INT0, INT1, INT2 respectively.

INT0 is usually on a fixed pin. In the case of a PIC24HFJ32GP202, it is pin 16.

pinout-int0.jpg

INT1 and INT2 are controlled through the peripheral IOs. This was covered in the IO section of the tutorial. Basically, you have the ability to map INT1 and INT2 to any of the RPx pins on the chip. This allows for great versatility. These pins are the locations where I would need to connect a device, or a signal that will send the signal to interrupt the processor’s current thread of instructions. There is a flip-flop inside the PIC, so that the microcontroller is not actually listening for a 1 or a 0, but rather looking for signal edge changes.

Before delving into how to control an external interrupts, a few words on the interrupt controller’s control registers. There are a few important registers that control certain things such as traps, exceptions, priorities of CPU level interrupts and interrupt errors. They are controlled by the registers SR, CORCON, INTCON1 and INTCON2. Generally I find that the default settings work very well. I’ve had only one or two instances where changing one of these settings were necessary. If you don’t understand what one of the functions do, leave it alone. The defaults work just fine for general purposes. However, in this tutorial on interrupts, we will be looking at one of the settings in INTCON2.

Now, let’s move on to the meat of the matter, so to speak. There are three main aspects of the interrupts to understand. These are the FLAG, the ENABLE, and the PRIORITY. All of these aspects of an interrupt are controlled by the interrupt registers. On all the datasheets for the PICs, there is usually a table that maps all the interrupts and their respective controlling registers. Let us look at one of them.

interrupt-registers.jpg

The ENABLE bit turns the interrupt on or off. When the enable bit is set to a 1, the microcontroller starts listening for either a positive edge or a negative edge. The FLAG bit indicates whether an interrupt has occurred. If an interrupt has occurred, the PIC will turn the FLAG bit from a 0 to a 1. This bit has to be turned back to a 0 manually. Lastly, there is a PRIORITY setting. This setting is usually 3 bits wide. The reason for the presence of a PRIORITY setting is the ability to prioritize interrupts from different sources. If two different devices report an interrupt, the microcontroller needs to know which ISR to execute first. For the PIC, there are 8 levels of interrupt priority, with 7 being the highest priority (hence, almost always executes on queue), and 0 as the interrupt disabled.

Interrupt Vectors

Interrupt vector are addresses that the microcontroller go to, to fetch the address of the start of the ISR. Normally this would have to be set manually, but microchip is generous enough to have mapped them all out for us. If to look at the “h” files for a particular chip that comes with C30, you’ll find a section on how to declare interrupt vectors without the need to list absolute addresses. Here is a sample section from the “p24FJ64GA004.h” file.


//The following macros can be used to declare interrupt
// service routines (ISRs). For example, to declare an ISR
// for the timer1 interrupt:
//
// void _ISR _T1Interrupt(void);

Basically we will need to declare these interrupt vectors for INT0 through INT2 in order to carve out a section of the programming memory to write the interrupt service routines. This is a critical step, and must be thoroughly understood. I will reinforce this concept with an example.

Toggle Device Example

Let us take for example, the application of detection edge transitions of a signal. We have a signal that goes from a TTL low to high, and back to low, over a certain amount of time, and we would like to toggle an output pin every time we detect a high to low transition (negative edge). In a pictograph way, we want an output to do the following: when an edge is detected, toggle the output.

interrupt-signal-trace.jpg

We will be using INT1, so we must first configure INT1 to an RP10 peripheral pin, and then make a physical connection between the interrupt signal and RP10. Next, we need to initiate the interrupts on the PIC, declare the interrupt vector, and submit the microcontroller into an infinite loop. The ISR itself needs to include two things. It needs to toggle RB9, which is the output pin, and it needs to reset the interrupt FLAG for INT1. The complete program would look like the following:


/*
Engscope Tutorial
Interrupts
March 27, 2008
Author: JL
*/

//compiled for a PIC24FJ64GA002, your mileage may vary
#include "../h/system.h"

_CONFIG2(0xFBFD);	//use XL external clock, at PPL x 4, and Fosc/2 (osc/2)
_CONFIG1(0x3F7F);	//use in Programming Mode

int main(void)
{
   //OSCCON	=	0x77C0;	//select Internal Clock
   OSCCON	=	0x33C0;	//select Primary Oscillator, External XL, PPL
   CLKDIV	=	0x0000;	//do not divide//Set up I/O Port
   AD1PCFG	=	0xFFFF;	//set to all digital I/O
   TRISB	=	0xFDFF;	//configure all PortB as input, RB9 as output

   //Set up External Interrupt
   RPINR0	=	0x0A00;	//set RP10 to external interrupt 1
   IntInit();    //init interrupts

   //Main Program Loop, Loop forever
   while(1)
   {
   }

   return(0);
}

//setup external pins
void IntInit(void);
void IntInit(void)
{
   INTCON2 = 0x0000;   /*Setup INT0, INT1, INT2, interupt on falling edge*/
   IFS1bits.INT1IF = 0;    /*Reset INT1 interrupt flag */
   IEC1bits.INT1IE = 1;    /*Enable INT1 Interrupt Service Routine */
   IPC5bits.INT1IP = 1;	/*set low priority*/
}

//_INT1Interrupt() is the INT1 interrupt service routine (ISR).
void __attribute__((__interrupt__)) _INT1Interrupt(void);
void __attribute__((__interrupt__, auto_psv)) _INT1Interrupt(void)
{
   LATBbits.LATB9 = ~LATBbits.LATB9;	//toggle through
   IFS1bits.INT1IF = 0;    //Clear the INT1 interrupt flag or else
   //the CPU will keep vectoring back to the ISR
}

I will go over each section. First the configurations are pretty standard. I have included several a “system.h” file, the contents of which does not affect this program. The next lines configure my clocks and programming modes.

Next I start my main() function with several oscillator and clock configuration. After that, I set up my IO ports. I want all IO to be digital, and configure all of the bank B pins as input, except for RB9, which will be an output.


//Set up I/O Port
AD1PCFG	=	0xFFFF;		//set to all digital I/O
TRISB 	=	0xFDFF;		//configure all PortB as input, RB9 as output

Next I need to set up my peripheral pins. In particular, the pin RP10 needs to be declared as the source of INT1:


//Set up External Interrupt
RPINR0	=	0x0A00;		//set RP10 to external interrupt 1

Next I initiate my interrupt with the function call to IntInit(), and lastly, the program loops indefinitely. The reason that there is no code in the main loop is because all the hard work is done through the ISR.

The initiation process is fairly simple. The INTCON2 register has a bit that can set or reset to detect a positive edge, or a negative edge. In this case, I have selected the negative edge.


INTCON2 = 0x0000;       /*Setup INT0, INT1, INT2, interupt on falling edge*/

The IntInit() function resets the FLAG, sets the ENABLE bit to turn on the interrupt channel, and lastly I set the priority to low (1).


IFS1bits.INT1IF = 0;    /*Reset INT1 interrupt flag */
IEC1bits.INT1IE = 1;    /*Enable INT1 Interrupt Service Routine */
IPC5bits.INT1IP = 1;	/*set low priority*/

Of course the priority can be set to high (7) if need be, but this is just a menial demonstration.

The main part of the interrupt is the ISR. Every time RP10 has a negative edge transition, the vector INT1Interrupt(void) is called. There is a cumbersome declaration process, which I have included in this example. There’s no real reason for this declaration, but in order for the compiler to recognize that this is where I want the interrupt vector to point to, I need to declare my ISR in the manner shown. Next I toggle my output pin with the line:


LATBbits.LATB9 = ~LATBbits.LATB9;	//toggle through ISR

And then reset the flag with


IFS1bits.INT1IF = 0;    //Clear the INT1 interrupt flag or else
//the CPU will keep vectoring back to the ISR

Note that you absolutely need to reset the FLAG bit manually. Otherwise you’ll be stuck in the ISR forever. Basically, the program structure is such that the ISR is doing the bulk of the work. This is critical because we don’t really know when an edge transition will occur. However, now are able to set up the controller so that when a transition does occur, we are ready to execute some instructions.

Now you have a device that counts transitions, and toggles a pin every time a count is made.

Table of Contents
Previous – Oscillator and Timing
Next – Timers

4 Responses to “07. Interrupts”
  1. jb3 says:

    hi,

    thanks for this code, exactly the same problem that i still have.
    in simulator, i cannot get int2/3 get work following your code.
    is it possible, that SIM doesn’t implement peripheral pin select?

    best regards,
    jb3

  2. jliu83 says:

    You know to tell you the truth, I don’t have much experience with the simulator. I find it easier and faster to code with an actual circuit. Also, there’s an error on the interrupt code that I just corrected, the IntInit() function was buried in the comments. That function needs to be called first. I’ve made the modifications.

    -J

  3. Tobias says:

    hI

    first. Nice Toturial.

    Just for nagging rights and for consistensi : in you full program you reset you flag in 2 differents ways. The problem is in the interrupt function where you write :
    ISRIFS1bits.INT1IF = 0; insted of
    IFS1bits.INT1IF = 0;

    -T

  4. jliu83 says:

    Thanks for the notice! Updated.

    -J

  5.  
Leave a Reply