本帖最后由 LINUX课程 于 2024-9-11 10:38 编辑
软件版本:vitis2021.1(vivado2021.1) 操作系统:WIN10 64bit 硬件平台:适用XILINX Z7/ZU系列FPGA
1 概述 在第十四章我们学习了I2C EEPROM的使用,在该章将会学习通过AXI-I2C来处理PL端eeprom的读写。 实验目的: - 掌握PL端的设备树的编写。
- 掌握vivado工程部分,axi-iic相关的总线搭建。
- 了解AXI-I2C IP的概念。
2 系统框图 3 介绍 3.1 AXI-IIC IP介绍 AXI-IIC控制器的功能构架图如下: 3.2 特性 -符合行业标准I2C协议 -通过axis4 - lite接口访问 -支持I2C主或从模式 -支持多主机操作 -软件可选择ACK位 -仲裁丢失中断并自动从主模式切换从模式 -寻址地址识别中断并自动从主模式到从模式 -起始位和停止位的产生和侦测 -重复起始位的产生 -ACK的产生和侦测 -总线忙检测 -支持1MHZ 、400KHZ 、100KHZ 工作时钟 -7位或10位寻址 -寻址使能和禁止 - 16字节深度的FIFO -节流功能 -动态生成启动和停止 3.3 寄存器概述 3.4 硬件电路分析 本实验只测试EEPROM 4 工程搭建 该部分生成的文件已经分别保存在“BOOT”文件夹内,可以直接使用。如果想要自己生成,按照下面步骤。 4.1 vivado工程搭建 详细的vivado工程搭建参考第一章,本章的vivado工程并非默认的vivado工程,请选择在该章目录中的vivado工程。 打开“soc_prj”文件夹。 进入vivado工程打开IP,查看IP是否连接正确。 4.2 设备树文件 打开vitis,导入之前生成的xsa文件,生成设备树。详细过程参考第一章vivado工程搭建部分。 将pl.dsti部分的设备树文件拷贝进“附件”文件夹“soc_dts”中的dts文件中。或者可以直接使用“22.axi_iic”文件夹中有已经完成了的设备树文件。 4.3 设置内核配置 由于我们自己写的驱动程序会和已有驱动冲突,因此我们需要先将kernel内的驱动去掉。 首先,使用source mz7xcfg.sh添加环境变量,然后使用make_kernel_menuconfig.sh来设置内核。 如上图所示,依次找到Device Drivers - Misc devices - EEPROM support,取消如图所示的选项,保存并退出。运行save_kernel_defconfig.sh保存内核配置文件。 依次执行make_kernel.sh编译内核,create_image.sh打包uboot文件。 使用/uisrc-lab-xlnx/boards/mzux/ubuntu/images/boot下的三个文件,替换sd卡BOOT分区中的三个文件。 4.4 制作系统文件 详细的制作系统文件参考第一章环境搭建部分,将从vivado和vitis中生成的文件复制进开发环境,使用脚本生成系统文件。也可以直接使用本章示例文件夹“3.boot”中的文件,按照第一章的步骤复制进开发环境进行系统制作,这里只做简单的介绍。(“4.BOOT”文件夹中有已经生成好的系统文件镜像文件,可以直接拷贝至系统SD卡的BOOT文件夹内) 5 程序分析 程序分析部分,主要研究下设备树文件,驱动程序和应用程序与第十四章的程序并无不同,相关具体函数可以参考之前的解释。 5.1 设备树分析 - /dts-v1/;
- /include/ "zynq-7000.dtsi"
- / {
- model = "Zynq MZ7X Development Board";
- compatible = "xlnx,zynq-MZ7X", "xlnx,zynq-7000";
- aliases {
- ethernet0 = &gem0;
- serial0 = &uart1;
- spi0 = &qspi;
- i2c0 = &axi_iic_0;
- mmc0 = &sdhci0;
- mmc1 = &sdhci1;
- };
- memory@0 {
- device_type = "memory";
- reg = <0x0 0x40000000>;
- };
- chosen {
- bootargs = "";
- stdout-path = "serial0:115200n8";
- };
- usb_phy0: phy0@e0002000 {
- compatible = "ulpi-phy";
- #phy-cells = <0>;
- reg = <0xe0002000 0x1000>;
- view-port = <0x0170>;
- drv-vbus;
- };
- };
- / {
- amba_pl: amba_pl {
- #address-cells = <1>;
- #size-cells = <1>;
- compatible = "simple-bus";
- ranges ;
- axi_iic_0: i2c@41600000 {
- #address-cells = <1>;
- #size-cells = <0>;
- clock-names = "s_axi_aclk";
- clocks = <&clkc 15>;
- compatible = "xlnx,axi-iic-2.1", "xlnx,xps-iic-2.00.a";
- interrupt-names = "iic2intc_irpt";
- interrupt-parent = <&intc>;
- interrupts = <0 29 4>;
- reg = <0x41600000 0x10000>;
- };
- };
- };
- &axi_iic_0 {
- status = "okay";
- clock-frequency = <400000>;
- // scl-gpios = <&gpio 38 GPIO_ACTIVE_HIGH>;
- // sda-gpios = <&gpio 39 GPIO_ACTIVE_HIGH>;
- // rtc@68 {
- // compatible = "dallas,ds1337";
- // reg = <0x68>;
- // };
- eeprom@50 {
- compatible = "atmel,24c02";
- /* compatible = "at24,24c02"; */
- reg = <0x50>;
- };
- };
- &gem0 {
- status = "okay";
- phy-mode = "rgmii-id";
- xlnx,ptp-enet-clock = <0x69f6bcb>;
- phy-handle = <ðernet_phy>;
- ethernet_phy: ethernet-phy@0 {
- reg = <0>;
- device_type = "ethernet-phy";
- };
- };
- &gpio0 {
- emio-gpio-width = <64>;
- gpio-mask-high = <0x0>;
- gpio-mask-low = <0x5600>;
- };
- &intc {
- num_cpus = <2>;
- num_interrupts = <96>;
- };
- &qspi {
- u-boot,dm-pre-reloc;
- status = "okay";
- is-dual = <0>;
- num-cs = <1>;
- flash@0 {
- compatible = "n25q128a11";
- reg = <0x0>;
- spi-tx-bus-width = <4>;
- spi-rx-bus-width = <4>;
- spi-max-frequency = <125000000>;
- };
- };
- &sdhci0 {
- u-boot,dm-pre-reloc;
- status = "okay";
- xlnx,has-cd = <0x1>;
- xlnx,has-power = <0x0>;
- xlnx,has-wp = <0x0>;
- };
- &sdhci1 {
- status = "okay";
- xlnx,has-cd = <0x1>;
- xlnx,has-power = <0x0>;
- xlnx,has-wp = <0x1>;
- };
- &uart1 {
- u-boot,dm-pre-reloc;
- status = "okay";
- };
- &usb0 {
- status = "okay";
- dr_mode = "host";
- usb-phy = <&usb_phy0>;
- usb-reset = <&gpio0 46 0>;
- };
- &clkc {
- fclk-enable = <0x7>;
- ps-clk-frequency = <33333333>;
- };
复制代码具体的设备树如何查看,请参考第六章基于设备树的平台驱动。 第22行,将“axi_iic_0”设置一个别名“i2c1”。 第48行,vitis生成的关于PL端子卡的相关i2c配置。 和68行,为“axi_iic_0”添加“eeprom”设备,方便自己编写的驱动获取相关设置。 第174行,将“i2c0”中的“eeprom”设备注释掉,避免与子卡的eeprom冲突。 5.2 驱动程序分析 - //添加头文件
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/input.h>
- #include <linux/interrupt.h>
- #include <linux/slab.h>
- #include <linux/of.h>
- #include <linux/of_irq.h>
- #include <linux/of_gpio.h>
- #include <linux/i2c.h>
- #include <linux/io.h>
- #include <linux/uaccess.h>
- #include <linux/cdev.h>
- #define EEPROM_MAJOR 400
- #define EEPROM_MINOR 0
- static struct class *EEPROM_cls;
- //设计一个全局数据对象
- struct eeprom_24c02r_device
- {
- struct cdev cdev;
- struct i2c_client *client; //记录匹配成功后的i2c client
- };
- struct eeprom_24c02r_device *eeprom_dev;
- int eeprom_i2c_send(struct i2c_client *client, char *buf, int count)
- {
- int ret = 0;
- struct i2c_adapter *adapter = client->adapter;
- struct i2c_msg msg;
- msg.addr = client->addr; //消息包是发送给哪个从设备
- msg.buf = buf; //传送的数据
- msg.flags = 0; //是发送数据还是接收数据
- msg.len = count; //数据的个数
- //参数1---i2c控制器对象---来自于client
- //参数2---统一的数据包
- //参数3---数据包的个数
- //返回值已经正确传输数据的个数
- ret = i2c_transfer(adapter, &msg, 1);
- return 0;
- }
- int eeprom_i2c_recv(struct i2c_client *client, char *buf, int count)
- {
- int ret = 0;
- struct i2c_adapter *adapter = client->adapter;
- struct i2c_msg msg;
- msg.addr = client->addr; //消息包是发送给哪个从设备
- msg.buf = buf; //传送的数据
- msg.flags = 1; //是发送数据还是接收数据
- msg.len = count; //数据的个数
- //参数1---i2c控制器对象---来自于client
- //参数2---统一的数据包
- //参数3---数据包的个数
- //返回值已经正确传输数据的个数
- ret = i2c_transfer(adapter, &msg, 1);
- return 0;
- }
- ssize_t eeprom_24c02r_read(struct file *filp, char __user *buf, size_t count, loff_t *fops)
- {
- int ret = 0;
- char *temp_buf = kzalloc(count, GFP_KERNEL);
- //先从从设备读取数据
- ret = eeprom_i2c_recv(eeprom_dev->client, temp_buf, count);
- printk("Successful packet acquisition ret = %d\n", ret);
- if (ret < 0)
- {
- printk("eeprom i2c recv error!\n");
- return -1;
- }
- //将数据传输到应用层
- ret = copy_to_user(buf, temp_buf, count);
- if (ret > 0)
- {
- printk("copy to user error!\n");
- return -EFAULT;
- }
- kfree(temp_buf);
- return count;
- }
- ssize_t eeprom_24c02r_write(struct file *filp, const char __user *buf, size_t count, loff_t *fops)
- {
- int ret = 0;
- char *temp_buf = kzalloc(count, GFP_KERNEL);
- //先从应用层获取数据
- ret = copy_from_user(temp_buf, buf, count);
- if (ret > 0)
- {
- printk("copy from user error!\n");
- return -EFAULT;
- }
- //将数据发送到底层
- ret = eeprom_i2c_send(eeprom_dev->client, temp_buf, count);
- printk("Successful packet acquisition ret = %d\n", ret);
- if (ret < 0)
- {
- printk("eeprom i2c send error!\n");
- return -1;
- }
- kfree(temp_buf);
- return count;
- }
- const struct file_operations eeprom_fops = {
- .owner = THIS_MODULE,
- .read = eeprom_24c02r_read,
- .write = eeprom_24c02r_write,
- };
- //驱动匹配到硬件后自动进入probe函数
- int eeprom_24c02r_probe(struct i2c_client *client, const struct i2c_device_id *id)
- {
- int ret = 0;
- //通过手动分配,组合主次设备号
- dev_t devno = MKDEV(EEPROM_MAJOR, EEPROM_MINOR);
- //给全局数据分配空间
- eeprom_dev = kzalloc(sizeof(*eeprom_dev), GFP_KERNEL);
- if (eeprom_dev == NULL)
- {
- printk("error,Failed to allocate eeprom_dev space!\n");
- return -1;
- }
- //记录i2c匹配成功的client
- eeprom_dev->client = client;
- //注册字符设备
- //参数一:注册的主设备
- //参数二:注册的设备共有多少次设备号
- //参数三:设备名字,在/proc/devices显示
- ret = register_chrdev_region(devno, 1, "eeprom_24c02r");
- if (ret < 0)
- {
- printk("register chrdev region error!\n");
- return 0;
- }
- //初始化字符设备
- cdev_init(&eeprom_dev->cdev, &eeprom_fops);
- eeprom_dev->cdev.owner = THIS_MODULE;
- //创建类
- EEPROM_cls = class_create(THIS_MODULE, "EEPROM_class");
- //创建设备
- device_create(EEPROM_cls, NULL, devno, NULL, "EEPROM_device%d", 0);
- //向系统注册一个设备
- cdev_add(&eeprom_dev->cdev, devno, 1);
- if (ret < 0)
- {
- printk("cdev add faile!\n");
- return -1;
- }
- printk("Test Success!\n");
- return 0;
- }
- int eeprom_24c02r_remove(struct i2c_client *clinet)
- {
- dev_t devno = MKDEV(EEPROM_MAJOR, EEPROM_MINOR);
- cdev_del(&eeprom_dev->cdev);
- //删除设备
- device_destroy(EEPROM_cls, devno);
- //删除类
- class_destroy(EEPROM_cls);
- unregister_chrdev_region(devno, 1);
- kfree(eeprom_dev);
- return 0;
- }
- //用于驱动从设备树中匹配相应的硬件
- const struct of_device_id of_eeprom_24c02r[] = {
- {.compatible = "atmel,24c02"},
- };
- //构建i2c driver
- struct i2c_driver eeprom_24c02r_drv = {
- .driver = {
- .owner = THIS_MODULE,
- .name = "eeprom_24c02r", //体现在/sys/bus/i2c/driver/eeprom_24c02r_drv
- .of_match_table = of_match_ptr(of_eeprom_24c02r) //从设备树中根据compatible获取相应的硬件信息
- },
- .probe = eeprom_24c02r_probe,
- .remove = eeprom_24c02r_remove,
- };
- //实现装载入口函数
- static __init int eeprom_24c02r_drv_init(void)
- {
- //构建i2c设备,并注册到i2c总线上
- return i2c_add_driver(&eeprom_24c02r_drv);
- }
- //实现卸载入口函数
- static __exit void eeprom_24c02r_drv_exit(void)
- {
- i2c_del_driver(&eeprom_24c02r_drv);
- }
- //声明装载入口函数和卸载入口函数
- module_init(eeprom_24c02r_drv_init);
- module_exit(eeprom_24c02r_drv_exit);
- //添加GPL协议
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("msxbo");
复制代码5.3 应用程序分析 - #include <linux/i2c-dev.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <sys/ioctl.h>
- #include <fcntl.h>
- #include <string.h>
- #include <stdlib.h>
- #include <unistd.h>
- //当执行app文件时没有带参数产生提示
- void print_usage(char *str)
- {
- printf("%s r : read at24c02 addresss 0x100\n", str);
- printf("%s w val : write at24c02 addresss 0x100\n", str);
- }
- int main(int argc, char *argv[])
- {
- int i;
- int fd;
- unsigned char val; //字节
- unsigned int register_addr = 0x100; //片内地址
- //存储读写数据的数组
- char wbuf[9] = {0};
- char rbuf[8] = {0};
- //判读执行文件时是否带有一个以上的参数,没有则提示
- if (argc < 2)
- {
- print_usage(argv[0]);
- exit(1);
- }
- /*打开设备文件*/
- fd = open("/dev/EEPROM_device0", O_RDWR);
- if (fd < 0)
- {
- perror("open failed");
- exit(1);
- }
- if (strcmp(argv[1], "r") == 0)
- {
- //确定读哪个位置,读操作时先写片内地址
- if (write(fd, ®ister_addr, 1) < 1)
- {
- perror("write failed");
- exit(1);
- }
- //将EEPROM中的数据读到rbuf中
- if (read(fd, rbuf, 8) < 0)
- {
- perror("read failed");
- exit(1);
- }
- else
- {
- for (i = 0; i < 8; i++)
- {
- printf("rbuf[%d] = 0x%c ", i, rbuf[i]);
- if (i == 3)
- printf("\n");
- }
- printf("\n");
- }
- }
- else if ((strcmp(argv[1], "w") == 0) && (argc == 3))
- {
- char num[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
- int x = strtoul(argv[2], NULL, 0);
- if (x < 0 || x > 15)
- {
- perror("write overflow");
- close(fd);
- exit(1);
- }
- else
- val = num[x];
- //先写片内地址
- wbuf[0] = register_addr; // 片内地址0x08
- wbuf[1] = val;
- printf("val=%d\n", wbuf[1]);
- for (i = 2; i < 9; i++)
- wbuf[i] = '9';
- //连续向fd文件中写入8个8字符
- if (write(fd, wbuf, 9) < 0)
- {
- perror("write failed");
- close(fd);
- exit(1);
- }
- printf("write data ok!\n");
- }
- close(fd);
- return 0;
- }
复制代码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)
- MODULE = eeprom_24c02r_drv
- APP = eeprom_24c02r_app
- all :
- #进入并调用内核源码目录中Makefile的规则, 将当前的目录中的源码编译成模块
- make -C $(KERNEL_DIR) M=$(CURRENT_DIR) modules
- rm -rf *.mod.c *.mod.o *.o *.symvers *.order
- ifneq ($(APP), )
- $(CROSS_COMPILE)gcc $(APP).c -o $(APP)
- endif
- clean :
- make -C $(KERNEL_DIR) M=$(CURRENT_DIR) clean
- rm -rf $(APP) *.tmp
- #指定编译哪个文件
- obj-m += $(MODULE).o
复制代码第10行,和第11行需要根据自己编写的驱动文件名进行修改,其他的部分与之前的一致。 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”命令。确认当前所需文件,已经被上传到当前的文件夹内了。 在管理员权限下,终端输入“chmod +x eeprom_24c02r”,改变应用程序的权限。 然后在root 状态下 insmod 安装驱动。“lsmod”查看当前安装的驱动。 通过以上操作可以确定应用程序,与驱动均已准备完成。 7.3 实验结果 在终端输入“./eeprom_24c02r_app r”,输出如下。可以看到,该应用程序读取了eeprom中前8个字节的内容。 再在终端输入“./eeprom_24c02r_app w 0xA”,输出如下。可以看到,读取的第一个字节的位置的0x1,已经被写入的修改了,变为了0xA。 测试成功。 |