【i.MX6ULL】驅(qū)動開發(fā)8——中斷法檢測按鍵

      網(wǎng)友投稿 685 2025-04-02

      上篇,學(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ù)

      【i.MX6ULL】驅(qū)動開發(fā)8——中斷法檢測按鍵

      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)容。

      上一篇:excel2003定義名稱的方法
      下一篇:打印預(yù)覽,單元格內(nèi)容顯示不全(打印預(yù)覽后面頁面顯示不全)
      相關(guān)文章
      亚洲精品无码专区在线播放| 亚洲老熟女@TubeumTV| 亚洲精品无码久久毛片| 一本天堂ⅴ无码亚洲道久久| 亚洲国产精品成人精品软件 | 亚洲AV女人18毛片水真多| 7777久久亚洲中文字幕蜜桃| 久久国产亚洲电影天堂| 亚洲国产精品va在线播放| 亚洲高清国产拍精品26U| 亚洲精品美女久久久久99| 亚洲精品少妇30p| 国产V亚洲V天堂A无码| 国产偷v国产偷v亚洲高清| 亚洲狠狠爱综合影院婷婷| 亚洲中文字幕一二三四区| 一本色道久久88—综合亚洲精品| 亚洲一区二区三区久久久久| 亚洲一区二区中文| 亚洲精品美女在线观看| 亚洲人成网站在线播放影院在线 | 亚洲国产精品久久丫| 亚洲综合日韩久久成人AV| 亚洲国产精品无码久久青草| 亚洲国产成人久久一区WWW| 亚洲精品无码成人片久久不卡| 亚洲毛片一级带毛片基地| 亚洲人成在线观看| 亚洲日本中文字幕区| 亚洲成人网在线播放| 亚洲AV无码国产精品色| 亚洲色欲色欲www| 亚洲日本乱码卡2卡3卡新区| 亚洲男同gay片| 亚洲AV日韩AV无码污污网站| 亚洲高清无码在线观看| 曰韩亚洲av人人夜夜澡人人爽 | 色九月亚洲综合网| 亚洲成人国产精品| 狠狠亚洲狠狠欧洲2019| 亚洲成a人片在线观看日本|