There are some really great chips out there, many with almost unbelievable numbers of interrupt vectors. Unfortunately, it always seems to come up that you have a single interrupt vector that has to be split between two or more different events.
So you have been an embedded engineering for a while, you know the drill... on interrupt, check the flag, execute the proper subtroutine, clear the flag, etc.
I am going to introduce you to a technique that I like to code into my APIs that emulates separate interrupt vectors, even when only one is being used. This technique is a bit more complex at first glance, but it makes your application more dynamic and adaptable to modification, contributing to code reuse and stability.
I am going to demonstrate this using an MSP430G2231 from the TI launchpad, but I have used this technique across manufacturers with no variation on the basic process. For simplicity, I will simply attempt to emulate multiple interrupt vectors on the port 1 interrupt pins since this requires the simplest level of understanding.
As you may know, each pin on port 1 has the ability to interrupt on a rising or falling edge. Unfortunately, each pin doesn't have its own interrupt vector, so - if you have multiple pins that need to interrupt the processor - you will need to check which pin caused the interrupt within the interrupt service routine. This causes you to write and re-write the interrupt routine as the application progresses towards the finish line and is not a very dynamic approach in the long run.
We are going to write lower-level functions that will always take care of the details of adding and removing interrupts for us, then associate higher-level functions using a defined API in order to made adding and removing interrupt functions easy and quick.
You don't have to separate this into its own file, but I do recommend it. Organizing your project into different files allows you to focus on the task at hand. In this case, I am going to create a library called 'dio' which consists of 'dio.h' and 'dio.c'.
Add the Header
By creating your header file first, you are forcing yourself to define the API. In our case, our header - dio.h - only contains four functions:
1 2 3 4 | void DIO_makeOutput(int port, int pin);
void DIO_makeInput(int port, int pin);
void DIO_enableInterrupt(int port, int pin, void (*functPtr)());
void DIO_disableInterrupt(int port, int pin);
|
We could easily add more functionality to this library, but that would distract from the goal of the article.
The 'makeInput' and 'makeOutput' functions are pretty easy to understand. They make a pin an input or output pin.
The 'enableInterrupt' function takes three arguments: port, pin, and functPtr. This last one is simply a function that you wish to be executed when the interrupt occurs.
The 'disableInterrupt' simply disables interrupts on that pin.
As hinted in the sample header file, function pointers are going to be utilized to accomplish interrupt vector multiplexing. In your C file, you will need to declare each interrupt for which you wish to emulate a vector.
1 2 3 4 5 6 7 8 | void (*DIO_p10FunctPtr)();
void (*DIO_p11FunctPtr)();
void (*DIO_p12FunctPtr)();
void (*DIO_p13FunctPtr)();
void (*DIO_p14FunctPtr)();
void (*DIO_p15FunctPtr)();
void (*DIO_p16FunctPtr)();
void (*DIO_p17FunctPtr)();
|
We are declaring variables, but this notation tells the compiler that we expect these to operate like function pointers. There are other ways that we can do this, but this also makes it clear to the reader that the intent is for these to be function pointers.
Now we get into the meat of the library. We are to write the ISR using the interrupt vector given to us in 'msp430.h'.
1 2 3 4 | #pragma vector=PORT2_VECTOR
__interrupt void DIO_port2IntFunct(){
}
|
All of the actual interrupt code will be executed within this interrupt. Finally, the interesting bit where your pin-specific code gets executed:
1 2 3 4 5 6 7 8 9 10 11 12 13 | #pragma vector=PORT1_VECTOR
__interrupt void DIO_port1IntFunct(){
/* find out which pin triggered the interrupt */
if(P1IFG & BIT0){
/* if the function pointer points to an address,
* then execute that function call */
if(DIO_p10FunctPtr != 0){
(*DIO_p10FunctPtr)();
}
P1IFG &= ~BIT0;
}else if(P1IFG & BIT1){
// ... more code follows...
|
For each available interrupt flag, the code must check the flag, execute the function point (only if there is one assigned!), and clear the hardware interrupt flag. The actual function is pretty long, so I'm not going to finish it here. You can find it on github.
Quick aside for those of you that aren't familiar with the notation (I'm looking at the Microchip guys!). Defines like 'BIT0' are simply hard-coded numbers. In the case of 'BIT0', it translates to 0b00000001. BIT1 would translate to 0b00000010. AVR and MSP430 series use this notation pretty heavily for most SFR interactions.
Up to thing point things make sense, but we haven't implemented the interrupt enabling function just yet.
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 41 42 43 44 45 46 47 48 49 50 51 52 53 | void DIO_enableInterrupt(int port, int pin, void (*functPtr)()){
/* pins that interrupt should generally be inputs */
DIO_makeInput(port, pin);
switch(port){
case 1:
{
unsigned char mask = 1 << pin;
P1IFG &= ~mask; // clear the interrupt flag
P1IE |= mask; // enable interupts on this pin
__bis_SR_register(GIE); // enable global interrupts
/* associate the function pointer */
switch(pin){
case 0: DIO_p10FunctPtr = functPtr; break;
case 1: DIO_p11FunctPtr = functPtr; break;
case 2: DIO_p12FunctPtr = functPtr; break;
case 3: DIO_p13FunctPtr = functPtr; break;
case 4: DIO_p14FunctPtr = functPtr; break;
case 5: DIO_p15FunctPtr = functPtr; break;
case 6: DIO_p16FunctPtr = functPtr; break;
case 7: DIO_p17FunctPtr = functPtr; break;
default: while(1);
}
break;
}
case 2:
{
unsigned char mask = 1 << pin;
P2IFG &= ~mask; // clear the interrupt flag
P2IE |= mask; // enable interupts on this pin
__bis_SR_register(GIE); // enable global interrupts
/* associate the function pointer */
switch(pin){
case 6: DIO_p26FunctPtr = functPtr; break;
case 7: DIO_p27FunctPtr = functPtr; break;
default: while(1);
}
break;
}
default:
{
while(1); // programmer's trap
}
}
}
|
First, a mask is created that is useful for setting the correct interrupt bits. The interrupt flag is cleared, interrupts enabled, and the global interrupt bit is set.
Finally, depending on the pin that was selected, the proper function pointer is loaded with the function pointer that was supplied in the parameters. This is the final link that we need to get interrupts up and going on this processor.
The 'default: while(1);' is a programmer's trap that I always place in switch/case statements. If I type an invalid value into the parameters, the program will get stuck right here when I'm debugging. When I pause the debugger, I will see immediately where I went wrong.
Why would you want to disable interrupts? I admit, this doesn't come up often, but adding the functionality is trivial once you have coded up to this point and it can add an interesting dynamism. For instance, you can have two interrupts 'competing' and one of them disable the other. This might be useful in a Jeopardy-style hand buzzer.
You should try to figure this one out yourself, but the source is available on github if you'd like to take a look.
So, up to this point you haven't done anything on the processor! Open your 'main.c' file and we will write a quick code that pits two interrupts against one another!
The goal is to write two 'interrupt' functions which - when executed - will disable the other interrupt function.
1 2 3 4 5 | #define LED1 BIT0
#define LED2 BIT6
void p13Int(void);
void p17Int(void);
|
First, we add a couple of defines that will make our lives easier when setting LEDs. Next, we declare our interrupt functions. I am associating these functions with pins 1.3 and 1.7, which explains the cryptic names. I usually try to add a more descriptive name that deals with the functionality, maybe 'deathToP17' might have been more appropriate?
1 2 3 4 5 6 7 8 9 10 11 12 13 | int main(void) {
WDTCTL = WDTPW | WDTHOLD; // Stop watchdog timer
/* adding interrupt is as simple as a function
* call with port, pin, and function pointer */
DIO_enableInterrupt(1, 3, &p13Int);
DIO_enableInterrupt(1, 7, &p17Int);
P1DIR |= (LED1 | LED2); // use to light the LEDs
P1OUT |= (LED1 | LED2); // turn the LEDs on
return 0;
}
|
The 'main' function is incredibly simple (my apologies for the spacing). We execute the 'DIO_enableInterrupt' function twice, one for each pin and function. Now this is why we went through the trouble of coding all of the ISR setting code back there. Using the ISR is now incredibly simple and - fortunately for us - the MSP430 series is very consistent on its interrupts, so you may never need to hand-code a DIO ISR again!
The LED setup are just for my own debugging, but I'm leaving it in there.
1 2 3 4 5 6 7 8 9 10 11 12 13 | void p13Int(void){
/* using the 'dio' files, we can easily
* manipulate this interrupt or other interrupts */
DIO_disableInterrupt(1, 7);
P1OUT ^= LED1; // toggle LED1
}
void p17Int(void){
DIO_disableInterrupt(1, 3);
P1OUT ^= LED2; // toggle LED2
}
|
Finally, the interrupt functions themselves! Each function simply disables the other function and toggles the LED.
Remember, if you place any variables in here, they should still be declared 'volatile'!
The complete source code can be found on github. Just place the files into your CCS project.