0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

【i.MX6ULL】驱动开发8—中断法检测按键 Linux中断和定时使用方法

码农爱学习 来源:码农爱学习 作者:码农爱学习 2022-05-25 09:09 次阅读

上篇,学习GPIO输入功能的使用,本篇,来学习使用中断的方式来检测按键的按下。

1 Linux中断介绍

1.1 中断的上半部与下半部

中断处理函数的执行,越快越好,但实际使用中,某些情况确实需要比较耗时是中断过程,为此,Linux内核将中断分为上半部和下半部两个处理部分

上半部:中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成

下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出

对于一个中断,如何划分出上下两部分呢?

时间敏感,将其放在上半部

硬件相关,将其放在上半部

要求不被其他中断打断,将其放在上半部

其他所有任务,考虑放在下半部

1.2 下半部的3种实现方式

1.2.1 软中断

Linux内核使用softirq_action结构体表示软中断:

struct softirq_action 
{ 
	void (*action)(struct softirq_action *); 
}; 

一共有 10 个软中断

enum 
{ 
    HI_SOFTIRQ = 0,          /* 高优先级软中断    */ 
    TIMER_SOFTIRQ,           /* 定时器软中断      */ 
    NET_TX_SOFTIRQ,          /* 网络数据发送软中断 */ 
    NET_RX_SOFTIRQ,          /* 网络数据接收软中断 */ 
    BLOCK_SOFTIRQ,           
    BLOCK_IOPOLL_SOFTIRQ,    
    TASKLET_SOFTIRQ,         /* tasklet 软中断   */ 
    SCHED_SOFTIRQ,           /* 调度软中断        */ 
    HRTIMER_SOFTIRQ,         /* 高精度定时器软中断 */ 
    RCU_SOFTIRQ,             /* RCU 软中断       */ 
        
    NR_SOFTIRQS 
};

要使用软中断,必须先使用open_softirq函数注册对应的软中断处理函数:

/**
 * nr: 要开启的软中断
 * action: 软中断对应的处理函数
 * return: 无
 */
void open_softirq(int nr,  void (*action)(struct softirq_action *)) 

注册好软中断以后需要通过raise_softirq函数触发:

/**
 * nr: 要触发的软中断
 * return: 无
 */
void raise_softirq(unsigned int nr) 

1.2.2 tasklet

Linux内核使用tasklet_struct结构体来表示tasklet:

struct tasklet_struct 
{ 
	struct tasklet_struct *next;     /* 下一个tasklet      */ 
	unsigned long state;             /* tasklet状态        */ 
	atomic_t count;                  /* 计数器, 记录对tasklet的引用数 */ 
	void (*func)(unsigned long);     /* tasklet执行的函数  */ 
	unsigned long data;              /* 函数func的参数     */ 
}; 

要使用 tasklet,必须先定义一个tasklet,然后初始化:

/**
 * t: 要初始化的tasklet
 * func: tasklet的处理函数
 * data: 要传递给func函数的参数
 * return: 无
 */
void tasklet_init(struct tasklet_struct *t,
                void (*func)(unsigned long),  
                        unsigned long data); 

在上半部(中断处理函数)中调用tasklet_schedule函数就能使tasklet在合适的时间运行:

/**
 * t: 要调度的tasklet
 * return: 无
 */
void tasklet_schedule(struct tasklet_struct *t)

1.2.3 工作队列

工作队列(work queue)是另外一种将中断的部分工作推后的一种方式,它可以实现一些tasklet不能实现的工作,比如工作队列机制可以睡眠。

Linux 内核使用work_struct结构体表示一个工作

struct work_struct { 
    atomic_long_t data;     
    struct list_head entry;  
    work_func_t func;        /* 工作队列处理函数  */ 
};

这些工作组织成工作队列,工作队列使用workqueue_struct结构体表示。

在工作队列机制中,将推后的工作交给一个称之为工作者线程(worker thread)的内核线程去完成。

1.3 中断API函数

1.3.1 request_irq中断请求函数

/**
 * irq: 要申请中断的中断号
 * handler: 中断处理函数,当中断发生以后就会执行此中断处理函数
 * flags: 中断标志
 * name: 中断名字
 * dev: 设备结构体
 * return: 0-中断申请成功, 其他负值-中断申请失败
 */
int request_irq(unsigned int irq,  
               irq_handler_t handler,  
               unsigned long flags, 
                  const char *name,  
                        void *dev) 

flags中断标志,有下面几种类型

中断标志 描述
IRQF_SHARED 多个设备共享一个中断线, 共享的所有中断都必须指定此标志
IRQF_ONESHOT 单次中断,中断执行一次就结束
IRQF_TRIGGER_NONE 无触发
IRQF_TRIGGER_RISING 上升沿触发
IRQF_TRIGGER_FALLING 下降沿触发
IRQF_TRIGGER_HIGH 高电平触发
IRQF_TRIGGER_LOW 低电平触发

1.3.2 free_irq中断释放函数

/**
 * irq: 要释放中断的中断号
 * dev: 设备结构体
 * return: 无
 */
void free_irq(unsigned int irq,  
                      void *dev) 

1.3.3 irq_handler_t中断处理函数

/**
 * int: 要处理的中断号
 * void *: 通用指针, 需要与request_irq函数的dev参数保持一致
 * return: irqreturn_t枚举类型
 */
irqreturn_t (*irq_handler_t) (int, void *)

irqreturn_t枚举类型定义:

enum irqreturn { 
	IRQ_NONE          = (0 << 0), 
    IRQ_HANDLED       = (1 << 0), 
	IRQ_WAKE_THREAD   = (1 << 1), 
}; 

typedef enum irqreturn irqreturn_t; 

1.3.4 中断使能/禁用函数

/**
 * int: 要使能的中断号
 */
void enable_irq(unsigned int irq) 
    
/**
 * int: 要禁用的中断号
 */
void disable_irq(unsigned int irq)

1.3.5 获取中断号

使用中断时,中断信息先写到了设备树里面,然后通过irq_of_parse_and_map函数从interupts属性中提取到对应的中断号

/**
 * dev: 设备节点
 * index: 索引号
 * return: 中断号
 */
unsigned int irq_of_parse_and_map(struct device_node *dev, 
                                                 int index) 

2 软件编写

仍使用上篇按键实验中用到的两个按键:

poYBAGKM90qAILxrAAFjbYSxyjc095.png

为了理解简单,本次程序暂不实现中断的下半部逻辑,直接将整个中断处理过程都放到中断的上半部中处理。

2.1 修改设备树文件

在上篇key实验代码的基础上,修改imx6ull-myboard.dts,主要是修改key子节点,添加中断,修改后内容如下:

key { 
    #address-cells = <1>; 
    #size-cells = <1>; 
    compatible = "myboard-irq-key"; 
    pinctrl-names = "default"; 
    pinctrl-0 = <&pinctrl_key>; 
    key1-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>;   /* SW2 */
    key2-gpio = <&gpio5 11 GPIO_ACTIVE_LOW>;   /* SW4 */
    interrupt-parent = <&gpio5>; 
    interrupts = <  1 IRQ_TYPE_EDGE_BOTH
                   11 IRQ_TYPE_EDGE_BOTH >;
    status = "okay"; 
}; 

2.2 按键中断驱动程序

2.2.1 硬件初始化与中断配置

static int keyio_init(void)
{
    unsigned char i = 0;
    int ret = 0;
    
    /* 设备树中获取key节点 */
    imx6uirq.nd = of_find_node_by_path("/key");
    if (imx6uirq.nd== NULL)
    {
        printk("key node not find!\r\n");
        return -EINVAL;
    } 

    /* 提取GPIO */
    imx6uirq.irqkeydesc[0].gpio = of_get_named_gpio(imx6uirq.nd ,"key1-gpio", 0);
    imx6uirq.irqkeydesc[1].gpio = of_get_named_gpio(imx6uirq.nd ,"key2-gpio", 0);
    if ((imx6uirq.irqkeydesc[0].gpio < 0)||(imx6uirq.irqkeydesc[1].gpio < 0))
    {
        printk("can't get key\r\n");
        return -EINVAL;
    }
    printk("key1_gpio=%d, key2_gpio=%d\r\n", imx6uirq.irqkeydesc[0].gpio, imx6uirq.irqkeydesc[1].gpio);
    
    /* 初始化key所使用的IO,并且设置成中断模式 */
    for (i = 0; i < KEY_NUM; i++)
    {
        memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(imx6uirq.irqkeydesc[i].name)); /* 缓冲区清零 */
        sprintf(imx6uirq.irqkeydesc[i].name, "key%d", i+1); /* 组合名字 */
        gpio_request(imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].name);
        gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);    
        imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i); /* 取到对应的中断号 */

        printk("key%d:gpio=%d, irqnum=%d\r\n",i+1,
                                              imx6uirq.irqkeydesc[i].gpio, 
                                              imx6uirq.irqkeydesc[i].irqnum);
    }
    /* 申请中断 */
    imx6uirq.irqkeydesc[0].handler = key1_handler;
    imx6uirq.irqkeydesc[1].handler = key2_handler;
    imx6uirq.irqkeydesc[0].value = KEY1VALUE;
    imx6uirq.irqkeydesc[1].value = KEY2VALUE;
    
    for (i = 0; i < KEY_NUM; i++)
    {
        /* 中断请求函数 */
        ret = request_irq(imx6uirq.irqkeydesc[i].irqnum,
                          imx6uirq.irqkeydesc[i].handler,
                          IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
                          imx6uirq.irqkeydesc[i].name,
                          &imx6uirq);
        if(ret < 0)
        {
            printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);
            return -EFAULT;
        }
    }

    /* 创建定时器 */
    init_timer(&imx6uirq.timer1);
    imx6uirq.timer1.function = timer1_function;
    init_timer(&imx6uirq.timer2);
    imx6uirq.timer2.function = timer2_function;
    return 0;
}

中断检测到按键按下后,为了消除按键抖动,这里使用定时器来进行按键消抖,因为本次实验用到两个按键,所以就先也使用两个定时器。

2.2.2 中断服务函数

static irqreturn_t key1_handler(int irq, void *dev_id)
{
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;

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

中断函数检测到按键按下后,会开启一个10ms的定时器,用来按键消抖。

2.2.3 定时器服务函数

定时器的10ms到达之后,会触发定时器服务函数,此时再次读取按键的值,若仍为按下,则是按键真的按下了,若10ms后又检测不到按键了,则说明是按键抖动导致的按键误触发。

void timer1_function(unsigned long arg)
{
    unsigned char value;
    struct irq_keydesc *keydesc;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;

    keydesc = &dev->irqkeydesc[0];

    value = gpio_get_value(keydesc->gpio); /* 读取IO值 */
    if(value == 1) /* 按下按键 */
    {
        printk("get key1: high\r\n");
        atomic_set(&dev->keyvalue, keydesc->value);
    }
    else /* 按键松开 */
    {
        printk("key1 release\r\n");
        atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
        atomic_set(&dev->releasekey, 1); /* 标记松开按键,即完成一次完整的按键过程 */            
    }    
}

2.2.4 按键读取函数

static ssize_t imx6uirq_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 imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);

    if (releasekey) /* 有按键按下 */
    {
        //printk("releasekey!\r\n");
        if (keyvalue & 0x80)
        {
            keyvalue &= ~0x80;
            ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
        }
        else
        {
            goto data_error;
        }
        atomic_set(&dev->releasekey, 0); /* 按下标志清零 */
    }
    else
    {
        goto data_error;
    }
    return 0;
    
data_error:
    return -EINVAL;
}

2.3 按键中断驱动程序

按键中断的应用程序,使用上篇的按键检测的应用程序即可

3 实验

编译设备树与驱动文件(irqkey-BSp.ko),使用上篇的按键应用程序(key-App),按下按键,会打印get key,松开按键,会打印key release。

pYYBAGKM91iAOQhOAACaYIadQzQ689.png

4 总结

本篇主要介绍了Linux中断的使用方法,通过按键来进行中断实验测试,并使用Linux定时器进行按键去抖。

poYBAGKM916AeO0CAAD7m3tGRn8927.png

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式
    +关注

    关注

    5082

    文章

    19126

    浏览量

    305213
  • Linux
    +关注

    关注

    87

    文章

    11304

    浏览量

    209507
  • 中断
    +关注

    关注

    5

    文章

    898

    浏览量

    41498
  • i.MX6
    +关注

    关注

    1

    文章

    37

    浏览量

    16308
收藏 人收藏

    评论

    相关推荐

    【迅为电子】i.MX6UL和i.MX6ULL芯片区别与开发板对比

    【迅为电子】i.MX6UL和i.MX6ULL芯片区别与开发板对比
    的头像 发表于 11-28 14:31 388次阅读
    【迅为电子】<b class='flag-5'>i.MX6</b>UL和<b class='flag-5'>i.MX6ULL</b>芯片区别与<b class='flag-5'>开发</b>板对比

    【新品】i.MX6ULL工业嵌入式核心板!NXP低功耗MPU,LCD显示

    核心板新品上市ECK20-6Y2XA系列核心板是亿佰特基于NXPCortex-A7内核i.MX6ULL处理器精心设计的,采用邮票孔连接的低成本、低功耗、高性价比、高可靠性的嵌入式核心板。可广泛应用于工业控制、HMI、IoT等领域。
    的头像 发表于 11-15 01:04 203次阅读
    【新品】<b class='flag-5'>i.MX6ULL</b>工业嵌入式核心板!NXP低功耗MPU,LCD显示

    i.MX Linux开发实战指南—基于野火i.MX系列开发

    电子发烧友网站提供《i.MX Linux开发实战指南—基于野火i.MX系列开发板.pdf》资料免费下载
    发表于 10-10 17:23 11次下载

    在NXP源码基础上如何适配ELF 1开发板的UART功能

    UART即通用异步收发器,是一种支持全双工串行通信协议的接口。在i.MX6ULL处理器平台上,该处理器原生支持多达8路的UART接口,提供了丰富的串行通信能力。 针对ELF 1开发板,实际引出了4路
    的头像 发表于 09-29 11:49 356次阅读
    在NXP源码基础上如何适配ELF 1<b class='flag-5'>开发</b>板的UART功能

    使用TPS6521815 PMIC为NXP i.MX 6ULL6UltraLite供电

    电子发烧友网站提供《使用TPS6521815 PMIC为NXP i.MX 6ULL6UltraLite供电.pdf》资料免费下载
    发表于 09-13 09:44 1次下载
    使用TPS6521815 PMIC为NXP <b class='flag-5'>i.MX</b> <b class='flag-5'>6ULL</b>、<b class='flag-5'>6</b>UltraLite供电

    如何在NXP源码基础上适配ELF 1开发板的PWM功能

    本次源码适配项目是在NXP i.MX6ULL EVK评估板所搭载的Linux内核源码(版本为Linux-imx_4.1.15)基础上进行的,主要目标是通过调整功能接口引脚配置,使其适应ELF 1
    的头像 发表于 09-10 10:00 933次阅读
    如何在NXP源码基础上适配ELF 1<b class='flag-5'>开发</b>板的PWM功能

    嵌入式学习-飞凌嵌入式ElfBoard ELF 1板卡-在NXP源码基础上适配ELF 1开发板的按键功能

    本次源码适配工作是在NXP i.MX6ULL EVK评估板的Linux内核源码(特定版本:Linux-imx_4.1.15)基础上进行的。主要目标是调整功能接口引脚配置,以适应ELF 1开发
    发表于 07-04 09:11

    飞凌嵌入式ElfBoard ELF 1板卡-在NXP源码基础上适配ELF 1开发板的按键功能

    本次源码适配工作是在NXP i.MX6ULL EVK评估板的Linux内核源码(特定版本:Linux-imx_4.1.15)基础上进行的。主要目标是调整功能接口引脚配置,以适应ELF 1开发
    发表于 07-03 09:07

    PLC中断功能的作用及使用方法

    PLC控制系统中的一项关键技术,对于提高系统的响应速度和实时性具有至关重要的作用。本文将对PLC中断功能的作用及使用方法进行详细的阐述。
    的头像 发表于 06-15 17:54 2047次阅读

    【GD32F303红枫派开发板使用手册】第四讲 GEXTI-按键中断检测实验

    通过本实验主要学习以下内容: •EXTI中断原理; •按键中断检测原理;
    的头像 发表于 05-31 10:13 571次阅读
    【GD32F303红枫派<b class='flag-5'>开发</b>板使用手册】第四讲 GEXTI-<b class='flag-5'>按键</b><b class='flag-5'>中断</b><b class='flag-5'>检测</b>实验

    浅析在NXP I.MX6ULL+Linux平台下进行WM8960音频芯片移植的过程

    本文详细记录在NXP I.MX6ULL+Linux平台下进行WM8960音频芯片移植的过程,其他平台操作方法类似,希望为大家提供帮助。
    的头像 发表于 05-17 11:33 1291次阅读
    浅析在NXP <b class='flag-5'>I.MX6ULL+Linux</b>平台下进行WM8960音频芯片移植的过程

    【GD32F470紫藤派开发板使用手册】第三讲 EXTI-按键中断检测实验

    通过本实验主要学习以下内容: EXTI中断原理; 按键中断检测原理;
    的头像 发表于 05-05 09:22 542次阅读
    【GD32F470紫藤派<b class='flag-5'>开发</b>板使用手册】第三讲 EXTI-<b class='flag-5'>按键</b><b class='flag-5'>中断</b><b class='flag-5'>检测</b>实验

    适用于 NXP i.MX 6ULL6ULZ 和 6UltraLite的TPS6521835电源管理IC数据表

    电子发烧友网站提供《适用于 NXP i.MX 6ULL6ULZ 和 6UltraLite的TPS6521835电源管理IC数据表.pdf》资料免费下载
    发表于 03-01 09:10 0次下载
    适用于 NXP <b class='flag-5'>i.MX</b> <b class='flag-5'>6ULL</b>、<b class='flag-5'>6</b>ULZ 和 <b class='flag-5'>6</b>UltraLite的TPS6521835电源管理IC数据表

    单片机外部中断定时中断的区别和用法

    单片机外部中断定时中断在触发来源、应用场景以及功能特点上存在明显差异** **。
    的头像 发表于 01-28 17:35 3126次阅读

    stm32中断怎么处理的

    STM32是一款非常强大的微控制器系列,具有丰富的外设和功能。中断是STM32中非常重要的部分,能够帮助我们提高系统的响应速度和效率。本文将详细介绍STM32中断的处理方法。 一、中断
    的头像 发表于 01-02 17:35 2589次阅读