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工程

  1. 在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,使能中断。

image-20240802000114357

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

image-20240802004511190

  1. 添加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]
  1. 直接编译综合,生成比特流文件ZYNQ_AXI_TEST_wrapper.bit

  2. 把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
      
  3. 将生成的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寄存器定义

image-20240802005710538

在Vivado中我们只配置使用了1个通道,因此控制GPIO方向和控制输入输出只需要操作FPIO_DATAGPIO_TRI寄存器就行了,其中GPIO_DATA读为输入电平,写为控制输出电平,GPIO_TRI写0为输出,写1为输入。对于GPIO中断使能、查询和清除,需要操作GIERIP IERIP 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文件中的其他函数如writereadunlock_ioctl等函数就可以操作虚拟地址实现寄存器的读写。设备树文件zynq-zc702.dtb由设备树源文件zynq-zc702.dts和其包含的其他源文件xxx.dtsixxx.h通过设备树编译器dtc转换得到。

设备树文件

  1. 在设备树文件中增加以下内容:
  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>;
  };
  1. 修改完设备树后在Linux源码根目录下使用make dtbs命令重新编译设备树文件,生成新的zynq-zc702.dtb文件,复制到ZYNQ SD卡的BOOT分区,替换原来的dtb文件。

驱动源文件

  1. 在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");
    
  2. 同时修改该文件夹目录下的Makefile文件,增加以下内容,作用是告诉编译器如果配置了CONFIG_GPIO_MYAXIGPIO宏,就编译gpio-myaxigpio.c文件

    obj-$(CONFIG_GPIO_MYAXIGPIO) += gpio-myaxigpio.o
    
  3. 修改该文件夹下的Kconfig文件,增加以下内容,作用是增加一个名叫CONFIG_GPIO_MYAXIGPIO的配置项

    config GPIO_MYAXIGPIO
    	tristate "MY AXI GPIO Testing Driver"
    	help
    	  MY AXI GPIO TEST DRIVER
    
    
  4. 做完以上步骤后,在linux源码路径下输入make menuconfig后,在Device Drivers > GPIO Support路径下应该可以看下新增加的MY AXI GPIO Testing Driver配置项,选中该项后通过M键将该驱动配置为编译为模块,保存退出。

    image-20240802013423063

  5. 在Linux源码目录下执行make modules命令编译模块,编译完成后在linux/drivers/gpio目录下应该会生成一个gpio-myaxigpio.ko文件,这个就是编译好的模块。

  6. 将上一步生成的模块文件拷贝到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命令也可以看到驱动加载过程中的一些调试打印信息

image-20240802020853567

编写应用程序

  1. 在虚拟机中新建一个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;
}

  1. 使用跨编译器编译,生成应用程序文件gpio_example

    arm-linux-gnueabihf-gcc my-axi-gpio-test.c  -o gpio_example 
    
  2. 将编译好的程序传到PS linux上,直接执行

    sudo ./gpio_example
    
  3. 执行完后GPIO中断是打开了的,通过给gpio输入跳变电平可以触发中断,使用cat /proc/interrupts命令查看中断计数有增加代表中断触发成功了。

posted @ 2024-11-08 01:14  LM358  阅读(695)  评论(0)    收藏  举报