查看: 589|回复: 0

[分享] 【经验分享】i.MX6ULL开发:驱动开发8——中断法检测按键

[复制链接]
  • TA的每日心情
    开心
    2020-12-18 12:56
  • 签到天数: 55 天

    [LV.5]常住居民I

    70

    主题

    220

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    1583
    最后登录
    2024-4-19
    发表于 2022-8-23 13:46:11 | 显示全部楼层 |阅读模式
    上篇,学习GPIO输入功能的使用,本篇,来学习使用中断的方式来检测按键的按下。
    1 Linux中断介绍1.1 中断的上半部与下半部
    中断处理函数的执行,越快越好,但实际使用中,某些情况确实需要比较耗时是中断过程,为此,Linux内核将中断分为上半部和下半部两个处理部分:
    • 上半部:中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成
    • 下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出

    对于一个中断,如何划分出上下两部分呢?
    • 对时间敏感,将其放在上半部
    • 和硬件相关,将其放在上半部
    • 要求不被其他中断打断,将其放在上半部
    • 其他所有任务,考虑放在下半部

    1.2 下半部的3种实现方式1.2.1 软中断
    Linux内核使用softirq_action结构体表示软中断:
    1. <font face="Arial">struct softirq_action
    2. {
    3. void (*action)(struct softirq_action *);
    4. };</font>
    复制代码
    一共有 10 个软中断
    1. <font face="Arial">enum
    2. {
    3.    HI_SOFTIRQ = 0,          /* 高优先级软中断    */
    4.    TIMER_SOFTIRQ,           /* 定时器软中断      */
    5.    NET_TX_SOFTIRQ,          /* 网络数据发送软中断 */
    6.    NET_RX_SOFTIRQ,          /* 网络数据接收软中断 */
    7.    BLOCK_SOFTIRQ,
    8.    BLOCK_IOPOLL_SOFTIRQ,
    9.    TASKLET_SOFTIRQ,         /* tasklet 软中断   */
    10.    SCHED_SOFTIRQ,           /* 调度软中断        */
    11.    HRTIMER_SOFTIRQ,         /* 高精度定时器软中断 */
    12.    RCU_SOFTIRQ,             /* RCU 软中断       */

    13.    NR_SOFTIRQS
    14. };</font>
    复制代码
    要使用软中断,必须先使用open_softirq函数注册对应的软中断处理函数:
    1. <font face="Arial">/**
    2. * nr: 要开启的软中断
    3. * action: 软中断对应的处理函数
    4. * return: 无
    5. */
    6. void open_softirq(int nr,  void (*action)(struct softirq_action *))</font>
    复制代码
    注册好软中断以后需要通过raise_softirq函数触发:
    1. <font face="Arial">/**
    2. * nr: 要触发的软中断
    3. * return: 无
    4. */
    5. void raise_softirq(unsigned int nr)</font>
    复制代码
    1.2.2 tasklet
    Linux内核使用tasklet_struct结构体来表示tasklet:
    1. <font face="Arial">struct tasklet_struct
    2. {
    3. struct tasklet_struct *next;     /* 下一个tasklet      */
    4. unsigned long state;             /* tasklet状态        */
    5. atomic_t count;                  /* 计数器, 记录对tasklet的引用数 */
    6. void (*func)(unsigned long);     /* tasklet执行的函数  */
    7. unsigned long data;              /* 函数func的参数     */
    8. };</font>
    复制代码
    要使用 tasklet,必须先定义一个tasklet,然后初始化:
    1. <font face="Arial">/**
    2. * t: 要初始化的tasklet
    3. * func: tasklet的处理函数
    4. * data: 要传递给func函数的参数
    5. * return: 无
    6. */
    7. void tasklet_init(struct tasklet_struct *t,
    8.                void (*func)(unsigned long),
    9.                        unsigned long data);</font>
    复制代码
    在上半部(中断处理函数)中调用tasklet_schedule函数就能使tasklet在合适的时间运行:
    1. <font face="Arial">/**
    2. * t: 要调度的tasklet
    3. * return: 无
    4. */
    5. void tasklet_schedule(struct tasklet_struct *t)</font>
    复制代码
    1.2.3 工作队列
    工作队列(work queue)是另外一种将中断的部分工作推后的一种方式,它可以实现一些tasklet不能实现的工作,比如工作队列机制可以睡眠。
    Linux 内核使用work_struct结构体表示一个工作:
    1. <font face="Arial">struct work_struct {
    2.    atomic_long_t data;
    3.    struct list_head entry;
    4.    work_func_t func;        /* 工作队列处理函数  */
    5. };</font>
    复制代码
    这些工作组织成工作队列,工作队列使用workqueue_struct结构体表示。
    在工作队列机制中,将推后的工作交给一个称之为工作者线程(worker thread)的内核线程去完成。
    1.3 中断API函数1.3.1 request_irq中断请求函数
    1. <font face="Arial">/**
    2. * irq: 要申请中断的中断号
    3. * handler: 中断处理函数,当中断发生以后就会执行此中断处理函数
    4. * flags: 中断标志
    5. * name: 中断名字
    6. * dev: 设备结构体
    7. * return: 0-中断申请成功, 其他负值-中断申请失败
    8. */
    9. int request_irq(unsigned int irq,
    10.               irq_handler_t handler,
    11.               unsigned long flags,
    12.                  const char *name,
    13.                        void *dev)</font>
    复制代码
    flags中断标志,有下面几种类型
    图片 1.png
    1.3.2 free_irq中断释放函数
    1. <font face="Arial">/**
    2. * irq: 要释放中断的中断号
    3. * dev: 设备结构体
    4. * return: 无
    5. */
    6. void free_irq(unsigned int irq,
    7.                      void *dev)</font>
    复制代码
    1.3.3 irq_handler_t中断处理函数
    1. <font face="Arial">/**
    2. * int: 要处理的中断号
    3. * void *: 通用指针, 需要与request_irq函数的dev参数保持一致
    4. * return: irqreturn_t枚举类型
    5. */
    6. irqreturn_t (*irq_handler_t) (int, void *)</font>
    复制代码
    irqreturn_t枚举类型定义:
    1. <font face="Arial">enum irqreturn {
    2. IRQ_NONE          = (0 << 0),
    3.    IRQ_HANDLED       = (1 << 0),
    4. IRQ_WAKE_THREAD   = (1 << 1),
    5. };

    6. typedef enum irqreturn irqreturn_t;</font>
    复制代码
    1.3.4 中断使能/禁用函数
    1. <font face="Arial">/**
    2. * int: 要使能的中断号
    3. */
    4. void enable_irq(unsigned int irq)

    5. /**
    6. * int: 要禁用的中断号
    7. */
    8. void disable_irq(unsigned int irq)</font>
    复制代码
    1.3.5 获取中断号
    使用中断时,中断信息先写到了设备树里面,然后通过irq_of_parse_and_map函数从interupts属性中提取到对应的中断号
    1. <font face="Arial">/**
    2. * dev: 设备节点
    3. * index: 索引号
    4. * return: 中断号
    5. */
    6. unsigned int irq_of_parse_and_map(struct device_node *dev,
    7.                                                 int index)</font>
    复制代码
    2 软件编写
    仍使用上篇按键实验中用到的两个按键:
    图片 2.png
    为了理解简单,本次程序暂不实现中断的下半部逻辑,直接将整个中断处理过程都放到中断的上半部中处理。
    2.1 修改设备树文件
    在上篇key实验代码的基础上,修改imx6ull-myboard.dts,主要是修改key子节点,添加中断,修改后内容如下:
    1. <font face="Arial">key {
    2.    #address-cells = <1>;
    3.    #size-cells = <1>;
    4.    compatible = "myboard-irq-key";
    5.    pinctrl-names = "default";
    6.    pinctrl-0 = <&pinctrl_key>;
    7.    key1-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>;   /* SW2 */
    8.    key2-gpio = <&gpio5 11 GPIO_ACTIVE_LOW>;   /* SW4 */
    9.    interrupt-parent = <&gpio5>;
    10.    interrupts = <  1 IRQ_TYPE_EDGE_BOTH
    11.                   11 IRQ_TYPE_EDGE_BOTH >;
    12.    status = "okay";
    13. };</font>
    复制代码
    2.2 按键中断驱动程序2.2.1 硬件初始化与中断配置
    1. <font face="Arial">static int keyio_init(void)
    2. {
    3.    unsigned char i = 0;
    4.    int ret = 0;

    5.    /* 设备树中获取key节点 */
    6.    imx6uirq.nd = of_find_node_by_path("/key");
    7.    if (imx6uirq.nd== NULL)
    8.    {
    9.        printk("key node not find!\r\n");
    10.        return -EINVAL;
    11.    }

    12.    /* 提取GPIO */
    13.    imx6uirq.irqkeydesc[0].gpio = of_get_named_gpio(imx6uirq.nd ,"key1-gpio", 0);
    14.    imx6uirq.irqkeydesc[1].gpio = of_get_named_gpio(imx6uirq.nd ,"key2-gpio", 0);
    15.    if ((imx6uirq.irqkeydesc[0].gpio < 0)||(imx6uirq.irqkeydesc[1].gpio < 0))
    16.    {
    17.        printk("can't get key\r\n");
    18.        return -EINVAL;
    19.    }
    20.    printk("key1_gpio=%d, key2_gpio=%d\r\n", imx6uirq.irqkeydesc[0].gpio, imx6uirq.irqkeydesc[1].gpio);

    21.    /* 初始化key所使用的IO,并且设置成中断模式 */
    22.    for (i = 0; i < KEY_NUM; i++)
    23.    {
    24.        memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(imx6uirq.irqkeydesc[i].name)); /* 缓冲区清零 */
    25.        sprintf(imx6uirq.irqkeydesc[i].name, "key%d", i+1); /* 组合名字 */
    26.        gpio_request(imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].name);
    27.        gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);
    28.        imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i); /* 取到对应的中断号 */

    29.        printk("key%d:gpio=%d, irqnum=%d\r\n",i+1,
    30.                                              imx6uirq.irqkeydesc[i].gpio,
    31.                                              imx6uirq.irqkeydesc[i].irqnum);
    32.    }
    33.    /* 申请中断 */
    34.    imx6uirq.irqkeydesc[0].handler = key1_handler;
    35.    imx6uirq.irqkeydesc[1].handler = key2_handler;
    36.    imx6uirq.irqkeydesc[0].value = KEY1VALUE;
    37.    imx6uirq.irqkeydesc[1].value = KEY2VALUE;

    38.    for (i = 0; i < KEY_NUM; i++)
    39.    {
    40.        /* 中断请求函数 */
    41.        ret = request_irq(imx6uirq.irqkeydesc[i].irqnum,
    42.                          imx6uirq.irqkeydesc[i].handler,
    43.                          IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
    44.                          imx6uirq.irqkeydesc[i].name,
    45.                          &imx6uirq);
    46.        if(ret < 0)
    47.        {
    48.            printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);
    49.            return -EFAULT;
    50.        }
    51.    }

    52.    /* 创建定时器 */
    53.    init_timer(&imx6uirq.timer1);
    54.    imx6uirq.timer1.function = timer1_function;
    55.    init_timer(&imx6uirq.timer2);
    56.    imx6uirq.timer2.function = timer2_function;
    57.    return 0;
    58. }</font>
    复制代码
    中断检测到按键按下后,为了消除按键抖动,这里使用定时器来进行按键消抖,因为本次实验用到两个按键,所以就先也使用两个定时器。
    2.2.2 中断服务函数
    1. <font face="Arial">static irqreturn_t key1_handler(int irq, void *dev_id)
    2. {
    3.     struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;

    4.     dev->timer1.data = (volatile long)dev_id;
    5.     mod_timer(&dev->timer1, jiffies + msecs_to_jiffies(10)); /* 10ms定时 */

    6.     return IRQ_RETVAL(IRQ_HANDLED);
    7. }</font>
    复制代码
    中断函数检测到按键按下后,会开启一个10ms的定时器,用来按键消抖。
    2.2.3 定时器服务函数
    定时器的10ms到达之后,会触发定时器服务函数,此时再次读取按键的值,若仍为按下,则是按键真的按下了,若10ms后又检测不到按键了,则说明是按键抖动导致的按键误触发。
    1. <font face="Arial">void timer1_function(unsigned long arg)
    2. {
    3.     unsigned char value;
    4.     struct irq_keydesc *keydesc;
    5.     struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;

    6.     keydesc = &dev->irqkeydesc[0];

    7.     value = gpio_get_value(keydesc->gpio); /* 读取IO值 */
    8.     if(value == 1) /* 按下按键 */
    9.     {
    10.         printk("get key1: high\r\n");
    11.         atomic_set(&dev->keyvalue, keydesc->value);
    12.     }
    13.     else /* 按键松开 */
    14.     {
    15.         printk("key1 release\r\n");
    16.         atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
    17.         atomic_set(&dev->releasekey, 1); /* 标记松开按键,即完成一次完整的按键过程 */
    18.     }
    19. }</font>
    复制代码
    2.2.4 按键读取函数
    1. <font face="Arial">static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
    2. {
    3.     int ret = 0;
    4.     unsigned char keyvalue = 0;
    5.     unsigned char releasekey = 0;
    6.     struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

    7.     keyvalue = atomic_read(&dev->keyvalue);
    8.     releasekey = atomic_read(&dev->releasekey);

    9.     if (releasekey) /* 有按键按下 */
    10.     {
    11.         //printk("releasekey!\r\n");
    12.         if (keyvalue & 0x80)
    13.         {
    14.             keyvalue &= ~0x80;
    15.             ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
    16.         }
    17.         else
    18.         {
    19.             goto data_error;
    20.         }
    21.         atomic_set(&dev->releasekey, 0); /* 按下标志清零 */
    22.     }
    23.     else
    24.     {
    25.         goto data_error;
    26.     }
    27.     return 0;

    28. data_error:
    29.     return -EINVAL;
    30. }</font>
    复制代码
    2.3 按键中断驱动程序
    按键中断的应用程序,使用上篇的按键检测的应用程序即可
    3 实验
    编译设备树与驱动文件(irqkey-BSp.ko),使用上篇的按键应用程序(key-App),按下按键,会打印get key,松开按键,会打印key release。
    图片 3.png
    4 总结
    本篇主要介绍了Linux中断的使用方法,通过按键来进行中断实验测试,并使用Linux定时器进行按键去抖。



    签到
    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /4 下一条

    Archiver|手机版|小黑屋|恩智浦技术社区

    GMT+8, 2024-4-20 06:45 , Processed in 0.117013 second(s), 20 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表