7| Linux按键输入驱动实验
Linux按键输入驱动实验
1|设备树修改
I.MX6U-ALPHA 开发板上的 KEY 使用了 UART1_CTS_B 这个 PIN,打开 imx6ull-lxg-emmc.dts,在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_key”的子节点,节点内容如下所示:
pinctrl_key: keygrp {
fsl,pins = <
MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080
>;
};
在根节点“/”下创建 KEY 节点,节点名为“key”,节点内容如下:
key {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
status = "okay";
};
然后检查 PIN 是否被其他外设使用
设备树编写完成以后使用“make dtbs”命令重新编译设备树,然后使用新编译出来的imx6ull-lxg-emmc.dtb 文件启动 Linux 系统。
启动成功以后进入“/proc/device-tree”目录中查看“key”节点是否存在,如果存在的话就说明设备树基本修改成功(具体还要驱动验证),结果如图 所示:
2|按键驱动程序编写
设备树准备好以后就可以编写驱动程序了,新建名为“11_key”的文件夹,然后在 11_key文件夹里面创建 vscode 工程,工作区命名为“key”。工程创建好以后新建 key.c 文件,在 key.c里面输入如下内容:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
/* 定义按键值 */
#define KEY0VALUE 0XF0 /* 按键值 */
#define INVAKEY 0X00 /* 无效的按键值 */
/* 设备号个数 */
#define KEYDEV_DEV_NUM 1
/* keydev设备结构体 */
struct key_dev {
struct cdev cdev;
dev_t devid;
struct class *class;
struct device *device;
int major;
int minor;
char *devname;
struct device_node *nd; //设备树节点
int key_gpio; //led所使用的GPIO编号
atomic_t keyvalue; /* 按键值 */
};
static struct key_dev keydev;
static int keyio_init(void)
{
keydev.nd = of_find_node_by_path("/key");
if(keydev.nd == NULL){
return -EINVAL;
}
keydev.key_gpio = of_get_named_gpio(keydev.nd,"key-gpios", 0);
if(keydev.key_gpio < 0){
printk("kernel:can't get key\r\n");
return -EINVAL;
}
printk("kernel:key_gpio=%d\r\n", keydev.key_gpio);
/* 初始化 key 所使用的 IO */
gpio_request(keydev.key_gpio, "key0"); /* 请求 IO */
gpio_direction_input(keydev.key_gpio); /* 设置为输入 */
return 0;
}
static int key_open(struct inode *inode, struct file *filp)
{
int ret = 0;
filp->private_data = &keydev; /* 设置私有数据 */
ret = keyio_init();
if(ret < 0){
return ret;
}
return 0;
}
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
struct key_dev *dev = filp->private_data;
unsigned char keyvalue;
if (gpio_get_value(dev->key_gpio) == 0) { /* key0 按下 */
while(!gpio_get_value(dev->key_gpio)); /* 等待按键释放 */
atomic_set(&dev->keyvalue, KEY0VALUE);
} else { /* 无效的按键值 */
atomic_set(&dev->keyvalue, INVAKEY);
}
keyvalue = atomic_read(&dev->keyvalue); /* 保存按键值 */
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
return ret;
}
static ssize_t key_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
static int key_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* 设备操作集合 */
static const struct file_operations key_fops = {
.owner = THIS_MODULE,
.write = key_write,
.read = key_read,
.open = key_open,
.release = key_release
};
static int __init mykey_init(void)
{
int ret = 0;
int result = 0;
/* 初始化原子变量 */
atomic_set(&keydev.keyvalue, INVAKEY);
/* 注册设备号 */
keydev.devname = "key";
keydev.major = 0;
if(keydev.major){
keydev.devid = MKDEV(keydev.major, keydev.minor);
ret = register_chrdev_region(keydev.devid, KEYDEV_DEV_NUM, keydev.devname);
}
else{
ret = alloc_chrdev_region(&keydev.devid, 0, KEYDEV_DEV_NUM, keydev.devname);
}
keydev.major = MAJOR(keydev.devid);
keydev.minor = MINOR(keydev.devid);
if(ret < 0){
printk("register devid failed!\r\n");
result = -EINVAL;
goto fail_register_devid;
}
printk("keydev MAJOR:%d MINOR:%d\r\n", keydev.major, keydev.minor);
/* 添加字符设备 */
keydev.cdev.owner = key_fops.owner;
cdev_init(&keydev.cdev, &key_fops);
ret = cdev_add(&keydev.cdev, keydev.devid, KEYDEV_DEV_NUM);
if(ret < 0){
printk("register chrdev failed!\r\n");
result = -EINVAL;
goto fail_register_cdev;
}
/* 创建设备节点 */
/* 1.创建类 */
keydev.class = class_create(THIS_MODULE, keydev.devname);
if(IS_ERR(keydev.class)){
printk("fail to create class!\r\n");
result = PTR_ERR(keydev.class);
goto fail_class;
}
/* 2.创建设备*/
keydev.device = device_create(keydev.class, NULL, keydev.devid, NULL, keydev.devname);
if(IS_ERR(keydev.device)){
printk("fail to create device!\r\n");
result = PTR_ERR(keydev.device);
goto fail_device;
}
return 0;
fail_device:
/* 摧毁类 */
class_destroy(keydev.class);
fail_class:
/* 注销字符设备 */
cdev_del(&keydev.cdev);
fail_register_cdev:
/* 注销设备号 */
unregister_chrdev_region(keydev.devid, KEYDEV_DEV_NUM);
fail_register_devid:
return result;
}
static void __exit mykey_exit(void)
{
/* 摧毁设备 */
device_destroy(keydev.class, keydev.devid);
/* 摧毁类 */
class_destroy(keydev.class);
/* 注销字符设备 */
cdev_del(&keydev.cdev);
/* 注销设备号 */
unregister_chrdev_region(keydev.devid, KEYDEV_DEV_NUM);
}
/* 驱动入口和出口 */
module_init(mykey_init);
module_exit(mykey_exit);
/* 许可 */
MODULE_LICENSE("GPL");
/* 作者信息 */
MODULE_AUTHOR("LXG@firestaradmin");
结构体 key_dev 为按键的设备结构体,原子变量 keyvalue 用于记录按键值。
函数 keyio_init 用于初始化按键,从设备树中获取按键的 gpio 信息,然后设置为输入。将按键的初始化代码提取出来,将其作为独立的一个函数有利于提高程序的模块化设计。
key_open 函数通过调用 keyio_init 函数来始化按键所使用的 IO,应用程序每次打开按键驱动文件的时候都会初始化一次按键 IO。
key_read 函数,应用程序通过 read 函数读取按键值的时候此函数就会执行。读取按键 IO 的电平,如果为 0 的话就表示按键按下了,如果按键按下的话就等待按键释放。按键释放以后标记按键值
驱动入口函数,调用 atomic_set 函数初始化原子变量默认为无效值。
key.c 文件代码很简单,重点就是 key_read 函数读取按键值,要对 keyvalue 进行保护。
3|测试APP编写
新建名为 keyApp.c 的文件,然后输入如下所示内容:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define KEY0VALUE 0XF0
#define INVAKEY 0X00
/*
* @description : main 主程序
* @param - argc : argv 数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int ret = 0, fd = 0;
char *filename;
unsigned char keyvalue;
if(argc != 2)
{
printf("bad usage!\r\n");
printf("usage:\r\n./keyApp <openFileName> \r\n");
return -1;
}
filename = argv[1];
/* 打开 key 驱动 */
fd = open(filename, O_RDWR);
if(fd < 0){
printf("can't open file %s\r\n", filename);
return -1;
}
/* 循环读取按键值数据! */
while(1){
read(fd, &keyvalue, sizeof(keyvalue));
if(keyvalue == KEY0VALUE){ /* KEY0 */
printf("KEY0 Press, value = %#X\r\n", keyvalue); /* 按下 */
}
}
/* 关闭 */
ret = close(fd);
if(ret < 0){
printf("close file %s failed!\r\n", filename);
return -1;
}
return 0;
}
循环读取/dev/key 文件,也就是循环读取按键值,并且将按键值打印出来。
4|运行测试
编译,加载,运行。
驱动加载成功以后如下命令来测试:
./keyApp /dev/key
输入上述命令以后,按下开发板的KEY0按键, 终端显示如图 49.4.2.1 所示:
当我们按下 KEY0 以后就会打印出“KEY0 Press, value = 0XF0”,表示按键按下。
但是大家可能会发现,有时候按下一次 KEY0 但是会输出好几行“KEY0 Press,value = 0XF0”,这是因为我们的代码没有做按键消抖处理。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!