The second step in using a GPIO is to read digital signals. At this level, I will take the simplest way: poll the channel periodically, check its value and take the proper action.

System requirements

The system is implemented in a Cora Z7-07S evaluation board. The PL is not used, but the AXI controller is used to access the two buttons and LEDs on the board.

While the button BTN1 is pushed, the blue LED in LD1 turns on, and BTN0 does the same for the red LED in LD0. When the button is released, the corresponding LED turns off. If both buttons are pressed simultaneously, both LEDs turn on.

Hardware configuration

LEDs and buttons are hardwired into the evaluation board and are connected to the AXI module. The relevant connections are summarised in the table below.

Board element Board signal name Chip signal name Chip pin number Diagram signal name
LD0 red LED0_R IO_L21P_T3_DQS_AD14P_35 N15 rgb_leds[0]
LD1 blue LED1_B IO_0_35 G14 rgb_leds[5]
BTN0 BTN0 IO_L4N_T0_35 D20 btns_2bits[0]
BTN1 BTN1 IO_L4P_T0_35 D19 btns_2bits[1]

Table 1: LEDs and button connections. In addition, the Elaborated Design window in Vivado shows another set of names for these signals. For example, BTN0 has the name btns_2bits_tri_i[0] and it is mapped to the Board Part Pin btns_2bits_tri_i_0.

I created a new Block design in Vivado, similar to the one used in the previous step, but this time, I also selected btns 2bits in the GPIO2 IP interface. Then, create the .xsa module file with the following steps:

  1. Use the Run Block Automation and RUN Connection Automation with All Automation selected.
  2. Validate Design.
  3. Regenerate Layout to organized it better.
  4. Generate Block Design, Use Global as Synthesis Options.
  5. Create HDL Wrapper. Find the block design file in the Sources tab of the Project Manager. The option is in the right-click menu.
  6. Check the pin assignments, voltage standards and pin direction by Open Elaborated Design.
  7. Generate Bitstream.
  8. Export Hardware from File menu. Include the bitstream. This step will create the .xsa from which a new platform will be created in Vitis.

Software

There are three projects to create in Vitis. First, the Platform project takes the .xsa file and builds the platform. The second is the Application project, which will have the code. I start the project with an Empty Application (C) template and then create the main.c file. At the same time, Vitis will create a System project named with the suffix _system after the application project name. I don’t know what this project is for; I guess it is an advanced feature.

The complete code is the following:

#include "xparameters.h"
#include "xgpio.h"
#include "xil_types.h"

#define GPIO_ID XPAR_AXI_GPIO_0_DEVICE_ID
#define LED_CHANNEL 1
#define BTN_CHANNEL 2

int main() {
	u32 led_mask = 0b100001; // 0bBGRBGR
	XGpio_Config *cfg_ptr;
	XGpio gpio_device;
	u32 btns;
	u32 leds;

	// Initialize AXI GPIO device
	cfg_ptr = XGpio_LookupConfig(GPIO_ID);
	XGpio_CfgInitialize(&gpio_device, cfg_ptr, cfg_ptr->BaseAddress);

	// Set direction of the GPIO bits.
	// Bits set to 0 are output and bits set to 1 are input.
	XGpio_SetDataDirection(&gpio_device, LED_CHANNEL, 0);
	XGpio_SetDataDirection(&gpio_device, BTN_CHANNEL, 0b11);

	while (1)
	{
		// Read both buttons
		btns = XGpio_DiscreteRead(&gpio_device, BTN_CHANNEL);

		// Find which button is pressed and turn on the corresponding LED
		if (btns == 0b01) {
			leds = led_mask & 0b000001;
		}
		else if (btns == 0b10) {
			leds = led_mask & 0b100000;
		}
		else if (btns == 0b11) {
			leds = led_mask;
		}
		else {
			leds = 0;
		}
		XGpio_DiscreteWrite(&gpio_device, LED_CHANNEL, leds);
	}
}

The new part here is the definition of the buttons channel.

#define BTN_CHANNEL 2

This tells us that the GPIO device is divided into two channels, one for the LEDs (channel 1) and another for the buttons (channel 2). It seems obvious, but keep in mind that this reflects the structure of the AXI GPIO module we used in the Block diagram in Vivado. Another alternative is to use two AXI modules, with one channel each. Both LED_CHANNEL and BTN_CHANNEL will point to channel 1 of two different GPIO_ID. Both configurations work for this simple example, but using one or two AXI modules has consequences on other elements, such as interruptions.

Initialisation of the AXI GPIO module

There is nothing new in the initialisation. Just notice that the initialisation happens for the LEDs and button channels simultaneously.

Only the port direction has to be set independently.

// Bits set to 0 are output and bits set to 1 are input.
XGpio_SetDataDirection(&gpio_device, LED_CHANNEL, 0);
XGpio_SetDataDirection(&gpio_device, BTN_CHANNEL, 0b11);

Read buttons state

In the main loop, each run polls the state of the buttons.

btns = XGpio_DiscreteRead(&gpio_device, BTN_CHANNEL);

Every time, the values are returned in an array of two elements.

Turn on the LEDs

The function to control the LEDs state is XGpio_DiscreteWrite(&gpio_device, LED_CHANNEL, leds). The variable leds has the value for all six LEDs in LD0 and LD1 combined. Here, we only use the first and sixth bit of the variable. The logic is summarised in the following table.

BTN0 BTN1 leds
0 0 0b000000
1 0 0b000001
0 1 0b100000
1 1 0b100001

Table 2: Logic table mapping the status of buttons and LEDs.

Compiling and uploading

In the Explorer tab, select the project name and Build from the hammer or right-click menu.

To upload and run the code, open again the right-click menu and select Run As.. > Run Hardware. Select the Configuration and OK.