linux驱动开发 | 非阻塞io 按键驱动

一、基础知识

读取键值的时候,首先判断有没有按键按下,如果没有,则睡眠

 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;
}

四、测试

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值