OpenEdv-开源电子网

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

[XILINX] 【正点原子FPGA连载】第二十八章 Linux按键输入实验--摘自【正点原子】领航者ZYNQ之Linux开发指南_V1.3

[复制链接]

1084

主题

1095

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4497
金钱
4497
注册时间
2019-5-8
在线时间
1203 小时
发表于 2020-12-24 16:23:02 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2020-12-24 16:23 编辑

1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-301505-1-1.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:712557122
5)关注正点原子公众号,获取最新资料更新
1.jpg
1120.png

第二十八章 Linux按键输入实验

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

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

image002.jpg

image004.jpg

29.2.1 按键原理图
从原理图可知,当PS_KEY0按键按下时,对应的管脚MIO12为低电平状态,松开的时候MIO12为高电平状态,所以可以通过读取MIO管脚的电平状态来判断按键是否被按下或松开!
1.3 实验程序编写
本实验对应的例程路径为:ZYNQ开发板光盘资料(A盘)\4_SourceCode\ZYNQ_7010\3_Embedded_Linux\Linux驱动例程\11_key。
1.3.1 修改设备树文件
打开system-top.dts文件,在根节点“/”下创建一个按键节点,节点名为“key”,节点内容如下:
示例代码31.3.1.1 创建key节点
  1. key {
  2.      compatible = "alientek,key";
  3.      status = "okay";
  4.      key-gpio = <&gpio0 12 GPIO_ACTIVE_LOW>;
  5. };
复制代码
这个节点内容很简单。
第50行,设置节点的compatible属性为“alientek,key”。
第52行,key-gpio属性指定了PS_KEY0按键所使用的GPIO。
设备树编写完成以后使用,在linux内核源码目录下执行下面这条命令重新编译设备树:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-system-top.dtb
image006.jpg

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

29.3.2 key节点
1.3.2 按键驱动程序编写
设备树准备好以后就可以编写驱动程序了,在drivers目录下新建名为“11_key”的文件夹,然后在11_key文件夹里面新建一个名为key.c的源文件,在key.c里面输入如下内容:
示例代码31.3.2.1 key.c文件代码

  1. ***************************************************************
  2.   Copyright ? ALIENTEK Co., Ltd. 1998-2029. Allrights reserved.
  3.   文件名    : key.c
  4.   作者      : 邓涛
  5.   版本      : V1.0
  6.   描述      : Linux按键输入驱动实验
  7.   其他      : 无
  8. 论坛      : www.openedv.com
  9.   日志      : 初版V1.0 2019/1/30 邓涛创建
  10. ***************************************************************/

  11. #include <linux/types.h>
  12. #include <linux/kernel.h>
  13. #include <linux/delay.h>
  14. #include <linux/ide.h>
  15. #include <linux/init.h>
  16. #include <linux/module.h>
  17. #include <linux/errno.h>
  18. #include <linux/gpio.h>
  19. #include <asm/mach/map.h>
  20. #include <asm/uaccess.h>
  21. #include <asm/io.h>
  22. #include <linux/cdev.h>
  23. #include <linux/of.h>
  24. #include <linux/of_address.h>
  25. #include <linux/of_gpio.h>

  26. #define KEY_CNT        1            /* 设备号个数 */
  27. #define KEY_NAME            "key"      /* 名字 */

  28. /* dtsled设备结构体 */
  29. struct key_dev {
  30.     dev_t devid;                        /* 设备号 */
  31.     struct cdev cdev;                  /* cdev */
  32.     struct class *class;        /* 类 */
  33.     struct device *device;    /* 设备*/
  34.     int major;                            /* 主设备号 */
  35.     int minor;                                  /* 次设备号 */
  36.     struct device_node *nd; /* 设备节点*/
  37.     int key_gpio;                       /* GPIO编号 */
  38.     int key_val;                         /* 按键值 */
  39.     struct mutex mutex;             /* 互斥锁 */
  40. };

  41. static struct key_dev key; /* led设备 */

  42. /*
  43. *@description           : 打开设备
  44. * @param– inode       : 传递给驱动的inode
  45. * @param– filp          : 设备文件,file结构体有个叫做private_data的成员变量
  46. *                                        一般在open的时候将private_data指向设备结构体。
  47. *@return                         : 0 成功;其他 失败
  48. */
  49. static int key_open(struct inode *inode, struct file *filp)
  50. {
  51.     return 0;
  52. }

  53. /*
  54. *@description           : 从设备读取数据
  55. * @param– filp          : 要打开的设备文件(文件描述符)
  56. * @param– buf          : 返回给用户空间的数据缓冲区
  57. * @param– cnt          : 要读取的数据长度
  58. * @param– offt          : 相对于文件首地址的偏移
  59. *@return                  : 读取的字节数,如果为负值,表示读取失败
  60. */
  61. static ssize_t key_read(struct file *filp, char __user *buf,
  62.             size_t cnt, loff_t *offt)
  63. {
  64.     int ret = 0;

  65.     /*互斥锁上锁 */
  66.     if (mutex_lock_interruptible(&key.mutex))
  67.         return -ERESTARTSYS;

  68.     /*读取按键数据 */
  69.     if (!gpio_get_value(key.key_gpio)) {
  70.         while(!gpio_get_value(key.key_gpio));
  71.         key.key_val = 0x0;
  72.     } else
  73.         key.key_val = 0xFF;

  74.     /*将按键数据发送给应用程序 */
  75.     ret = copy_to_user(buf, &key.key_val, sizeof(int));

  76.     /*解锁 */
  77.     mutex_unlock(&key.mutex);

  78.     return ret;
  79. }

  80. /*
  81. *@description           : 向设备写数据
  82. * @param– filp          : 设备文件,表示打开的文件描述符
  83. * @param– buf          : 要写给设备写入的数据
  84. * @param– cnt          : 要写入的数据长度
  85. * @param– offt          : 相对于文件首地址的偏移
  86. *@return                         : 写入的字节数,如果为负值,表示写入失败
  87. */
  88. static ssize_t key_write(struct file *filp, const char __user *buf,
  89.              size_t cnt, loff_t *offt)
  90. {
  91.      return 0;
  92. }

  93. /*
  94.   * @description           : 关闭/释放设备
  95.   * @param – filp          : 要关闭的设备文件(文件描述符)
  96. * @return                         : 0 成功;其他 失败
  97.   */
  98. static int key_release(struct inode *inode, struct file *filp)
  99. {
  100.      return 0;
  101. }

  102. /* 设备操作函数 */
  103. static struct file_operationskey_fops = {
  104.      .owner           = THIS_MODULE,
  105.      .open             = key_open,
  106.      .read       = key_read,
  107.      .write            = key_write,
  108.      .release   = key_release,
  109. };

  110. static int __init mykey_init(void)
  111. {
  112.      const char *str;
  113.      int ret;

  114.      /*初始化互斥锁 */
  115.      mutex_init(&key.mutex);

  116.      /*1.获取key节点 */
  117.      key.nd = of_find_node_by_path("/key");
  118.      if(NULL == key.nd) {
  119.          printk(KERN_ERR "key:Failed to get key node\n");
  120.          return -EINVAL;
  121.      }

  122.      /*2.读取status属性 */
  123.      ret = of_property_read_string(key.nd, "status", &str);
  124.      if(!ret) {
  125.          if (strcmp(str, "okay"))
  126.              return -EINVAL;
  127.      }

  128.      /*3.获取compatible属性值并进行匹配 */
  129.      ret = of_property_read_string(key.nd, "compatible", &str);
  130.      if(ret) {
  131.          printk(KERN_ERR "key:Failed to get compatible property\n");
  132.          return ret;
  133.      }

  134.      if (strcmp(str, "alientek,key")) {
  135.          printk(KERN_ERR "key:Compatible match failed\n");
  136.          return -EINVAL;
  137.      }

  138.      printk(KERN_INFO "key:device matching successful!\r\n");

  139.      /*4.获取设备树中的key-gpio属性,得到按键所使用的GPIO编号*/
  140.      key.key_gpio = of_get_named_gpio(key.nd, "key-gpio", 0);
  141.      if(!gpio_is_valid(key.key_gpio)) {
  142.          printk(KERN_ERR "key: Failedto get key-gpio\n");
  143.          return -EINVAL;
  144.      }

  145.      printk(KERN_INFO "key:key-gpio num = %d\r\n", key.key_gpio);

  146.      /*5.申请GPIO */
  147.      ret = gpio_request(key.key_gpio, "Key Gpio");
  148.      if (ret) {
  149.          printk(KERN_ERR "key:Failed to request key-gpio\n");
  150.          return ret;
  151.      }

  152.      /*6.将GPIO设置为输入模式 */
  153.      gpio_direction_input(key.key_gpio);

  154.      /*7.注册字符设备驱动 */
  155.       /* 创建设备号 */
  156.      if (key.major) {
  157.         key.devid = MKDEV(key.major, 0);
  158.          ret = register_chrdev_region(key.devid, KEY_CNT, KEY_NAME);
  159.          if (ret)
  160.              goto out1;
  161.      } else {
  162.          ret = alloc_chrdev_region(&key.devid, 0, KEY_CNT, KEY_NAME);
  163.          if (ret)
  164.              goto out1;

  165.          key.major = MAJOR(key.devid);
  166.          key.minor = MINOR(key.devid);
  167.      }

  168.      printk(KERN_INFO "key:major=%d, minor=%d\r\n", key.major, key.minor);

  169.       /* 初始化cdev */
  170.      key.cdev.owner = THIS_MODULE;
  171.      cdev_init(&key.cdev, &key_fops);

  172.       /* 添加cdev */
  173.      ret = cdev_add(&key.cdev, key.devid, KEY_CNT);
  174.      if (ret)
  175.          goto out2;

  176.       /* 创建类 */
  177.      key.class = class_create(THIS_MODULE, KEY_NAME);
  178.      if (IS_ERR(key.class)) {
  179.          ret = PTR_ERR(key.class);
  180.          goto out3;
  181.      }

  182.       /* 创建设备 */
  183.      key.device = device_create(key.class, NULL,
  184.                  key.devid, NULL, KEY_NAME);
  185.      if (IS_ERR(key.device)) {
  186.          ret = PTR_ERR(key.device);
  187.          goto out4;
  188.      }

  189.      return 0;

  190. out4:
  191.      class_destroy(key.class);

  192. out3:
  193.      cdev_del(&key.cdev);

  194. out2:
  195.      unregister_chrdev_region(key.devid, KEY_CNT);

  196. out1:
  197.      gpio_free(key.key_gpio);

  198.      return ret;
  199. }

  200. static void __exit mykey_exit(void)
  201. {
  202.      /*注销设备 */
  203.      device_destroy(key.class, key.devid);

  204.      /*注销类 */
  205.      class_destroy(key.class);

  206.      /*删除cdev */
  207.      cdev_del(&key.cdev);

  208.      /*注销设备号 */
  209.      unregister_chrdev_region(key.devid, KEY_CNT);

  210.      /*释放GPIO */
  211.      gpio_free(key.key_gpio);
  212. }

  213. /* 驱动模块入口和出口函数注册 */
  214. module_init(mykey_init);
  215. module_exit(mykey_exit);

  216. MODULE_AUTHOR("DengTao<773904075@qq.com>");
  217. MODULE_DESCRIPTION("AlientekGpio Key Driver");
  218. 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函数读取按键值,要对读取过程进行保护。
1.3.3 编写测试APP
在本章实验目录下新建名为keyApp.c的文件,然后输入如下所示内容:
示例代码31.3.3.1 keyApp.c文件代码

  1. /***************************************************************
  2.   Copyright ? ALIENTEK Co., Ltd. 1998-2029. Allrights reserved.
  3.   文件名        : keyApp.c
  4.   作者          : 邓涛
  5.   版本          : V1.0
  6.   描述          : 按键测试应用程序
  7.   其他          : 无
  8.   使用方法      : ./keyApp /dev/key
  9.   论坛          : www.openedv.com
  10.   日志          : 初版V1.0 2019/1/30 邓涛创建
  11. ***************************************************************/

  12. #include <stdio.h>
  13. #include <unistd.h>
  14. #include <sys/types.h>
  15. #include <sys/stat.h>
  16. #include <fcntl.h>
  17. #include <stdlib.h>
  18. #include <string.h>

  19. /*
  20.   * @description         : main主程序
  21.   * @param - argc        : argv数组元素个数
  22.   * @param - argv        : 具体参数
  23.   * @return              : 0 成功;其他 失败
  24.   */
  25. int main(int argc, char *argv[])
  26. {
  27.      int fd, ret;
  28.      int key_val;

  29.      /* 判断传参个数是否正确 */
  30.      if(2 != argc) {
  31.          printf("Usage:\n"
  32.                 "\t./keyApp/dev/key\n"
  33.                  );
  34.          return -1;
  35.      }

  36.      /* 打开设备 */
  37.      fd = open(argv[1],O_RDONLY);
  38.      if(0 > fd) {
  39.          printf("ERROR:%s file open failed!\n", argv[1]);
  40.          return -1;
  41.      }

  42.      /* 循环读取按键数据 */
  43.      for ( ; ; ) {

  44.          read(fd, &key_val, sizeof(int));
  45.          if (0x0 ==key_val)
  46.              printf("PS_KEY0Press, value = 0x%x\n", key_val);
  47.      }

  48.      /* 关闭设备 */
  49.      close(fd);
  50.      return 0;
  51. }
复制代码
第48~53行,循环读取/dev/key文件,也就是循环读取按键值,如果读取到的值为0,则表示是一次有效的按键(按下之后松开标记为一次有效状态),并打印信息出来。
1.4 运行测试1.4.1 编译驱动程序和测试APP
1、编译驱动程序
编写Makefile文件,将10_mutex实验目录下的Makefile文件拷贝到本实验目录下,打开Makefile文件,将obj-m变量的值改为key.o,修改完之后Makefile内容如下所示:
示例代码31.4.1.1 Makefile.c文件内容

  1. KERN_DIR := /home/zynq/linux/kernel/linux-xlnx-xilinx-v2018.3

  2. obj-m := key.o

  3. all:
  4.      make ARCH=armCROSS_COMPILE=arm-linux-gnueabihf- -C $(KERN_DIR) M=`pwd`modules

  5. clean:
  6.      make -C $(KERN_DIR) M=`pwd`clean
复制代码
第3行,设置obj-m变量的值为key.o。
Makefile文件修改完成之后保存退出,在本实验目录下输入如下命令编译出驱动模块文件:
make
编译成功以后就会生成一个名为“key.ko”的驱动模块文件。
2、编译测试APP
输入如下命令编译测试keyApp.c这个测试程序:
arm-linux-gnueabihf-gcc keyApp.c -o keyApp
编译成功以后就会生成keyApp这个应用程序。
1.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          //加载驱动
复制代码
如下所示:
image010.jpg

29.4.1 加载key驱动模块
驱动加载成功以后如下命令来测试:
  1. ./keyApp /dev/key
复制代码
按下开发板上的PS_KEY0按键,keyApp就会获取并且输出按键信息,如图 42.4.2所示:
image012.jpg

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




正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

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

本版积分规则



关闭

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

正点原子公众号

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

GMT+8, 2024-6-26 12:34

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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