电子工程师技术服务社区
公告
登录
|
注册
首页
技术问答
厂商活动
正点原子
板卡试用
资源库
下载
文章
社区首页
文章
【i.MX6ULL】驱动开发8——中断法检测按键
分 享
扫描二维码分享
【i.MX6ULL】驱动开发8——中断法检测按键
i.MX6ULL
嵌入式
驱动
码农爱学习
关注
发布时间: 2021-11-30
丨
阅读: 477
上篇,学习**GPIO输入功能**的使用,本篇,来学习使用中断的方式来检测按键的按下。 [TOC] # 1 Linux中断介绍 ## 1.1 中断的上半部与下半部 中断处理函数的执行,越快越好,但实际使用中,某些情况确实需要比较耗时是中断过程,为此,**Linux内核将中断分为上半部和下半部两个处理部分**: - 上半部:中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成 - 下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出 对于一个中断,如何划分出上下两部分呢? - 对**时间敏感**,将其放在上半部 - 和**硬件相关**,将其放在上半部 - 要求**不被其他中断打断**,将其放在上半部 - 其他所有任务,考虑放在下半部 ## 1.2 下半部的3种实现方式 ### 1.2.1 软中断 Linux内核使用softirq_action结构体表示软中断: ```c struct softirq_action { void (*action)(struct softirq_action *); }; ``` 一共有 10 个软中断 ```c 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函数注册对应的软中断处理函数: ```c /@@** * nr: 要开启的软中断 * action: 软中断对应的处理函数 * return: 无 */ void open_softirq(int nr, void (*action)(struct softirq_action *)) ``` 注册好软中断以后需要通过raise_softirq函数触发: ```c /@@** * nr: 要触发的软中断 * return: 无 */ void raise_softirq(unsigned int nr) ``` ### 1.2.2 tasklet Linux内核使用tasklet_struct结构体来表示tasklet: ```c 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,然后初始化: ```c /@@** * t: 要初始化的tasklet * func: tasklet的处理函数 * data: 要传递给func函数的参数 * return: 无 */ void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data); ``` 在上半部(中断处理函数)中调用tasklet_schedule函数就能使tasklet在合适的时间运行: ```c /@@** * t: 要调度的tasklet * return: 无 */ void tasklet_schedule(struct tasklet_struct *t) ``` ### 1.2.3 工作队列 工作队列(work queue)是另外一种将中断的部分工作推后的一种方式,它可以实现一些tasklet不能实现的工作,比如工作队列机制可以睡眠。 Linux 内核使用work_struct结构体表示一个**工作**: ```c 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中断请求函数 ```c /@@** * 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中断释放函数 ```c /@@** * irq: 要释放中断的中断号 * dev: 设备结构体 * return: 无 */ void free_irq(unsigned int irq, void *dev) ``` ### 1.3.3 irq_handler_t中断处理函数 ```c /@@** * int: 要处理的中断号 * void *: 通用指针, 需要与request_irq函数的dev参数保持一致 * return: irqreturn_t枚举类型 */ irqreturn_t (*irq_handler_t) (int, void *) ``` irqreturn_t枚举类型定义: ```c enum irqreturn { IRQ_NONE = (0 << 0), IRQ_HANDLED = (1 << 0), IRQ_WAKE_THREAD = (1 << 1), }; typedef enum irqreturn irqreturn_t; ``` ### 1.3.4 中断使能/禁用函数 ```c /@@** * int: 要使能的中断号 */ void enable_irq(unsigned int irq) /@@** * int: 要禁用的中断号 */ void disable_irq(unsigned int irq) ``` ### 1.3.5 获取中断号 使用中断时,中断信息先写到了设备树里面,然后通过**irq_of_parse_and_map**函数从**interupts**属性中提取到对应的中断号 ```c /@@** * dev: 设备节点 * index: 索引号 * return: 中断号 */ unsigned int irq_of_parse_and_map(struct device_node *dev, int index) ``` # 2 软件编写 仍使用上篇按键实验中用到的两个按键: ![](https://cf04.ickimg.com/bbsimages/202111/7cca8c4008799b94c8edd00acc027587.png) 为了理解简单,本次程序暂不实现中断的下半部逻辑,直接将整个中断处理过程都放到中断的上半部中处理。 ## 2.1 修改设备树文件 在上篇key实验代码的基础上,修改imx6ull-myboard.dts,主要是修改key子节点,添加中断,修改后内容如下: ```c 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 硬件初始化与中断配置 ```c 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 中断服务函数 ```c 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后又检测不到按键了,则说明是按键抖动导致的按键误触发。 ```c 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 按键读取函数 ```c 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。 ![](https://cf04.ickimg.com/bbsimages/202111/305041e935cda5bee3469a9f23476c36.png) # 4 总结 本篇主要介绍了Linux中断的使用方法,通过按键来进行中断实验测试,并使用Linux定时器进行按键去抖。 ![](https://cf04.ickimg.com/bbsimages/202111/8e6ff90b8b492bdf771bbfd863f74005.png)
原创作品,未经权利人授权禁止转载。详情见
转载须知
。
举报文章
点赞
(
0
)
码农爱学习
关注
评论
(0)
登录后可评论,请
登录
或
注册
相关文章推荐
MK-米客方德推出工业级存储卡
Beetle ESP32 C3 蓝牙数据收发
Beetle ESP32 C3 wifi联网获取实时天气信息
开箱测评Beetle ESP32-C3 (RISC-V芯片)模块
正点原子数控电源DP100测评
DP100试用评测-----开箱+初体验
Beetle ESP32 C3环境搭建
【花雕体验】16 使用Beetle ESP32 C3控制8X32位WS2812硬屏之二
X
你的打赏是对原创作者最大的认可
请选择打赏IC币的数量,一经提交无法退回 !
100IC币
500IC币
1000IC币
自定义
IC币
确定
X
提交成功 ! 谢谢您的支持
返回
我要举报该内容理由
×
广告及垃圾信息
抄袭或未经授权
其它举报理由
请输入您举报的理由(50字以内)
取消
提交