ZYNQ AXI-GPIO Linux驱动实验
ZYNQ AXI-GPIO Linux驱动实验
简介
在Linux中访问PL中自定义设备,主要分为三步实现。首先需要在Vivado中创建工程生成PL部分的bin文件,在Linux中通过FPGA_MANAGER接口将bin文件烧写到PL中;然后在PS的Linux中编写自定义设备的驱动,将自定义设备的寄存器操作封装为标准驱动接口函数提供给Linux系统;最后再编写Linux应用程序,在应用程序中通过上一步驱动程序提供的函数接口操作PL中的设备,实现在Linux中读写PL内部的寄存器等功能。
搭建Vivado工程
-
在Vivado中使用Block Design的方式例化ZYNQ PS IP和AXI-GPIO IP,将AXI-GPIO IP的AXI总线和PS的GP0总线连接,GPIO的复位使用PS输出的FCLK_RESET0控制,GPIO的中断输出接到PS的共享中断IRQ_F2P上。
AXI-GPIO IP内部可配置通道数量、是否使能中断等参数,这里为了简单起见设置GPIO通道为1,使能中断。

- 搭建完Block Design后,在Address Editor窗口可以设置AXI-GPIO模块的基地址,这里使用默认地址0x41200000。

- 添加XDC文件,约束GPIO管脚。
set_property IOSTANDARD LVCMOS33 [get_ports {GPIO_0_tri_io[0]}]
set_property PACKAGE_PIN L16 [get_ports {GPIO_0_tri_io[0]}]
set_property OFFCHIP_TERM NONE [get_ports GPIO_0_tri_io[0]]
//fclk和axi_resetn为调试用,可不引出
set_property IOSTANDARD LVCMOS33 [get_ports fclk]
set_property PACKAGE_PIN L17 [get_ports fclk]
set_property IOSTANDARD LVCMOS33 [get_ports axi_resetn]
set_property PACKAGE_PIN K17 [get_ports axi_resetn]
-
直接编译综合,生成比特流文件ZYNQ_AXI_TEST_wrapper.bit
-
把bit文件转换为bit.bin文件
-
首先编写一个bif后缀文件,命名为bit2bin.bif,放在bit文件所在的文件夹中,内容如下:
all: { ZYNQ_AXI_TEST_wrapper.bit } -
在Vivado TCL命令串口输入以下命令转换得到bit.bin文件ZYNQ_AXI_TEST_wrapper.bit.bin
cd {bit2bin.bif的文件路径} bootgen -image bit2bin.bif -arch zynq -process_bitstream bin
-
-
将生成的bin文件拷贝到PS的Linux中并加载
-
新建文件夹/lib/firmware
sudo mkdir /lib/firmware -
复制bin文件到/lib/firmware文件夹
cp ZYNQ_AXI_TEST_wrapper.bit.bin /lib/firmware -
加载bin文件到PL
echo 0 > /sys/class/fpga_manager/fpga0/flags cd /lib/firmware echo ZYNQ_AXI_TEST_wrapper.bit.bin > /sys/class/fpga_manager/fpga0/firmware -
如果加载成功,输入dmesg命令查看系统日志,应该会出现以下内容
fpga_manager fpga0: writing ZYNQ_AXI_TEST_wrapper.bit.bin to Xilinx Zynq FPGA Manager
-
编写Linux驱动
AXI-GPIO寄存器定义

在Vivado中我们只配置使用了1个通道,因此控制GPIO方向和控制输入输出只需要操作FPIO_DATA和GPIO_TRI寄存器就行了,其中GPIO_DATA读为输入电平,写为控制输出电平,GPIO_TRI写0为输出,写1为输入。对于GPIO中断使能、查询和清除,需要操作GIER、IP IER、IP ISR三个寄存器,其中GOER为全局中断使能寄存器,写0x80000000使能,写0不使能;IP IER为通道中断使能寄存器,写0x1使能通道1中断,写0x0关闭中断;IP ISR为中断状态寄存器,有中断时读为1,无中断时读为0,向该寄存器写1可以翻转中断信号电平,用于软件触发中断。
编写platform驱动
platform驱动需要自己实现两部分内容,分别是驱动文件xxx.c和设备树文件zynq-zc702.dtb,其中c文件实现设备的注册、基本驱动函数等功能,设备树文件提供GPIO IP的一些硬件信息,如基地址、中断号等。c文件通过编译器和linux源代码一起编译成一个xxx.ko文件,在linux中执行insmod xxx.ko命令插入模块时,c文件中实现的probe函数会被执行,在probe函数中会解析设备树文件,获取硬件信息,如基地址、中断号,再通过ioremap函数将IP物理地址映射到虚拟地址并保存到某个地址上。c文件中的其他函数如write、read、unlock_ioctl等函数就可以操作虚拟地址实现寄存器的读写。设备树文件zynq-zc702.dtb由设备树源文件zynq-zc702.dts和其包含的其他源文件xxx.dtsi和xxx.h通过设备树编译器dtc转换得到。
设备树文件
- 在设备树文件中增加以下内容:
my_gpio {
status = "okay";
// compatible 属性代表该设备的名字,需要和驱动c文件中的匹配列表一致
compatible = "my-axi-gpio";
// reg 属性代表了该设备的物理基地址和范围,范围为0x41200000~0x41200100,提供给驱动c文件映射到对应的虚拟地址再操作
reg = <0x41200000 0x100>;
// 下面内容代表该设备有一个中断控制器,中断号为29(实际为61,对应Vivado中连接到PS的中断号61-32)
interrupt-controller ;
interrupt-names = "ip2intc_irpt";
interrupt-parent = <&intc>;
interrupts = <0 29 4>;
};
- 修改完设备树后在Linux源码根目录下使用make dtbs命令重新编译设备树文件,生成新的zynq-zc702.dtb文件,复制到ZYNQ SD卡的BOOT分区,替换原来的dtb文件。
驱动源文件
-
在linux源代码工程中linux/drivers/gpio增加一个源文件gpio-myaxigpio.c
/* led_mod.c */ #include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/cdev.h> #include <linux/ioctl.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/ioport.h> #include <linux/of_address.h> #define DEV_NUM 1 #define DEV_NAME "my-axi-gpio" /* 设备名字 */ // ioctl函数支持的命令 #define GPIO_SET_DIRECTION _IOW('G', 1, int) #define GPIO_SET_VALUE _IOW('G', 2, int) #define GPIO_GET_VALUE _IOR('G', 3, int) #define GPIO_GLB_ENABLE_IRQ _IOW('G', 4, int) #define GPIO_ENABLE_IRQ _IOW('G', 5, int) #define GPIO_GET_IRQS _IOR('G', 6, int) #define GPIO_CLR_IRQS _IOW('G', 7, int) // ip 基地址和寄存器偏移 #define ZYNQ_AXI_GPIO_0_BASE 0x41200000 #define ZYNQ_FCLK_RESET_BASE 0XF8000240 #define GPIO_DATA 0x0000 #define GPIO_TRI 0X0004 #define GPIO2_DATA 0X0008 #define GPIO2_TRI 0X000C #define GPIO_GIER 0x011c #define GPIO_IP_IER 0X0128 #define FPIO_IP_ISR 0x0120 struct myaxigpio_reg { void __iomem *gpio_data; void __iomem *gpio_tri; void __iomem *gpio2_data; void __iomem *gpio2_tri; void __iomem *gpio_gier; void __iomem *gpio_ip_ier; void __iomem *gpio_ip_isr; void __iomem *fclk_reset; }; // 自定义的设备信息结构体 struct myaxigpio_dev { dev_t devid; struct cdev cdev; struct class *class; struct device *device; int major; int minor; int irq; struct myaxigpio_reg reg; }; static void __iomem *data_addr; /* 映射后的寄存器虚拟地址指针 */ // 设备 static struct myaxigpio_dev myaxigpio; // 映射物理地址到虚拟地址 static void gpio_remap(struct myaxigpio_dev *gpio_dev) { gpio_dev->reg.fclk_reset = ioremap(ZYNQ_FCLK_RESET_BASE, 4); if (!gpio_dev->reg.fclk_reset) { printk(KERN_ERR "Failed to remap fclk_reset\n"); // Handle error if needed } gpio_dev->reg.gpio_data = ioremap(ZYNQ_AXI_GPIO_0_BASE + GPIO_DATA, 4); if (!gpio_dev->reg.gpio_data) { printk(KERN_ERR "Failed to remap GPIO data\n"); // Handle error if needed } gpio_dev->reg.gpio_tri = ioremap(ZYNQ_AXI_GPIO_0_BASE + GPIO_TRI, 4); if (!gpio_dev->reg.gpio_tri) { printk(KERN_ERR "Failed to remap GPIO TRI\n"); // Handle error if needed } gpio_dev->reg.gpio2_data = ioremap(ZYNQ_AXI_GPIO_0_BASE + GPIO2_DATA, 4); if (!gpio_dev->reg.gpio2_data) { printk(KERN_ERR "Failed to remap GPIO2 data\n"); // Handle error if needed } gpio_dev->reg.gpio2_tri = ioremap(ZYNQ_AXI_GPIO_0_BASE + GPIO2_TRI, 4); if (!gpio_dev->reg.gpio2_tri) { printk(KERN_ERR "Failed to remap GPIO2 TRI\n"); // Handle error if needed } gpio_dev->reg.gpio_gier = ioremap(ZYNQ_AXI_GPIO_0_BASE + GPIO_GIER, 4); if (!gpio_dev->reg.gpio_gier) { printk(KERN_ERR "Failed to remap GPIO GIER\n"); // Handle error if needed } gpio_dev->reg.gpio_ip_ier = ioremap(ZYNQ_AXI_GPIO_0_BASE + GPIO_IP_IER, 4); if (!gpio_dev->reg.gpio_ip_ier) { printk(KERN_ERR "Failed to remap GPIO IP IER\n"); // Handle error if needed } gpio_dev->reg.gpio_ip_isr = ioremap(ZYNQ_AXI_GPIO_0_BASE + FPIO_IP_ISR, 4); if (!gpio_dev->reg.gpio_ip_isr) { printk(KERN_ERR "Failed to remap GPIO IP ISR\n"); // Handle error if needed } printk("ioremap fclk_reset = 0x%x\n", gpio_dev->reg.fclk_reset); } // 取消映射 static void gpio_unmap(struct myaxigpio_dev *gpio_dev) { if (gpio_dev->reg.fclk_reset) iounmap(gpio_dev->reg.fclk_reset); if (gpio_dev->reg.gpio_data) iounmap(gpio_dev->reg.gpio_data); if (gpio_dev->reg.gpio_tri) iounmap(gpio_dev->reg.gpio_tri); if (gpio_dev->reg.gpio2_data) iounmap(gpio_dev->reg.gpio2_data); if (gpio_dev->reg.gpio2_tri) iounmap(gpio_dev->reg.gpio2_tri); if (gpio_dev->reg.gpio_gier) iounmap(gpio_dev->reg.gpio_gier); if (gpio_dev->reg.gpio_ip_ier) iounmap(gpio_dev->reg.gpio_ip_ier); if (gpio_dev->reg.gpio_ip_isr) iounmap(gpio_dev->reg.gpio_ip_isr); } static int led_open(struct inode *inode, struct file *filp) { struct myaxigpio_dev *gpio_dev; // 从open的设备中获取到对应的设备信息结构体myaxigpio_dev,保存到filp结构体中, // 调用open函数后file也会传到write、read、ioctl等函数中,用于获取设备信息 gpio_dev = container_of(inode->i_cdev, struct myaxigpio_dev, cdev); filp->private_data = gpio_dev; // 将设备结构体指针存储到文件私有数据中 return 0; } // read未使用 static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { return 0; } // write未使用 static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { int ret; int val; char kern_buf[1]; ret = copy_from_user(kern_buf, buf, cnt); // 得到应用层传递过来的数据 if (0 > ret) { printk(KERN_ERR "kernel write failed!\r\n"); return -EFAULT; } val = readl(data_addr); printk("write-read:0x%x\r\n", val); val = kern_buf[0]; printk("write 0x%x now\r\n", val); writel(val, data_addr); return 0; } static int led_release(struct inode *inode, struct file *filp) { return 0; } // ioctl函数,应用程序中调用该接口,通过不同命令实现不同的寄存器操作 static long led_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { // struct myaxigpio_dev *dev = filp->private_data; struct myaxigpio_dev *gpio_dev; int direction, value; int ret = 0; // 从filp中获取对于的设备信息结构体myaxigpio_dev,里面存着虚拟地址等信息用于操作寄存器 gpio_dev = filp->private_data; printk("reg:%0x", gpio_dev->reg.gpio_data); switch (cmd) { case GPIO_SET_DIRECTION: if (copy_from_user(&direction, (void __user *)arg, sizeof(int))) { return -EFAULT; } // 设置GPIO的方向,这里假设gpio_tri是设置方向的寄存器 writel(direction, gpio_dev->reg.gpio_tri); break; case GPIO_SET_VALUE: if (copy_from_user(&value, (void __user *)arg, sizeof(int))) { return -EFAULT; } // 设置GPIO的输出电平,这里假设gpio_data是输出寄存器 writel(value, gpio_dev->reg.gpio_data); break; case GPIO_GET_VALUE: // 读取GPIO的输入电平,这里假设gpio_data是输入寄存器 value = readl(gpio_dev->reg.gpio_data); if (copy_to_user((void __user *)arg, &value, sizeof(int))) { return -EFAULT; } break; // 使能全局中断 case GPIO_GLB_ENABLE_IRQ: if (copy_from_user(&value, (void __user *)arg, sizeof(int))) { return -EFAULT; } writel(value, gpio_dev->reg.gpio_gier); break; // 使能通道中断 case GPIO_ENABLE_IRQ: if (copy_from_user(&value, (void __user *)arg, sizeof(int))) { return -EFAULT; } writel(value, gpio_dev->reg.gpio_ip_ier); break; // 获取中断状态 case GPIO_GET_IRQS: value = readl(gpio_dev->reg.gpio_ip_isr); if (copy_to_user((void __user *)arg, &value, sizeof(int))) { return -EFAULT; } break; // 清除中断 case GPIO_CLR_IRQS: if (copy_from_user(&value, (void __user *)arg, sizeof(int))) { return -EFAULT; } writel(value, gpio_dev->reg.gpio_ip_isr); break; default: return -EINVAL; } return ret; } // 中断回调函数 static irqreturn_t example_irq_handler(int irq, void *dev_id) { struct myaxigpio_dev *gpio_dev = dev_id; int value; // 处理中断 // printk("Interrupt handled for device with base address\n"); // 确保中断已被处理 // 根据具体设备可能需要清除中断标志或执行其他操作 value = 1; // 写1清除中断 writel(value, gpio_dev->reg.gpio_ip_isr); return IRQ_HANDLED; } // 将驱动中的函数和应用程序中可操作的函数对应起来 static struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .read = led_read, .write = led_write, .unlocked_ioctl = led_ioctl, .release = led_release, }; // 当insmod驱动模块时,linux会匹配设备树中的compatible属性和驱动文件中的of_match_table列表,当匹配时会执行驱动中的probe函数 // 在probe函数中实现创建一个字符设备,映射ip的物理地址到虚拟地址,所有信息保存到一个自定义的结构体myaxigpio中。 // 执行完probe函数后,在应用程序中再调用open、write、read、ioctl等函数时,linux就会调用驱动程序中实现的open、write等函数, // 具体对应关系在led_fops结构体中实现的 static int gpio_probe(struct platform_device *pdev) { int ret = 0; struct resource *res; pr_info("Platform probe called\n"); // platform_get_resource(pdev, IORESOURCE_MEM, 0)获取到设备树中设备的物理地址 0x412000000~0x41200100 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { pr_err("Failed to get platform resource\n"); return -ENODEV; } // request_mem_region 申请对应物理地址的资源,避免不同驱动都在使用这段地址 if (!request_mem_region(res->start, resource_size(res), pdev->name)) { pr_err("Failed to request memory region\n"); return -EBUSY; } // gpio_remap将物理地址映射到虚拟地址,虚拟地址保存到myaxigpio结构体里面 gpio_remap(&myaxigpio); // 创建字符设备标准操作,申请主设备号、创建字符设备 if (myaxigpio.major) { myaxigpio.devid = MKDEV(myaxigpio.major, 0); ret = register_chrdev_region(myaxigpio.devid, DEV_NUM, DEV_NAME); if (ret) { printk(KERN_ERR "Failed to register chardev region\n"); goto err_chrdev_region; } } else { ret = alloc_chrdev_region(&myaxigpio.devid, 0, DEV_NUM, DEV_NAME); if (ret) { printk(KERN_ERR "Failed to allocate chardev region\n"); goto err_chrdev_alloc; } myaxigpio.major = MAJOR(myaxigpio.devid); myaxigpio.minor = MINOR(myaxigpio.devid); } // 在linux的/dev目录下创建设备 myaxigpio.class = class_create(THIS_MODULE, DEV_NAME); if (IS_ERR(myaxigpio.class)) { ret = PTR_ERR(myaxigpio.class); printk(KERN_ERR "Failed to create device class\n"); goto err_class_create; } // 将驱动实现的open、write等函数绑定到设备,函数在led_fops结构体中 cdev_init(&myaxigpio.cdev, &led_fops); ret = cdev_add(&myaxigpio.cdev, myaxigpio.devid, 1); if (ret < 0) { printk(KERN_ERR "Failed to add cdev\n"); goto err_cdev_add; } myaxigpio.device = device_create(myaxigpio.class, NULL, myaxigpio.devid, NULL, DEV_NAME); if (IS_ERR(myaxigpio.device)) { ret = PTR_ERR(myaxigpio.device); printk(KERN_ERR "Failed to create device\n"); goto err_device_create; } // platform_get_irq(pdev, 0) 从设备树中获取设备的中断号,映射到linux的虚拟中断号并返回,记录到myaxigpio结构体里面 myaxigpio.irq = platform_get_irq(pdev, 0); if (myaxigpio.irq < 0) return myaxigpio.irq; // 使用虚拟中断号申请中断,绑定中断回调函数为example_irq_handler ret = request_irq(myaxigpio.irq, example_irq_handler, IRQF_TRIGGER_RISING, "my-axi-irq", &myaxigpio); printk("request irq ret = %d\n", ret); if (ret) { printk(KERN_ALERT "Failed to request IRQ\n"); return ret; } printk("add irq\n"); printk("reg:%0x", myaxigpio.reg.gpio_data); writel(0xf, myaxigpio.reg.fclk_reset); udelay(10); writel(0x0, myaxigpio.reg.fclk_reset); printk("axi-gpio reset commplete\n"); printk("axi-gpio dev add\n"); dev_err(&pdev->dev, "dev info test\n"); return 0; // 错误处理,主要是之前初始化步骤失败后释放相关资源 err_device_create: cdev_del(&myaxigpio.cdev); err_cdev_add: class_destroy(myaxigpio.class); err_class_create: unregister_chrdev_region(myaxigpio.devid, 1); err_chrdev_alloc: err_chrdev_region: gpio_unmap(&myaxigpio); err_ioremap: return ret; } // remove函数在linux中执行rmmod时执行,用于释放之前申请的资源 static int gpio_remove(struct platform_device *pdev) { struct resource *res; device_destroy(myaxigpio.class, myaxigpio.devid); cdev_del(&myaxigpio.cdev); class_destroy(myaxigpio.class); unregister_chrdev_region(myaxigpio.devid, 1); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res) { gpio_unmap(&myaxigpio); release_mem_region(res->start, resource_size(res)); // 释放内存区域 } free_irq(myaxigpio.irq, &myaxigpio); printk("gpio_remove\r\n"); return 0; } // of_device_id用于和设备树中的compatible属性匹配,用来识别该驱动和设备树中的设备是否匹配 // 匹配就执行probe函数 static const struct of_device_id myaxigpio_of_match[] = { { .compatible = "my-axi-gpio" }, {} }; static struct platform_driver myaxigpio_driver = { .driver = { .name = "my-axi-gpio", .of_match_table = myaxigpio_of_match }, .probe = gpio_probe, .remove = gpio_remove, }; /* 驱动模块入口和出口函数注册 */ // 代替module_init和module_exit module_platform_driver(myaxigpio_driver); MODULE_AUTHOR("mlia"); MODULE_DESCRIPTION("ZYNQ AXI-GPIO LED Test Driver"); MODULE_LICENSE("GPL"); -
同时修改该文件夹目录下的Makefile文件,增加以下内容,作用是告诉编译器如果配置了CONFIG_GPIO_MYAXIGPIO宏,就编译gpio-myaxigpio.c文件
obj-$(CONFIG_GPIO_MYAXIGPIO) += gpio-myaxigpio.o -
修改该文件夹下的Kconfig文件,增加以下内容,作用是增加一个名叫CONFIG_GPIO_MYAXIGPIO的配置项
config GPIO_MYAXIGPIO tristate "MY AXI GPIO Testing Driver" help MY AXI GPIO TEST DRIVER -
做完以上步骤后,在linux源码路径下输入make menuconfig后,在Device Drivers > GPIO Support路径下应该可以看下新增加的MY AXI GPIO Testing Driver配置项,选中该项后通过M键将该驱动配置为编译为模块,保存退出。
![image-20240802013423063]()
-
在Linux源码目录下执行make modules命令编译模块,编译完成后在linux/drivers/gpio目录下应该会生成一个gpio-myaxigpio.ko文件,这个就是编译好的模块。
-
将上一步生成的模块文件拷贝到PS linux中,执行insmod gpio-myaxigpio.ko命令,加载我们自己编写的驱动模块,正常执行的话驱动就会识别到设备树中新添加的my_gpio设备,并获取地址等信息对设备进行初始化,最终在/dev目录下生成一个my-axi-gpio设备,这个设备名是在驱动文件中定义的。
![image-20240802020739946]()
使用cat /proc/interrupts命令查看注册的中断,可以看到有一个名为my-axi-irq的中断,中断号为61,与Vivado中是对应的。
![image-20240802021359435]()
使用dmesg命令也可以看到驱动加载过程中的一些调试打印信息

编写应用程序
- 在虚拟机中新建一个c源文件my-axi-gpio-test.c,输入以下内容:
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <errno.h>
#define DEV_NAME "/dev/my-axi-gpio"
#define GPIO_SET_DIRECTION _IOW('G', 1, int)
#define GPIO_SET_VALUE _IOW('G', 2, int)
#define GPIO_GET_VALUE _IOR('G', 3, int)
#define GPIO_GLB_ENABLE_IRQ _IOW('G', 4, int)
#define GPIO_ENABLE_IRQ _IOW('G', 5, int)
#define GPIO_GET_IRQS _IOR('G', 6, int)
#define GPIO_CLR_IRQS _IOW('G', 7, int)
int main()
{
int fd;
int value, direction;
fd = open(DEV_NAME, O_RDWR);
if (fd < 0)
{
perror("Failed to open the device...");
return errno;
}
// 设置GPIO方向为输出
direction = 0; // 0表示输出,1表示输入
if (ioctl(fd, GPIO_SET_DIRECTION, &direction))
{
perror("ioctl GPIO_SET_DIRECTION failed");
close(fd);
return errno;
}
// 设置GPIO输出高电平
value = 1; // 1表示高电平,0表示低电平
if (ioctl(fd, GPIO_SET_VALUE, &value))
{
perror("ioctl GPIO_SET_VALUE failed");
close(fd);
return errno;
}
// 设置GPIO方向为输出
direction = 1; // 0表示输出,1表示输入
if (ioctl(fd, GPIO_SET_DIRECTION, &direction))
{
perror("ioctl GPIO_SET_DIRECTION failed");
close(fd);
return errno;
}
// 读取GPIO输入电平
if (ioctl(fd, GPIO_GET_VALUE, &value))
{
perror("ioctl GPIO_GET_VALUE failed");
close(fd);
return errno;
}
printf("GPIO input value: %d\n", value);
// 设置全局中断
value = 0x80000000;
if (ioctl(fd, GPIO_GLB_ENABLE_IRQ, &value))
{
perror("ioctl GPIO_GLB_ENABLE_IRQ failed");
close(fd);
return errno;
}
// 设置GPIO CH0中断
value = 1;
if (ioctl(fd, GPIO_ENABLE_IRQ, &value))
{
perror("ioctl GPIO_ENABLE_IRQ failed");
close(fd);
return errno;
}
// 清除GPIO CH0中断
value = 1;
if (ioctl(fd, GPIO_CLR_IRQS, &value))
{
perror("ioctl GPIO_CLR_IRQS failed");
close(fd);
return errno;
}
printf("gpio irq init complete\n");
close(fd);
return 0;
}
-
使用跨编译器编译,生成应用程序文件gpio_example
arm-linux-gnueabihf-gcc my-axi-gpio-test.c -o gpio_example -
将编译好的程序传到PS linux上,直接执行
sudo ./gpio_example -
执行完后GPIO中断是打开了的,通过给gpio输入跳变电平可以触发中断,使用cat /proc/interrupts命令查看中断计数有增加代表中断触发成功了。




浙公网安备 33010602011771号