To implement this interrupt structure correctly, we will need to write two functions:
Interrupt service routine (ISR) – defines the actions that occur when the interrupt occurs.
Interrupt Set Up – Configures the interrupts. This routine sets up and enables a GPIO interrupt. It will be generic for all interrupts within the system to aid code reuse.
Thankfully the standalone board support package (BSP) contains a number of functions that greatly ease this task. You’ll find these functions within the following header files:
Xparameters.h – defines processor device IDs
XGPIOS.h – drivers for GPIO configuration and use
Xscugic.h – drivers for GIC (General Interrupt Controller) configuration and use
Xil_exception.h – exception functions for the ARM Cortex-A9 processor
Just as before, when we wrote code to communicate with the Zynq SoC’s XADC (MicroZed XADC Software: Adam Taylor’s MicroZed Chronicles Part 8), we need to know the hardware ID parameters for the on-chip devices we wish to use. For this example, those devices are the GIC and GPIO. These parameters are provided mostly within the BSP header file xparameters.h with the exception of the interrupt ID, which is provided by xparameters_ps.h. There is no need to declare this header file within your source code; it is included by the xparameters.h file.
To set up the interrupts, we initialize the exceptions, configure and initialize the GIC, and then connect the GIC to the interrupt handling hardware. The functions needed to do this are provided within the Xil_exception.h and Xscugic.h files.
When it comes to configuring the GPIO to function as an interrupt source, we can configure either an entire I/O bank or an individual pin using functions provided within xgpiops.h. For example:
voidXGpioPs_IntrEnable(XGpioPs *InstancePtr, u8 Bank, u32 Mask);
voidXGpioPs_IntrEnablePin(XGpioPs *InstancePtr, int Pin);
We also need to configure the interrupt correctly: edge triggered or level triggered and which edge or level? We configure the interrupt using this function:
voidXGpioPs_SetIntrTypePin(XGpioPs *InstancePtr, int Pin, u8 IrqType);
Where the IrqType is defined by one of the five definitions within the xgpiops.h file:
#define XGPIOPS_IRQ_TYPE_EDGE_RISING 0 /**< Interrupt on Rising edge */
#define XGPIOPS_IRQ_TYPE_EDGE_BOTH 2 /**< Interrupt on both edges */
#define XGPIOPS_IRQ_TYPE_LEVEL_HIGH 3 /**< Interrupt on high level */
#define XGPIOPS_IRQ_TYPE_LEVEL_LOW 4 /**< Interrupt on low level */
Note that we can configure edge-triggered interrupts to be either rising- or falling-edge interrupts or we can have the interrupt trigger on both rising and falling edges.
If you decide to enable an entire I/O bank, you’ll need to know which bank is associated with the pin or pins you wish to use for interrupts. The Zynq SoC supports a maximum of 118 GPIO pins. For the configuration in this example, all of the MIO (54 pins) are being used as GPIO along with the EMIO (64 pins), broken into four banks with each bank containing 32 pins.
The interrupt set-up function will also define the ISR, which will be called when an interrupt occurs. We use this function:
Having written the interrupt set-up code, our next step will be to write the actual ISR that will be called when an interrupt occurs. An ISR can be as simple or as complicated as the application requires. For this example, the ISR will perform the same task as in the previous polled I/O example: it will toggle the status of the led on and off each time the button is pressed. The ISR will also print out to the console a message when it is run.