Linux 按键输入实验
1、添加 pinctrl 节点
首先修改在设备树里面添加关于按键的节点。I.MX6U-ALPHA 开发板上的 KEY 使用了 UART1_CTS_B 这个 PIN,打开 imx6ull-alientekemmc.dts,在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_key”的子节点,节点内容如下所示:
pinctrl_key: keygrp {
fsl,pins = <
MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* KEY0 */
>;
};
2、 添加key设备节点:
在根节点“/”下创建 KEY 节点,节点名为“key”,节点内容如下:
key {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */
status = "okay";
};
pinctrl-0 属性设置 KEY 所使用的 PIN 对应的 pinctrl 节点。key-gpio 属性指定了 KEY 所使用的 GPIO。
3、检查 PIN 是否被其他外设使用
在本章实验中蜂鸣器使用的 PIN 为 UART1_CTS_B,因此先检查 PIN 为 UART1_CTS_B 这个 PIN 有没有被其他的 pinctrl 节点使用,如果有使用的话就要屏蔽掉,然后再检查 GPIO1_IO18这个 GPIO 有没有被其他外设使用,如果有的话也要屏蔽掉。
按键驱动和 LED 驱动原理上来讲基本都是一样的,都是操作 GPIO,只不过一个是读取GPIO 的高低电平,一个是从 GPIO 输出高低电平。本章我们实现按键输入,在驱动程序中使用一个整形变量来表示按键值,应用程序通过 read 函数来读取按键值,判断按键有没有按下。
按键驱动程序为:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define KEY_CNT 1 /* 设备号个数 */
#define KEY_NAME "key" /* 名字 */
/* 定义按键值 */
#define KEY0VALUE 0XF0 /* 按键值 */
#define INVAKEY 0X00 /* 无效的按键值 */
struct key_dev
{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
int key_gpio; /* led 所使用的 GPIO 编号 */
atomic_t keyvalue; /* 原子变量 */
};
struct key_dev keydev; /* led 设备 */
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-gpio", 0);
if (keydev.key_gpio < 0)
{
printk("can't get key0\r\n");
return -EINVAL;
}
printk("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(); /* 初始化按键 IO */
if (ret < 0)
{
return ret;
}
return 0;
}
/*
* @description : 从设备读取数据
* @param – filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param – offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char value;
struct key_dev *dev = filp->private_data;
if(gpio_get_value(dev->key_gpio) == 0)
{
while(!gpio_get_value(dev->key_gpio));
atomic_set(&dev->keyvalue,KEY0VALUE);
}
else{
atomic_set(&dev->keyvalue,INVAKEY);
}
value = atomic_read(&dev->keyvalue);
ret = copy_to_user(buf,&value,sizeof(value));
return ret;
}
/* 设备操作函数 */
static struct file_operations key_fops = {
.owner = THIS_MODULE,
.open = key_open,
.read = key_read,
};
static int __init mykey_init(void)
{
int ret = 0;
atomic_set(&keydev.keyvalue, INVAKEY);
/*1、创建设备号*/
if (keydev.major)
{
keydev.devid = MKDEV(keydev.major, 0);
register_chrdev_region(keydev.devid, KEY_CNT, KEY_NAME);
}
else
{
alloc_chrdev_region(&keydev.devid, 0, KEY_CNT, KEY_NAME);
keydev.major = MAJOR(keydev.devid);
keydev.minor = MINOR(keydev.devid);
}
printk("newcheled major: %d minor: %d", keydev.major, keydev.minor);
keydev.cdev.owner = THIS_MODULE;
cdev_init(&keydev.cdev, &key_fops);
cdev_add(&keydev.cdev, keydev.devid, KEY_CNT);
keydev.class = class_create(THIS_MODULE, KEY_NAME);
if (IS_ERR(keydev.class))
{
return PTR_ERR(keydev.class);
}
keydev.device = device_create(keydev.class, NULL, keydev.devid, NULL, KEY_NAME);
if (IS_ERR(keydev.device))
{
return PTR_ERR(keydev.device);
}
return 0;
}
static void __exit mykey_exit(void)
{
/* 注销字符设备驱动 */
gpio_free(keydev.key_gpio);
cdev_del(&keydev.cdev); /* 删除 cdev */
unregister_chrdev_region(keydev.devid,KEY_CNT);
device_destroy(keydev.class, keydev.devid);
class_destroy(keydev.class);
}
module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wyw");
其实按键与LED相似的就是一个是读一个是写。按键则是读取高低电平。也就是检测这个GPIO编号时候,利用原子变量写入key->value这个值。
key_open 函数通过调用 keyio_init 函数来始化按键所使用的 IO,应用程序每次打开按键驱动文件的时候都会初始化一次按键 IO。key_read 函数,应用程序通过 read 函数读取按键值的时候此函数就会执行。读取按键 IO 的电平,如果为 0 的话就表示按键按下了,如果按键按下的话就等待按键释放。按键释放以后标记按键值为 KEY0VALUE。
测试App程序为:
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#define KEY0VALUE 0XF0
#define INVAKEY 0X00
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char keyvalue;
if (argc != 2)
{
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0)
{
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
while (1)
{
read(fd, &keyvalue, sizeof(keyvalue));
if(keyvalue == KEY0VALUE)
{
printf("KEY0 Press, value = %#X\r\n", keyvalue);/* 按下 */
}
}
printf("App running finished!");
retvalue = close(fd); /* 关闭文件 */
if (retvalue < 0)
{
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}
现在就是按键按下终端输出如下图所示的现象: