This video presentation is the sixth installment of the Essentials of MQX RTOS Application Development training course. In this session, you will be introduced to interrupts, the scheduler, and the ISR table.
This training was created by Embedded Access Inc., a Freescale sponsored training provider and proven partner.
|Session 6 Course Line||Lab Outline|
First, watch the video for Session 6: Interrupts.
Then, follow through with the interactive lab assignment below.
SESSION 6: LAB ASSIGNMENT
Effective interrupt handling is a critical part of most any embedded system. There are a few steps you have to learn in order to set up and use interrupts but taking the time to understand these steps are well worth the benefits. In this lab we'll replace the code that polls the switches to monitor for changes in state with interrupt driven code. The reaction the system has to switch presses will be updated to reflect what we'll need them to do for our application.
The objective of this lab is to learn how to handle interrupts in MQX by converting the polled switch handling to interrupt driven switch handling.This objective will be accomplished by:
- Initializing interrupt handling on lwgpio switch inputs
- Installing an isr for each switch
- Enabling lwgpio interrupt in GPIO device
- Enabling lwgpio interrupt in Interrupt controller device
- Writing ISR for switch interrupt that sends a message to the health task
New functions/ structures you will use:
lwgpio_int_init, _int_install_isr, lwgpio_int_enable, _int_install_isr, lwgpio_int_get_vector, lwgpio_int_enable, _bsp_int_init, lwgpio_int_clear_flag
REORGANIZING SWITCH INITIALIZATION
- First we need to do a bit of re-organization of the code to facility the changes we are going to make. Encapsulate the initialization of each switch into a function because there is a lot of common code to initialize the GPIO, set the functionality, and set the attribute.
- Create a new function called init_switch() and add it to InputTask.c. Your function should have the following parameters:
- Move the code you had in the initialization section of the Input Task that you used to initialize, set the functionality, and set the attribute of the of the GPIO and put this code into your new function. The parameters used will be the passed in parameters.
- In the initialization section of the Input Task where you removed the initialize, set the functionality, and set the attributes function calls put in a call to your new init_switch() function using the appropriate parameters that your function is expecting. You will need to call init_switch() twice, once for each switch.
- Compile and run your application to confirm that it operates as it did before. Nothing has functionally changed, but it's a good opportunity to check everything before we move on. Ensure that the green LED is flashing and that pressing sw 1 turns on the orange LED and that pressing sw 2 turns on the yellow LED.
SETTING UP THE INTERRUPTS
- Now we need to set up the GPIO to be interrupt driven. This will require access to the ISR for the interrupt from each switch. This means our init_switch() function will need a fourth parameter which is a pointer to our ISR, which will be of type INT_ISR_FPTR.
- In the init_switch() function, after the set up code we have there already we need to initialize the interrupt using the lwgpio_int_init(). The input from the switches is active low so the mode we'll use for these inputs is LWGPIO_INT_MODE_FALLING. If you recall, during the set up we defined that the internal pull ups should be used and that a closed switch will connect the input to ground. So having the interrupt triggered when the input is falling syncs up with the switch being pressed.
- The next step is for init_switch() to install an interrupt service routine (ISR) that will be called when the falling transition is detected on one of the switch inputs. This is done using _int_install_isr() function. As you saw in the video you need the vector number for the vector assigned to that pin. You can get the vector number using the lwgpio_int_get_vector() function. A pointer to the ISR is the parameter we added two steps ago but we also need to pass the ISR a parameter of the GPIO that triggered the interrupt so the ISR can clear the GPIO / interrupt.
- Next the interrupt needs to be enabled and this needs to happen in two places, in the GPIO device and in the Interrupt Controller. Enabling an ISR in the GPIO device is done using lwgpio_int_enable() function.
- Enabling the interrupt in the Interrupt Controller is done using the _bsp_int_init() function. This function requires the vector number which can be retrieved in the same way that was used two steps ago when installing the ISR. You also pass with this function the priority level and the priority sub level. The last parameter is the enable flag that you'd set to TRUE.
- Where init_switch() is called from the Input Task you'll need to add in the last parameter which is your ISR. You can't use the same ISR for both switches of course so you'll need two different ISRs; 'sw1_isr' and 'sw2_isr'.
CREATING THE ISR FUNCTION
- Now we're ready to write our ISR so we need to create the 'sw1_isr' function in InputTask.c. An ISR takes a standard pointer as a parameter but we really need this to be of type LWGPIO_STRUCT_PTR so the first thing to do is to create a variable of type LWGPIO_STRUCT_PTR and set it to the passed parameter type cast to this same type.
- The first thing an ISR needs to do is to clear the flag associated with this interrupt. Failing to do so will cause you to immediately re-enter the ISR again as soon as you exit it. This is done with the lwgpio_int_clear_flag() function and we need to pass it the pointer to the LWGPIO_STRUCT that we just sorted out in the previous step.
- The only other thing we want to do with our ISR is to send a message to the Health Task that a switch was pressed. You can copy all of this code from the body of the Input Task. Don't forget to declare a pointer of type APPLICATION_MESSAGE as that will be needed by the code. Since this is the ISR for switch 1 the MESSAGE_TYPE of our message should be SW1_MESSAGE and since we are only getting this interrupt when the switch is pressed the DATA field of our message can be set to 0.
- Create the sw2_isr function to behave in the same way that sw1_isr does, but for switch 2 of course. Since these functions are almost identical it is probably easiest to just start with a copy of sw1_isr and make the appropriate changes.
- Since our handling of the switches is now interrupt drive we don't need the code in the Input Task that was polling the switches. You can comment out all of the polling code and message passing code in the while(1) loop of the Input Task and just leave in the time delay.
- Compile and run your code. Confirm that you see a single message from the Display task each time either of the switches are pressed.
CHANGING THE REACTION TO SWITCH PRESSES
- You may have noticed however that the first time the switches are pressed the corresponding LED goes on, but it never goes off. This is because we were using the release of the switches to turn the LEDs off and the ISRs are only reacting to pressing of the switch and not the release of the switch. This could be fixed by implementing additional ISRs that are activated by a low to high transition of the switch inputs, however the purpose of the switches isn't to control the LEDs so it would be better to update the functionality of the switches and the LEDs.
- Our intention is to use switch 2 to clear an alarm status and since the orange, yellow, and blue LEDs (LEDs 0, 1, and 3 respectively) are used to indicate alarm situations these LEDs should all be turned off when switch 2 is pressed. Switch 1 will be used to simulate a security input that indicates an alarm event and so the blue LED will be used to represent that status. Since the Display Task is to handle all print outs and LEDs we'll update the code here. When a switch press message is received from switch 2 (message type SW2_MESSAGE) then the orange, red, and blue LEDs should be set to high (LWGPIO_VALUE_HIGH) using the lwgpio_set_value() function to turn the LED off.
- To control the activation of the security input update the response in the Display Task to a message of type SW1_MESSAGE should be to turn on the orange LED (LED 0) to low (LWGPIO_VALUE_LOW).
- Compile and flash your code to the board. Each press of switch 1 should turn on the orange LED and if the orange LED is on a press of switch 2 will turn it off.
Need more help? The full source code for this lab can be found in the 'Lab Source Code' folder here.