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”节点是否存在,如果存在的话就说明设备树基本修改成功(具体还要驱动验证),结果如图 所示:

image-20200823175735837

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 所示:

image-20200823184134402

当我们按下 KEY0 以后就会打印出“KEY0 Press, value = 0XF0”,表示按键按下。

但是大家可能会发现,有时候按下一次 KEY0 但是会输出好几行“KEY0 Press,value = 0XF0”,这是因为我们的代码没有做按键消抖处理。