Interrupts: Programming
Configuring interrupts is a process aided by DriverLib functions and macros, but the process itself is not a straightforward one. Since there are so many interrupt sources, each with its own specific interrupt handler, you will need to do some research in order to figure out what you will be looking for. The process can be broken down into a simple set of instructions, though:
Part 1: Hardware initialization
Part 2: Global variable setup
Part 3: Writing the ISR
Part 4: Writing the rest of the code
Before you read the rest of this page, it is best that you import the following repository:
Part1: HARDWARE Initialization
This part has two major steps on its own.
Init_A: Configuring the interrupt source
Init_B: Configure the interrupt controller:
Init_A: Configuring the interrupt source
For this, we need to talk to the interrupt source (the peripherals such as GPIO, UART, I2C, etc.). It consists of several possible steps.
Init_A_1: We start by clearing the interrupt, which is counter-intuitive! Sometimes the interrupt source has a leftover interrupt and as soon as we enable the interrupt, we get an interrupt. Clearing the flag guarantees that we start with a clean slate.
This is peripheral-specific. Examples of functions that clear interrupt flags:
extern void Timer32_clearInterruptFlag(uint32_t timer);
extern void GPIO_clearInterruptFlag(uint_fast8_t selectedPort,
uint_fast16_t selectedPins);
extern void UART_clearInterruptFlag(uint32_t moduleInstance, uint_fast8_t mask);
Some interrupts are cleared by a specific action. Refer to each peripheral to see how interrupts are cleared.
Init_A_2: We make it possible for the interrupt source to trigger an interrupt. (This is creating the possibility that the yellow wires in the architecture figure become 1 occasionally.)
Init_A_3: For some peripherals, we set under what conditions the peripheral should trigger an interrupt.
For all these steps, you need to look at the MSP432 user manual chapter associated with the interrupt source, e.g. GPIO, or the Driverlib header file for that component, e.g. io.h, or the Driverlib manual for that component.
Init_B: Configure the interrupt controller
For this, We need to talk to NVIC (Nested-Vector Interrupt Controller). We should enable the interrupt in the interrupt controller and possibly set the priorities.
Init_B_1: We need to set the enable interrupt bit in the interrupt controller to 1. This means we are allowing the orange signals in the architecture figure to become 1 when needed. Each interrupt source is associated with a certain register in the interrupt controller. Certain macros in the interrupt.h (part of driverlib) help us figure out what source is associated with what enable-register.
Init_B_2: If we have more than one interrupt and we wish to set priorities, this is the time to do it.
For the above steps, you need to look at the MSP432 user manual chapter associated with NVIC or the Driverlib header file for interrupt.h, or the Driverlib manual for that NVIC (chapter 12).
The below two figures show the initialization for one button (digital i/o) and one Timer32. The steps associated with interrupt initialization are marked.
PART2: Declare the communication variables
We need to declare the variables that can be used as means of communication between ISR and the rest of the functions.
The variables need to be global because both ISR and some other functions have to access them without passing it to each other. This means it is declared outside function boundaries.
They need to be volatile because we do not want the compiler to optimize the code thinking the variables are not changing.
It is best that the variables are static global which means they can only be accessed within the file they are defined in. This will reduce their scope which reduces the negative effects of using global variables.
It is best that these variables are given an initial value so that our program has a known initial state.
The below screenshot shows these steps for the basic example mentioned earlier on this page.
PART 3: writing the ISR
To start, we find the name of the interrupt trigger that ISR we need. In CCS, there is a subdirectory called ccs within each project which contains a file startup_msp432p401r_ccs.c. That file contains a default interrupt vector table. The below figure shows a snippet of that file. As you can see some peripherals such as ADC14 (the A/D) has only one ISR while some such as Timer32 have more than one.
ISR_A: Body of the ISR
Several interrupts can call the same ISR. We should check what really caused the interrupt.
We should take action based on the interrupt. One of the most common actions is to set a flag(boolean) that other functions can see and react to.
ISR_B: Clearing the interrupt
Similar to Init_A_1, we have to talk to the interrupt source and tell the source the interrupt has been serviced so that the interrupt source clears the interrupt. (This is the green line on the figure)
The below screenshot shows the ISR for port 1 that checks a button, S1.
part 4: writing the rest of the code
Finally, we write the body of the code for other functions.
An important part here is to update the global variable in a way that we know we have reacted to it.