GPIO and Petalinux - Part 3 (Go, UIO, Go!)
This is part 3 of the GPIO and Petalinux series of tutorials, aiming at hobbyists and/or professionals, working with Embedded Linux.?
In Part 1 I've started with the basics of linux Kernel and Petalinux, overviewed the various options to read and write to/from GPIO, then headed towards UIO method.
In part 2 I've designed an example design with the good old Zed board. I've explained about the device tree and added support for UIO driver. The article was finished just before creating the Petalinux image,
Now in part 3, let's wrap it up, sending the following command:
petalinux-build
and after few minutes we'll receive the following message in our console:
which means all files are now located at 2 folders:
Both folder are identical and it's up to the user to decide from where to package the image files.
Packaging the image file
Using the following commands, we'll package the files needed in order to create the image.ub, boot.bin and our rootfs files (where all our filesystem is located at):
cd /fpga/projects/linux_images/UIO_wIRQ_PL/UIO_wIRQ/images/linux
petalinux-package --boot --fsbl zynq_fsbl.elf --u-boot --fpga system.bit --force
One can also create these files manually, but it is much easier do it through Petalinux commands (even a must, as there are many posts at Xilinx forums that undergoing this way manually not always works as expected).
The petalinux-package command simply takes the relevant files, all marked with double dash, in order to create the following files:
BOOT.bin - this is a compiled u-boot OS in binary format (both the first stage and second stage boot loader, as well as the bitstream, among other components).
image.ub - The Petalinux image which consists of kernel image, device tree blob and minimal rootfs.
rootfs.tar.gz - this is the most basic component of Linux. It contains all the applications (e.g., our main application, which runs at startup), devices, configurations, etc.
UG1157 has a lot of interesting info about petalinux-package and I recommend to go over it.
Important to note that the above command is used for booting from an SD card. In the case of using a flash to boot from, we'll need to alter this command and add also the kernel (this cab be dealt in a later tutorial).
Anyway, we've now reached the finish line and ready to place those 3 files in our SD card.
well, as always, not so fast...
copying Petalinux files into the SD card
We'll first need to partition the SD card with 2 (or more) partitions. I've explained it thoroughly in my Hackster project page, where I wrote about a method for creating a dynamic-static IP (worth reading...). So, I'll not go over it again here.
Once the files are located at the correct folders, we can power on the board and Linux should power up.
Checking UIO
Once we're in, the next step is to validate we have the UIO driver installed, looking at the "/dev" folder which contains the installed Drivers:
We can also see it under /sys/class/uio, where other interesting stuff is hidden:
The 'maps' folder include all address mapping info regarding the component we used. We can go over the various parameters and see some familiar addresses, size, etc (from part 2 my tutorial).
All this clearly shows us the UIO was indeed installed correctly.
Checking UIO is functioning correctly
This is the most interesting part, so I hope you all wide awake. If not, by all means, get yourself a strong cup of coffee, cause the fun begins and it is a bit complicated.
For testing the UIO, I will generate an interrupt in the Zed board by hitting the push button and I expect to see the interrupt counter increased at CPU side. In Linux it is located at /Proc/Interrupts.
/Proc/Interrupts file
Interrupts to CPU allows devices like keyboards, mouse, pushbutton (in our case) to signal the CPU it wishes to talk with it.
On Linux we have a file which holds that info, including number of times the CPU was informed, the type of interrupt raised and more. This file is located at /Proc/Interrupts
I will use the AXI GPIO IP block for that matter (the projects are in my Github page) and as a reference this link helped me a lot so I recommend go over it also.
For reaching the various AXI GPIO registers I'll use the devmem2 application . It is a small application very useful for reading and writing to almost any available address . This is a very handy tool for developers before you have a fancy GUI for that matter, or other form of user interface.
From this table taken from the AXI GPIO block manual (PG144):
Setting these registers turn on the corresponding LEDs connected to GPIO2 in my design:
Now, we’ll try to see the interrupt is incremented:
According to the below picture, we’ll write to the correct registers:
devmem 0x41200128 w 0x1 #for GPIO channel 1
devmem 0x41200128 w 0x2 #for GPIO channel 2
devmem 0x41200128 w 0x3 #for GPIO both channels
2. Enable global interrupt:
devmem 0x4120011c w 0x80000000 #for both GPIO channels
Now, before we hit the pushbutton to set the interrupt, let’s look at /proc/interrupts:
We can see here the 2 CPU's (Dual-core ARM Cortex-A9 in the case of the Zedboard), the trigger type ('level triggered' or 'edge triggered'), the functionality and the interrupt number.
Pay attention that at the 'gpio' interrupt value the interrupt number is 61:
46:? ? ? ? ? 1? ? ? ? ? 0 ? ? GIC-0? 61 Level ? ? gpio
Why is it 61? back in part 2, where I've explained about the device tree, the number was 29! That is because 29 + 32 = 61 (as explained, the kernel device tree parser adds 32 to the IRQ id).
Now, we’ll click a pushbutton (any button) and the interrupt will fire. We can see that in the interrupt file. The counter has increased by 1:
Reading this address shows the Interrupt status register in the AXI GPIO:
领英推荐
devmem 0x41200120
> 0x00000001
Which means an interrupt has occurred. Writing 0x1 to the same register toggles the status of the bit:
devmem 0x41200120 w 0x1
> 0x00000000
The above scenario is based on the AXI GPIO manual:
Clearing the UIO interrupt
An interesting issue is related to clearing GPIO line at /cat/interrupts. After the interrupt has fired, it is ‘stuck’ at ‘1’.
How do we clear it?
UIO-How-to explains:
So, we need to clear this bit, via source code, or any other method.
From the kernel UIO driver website:
So, I'll echo the UIO with '1' to clear the interrupt:
echo 0x1>/dev/uio0
But strangely, the interrupt counter at /proc/interrupts increments all the time. Why is that?
well, this took me a while to find out, and the reason is AXI GPIO supports only level triggered interrupts. The Disable bit was cleared indeed, but the interrupt level is still high (since it is defined as ‘Level'), and the interrupt counter keeps incrementing.
Going back to Vivado
Figuring out my design is wrong, since the GPIO AXI is level triggered and I cannot clear the UIO interrupt, I went back to the PL and decided to design a new project, where I have an additional interrupt source, and this time, I will design it as 'Edge triggered':
I’ve deleted the push_buttons port and defined my own ports, per the locations stated in the Zedboard manual (button_center, button_left, etc).
Tip #1
The custom_io block is a modified version of the “Tools → create and Package New IP…”. A nice trick to avoid the cumbersome methodology of using the packaged IP in a new project (using right click → “edit in IP packager”) is to copy the code inside the created IP into a new RTL module (save it with a new name), then add it to the BD using Add Module (right click in BD). This is actually what I did, thus the RTL letters are shown inside the block.
In my vhd code I created a simple pulse (i.e., interrupt) whenever the user clicks one of the buttons.
Tip#2
?to define the interrupt as a ‘rising_edge’ type, I’ve added these lines to my vhd code:
attribute X_INTERFACE_INFO : string;
attribute X_INTERFACE_INFO of interrupt_out : signal is "xilinx.com:signal:interrupt:1.0 interrupt_out INTERRUPT";
attribute X_INTERFACE_PARAMETER : string;
attribute X_INTERFACE_PARAMETER of interrupt_out : signal is "SENSITIVITY EDGE_RISING";
This makes the pin ‘interrupt_out’ an interrupt type signal of rising_edge:
So, the design now includes 2 sub-IP’s; my custom IP, which has an 'interrupt out' signal of rising_edge type, and the AXI GPIO original block which has an interrupt of type ‘level’.
Since I've 'concat'-ed both interrupts this is clearly shown in the BD:
Last, but not least, is to change the device tree, and add to system-user.dtsi the new created custom IP:
/include/ "system-conf.dtsi"
/ {
????gpio@41200000{
????????compatible = "axi_gpio_0, generic-uio, ui_pdrv";?
????????status = "okay";
????????};
chosen {??????
bootargs = "console=ttyPS0,115200 earlyprintk uio_pdrv_genirq.of_id=generic-uio";
};?
??
};
&axi_gpio_0
????????{
????????????compatible = "generic-uio";
????????};
/* adding my new custom rising edge triggered IP*/
?&custom_IO_v1_0_S00_A_0
????????{
????????????compatible = "generic-uio";
????????};
Now, next steps are similar (but not exact) to what I did before:?
Testing the UIO after rising edge trigger
In the first push button, examined the /proc/interrupts, to test the new interrupt type. We can see we have 2 IP's, our original AXI GPIO ('Level') and our new custom trigger source ('Edge'):
Then clearing the interrupt using:
echo 0x1>/dev/uio1 # --> since uio1 is my custom IP, while uio0 is the AXI GPIO.
Question arises, how do we know to which 'uio' should we write? we now have 2 UIO's!
TIP#3
Go to /sys/class/uio.
For example, see here from a different project we have various components, all UIO drivers based. Each one has its own UIO index. The key is to compare it with the system-conf-dtsi file:
we'll compare it with system-conf.dtsi file:
We'll follow the addresses on the dtsi file and compare the index of the UIO.
Back to our project, Clicking the button again:
Then clearing the interrupt using:
echo 0x1>/dev/uio1 # --> since uio1 is my custom IP, while uio0 is the AXI GPIO.
Clicking the button again:
Vuala! Exactly what I wished for!
To sum it all up:
I've started with a design based on GPIO AXI for triggering an interrupt. This indeed worked well, but I could not disable the interrupt and the result was the interrupt counter at the /proc/interrupt kept increasing since the AXI GPIO supports only level triggered interrupts. Next, I've added my own interrupt trigger and made it rising edge one. It did worked flawlessly. This should remind us that we better use rising edge trigger type rather than Level.
And lastly, as a work of caution:
One may have other solution (maybe even simpler one) for this task, but from my point of view, this indeed did the trick...
Now I can say: Q.E.D!
Saint Petersburg National Research University of Information Technologies, Mechanics and Optics
5 个月Thank you for the tutorials. They helped me a lot to understand uio.
Principal Design Engineer , New Space Systems
2 年Awesome series Thanks
Computer Achitecture Performace modeling expert
2 年Please please write tutorial on how to send signal from uio driver ......
Embedded Software Engineer at Space Ground System Solutions
3 年Great tutorials! They helped me out a lot. I found that I could use the AXI GPIO level interrupts as is; no edge needed. All I needed to do was read the AXI GPIO interrupt status register and write that value back to the interrupt status register to clear the current interrupt. Then a proceeding interrupt would be acknowledge (counted once). Each time a GPIO interrupt event occurred I would: read/use the GPIO value, read the interrupt status register, write the interrupt status register, wait for the next interrupt event, repeat the process. Hope that helps someone. Thanks again.
Embedded Systems Specialist at Avnet
3 年Great tutorials for demystifying hardware using UIO very useful in early stages of FPGA IP development when driver is not developed.