本帖最后由 LINUX课程 于 2024-9-11 10:36 编辑
软件版本:vitis2021.1(vivado2021.1) 操作系统:WIN10 64bit 硬件平台:适用XILINX Z7/ZU系列FPGA
1 概述 在前面的第四章,我们已经了解了如何使用GPIO子系统来实现对PS端的KEY进行控制,在该章我们将继续使用GPIO子系统来对PL端的KEY进行控制。 实验目的: - 掌握从LINUX对PL端的GPIO的KEY进行控制。
- 了解AXI-GPIO IP的相关概念。
- 掌握GPIO子系统的编程。
2 系统框图 上图是概念上的框架连接图。从AXI-GPIO中将会引出两个GPIO的管脚,一个是BTN按钮,一个是LED。本章实验将使用BTN按钮部分。
3 介绍 3.1 AXI-GPIO IP介绍 AXI-GPIO IP通过AXI-Lite-slave连接到PS。具体2个通道,每个通道32个GPIO。在IP里面可以固定配置为输入或者输出,也可以通过软件控制寄存器配置为输入或者输出。并且支持中断输入,AXI-GPIO的构架如下图所示。 3.2 特性 -支持axis4 - lite接口规范 -支持可配置的单或双GPIO通道 -支持GPIO引脚1 ~ 32位的通道宽度配置 -支持动态规划每个GPIO位作为输入或输出 -支持每个通道的单独配置 -支持所有寄存器的每个位的独立重置值 -支持可选的中断请求生成 3.3 寄存器说明 1:数据寄存器GPIOx_DATA 有两个GPIO数据寄存器(GPIO_DATA和GPIO2_DATA),每个通道对应一个。通道1数据寄存器(GPIO_DATA)始终存在;通道2数据寄存器(GPIO2_DATA)只有当IP配置为双通道(Enable dual channel = 1)时才存在。 2:三态寄存器GPIOx_TRI 有两个AXI GPIO 3-state控制寄存器(GPIO_TRI和GPIO2_TRI),每个通道对应一个。通道2 3状态控制寄存器(GPIO2_TRI)只有在IP配置为双通道使能双通道= 1时才存在。 3:全局中断使能寄存器(GIER) 设bit位为1使置31能中断。 4:IP-CORE中断使能寄存器(IPIER) 5:IP状态寄存器(IPISR) 4 搭建工程 该部分生成的文件已经分别保存在“3.boot”和“4.BOOT”文件夹内,可以直接使用。如果想要自己生成,按照下面步骤。 4.1 vivado工程搭建 详细的vivado工程搭建参考第一章,本章的vivado工程并非默认的vivado工程,请选择在该章目录中的vivado工程。 打开“soc_prj”文件夹。 进入vivado工程部分,打开,检查IP是否连接正确。 4.2 设备树文件 将vitis中生成的设备树文件中的pl.dtsi部分复制进原先的zynqmp-mzux.dts文件中。 4.3 制作系统文件 详细的制作系统文件参考第一章环境搭建部分,将从vivado和vitis中生成的文件复制进开发环境,使用脚本生成系统文件。也可以直接使用本章示例文件夹“3.boot”中的文件,按照第一章的步骤复制进开发环境进行系统制作,这里只做简单的介绍。(“4.BOOT”文件夹中有已经生成好的系统文件镜像文件,可以直接拷贝至系统SD卡的BOOT文件夹内) 5 程序分析 该实验的程序与之前的GPIO子系统的程序极为相似,GPIO子系统相关的代码如有疑问,可以参考之前的第四章GPIO子系统程序分析部分。 5.1 驱动程序分析 - //添加头文件
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/gpio.h>
- #include <linux/uaccess.h>
- #define PL_GPIO_BTN 1022
- static int key_major;
- static int ret = 0;
- static struct class *key_cls;
- int key_open(struct inode *inode, struct file *filp)
- {
- printk("----^v^-----key open\n");
- //2、将 PL_GPIO_BTN 设置为输入
- ret = gpio_direction_input(PL_GPIO_BTN);
- if (ret != 0)
- {
- printk("gpio direction input MIO_PIN_42 fail!\n");
- }
- return 0;
- }
- ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *fops)
- {
- int value[4] = {0};
- printk("----^v^-----\n");
- printk("key read\n");
- //3、获取 PL_GPIO_BTN 的值
- value[0] = gpio_get_value(PL_GPIO_BTN);
- ret = copy_to_user(buf, value, count);
- if (ret < 0)
- {
- printk("gpio get value failed!\n");
- return ret;
- }
- return 0;
- }
- int key_close(struct inode *inode, struct file *filp)
- {
- printk("----^v^-----\n");
- printk("key close\n");
- return 0;
- }
- const struct file_operations key_fops = {
- .open = key_open,
- .read = key_read,
- .release = key_close,
- };
- //实现装载入口函数和卸载入口函数
- static __init int key_drv_v1_init(void)
- {
- printk("----^v^-----\n");
- printk("key drv v1 init\n");
- //申请主设备号
- //参数1----需要的主设备号,>0静态分配, ==0自动分配
- //参数2----设备的描述 信息,体现在cat /proc/devices, 一般自定义
- //参数3----文件描述集合
- //返回值,小于0报错
- key_major = register_chrdev(0, "key_drv_v1", &key_fops);
- if (key_major < 0)
- {
- printk("register chrdev faile!\n");
- return key_major;
- }
- printk("register chrdev ok!\n");
- //自动创建设备节点
- //创建设备的类别
- //参数1----设备的拥有者,当前模块,直接填THIS_MODULE
- //参数2----设备类别的名字,自定义
- //返回值:类别结构体指针,其实就是分配了一个结构体空间
- key_cls = class_create(THIS_MODULE, "key_class");
- printk("class create ok!\n");
- //创建设备
- //参数1----设备对应的类别
- //参数2----当前设备的父类,直接填NULL
- //参数3----设备节点关联的设备号
- //参数4----私有数据直接填NULL
- //参数5----设备节点的名字
- device_create(key_cls, NULL, MKDEV(key_major, 0), NULL, "mykey%d", 0);
- printk("device create ok!\n");
- //1、PL_GPIO_BTN 申请 GPIO 口
- ret = gpio_request(PL_GPIO_BTN, "key1");
- if (ret < 0)
- {
- printk("gpio request key1 error!\n");
- return ret;
- }
- return 0;
- }
- static __exit void key_drv_v1_exit(void)
- {
- printk("----^v^-----\n");
- printk("key drv v1 exit\n");
- //4、释放按键 GPIO
- gpio_free(PL_GPIO_BTN);
- //删除设备
- device_destroy(key_cls, MKDEV(key_major, 0));
- //删除类
- class_destroy(key_cls);
- //注销主设备号
- unregister_chrdev(key_major, "key_drv_v1");
- }
- //申明装载入口函数和卸载入口函数
- module_init(key_drv_v1_init);
- module_exit(key_drv_v1_exit);
- //添加GPL协议
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("msxbo");
复制代码注意第7行的代码,第7行的代码中的511是之前在vivado中设置AXI-GPIO时分配的一个管脚,之前在终端输入“ls /sys/class/gpio”中展示的也就是这个地址。 5.2 应用程序分析 - #include <stdio.h>
- #include <stdlib.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <unistd.h>
- int main(void)
- {
- int fd = 0;
- int i = 0;
- int a[4] = {0};
- fd = open("/dev/mykey0", O_RDWR);
- if (fd < 0)
- {
- perror("open fail!\n");
- exit(1);
- }
- while (1)
- {
- read(fd, a, sizeof(a));
- printf("but value %d\n", a[0]);
- sleep(3);
- }
- close(fd);
- return 0;
- }
复制代码第21至26行,该程序将会每3秒读取一次设备中的值。 6 程序编译 详细的程序编译部分可以参考第一章程序编译部分。将编译好的文件上传至开发板。 6.1 Makefile文件分析 - #已经编译过的内核源码路径
- KERNEL_DIR = /home/uisrc/uisrc-lab-xlnx/sources/kernel
- export ARCH=arm
- export CROSS_COMPILE=arm-linux-gnueabihf-
- #当前路径
- CURRENT_DIR = $(shell pwd)
- all :
- #进入并调用内核源码目录中Makefile的规则, 将当前的目录中的源码编译成模块
- make -C $(KERNEL_DIR) M=$(CURRENT_DIR) modules
- rm -rf *.symvers *.order *.o *.mod.o *.mod.c
- clean :
- make -C $(KERNEL_DIR) M=$(CURRENT_DIR) clean
- #指定编译哪个文件
- obj-m += axi_user_gpio.o
复制代码7 演示 7.1 硬件准备 SD2.0 启动 01 而模式开关为 ON OFF(7100 需要先将系统烧录进qspi,然后才能从qspi启动sd卡,“[米联客-XILINX-H3_CZ08_7100] LINUX基础篇连载-04 从vitis移植Ubuntu实现二次开发”) 将 PS 端串口线连接电脑,如果要使用 ssh 登录,将网口线同样连接至电脑,最后给开发板通电。每次重新上电,需要重新插拔 PS 串口,否则会登录失败。 接入12V直流电源开机。 7.2 程序准备 将驱动与程序上传至开发板,查看文件是否传输成功。 在终端输入“ls”命令。确认当前所需文件,已经被上传到当前的文件夹内了。 然后在root 状态下 insmod 安装驱动。“lsmod”查看当前安装的驱动。 在管理员权限下,终端输入“chmod +x key_app_v1”,改变应用程序的权限。 通过以上操作可以确定应用程序,与驱动均已准备完成。 7.3 实验结果 在终端输入“./key_app_v1”,输出如下。可以看到,该应用程序每隔3秒就会读取了PL_BUT的值。按钮松开时是高电平,获取的值为1。 按住PL_BUT,再来查看终端输出,输出为0。 测试成功。 |