[X]关闭

[米联客-XILINX-H3_CZ08_7100] LINUX驱动篇连载-13 AXI-IIC

文档创建者:LINUX课程
浏览次数:285
最后更新:2024-09-10
文档课程分类-AMD-ZYNQ
AMD-ZYNQ: ZYNQ-SOC » 2_LINUX应用开发
本帖最后由 LINUX课程 于 2024-9-11 10:38 编辑

软件版本:vitis2021.1(vivado2021.1)
操作系统:WIN10 64bit
硬件平台:适用XILINX Z7/ZU系列FPGA
登录“米联客”FPGA社区-www.uisrc.com视频课程、答疑解惑!

1 概述
在第十四章我们学习了I2C EEPROM的使用,在该章将会学习通过AXI-I2C来处理PL端eeprom的读写。
        实验目的:
  • 掌握PL端的设备树的编写。
  • 掌握vivado工程部分,axi-iic相关的总线搭建。
  • 了解AXI-I2C IP的概念。
2 系统框图
图片3.jpg
3 介绍
3.1 AXI-IIC IP介绍
AXI-IIC控制器的功能构架图如下:
image.jpg
3.2 特性
-符合行业标准I2C协议
-通过axis4 - lite接口访问
-支持I2C主或从模式
-支持多主机操作
-软件可选择ACK位
-仲裁丢失中断并自动从主模式切换从模式
-寻址地址识别中断并自动从主模式到从模式
-起始位和停止位的产生和侦测
-重复起始位的产生
-ACK的产生和侦测
-总线忙检测
-支持1MHZ 、400KHZ 、100KHZ 工作时钟
-7位或10位寻址
-寻址使能和禁止
- 16字节深度的FIFO
-节流功能
-动态生成启动和停止
3.3 寄存器概述
image.jpg
3.4 硬件电路分析
本实验只测试EEPROM
image.jpg
4 工程搭建
该部分生成的文件已经分别保存在“BOOT”文件夹内,可以直接使用。如果想要自己生成,按照下面步骤。
4.1 vivado工程搭建
详细的vivado工程搭建参考第一章,本章的vivado工程并非默认的vivado工程,请选择在该章目录中的vivado工程。
打开“soc_prj”文件夹。
image.jpg
进入vivado工程打开IP,查看IP是否连接正确。
image.jpg
4.2 设备树文件
打开vitis,导入之前生成的xsa文件,生成设备树。详细过程参考第一章vivado工程搭建部分。
image.jpg
将pl.dsti部分的设备树文件拷贝进“附件”文件夹“soc_dts”中的dts文件中。或者可以直接使用“22.axi_iic”文件夹中有已经完成了的设备树文件。
4.3 设置内核配置
由于我们自己写的驱动程序会和已有驱动冲突,因此我们需要先将kernel内的驱动去掉。
image.jpg
首先,使用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 设备树分析
  1. /dts-v1/;
  2. /include/ "zynq-7000.dtsi"

  3. / {
  4. model = "Zynq MZ7X Development Board";
  5. compatible = "xlnx,zynq-MZ7X", "xlnx,zynq-7000";

  6. aliases {
  7.   ethernet0 = &gem0;
  8.   serial0 = &uart1;
  9.   spi0 = &qspi;
  10.   i2c0 = &axi_iic_0;
  11.   mmc0 = &sdhci0;
  12.   mmc1 = &sdhci1;
  13. };

  14. memory@0 {
  15.   device_type = "memory";
  16.   reg = <0x0 0x40000000>;
  17. };

  18. chosen {
  19.   bootargs = "";
  20.   stdout-path = "serial0:115200n8";
  21. };

  22. usb_phy0: phy0@e0002000 {
  23.   compatible = "ulpi-phy";
  24.   #phy-cells = <0>;
  25.   reg = <0xe0002000 0x1000>;
  26.   view-port = <0x0170>;
  27.   drv-vbus;
  28. };
  29. };

  30. / {
  31. amba_pl: amba_pl {
  32.   #address-cells = <1>;
  33.   #size-cells = <1>;
  34.   compatible = "simple-bus";
  35.   ranges ;
  36.   axi_iic_0: i2c@41600000 {
  37.    #address-cells = <1>;
  38.    #size-cells = <0>;
  39.    clock-names = "s_axi_aclk";
  40.    clocks = <&clkc 15>;
  41.    compatible = "xlnx,axi-iic-2.1", "xlnx,xps-iic-2.00.a";
  42.    interrupt-names = "iic2intc_irpt";
  43.    interrupt-parent = <&intc>;
  44.    interrupts = <0 29 4>;
  45.    reg = <0x41600000 0x10000>;
  46.   };
  47. };
  48. };

  49. &axi_iic_0 {
  50. status = "okay";
  51. clock-frequency = <400000>;
  52. // scl-gpios = <&gpio 38 GPIO_ACTIVE_HIGH>;
  53. // sda-gpios = <&gpio 39 GPIO_ACTIVE_HIGH>;

  54. // rtc@68 {
  55. //  compatible = "dallas,ds1337";
  56. //  reg = <0x68>;
  57. // };

  58. eeprom@50 {
  59.   compatible = "atmel,24c02";
  60.   /* compatible = "at24,24c02"; */
  61.   reg = <0x50>;
  62. };
  63. };

  64. &gem0 {
  65. status = "okay";
  66. phy-mode = "rgmii-id";
  67. xlnx,ptp-enet-clock = <0x69f6bcb>;
  68. phy-handle = <&ethernet_phy>;

  69. ethernet_phy: ethernet-phy@0 {
  70.   reg = <0>;
  71.   device_type = "ethernet-phy";
  72. };
  73. };

  74. &gpio0 {
  75. emio-gpio-width = <64>;
  76. gpio-mask-high = <0x0>;
  77. gpio-mask-low = <0x5600>;
  78. };

  79. &intc {
  80. num_cpus = <2>;
  81. num_interrupts = <96>;
  82. };

  83. &qspi {
  84. u-boot,dm-pre-reloc;
  85. status = "okay";
  86. is-dual = <0>;
  87. num-cs = <1>;
  88. flash@0 {
  89.   compatible = "n25q128a11";
  90.   reg = <0x0>;
  91.   spi-tx-bus-width = <4>;
  92.   spi-rx-bus-width = <4>;
  93.   spi-max-frequency = <125000000>;
  94. };
  95. };

  96. &sdhci0 {
  97. u-boot,dm-pre-reloc;
  98. status = "okay";
  99. xlnx,has-cd = <0x1>;
  100. xlnx,has-power = <0x0>;
  101. xlnx,has-wp = <0x0>;
  102. };

  103. &sdhci1 {
  104. status = "okay";
  105. xlnx,has-cd = <0x1>;
  106. xlnx,has-power = <0x0>;
  107. xlnx,has-wp = <0x1>;
  108. };

  109. &uart1 {
  110. u-boot,dm-pre-reloc;
  111. status = "okay";
  112. };

  113. &usb0 {
  114. status = "okay";
  115. dr_mode = "host";
  116. usb-phy = <&usb_phy0>;
  117. usb-reset = <&gpio0 46 0>;
  118. };

  119. &clkc {
  120. fclk-enable = <0x7>;
  121. ps-clk-frequency = <33333333>;
  122. };
复制代码
具体的设备树如何查看,请参考第六章基于设备树的平台驱动。
第22行,将“axi_iic_0”设置一个别名“i2c1”。
第48行,vitis生成的关于PL端子卡的相关i2c配置。
和68行,为“axi_iic_0”添加“eeprom”设备,方便自己编写的驱动获取相关设置。
第174行,将“i2c0”中的“eeprom”设备注释掉,避免与子卡的eeprom冲突。
5.2 驱动程序分析
  1. //添加头文件
  2. #include <linux/init.h>
  3. #include <linux/module.h>
  4. #include <linux/input.h>
  5. #include <linux/interrupt.h>
  6. #include <linux/slab.h>
  7. #include <linux/of.h>
  8. #include <linux/of_irq.h>
  9. #include <linux/of_gpio.h>
  10. #include <linux/i2c.h>
  11. #include <linux/io.h>
  12. #include <linux/uaccess.h>
  13. #include <linux/cdev.h>

  14. #define EEPROM_MAJOR 400
  15. #define EEPROM_MINOR 0

  16. static struct class *EEPROM_cls;

  17. //设计一个全局数据对象
  18. struct eeprom_24c02r_device
  19. {
  20. struct cdev cdev;
  21. struct i2c_client *client; //记录匹配成功后的i2c client
  22. };

  23. struct eeprom_24c02r_device *eeprom_dev;

  24. int eeprom_i2c_send(struct i2c_client *client, char *buf, int count)
  25. {
  26. int ret = 0;
  27. struct i2c_adapter *adapter = client->adapter;
  28. struct i2c_msg msg;

  29. msg.addr = client->addr; //消息包是发送给哪个从设备
  30. msg.buf = buf;    //传送的数据
  31. msg.flags = 0;    //是发送数据还是接收数据
  32. msg.len = count;   //数据的个数

  33. //参数1---i2c控制器对象---来自于client
  34. //参数2---统一的数据包
  35. //参数3---数据包的个数
  36. //返回值已经正确传输数据的个数
  37. ret = i2c_transfer(adapter, &msg, 1);
  38. return 0;
  39. }

  40. int eeprom_i2c_recv(struct i2c_client *client, char *buf, int count)
  41. {
  42. int ret = 0;
  43. struct i2c_adapter *adapter = client->adapter;
  44. struct i2c_msg msg;

  45. msg.addr = client->addr; //消息包是发送给哪个从设备
  46. msg.buf = buf;    //传送的数据
  47. msg.flags = 1;    //是发送数据还是接收数据
  48. msg.len = count;   //数据的个数

  49. //参数1---i2c控制器对象---来自于client
  50. //参数2---统一的数据包
  51. //参数3---数据包的个数
  52. //返回值已经正确传输数据的个数
  53. ret = i2c_transfer(adapter, &msg, 1);
  54. return 0;
  55. }

  56. ssize_t eeprom_24c02r_read(struct file *filp, char __user *buf, size_t count, loff_t *fops)
  57. {
  58. int ret = 0;
  59. char *temp_buf = kzalloc(count, GFP_KERNEL);

  60. //先从从设备读取数据
  61. ret = eeprom_i2c_recv(eeprom_dev->client, temp_buf, count);
  62. printk("Successful packet acquisition ret = %d\n", ret);
  63. if (ret < 0)
  64. {
  65.   printk("eeprom i2c recv error!\n");
  66.   return -1;
  67. }

  68. //将数据传输到应用层
  69. ret = copy_to_user(buf, temp_buf, count);
  70. if (ret > 0)
  71. {
  72.   printk("copy to user error!\n");
  73.   return -EFAULT;
  74. }

  75. kfree(temp_buf);

  76. return count;
  77. }

  78. ssize_t eeprom_24c02r_write(struct file *filp, const char __user *buf, size_t count, loff_t *fops)
  79. {
  80. int ret = 0;
  81. char *temp_buf = kzalloc(count, GFP_KERNEL);

  82. //先从应用层获取数据
  83. ret = copy_from_user(temp_buf, buf, count);
  84. if (ret > 0)
  85. {
  86.   printk("copy from user error!\n");
  87.   return -EFAULT;
  88. }

  89. //将数据发送到底层
  90. ret = eeprom_i2c_send(eeprom_dev->client, temp_buf, count);
  91. printk("Successful packet acquisition ret = %d\n", ret);
  92. if (ret < 0)
  93. {
  94.   printk("eeprom i2c send error!\n");
  95.   return -1;
  96. }

  97. kfree(temp_buf);

  98. return count;
  99. }

  100. const struct file_operations eeprom_fops = {
  101. .owner = THIS_MODULE,
  102. .read = eeprom_24c02r_read,
  103. .write = eeprom_24c02r_write,
  104. };

  105. //驱动匹配到硬件后自动进入probe函数
  106. int eeprom_24c02r_probe(struct i2c_client *client, const struct i2c_device_id *id)
  107. {
  108. int ret = 0;
  109. //通过手动分配,组合主次设备号
  110. dev_t devno = MKDEV(EEPROM_MAJOR, EEPROM_MINOR);

  111. //给全局数据分配空间
  112. eeprom_dev = kzalloc(sizeof(*eeprom_dev), GFP_KERNEL);
  113. if (eeprom_dev == NULL)
  114. {
  115.   printk("error,Failed to allocate eeprom_dev space!\n");
  116.   return -1;
  117. }

  118. //记录i2c匹配成功的client
  119. eeprom_dev->client = client;

  120. //注册字符设备
  121. //参数一:注册的主设备
  122. //参数二:注册的设备共有多少次设备号
  123. //参数三:设备名字,在/proc/devices显示
  124. ret = register_chrdev_region(devno, 1, "eeprom_24c02r");
  125. if (ret < 0)
  126. {
  127.   printk("register chrdev region error!\n");
  128.   return 0;
  129. }

  130. //初始化字符设备
  131. cdev_init(&eeprom_dev->cdev, &eeprom_fops);
  132. eeprom_dev->cdev.owner = THIS_MODULE;
  133. //创建类
  134. EEPROM_cls = class_create(THIS_MODULE, "EEPROM_class");
  135. //创建设备
  136. device_create(EEPROM_cls, NULL, devno, NULL, "EEPROM_device%d", 0);

  137. //向系统注册一个设备
  138. cdev_add(&eeprom_dev->cdev, devno, 1);
  139. if (ret < 0)
  140. {
  141.   printk("cdev add faile!\n");
  142.   return -1;
  143. }
  144. printk("Test Success!\n");
  145. return 0;
  146. }

  147. int eeprom_24c02r_remove(struct i2c_client *clinet)
  148. {
  149. dev_t devno = MKDEV(EEPROM_MAJOR, EEPROM_MINOR);
  150. cdev_del(&eeprom_dev->cdev);
  151. //删除设备
  152. device_destroy(EEPROM_cls, devno);
  153. //删除类
  154. class_destroy(EEPROM_cls);
  155. unregister_chrdev_region(devno, 1);
  156. kfree(eeprom_dev);
  157. return 0;
  158. }

  159. //用于驱动从设备树中匹配相应的硬件
  160. const struct of_device_id of_eeprom_24c02r[] = {
  161. {.compatible = "atmel,24c02"},
  162. };

  163. //构建i2c driver
  164. struct i2c_driver eeprom_24c02r_drv = {
  165. .driver = {
  166.   .owner = THIS_MODULE,
  167.   .name = "eeprom_24c02r",       //体现在/sys/bus/i2c/driver/eeprom_24c02r_drv
  168.   .of_match_table = of_match_ptr(of_eeprom_24c02r) //从设备树中根据compatible获取相应的硬件信息
  169. },
  170. .probe = eeprom_24c02r_probe,
  171. .remove = eeprom_24c02r_remove,
  172. };

  173. //实现装载入口函数
  174. static __init int eeprom_24c02r_drv_init(void)
  175. {
  176. //构建i2c设备,并注册到i2c总线上
  177. return i2c_add_driver(&eeprom_24c02r_drv);
  178. }

  179. //实现卸载入口函数
  180. static __exit void eeprom_24c02r_drv_exit(void)
  181. {
  182. i2c_del_driver(&eeprom_24c02r_drv);
  183. }

  184. //声明装载入口函数和卸载入口函数
  185. module_init(eeprom_24c02r_drv_init);
  186. module_exit(eeprom_24c02r_drv_exit);
  187. //添加GPL协议
  188. MODULE_LICENSE("GPL");
  189. MODULE_AUTHOR("msxbo");
复制代码
5.3 应用程序分析
  1. #include <linux/i2c-dev.h>
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <fcntl.h>
  5. #include <stdlib.h>
  6. #include <stdio.h>
  7. #include <sys/ioctl.h>
  8. #include <fcntl.h>
  9. #include <string.h>
  10. #include <stdlib.h>
  11. #include <unistd.h>

  12. //当执行app文件时没有带参数产生提示
  13. void print_usage(char *str)
  14. {
  15. printf("%s r     : read at24c02 addresss 0x100\n", str);
  16. printf("%s w val : write at24c02 addresss 0x100\n", str);
  17. }

  18. int main(int argc, char *argv[])
  19. {
  20. int i;
  21. int fd;
  22. unsigned char val; //字节

  23. unsigned int register_addr = 0x100; //片内地址

  24. //存储读写数据的数组
  25. char wbuf[9] = {0};
  26. char rbuf[8] = {0};

  27. //判读执行文件时是否带有一个以上的参数,没有则提示
  28. if (argc < 2)
  29. {
  30.   print_usage(argv[0]);
  31.   exit(1);
  32. }

  33. /*打开设备文件*/
  34. fd = open("/dev/EEPROM_device0", O_RDWR);
  35. if (fd < 0)
  36. {
  37.   perror("open failed");
  38.   exit(1);
  39. }

  40. if (strcmp(argv[1], "r") == 0)
  41. {
  42.   //确定读哪个位置,读操作时先写片内地址
  43.   if (write(fd, &register_addr, 1) < 1)
  44.   {
  45.    perror("write failed");
  46.    exit(1);
  47.   }

  48.   //将EEPROM中的数据读到rbuf中
  49.   if (read(fd, rbuf, 8) < 0)
  50.   {
  51.    perror("read failed");
  52.    exit(1);
  53.   }
  54.   else
  55.   {
  56.    for (i = 0; i < 8; i++)
  57.    {
  58.     printf("rbuf[%d] = 0x%c ", i, rbuf[i]);
  59.     if (i == 3)
  60.      printf("\n");
  61.    }
  62.    printf("\n");
  63.   }
  64. }
  65. else if ((strcmp(argv[1], "w") == 0) && (argc == 3))
  66. {
  67.   char num[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
  68.   int x = strtoul(argv[2], NULL, 0);
  69.   if (x < 0 || x > 15)
  70.   {
  71.    perror("write overflow");
  72.    close(fd);
  73.    exit(1);
  74.   }
  75.   else
  76.    val = num[x];

  77.   //先写片内地址
  78.   wbuf[0] = register_addr; // 片内地址0x08
  79.   wbuf[1] = val;
  80.   printf("val=%d\n", wbuf[1]);

  81.   for (i = 2; i < 9; i++)
  82.    wbuf[i] = '9';

  83.   //连续向fd文件中写入8个8字符
  84.   if (write(fd, wbuf, 9) < 0)
  85.   {
  86.    perror("write failed");
  87.    close(fd);
  88.    exit(1);
  89.   }

  90.   printf("write data ok!\n");
  91. }

  92. close(fd);
  93. return 0;
  94. }
复制代码
6 程序编译
详细的程序编译部分可以参考第一章程序编译部分。将编译好的文件上传至开发板。
6.1 Makefile文件分析
  1. #已经编译过的内核源码路径
  2. KERNEL_DIR = /home/uisrc/uisrc-lab-xlnx/sources/kernel

  3. export ARCH=arm
  4. export CROSS_COMPILE=arm-linux-gnueabihf-

  5. #当前路径
  6. CURRENT_DIR = $(shell pwd)

  7. MODULE =  eeprom_24c02r_drv
  8. APP = eeprom_24c02r_app

  9. all :
  10. #进入并调用内核源码目录中Makefile的规则, 将当前的目录中的源码编译成模块
  11. make -C $(KERNEL_DIR) M=$(CURRENT_DIR) modules
  12. rm -rf *.mod.c *.mod.o *.o *.symvers *.order

  13. ifneq ($(APP), )
  14. $(CROSS_COMPILE)gcc $(APP).c -o $(APP)
  15. endif

  16. clean :
  17. make -C $(KERNEL_DIR) M=$(CURRENT_DIR) clean
  18. rm -rf $(APP) *.tmp

  19. #指定编译哪个文件
  20. 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实现二次开发”)
2f5038eb9880afd532753935815b079.jpg
将 PS 端串口线连接电脑,如果要使用 ssh 登录,将网口线同样连接至电脑,最后给开发板通电。每次重新上电,需要重新插拔 PS 串口,否则会登录失败。
image.jpg
接入12V直流电源开机。
7.2 程序准备
将驱动与程序上传至开发板,查看文件是否传输成功。
在终端输入“ls”命令。确认当前所需文件,已经被上传到当前的文件夹内了。
image.jpg
在管理员权限下,终端输入“chmod +x eeprom_24c02r”,改变应用程序的权限。
image.jpg
然后在root 状态下 insmod 安装驱动。“lsmod”查看当前安装的驱动。
image.jpg
通过以上操作可以确定应用程序,与驱动均已准备完成。
7.3 实验结果
在终端输入“./eeprom_24c02r_app r”,输出如下。可以看到,该应用程序读取了eeprom中前8个字节的内容。
image.jpg
再在终端输入“./eeprom_24c02r_app w 0xA”,输出如下。可以看到,读取的第一个字节的位置的0x1,已经被写入的修改了,变为了0xA。
image.jpg
测试成功。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则