OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 348|回复: 0

《DFZU2EG_4EV MPSoC开发板之嵌入式Linux 驱动开发指南》第二十六章 Linux按键输入实验

[复制链接]

1050

主题

1061

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4363
金钱
4363
注册时间
2019-5-8
在线时间
1193 小时
发表于 2024-3-12 15:25:06 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2024-3-11 15:59 编辑

第二十六章 Linux按键输入实验
1)实验平台:正点原子 DFZU2EG_4EV MPSoC开发板

2) 章节摘自【正点原子】DFZU2EG_4EV MPSoC开发板之嵌入式Linux 驱动开发指南 V1.0

3)购买链接:https://detail.tmall.com/item.htm?id=692450874670

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz-MPSOC.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)Linux技术交流QQ群:887820935

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

在前几章我们都是使用的GPIO输出功能,还没有用过GPIO输入功能,本章我们就来学习一下如何在Linux下编写GPIO输入驱动程序。ZYNQ MPSoC开发板上有4个用户按键,本章我们就以PS_KEY1按键为例,使用此按键来完成GPIO输入驱动程序,同时利用第二十五章讲的互斥锁来对按键值进行保护。

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

image003.png
图 26.2.2 PS_KEY按键引脚
从原理图可知,当PS_KEY1按键按下时,对应的管脚MIO40为低电平状态,松开的时候MIO40为高电平状态,所以可以通过读取MIO40管脚的电平状态来判断按键是否被按下或松开!
1.3 实验程序编写
本实验对应的例程路径为:开发板光盘资料(A盘)\4_SourceCode\3_Embedded_Linux\Linux驱动例程\10_key。
1.3.1 修改设备树文件
打开system-user.dtsi文件,在根节点“/”下创建一个按键节点,节点名为“key”,节点内容如下:
  1. 示例代码26.3.1.1创建key节点
  2. 19 key {
  3. 20     compatible = "alientek,key";
  4. 21     status = "okay";
  5. 22     key-gpio = <&gpio 40 GPIO_ACTIVE_LOW>;
  6. 23 };
复制代码
这个节点内容很简单。
第20行,设置节点的compatible属性为“alientek,key”。
第22行,key-gpio属性指定了PS_KEY1按键所使用的GPIO。
设备树编写完成以后使用,在linux内核源码目录下执行下面这条命令重新编译设备树:
  1. make dtbs
复制代码
image005.png
图 26.3.1 重新编译设备树
然后将新编译出来的system-top.dtb文件重命名为system.dtb,将system.dtb文件拷贝到SD启动卡的fat分区,替换以前的system.dtb文件(先将sd卡中原来的system.dtb文件删除),替换完成插入开发板,然后开发板重新上电启动。启动成功以后进入“/proc/device-tree”目录中查看“key”节点是否存在,如果存在的话就说明设备树基本修改成功(具体还要驱动验证),结果如图 26.3.2所示:
image007.png
图 26.3.2 key节点
1.3.2 按键驱动程序编写
设备树准备好以后就可以编写驱动程序了,在drivers目录下新建名为“10_key”的文件夹,然后在10_key文件夹里面新建一个名为key.c的源文件,在key.c里面输入如下内容:
  1. 示例代码26.3.2.1key.c文件代码
  2. 1   /***************************************************************
  3. 2  Copyright &#169; ALIENTEK Co.,Ltd. 1998-2029. All rights reserved.
  4. 3  文件名    :key.c
  5. 4  作者      : 邓涛
  6. 5  版本      : V1.0
  7. 6  描述      : Linux按键输入驱动实验
  8. 7  其他      : 无
  9. 8  论坛      :www.openedv.com
  10. 9   日志      : 初版V1.0 2019/1/30 邓涛创建
  11. 10 ***************************************************************/
  12. 11  
  13. 12  #include <linux/types.h>
  14. 13  #include <linux/kernel.h>
  15. 14  #include <linux/delay.h>
  16. 15  #include <linux/ide.h>
  17. 16  #include <linux/init.h>
  18. 17  #include <linux/module.h>
  19. 18  #include <linux/errno.h>
  20. 19  #include <linux/gpio.h>
  21. 20  #include <asm/uaccess.h>
  22. 21  #include <asm/io.h>
  23. 22  #include <linux/cdev.h>
  24. 23  #include <linux/of.h>
  25. 24  #include <linux/of_address.h>
  26. 25  #include <linux/of_gpio.h>
  27. 26  
  28. 27  #defineKEY_CNT     1       /* 设备号个数 */
  29. 28  #defineKEY_NAME    "key"   /*名字 */
  30. 29  
  31. 30  /* dtskey设备结构体 */
  32. 31  struct key_dev {
  33. 32      dev_t devid;               /* 设备号 */
  34. 33      struct cdev cdev;          /* cdev */
  35. 34      struct class *class;          /*类 */
  36. 35      struct device *device;       /* 设备 */
  37. 36      intmajor;                /* 主设备号*/
  38. 37      intminor;                /* 次设备号*/
  39. 38      struct device_node *nd;      /*设备节点 */
  40. 39      intkey_gpio;              /* GPIO编号*/
  41. 40      intkey_val;               /* 按键值*/
  42. 41      struct mutex mutex;         /* 互斥锁 */
  43. 42  };
  44. 43  
  45. 44  static struct key_dev key;      /* led设备 */
  46. 45  
  47. 46  /*
  48. 47   * @description     : 打开设备
  49. 48   * @param – inode : 传递给驱动的inode
  50. 49   * @param – filp  : 设备文件,file结构体有个叫做private_data的成员变量
  51. 50   *                    一般在open的时候将private_data指向设备结构体。
  52. 51   * @return          : 0 成功;其他 失败
  53. 52   */
  54. 53  static int key_open(struct inode *inode,struct file *filp)
  55. 54  {
  56. 55      return 0;
  57. 56  }
  58. 57  
  59. 58  /*
  60. 59   * @description     : 从设备读取数据
  61. 60   * @param – filp    : 要打开的设备文件(文件描述符)
  62. 61   * @param – buf    : 返回给用户空间的数据缓冲区
  63. 62   * @param – cnt    : 要读取的数据长度
  64. 63   * @param – offt    : 相对于文件首地址的偏移
  65. 64   * @return         : 读取的字节数,如果为负值,表示读取失败
  66. 65   */
  67. 66  static ssize_t key_read(struct file *filp,char __user *buf,
  68. 67              size_t cnt, loff_t *offt)
  69. 68  {
  70. 69      intret = 0;
  71. 70  
  72. 71      /* 互斥锁上锁 */
  73. 72      if(mutex_lock_interruptible(&key.mutex))
  74. 73          return -ERESTARTSYS;
  75. 74  
  76. 75      /* 读取按键数据 */
  77. 76      if(!gpio_get_value(key.key_gpio)) {
  78. 77          while(!gpio_get_value(key.key_gpio));
  79. 78          key.key_val= 0x0;
  80. 79      }else
  81. 80          key.key_val= 0xFF;
  82. 81  
  83. 82      /* 将按键数据发送给应用程序 */
  84. 83      ret =copy_to_user(buf, &key.key_val, sizeof(int));
  85. 84  
  86. 85      /* 解锁 */
  87. 86      mutex_unlock(&key.mutex);
  88. 87  
  89. 88      return ret;
  90. 89  }
  91. 90  
  92. 91  /*
  93. 92   * @description     : 向设备写数据
  94. 93   * @param – filp    : 设备文件,表示打开的文件描述符
  95. 94   * @param – buf    : 要写给设备写入的数据
  96. 95   * @param – cnt    : 要写入的数据长度
  97. 96   * @param – offt    : 相对于文件首地址的偏移
  98. 97   * @return         : 写入的字节数,如果为负值,表示写入失败
  99. 98   */
  100. 99  static ssize_t key_write(struct file *filp,const char __user *buf,
  101. 100             size_t cnt, loff_t *offt)
  102. 101 {
  103. 102     return 0;
  104. 103 }
  105. 104
  106. 105 /*
  107. 106  * @description     : 关闭/释放设备
  108. 107  * @param – filp    : 要关闭的设备文件(文件描述符)
  109. 108  * @return         : 0 成功;其他 失败
  110. 109  */
  111. 110 static int key_release(struct inode *inode,struct file *filp)
  112. 111 {
  113. 112     return 0;
  114. 113 }
  115. 114
  116. 115 /* 设备操作函数*/
  117. 116 static struct file_operations key_fops ={
  118. 117     .owner     =THIS_MODULE,
  119. 118     .open      = key_open,
  120. 119     .read      = key_read,
  121. 120     .write      =key_write,
  122. 121     .release    =key_release,
  123. 122 };
  124. 123
  125. 124 static int __init mykey_init(void)
  126. 125 {
  127. 126     const char *str;
  128. 127     intret;
  129. 128
  130. 129     /* 初始化互斥锁 */
  131. 130     mutex_init(&key.mutex);
  132. 131
  133. 132     /* 1.获取key节点*/
  134. 133     key.nd= of_find_node_by_path("/key");
  135. 134     if(NULL == key.nd){
  136. 135         printk(KERN_ERR"key: Failed to get keynode\n");
  137. 136         return -EINVAL;
  138. 137     }
  139. 138
  140. 139     /* 2.读取status属性*/
  141. 140     ret =of_property_read_string(key.nd, "status",&str);
  142. 141     if(!ret) {
  143. 142         if(strcmp(str, "okay"))
  144. 143             return -EINVAL;
  145. 144     }
  146. 145
  147. 146     /* 3.获取compatible属性值并进行匹配 */
  148. 147     ret =of_property_read_string(key.nd, "compatible",&str);
  149. 148     if(ret) {
  150. 149         printk(KERN_ERR"key: Failed to get compatibleproperty\n");
  151. 150         return ret;
  152. 151     }
  153. 152
  154. 153     if(strcmp(str, "alientek,key")){
  155. 154         printk(KERN_ERR"key: Compatible matchfailed\n");
  156. 155         return -EINVAL;
  157. 156     }
  158. 157
  159. 158     printk(KERN_INFO"key: device matchingsuccessful!\r\n");
  160. 159
  161. 160     /* 4.获取设备树中的key-gpio属性,得到按键所使用的GPIO编号 */
  162. 161     key.key_gpio= of_get_named_gpio(key.nd,"key-gpio",0);
  163. 162     if(!gpio_is_valid(key.key_gpio)){
  164. 163         printk(KERN_ERR"key: Failed to getkey-gpio\n");
  165. 164         return -EINVAL;
  166. 165     }
  167. 166
  168. 167     printk(KERN_INFO"key: key-gpio num =%d\r\n", key.key_gpio);
  169. 168
  170. 169     /* 5.申请GPIO */
  171. 170     ret =gpio_request(key.key_gpio, "Key Gpio");
  172. 171     if(ret) {
  173. 172         printk(KERN_ERR"key: Failed to requestkey-gpio\n");
  174. 173         return ret;
  175. 174     }
  176. 175
  177. 176     /* 6.将GPIO设置为输入模式 */
  178. 177    gpio_direction_input(key.key_gpio);
  179. 178
  180. 179     /* 7.注册字符设备驱动 */
  181. 180      /* 创建设备号 */
  182. 181     if(key.major) {
  183. 182         key.devid= MKDEV(key.major, 0);
  184. 183         ret =register_chrdev_region(key.devid, KEY_CNT,KEY_NAME);
  185. 184         if(ret)
  186. 185             goto out1;
  187. 186     }else {
  188. 187         ret =alloc_chrdev_region(&key.devid, 0, KEY_CNT, KEY_NAME);
  189. 188         if(ret)
  190. 189             goto out1;
  191. 190
  192. 191         key.major= MAJOR(key.devid);
  193. 192         key.minor= MINOR(key.devid);
  194. 193     }
  195. 194
  196. 195     printk(KERN_INFO"key: major=%d,minor=%d\r\n", key.major,key.minor);
  197. 196
  198. 197      /* 初始化cdev */
  199. 198     key.cdev.owner= THIS_MODULE;
  200. 199     cdev_init(&key.cdev, &key_fops);
  201. 200
  202. 201      /* 添加cdev */
  203. 202     ret =cdev_add(&key.cdev, key.devid,KEY_CNT);
  204. 203     if(ret)
  205. 204         goto out2;
  206. 205
  207. 206      /* 创建类 */
  208. 207     key.class= class_create(THIS_MODULE, KEY_NAME);
  209. 208     if(IS_ERR(key.class)) {
  210. 209         ret =PTR_ERR(key.class);
  211. 210         goto out3;
  212. 211     }
  213. 212
  214. 213      /* 创建设备 */
  215. 214     key.device= device_create(key.class, NULL,
  216. 215                 key.devid,NULL, KEY_NAME);
  217. 216     if(IS_ERR(key.device)) {
  218. 217         ret =PTR_ERR(key.device);
  219. 218         goto out4;
  220. 219     }
  221. 220
  222. 221     return 0;
  223. 222
  224. 223 out4:
  225. 224     class_destroy(key.class);
  226. 225
  227. 226 out3:
  228. 227     cdev_del(&key.cdev);
  229. 228
  230. 229 out2:
  231. 230    unregister_chrdev_region(key.devid, KEY_CNT);
  232. 231
  233. 232 out1:
  234. 233     gpio_free(key.key_gpio);
  235. 234
  236. 235     return ret;
  237. 236 }
  238. 237
  239. 238 static void __exit mykey_exit(void)
  240. 239 {
  241. 240     /* 注销设备 */
  242. 241     device_destroy(key.class,key.devid);
  243. 242
  244. 243     /* 注销类 */
  245. 244     class_destroy(key.class);
  246. 245
  247. 246     /* 删除cdev */
  248. 247     cdev_del(&key.cdev);
  249. 248
  250. 249     /* 注销设备号 */
  251. 250    unregister_chrdev_region(key.devid, KEY_CNT);
  252. 251
  253. 252     /* 释放GPIO */
  254. 253     gpio_free(key.key_gpio);
  255. 254 }
  256. 255
  257. 256 /* 驱动模块入口和出口函数注册 */
  258. 257 module_init(mykey_init);
  259. 258 module_exit(mykey_exit);
  260. 259
  261. 260 MODULE_AUTHOR("DengTao <773904075@qq.com>");
  262. 261 MODULE_DESCRIPTION("AlientekGpio Key Driver");
  263. 262 MODULE_LICENSE("GPL");
复制代码
第31~42行,结构体key_dev为按键的设备结构体,第39行的key_gpio表示按键对应的GPIO编号,第40行key_val用来保存读取到的按键值,第41行定义了一个互斥锁变量mutex,用来保护按键读取过程。
第66~89行,在key_read函数中,通过gpio_get_value函数读取按键值,如果当前为按下状态,则使用while循环等待按键松开,松开之后将key_val变量置为0x0,从按键按下状态到松开状态视为一次有效状态;如果当前为松开状态,则将key_val变量置为0xFF,表示为无效状态。使用copy_to_user函数将key_val值发送给上层应用;第72行,调用mutex_lock_interruptible函数上锁(互斥锁),第86行解锁,对整个读取按键过程进行保护,因为在于用于保存按键值的key_val是一个全局变量,如果上层有多个应用对按键进行了读取操作,将会出现第二十五章说到的并发访问,这对系统来说是不利的,所以这里使用了互斥锁进行了保护。应用程序通过read函数读取按键值的时候key_read函数就会执行!
第130行,调用mutex_init函数初始化互斥锁。
第177行,调用gpio_direction_input函数将按键对应的GPIO设置为输入模式。
key.c文件代码很简单,重点就是key_read函数读取按键值,要对读取过程进行保护。

1.3.3 编写测试APP
在本章实验目录下新建名为keyApp.c的文件,然后输入如下所示内容:
  1. 示例代码26.3.3.1keyApp.c文件代码
  2. 1 #include <stdio.h>
  3. 2 #include <unistd.h>
  4. 3 #include <sys/types.h>
  5. 4 #include <sys/stat.h>
  6. 5 #include <fcntl.h>
  7. 6 #include <stdlib.h>
  8. 7 #include <string.h>
  9. 8
  10. 9 /*
  11. 10  * @description         : main主程序
  12. 11  * @param - argc        : argv数组元素个数
  13. 12  * @param - argv        : 具体参数
  14. 13  * @return              : 0 成功;其他 失败
  15. 14  */
  16. 15 int main(int argc, char *argv[])
  17. 16 {
  18. 17     int fd, ret;
  19. 18     int key_val;
  20. 19
  21. 20     /*判断传参个数是否正确 */
  22. 21     if(2 != argc) {
  23. 22         printf("Usage:\n"
  24. 23                "\t./keyApp /dev/key\n"
  25. 24                 );
  26. 25         return -1;
  27. 26     }
  28. 27
  29. 28     /*打开设备 */
  30. 29     fd = open(argv[1], O_RDONLY);
  31. 30     if(0 > fd) {
  32. 31         printf("ERROR:%s file open failed!\n", argv[1]);
  33. 32         return -1;
  34. 33     }
  35. 34
  36. 35     /*循环读取按键数据 */
  37. 36     for ( ; ; ) {
  38. 37
  39. 38         read(fd, &key_val, sizeof(int));
  40. 39         if (0x0 == key_val)
  41. 40             printf("PS_KEY1Press, value = 0x%x\n", key_val);
  42. 41     }
  43. 42
  44. 43     /*关闭设备 */
  45. 44     close(fd);
  46. 45     return 0;
  47. 46 }
复制代码
第36~41行,循环读取/dev/key文件,也就是循环读取按键值,如果读取到的值为0,则表示是一次有效的按键(按下之后松开标记为一次有效状态),并打印信息出来。

1.4 运行测试
1.4.1 编译驱动程序和测试APP
1、编译驱动程序
编写Makefile文件,将9_mutex实验目录下的Makefile文件拷贝到本实验目录下,打开Makefile文件,将obj-m变量的值改为key.o,修改完之后Makefile内容如下所示:
  1. 示例代码26.4.1.1Makefile.c文件内容
  2. KERN_DIR:= /home/shang/git.d/linux-xlnx
  3. obj-m:= key.o
  4. all:
  5.        make -C $(KERN_DIR) M=`pwd` modules
  6. clean:
  7.        make -C $(KERN_DIR) M=`pwd` clean
复制代码
Makefile文件修改完成之后保存退出,在本实验目录下输入如下命令编译出驱动模块文件:
  1. make
复制代码
编译成功以后就会生成一个名为“key.ko”的驱动模块文件。
2、编译测试APP
输入如下命令编译测试keyApp.c这个测试程序:
  1. $CC keyApp.c -o keyApp
复制代码
编译成功以后就会生成keyApp这个应用程序。
1.4.2 运行测试
使用scp命令将上一小节编译出来的key.ko和keyApp这两个文件拷贝到开发板根文件系统/lib/modules/4.19.0目录中,重启开发板,进入到目录/lib/modules/4.19.0中,输入如下命令加载key.ko驱动模块:
  1. depmod                           //第一次加载驱动的时候需要运行此命令
  2. modprobe key.ko              //加载驱动
复制代码
如下所示:
image009.png
图 26.4.1 加载key驱动模块
驱动加载成功以后如下命令来测试:
  1. ./keyApp /dev/key
复制代码
按下开发板上的PS_KEY1按键,keyApp就会获取并且输出按键信息,如下图所示:
image011.png
图 26.4.2 打印按键值
从上图可以看出,当我们按下PS_KEY1再松开以后就会打印出“PS_KEY1 Press, value = 0x0”,表示这是一次完整的按键按下、松开事件。但是大家在测试过程可能会发现,有时候按下PS_KEY1会输出好几行“PS_KEY1 Press, value = 0x0”,这是因为我们的代码没有做按键消抖处理,是属于正常情况。
如果要卸载驱动的话输入如下命令即可:
  1. rmmod key.ko
复制代码
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-5-14 22:32

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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