0
点赞
收藏
分享

微信扫一扫

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

Linux按键输入实验​

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


Linux下按键驱动原理

按键驱动和LED驱动原理上来讲基本都是一样的,都是操作GPIO,只不过一个是读取GPIO的高低电平,一个是从GPIO输出高低电平。本章我们实现按键输入,在驱动程序中实现read函数,读取按键值并将数据发送给上层应用测试程序,在read函数中,使用了互斥锁对读数据过程进行了保护,后面会讲解为什么使用互斥锁进行保护。Linux下的按键驱动原理很简单,接下来开始编写驱动。

注意,本章例程只是为了演示Linux下GPIO输入驱动的编写,实际中的按键驱动并不会采用本章中所讲解的方法,Linux下的input子系统专门用于输入设备!

硬件原理图分析

打开ZYNQ MPSoC底板原理图,找到PS_KEY1按键原理图,如下所示:

《DFZU2EG_4EV MPSoC之嵌入式Linux开发指南》第二十六章  Linux按键输入实验​_键值


26.2.1 PS_KEY按键原理图

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


26.2.2 PS_KEY按键引脚

从原理图可知,当PS_KEY1按键按下时,对应的管脚MIO40为低电平状态,松开的时候MIO40为高电平状态,所以可以通过读取MIO40管脚的电平状态来判断按键是否被按下或松开!

实验程序编写

本实验对应的例程路径为:开发板光盘资料(A盘)\4_SourceCode\ 3_Embedded_Linux\Linux驱动例程\10_key

修改设备树文件

打开system-user.dtsi文件,在根节点“/”下创建一个按键节点,节点名为“key”,节点内容如下:

示例代码26.3.1.1创建key节点

19 key {​
20 compatible = "alientek,key";​
21 status = "okay";​
22 key-gpio = <&gpio 40 GPIO_ACTIVE_LOW>;​
23 };

这个节点内容很简单。

第20行,设置节点的compatible属性为“alientek,key”。

第22行,key-gpio属性指定了PS_KEY1按键所使用的GPIO

设备树编写完成以后使用,在linux内核源码目录下执行下面这条命令重新编译设备树:

make dtbs

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


26.3.1 重新编译设备树

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

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


26.3.2 key节点

按键驱动程序编写

设备树准备好以后就可以编写驱动程序了,在drivers目录下新建名为“10_key”的文件夹,然后在10_key文件夹里面新建一个名为key.c的源文件,在key.c里面输入如下内容:

示例代码26.3.2.1 key.c文件代码

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

编写测试APP

在本章实验目录下新建名为keyApp.c的文件,然后输入如下所示内容:

示例代码26.3.3.1 keyApp.c文件代码

1 #include <stdio.h>​
2 #include <unistd.h>​
3 #include <sys/types.h>​
4 #include <sys/stat.h>​
5 #include <fcntl.h>​
6 #include <stdlib.h>​
7 #include <string.h>​
8 ​
9 /*​
10 * @description : main主程序​
11 * @param - argc : argv数组元素个数​
12 * @param - argv : 具体参数​
13 * @return : 0 成功;其他 失败​
14 */​
15 int main(int argc, char *argv[])​
16 {​
17 int fd, ret;​
18 int key_val;​
19 ​
20 /* 判断传参个数是否正确 */​
21 if(2 != argc) {​
22 printf("Usage:\n"​
23 "\t./keyApp /dev/key\n"​
24 );​
25 return -1;​
26 }​
27 ​
28 /* 打开设备 */​
29 fd = open(argv[1], O_RDONLY);​
30 if(0 > fd) {​
31 printf("ERROR: %s file open failed!\n", argv[1]);​
32 return -1;​
33 }​
34 ​
35 /* 循环读取按键数据 */​
36 for ( ; ; ) {​
37 ​
38 read(fd, &key_val, sizeof(int));​
39 if (0x0 == key_val)​
40 printf("PS_KEY1 Press, value = 0x%x\n", key_val);​
41 }​
42 ​
43 /* 关闭设备 */​
44 close(fd);​
45 return 0;​
46 }

36~41行,循环读取/dev/key文件,也就是循环读取按键值,如果读取到的值为0,则表示是一次有效的按键(按下之后松开标记为一次有效状态),并打印信息出来。

运行测试

编译驱动程序和测试APP

1、编译驱动程序

编写Makefile文件,将9_mutex实验目录下的Makefile文件拷贝到本实验目录下,打开Makefile文件,将obj-m变量的值改为key.o,修改完之后Makefile内容如下所示:

示例代码26.4.1.1 Makefile.c文件内容

KERN_DIR := /home/shang/git.d/linux-xlnx​
obj-m := key.o​
all:​
make -C $(KERN_DIR) M=`pwd` modules​
clean:​
make -C $(KERN_DIR) M=`pwd` clean

Makefile文件修改完成之后保存退出,在本实验目录下输入如下命令编译出驱动模块文件:

make

编译成功以后就会生成一个名为“key.ko”的驱动模块文件。

2、编译测试APP

输入如下命令编译测试keyApp.c这个测试程序:

$CC keyApp.c -o keyApp

编译成功以后就会生成keyApp这个应用程序。

运行测试

使用scp命令将上一小节编译出来的key.ko和keyApp这两个文件拷贝到开发板根文件系统/lib/modules/4.19.0目录中,重启开发板,进入到目录/lib/modules/4.19.0中,输入如下命令加载key.ko驱动模块:

depmod     //第一次加载驱动的时候需要运行此命令​
modprobe key.ko //加载驱动

如下所示:

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


26.4.1 加载key驱动模块

驱动加载成功以后如下命令来测试:

./keyApp /dev/key

按下开发板上的PS_KEY1按键,keyApp就会获取并且输出按键信息,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Linux开发指南》第二十六章  Linux按键输入实验​_键值_06


26.4.2 打印按键值

从上图可以看出,当我们按下PS_KEY1再松开以后就会打印出“PS_KEY1 Press, value = 0x0”,表示这是一次完整的按键按下、松开事件。但是大家在测试过程可能会发现,有时候按下PS_KEY1会输出好几行“PS_KEY1 Press, value = 0x0”,这是因为我们的代码没有做按键消抖处理,是属于正常情况。

如果要卸载驱动的话输入如下命令即可:

rmmod key.ko


举报

相关推荐

0 条评论