GPIO Interupts

Configuring the Zynq GPIO module to generate Interrupt requests.

2587

Interrupts from GPIO

The GPIO module features logic that can generate interrupts for inputs. Each channel can be configured to set interrupt flags from rising/falling edges, or having a high-low level. Before reading this document, make sure you have a basic understanding of the operation of the GPIO module.

Interrupt configuration registers

Each bank has it’s own set of Interrupt registers. The relevant registers and their purpose are listed below:

INT_MASK[n] Read only register, displays which channel’s interrupts are masked(disabled). masked interrupts read as a 1(disabled), and unmasked read as a 0(enabled).

INT_EN[n] Writing a 1 to a bitfield in this register enables(unmasks) interrupts for the corresponding channel writing a 0 to a bitfield has no effect.

INT_DIS[n] Writing a 1 to a bitfield in this register disables(masks) interrupts for the corresponding channel writing a 0 to a bitfield has no effect.

INT_STAT[n] Displays interrupt status of each channel. Writing a 1 to a bitfield clears the interrupt flag for that channel.

INT_TYPE[n] Defines whether the flag for each channel is level-sensitive(0) or edge-sensitive(1)

INT_POLARITY[n] Determines for each channel if the interrupt is active low or active high

INT_ON_ANY[n] If a channel’s interrupt type is set to edge-sensitive and the corresponding bit in this register is set, interrupts will trigger on both edges.

The table below shows the different sensitivity and polarities and how to configure the type, polarity, and any edge registers.

Event INT_TYPE INT_POLARITY INT_ANY
Rising Edge 1 1 0
Falling Edge 1 0 0
Both Edges 1 x 1
High Level 0 1 x
Low Level 0 0 x

How GPIO interrupts work

when a GPIO channel is configured as an input, it can set bits in its bank’s interrupt status regiter. The bit for a channel will be set when the event the channel is configured for occurs; The events and their associated configuration in registers is shown in the table above. For example, if a GPIO channel connected to a button is configured for rising edge events, the flag for that channel will be set in the corresponding bitfield in the corresponding BANK’s GPIO_INT_STAT register. If a bitfield is set in the status register (commonly referred to as a flag) and the interrupts are enabled(unmasked) for the corresponding channel, the module will signal an interrupt to the GIC. If the interrupt is accepted by the GIC and processor, the processor will jump to the interrupt vector and execute the code there.

Addresses

Below are c macros to access the interrupt registers for the GPIO module.

//interrupt mask readonly reg
#define GPIO_INT_MASK(n)	*(((uint32_t *) 0xE000A20C) + 16*(n))

//interrupt enable
#define GPIO_INT_EN(n)		*(((uint32_t *) 0xE000A210) + 16*(n))

//interrupt disable
#define GPIO_INT_DIS(n)		*(((uint32_t *) 0xE000A214) + 16*(n))

//interrupt status register
#define GPIO_INT_STAT(n)	*(((uint32_t *) 0xE000A218) + 16*(n))

//interrupt edge/level sensitivity register
#define GPIO_INT_TYPE(n)	*(((uint32_t *) 0xE000A21C) + 16*(n))

//interrupt polarity register
#define GPIO_INT_POL(n)		*(((uint32_t *) 0xE000A220) + 16*(n))

//interrupt any edge register
#define GPIO_INT_ANY(n)		*(((uint32_t *) 0xE000A224) + 16*(n))

Accessing GPIO interrupt registers

Let’s define a few simple functions to interact with these registers. The same as in the document describing the GPIO, these functions will make use of bitmasks to decide which fields (channels in the case of GPIO) in the register to modify. With the Above definitions you can still read and write values directly to the registers, but writing functions this way allows easy access to the bitfields you want, without modifying the other fields in the register.

//disables interrupts for channels set in the mask, for the given bank
void disable_GPIO_interrupt(int bank, uint32_t mask)
{
	GPIO_INT_DIS(bank) = mask;
}

//enables interrupts for channels set in the mask, for the given bank
void enable_GPIO_interrupt(int bank, uint32_t mask)
{
	GPIO_INT_EN(bank) = mask;
}

//sets int sensitivity of the channels set in the mask to level-sensitive
void set_GPIO_int_level_sens(int bank, uint32_t mask)
{
	GPIO_INT_TYPE(bank) &= ~mask;
}
//sets int sensitivity of the channels set in the mask to edge-sensitive
void set_GPIO_int_edge_sens(int bank, uint32_t mask)
{
	GPIO_INT_TYPE(bank) |= mask;
}

//sets polarity of the channels set in the mask to high
void set_GPIO_int_pol_high(int bank, uint32_t mask)
{
	GPIO_INT_POL(bank) |= mask;
}

//sets polarity of the channels set in the mask to high
void set_GPIO_int_pol_low(int bank, uint32_t mask)
{
	GPIO_INT_POL(bank) &= ~mask;
}

//sets edges of the channels set in the mask to both (if edge sensitive)
void set_GPIO_int_any_edge(int bank, uint32_t mask)
{
	GPIO_INT_ANY(bank) |= mask;
}

//sets edges of the channels set in the mask to only specified edge [from pol] (if edge sensitive)
void clear_GPIO_int_any_edge(int bank, uint32_t mask)
{
	GPIO_INT_ANY(bank) &= ~mask;
}

//clears the fields in given bank's int flag reg, based on the mask
void clear_GPIO_int_status(int bank, uint32_t mask)
{
	GPIO_INT_STAT(bank) = mask;
}

// returns the given bank's interrupt flags, masked by the 2nd parameter
uint32_t get_GPIO_int_status(int bank, uint32_t mask)
{
	return GPIO_INT_STAT(bank) & mask;
}

Note, because of the way the functions are written, you can only write to one GPIO bank’s registers at a time. If the peripherals you want to configure are split across banks, you need to call the function for each bank. If the peripherals are in the same bank you can combine the bitmasks using the bitwise-or.

Defining Constants

To facilitate more readable code, it’s a good idea to set constants for which GPIO bank peripherals are in, and what bitfield controls that peripheral’s channel.

//btn4 is 18 in bank 1, btn5 is 19 in bank 1
//define bit masks for easy access in banks
#define BT4_MASK 0x40000
#define BT5_MASK 0x80000
//define the bank the buttons are in
#define BT4_BANK 1
#define BT5_BANK 1

Usage examples

A few exampls of using the given functions are shown below.

//disabling button 4 interrupts
disable_GPIO_interrupt(BT4_BANK, BT4_MASK);

//disable interrupts for first 4 bits in bank 2
disable_GPIO_interrupt(2, 0xF);

//get interrupt flags for bank 1
uint32_t flags;
flags = get_GPIO_int_status(1, 0xFFFFFFFF);

//get the interrupt flag for button 4
flags = get_GPIO_int_status(BT4_BANK, BT4_MASK); //this will return the bitmask if the button 4 flag is set.

Since the get_GPIO_int_status function returns a bitwise and of the mask and the bank’s interrupt status register, be careful how you interpret the data.

int flags = get_GPIO_int_status(BT4_BANK, BT4_MASK|BT5_MASK); //get flags for bt4 and bt5

//check the BT4 flag only
if( (flags & BT4)!=0 )
{
	//BT4 flag set
}

if( (flags & BT5)!=0 )
{
	//BT5 flag set
}
The above examples show how to check individual flags, as well as a combination of flags, the use of interrupt flags will be covered in the next section, but

if( (flags&BT4) & !(flags&BT5) )
{
	//only BT4 flags set, BT5 flag definitly not set
}

The above examples show how to check individual flags, as well as a combination of flags. It’s also a good idea to define functions to check the specific flags you want.

//returns 1 if btn4 interrupt flag is high, 0 if low
int get_btn4_flag()
{
	return 0!=get_GPIO_int_status(BT4_BANK, BT4_MASK);
}

//returns 1 if btn5 interrupt flag is high, 0 if low
int get_btn5_flag()
{
	return 0!=get_GPIO_int_status(BT5_BANK, BT5_MASK);
}

You’ll also want a function to clear the flags for specific buttons, to clear a flag, just write a 1 to the bit in the flag register, or use the clear_GPIO_int_status function.

void clear_btn4_flag()
{
	clear_GPIO_int_status(BT4_BANK, BT4_MASK);
}

//same as above, just writing directly to register
void clear_bt4_flag_alt()
{
	GPIO_INT_STAT(BT4_MASK) = BT4_MASK)
}

Configure the GPIO module for interrupts

The GPIO module is capable of generating interrupts that the GIC can process and forward to the processor. Before the processor will accept an interrupt from the module, the GIC, and the core must be configured properly. Make sure you read the relevant documents to configuring the interrupt system as a whole.

To setup interrupts from the GPIO module you need to configure the event for a channel and unmask the interrupts

First, before you make any changes, clear the interrupt flag for the corresponding channel. Then configure the event, using the Interrupt polarity, sensitivity, and ‘any edge’ registers. Clear the interrupt flag in the interrupt status register for the channel to clear out any interrupt flags before the channel’s interrupts were disabled. Finally unmask the interrupt.

These steps are shown in the code below. The code sets up the GPIO module to configure bt4 to generate rising edge interrupts and for bt5 to generate falling edge interrupts. Make sure you configure your buttons to respond to the event you’re interested in generating interrupts for.

//configures the GPIO module to generate interrupts for 
void configure_button_interrupts()
{
	//disable all GPIO interrupts
	disable_GPIO_interrupt(BT4_BANK, BT4_MASK | BT5_MASK);


	//set buttons 4 and 5 to edge sensitive
	set_GPIO_int_edge_sens(BT4_BANK, BT4_MASK | BT5_MASK);

	//set button 4 to rising edge interrupts
	set_GPIO_int_pol_high(BT4_BANK, BT4_MASK);

	//set button 5 to falling edge interrupts
	set_GPIO_int_pol_low(BT5_BANK, BT5_MASK);

	//set so only use the defined edge
	clear_GPIO_int_any_edge(BT4_BANK, BT4_MASK | BT5_MASK);

	//clear interrupt flags for button 4 and 5
	clear_GPIO_int_status(BT4_BANK, BT4_MASK | BT5_MASK);

	//enable interrupts for button 4 and 5
	enable_GPIO_interrupt(BT4_BANK, BT4_MASK|BT5_MASK);
}