Lecture 20 - Interrupts 10:10 Interrupt Recap Interrupts allow a processor to respond (almost) instantaneously to a hardware event. Hardware Hardware Software (Peripheral) (Interrupt Controller) Interrupt Trigger | | +---------- Interrupt Enable <----------- InterruptEnable() | | V V +-------+ | AND | +---+---+ | (1) +--------------> Interrupt Vector -- Vector --> to CPU Table ISR() { ---------+--------- ... Trigger | Vector Clear Interrupt Flag ---------+--------- } | 1 | ISR | | Clear Trigger <----------------------------------------------+ INTERRUPT TRIGGER = The hardware event that initiates the interrupt INTERRUPT ENABLE = A flag in the interrupt controller that can mask an interrupt trigger INTERRUPT FLAG = INTERRUPT TRIGGER & INTERRUPT ENABLE = The indication that an interrupt should be serviced INTERRUPT VECTOR = The address location in memory that holds the start of the ISR INTERRUPT SERVICE ROUTINE = The software implementation of the interrupt processing CLEARING THE INTERRUPT FLAG = The final activity in the ISR that clears the interrupt flag in hardware When multiple interrupt triggers are involved, we need PRIORITY among interrupts. Each interrupt trigger can be involved with a priority from 7 (lowest) to 0 (highest). In addition, there is an implicit priority defined by the order of the interrupt trigger in the interrupt vector table: trigger listed earlier (lower-numbered vectors) have an implicit higher priority. (Note - the Cortex-M4 supports an even more-refined implementation of interrupt priorities with group-level and subgroup-level priorities. We will ignore this advanced concept in this course, and only deal with the main priority levels, 0 to 7.) 10:20 How to write an ISR? We have to work through the following steps: (1) Determine the interrupt trigger that should be used for the ISR (2) Determine how to enable interrupts from that trigger (3) Determine how to clear interrupt flags from the trigger (4) Write the ISR Question: how do you know what interrupt triggers are available? The fastest, easiest way is to peek into the interrupt vector table, which has an entry for each interrupt trigger. In CCS, there is a subdirectory ccs with each project which contains a file startup_msp432p401r_ccs.c. That file contains a default interrupt vector table: void (* const interruptVectors[])(void) = { ... TA0_0_IRQHandler, /* TA0_0 Interrupt */ TA0_N_IRQHandler, /* TA0_N Interrupt */ TA1_0_IRQHandler, /* TA1_0 Interrupt */ TA1_N_IRQHandler, /* TA1_N Interrupt */ TA2_0_IRQHandler, /* TA2_0 Interrupt */ TA2_N_IRQHandler, /* TA2_N Interrupt */ TA3_0_IRQHandler, /* TA3_0 Interrupt */ TA3_N_IRQHandler, /* TA3_N Interrupt */ ... }; The entries of that table are functions such as extern void TA0_0_IRQHandler (void) __attribute__((weak,alias("Default_Handler"))); The notation is a bit strange, but it means the following: If the user provides an implementation for TA0_0_IRQHandler, then the value of that vector will be the entry point of the user function. If the user does NOT provide an implementation for TA0_0_IRQHandler, then the compiler uses the default, Default_Handler. That function is defined in the same C file as follows: void Default_Handler(void) { /* Fault trap exempt from ULP advisor */ #pragma diag_push #pragma CHECK_ULP("-2.1") /* Enter an infinite loop. */ while(1) { } #pragma diag_pop } In a nutshell, if you send an interrupt trigger into the interrupt controller and the interrupt is enabled, and there is no ISR definition, the program will hang (go in an infinite loop). You can test this in the debugger (we will do this later). 10:25 Example 1: MSP432-ISR-TIMER32-BLINK In this case, we want to handle interrupts from the Timer32. (1) Determine the interrupt trigger that should be used for the ISR What are the interrupt triggers for Timer32? T32_INT1_IRQHandler, /* T32_INT1 Interrupt */ T32_INT2_IRQHandler, /* T32_INT2 Interrupt */ T32_INTC_IRQHandler, /* T32_INTC Interrupt */ Meaning? T32_INT1_IRQHandler -> TIMER32_0_BASE interrupts T32_INT2_IRQHandler -> TIMER32_1_BASE interrupts T32_INTC_IRQHandler -> TIMER32_{0,1}_BASE interrupts (2) Determine how to enable interrupts from that trigger To enable TIMER32_0_BASE interrupts, we add the following: Interrupt_enableInterrupt(INT_T32_INT1); Interrupt_enableMaster(); -> The driverlib user guide has a long list of arguments for the enableInterrupt() function (3) Determine how to clear interrupt flags from the trigger -> That functionality is (most often) device dependent. Therefore, in driverlib, look under the Timer32 device Timer32_clearInterruptFlag(TIMER32_0_BASE); (4) Write the ISR By consulting the interrupt vector table, we know that the function should be called: void T32_INT1_IRQHandler(). We define it as follows void T32_INT1_IRQHandler() { GPIO_toggleOutputOnPin(GPIO_PORT_P2, GPIO_PIN6); if (GPIO_getInputPinValue(GPIO_PORT_P5, GPIO_PIN1) != 0) // clear interrupt only when button is not pressed Timer32_clearInterruptFlag(TIMER32_0_BASE); } -- Demonstrate running it with and without the ISR, and demonstrate the Default Handler 10:35 Example 2: MSP432-ISR-TIMER32-TWOTIMER When we work with MULTIPLE interrupts, we need to consider interrupt priority. In the example, we have two timers, one running at 2Hz overflow, and the second running at 6Hz overflow: Timer32_initModule(TIMER32_0_BASE, TIMER32_PRESCALER_1, TIMER32_32BIT, TIMER32_PERIODIC_MODE); Timer32_setCount(TIMER32_0_BASE, 1500000); // 2 Hz Timer32_initModule(TIMER32_1_BASE, TIMER32_PRESCALER_1, TIMER32_32BIT, TIMER32_PERIODIC_MODE); Timer32_setCount(TIMER32_1_BASE, 500000); // 6 Hz Each of them drive a different ISR: Interrupt_enableInterrupt(INT_T32_INT1); Timer32_startTimer(TIMER32_0_BASE, false); Interrupt_enableInterrupt(INT_T32_INT2); Timer32_startTimer(TIMER32_1_BASE, false); void T32_INT1_IRQHandler() { volatile int i; GPIO_setOutputHighOnPin (GPIO_PORT_P2, GPIO_PIN6); for (i=0; i<50000; i++) ; GPIO_setOutputLowOnPin (GPIO_PORT_P2, GPIO_PIN6); Timer32_clearInterruptFlag(TIMER32_0_BASE); } void T32_INT2_IRQHandler() { volatile int i; GPIO_setOutputHighOnPin (GPIO_PORT_P2, GPIO_PIN0); for (i=0; i<10000; i++) ; GPIO_setOutputLowOnPin (GPIO_PORT_P2, GPIO_PIN0); Timer32_clearInterruptFlag(TIMER32_1_BASE); } What is the ISR doing? - It sets a led, does an idle loop, and turns the LED off - The INT1 takes a little longer, because it takes more iterations What is the default priority of these two ISR? - Because of the interrupt vector table, INT1 is a little more important than INT2: T32_INT1_IRQHandler, /* T32_INT1 Interrupt */ T32_INT2_IRQHandler, /* T32_INT2 Interrupt */ T32_INTC_IRQHandler, /* T32_INTC Interrupt */ - So what do we expect? INT1 has priority over INT2, but it is a slower and longer interrupt. THerefore, INT2 will be periodically interrupted. INT1 runs: XXXXXXXXXXX XXXXXXXXXXX XXXXXXXXX INT2 runs: XXX XX XXX XXX XXX XXX XXX You can easily observe this effect on the LEDs - To make sure that INT2 can always run, we need to increase the priority of INT2: Interrupt_setPriority(INT_T32_INT1,LEVEL(0x2)); Interrupt_setPriority(INT_T32_INT2,LEVEL(0x1)); 10:45 Example 3: MSP432-ISR-TIMER32-LONGTIMER This example illustrates how you can combine arguments with ISRs. Because ISRs can only access global variables, you need to implement data variables as such. In this example, we wish to measure how long a button is pressed. (we use printf, so you can only run this in the CCS debugger). The variable used for communication is glbTicks. Note the prefix 'glb'. I put this prefix on each global variable to remind me that this is a global variable. The interrupt service routine is a timer ISR that is called every millisecond: // Timer Initialization Timer32_initModule(TIMER32_0_BASE, TIMER32_PRESCALER_1, TIMER32_16BIT, TIMER32_PERIODIC_MODE); Timer32_setCount(TIMER32_0_BASE, 3000); // 1000 Hz // Enable interrupts from Timer32 INT1 and start the timer Interrupt_enableInterrupt(INT_T32_INT1); Timer32_startTimer(TIMER32_0_BASE, false); // Enabling MASTER interrupts Interrupt_enableMaster(); void T32_INT1_IRQHandler() { glbTicks++; Timer32_clearInterruptFlag(TIMER32_0_BASE); } The main program will reset glbTicks when button S1 is pressed, and prints its value when the button is released 10:50 Example 4: MSP432-ISR-GPIO GPIO ports can be used as interrupt sources as well. In this case, a level change in a GPIO port triggers an ISR. For port 0 and port 1, the interrupt trigger can be configured per port bit. In this example, we use the interrupt trigger from port 1. We enable the interrupt from PIN1: // BUTTON 1 from Launchpad GPIO_setAsInputPinWithPullUpResistor (GPIO_PORT_P1, GPIO_PIN1); // Enable interrupts from GPIO port P1 GPIO_enableInterrupt(GPIO_PORT_P1, GPIO_PIN1); Interrupt_enableInterrupt(INT_PORT1); Note that in this example, there is a single interrupt vector (INT_PORT1) that handles all of the bits in PORT1. Through GPIO_enableInterrupt, we allow only PIN1 to trigger an interrupt. PORT_P1 GPIO_PIN1 is the left button on the main launchpad. What happens in this example? When we press the button, we toggle the LED. Note that the interrupt is triggered either on 0->1 transition, or else to 1->0 transition. The default is on 1->0. This can be changed using a driverlib call (GPIO_interruptEdgeSelect). 10:55 Example 5: MSP432-ISR-UART The UART has its own interrupt vector. Like the GPIO ports, there are several triggers possible that all call the same vector: - When the UART receives a character - When the UART can send a character - When a receive error occurs In the example, interrupts are tied to the reception of characters: UART_enableInterrupt(EUSCI_A0_BASE,EUSCI_A_UART_RECEIVE_INTERRUPT); Interrupt_enableInterrupt(INT_EUSCIA0); And the ISR simply echoes the received characters: void EUSCIA0_IRQHandler() { GPIO_toggleOutputOnPin(GPIO_PORT_P2, GPIO_PIN6); uint8_t c = UART_receiveData(EUSCI_A0_BASE); UART_transmitData(EUSCI_A0_BASE,c); UART_clearInterruptFlag(EUSCI_A0_BASE, EUSCI_A_UART_RECEIVE_INTERRUPT); } 10:58 Final Example: MSP432-ISR-MICROPHONE-LCD This example is for self-study. It does something similar as the msp430-microphone-lcd example (that we discussed right before the break, in the ADC lecture), but it uses a timer interrupt to make sure that samples are taken at exactly 8 KHz. 10:59 Conclusions - In Lab 4, we will be making use of a timer interrupt. - Interrupts are a powerful (and dangerous) concept. Use them sparingly, and when you do, carefully work through all of the steps of interrupt design: (1) Determine the interrupt trigger that should be used for the ISR (2) Determine how to enable interrupts from that trigger (3) Determine how to clear interrupt flags from the trigger (4) Write the ISR