Linux驱动开发十.中断——3.按键产生的外部中断和用户APP交互


现在我们已经能够使用GPIO外设产生一个外部中断了,下面要做的就是如何在用户APP里获取到中断产生的信息。

用户APP的思路是在while循环里一直调用read函数从内核里读取驱动的信息,内核在文件被read的时候向用户态APP传递数据。驱动程序在每次按键按下后触发中断,中断通过定时器消除按键抖动后调用定时器回调函数。在定时器对回调函数内我们对按键的值以及按键状态进行标记,在文件操作集合绑定的read对应函数里通过按键标记进行相关操作。下面就对代码的改动部分进行相关说明

设备结构体

设备结构体里主要是添加了两个原子变量用来描述按键的值和按键是否被释放的状态字。

 1 struct new_dev
 2 {
 3     dev_t dev_id;
 4     int major;
 5     int minor;
 6     struct class *class;
 7     struct device *device;
 8     struct cdev cdev;
 9     struct device_node *dev_nd;
10     int dev_gpio;
11 
12     struct irq_keydesc irqkey[KEY_NUM];         //按键描述数组
13 
14     struct timer_list timer;    //定时器
15     int timer_per;
16 
17     atomic_t keyvalue;          //按键值
18     atomic_t keyreleased;       //1表示1次有效按键并被释放,0表示未被释放
19 };

就是第17行和18行。因为中断和用户态APP可能会同时写入和读取这两个变量的值,所以使用了原子变量来防止并发导致的竞争现象。keyvalue表示按键的值,keyreleased表示按键否按下被释放,如果按键触发中断,该变量值置0,释放后将该变量置一。

定时器回调函数

定时器回调函数是在中断触发定时器后定时器超时执行的。为了简化描述,下面我们简化定时器回调函数的触发过程,用中断触发来描述这个过程。

 1 timer_func(unsigned long arg){
 2 
 3     int value = 0;
 4     struct new_dev *dev =(struct new_dev*)arg;
 5 
 6     value = gpio_get_value(dev->irqkey[0].gpio);
 7     if(value == 0){     
 8         //按下
 9         atomic_set(&dev->keyvalue,dev->irqkey[0].value);
10     }
11     else{
12         //释放
13         atomic_set(&dev->keyvalue,0x80|(dev->irqkey[0].value)); //将最高位置一,表示按钮释放
14         atomic_set(&dev->keyreleased,1);
15     }
16 }

因为我们使用了双边沿触发外部中断,在按键状态改变时,读取gpio的状态给value,按键按下时,value值为0,执行if条件里的语句,将原子变量keyvalue的值设置为指定的值。按键抬起时value值变成1,执行else里内容,我们将keyvalue的值最高位置个1(这个过程有各种思路,只要和后面read函数里的过程自洽就可以了);同时将keyreleased置1,表示出现了按键被按下然后被释放掉过程。

内核态read函数

内核态read函数在用户态APP出现文件read时执行。

 1 static ssize_t new_dev_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt)
 2 {
 3     int ret = 0;
 4     unsigned char keyvalue;
 5     unsigned char keyreleased;
 6     struct new_dev *dev = filp->private_data;       //私有数据
 7 
 8     keyvalue = atomic_read(&dev->keyvalue);         //获取按键状态标志及按键值
 9     keyreleased = atomic_read(&dev->keyreleased);
10 
11     if(keyreleased){                                //出现按键被按下并释放的过程
12         if(keyvalue&0x80){
13             //获取最高位,如果为1表示出现过按键被按下后释放掉状态,是有效状态
14             keyvalue &= ~0x80;
15             ret = copy_to_user(buf,&keyvalue,sizeof(keyvalue));
16         }
17         else{ 
18             goto data_err;                          
19         }
20         atomic_set(&dev->keyreleased,0);            //恢复keyreleased的状态
21     }
22     else{ 
23         goto data_err;                              //返回负数,在用户态跳出循环
24     }
25 
26     return ret;
27 
28     data_err:
29         return -EINVAL;
30 };

在第8、9行,获取了按键的值和状态标志。如果发生过按键按下并释放的过程,keyreleased的值为1,执行第11行内的if条件内语句。然后用了另一个if条件来判断按键值keyvalue的值的最高位是否为1(或了个0x80取最高位,和前面要自洽的地方闭环了),如果最高位为1,对应确实发生了按键按下然后释放的过程,因为开始将最高位置一,通过第14行的代码获取到置一前的值(0x80取反后与运算),然后用copy_to_user函数将按键值发送给用户态APP。最后要通过20行的代码恢复原子变量keyreleased的值至0。

用户态APP

用户态APP里很简单,就是在while循环里调用read函数

/**
 * @file irqAPP.c
 * @author your name (you@domain.com)
 * @brief 按键中断APP测试程序
 * @version 0.1
 * @date 2022-07-26
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>

/**
 * @brief 
 * 
 * @param argc                      //参数个数 
 * @param argv                      //参数
 * @return int 
 */
int main(int argc,char *argv[])
{
    char *filename;                 //文件名
    filename = argv[1];             //
    int value = 0;
    int ret = 0;                    //初始化操作返回值
    int f = 0;                      //初始化文件句柄

    unsigned char data;             //内核传来的值

    f = open(filename, O_RDWR);     //打开文件
    if(f < 0){
    printf("file open error/r/n");
    return -1;
    }

    while(1){
        ret = read(f,&data,sizeof(data));
        if(ret<0){
            //读取错误
            printf("data read err/r/n")
        }
        else{
            if(data)               //获取数据有效
                printf("key value = %#X/r/n",data);
        }
        }
    
    close(f);                       //关闭文件
    return 0;
}

编译完成以后,可以试一下

Linux驱动开发十.中断——3.按键产生的外部中断和用户APP交互

这里相当于每次按下按键后没什么事情发生,但是弹起按键后会触发一个printf事件。最后放出来整体的驱动代码

Linux驱动开发十.中断——3.按键产生的外部中断和用户APP交互

/**
 * @file irq.c
 * @author your name (you@domain.com)
 * @brief 按键中断
 * @version 0.1
 * @date 2022-07-24
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/irq.h>
#include <linux/interrupt.h>

#define DEVICE_CNT      1
#define DEVICE_NAME    "imx6uirq"

#define KEY_NUM         1
#define KEY0VALUE       0x01
#define INVALKEYS       0xFF

/**
 * @brief 按键中断结构体 
 * 
 */
struct irq_keydesc {
    int gpio;                           //io编号
    int irqnum;                         //中断号
    unsigned char value;                //键值
    char name[10];                      //按键名字
    irqreturn_t (*handler)(int,void*);  //中断处理函数  
};

/**
 * @brief 设备结构体
 * 
 */
struct new_dev
{
    dev_t dev_id;
    int major;
    int minor;
    struct class *class;
    struct device *device;
    struct cdev cdev;
    struct device_node *dev_nd;
    int dev_gpio;

    struct irq_keydesc irqkey[KEY_NUM];         //按键描述数组

    struct timer_list timer;    //定时器
    int timer_per;

    atomic_t keyvalue;          //
    atomic_t keyreleased;       //1表示1次有效按键并被释放,0表示未被释放
};

struct new_dev new_dev;

static int new_dev_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &new_dev;     /* 设置私有数据 */
    return 0;
}

static ssize_t new_dev_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt)
{
    int ret = 0;
    unsigned char keyvalue;
    unsigned char keyreleased;
    struct new_dev *dev = filp->private_data;       //私有数据

    keyvalue = atomic_read(&dev->keyvalue);         //获取按键状态标志及按键值
    keyreleased = atomic_read(&dev->keyreleased);

    if(keyreleased){                                //出现按键被按下并释放的过程
        if(keyvalue&0x80){
            //获取最高位,如果为1表示出现过按键被按下后释放掉状态,是有效状态
            keyvalue &= ~0x80;
            ret = copy_to_user(buf,&keyvalue,sizeof(keyvalue));
        }
        else{ 
            goto data_err;                          
        }
        atomic_set(&dev->keyreleased,0);            //恢复keyreleased的状态
    }
    else{ 
        goto data_err;                              //返回负数,在用户态跳出循环
    }

    return ret;

    data_err:
        return -EINVAL;
};

/**
 * @brief 文件操作集合
 * 
 */
static const struct file_operations key_fops = {
    .owner = THIS_MODULE,
    // .write = new_dev_write,
    .open =  new_dev_open,
    .read = new_dev_read,
    // .release = new_dev_release,
};




static irqreturn_t key0_handle_irq(int irq, void *dev_id)
{   
    int value = 0;
    struct new_dev *dev = dev_id;
    dev->timer.data = dev_id;
    mod_timer(&dev->timer,jiffies + msecs_to_jiffies(10));
    return IRQ_HANDLED;
}


timer_func(unsigned long arg){

    int value = 0;
    struct new_dev *dev =(struct new_dev*)arg;

    value = gpio_get_value(dev->irqkey[0].gpio);
    if(value == 0){     
        //按下
        atomic_set(&dev->keyvalue,dev->irqkey[0].value);
    }
    else{
        //释放
        atomic_set(&dev->keyvalue,0x80|(dev->irqkey[0].value)); //将最高位置一,表示按钮释放
        atomic_set(&dev->keyreleased,1);
    }
}

static int dev_gpio_init(struct new_dev *dev)
{
    int ret = 0;
    int i = 0;

    //搜索设备树节点
    dev->dev_nd = of_find_node_by_path("/key");
    if(dev->dev_nd == NULL){
        printk("can't find device key/r/n");
        ret = -EINVAL;
        goto fail_nd;
    }

    for(i=0;i<KEY_NUM;i++)
    {   
        dev->irqkey[i].gpio = of_get_named_gpio(dev->dev_nd,"key-gpios",i); //多个按键获取
        if(dev->irqkey[i].gpio<0){
            ret = -EINVAL;
            goto fail_gpio_num;
        }
        ret = gpio_request(dev->irqkey[i].gpio,dev->irqkey[i].name);
        if(ret){
            ret = -EBUSY;
            goto fail_gpio_request;
        }
        gpio_direction_input(dev->irqkey[i].gpio);
        dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio);           //获取中断号
        // dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->dev_nd,i)        //方法2获取中断号
    }

    dev->irqkey[0].handler = key0_handle_irq;
    dev->irqkey[0].value = KEY0VALUE;

    for(i=0;i<KEY_NUM;i++){
        memset(dev->irqkey[i].name,0,sizeof(dev->irqkey[i].name));
        sprintf(dev->irqkey[i].name,"KEY%d",i);  //将格式化数据写入字符串中
        ret = request_irq(dev->irqkey[i].irqnum,                            //中断号
                            key0_handle_irq,                                //中断处理函数
                            IRQ_TYPE_EDGE_RISING|IRQ_TYPE_EDGE_FALLING,     //中断处理函数
                            dev->irqkey[i].name,                            //中断名称
                            dev                                             //设备结构体
                            );
        if(ret){
            printk("irq %d request err/r/n",dev->irqkey[i].irqnum);
            goto fail_irq;
        }
    }
    //此处不设置定时值,防止定时器add后直接运行
    init_timer(&dev->timer);
    dev->timer.function = timer_func;

    return 0;
    fail_gpio_request:
    fail_irq:
        for(i=0; i<KEY_NUM;i++){
            gpio_free(dev->irqkey[i].gpio);
        }
    fail_gpio_num:
    fail_nd:
        return ret; 
}

static int __init key_init(void){

    int ret = 0; 
    // unsigned int val = 0;
    //申请设备号
    new_dev.major = 0;
    if(new_dev.major){
        //手动指定设备号,使用指定的设备号
        new_dev.dev_id = MKDEV(new_dev.major,0);
        ret = register_chrdev_region(new_dev.dev_id,DEVICE_CNT,DEVICE_NAME);
    }
    else{
        //设备号未指定,申请设备号
        ret = alloc_chrdev_region(&new_dev.dev_id,0,DEVICE_CNT,DEVICE_NAME);
        new_dev.major = MAJOR(new_dev.dev_id);
        new_dev.minor = MINOR(new_dev.dev_id);
    }
    printk("dev id geted!/r/n");

    if(ret<0){
        //设备号申请异常,跳转至异常处理
        goto faile_devid;
    }

    //字符设备cdev初始化
    new_dev.cdev.owner = THIS_MODULE;

    cdev_init(&new_dev.cdev,&key_fops);                 //文件操作集合映射

    ret = cdev_add(&new_dev.cdev,new_dev.dev_id,DEVICE_CNT);
    if(ret<0){
        //cdev初始化异常,跳转至异常处理
        goto fail_cdev;
    }

    printk("chr dev inited!/r/n");


    //自动创建设备节点
    new_dev.class = class_create(THIS_MODULE,DEVICE_NAME);
    if(IS_ERR(new_dev.class)){
        //class创建异常处理
        printk("class err!/r/n");
        ret = PTR_ERR(new_dev.class);
        goto fail_class;
    }
    printk("dev class created/r/n");
    new_dev.device = device_create(new_dev.class,NULL,new_dev.dev_id,NULL,DEVICE_NAME);
    if(IS_ERR(new_dev.device)){
        //设备创建异常处理
        printk("device err!/r/n");
        ret = PTR_ERR(new_dev.device);
        goto fail_device;
    }
    printk("device created!/r/n");


    ret = dev_gpio_init(&new_dev);
    if(ret<0){
        goto fail_gpio_init;
    }

    //初始化原子变量
    atomic_set(&new_dev.keyvalue,INVALKEYS);
    atomic_set(&new_dev.keyreleased,0);
    return ret;


fail_gpio_init:
fail_device:
    //device创建失败,意味着class创建成功,应该将class销毁
    printk("device create err,class destroyed/r/n");
    class_destroy(new_dev.class);
fail_class:
    //类创建失败,意味着设备应该已经创建成功,此刻应将其释放掉
    printk("class create err,cdev del/r/n");
    cdev_del(&new_dev.cdev);
fail_cdev:
    //cdev初始化异常,意味着设备号已经申请完成,应将其释放
    printk("cdev init err,chrdev register/r/n");
    unregister_chrdev_region(new_dev.dev_id,DEVICE_CNT);
faile_devid:
    //设备号申请异常,由于是第一步操作,不需要进行其他处理
    printk("dev id err/r/n");
    return ret;
}

static void __exit key_exit(void){
    int i = 0;
    //释放中断
    for(i=0;i<KEY_NUM;i++){
        free_irq(new_dev.irqkey[i].irqnum,&new_dev);
    }

    for(i=0;i<KEY_NUM;i++){
        gpio_free(new_dev.irqkey[i].gpio);
    }

    del_timer_sync(&new_dev.timer);
    cdev_del(&new_dev.cdev);
    unregister_chrdev_region(new_dev.dev_id,DEVICE_CNT);

    device_destroy(new_dev.class,new_dev.dev_id);
    class_destroy(new_dev.class);

}

module_init(key_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZeqiZ");

View Code

 

原创文章,作者:,如若转载,请注明出处:https://blog.ytso.com/tech/aiops/277227.html

(0)
上一篇 2022年7月27日 00:19
下一篇 2022年7月27日 00:33

相关推荐

发表回复

登录后才能评论