一、基础知识
读取键值的时候,首先判断有没有按键按下,如果没有,则睡眠
ret = wait_event_interruptible(dev->r_wait,atomic_read(&dev->releasekey));
按键按下,唤醒驱动
wake_up(&dev->r_wait);
二、按键驱动
#include <linux/ide.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/of_irq.h>
#include <linux/poll.h>
/********************************************************************
* key.c
* 作者:张亚胜
* 版本:V1.0
* 描述:非阻塞io读取按键
* 其他:无
* 网站:www.s123.xyz
* 日志:出版V1.0 2020/8/8 张亚胜创建
* ******************************************************************/
#define KEY_CNT 1 /* 设备号个数 */
#define KEY_NAME "noblockiokey" /* 设备名字 */
/* 定义按键值 */
#define KEY0VALUE 0X01 /* 按键值 */
#define INVAKEY 0Xff /* 无效键值 */
#define KEY_NUM 1 /* 按键数量 */
/* 中断IO描述结构体 */
struct irq_keydesc{
int gpio; /* gpio编号 */
int irqnum; /* 中断号 */
unsigned char value; /* 按键对应的键值 */
char name[10]; /* 名字 */
irqreturn_t (*handler)(int,void *); /* 中断服务函数 */
};
/* keydev设备结构体 */
struct key_dev{
dev_t dev_id; /* 设备号 */
struct cdev cdev; /* 字符设备 */
struct class *class; /* mdev 自动节点文件使用 */
struct device *device; /* class使用 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
int key_gpio; /* key gpio 编号 */
atomic_t keyvalue; /* 按键值 */
atomic_t releasekey; /* 标记是否完成一次按键操作 */
struct timer_list timer;/* 定义一个定时器 */
struct irq_keydesc irqkeydesc[KEY_NUM];/* 按键描述数组 */
wait_queue_head_t r_wait; /* 读等待队列头 */
};
struct key_dev keydev; /* key设备 */
//中断服务函数
//处理方法:开定时器,定时10ms,用于去抖
//参数:中断号 设备结构
//返回值:中断执行结果
static irqreturn_t key0_handler(int arg,void *dev_id)
{
struct key_dev *dev = (struct key_dev *)dev_id;
dev->timer.data = (volatile long)dev_id;
mod_timer(&dev->timer,jiffies+msecs_to_jiffies(10));
printk("key irq\r\n");
return IRQ_RETVAL(IRQ_HANDLED);
}
//定时器服务函数
//用于按键去抖,定时时间到了以后,再次读取按键值,如果按键还是处于按下状态就表示按键有效
//参数:设备结构体
void timer_function(unsigned long arg)
{
unsigned char value=0;
struct key_dev *dev = (struct key_dev *)arg;
value = gpio_get_value(dev->irqkeydesc[0].gpio);
if(value == 0){
atomic_set(&dev->keyvalue,dev->irqkeydesc[0].value);//按键按下
}else{
atomic_set(&dev->keyvalue,0x80|(dev->irqkeydesc[0].value));//最高位置一 表示按键有效
atomic_set(&dev->releasekey,1);//按下再松开 算是一次按键操作
}
/* 唤醒进程 */
if(atomic_read(&dev->releasekey)){
wake_up(&dev->r_wait);
}
}
static int keyio_init(void)
{
int ret;
unsigned char i=0;
/* 获取设备节点 */
keydev.nd = of_find_node_by_path("/key");
if(keydev.nd == NULL){
printk("keydev node can not found!\r\n");
return -EINVAL;
}
/* 获取gpio属性 得到led的gpio编号 */
for (i = 0; i < KEY_NUM; i++)
{
keydev.irqkeydesc[i].gpio = of_get_named_gpio(keydev.nd,"key-gpio",i);
if(keydev.irqkeydesc[i].gpio < 0){
printk("key-gpio get failed!\r\n");
}else {
printk("key-gpio = %d\r\n",keydev.irqkeydesc[i].gpio);
}
}
/* 初始化key所使用的IO,并且设置成中断模式 */
for(i=0;i<KEY_NUM;i++)
{
memset(keydev.irqkeydesc[i].name,0,sizeof(keydev.irqkeydesc[i].name));
sprintf(keydev.irqkeydesc[i].name,"KEY%d",i);
gpio_request(keydev.irqkeydesc[i].gpio,keydev.irqkeydesc[i].name);
gpio_direction_input(keydev.irqkeydesc[i].gpio);
keydev.irqkeydesc[i].irqnum = irq_of_parse_and_map(keydev.nd,i);//获取中断号
#if 0
keydev.irqkeydesc[i].irqnum = gpio_to_irq(keydev.irqkeydesc[i].gpio);
#endif
printk("key%d:gpio=%d,irqnum=%d\r\n",i,keydev.irqkeydesc[i].gpio,keydev.irqkeydesc[i].irqnum);
}
/* 申请中断 */
keydev.irqkeydesc[0].handler = key0_handler;
keydev.irqkeydesc[0].value = KEY0VALUE;
for(i=0;i<KEY_NUM;i++)
{
ret = request_irq(keydev.irqkeydesc[i].irqnum,keydev.irqkeydesc[i].handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,keydev.irqkeydesc[i].name,&keydev);
if(ret<0){
printk("irq %d request failed!\r\n",keydev.irqkeydesc[i].irqnum);
goto fail_irq;
}
}
/* 创建定时器 */
init_timer(&keydev.timer);
keydev.timer.function = timer_function;
return 0;
fail_irq:
for(i=0;i<KEY_NUM;i++)
{
gpio_free(keydev.irqkeydesc[i].gpio);
}
return ret;
}
static int key_open(struct inode *inode,struct file *filp)
{
filp->private_data = &keydev;
return 0;
}
static ssize_t key_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt)
{
int ret=0;
unsigned char keyvalue=0;
unsigned char releasekey=0;
struct key_dev *dev = (struct key_dev *)filp->private_data;
if(filp->f_flags & O_NONBLOCK){ /* 非阻塞访问 */
if(atomic_read(&dev->releasekey) == 0){ /* 没有按键按下 */
return -EAGAIN;
}
} else {
ret = wait_event_interruptible(dev->r_wait,atomic_read(&dev->releasekey));
if(ret){
return ret;
}
}
#if 0
/* 等待事件 */
wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); /* 等待按键有效 */
#endif
#if 0
DECLARE_WAITQUEUE(wait,current);//定义一个等待队列
if(atomic_read(&dev->releasekey) == 0){//没有按键按下
add_wait_queue(&dev->r_wait,&wait);//添加到等待队列头
__set_current_state(TASK_INTERRUPTIBLE);//设置任务状态
schedule();//进行一次任务切换
if(signal_pending(current)){//判断是否为信号引起的唤醒
ret = -ERESTARTSYS;
goto wait_error;
}
__set_current_state(TASK_RUNNING);//设置为运行状态
remove_wait_queue(&dev->r_wait,&wait);//将等待队列移除
}
#endif
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
//key down
if(releasekey){
atomic_set(&dev->releasekey,0);//按下标志清零
if(keyvalue & 0x80 ){
keyvalue &= ~0x80;
if(keyvalue == KEY0VALUE){
ret = copy_to_user(buf,&keyvalue,sizeof(keyvalue));
} else {
goto data_error;
}
} else {
goto data_error;
}
} else {
goto data_error;
}
//读取按键这里报错 原因是这里忘记返回0了
return 0;
#if 0
set_current_state(TASK_RUNNING);
remove_wait_queue(&dev->r_wait,&wait);
#endif
data_error:
return -EINVAL;
}
unsigned int imx6uirq_poll(struct file *filp,struct poll_table_struct *wait)
{
int mask=0;
struct key_dev *dev = filp->private_data;
poll_wait(filp, &dev->r_wait, wait);
/* 是否可读 */
if(atomic_read(&dev->releasekey)){//按键按下 可读
mask = POLLIN | POLLRDNORM;//返回POLLIN
}
return mask;
}
static struct file_operations keydev_fops = {
.owner = THIS_MODULE,
.open = key_open,
.read = key_read,
.poll = imx6uirq_poll,
};
//key_init已经被使用了
static int __init key_init1(void)
{
int ret=0;
/* 注册字符设备驱动 */
if(keydev.major){
keydev.dev_id = MKDEV(keydev.major,0);//构建设备号 子设备号一般设置0
ret = register_chrdev_region(keydev.dev_id,KEY_CNT,KEY_NAME);
}else{
ret = alloc_chrdev_region(&keydev.dev_id,0,KEY_CNT,KEY_NAME);//申请设备号
keydev.major = MAJOR(keydev.dev_id);//获取主设备号
keydev.minor = MINOR(keydev.dev_id);//获取子设备号
}
if(ret < 0){
goto fail_devid;
}
printk("keydev major=%d,minor=%d\r\n",keydev.major,keydev.minor);
/* 初始化cdev */
keydev.cdev.owner = THIS_MODULE;
cdev_init(&keydev.cdev,&keydev_fops);//初始化cdev并绑定设备操作集合
/* 添加一个cdev */
ret=cdev_add(&keydev.cdev,keydev.dev_id,KEY_CNT);
if(ret)
goto fail_cdevadd;
/* 创建类 */
keydev.class = class_create(THIS_MODULE,KEY_NAME);
if(IS_ERR(keydev.class)){
ret = PTR_ERR(keydev.class);
goto fail_class;
}
/* 创建设备节点 mknod 向类中添加设备 */
keydev.device = device_create(keydev.class,NULL,keydev.dev_id,NULL,KEY_NAME);//成功后就会生成/dev/KEY_NAME这个设备文件
if(IS_ERR(keydev.device)){
ret = PTR_ERR(keydev.device);
goto fail_device;
}
ret = keyio_init();
if(ret < 0){
goto fail_keyinit;
}
/* 初始化原子变量 */
atomic_set(&keydev.keyvalue,INVAKEY);
atomic_set(&keydev.releasekey,INVAKEY);
/* 初始化队列头 */
init_waitqueue_head(&keydev.r_wait);
return 0;
fail_keyinit:
fail_device:
class_destroy(keydev.class);
fail_class:
cdev_del(&keydev.cdev);
fail_cdevadd:
unregister_chrdev_region(keydev.dev_id,KEY_CNT);
fail_devid:
return ret;
}
static void __exit key_exit(void)
{
unsigned char i=0;
/* 删除定时器 */
del_timer_sync(&keydev.timer);
/* 释放中断 */
for(i=0;i<KEY_NUM;i++)
{
free_irq(keydev.irqkeydesc[i].irqnum,&keydev);
}
/* 释放gpio */
for(i=0;i<KEY_NUM;i++)
{
gpio_free(keydev.irqkeydesc[i].gpio);
}
/* 注销字符设备 */
cdev_del(&keydev.cdev);
/* 释放设备号 不管是申请的还是注册的 都用下面打函数释放 */
unregister_chrdev_region(keydev.dev_id,KEY_CNT);
/* 删除设备节点 */
device_destroy(keydev.class,keydev.dev_id);
/* 删除类 */
class_destroy(keydev.class);
}
module_init(key_init1);
module_exit(key_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zys");
三、测试app
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <poll.h>
/********************************************************************
* 文件名:noblockioApp.c
* 作者:张亚胜
* 版本:V1.0
* 描述:按键输入测试程序
* 其他:使用方法:./noblockioApp /dev/noblockiokey
* 网站:www.s123.xyz
* 日志:出版V1.0 2020/8/8 张亚胜创建
* ******************************************************************/
/*
* @description : main 主程序
* @param - argc : argv 数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd,ret;
char *filename;
unsigned char data;
struct pollfd fds;
fd_set readfds;
struct timeval timeout;
if(argc != 2){
printf("params err!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename,O_RDWR | O_NONBLOCK);//非阻塞打开
if(fd<0){
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
#if 0
while(1)
{
fds.fd = fd;
fds.events = POLLIN;
ret = poll(&fds,1,500);
if(ret == 0){
//超时
}else if(ret < 0){
//错误
}else{
if(fds.revents | POLLIN)
{
ret = read(fd,&data,sizeof(data));
if(data)
printf("key0 press,val=%#X\r\n",data);
}
}
}
#else
while(1)
{
FD_ZERO(&readfds);
FD_SET(fd,&readfds);
/* 构造超时时间 */
timeout.tv_sec = 0;
timeout.tv_usec = 500000;//500ms
ret = select(fd+1,&readfds,NULL,NULL,&timeout);
switch (ret)
{
case 0://超时
break;
case -1://错误
break;
default://可以读取数据
if(FD_ISSET(fd,&readfds)){
ret = read(fd,&data,sizeof(data));
if(ret<0){
//读取错误
}else{
if(data){
printf("key0 press,val=%#X\r\n",data);
}
}
}
break;
}
}
#endif
ret = close(fd);
if(ret < 0){
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return ret;
}
四、测试