Digital Input with Interrupt

In this tutorial i will show how to read a digital input by using a interrupt.. This i

s for the TM4C123 launchpad and was made using IAR workbench free licence limited to 32Kb code.

It will be used SW1 from the launchpad, the switch one the left as our digital input. The idea is to control a LED toggle.

First has any code configure the clock, to 80Mhz in this case:

SysCtlClockSet(SYSCTL_SYSDIV_2_5|SYSCTL_USE_PLL|SYSCTL_OSC_MAIN|SYSCTL_XTAL_16MHZ);

Let's configure the Button pin and LED pin like in Simple Digital Input:

SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF); SysCtlDelay(3); GPIOPinTypeGPIOInput(GPIO_PORTF_BASE, GPIO_PIN_4); GPIOPadConfigSet(GPIO_PORTF_BASE,GPIO_PIN_4,GPIO_STRENGTH_2MA,GPIO_PIN_TYPE_STD_WPU); GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1);

Now the interrupt

If you don't know what a interrupt is i advise you consulting this Microprocessor Design/Interrupts. I think gives a general idea on what a interrupt does. There's still allot more to say about interrupts for in general that will do. If you really want to know about ARM-M4 interrupts you can check this out A Beginner’s Guide on Interrupt Latency, but consider that this is complex.

I hope you remember in Understanding the TIVA GPIO that in the specs it specified the type of interrupts the GPIO can interrupt but let's see them again.

  • It has the ability to sense edges:

    • rising, falling, or both

    • Edge sensing is the ability so sense when a signal changes from one state to another (Ex: from 0 to 1 is a rising edge)

  • Level sensing:

    • High and Low

    • Level sensing is sensing the state of the pin. (Ex: if an interrupt is set to level sensing low, then it keeps calling the interrupt handler while the pin is 0)

Important to note that a interrupt is per GPIO. In the TM4C123 lauchpad each GPIO can only have 1 interrupt handler, meaning that you can't get separate handlers per pin. I will talk about handlers in a bit.

So how to set the interrupt type?

GPIOIntTypeSet(GPIO_PORTF_BASE,GPIO_PIN_4,GPIO_FALLING_EDGE);

This will set the PF4 interrupt to be falling edge, note that we still haven't enabled the interrupt so it's still not working.

You can change the 3rd parameter, which is the type of interrupt, with this macros:

  • GPIO_FALLING_EDGE sets detection to edge and trigger to falling

  • GPIO_RISING_EDGE sets detection to edge and trigger to rising

  • GPIO_BOTH_EDGES sets detection to both edges

  • GPIO_LOW_LEVEL sets detection to low level

  • GPIO_HIGH_LEVEL sets detection to high level

Now the interrupt handler

Well if you read the Microprocessor Design/Interrupts you should have read about interrupt handlers. Interrupt handler is where the code goes when a interrupt is called. I'm going to teach the way i use to set the interrupt handler but there is a way to set it in the startup.c that is required for CCS and IAR projects. The way to set it is to first create a:

void PortFIntHandler(); function. This is where the code will run when the interrupt is called.

Then you need to do:

GPIOIntRegister(GPIO_PORTF_BASE,PortFIntHandler);

You can change the name of the handler to any you want but it needs to be a void and accept no parameters.

Almost there now enable the interrupt:

GPIOIntEnable(GPIO_PORTF_BASE, GPIO_INT_PIN_4);

You can use any of these sources to enable the interrut:

  • GPIO_INT_PIN_0 - interrupt due to activity on Pin 0.

  • GPIO_INT_PIN_1 - interrupt due to activity on Pin 1.

  • GPIO_INT_PIN_2 - interrupt due to activity on Pin 2.

  • GPIO_INT_PIN_3 - interrupt due to activity on Pin 3.

  • GPIO_INT_PIN_4 - interrupt due to activity on Pin 4.

  • GPIO_INT_PIN_5 - interrupt due to activity on Pin 5.

  • GPIO_INT_PIN_6 - interrupt due to activity on Pin 6.

  • GPIO_INT_PIN_7 - interrupt due to activity on Pin 7.

  • GPIO_INT_DMA - interrupt due to DMA activity on this GPIO module.

Inside the Interrupt Handler:

First of all the most important thing to have in a handler:

GPIOIntClear(GPIO_PORTF_BASE, GPIO_INT_PIN_4);

This will clear the flag that tells the NVIC (a peripheral that takes care of interrupts) to go to the handler. If you don't clear it, it will create a infinite loop of always getting right back into the handler after leaving it.

Note that the 2nd parameter is the flag to be cleared. Remember i said each GPIO has only 1 interrupt handler? So if you want a interrupt in pin4 but also in pin5? Well you have to check the flags and of course clear them.

This is how to check which flag was set and called the interrupt handler, it checks 2 different pin flags just to show the example.

uint32_t status=0; status = GPIOIntStatus(GPIO_PORTF_BASE,true); GPIOIntClear(GPIO_PORTF_BASE,status); if( (status & GPIO_INT_PIN_4) == GPIO_INT_PIN_4){ //Then there was a pin4 interrupt} if( (status & GPIO_INT_PIN_5) == GPIO_INT_PIN_5){ //Then there was a pin5 interrupt}

In general you should always do this even if you only have 1 interrupt enabled.

Now the toggle the LED

You can use the same part of the code has in Simple Digital Input but with 1 difference. The variable "state" needs to be global. This is because the value of "state" needs to be preserved between interrupts and if it was local that wouldn't happen.

State needs to be volatile, normally every global variable used in a interrupt needs to be volatile:

volatile uint8_t value=0;

uint8_t value=0; value= GPIOPinRead(GPIO_PORTF_BASE,GPIO_PIN_4); if( value==0) state^=GPIO_PIN_1; GPIOPinWrite(GPIO_PORTF_BASE,GPIO_PIN_1, state); SysCtlDelay(7000000);

Now that delay... that shouldn't be there. In this case we will keep it because the processor is only doing that but a interrupt should be as fast as possible. It's like something that runs in the background of your main code so it needs to be. There can be multiple interrupts so that also why it needs to be fast. The delay is for debouncing but you should use something better that doesn't take much time, either by software or hardware.

Well i hope you liked it and understand now in general how to use interrupts with the GPIO using the TivaWare GPIO API.

Here is the final code:

/* This code was made using TivaWare 2.1.0.12573. TivaWare rights to it are all own byTexas Instruments This code shows how to toggle the RED LED (PF1) of the TM4C123 Launchpadwith the left button at PF4, using Tivaware. In this one a interrupt isused to sense the button being pressed Luís Afonso*/#include <stdint.h>#include <stdbool.h>#include "inc/hw_types.h"#include "inc/hw_gpio.h"#include "driverlib/pin_map.h"#include "driverlib/sysctl.c"#include "driverlib/sysctl.h"#include "driverlib/gpio.c"#include "driverlib/gpio.h"/* These defines help if you want to change the LED pin or the Button pin. Remember if you change to a diferent GPIO you need to enable the system clock on it*/#define LED_PERIPH SYSCTL_PERIPH_GPIOF#define LED_BASE GPIO_PORTF_BASE#define RED_LED GPIO_PIN_1#define Button_PERIPH SYSCTL_PERIPH_GPIOF#define ButtonBase GPIO_PORTF_BASE#define Button GPIO_PIN_4#define ButtonInt GPIO_INT_PIN_4volatile uint8_t value=0; void PortFIntHandler(){ uint32_t status=0; status = GPIOIntStatus(ButtonBase,true); GPIOIntClear(ButtonBase,status); if(status & ButtonInt == ButtonInt){ //Then there was a Button pin interrupt uint8_t value=0; value= GPIOPinRead(GPIO_PORTF_BASE,GPIO_PIN_4); if( value==0) state^=RED_LED; GPIOPinWrite(LED_BASE,RED_LED, state); /* This delay is for deboucing but since it's in a interrupt it should be used a better method that is faster */ SysCtlDelay(7000000); } } int main(void){ //Set the clock to 80Mhz SysCtlClockSet(SYSCTL_SYSDIV_2_5|SYSCTL_USE_PLL|SYSCTL_OSC_MAIN|SYSCTL_XTAL_16MHZ); /* No need to enable the button peripheral since it's the same as the LED in this case */ SysCtlPeripheralEnable(LED_PERIPH); SysCtlDelay(3); /* Configure the switch on the left of the launchpad, GPIO_PIN_4 to a input with internal pull-up. */ GPIOPinTypeGPIOInput(ButtonBase, Button); GPIOPadConfigSet(ButtonBase ,Button,GPIO_STRENGTH_2MA,GPIO_PIN_TYPE_STD_WPU); GPIOIntTypeSet(GPIO_PORTF_BASE,GPIO_PIN_4,GPIO_FALLING_EDGE); GPIOIntRegister(GPIO_PORTF_BASE,PortFIntHandler); GPIOIntEnable(GPIO_PORTF_BASE, GPIO_INT_PIN_4); /* Configures the Red LED, GPIO_PIN_1, to output */ GPIOPinTypeGPIOOutput(LED_BASE, RED_LED); /* A variable is created to store the return value of GPIOPinRead and then checked if it's 0 or not.The button when pressed, connected the pin to the GND so it gives 0. We use a variable "state" because in GPIOPinWrite we need to use GPIO_PIN_1 to set the pin HIGH. So we XOR the state with GPIO_PIN_1 so it toggles the "state" every time the button is pressed. The SysCtlDelay of about 0.25s is a rude way to avoid bouncing. The button being mechanical doesn't give a perfect 1 and 0 signal, instead, to the "eyes" of the digital pin, it bounces between 1 and 0 when you press it before stabilizing*/ uint32_t value=0; uint8_t state=0; while(1){ value= GPIOPinRead(ButtonBase,Button); if( value==0) state^=RED_LED; GPIOPinWrite(LED_BASE,RED_LED, state); SysCtlDelay(7000000); } }