【i.MX6ULL】驅(qū)動開發(fā)8——中斷法檢測按鍵
上篇,學(xué)習(xí)GPIO輸入功能的使用,本篇,來學(xué)習(xí)使用中斷的方式來檢測按鍵的按下。
1 Linux中斷介紹1.1 中斷的上半部與下半部1.2 下半部的3種實現(xiàn)方式1.2.1 軟中斷1.2.2 tasklet1.2.3 工作隊列1.3 中斷API函數(shù)1.3.1 request_irq中斷請求函數(shù)1.3.2 free_irq中斷釋放函數(shù) 1.3.3 irq_handler_t中斷處理函數(shù)1.3.4 中斷使能/禁用函數(shù)1.3.5 獲取中斷號2 軟件編寫2.1 修改設(shè)備樹文件2.2 按鍵中斷驅(qū)動程序2.2.1 硬件初始化與中斷配置2.2.2 中斷服務(wù)函數(shù)2.2.3 定時器服務(wù)函數(shù)2.2.4 按鍵讀取函數(shù)2.3 按鍵中斷驅(qū)動程序3 實驗4 總結(jié)
1 Linux中斷介紹
1.1 中斷的上半部與下半部
中斷處理函數(shù)的執(zhí)行,越快越好,但實際使用中,某些情況確實需要比較耗時是中斷過程,為此,Linux內(nèi)核將中斷分為上半部和下半部兩個處理部分:
上半部:中斷處理函數(shù),那些處理過程比較快,不會占用很長時間的處理就可以放在上半部完成
下半部:如果中斷處理過程比較耗時,那么就將這些比較耗時的代碼提出來,交給下半部去執(zhí)行,這樣中斷處理函數(shù)就會快進(jìn)快出
對于一個中斷,如何劃分出上下兩部分呢?
對時間敏感,將其放在上半部
和硬件相關(guān),將其放在上半部
要求不被其他中斷打斷,將其放在上半部
其他所有任務(wù),考慮放在下半部
1.2 下半部的3種實現(xiàn)方式
1.2.1 軟中斷
Linux內(nèi)核使用softirq_action結(jié)構(gòu)體表示軟中斷:
struct softirq_action { void (*action)(struct softirq_action *); };
一共有 10 個軟中斷
enum { HI_SOFTIRQ = 0, /* 高優(yōu)先級軟中斷 */ TIMER_SOFTIRQ, /* 定時器軟中斷 */ NET_TX_SOFTIRQ, /* 網(wǎng)絡(luò)數(shù)據(jù)發(fā)送軟中斷 */ NET_RX_SOFTIRQ, /* 網(wǎng)絡(luò)數(shù)據(jù)接收軟中斷 */ BLOCK_SOFTIRQ, BLOCK_IOPOLL_SOFTIRQ, TASKLET_SOFTIRQ, /* tasklet 軟中斷 */ SCHED_SOFTIRQ, /* 調(diào)度軟中斷 */ HRTIMER_SOFTIRQ, /* 高精度定時器軟中斷 */ RCU_SOFTIRQ, /* RCU 軟中斷 */ NR_SOFTIRQS };
要使用軟中斷,必須先使用open_softirq函數(shù)注冊對應(yīng)的軟中斷處理函數(shù):
/** * nr: 要開啟的軟中斷 * action: 軟中斷對應(yīng)的處理函數(shù) * return: 無 */ void open_softirq(int nr, void (*action)(struct softirq_action *))
注冊好軟中斷以后需要通過raise_softirq函數(shù)觸發(fā):
/** * nr: 要觸發(fā)的軟中斷 * return: 無 */ void raise_softirq(unsigned int nr)
1.2.2 tasklet
Linux內(nèi)核使用tasklet_struct結(jié)構(gòu)體來表示tasklet:
struct tasklet_struct { struct tasklet_struct *next; /* 下一個tasklet */ unsigned long state; /* tasklet狀態(tài) */ atomic_t count; /* 計數(shù)器, 記錄對tasklet的引用數(shù) */ void (*func)(unsigned long); /* tasklet執(zhí)行的函數(shù) */ unsigned long data; /* 函數(shù)func的參數(shù) */ };
要使用 tasklet,必須先定義一個tasklet,然后初始化:
/** * t: 要初始化的tasklet * func: tasklet的處理函數(shù) * data: 要傳遞給func函數(shù)的參數(shù) * return: 無 */ void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);
在上半部(中斷處理函數(shù))中調(diào)用tasklet_schedule函數(shù)就能使tasklet在合適的時間運(yùn)行:
/** * t: 要調(diào)度的tasklet * return: 無 */ void tasklet_schedule(struct tasklet_struct *t)
1.2.3 工作隊列
工作隊列(work queue)是另外一種將中斷的部分工作推后的一種方式,它可以實現(xiàn)一些tasklet不能實現(xiàn)的工作,比如工作隊列機(jī)制可以睡眠。
Linux 內(nèi)核使用work_struct結(jié)構(gòu)體表示一個工作:
struct work_struct { atomic_long_t data; struct list_head entry; work_func_t func; /* 工作隊列處理函數(shù) */ };
這些工作組織成工作隊列,工作隊列使用workqueue_struct結(jié)構(gòu)體表示。
1.3 中斷API函數(shù)
1.3.1 request_irq中斷請求函數(shù)
/** * irq: 要申請中斷的中斷號 * handler: 中斷處理函數(shù),當(dāng)中斷發(fā)生以后就會執(zhí)行此中斷處理函數(shù) * flags: 中斷標(biāo)志 * name: 中斷名字 * dev: 設(shè)備結(jié)構(gòu)體 * return: 0-中斷申請成功, 其他負(fù)值-中斷申請失敗 */ int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
flags中斷標(biāo)志,有下面幾種類型
1.3.2 free_irq中斷釋放函數(shù)
/** * irq: 要釋放中斷的中斷號 * dev: 設(shè)備結(jié)構(gòu)體 * return: 無 */ void free_irq(unsigned int irq, void *dev)
1.3.3 irq_handler_t中斷處理函數(shù)
/** * int: 要處理的中斷號 * void *: 通用指針, 需要與request_irq函數(shù)的dev參數(shù)保持一致 * 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 中斷使能/禁用函數(shù)
/** * int: 要使能的中斷號 */ void enable_irq(unsigned int irq) /** * int: 要禁用的中斷號 */ void disable_irq(unsigned int irq)
1.3.5 獲取中斷號
使用中斷時,中斷信息先寫到了設(shè)備樹里面,然后通過irq_of_parse_and_map函數(shù)從interupts屬性中提取到對應(yīng)的中斷號
/** * dev: 設(shè)備節(jié)點 * index: 索引號 * return: 中斷號 */ unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
2 軟件編寫
仍使用上篇按鍵實驗中用到的兩個按鍵:
為了理解簡單,本次程序暫不實現(xiàn)中斷的下半部邏輯,直接將整個中斷處理過程都放到中斷的上半部中處理。
2.1 修改設(shè)備樹文件
在上篇key實驗代碼的基礎(chǔ)上,修改imx6ull-myboard.dts,主要是修改key子節(jié)點,添加中斷,修改后內(nèi)容如下:
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 按鍵中斷驅(qū)動程序
2.2.1 硬件初始化與中斷配置
static int keyio_init(void) { unsigned char i = 0; int ret = 0; /* 設(shè)備樹中獲取key節(jié)點 */ 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,并且設(shè)置成中斷模式 */ for (i = 0; i < KEY_NUM; i++) { memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(imx6uirq.irqkeydesc[i].name)); /* 緩沖區(qū)清零 */ 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); /* 取到對應(yīng)的中斷號 */ 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++) { /* 中斷請求函數(shù) */ 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; } } /* 創(chuàng)建定時器 */ init_timer(&imx6uirq.timer1); imx6uirq.timer1.function = timer1_function; init_timer(&imx6uirq.timer2); imx6uirq.timer2.function = timer2_function; return 0; }
中斷檢測到按鍵按下后,為了消除按鍵抖動,這里使用定時器來進(jìn)行按鍵消抖,因為本次實驗用到兩個按鍵,所以就先也使用兩個定時器。
2.2.2 中斷服務(wù)函數(shù)
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); }
中斷函數(shù)檢測到按鍵按下后,會開啟一個10ms的定時器,用來按鍵消抖。
2.2.3 定時器服務(wù)函數(shù)
定時器的10ms到達(dá)之后,會觸發(fā)定時器服務(wù)函數(shù),此時再次讀取按鍵的值,若仍為按下,則是按鍵真的按下了,若10ms后又檢測不到按鍵了,則說明是按鍵抖動導(dǎo)致的按鍵誤觸發(fā)。
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); /* 標(biāo)記松開按鍵,即完成一次完整的按鍵過程 */ } }
2.2.4 按鍵讀取函數(shù)
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); /* 按下標(biāo)志清零 */ } else { goto data_error; } return 0; data_error: return -EINVAL; }
2.3 按鍵中斷驅(qū)動程序
按鍵中斷的應(yīng)用程序,使用上篇的按鍵檢測的應(yīng)用程序即可
3 實驗
編譯設(shè)備樹與驅(qū)動文件(irqkey-BSp.ko),使用上篇的按鍵應(yīng)用程序(key-App),按下按鍵,會打印get key,松開按鍵,會打印key release。
4 總結(jié)
本篇主要介紹了Linux中斷的使用方法,通過按鍵來進(jìn)行中斷實驗測試,并使用Linux定時器進(jìn)行按鍵去抖。
Linux 單片機(jī)
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。