Over the last few weeks, we have been looking at how we can effectively work with PetaLinux using the ZUBoard as the reference platform.
Last week, we examined the Userspace IO and how it would enable us to work with custom IP and interrupts. We used the AXI GPIO as an example and demonstrated the interrupt functionality using command line interaction.
In this blog, we are going to continue that development by writing a software application which uses UIO to access the AXI GPIO memory space to set the LED colors and configure and support the interrupt.
We will follow a process similar to how we previously worked with the spidev and I2Cdev. This time, however, we will need to pull out information from the files associated with the UIO definition.
To get started with our application, we are going to include the libraries necessary to work with the UIO:
#include <fcntl.h>
#include <sys/mman.h>
We are also going to define two constants which point to the map0 elements under sys/class/uio:
#define UIO_SIZE "/sys/class/uio/uio4/maps/map0/size"
#define UIO_ADDR "/sys/class/uio/uio4/maps/map0/addr"
We are able to access the UIO size by using these. This is important for configuring the memory map access correctly. The address variable I read to provide verification against our hardware design within Vivado.
We can use the open function to open the UIO device and trap it should an error occur
GPIO_UIO = open("/dev/uio4", O_RDWR);
if (GPIO_UIO < 0) {
perror("UIO Open Error:");
}
Once the UIO device has been opened, I then read the files containing the address and size. To do this, we use fopen and open the files as read only. This traps for any errors which might occur.
size_gpio = fopen(UIO_SIZE, "r");
if (size_gpio == NULL){
perror("UIO Size Open Error:");
}
fscanf(size_gpio,"0x%16X",&uio_size);
printf("size of UIO Memory: 0x%x\n",uio_size);
size_addr = fopen(UIO_ADDR, "r");
if (size_addr == NULL){
perror("UIO Addr Open Error:");
}
fscanf(size_addr,"0x%16X",&uio_addr);
printf("size of UIO Memory: 0x%x\n",uio_addr);
To extract the address and size information from the file, we use fscanf to read up to the first 16 characters and store it within the variable passed to it.
With the UIO device opened and the size information read from the UIO file, we can run the memory map function.
ptr = mmap(NULL, uio_size, PROT_READ|PROT_WRITE, MAP_SHARED, GPIO_UIO, 0);
if (ptr == MAP_FAILED) {
perror("mmap error:");
}
This will return a pointer which gives us the ability to access memory within the allocated memory space.
We can then use this code to toggle the LEDs on the ZUBoard.
for(int i = 0; i<10; i++){
*(volatile unsigned long *) (ptr + 0x08) = 0x04;
usleep(500000);
*(volatile unsigned long *) (ptr + 0x08) = 0x00;
usleep(500000);
}
This will toggle the LED red 10 times. Once this sequence has been completed, we are ready to test the interrupt handling.
The first thing we need to do is use the mmap pointer to access the AXI GPIO interrupt registers for the AXI GPIO.
*(volatile unsigned long *) (ptr + 0x11C) = 0x01;
*(volatile unsigned long *) (ptr + 0x128) = 0x03;
UIO interrupts work by performing a read to the UIO file. This read will be blocking until the interrupt occurs, at which point the generic interrupt handler will disable the interrupt in the kernel. We are then able to run the ISR for that interrupt occurring.
en = read(GPIO_UIO, &enable, sizeof(enable));
if (en == (ssize_t)sizeof(enable)) {
*(volatile unsigned long *) (ptr + 0x08) = 0x04;
printf("GPIO Button Pushed #%u!\n", enable);
}
To reenable theinterrupts we then perform a write to the UIO file with a value of 0x1 which will ensure the interrupt will again be captured the next time we call the read function.
en = write(GPIO_UIO, &enable, sizeof(enable));
if (en != (ssize_t)sizeof(enable)) {
perror("write");
}
In the case of the AXI GPIO, the interrupt is being raised when its inputs change. Pressing the PL button will generate this interrupt.
When we receive the interrupt, we must be careful to ensure we not only reset the interrupt handler on the UIO device but also clear any interrupts in the peripheral that we are working with.
We can do this for the AXI GPIO by writing to the interrupt status register which clears the interrupt bit when written.
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#define UIO_SIZE "/sys/class/uio/uio4/maps/map0/size"
#define UIO_ADDR "/sys/class/uio/uio4/maps/map0/addr"
static int GPIO_UIO;
int main()
{
unsigned int uio_addr;
unsigned int uio_size;
FILE * size_gpio;
FILE * size_addr;
void *ptr;
uint32_t enable = 1;
ssize_t en;
unsigned int led_value;
GPIO_UIO = open("/dev/uio4", O_RDWR);
if (GPIO_UIO < 0) {
perror("UIO Open Error:");
}
size_gpio = fopen(UIO_SIZE, "r");
if (size_gpio == NULL){
perror("UIO Size Open Error:");
}
fscanf(size_gpio,"0x%16X",&uio_size);
printf("size of UIO Memory: 0x%x\n",uio_size);
size_addr = fopen(UIO_ADDR, "r");
if (size_addr == NULL){
perror("UIO Addr Open Error:");
}
fscanf(size_addr,"0x%16X",&uio_addr);
printf("size of UIO Memory: 0x%x\n",uio_addr);
ptr = mmap(NULL, uio_size, PROT_READ|PROT_WRITE, MAP_SHARED, GPIO_UIO, 0);
if (ptr == MAP_FAILED) {
perror("mmap error:");
}
for(int i = 0; i<10; i++){
*(volatile unsigned int *) (ptr + 0x08) = 0x04;
usleep(500000);
*(volatile unsigned int *) (ptr + 0x08) = 0x00;
usleep(500000);
}
*(volatile unsigned int *) (ptr + 0x11C) = 0x80000000;
*(volatile unsigned int *) (ptr + 0x128) = 0x03;
printf("Please press the interrupt\n");
while(1){
*(volatile unsigned int *) (ptr + 0x120) = 0x01;
en = write(GPIO_UIO, &enable, sizeof(enable));
if (en != (ssize_t)sizeof(enable)) {
perror("write");
}
en = read(GPIO_UIO, &enable, sizeof(enable));
if (en == (ssize_t)sizeof(enable)) {
led_value = ~led_value;
*(volatile unsigned int *) (ptr + 0x08) = led_value;
printf("GPIO Button Pushed #%u!\n", enable);
//usleep(5000);
}
usleep(500000);
}
return 0;
}
If we look at the interrupts for the number received again, we will see that the GPIO interrupts have been incrementing.
I have uploaded all of the necessary files to the GitHub.
This brings our work with PetaLinux to an end for now, but I think it has been very useful and I will be doing more PetaLinux-based projects in Hackster coming soon!
Workshops and Webinars
If you enjoyed the blog why not take a look at the free webinars, workshops and training courses we have created over the years. Highlights include
Introduction to Vivado learn how to use AMD Vivado
Ultra96, MiniZed & ZU1 three day course looking at HW, SW and Petalinux
Arty Z7-20 Class looking at HW, SW and Petalinux
Mastering MicroBlaze learn how to create MicroBlaze solutions
HLS Hero Workshop learn how to create High Level Synthesis based solutions
Perfecting Petalinux learn how to create and work with petalinux OS
Embedded System Book Do you want to know more about designing embedded systems from scratch? Check out our book on creating embedded systems. This book will walk you through all the stages of requirements, architecture, component selection, schematics, layout, and FPGA / software design. We designed and manufactured the board at the heart of the book! The schematics and layout are available in Altium here Learn more about the board (see previous blogs on Bring up, DDR validation, USB, Sensors) and view the schematics here.
Sponsored by AMD
Comments