Another blinking LED tutorial
When starting learning the ZYNQ7 PS I skipped the blinking LED tutorial. So, now I’m cursed with the hardware equivalent of the Hello World curse. To break this evil spell I have to write this tutorial.
I don’t remember where I got the code I’m using here, but it’s not 100% mine. If it’s yours, let me know. I will credit you appropriately.
System requirements
The system uses a Cora Z7-07S board to implement the blinking of both LEDs available on the board. The functionality is programmed in the PS1. Both LEDs change state at the same time, switching between two colours, blue and red. The blinking period should be around 1 s.
Hardware configuration
The LEDs’ physical connections are summarised in the table below 2. Here, keep in mind that the evaluation board has the ZYNQ chip with part number xc7z007sclg400. The pin numbers are relative to that package.
| Board element | Board signal name | Chip signal name | Chip pin number | Route | Diagram signal name |
|---|---|---|---|---|---|
| LD0 | LED0_R | IO_L21P_T3_DQS_AD14P_35 | N15 | AXI GPIO | rgb_leds[0] |
| LD0 | LED0_G | IO_L16P_T2_35 | G17 | AXI GPIO | rgb_leds[1] |
| LD0 | LED0_B | IO_L22N_T3_AD7N_35 | L15 | AXI GPIO | rgb_leds[2] |
| LD1 | LED1_R | IO_L23N_T3_35 | M15 | AXI GPIO | rgb_leds[3] |
| LD1 | LED1_G | IO_L22P_T3_AD7P_35 | L14 | AXI GPIO | rgb_leds[4] |
| LD1 | LED1_B | IO_0_35 | G14 | AXI GPIO | rgb_leds[5] |
Table 1: LED’s connections.
Access to the LEDs from the PS has to be done throughout the AXI module. This is done by creating a new Block design in Vivado, populating it with a ZYNQ7 Processing System and adding an AXI GPIO module. In the AXI module configuration, select rgb leds in the Board interface for the GPIO. Doing this, the IP Configuration takes the right values for the GPIO Width. I suppose that the definition of rgb leds is done by the board description loaded when creating the Vivado project.
To generate the module, follow these steps:
- Use the Run Block Automation and RUN Connection Automation with All Automation selected.
- Validate Design.
- Regenerate Layout to organized it better.
- Generate Block Design, Use Global as Synthesis Options.
- Create HDL Wrapper. Find the block design file in the Sources tab of the Project Manager. The option is in the right-click menu.
- Check the pin assignments, voltage standards and pin direction by Open Elaborated Design.
- Generate Bitstream.
- Export Hardware from File menu. Include the bitstream. This step will create the .xsa from which a new platform will be created in Vitis.
Following this procedure, it is not necessary to load a constraints file. The board information is already available after selecting the Cora Z7-7S board when creating the project. Then, viewing the elaborated design is optional. The pin assignment is automatically done. In all pins, the I/O standard is LVCMOS33.
Software
Having the hardware configured, I can focus on what I’m interested in for this post.
First, create a new Platform from the .xsa file saved in the hardware configuration process. Then, Build the platform project.
After the platform was built, create a new Application project, which uses the platform previously created. Use an Empty Application (C) template for the main .c file. Create a main.c file and copy the code shown at the end of this post. Now let’s discuss it step-by-step.
Header
In the header, three files are included.
#include "xparameters.h"
#include "xgpio.h"
#include "xil_types.h"
xparameters.h contains information specific to the platform. It is closely linked with the hardware description designed in Vivado, so it is different for every platform we create.
xgpio.h and xil_types.h are libraries from Xilinx. The first one contains the drivers for the AXI GPIO module, while the second one defines data types used in the Xilinx framework.
The second piece of code defines the parameters LED_ID and LED_CHANNEL which later are used in the main code.
#define LED_ID XPAR_AXI_GPIO_0_DEVICE_ID
#define LED_CHANNEL 1
The value of XPAR_AXI_GPIO_0_DEVICE_ID is defined in xparameters.h. The AXI IO device can contain several channels, but I don’t know the details now. I tried with different values, but only with 1 does it work. I thought different LEDs were connected to different LED_CHANNEL, but that is not true. Both LEDs can be driven from channel 1.
The main() function is composed of three blocks.
Variable definitions
unsigned int i;
unsigned int period = 1e7;
u32 led_mask = 0b100001;
XGpio led_device;
XGpio_Config *cfg_ptr;
The variable i is used as a counter in a loop, and is used together with period to set the LED blinking period. The value 1e7 was obtained by trial and error. led_mask contains the initial value of the LEDs. The three most significant bits control LD1 and the last three bits control LD0. The value 0b100001 can be understood as bgrbgr. It means that LD1 will start with the blue on and LD0 with the red turned on. The AXI GPIO module is represented by led_device, which is a structure of type XGpio containing information such as BaseAddress and other descriptors. The set of parameters to configure the AXI GPIO are contained in cfg_ptr. The type definition of the structures XGpio and XGpio_Config is stated in the library’s header xgpio.h.
Initialization of the AXI GPIO module
cfg_ptr = XGpio_LookupConfig(LED_ID);
XGpio_CfgInitialize(&led_device, cfg_ptr, cfg_ptr->BaseAddress);
XGpio_SetDataDirection(&led_device, LED_CHANNEL, 0);
The functions XGpio_LookupConfig, XGpio_CfgInitialize and XGpio_SetDataDirection are also part of the xgpio library. The first gets the configuration information from an internal lookup table which contains parameters for each device in the system. The Initialization is done in the second function. The third one, sets the direction of the IO port, 0 means output, and 1 is input.
Main loop
i = period;
do i--;
while (i!=0);
led_mask ^= 0b101101;
XGpio_DiscreteWrite(&led_device, LED_CHANNEL, led_mask);
In the main loop, the blinking period is controlled simply by counting until the period and restarting again. When the maximum count is reached, the led_mask is changed to alternate between blue and red light. This is done using the binary operation XOR, the value 0b101101 allows to switch between both states.
0b100001 XOR 0b101101 = 0b001100
0b001100 XOR 0b101101 = 0b100001
In the last sentence write the mask value to LD0 and LD1. The function XGpio_DiscreteWrite is part of the xgpio library.
Compiling and uploading
In the Explorer tab select the project name and Build it from the hammer or from the 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.
Complete LEDs blink code
#include "xparameters.h" // Definitions from the platform.
#include "xgpio.h" // Functions to deal with GPIO.
#include "xil_types.h" // Basic types for Xilinx software IP.
#define LED_ID XPAR_AXI_GPIO_0_DEVICE_ID
#define LED_CHANNEL 1
int main() {
unsigned int i;
unsigned int period = 1e7;
// The LED mask controls both LEDs.
// The lower 3 bits sets LD0 and the 3 higher bits, LD1.
u32 led_mask = 0b100001; // 0bBGRBGR
XGpio_Config *cfg_ptr;
XGpio led_device;
// Initialize LED Device
cfg_ptr = XGpio_LookupConfig(LED_ID);
XGpio_CfgInitialize(&led_device, cfg_ptr, cfg_ptr->BaseAddress);
// Set LED direction
// Bits set to 0 are output and bits set to 1 are input.
XGpio_SetDataDirection(&led_device, LED_CHANNEL, 0);
while (1)
{
i = period;
do i--;
while (i!=0);
led_mask ^= 0b101101; // XOR
XGpio_DiscreteWrite(&led_device, LED_CHANNEL, led_mask);
}
}
Bonus
I wondered what would happen if I created the Vivado project for the chip only without selecting the Cora Board, and I tried.
- I selected the Part xc7z007sclg400-1
- Now, the AXI GPIO module doesn’t show the Board Interface tab. Only the manual GPIO configuration is present. I changed the GPIO width to 6. The component is named axi_gpio_0.
- The output port name is gpio_rtl_0.
- Open Elaborated Design and select the pins according to the table. The I/O standard is LVCMOS33. The port direction is set to INOUT and cannot be changed.
An error appeared when programming and running the device: “Error while launching program: Memory write error at 0x100000. Cannot flush CPU cache. APB AP transaction error, DAP status 0xF0000021”.
To try to solve the error, I changed the clock frequency to the same one found in the first platform, but it didn’t work out.
I close the post here now. If there is any update on the subject, I will post it in a different article.
-
The PS is the Processing System. In ZYNQ 7000S the PS is a single-core ARM Cortex-A9. ↩
-
Board signal name, Chip signal name and Chip pin number are the signal names shown in the Cora Z7 schematics. In addition, the Chip pin number and Chip signal name can be found in the package description txt file, which for the current chip is xc7z007sclg400pkg.txt. Similar information can be found in the Cora-Z7-07S-Master.xdc constraints file. ↩
Enjoy Reading This Article?
Here are some more articles you might like to read next: