请选择 进入手机版 | 继续访问电脑版
[X]关闭

[米联客-XILINX-H3_CZ08_7100] LINUX驱动篇连载-03 寄存器操作MIO

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

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

1 概述
本课对寄存器 MIO 进行操作,从而实现将 MIO 复用位 GPIO从而通过 GPIO对LED实现控制。从而掌握从寄存器层面的对硬件的控制。
        实验目的:
  • 熟悉 MIO 的概念。
  • 掌握查询寄存器的方法。
  • 掌握寄存器实现 GPIO 控制硬件的原理。
  • 熟悉虚拟地址和物理地址的概念。
  • 熟悉GPIO的概念。
  • 掌握powershell的scp的使用。
2 系统框图
学会了基本驱动的开发,接下来要做的事情就是在LED上进行实验。在PS端接有几个LED灯,这些灯接在了MIO上,而MIO可以复用成为GPIO,因此控制GPIO其实就是控制了LED等器件。本章节通过读写寄存器来实现对GPIO的控制。
图片10.jpg
3 介绍
本实验是通过对寄存器的控制从而控制引脚,然后通过引脚电气特性控制的外部硬件,本节将讲述下寄存器参数该如何查询,寄存器所代表的含义又是什么,本实验的操作流程是什么样的。
3.1 寄存器查询手册
使用GPIO需要设置寄存器,一个是设置GPIO的管脚功能的 GPIO Module 寄存器。查询寄存器地址需要用到一个官方文档,编号的ug585。
在附件中我们同样也准备了一份离线版的ug585以供查阅,该文档很重要,我们需要经常用到这个手册,所以请保存在常用的位置。在这部分,我们仅需要记得到寄存器功能在哪里找就行了。
我们需要查询的寄存器是 XGPIOPS,在ug585文档的1346页:
1726020333626.jpg
每个寄存器都大同小异,学会了看一个,那所有寄存器都可以按照这个方法来看。
完成一个GPIO的设置,需要设置以下内容:
  • 方向寄存器[GPIO]
  • 使能寄存器[GPIO]
  • 输出数据寄存器[GPIO]
  • 输出数据控制寄存器[可选][GPIO]
1:输出寄存器
输出寄存器是GPIO输出,这个寄存器控制了GPIO的输出值,其名字为DATA_X,其中X代表的是bank号,不知道bank号的话下一节会介绍。我们点击其中的一个,查看其具体用法:
image.jpg
当GPIO信号配置为输出时,此寄存器控制正在输出的值。一次写入该寄存器的所有22位。从该寄存器读取将返回先前写入DATA或MASK_DATA_ {LSW,MSW}的值,而非返回设备引脚上的值。注意:此寄存器不影响输出驱动器的使能,请参见DIRM_1和OEN_1寄存器。该寄存器控制bank1的输出值,该值对应于MIO [53:32]。
可以看到这个寄存器的0~22位分别对应了该bank上的22个GPIO引脚。
2:输入寄存器
其名字为DATA_X_RO,功能与输出寄存器相反,用法一致。
image.jpg
0~22位分别对应该bank的22个GPIO引脚。
3:方向寄存器
名字为DIRM_X,X对应的依旧为bank号。方向寄存器决定的是GPIO是输入还是输出,直接影响到前两个寄存器的是否工作。
image.jpg
该寄存器控制IO引脚是用作输入还是输出。输入逻辑始终打开,因此可以高效地启用或禁用输出驱动器。bank的每一位都是独立控制的。该寄存器控制bank0,它对应于MIO [53:32]。
可以看到0~21位控制了bank1上的22个管脚,设置0为输入,设置1为输出。
4:使能寄存器
名字为ONE_X,X对应的依旧为bank号。使能寄存器控制了GPIO的输出使能,打开使能允许输出,关闭则禁用输出。
image.jpg
将IO配置为输出时,这将控制是否启用输出。当输出禁用时,该引脚为三态。该寄存器控制bank1,它对应于MIO [53:32]。
0~22位控制了bank1上的26个管脚,1为使能输出,0为禁用输出。
3.2 物理地址与虚拟地址
提到寄存器,那就不得不说说地址了。寄存器作为系统中存储数据的器件,每个数据都有其自身的物理地址。而与物理地址相对的则是Linux系统下的虚拟地址。在裸机等通常程序里,是不存在虚拟地址一说的,对地址的操作就是对物理地址的操作。但是在Linux下,对物理地址直接访问是行不通的,需要通过虚拟地址间接访问,因此所有对寄存器的操作都需要通过地址映射。
之所以要使用虚拟地址是因为以前的电脑内存非常小,程序也是非常小,因此靠手动便可以管理内存。但是日新月异,今天的程序已经非常庞大了,虽然内存也在飞速发展,但是无法跟上日益臃肿的应用程序。众所周知要想运行一个程序,就必须将其完整地读入内存,如果程序本身就大于内存,那除了加内存可以运行程序,那剩下的唯一办法就是使用虚拟地址了。
image.jpg
虚拟地址可以模拟出几倍于物理地址的内存空间,当程序运行时,被运行的部分读入内存,其他部分依旧存放于外存。物理地址于虚拟地址的关系如上图。
虚拟内存与物理内存是对应的,但是没有顺序。这个顺序是由MMU管理的,MMU全称Memory Management Unit,即内存管理单元。它的功能包括虚拟地址到物理地址的转换(即虚拟内存管理)、内存保护、中央处理器高速缓存的控制等等。
在本章驱动编写时,会使用ioremap、iounmap这两个函数,是用来申请物理地址对应的虚拟地址的。因为在Linux内核启动之后,会初始化 MMU,将物理地址映射为虚拟地址。这个时候,不能向寄存器的物理基地址写入数据,而是应该用ioremap向系统申请的虚拟地址,然后在虚拟地址上进行寄存器的写入和读取操作。
3.3 MIO的介绍
ZYNQ SOC PS部分的IO包括PS-MIO和PS-EMIO。MIO分布在PS的Bank0 、BANK1, EMIO分布在PS的Bank2、Bank3。PS-MIO具有54个GPIO ,PS-EMIO具有最多64个。PS-MIO的IO位置是固定好的,功能也是预先定义好了,而PS-EMIO是通过把芯片内部PS的PS-EMIO引线接到了PL部分的FPGA Pin脚上。
在实际的应用中PS-MIO主要满足必要常用的外设IO需求,比如串口、SDIO、以太网等,而PS-EMIO按需配置,用多少,配置多少。
PS的54个MIO与PS直接相连。不需要添加FPGA的引脚约束,对于EMIO是通过FPGA的IO扩展的,除了需要对PS的ZYNQ IP设置,还需要绑定到FPGA的引脚,所以需要对EMIO做IO引脚约束。
GPIO控制和状态寄存器是从基地址0xE000_A000开始的存储器映射。
3.4 PS-MIO/EMIO的BANK
image.jpg
PS-MIO:
Bank0:MIO[31:0] GPIO PIN脚号:MIO31~MIO0
Bank1:MIO[53:32] GPIO PIN脚号:MIO53~MIO32
PS-EMIO:
Bank2:EMIO[ 0:31] 可以分配到任意的FPGA IO
Bank3:EMIO[32:63] 可以分配到任意的FPGA IO
所以PS-MIO和EMIO 最多118个IO可用
3.5 PS-MIO/EMIO的寄存器
上图中,每个BANK的IO都具有以下的IO结构,下图中不管是MIO还是EMIO,他们的控制寄存器、状态寄存器等都一样,差异也仅仅是MIO是PS固定好了,而EMIO可以把信号映射到FPGA的IO上。
image.jpg
本方案只讲解MIO/EMIO的输入输出配置,中断相关的寄存器,下一个方案中讲解。
1:DATA_RO寄存器:
MIO/EMIO的数据只读寄存器,不管是输入还是输出,都可以通过该寄存器读取到当前IO的状态;
2:DATA寄存器:
MIO/EMIO数据寄存器,可读写,一般是写入数据控制IO的输出值;  
3:MASK_DATA_LSW寄存器:
        BANK IO中低16位IO位设置寄存器。该寄存器分高16中对应位设置0,用于数据的更新输出,低16用于设置需要输出的数据。
4:MASK_DATA_MSW寄存器:
        BANK IO中高16位IO位设置寄存器。该寄存器分高16中对应位设置0,用于数据的更新输出,低16用于设置需要输出的数据。
5:DIRM:寄存器
MIO/EMIO的方向控制寄存器,当DIRM[x]==0时候,禁止输出
6:OEN寄存器:
        MIO/EMIO输出使能寄存器,当配置成MIO的时候,设置OEN[x]=0,MIO处于三态,但是EMIO不支持三态。
当设置DIRM[x] & OEN[x]=0  MIO/EMIO输出;
当设置DIRM[x] & OEN[x]=1  MIO/EMIO输入;
关于更多寄存器的定义说明,可以参考技术手册ug585-Zynq-7000-TRM.pdf
注意:如果MIO TRI_ENABLE设置为1,则启用三态并禁用驱动器,则OEN将被忽略并且输出为三态。
3.6 硬件电路分析
image.jpg
这里我们需要用到2个MIO,6个EMIO,其中MIO51的输出高电平是1.8V所以亮度会比较低一些。
配套工程的FPGA PIN脚定义路径为soc_prj/uisrc/04_pin/ fpga_pin.xdc。
4 搭建工程
        本实验是在已经完成了虚拟机安装,和开发环境部署的基础上进行的,详细的虚拟机安装和开发环境部署请参考1.5.4 Linux环境的搭建。
4.1 设置vivado工程
1:打开vivado工程
将附件中的“soc_prj”文件夹移至桌面,然后再通过vivado打开。
image.jpg
2:打开“system.bd”
双击“system.bd”文件。
image.jpg
3:双击右侧的“ZYNQ UltraSCALE+”
image.jpg
进入“Re-customize IP”。
image.jpg
确认 GPIO 已经勾选之后,重新制作开发板系统。制作系统参考1.4.2部分。
4.2 进入开发环境
1:打开虚拟机
双击桌面的“VMware”。
image.jpg
2:检查编译环境
登录虚拟机之后,在下方的菜单栏点击文件夹“file”,查看部署文件“uisrc-lab-xlnx”是否存在,是否解压缩,内部路径是否正确。如果是在图形界面使用右击点击解压的话,可能会嵌套一个文件。请确保文件路径无误,否则编译会失败。(虚拟机用户名:uisrc,密码:root)
image.jpg
4.3 上传文件至虚拟机
上传文件可以使用U盘,也可以使用scp,也可以使用VMware的“共享文件夹”或者“VMware tools”。在这里仅演示“VMware tools”。这是最简单的方式。
1:在虚拟机中打开终端
在虚拟机内的Ubuntu界面,按下“win”键(也就是windows下呼出关机的菜单键),在跳出的搜索框中输入“terminal”,鼠标点击选择“terminal”。
image.jpg
2:通过命令行下载 vm-tools 软件
在确保已经配置的虚拟机能够连接上网络的情况下进行,可以先用“sudo apt update”进行测试。第一次执行,时间会久一些,请耐心等待。(密码为root)
image.jpg
完成了更新之后,在终端输入“sudo apt install open-vm-tools-desktop”,在提示需要输入的地方输入“y”,回车。
image.jpg
完成了安装提示之后,重启虚拟机,再次登入之后,就可以把桌面的文件直接拖到虚拟机中。
image.jpg
松开鼠标,会发现文件已经显示在 Ubuntu的桌面上了。
image.jpg
至此,工程的搭建部分全部完成。
5 程序分析
5.1 驱动程序分析
对代码进行分析,有助于我们理解并掌握驱动的编写。我们将从“2.LED”文件中的“led_drv_v1”开始程序的分析。
led_drv_v1.c
  1. //添加头文件
  2. #include <linux/init.h>
  3. #include <linux/module.h>
  4. #include <linux/gpio.h>

  5. static int led_major;
  6. static struct class *led_cls;

  7. /* gpio 内存映射地址 */
  8. static unsigned long *gpio_addr = NULL;

  9. /* gpio 寄存器物理基地址 */
  10. #define GPIO_BASE_ADDR 0xE000A000

  11. /* gpio 寄存器所占空间大小 */
  12. #define GPIO_BASE_SIZE 0x2E8

  13. // /* gpio 方向寄存器 */
  14. #define GPIO_DIRM_1 (unsigned int *)((unsigned long)gpio_addr + 0x00000244)
  15. // /* gpio 使能寄存器 */
  16. #define GPIO_OEN_1 (unsigned int *)((unsigned long)gpio_addr + 0x00000248)
  17. /* gpio 输出数据寄存器 */
  18. #define GPIO_DATA_1 (unsigned int *)((unsigned long)gpio_addr + 0x00000044)
  19. // /* gpio 输出数据控制寄存器1 */
  20. // #define GPIO_MASK_DATA_0_LSW (unsigned int *)((unsigned long)gpio_addr + 0x00000000)
  21. // /* gpio 输出数据控制寄存器2 */
  22. // #define GPIO_MASK_DATA_0_MSW (unsigned int *)((unsigned long)gpio_addr + 0x00000004)

  23. const struct file_operations led_fops = {};

  24. //实现装载入口函数和卸载入口函数
  25. static __init int led_drv_v1_init(void)
  26. {
  27. printk("-------^v^-------\n");
  28. printk("-led drv v1 init-\n");
  29. //申请主设备号
  30. //参数1----需要的主设备号,>0静态分配, ==0自动分配
  31. //参数2----设备的描述 信息,体现在cat /proc/devices, 一般自定义
  32. //参数3----文件描述集合
  33. //返回值,小于0报错
  34. led_major = register_chrdev(0, "led_drv_v1", &led_fops);
  35. if (led_major < 0)
  36. {
  37.   printk("register chrdev faile!\n");
  38.   return led_major;
  39. }
  40. printk("register chrdev ok!\n");

  41. //动创建设备节点
  42. //创建设备的类别
  43. //参数1----设备的拥有者,当前模块,直接填THIS_MODULE
  44. //参数2----设备类别的名字,自定义
  45. //返回值:类别结构体指针,其实就是分配了一个结构体空间
  46. led_cls = class_create(THIS_MODULE, "led_class");
  47. printk("class create ok!\n");

  48. //创建设备
  49. //参数1----设备对应的类别
  50. //参数2----当前设备的父类,直接填NULL
  51. //参数3----设备节点关联的设备号
  52. //参数4----私有数据直接填NULL
  53. //参数5----设备节点的名字
  54. device_create(led_cls, NULL, MKDEV(led_major, 0), NULL, "Myled%d", 0);
  55. printk("device create ok!\n");

  56. //硬件初始化
  57. //内存映射
  58. gpio_addr = ioremap(GPIO_BASE_ADDR, GPIO_BASE_SIZE);
  59. printk("gpio_addr init over! 0x%lx\n", *gpio_addr);

  60. // /* MIO23 设置成输出 */
  61. // 0000 0000 0000 1000 0000 0000 0000 0000
  62. // 0x00080000
  63. iowrite32((ioread32(GPIO_DIRM_1) | 0x00080000), GPIO_DIRM_1);
  64. // /* MIO23 使能 */
  65. iowrite32((ioread32(GPIO_OEN_1) | 0x00080000), GPIO_OEN_1);

  66. /* MASK_DATA方式按灭LED2 */
  67. //iowrite32((ioread32(GPIO_MASK_DATA_0_LSW ) & 0x0), GPIO_MASK_DATA_0_LSW );
  68. //printk("GPIO_MASK_DATA_0_LSW = 0x%x\n", *GPIO_MASK_DATA_0_LSW);
  69. //iowrite32((ioread32(GPIO_MASK_DATA_0_MSW ) & 0x0), GPIO_MASK_DATA_0_MSW );
  70. //printk("GPIO_MASK_DATA_0_MSW = 0x%x\n", *GPIO_MASK_DATA_0_MSW);
  71. /* DATA方式按灭LED2 */
  72. // 1111 1111 1111 1111 1111 1111 1111 1111
  73. //                51         32
  74. // 1111 1111 1111 0111 1111 1111 1111 1111
  75. iowrite32((ioread32(GPIO_DATA_1) & 0xFFF7FFFF), GPIO_DATA_1);
  76. printk("GPIO_DATA_1 = 0x%x\n", *GPIO_DATA_1);
  77. return 0;
  78. }

  79. static __exit void led_drv_v1_exit(void)
  80. {
  81. iowrite32((ioread32(GPIO_DATA_1) | 0xFFFFFFFF), GPIO_DATA_1);
  82. //内存释放
  83. iounmap(gpio_addr);
  84. // iounmap(iou_slcr);
  85. //删除设备
  86. device_destroy(led_cls, MKDEV(led_major, 0));
  87. //删除类
  88. class_destroy(led_cls);
  89. //注销主设备号
  90. unregister_chrdev(led_major, "led_drv_v1");

  91. printk("-------^v^-------\n");
  92. printk("-led drv v1 exit-\n");
  93. }

  94. //申明装载入口函数和卸载入口函数
  95. module_init(led_drv_v1_init);
  96. module_exit(led_drv_v1_exit);

  97. //添加GPL协议
  98. MODULE_LICENSE("GPL");
  99. MODULE_AUTHOR("msxbo");
复制代码
行10,用来储存映射后虚拟地址的基地址。87、88行赋值。
行13,是我们要控制的寄存器物理地址的基地址。
行16,是两个寄存器的完整大小,在地址转换的时候直接映射整个寄存器,方便理解。
行18~27,GPIO的控制寄存器。
行87,使用data方法熄灭led。
在操作寄存器的时候,要看清楚与和或,特别是在置一或置零时。
1:内存映射
  1. static __init int led_drv_v1_init(void)
  2. {
  3.     ......
  4.     //1.内存映射
  5.     gpio_addr = ioremap(GPIO_BASE_ADDR, GPIO_BASE_SIZE);
  6.     ......
  7.     return 0;
  8. }
复制代码
含义:内存映射的目的是为了把物理地址映射为虚拟地址,方便对寄存器进行操作。
具体分析:
        分析一个“GPIO Module”寄存器。
  • GPIO_BASE_ADDR:这是寄存器手册中查询到的“GPIO Module”的“Base Address”。具体手册的查询,查看介绍3.1,寄存器手册的查询。
image.jpg
  • GPIO_BASE_SIZE:这是该寄存器的容量,也就是“GPIO Module”的容量。从“GPIO Module”的最后一行可以看到,它的最后一位是“0x0000000364”,位宽是 32 位。简单的算下,就是 4 字节,所以“GPIO Module”的容量为“0x0000000368”。
image.jpg
2:设置MIO功能
  1. static __init int led_drv_v1_init(void)
  2. {
  3.     ......
  4.     //2.MIO40 MIO42 设置成GPIO,参考手册设置
  5.     iowrite32((ioread32(GPIO_PIN_23) & 0x0), GPIO_PIN_23);
  6.     printk("gpio MIO23 init over!\n");
  7.     ......
  8.     return 0;
  9. }
复制代码
含义:将MIO的功能设置为GPIO。
具体分析:
  • iowrite32:这个函数会把前面的值写入到后面的目的地址。
  • ioread32:这个函数则是读取目的地址的值。
  • 0x0:这个部分需要查询寄存器手册的“IOU_SLCR Module”部分,找到需要设置的MIO引脚的序号,然后根据自己的需要设置手册中的相应的值。
  • GPIO_PIN_38:这是在之前确定好的“iou_slcr”进行偏移的地址。偏移地址可以在寄存器手册中查询到,这里仅做演示。
3:设置MIO输出电流的大小
  1. static __init int led_drv_v1_init(void)
  2. {
  3.     ......
  4.     // 参考手册,可以知道每一位的地址都是由 CTRL0 和 CTRL1 控制的,在这里都是 10, 查看手   册得 8 mA。
  5.     iowrite32((ioread32(IOU_SLCR_BANK0_CTRL0) | 0x3FFFFFF), IOU_SLCR_BANK0_CTRL0);
  6.     iowrite32((ioread32(IOU_SLCR_BANK0_CTRL1) & 0x0), IOU_SLCR_BANK0_CTRL1);
  7.     ......
  8.     return 0;
  9. }
复制代码
含义:将Bank1的电流都设置为8mA,Bank1控制的是MIO引脚 [26:51]。
具体分析:
寄存器部分的操作都是按位计算的,想要什么功能,在手册中查到之后,就需要通过寄存器把想要控制的引脚的地址写入对应功能所需的设置。这里以控制电流为例。
因为需要控制的引脚为MIO38和MIO23,38 和 23 都是bank1所负责的区域。因此,在寄存器手册中找到对应的bank1部分。
image.jpg
这个部分的寄存器都是控制bank1部分功能的,我们先介绍ctrl0和ctrl1两个部分。这两个部分是控制电流的。其他的会在接下来的部分进行介绍。
通过查看“bank1_crtl0 (IOU_SLCR) Register”的“Description”可以知道,ctrl0得和ctrl1两个部分才能控制电流的大小。
image.jpg
我们需要控制的是MIO40和MIO42,这里为了方便起见,将ctrl0全部设置为1,将ctrl1全部设置为0,查表可以知道是8mA。
  • &:按位与,可以理解为交集,交集是指A与B共有的。
  • |:按位或,可以理解为并集,并集是指A和B所有的。
4:设置引脚是 Schmitt Trigger 还是 CMOS Input
  1. static __init int led_drv_v1_init(void)
  2. {
  3.     ......
  4.     //4.选择引脚是Schmitt还是CMOS
  5.     iowrite32((ioread32(IOU_SLCR_BANK0_CTRL3) & 0x0), IOU_SLCR_BANK0_CTRL3);
  6.     ......
  7.     return 0;
  8. }
复制代码
含义:设置MIO引脚的功能,这里选择 CMOS Input。
5:设置引脚的上下拉及使能
  1. static __init int led_drv_v1_init(void)
  2. {
  3.     ......
  4.     //5.输出管脚上下拉及使能
  5.     iowrite32((ioread32(IOU_SLCR_BANK0_CTRL4) | 0x3FFFFFF), IOU_SLCR_BANK0_CTRL4);
  6.     iowrite32((ioread32(IOU_SLCR_BANK0_CTRL5) | 0x3FFFFFF), IOU_SLCR_BANK0_CTRL5);
  7.     ......
  8.     return 0;
  9. }
复制代码
含义:设置MIO引脚的上拉或者下拉,并且使能。
具体分析:
在ctrl4部分将其全部设置为1,对照寄存器手册可以知道是上拉,并且bank1上所有的都是上拉。同理在ctrl5部分也将其全部设置为1,对照寄存器手册可以看到bank1上 [26:51] 的MIO都被启动了。
6:设置MIO的速率
  1. //实现装载入口函数和卸载入口函数
  2. static __init int led_drv_v1_init(void)
  3. {
  4.     ......
  5.     //6.MIO速率的选择
  6.     iowrite32((ioread32(IOU_SLCR_BANK0_CTRL6) & 0x0), IOU_SLCR_BANK0_CTRL6);
  7.     ......
  8.     return 0;
  9. }
复制代码
含义:设置MIO的速率。
具体分析:
Ctrl6 可以设置MIO的速率,0为低速,1为高速。
7:设置GPIO的输入输出
  1. //实现装载入口函数和卸载入口函数
  2. static __init int led_drv_v1_init(void)
  3. {
  4.        ......
  5. iowrite32((ioread32(GPIO_DIRM_0) | 0x00820000), GPIO_DIRM_0);
  6. iowrite32((ioread32(GPIO_OEN_0) | 0x00820000), GPIO_OEN_0);
  7.       ......
  8.       return 0;
  9. }
复制代码
含义:将GPIO的输入输出部分,MIO设置为输出。
具体分析:
这个部分设置的是GPIO的相关功能,所以要去“GPIO Module”部分去找相关的寄存器的功能。
  • GPIO_DIRM_0:是指控制GPIO的输入输出方向。
  • GPIO_OEN_0:则是用来控制使能,也就是enable的意思。
8:通过Data的方式熄灭LED
  1. static __init int led_drv_v1_init(void)
  2. {
  3.     ......
  4.     iowrite32((ioread32(GPIO_DATA_0) & 0xFF7FFFFF), GPIO_DATA_0);
  5.     printk("GPIO_DATA_0 = 0x%x\n", *GPIO_DATA_0);
  6.     ......
  7.     return 0;
  8. }
复制代码
含义:将已经配置GPIO的MIO,MIO23的输出设置为0,也就是关闭输出。
具体分析:
这是通过控制DATA寄存器的方式熄灭LED,还可以使用MASK_DATA的方式熄灭LED。具体的设置的值需要自己查寄存器手册修改。DATA寄存器的说明在介绍部分。
9:内存释放
  1. static __exit void led_drv_v1_exit(void)
  2. {
  3. ......
  4. //9.内存释放
  5. iounmap(gpio_addr);
  6. iounmap(iou_slcr);
  7. ......
  8. }
复制代码
含义:将申请的虚拟内存资源进行释放。
具体分析:
在通过ioremap向系统申请了系统资源之后,在驱动退出的时候,需要将资源释放。释放用iounmap函数。
6 程序编译
        这部分主要分为两部分。第一部分先对Makefile文件进行分析,第二部分是完成驱动文件的编译。目的是为了生成能在开发板上运行的驱动模块。
6.1 Makefile文件分析
在led_drv_v1的文件中,可以找到用来在开发环境中进行编译的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. all :
  8. #进入并调用内核源码目录中Makefile的规则, 将当前的目录中的源码编译成模块
  9. make -C $(KERNEL_DIR) M=$(CURRENT_DIR) modules
  10. rm -rf *.symvers *.order *.o *.mod.o *.mod.c


  11. clean :
  12. make -C $(KERNEL_DIR) M=$(CURRENT_DIR) clean


  13. #指定编译哪个文件
  14. obj-m += led_drv_v1.o
复制代码
基础的 Makefile 语法在基础部分已经讲过了,这部分将会讲些比较重要的部分。
首先,查看第 2 行,KERNEL_DIR的路径是否是符合当前环境部署路径,如果是自己改的部署的解压缩路径,那么就需要更改第 2 行的路径。
其次,查看第20行,确定需要编译的文件名是否是需要编译的文件。
6.2 在虚拟机进行交叉编译
        详细的步骤请参考“Hello World”部分的“在虚拟机上进行交叉编译”。这里仅做简单的演示。
1:打开终端
在“1.led_drv_v1”中打开终端,输入“ls -l”或者“ll”,查看当前的路径下是否存在“led_drv_v1.c”和“Makefile”文件。
image.jpg
2:编译文件
在确定存在的情况下,在终端输入“make”。完成编译之后,输入“ls -l”,可以发现驱动文件已经生成了,也就是“led_drv_v1.ko”文件,下图第二个。
image.jpg
3:将编译好的驱动文件(.ko)上传到开发板
如何把文件从桌面传递到虚拟机中,在之前的3.4.3节,也就是“上传文件至虚拟机”已经讲过了。这部分的话,就是反向将编译好的“.ko”文件从虚拟机拖回桌面。然后再使用xshell的lrzsz从桌面传递给板子。当然,也可以使用powershell 的scp命令把文件传递到板子。
简单的演示下如何在windows桌面使用powershell把文件传递到开发板上。
3-1:打开powershell
点击菜单栏的“开始”按钮。在弹出页面后直接在键盘输入“powershell”。鼠标点击“Windows PowerShell”。
3-2:在powershell中上传驱动文件
在确定能连接通开发板的基础上,再进行scp传输,否则会失败。可以用“ping”确定是否连接到开发板。
而且scp是基于ssh传输的,需要使用到网口,使用的时候,请把开发板的网口连接至主机,在主机设置共享网络,这样开发板就能够连接上网络了。
首先确定开发板的ip地址是否能够“ping”通。Ip地址可以通过串口连接至开发板后终端输入“ifconfig”获得。
image.jpg
在ping通之后,确认主机与开发板能够传输数据。
在“PowerShell”终端输入“scp .\Desktop\led_drv_v1.ko uisrc@192.168.137.100:/home/uisrc”。
问你是否继续连接,输入“yes”。
image.jpg
问账户“uisrc”的登入密码,输入“root”
image.jpg
等待传输成功。
在xshell中输入“ls”查看,发现驱动文件已经存在于当前目录。
image.jpg
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
7.2 程序准备
在终端中使用输入“ls”,查看是否存在“led_drv_v1.ko”文件。
image.jpg
确认存在之后,进入root权限,然后在终端输入“insmod led_drv_v1.ko”。
image.jpg
输入“lsmod”查看是否安装成功。
image.jpg
7.3 实验结果
在安装了“led_drv_v1.ko”驱动之后,开发板上左上侧的PS_LED熄灭,则成功。
在终端输入“rmmod led_drv_v1.ko”,原本熄灭的灯会亮起。
image.jpg
查看开发板,PS端的LED全部亮起,则成功。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则