For this tutorial you’ll need the following:
- A PIC24 hardware platform – such as our picoTRONICS24 or nanoTRONICS24 microcontroller development boards
- Microchip’s FREE MPLAB X IDE – make sure you’ve got the latest version – they are updating it all the time
- Microchip’s FREE C30 or XC16 C Compiler – also make sure you have the latest version
- We also assume you have working knowledge of C/C++ – if you don’t have this there a many good C/C++ tutorials available on the internet.
The Basics of Interrupts
It’s not a massive stretch to say that interrupts are probably one of the most useful features of modern computers and microcontrollers. PICs and the PIC24 series microcontrollers are of course no exception.
Interrupts are used in all modern, and not so modern computer systems and are the basis for event driven programming. We remember reading someone’s account of setting up interrupts for the old PC Creative Sound Blaster 16 Sound Cards – a right nightmare if we remember rightly! Luckily for computer users, PC hardware and operating systems have moved on, a lot, since those days and now interrupts are transparent to your average computer user.
What Are Interrupts?
Well, interrupts are pretty much as the word describes. Interrupts, interrupt the current instructions, or soon to be executed instructions, running on a microprocessor. The processor then makes a jump in the code and runs another set of instructions called an “interrupt service routine”, or ISR. Most modern processors, of which the PIC24 is no exception, support multiple ISRs.
These interrupt “signals” can come from a number of sources – both external to the microcontroller or generated internally. An example of an external interrupt would include an external interrupt pin or line and an example of an internal one would include a timer.
Why Are Interrupts Important?
Interrupts are important because they allow a section of code to be interrupted at an absolutely known time. Without the function of interrupts, in all but the simplest of applications, it is almost impossible to know how many clock cycles it takes to get to, or execute a section of code. So if you need time critical code, you need interrupts!
So when might you need time critical code? Well remarkable often really! Let’s say that you want to turn a pump on and off everywhere three hours. It’s impossible to write a loop in the applications main() function, that can achieve this level of timing accuracy. To do this, you need to user a timer, more on these in a future tutorial, and have it interrupt the microcontroller at regular intervals – let’s say, for convenience, exactly every second. Using the timers ISRs, you can then use the microcontroller to turn your pump on and off every three hours. Easy really! Well it isn’t really that difficult.
Interrupt Types on the PIC24F series microcontrollers
The PIC24 series of microcontrollers support a large number of interrupt sources. We’ll outline some of these in the section below:
External Interrupt Pins
These are CMOS / TTL compatible pins (also known as normal IO pins) that support an additional external interrupt function. This means when the input to one of these pins changes (for example rising edge, falling edge rather than a pin just being high or low) an interrupt can be generated.
The PIC24FJ64GB004 has a number of external interrupt sources. The primary one is called External Interrupt 0 (INT0) – with its own dedicated IO pin. The other two external interrupts are INT1 and INT2 – these can be assigned to any IO pin that supports the Peripheral Pin Select (PPS) functionality.
The PIC24FJ64GB004 includes loads of “communications peripherals” these include:
All these peripherals can be configured to use interrupts as part of their communications structure or framework.
These interrupts are those generated by peripheral modules internal to the microcontroller and include modules such as:
- Analogue to digital converters (ADCs)
- Real Time Clock / Calendar (RTCC)
- Oscillator fail etc
How interrupts work on the PIC24F series
Like most functions on the PIC24 microcontrollers, and all microcontrollers for that matter, interrupts are controlled by a series of registers. On the PIC24F series they controlled by the registers STATUS REGISTER (SR), CORCON, INTCON1 and INTCON2, INTERRUPT ENABLE CONTROL x (IECx), INTERRUPT FLAG STATUS x (IFSx) and INTERRUPT PRIORITY CONTROL x (IPCx), INTERRUPT CONTROL STATUS REGISTER (INTTREG). In general the default settings work fairly well, but it always pays to check the relevant section of the datasheet to confirm anything you are not sure about. Never underestimate the power of the datasheet! We will however go through the main settings you’ll need in the remainder of this tutorial.
In general all interrupts are controlled in the same way. There is an ENABLE bit, for each interrupt source, which turns the interrupt on or off, that can be found in the IECx register. When an interrupt is trigger (or activated) a status bit, for that particular interrupt source, is set in the relevant IFSx register. This flag is cleared, manually (by setting it back to zero), in software when the interrupt is “serviced”. Each interrupt source can also be assigned an individual priority level (1-8) using the relevant IPCx register. This priority functionality allows the designer to priorities one interrupt source above another. For example, it allows you to say that an external pin interrupt is more important (or higher priority) than a UART interrupt. This priority will take effect if two interrupts occur at the same time (7 is the highest priority and 0 is disabled).
All global interrupt control functionality is set in using the INTCON1 and INTCON2 registers.
Depending on your previous microcontroller experience you might have had to deal with interrupt vectors. These are the address, in memory, where the microcontroller goes to the fetch the Interrupt Service Routine (ISR). An ISR is the software code that executes when an interrupt is triggered. If you are writing your code in C for the PIC24 series devices you don’t have to worry about ISR addresses and the like. Microchip provides a set of definitions for these ISRs, with their C30 or XC16 compilers. You can either look in the header file, for your microcontroller, or the manual for your compiler, to find out what the ISRs are called.
At least initially, all of this may seem a little bit daunting, but we can assure you that interrupts need not be overly complicated and are an extremely useful feature of any microcontroller. To highlight how “simple” interrupts can be, we’ll work through an example of setting up an external interrupt to toggle an output (a LED) every time a low-to-high transition is detected on the external interrupt IO pin.
For this example we will use INT1, which in a lot of circumstances is more convenient than INT0, as it can be moved between IO using the Peripheral Pin Se lect (PPS) feature. INT0 on the other hand is tethered to a particular IO pin.
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:
So what steps do we need to complete to get this external interrupt functionality working:
- INT1 must be configured to use RPx
- INT1 must be initialised so it interrupts on low-to-high transitions
- INT1 must be enabled
- INT1 must have an ISR configured so the PIC microcontroller knows what code to execute when an interrupt occurs.
- Once the INT1 interrupt occurs the INT1 interrupt flag MUST be cleared in the ISR or the interrupt will continuously trigger, forever!
Full source code for this tutorial can be downloaded from our website, but the embedded code below shows the code that is unique to this tutorial, along with a short explanation of each section.
It should also be noted, that on the PIC24F series devices, another way to accomplish this same task would be to utilise the Change Notification functionality. Perhaps this will be a topic for a future tutorial.
Next we start our main() function with several oscillator and clock configuration. After that, we set up our IO ports. We want all IO to be digital, and configure all of the bank B pins as inputs, except for RB9, which will be an output.
// Setup the microcontrollers clocking
// Turn off analogue functions on all pins
AD1PCFG = 0xFFFF;
// Enable the LED pin as an output
TRISBbits.TRISB9 = 0;
// Enable the switch as an input
TRISBbits.TRISB13 = 1;
// Turn the LED on
LATBbits.LATB9 = 1;
Next we need to set up our peripheral pins. In particular, the pin RP13, this needs to be declared as the source of INT1:
// Step 1 – configure the PPS function to assign INT1 to an IO pin
PPSUnLock; // Unlock the PPS functionality
// Configure INT1 to the correct IO pin
iPPSInput( IN_FN_PPS_INT1, IN_PIN_PPS_RP13 );
PPSLock; // Lock the PPS functionality
Now we need to configure the INT1 interrupt to do what we want. This is easily implemented by using the Microchip provided functions:
// Now it is time to configure the INT1 functionality
/* Step 2 – Setup the interrupt with the following attributes:
* – Interrupt enabled
* – Interrupt on a rising edge (low-to-high)
* – With a priority of 2 (this is arbitrary in this example)
ConfigINT1( INT_ENABLE & RISING_EDGE_INT & INT_PRI_2 );
Finally we need to configure an Interrupt Service Routine (ISR) for our INT1 interrupt. Inside this ISR we invert the LED to show that the interrupt is functioning correctly:
* This function is the ISR (interrupt service routine) responsible for dealing with the
* external interrupt INT1
void __attribute__((__interrupt__, auto_psv )) _ISR _INT1Interrupt (void)
// Clear the interrupt status flag associated with this interrupt – very important
// Temporarily disable this interrupt – so it doesn’t trigger multiple times due to button debounce
// Invert LED
LATBbits.LATB9 = ~PORTBbits.RB9;
// Delay to await for the button position to stabilised
// Clear the interrupt status flag associated with this interrupt – very important
// Re-enable the interrupt
With this complete, the INT1 peripheral and its interrupt are configured to toggle the LED each time the switch is pushed. Once again, as you can see it is very simple task.