搜索
bottom↓
回复: 0

【正点原子FPGA连载】第三十一章Linux按键输入实验--摘自【正点原子】领航者 ZYNQ 之linux驱动开发指南

[复制链接]

出0入234汤圆

发表于 2020-9-9 15:26:01 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2020-9-16 12:26 编辑

1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz.html
4)正点原子官方B站:
https://space.bilibili.com/394620890
5)对正点原子FPGA感兴趣的同学可以加群讨论:876744900 点击加入:
QQ群头像.png                                                
6)关注正点原子公众号,获取最新资料


100846rel79a9p4uelap24.jpg

100846f1ce1fg14zbg0va4.png

第三十一章Linux按键输入实验



在前几章我们都是使用的GPIO输出功能,还没有用过GPIO输入功能,本章我们就来学习一下如果在Linux下编写GPIO输入驱动程序。领航者开发板上有5个按键,四个轻触式按键和一个触摸按键,本章我们就以PS_KEY0按键为例,使用此按键来完成GPIO输入驱动程序,同时利用第三十章讲的互斥锁来对按键值进行保护。

31.1Linux下按键驱动原理
按键驱动和LED驱动原理上来讲基本都是一样的,都是操作GPIO,只不过一个是读取GPIO的高低电平,一个是从GPIO输出高低电平。本章我们实现按键输入,在驱动程序中实现read函数,读取按键值并将数据发送给上层应用测试程序,在read函数中,使用了互斥锁对读数据过程进行了保护,后面会讲解为什么使用互斥锁进行保护。Linux下的按键驱动原理很简单,接下来开始编写驱动。
注意,本章例程只是为了演示Linux下GPIO输入驱动的编写,实际中的按键驱动并不会采用本章中所讲解的方法,Linux下的input子系统专门用于输入设备!
31.2硬件原理图分析
打开领航者底板原理图,找到PS_KEY0按键原理图,如下所示:

阿莫论坛发帖领航者专用1489.png

阿莫论坛发帖领航者专用1491.png

图 42.2.1 按键原理图

从原理图可知,当PS_KEY0按键按下时,对应的管脚MIO12为低电平状态,松开的时候MIO12为高电平状态,所以可以通过读取MIO管脚的电平状态来判断按键是否被按下或松开!
31.3实验程序编写
本实验对应的例程路径为:ZYNQ开发板光盘资料(A盘)\4_SourceCode\ZYNQ_7010\3_Embedded_Linux\Linux驱动例程\11_key。
31.3.1修改设备树文件
打开system-top.dts文件,在根节点“/”下创建一个按键节点,节点名为“key”,节点内容如下:
示例代码31.3.1.1 创建key节点
  1. 49 key {
  2. 50     compatible = "alientek,key";
  3. 51     status = "okay";
  4. 52     key-gpio = <&gpio0 12 GPIO_ACTIVE_LOW>;
  5. 53 };
复制代码

这个节点内容很简单。
第50行,设置节点的compatible属性为“alientek,key”。
第52行,key-gpio属性指定了PS_KEY0按键所使用的GPIO。
设备树编写完成以后使用,在linux内核源码目录下执行下面这条命令重新编译设备树:
  1. make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- system-top.dtb
复制代码
阿莫论坛发帖领航者专用11135.png


图 42.3.1 重新编译设备树

然后将新编译出来的system-top.dtb文件重命名为system.dtb,将system.dtb文件拷贝到SD启动卡的Fat分区,替换以前的system.dtb文件,替换完成之后重启开发板。启动成功以后进入“/proc/device-tree”目录中查看“key”节点是否存在,如果存在的话就说明设备树基本修改成功(具体还要驱动验证),结果如图 42.3.2所示:
阿莫论坛发帖领航者专用11407.png

图 42.3.2 key节点

31.3.2按键驱动程序编写
设备树准备好以后就可以编写驱动程序了,在drivers目录下新建名为“11_key”的文件夹,然后在11_key文件夹里面新建一个名为key.c的源文件,在key.c里面输入如下内容:
示例代码31.3.2.1 key.c文件代码
  1.   1 /***************************************************************
  2.   2  Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
  3.   3  文件名    : key.c
  4.   4  作者      : 邓涛
  5.   5  版本      : V1.0
  6.   6  描述      : Linux按键输入驱动实验
  7.   7  其他      : 无
  8.   8  论坛      : <a href="www.openedv.com" target="_blank">www.openedv.com</a>
  9.   9  日志      : 初版V1.0 2019/1/30 邓涛创建
  10. 10  ***************************************************************/
  11. 11
  12. 12 #include <linux/types.h>
  13. 13 #include <linux/kernel.h>
  14. 14 #include <linux/delay.h>
  15. 15 #include <linux/ide.h>
  16. 16 #include <linux/init.h>
  17. 17 #include <linux/module.h>
  18. 18 #include <linux/errno.h>
  19. 19 #include <linux/gpio.h>
  20. 20 #include <asm/mach/map.h>
  21. 21 #include <asm/uaccess.h>
  22. 22 #include <asm/io.h>
  23. 23 #include <linux/cdev.h>
  24. 24 #include <linux/of.h>
  25. 25 #include <linux/of_address.h>
  26. 26 #include <linux/of_gpio.h>
  27. 27
  28. 28 #define KEY_CNT                1                /* 设备号个数 */
  29. 29 #define KEY_NAME                "key"        /* 名字 */
  30. 30
  31. 31 /* dtsled设备结构体 */
  32. 32 struct key_dev {
  33. 33     dev_t devid;                                /* 设备号 */
  34. 34     struct cdev cdev;                        /* cdev */
  35. 35     struct class *class;                /* 类 */
  36. 36     struct device *device;        /* 设备 */
  37. 37     int major;                                        /* 主设备号 */
  38. 38     int minor;                                        /* 次设备号 */
  39. 39     struct device_node *nd;        /* 设备节点 */
  40. 40     int key_gpio;                                /* GPIO编号 */
  41. 41     int key_val;                                /* 按键值 */
  42. 42     struct mutex mutex;                /* 互斥锁 */
  43. 43 };
  44. 44
  45. 45 static struct key_dev key;        /* led设备 */
  46. 46
  47. 47 /*
  48. 48  * @description                : 打开设备
  49. 49  * @param – inode                : 传递给驱动的inode
  50. 50  * @param – filp                : 设备文件,file结构体有个叫做private_data的成员变量
  51. 51  *                                                   一般在open的时候将private_data指向设备结构体。
  52. 52  * @return                                : 0 成功;其他 失败
  53. 53  */
  54. 54 static int key_open(struct inode *inode, struct file *filp)
  55. 55 {
  56. 56     return 0;
  57. 57 }
  58. 58
  59. 59 /*
  60. 60  * @description                : 从设备读取数据
  61. 61  * @param – filp                : 要打开的设备文件(文件描述符)
  62. 62  * @param – buf                : 返回给用户空间的数据缓冲区
  63. 63  * @param – cnt                : 要读取的数据长度
  64. 64  * @param – offt                : 相对于文件首地址的偏移
  65. 65  * @return                        : 读取的字节数,如果为负值,表示读取失败
  66. 66  */
  67. 67 static ssize_t key_read(struct file *filp, char __user *buf,
  68. 68             size_t cnt, loff_t *offt)
  69. 69 {
  70. 70     int ret = 0;
  71. 71
  72. 72     /* 互斥锁上锁 */
  73. 73     if (mutex_lock_interruptible(&key.mutex))
  74. 74         return -ERESTARTSYS;
  75. 75
  76. 76     /* 读取按键数据 */
  77. 77     if (!gpio_get_value(key.key_gpio)) {
  78. 78         while(!gpio_get_value(key.key_gpio));
  79. 79         key.key_val = 0x0;
  80. 80     } else
  81. 81         key.key_val = 0xFF;
  82. 82
  83. 83     /* 将按键数据发送给应用程序 */
  84. 84     ret = copy_to_user(buf, &key.key_val, sizeof(int));
  85. 85
  86. 86     /* 解锁 */
  87. 87     mutex_unlock(&key.mutex);
  88. 88
  89. 89     return ret;
  90. 90 }
  91. 91
  92. 92 /*
  93. 93  * @description                : 向设备写数据
  94. 94  * @param – filp                : 设备文件,表示打开的文件描述符
  95. 95  * @param – buf                : 要写给设备写入的数据
  96. 96  * @param – cnt                : 要写入的数据长度
  97. 97  * @param – offt                : 相对于文件首地址的偏移
  98. 98  * @return                                : 写入的字节数,如果为负值,表示写入失败
  99. 99  */
  100. 100 static ssize_t key_write(struct file *filp, const char __user *buf,
  101. 101             size_t cnt, loff_t *offt)
  102. 102 {
  103. 103     return 0;
  104. 104 }
  105. 105
  106. 106 /*
  107. 107  * @description                : 关闭/释放设备
  108. 108  * @param – filp                : 要关闭的设备文件(文件描述符)
  109. 109  * @return                                : 0 成功;其他 失败
  110. 110  */
  111. 111 static int key_release(struct inode *inode, struct file *filp)
  112. 112 {
  113. 113     return 0;
  114. 114 }
  115. 115
  116. 116 /* 设备操作函数 */
  117. 117 static struct file_operations key_fops = {
  118. 118     .owner                = THIS_MODULE,
  119. 119     .open                = key_open,
  120. 120     .read                = key_read,
  121. 121     .write                = key_write,
  122. 122     .release        = key_release,
  123. 123 };
  124. 124
  125. 125 static int __init mykey_init(void)
  126. 126 {
  127. 127     const char *str;
  128. 128     int ret;
  129. 129
  130. 130     /* 初始化互斥锁 */
  131. 131     mutex_init(&key.mutex);
  132. 132
  133. 133     /* 1.获取key节点 */
  134. 134     key.nd = of_find_node_by_path("/key");
  135. 135     if(NULL == key.nd) {
  136. 136         printk(KERN_ERR "key: Failed to get key node\n");
  137. 137         return -EINVAL;
  138. 138     }
  139. 139
  140. 140     /* 2.读取status属性 */
  141. 141     ret = of_property_read_string(key.nd, "status", &str);
  142. 142     if(!ret) {
  143. 143         if (strcmp(str, "okay"))
  144. 144             return -EINVAL;
  145. 145     }
  146. 146
  147. 147     /* 3.获取compatible属性值并进行匹配 */
  148. 148     ret = of_property_read_string(key.nd, "compatible", &str);
  149. 149     if(ret) {
  150. 150         printk(KERN_ERR "key: Failed to get compatible property\n");
  151. 151         return ret;
  152. 152     }
  153. 153
  154. 154     if (strcmp(str, "alientek,key")) {
  155. 155         printk(KERN_ERR "key: Compatible match failed\n");
  156. 156         return -EINVAL;
  157. 157     }
  158. 158
  159. 159     printk(KERN_INFO "key: device matching successful!\r\n");
  160. 160
  161. 161     /* 4.获取设备树中的key-gpio属性,得到按键所使用的GPIO编号 */
  162. 162     key.key_gpio = of_get_named_gpio(key.nd, "key-gpio", 0);
  163. 163     if(!gpio_is_valid(key.key_gpio)) {
  164. 164         printk(KERN_ERR "key: Failed to get key-gpio\n");
  165. 165         return -EINVAL;
  166. 166     }
  167. 167
  168. 168     printk(KERN_INFO "key: key-gpio num = %d\r\n", key.key_gpio);
  169. 169
  170. 170     /* 5.申请GPIO */
  171. 171     ret = gpio_request(key.key_gpio, "Key Gpio");
  172. 172     if (ret) {
  173. 173         printk(KERN_ERR "key: Failed to request key-gpio\n");
  174. 174         return ret;
  175. 175     }
  176. 176
  177. 177     /* 6.将GPIO设置为输入模式 */
  178. 178     gpio_direction_input(key.key_gpio);
  179. 179
  180. 180     /* 7.注册字符设备驱动 */
  181. 181      /* 创建设备号 */
  182. 182     if (key.major) {
  183. 183         key.devid = MKDEV(key.major, 0);
  184. 184         ret = register_chrdev_region(key.devid, KEY_CNT, KEY_NAME);
  185. 185         if (ret)
  186. 186             goto out1;
  187. 187     } else {
  188. 188         ret = alloc_chrdev_region(&key.devid, 0, KEY_CNT, KEY_NAME);
  189. 189         if (ret)
  190. 190             goto out1;
  191. 191
  192. 192         key.major = MAJOR(key.devid);
  193. 193         key.minor = MINOR(key.devid);
  194. 194     }
  195. 195
  196. 196     printk(KERN_INFO "key: major=%d, minor=%d\r\n", key.major, key.minor);
  197. 197
  198. 198      /* 初始化cdev */
  199. 199     key.cdev.owner = THIS_MODULE;
  200. 200     cdev_init(&key.cdev, &key_fops);
  201. 201
  202. 202      /* 添加cdev */
  203. 203     ret = cdev_add(&key.cdev, key.devid, KEY_CNT);
  204. 204     if (ret)
  205. 205         goto out2;
  206. 206
  207. 207      /* 创建类 */
  208. 208     key.class = class_create(THIS_MODULE, KEY_NAME);
  209. 209     if (IS_ERR(key.class)) {
  210. 210         ret = PTR_ERR(key.class);
  211. 211         goto out3;
  212. 212     }
  213. 213
  214. 214      /* 创建设备 */
  215. 215     key.device = device_create(key.class, NULL,
  216. 216                 key.devid, NULL, KEY_NAME);
  217. 217     if (IS_ERR(key.device)) {
  218. 218         ret = PTR_ERR(key.device);
  219. 219         goto out4;
  220. 220     }
  221. 221
  222. 222     return 0;
  223. 223
  224. 224 out4:
  225. 225     class_destroy(key.class);
  226. 226
  227. 227 out3:
  228. 228     cdev_del(&key.cdev);
  229. 229
  230. 230 out2:
  231. 231     unregister_chrdev_region(key.devid, KEY_CNT);
  232. 232
  233. 233 out1:
  234. 234     gpio_free(key.key_gpio);
  235. 235
  236. 236     return ret;
  237. 237 }
  238. 238
  239. 239 static void __exit mykey_exit(void)
  240. 240 {
  241. 241     /* 注销设备 */
  242. 242     device_destroy(key.class, key.devid);
  243. 243
  244. 244     /* 注销类 */
  245. 245     class_destroy(key.class);
  246. 246
  247. 247     /* 删除cdev */
  248. 248     cdev_del(&key.cdev);
  249. 249
  250. 250     /* 注销设备号 */
  251. 251     unregister_chrdev_region(key.devid, KEY_CNT);
  252. 252
  253. 253     /* 释放GPIO */
  254. 254     gpio_free(key.key_gpio);
  255. 255 }
  256. 256
  257. 257 /* 驱动模块入口和出口函数注册 */
  258. 258 module_init(mykey_init);
  259. 259 module_exit(mykey_exit);
  260. 260
  261. 261 MODULE_AUTHOR("DengTao <<a href="mailto:773904075@qq.com">773904075@qq.com</a>>");
  262. 262 MODULE_DESCRIPTION("Alientek Gpio Key Driver");
  263. 263 MODULE_LICENSE("GPL");
复制代码

        第32~43行,结构体key_dev为按键的设备结构体,第40行的key_gpio表示按键对应的GPIO编号,第41行key_val用来保存读取到的按键值,第42行定义了一个互斥锁变量mutex,用来保护按键读取过程。
第67~90行,在key_read函数中,通过gpio_get_value函数读取按键值,如果当前为按下状态,则使用while循环等待按键松开,松开之后将key_val变量置为0x0,从按键按下状态到松开状态视为一次有效状态;如果当前为松开状态,则将key_val变量置为0xFF,表示为无效状态。使用copy_to_user函数将key_val值发送给上层应用;第73行,调用mutex_lock_interruptible函数上锁(互斥锁),第87行解锁,对整个读取按键过程进行保护,因为在于用于保存按键值的key_val是一个全局变量,如果上层有多个应用对按键进行了读取操作,将会出现第二十九章说到的并发访问,这对系统来说是不利的,所以这里使用了互斥锁进行了保护。应用程序通过read函数读取按键值的时候key_read函数就会执行!
第131行,调用mutex_init函数初始化互斥锁。
第178行,调用gpio_direction_input函数将按键对应的GPIO设置为输入模式。
key.c文件代码很简单,重点就是key_read函数读取按键值,要对读取过程进行保护。
31.3.3编写测试APP
在本章实验目录下新建名为keyApp.c的文件,然后输入如下所示内容:
示例代码31.3.3.1 keyApp.c文件代码
  1. 1 /***************************************************************
  2.   2  Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
  3.   3  文件名        : keyApp.c
  4.   4  作者          : 邓涛
  5.   5  版本          : V1.0
  6.   6  描述          : 按键测试应用程序
  7.   7  其他          : 无
  8.   8  使用方法      : ./keyApp /dev/key
  9.   9  论坛          : <a href="www.openedv.com" target="_blank">www.openedv.com</a>
  10. 10  日志          : 初版V1.0 2019/1/30 邓涛创建
  11. 11  ***************************************************************/
  12. 12
  13. 13 #include <stdio.h>
  14. 14 #include <unistd.h>
  15. 15 #include <sys/types.h>
  16. 16 #include <sys/stat.h>
  17. 17 #include <fcntl.h>
  18. 18 #include <stdlib.h>
  19. 19 #include <string.h>
  20. 20
  21. 21 /*
  22. 22  * @description         : main主程序
  23. 23  * @param - argc        : argv数组元素个数
  24. 24  * @param - argv        : 具体参数
  25. 25  * @return              : 0 成功;其他 失败
  26. 26  */
  27. 27 int main(int argc, char *argv[])
  28. 28 {
  29. 29     int fd, ret;
  30. 30     int key_val;
  31. 31
  32. 32     /* 判断传参个数是否正确 */
  33. 33     if(2 != argc) {
  34. 34         printf("Usage:\n"
  35. 35                "\t./keyApp /dev/key\n"
  36. 36                 );
  37. 37         return -1;
  38. 38     }
  39. 39
  40. 40     /* 打开设备 */
  41. 41     fd = open(argv[1], O_RDONLY);
  42. 42     if(0 > fd) {
  43. 43         printf("ERROR: %s file open failed!\n", argv[1]);
  44. 44         return -1;
  45. 45     }
  46. 46
  47. 47     /* 循环读取按键数据 */
  48. 48     for ( ; ; ) {
  49. 49
  50. 50         read(fd, &key_val, sizeof(int));
  51. 51         if (0x0 == key_val)
  52. 52             printf("PS_KEY0 Press, value = 0x%x\n", key_val);
  53. 53     }
  54. 54
  55. 55     /* 关闭设备 */
  56. 56     close(fd);
  57. 57     return 0;
  58. 58 }
复制代码

        第48~53行,循环读取/dev/key文件,也就是循环读取按键值,如果读取到的值为0,则表示是一次有效的按键(按下之后松开标记为一次有效状态),并打印信息出来。
31.4运行测试
31.4.1编译驱动程序和测试APP
1、编译驱动程序
编写Makefile文件,将10_mutex实验目录下的Makefile文件拷贝到本实验目录下,打开Makefile文件,将obj-m变量的值改为key.o,修改完之后Makefile内容如下所示:
示例代码31.4.1.1 Makefile.c文件内容
  1. 1 KERN_DIR := /home/zynq/linux/kernel/linux-xlnx-xilinx-v2018.3
  2.   2
  3.   3 obj-m := key.o
  4.   4
  5.   5 all:
  6.   6         make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERN_DIR) M=`pwd` modules
  7.   7
  8.   8 clean:
  9.   9         make -C $(KERN_DIR) M=`pwd` clean
复制代码

        第3行,设置obj-m变量的值为key.o。
Makefile文件修改完成之后保存退出,在本实验目录下输入如下命令编译出驱动模块文件:
  1. make
复制代码

编译成功以后就会生成一个名为“key.ko”的驱动模块文件。
2、编译测试APP
输入如下命令编译测试keyApp.c这个测试程序:
  1. arm-linux-gnueabihf-gcc keyApp.c -o keyApp
复制代码

编译成功以后就会生成keyApp这个应用程序。
31.4.2运行测试
将上一小节编译出来的key.ko和keyApp这两个文件拷贝到开发板根文件系统/lib/modules/4.14.0-xilinx目录中,重启开发板,进入到目录/lib/modules/4.14.0-xilinx中,输入如下命令加载key.ko驱动模块:
  1. depmod                                //第一次加载驱动的时候需要运行此命令
  2. modprobe key.ko                //加载驱动
复制代码

如下所示:
阿莫论坛发帖领航者专用111701.png

图 42.4.1 加载key驱动模块

驱动加载成功以后如下命令来测试:
  1. ./keyApp /dev/key
复制代码

按下开发板上的PS_KEY0按键,keyApp就会获取并且输出按键信息,如图 42.4.2所示:
阿莫论坛发帖领航者专用111873.png

图 42.4.2 打印按键值

从图 42.4.2可以看出,当我们按下PS_KEY0再松开以后就会打印出“KEY0 Press, value = 0x0”,表示这是一次完整的按键按下、松开事件。但是大家在测试过程可能会发现,有时候按下PS_KEY0会输出好几行“KEY0 Press, value = 0x0”,这是因为我们的代码没有做按键消抖处理,是属于正常情况。
如果要卸载驱动的话输入如下命令即可:
  1. rmmod key.ko
复制代码

阿莫论坛20周年了!感谢大家的支持与爱护!!

曾经有一段真挚的爱情摆在我的面前,我没有珍惜,现在想起来,还好我没有珍惜……
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片。注意:要连续压缩2次才能满足要求!!】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-8-25 14:13

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表