Standalone Peripheral Drivers
Board Support Packages and Drivers
NOTE: we are using the Cortex-A53, but all of this content would be valid if we had selected a Cortex-R5 core for our standalone domain.
If we take a look to the Board Support Package for the psu_cortexa53_0 domain, we can see an entry with the list of available Peripheral Drivers in our ultra96v2_standalone_platform:
If we click in the entry, we will get access for each of the peripheral devices details, including:
- Associated driver
- Driver Documentation
- Importable Examples
The list of drivers is created automatically from the list of devices in our Vivado design when creating the platform, and can be edited in the Modify BSP Settings menu.
The Xilinx Parameters File
In every standalone BSP, the low level definitions for all of the supported peripherals, are contained in the xparameters.h file, including:
- Peripheral Name
- Address Range
- Interrupt lanes
Before going forward, we should take a look to the contents of the xparameters.h file in the ultra96v2_standalone_platform, e.g.:
-
Sources for the platform:
- psu_cortexa53_0
- standalone_domain
- bsp
- psu_cortexa53_0
- include
- xparameters.h
- include
- psu_cortexa53_0
- bsp
- standalone_domain
- psu_cortexa53_0
-
Exported platform:
- export
- ultra96v2_standalone_platform
- sw
- ultra96v2_standalone_platform
- standalone_domain
- bspinclude
- include
- xparameters.h
- include
- bspinclude
- standalone_domain
- ultra96v2_standalone_platform
- sw
- ultra96v2_standalone_platform
- export
Processing System GPIO Examples
NOTE: before starting, be sure that your Ultra96-V2 is connected to the host and the JTAG boot mode is set:
- Switch 1: ON
- Switch 2: ON
As a very simple but visual example, we will focus in the driver for the GPIO in the Processing System. In our standalone design for Ultra96-V2 in Vivado, we left the GPIO in the Low Power Domain of the PS marked as active.
In the Ultra96-V2, these are the PS GPIO that are connected to the board user LEDS and Pushbuttons and its associated pins:
GPIO PS | PIN |
---|---|
USER_PUSH_BUTTON | 23 |
USER_LED_O | 17 |
USER_LED_1 | 18 |
USER_LED_2 | 19 |
USER_LED_3 | 20 |
If we review the list of available Peripheral Drivers, we can identify the following entry:
- Peripheral: psu_gpio_0
- Driver: gpiops
By clicking in the Documentation entry, we will have access to the Driver API in browsable HTML format.
Now, if we want to use the examples provided by Xilinx, we need to click in Import Examples:
After we do this, a dialog with the available examples for GPIO PS will appear:
GPIO Polled Example
As a first example on how to use the GPIO PS driver, we will use the polled GPIO.
In order to do this, make the following selection to load xgpiops_polled_example:
After successfully importation, we will see that the following items have been created associated to ultra96v2_standalone_platform:
- Application: xgpiops_polled_example_1, the actual application
- System: system_bsp_example_1, the system containing the application
In addition, the file containing the source code, xgpiops_polled_example.c has been automatically opened in Vitis.
The file contains comments about every software library and function it is being employed. One of the most important parts is the include section, when we should highlight the following entries:
- xparameters.h: where the application takes the actual peripheral definitions from.
- xgpiops.h: where the API for the Processing System GPIO is accessed.
- xplatform_info.h: where the information about our actual architecture is defined, e.g. MPSoC vs Versal vs Zynq-7000
Then, the application performs this test:
- Configure a LED as output and makes it blink 16 times
- Configure a Push Button as input and print the value in the serial port.
Before building, we must modify the code in the xgpiops_polled_example.c file to adapt to our setup.
First, as we are using a Cortex-A53 core, we must slow down the blinking frequency for the led to have a little more time for us when executing the example. In order to do this, in the line 98 we can change from:
#define LED_DELAY 10000000
To:
#define LED_DELAY 50000000
Because the example was targeted to the ZCU102 board when used on a MPSoC architecture, we need to change the pin numbers in the xgpiops_polled_example.c code to match those in the Ultra96-V2. In order to do this, in the line 190, we need to change from the ZCU102 values:
case XPLAT_ZYNQ_ULTRA_MP:
Input_Pin = 22;
Output_Pin = 23;
break;
to the Ultra96-V2 ones, e.g.:
case XPLAT_ZYNQ_ULTRA_MP:
Input_Pin = 23;
Output_Pin = 17;
break;
Once done, we save the modifications by clicking in the Save button of Vitis or using the Ctrl+S keyboard shortcut.
Now, we can use the Assistant view to build the application in Debug mode:
After the build has been successful, we open the Debug Configurations from the Assistant:
In the Debug Configurations, perform the following actions:
- Double click in the Single Application Debug entry in the left panel to create a new configuration.
- Change the Name field to Debugger_xgpiops_polled_example_1 to identify our new configuration.
- In the Project field, click Browse and select xgpiops_polled_example_1 from the available projects list.
Once done, apply the changes by clicking on the Apply button.
Before proceeding, check the following tabs in your configuration:
-
Aplication:
- Debug/xgpiops_polled_example_1.elf will be loaded to psu_cortexa53_0 CPU.
- Stop at 'Main' option is checked.
-
Target Setup:
- Use FSBL flow for initialization option is checked.
Once done, power up the Ultra96-V2 and launch the debug configuration by clicking in Debug.
By doing this, you'll be switched to the Debug perspective and the program execution will be stopped at the Main method.
Make sure that you have the Vitis Serial Terminal program (or an external alternative) connected to /dev/ttyUSB1.
Now, resume the execution by clicking on Resume or by using the F8 keyboard shortcut:
If everything is OK, we will see one of the user LEDs in the Ultra96-V2 blinking (placed in the PCB border just between the USB sockets).
In addition, in the serial terminal, we will see the following messages:
Note that the user push button in the Ultra96-V2 is active low. If we relaunch the Debug process and press the push button when the LED is blinking, the GPIO input will be 0x0 (the user push button is the one placed close to the microSD socket).
Once we have finished with the debug session, we can disconnect the debugger:
GPIO Interrupt Example
As a more advanced example on how to use the GPIO PS driver, we will use the interrupt driven GPIO.
In order to do this, make the following selection to load xgpiops_intr_example:
After successfully importation, we will see that the following items have been created associated to ultra96v2_standalone_platform:
- Application: xgpiops_intr_example_1, the actual application
- System: system_bsp_example_2, the system containing the application
In addition, the file containing the source code, xgpiops_intr_example.c has been automatically opened in Vitis.
The file contains comments about every software library and function it is being employed. One of the most important parts is the include section, when we should highlight the following entries:
- xparameters.h: where the application takes the actual peripheral definitions from.
- xgpiops.h: where the API for the Processing System GPIO is accessed.
- xscugic.h: the generic interrupt controller driver component.
- xil_exception.h: contains exception related driver functions or macros to access the device.
- xplatform_info.h: where the information about our actual architecture is defined, e.g. MPSoC vs Versal vs Zynq-7000
Then, the application performs this test:
- Configure a LED as output and turn it off.
- Configure a Push Button as input.
- Configure the interrupt controller.
- Enable falling edge interrupts in the Push Button.
- Set an interrupt handling routine that turns on the LED and exit the program.
Before building, we must modify the code in the xgpiops_intr_example.c file to adapt to our Ultra96-V2 board.
Because the example was targeted to the ZCU102 board when used on a MPSoC architecture, we need to change the pin numbers in the xgpiops_intr_example.c code to match those in the Ultra96-V2. In order to do this, in the line 194, we need to change from the ZCU102 values:
case XPLAT_ZYNQ_ULTRA_MP:
Input_Pin = 22;
Output_Pin = 23;
break;
to the Ultra96-V2 ones, e.g.:
case XPLAT_ZYNQ_ULTRA_MP:
Input_Pin = 23;
Output_Pin = 17;
break;
Now, if we take a look to the XGpioPS_SetIntrType() documentation, we have that:
void XGpioPs_SetIntrType (
XGpioPs *InstancePtr,
u8 Bank,
u32 IntrType,
u32 IntrPolarity,
u32 IntrOnAny)
And the input arguments mean:
- InstancePtr: is a pointer to an XGpioPs instance.
- Bank: is the bank number of the GPIO to operate on. Valid values are 0 to XGPIOPS_MAX_BANKS - 1.
- IntrType: is the 32 bit mask of the interrupt type. 0 means Level Sensitive and 1 means Edge Sensitive.
- IntrPolarity: is the 32 bit mask of the interrupt polarity. 0 means Active Low or Falling Edge and 1 means Active High or Rising Edge.
- IntrOnAny: is the 32 bit mask of the interrupt trigger for edge triggered interrupts. 0 means trigger on single edge using the configured interrupt polarity and 1 means trigger on both edges.
In the line 348, we have the interrupt type configuration:
/* Enable falling edge interrupts for all the pins in bank 0. */
XGpioPs_SetIntrType(Gpio, GPIO_BANK, 0x00, 0xFFFFFFFF, 0x00);
So this is actually enabling Level Sensitive and Active High. As the push-button in the Ultra96-V2 is Active Low when pressed, this configuration would trigger the interrupt as soon as the program is executed.
In order to properly configure Falling Edge interrupts, we need to modify the code to:
/* Enable falling edge interrupts for all the pins in bank 0. */
XGpioPs_SetIntrType(Gpio, GPIO_BANK, 0xFFFFFFFF, 0x0, 0x0);
Finally, in the interrupt handling routine, the program checks the value of the input and exits the program if the input pin value is not 0. As we are enabling falling edge interrupts, the input pin value will always be 0 when the interrupt is attended. In order to fix this, in line 275 we must modify the if condition and fix the value for the output LED, so we change from:
if (DataRead != 0) {
XGpioPs_SetDirectionPin(Gpio, Output_Pin, 1);
XGpioPs_SetOutputEnablePin(Gpio, Output_Pin, 1);
XGpioPs_WritePin(Gpio, Output_Pin, DataRead);
AllButtonsPressed = TRUE;
}
to:
if (DataRead == 0) {
XGpioPs_SetDirectionPin(Gpio, Output_Pin, 1);
XGpioPs_SetOutputEnablePin(Gpio, Output_Pin, 1);
XGpioPs_WritePin(Gpio, Output_Pin, 1);
AllButtonsPressed = TRUE;
}
Once done, we save the modifications by clicking in the Save button of Vitis or using the Ctrl+S keyboard shortcut.
Now, we can use the Assistant view to build the application in Debug mode:
After the build has been successful, we open the Debug Configurations from the Assistant:
In the Debug Configurations, perform the following actions:
- Double click in the Single Application Debug entry in the left panel to create a new configuration.
- Change the Name field to Debugger_xgpiops_intr_example_1 to identify our new configuration.
- In the Project field, click Browse and select xgpiops_intr_example_1 from the available projects list.
Once done, apply the changes by clicking on the Apply button.
Before proceeding, check the following tabs in your configuration:
-
Aplication:
- Debug/xgpiops_intr_example_1.elf will be loaded to psu_cortexa53_0 CPU.
- Stop at 'Main' option is checked.
-
Target Setup:
- Use FSBL flow for initialization option is checked.
Once done, power up the Ultra96-V2 and launch the debug configuration by clicking in Debug.
By doing this, you'll be switched to the Debug perspective and the program execution will be stopped at the Main method.
Make sure that you have the Vitis Serial Terminal program (or an external alternative) connected to /dev/ttyUSB1.
Now, resume the execution by clicking on Resume or by using the F8 keyboard shortcut:
Just after we resume the execution, we will see that the user LED is cleared and, in the serial terminal, we will get the following message:
Then, as soon as we push the button user button, the user LED is turned on again and, in the serial terminal, we will be informed that the test was successful:
Once we have finished with the debug session, we can disconnect the debugger: