Linux 中断实验
目錄
- Linux 中斷簡介
- Linux 中斷API 函數(shù)
- 上半部與下半部
- 設(shè)備樹中斷信息節(jié)點(diǎn)
- 獲取中斷號
- 硬件原理圖分析
- 實(shí)驗(yàn)程序編寫
- 修改設(shè)備樹文件
- 按鍵中斷驅(qū)動程序編寫
- 編寫測試APP
- 運(yùn)行測試
- 編譯驅(qū)動程序和測試APP
- 運(yùn)行測試
不管是裸機(jī)實(shí)驗(yàn)還是Linux 下的驅(qū)動實(shí)驗(yàn),中斷都是頻繁使用的功能,關(guān)于I.MX6U 的中斷原理已經(jīng)在第十七章做了詳細(xì)的講解,在裸機(jī)中使用中斷我們需要做一大堆的工作,比如配置寄存器,使能IRQ 等等。Linux 內(nèi)核提供了完善的中斷框架,我們只需要申請中斷,然后注冊中斷處理函數(shù)即可,使用非常方便,不需要一系列復(fù)雜的寄存器配置。本章我們就來學(xué)習(xí)一下如何在Linux 下使用中斷。
Linux 中斷簡介
Linux 中斷API 函數(shù)
先來回顧一下裸機(jī)實(shí)驗(yàn)里面中斷的處理方法:
①、使能中斷,初始化相應(yīng)的寄存器。
②、注冊中斷服務(wù)函數(shù),也就是向irqTable 數(shù)組的指定標(biāo)號處寫入中斷服務(wù)函數(shù)
②、中斷發(fā)生以后進(jìn)入IRQ 中斷服務(wù)函數(shù),在IRQ 中斷服務(wù)函數(shù)在數(shù)組irqTable 里面查找具體的中斷處理函數(shù),找到以后執(zhí)行相應(yīng)的中斷處理函數(shù)。
在Linux 內(nèi)核中也提供了大量的中斷相關(guān)的API 函數(shù),我們來看一下這些跟中斷有關(guān)的API 函數(shù):
1、中斷號
每個(gè)中斷都有一個(gè)中斷號,通過中斷號即可區(qū)分不同的中斷,有的資料也把中斷號叫做中斷線。在Linux 內(nèi)核中使用一個(gè)int 變量表示中斷號,關(guān)于中斷號我們已經(jīng)在第十七章講解過了。
2、request_irq 函數(shù)
在Linux 內(nèi)核中要想使用某個(gè)中斷是需要申請的,request_irq 函數(shù)用于申請中斷,request_irq函數(shù)可能會導(dǎo)致睡眠,因此不能在中斷上下文或者其他禁止睡眠的代碼段中使用request_irq 函數(shù)。request_irq 函數(shù)會激活(使能)中斷,所以不需要我們手動去使能中斷,request_irq 函數(shù)原型如下:
函數(shù)參數(shù)和返回值含義如下:
irq:要申請中斷的中斷號。
handler:中斷處理函數(shù),當(dāng)中斷發(fā)生以后就會執(zhí)行此中斷處理函數(shù)。
flags:中斷標(biāo)志,可以在文件include/linux/interrupt.h 里面查看所有的中斷標(biāo)志,這里我們介紹幾個(gè)常用的中斷標(biāo)志,如表51.1.1.1 所示:
| IRQF_SHARED | 多個(gè)設(shè)備共享一個(gè)中斷線,共享的所有中斷都必須指定此標(biāo)志。如果使用共享中斷的話,request_irq 函數(shù)的dev 參數(shù)就是唯一區(qū)分他們的標(biāo)志。 |
| IRQF_ONESHOT | 單次中斷,中斷執(zhí)行一次就結(jié)束。 |
| IRQF_TRIGGER_NONE | 無觸發(fā)。 |
| IRQF_TRIGGER_RISING | 上升沿觸發(fā)。 |
| IRQF_TRIGGER_FALLING | 下降沿觸發(fā)。 |
| IRQF_TRIGGER_HIGH | 高電平觸發(fā)。 |
| IRQF_TRIGGER_LOW | 低電平觸發(fā)。 |
比如I.MX6U-ALPHA 開發(fā)板上的KEY0 使用GPIO1_IO18,按下KEY0 以后為低電平,因此可以設(shè)置為下降沿觸發(fā),也就是將flags 設(shè)置為IRQF_TRIGGER_FALLING。表51.1.1.1 中的這些標(biāo)志可以通過“|”來實(shí)現(xiàn)多種組合。
name:中斷名字,設(shè)置以后可以在/proc/interrupts 文件中看到對應(yīng)的中斷名字。
dev:如果將flags 設(shè)置為IRQF_SHARED 的話,dev 用來區(qū)分不同的中斷,一般情況下將dev 設(shè)置為設(shè)備結(jié)構(gòu)體,dev 會傳遞給中斷處理函數(shù)irq_handler_t 的第二個(gè)參數(shù)。
返回值:0 中斷申請成功,其他負(fù)值中斷申請失敗,如果返回-EBUSY 的話表示中斷已經(jīng)被申請了。
3、free_irq 函數(shù)
使用中斷的時(shí)候需要通過request_irq 函數(shù)申請,使用完成以后就要通過free_irq 函數(shù)釋放掉相應(yīng)的中斷。如果中斷不是共享的,那么free_irq 會刪除中斷處理函數(shù)并且禁止中斷。free_irq函數(shù)原型如下所示:
函數(shù)參數(shù)和返回值含義如下:
irq:要釋放的中斷。
dev:如果中斷設(shè)置為共享(IRQF_SHARED)的話,此參數(shù)用來區(qū)分具體的中斷。共享中斷只有在釋放最后中斷處理函數(shù)的時(shí)候才會被禁止掉。
返回值:無。
4、中斷處理函數(shù)
使用request_irq 函數(shù)申請中斷的時(shí)候需要設(shè)置中斷處理函數(shù),中斷處理函數(shù)格式如下所示:
第一個(gè)參數(shù)是要中斷處理函數(shù)要相應(yīng)的中斷號。第二個(gè)參數(shù)是一個(gè)指向void 的指針,也就是個(gè)通用指針,需要與request_irq 函數(shù)的dev 參數(shù)保持一致。用于區(qū)分共享中斷的不同設(shè)備,dev 也可以指向設(shè)備數(shù)據(jù)結(jié)構(gòu)。中斷處理函數(shù)的返回值為irqreturn_t 類型,irqreturn_t 類型定義如下所示:
10 enum irqreturn { 11 IRQ_NONE = (0 << 0), 12 IRQ_HANDLED = (1 << 0), 13 IRQ_WAKE_THREAD = (1 << 1), 14 }; 15 16 typedef enum irqreturn irqreturn_t;可以看出irqreturn_t 是個(gè)枚舉類型,一共有三種返回值。一般中斷服務(wù)函數(shù)返回值使用如下形式:
return IRQ_RETVAL(IRQ_HANDLED)5、中斷使能與禁止函數(shù)
常用的中斷使用和禁止函數(shù)如下所示:
void enable_irq(unsigned int irq) void disable_irq(unsigned int irq)enable_irq 和disable_irq 用于使能和禁止指定的中斷,irq 就是要禁止的中斷號。disable_irq函數(shù)要等到當(dāng)前正在執(zhí)行的中斷處理函數(shù)執(zhí)行完才返回,因此使用者需要保證不會產(chǎn)生新的中斷,并且確保所有已經(jīng)開始執(zhí)行的中斷處理程序已經(jīng)全部退出。在這種情況下,可以使用另外一個(gè)中斷禁止函數(shù):
void disable_irq_nosync(unsigned int irq)disable_irq_nosync 函數(shù)調(diào)用以后立即返回,不會等待當(dāng)前中斷處理程序執(zhí)行完畢。上面三個(gè)函數(shù)都是使能或者禁止某一個(gè)中斷,有時(shí)候我們需要關(guān)閉當(dāng)前處理器的整個(gè)中斷系統(tǒng),也就是在學(xué)習(xí)STM32 的時(shí)候常說的關(guān)閉全局中斷,這個(gè)時(shí)候可以使用如下兩個(gè)函數(shù):
local_irq_enable() local_irq_disable()local_irq_enable 用于使能當(dāng)前處理器中斷系統(tǒng),local_irq_disable 用于禁止當(dāng)前處理器中斷系統(tǒng)。假如A 任務(wù)調(diào)用local_irq_disable 關(guān)閉全局中斷10S,當(dāng)關(guān)閉了2S 的時(shí)候B 任務(wù)開始運(yùn)行,B 任務(wù)也調(diào)用local_irq_disable 關(guān)閉全局中斷3S,3 秒以后B 任務(wù)調(diào)用local_irq_enable 函數(shù)將全局中斷打開了。此時(shí)才過去2+3=5 秒的時(shí)間,然后全局中斷就被打開了,此時(shí)A 任務(wù)要關(guān)閉10S 全局中斷的愿望就破滅了,然后A 任務(wù)就“生氣了”,結(jié)果很嚴(yán)重,可能系統(tǒng)都要被A 任務(wù)整崩潰。為了解決這個(gè)問題,B 任務(wù)不能直接簡單粗暴的通過local_irq_enable 函數(shù)來打開全局中斷,而是將中斷狀態(tài)恢復(fù)到以前的狀態(tài),要考慮到別的任務(wù)的感受,此時(shí)就要用到下面兩個(gè)函數(shù):
local_irq_save(flags) local_irq_restore(flags)這兩個(gè)函數(shù)是一對,local_irq_save 函數(shù)用于禁止中斷,并且將中斷狀態(tài)保存在flags 中。local_irq_restore 用于恢復(fù)中斷,將中斷到flags 狀態(tài)。
上半部與下半部
在有些資料中也將上半部和下半部稱為頂半部和底半部,都是一個(gè)意思。我們在使用request_irq 申請中斷的時(shí)候注冊的中斷服務(wù)函數(shù)屬于中斷處理的上半部,只要中斷觸發(fā),那么中斷處理函數(shù)就會執(zhí)行。我們都知道中斷處理函數(shù)一定要快點(diǎn)執(zhí)行完畢,越短越好,但是現(xiàn)實(shí)往往是殘酷的,有些中斷處理過程就是比較費(fèi)時(shí)間,我們必須要對其進(jìn)行處理,縮小中斷處理函數(shù)的執(zhí)行時(shí)間。比如電容觸摸屏通過中斷通知SOC 有觸摸事件發(fā)生,SOC 響應(yīng)中斷,然后通過IIC 接口讀取觸摸坐標(biāo)值并將其上報(bào)給系統(tǒng)。但是我們都知道IIC 的速度最高也只有400Kbit/S,所以在中斷中通過IIC 讀取數(shù)據(jù)就會浪費(fèi)時(shí)間。我們可以將通過IIC 讀取觸摸數(shù)據(jù)的操作暫后執(zhí)行,中斷處理函數(shù)僅僅相應(yīng)中斷,然后清除中斷標(biāo)志位即可。這個(gè)時(shí)候中斷處理過程就分為了兩部分:
上半部:上半部就是中斷處理函數(shù),那些處理過程比較快,不會占用很長時(shí)間的處理就可以放在上半部完成。
下半部:如果中斷處理過程比較耗時(shí),那么就將這些比較耗時(shí)的代碼提出來,交給下半部去執(zhí)行,這樣中斷處理函數(shù)就會快進(jìn)快出。
因此,Linux 內(nèi)核將中斷分為上半部和下半部的主要目的就是實(shí)現(xiàn)中斷處理函數(shù)的快進(jìn)快出,那些對時(shí)間敏感、執(zhí)行速度快的操作可以放到中斷處理函數(shù)中,也就是上半部。剩下的所有工作都可以放到下半部去執(zhí)行,比如在上半部將數(shù)據(jù)拷貝到內(nèi)存中,關(guān)于數(shù)據(jù)的具體處理就可以放到下半部去執(zhí)行。至于哪些代碼屬于上半部,哪些代碼屬于下半部并沒有明確的規(guī)定,一切根據(jù)實(shí)際使用情況去判斷,這個(gè)就很考驗(yàn)驅(qū)動編寫人員的功底了。這里有一些可以借鑒的參考點(diǎn):
①、如果要處理的內(nèi)容不希望被其他中斷打斷,那么可以放到上半部。
②、如果要處理的任務(wù)對時(shí)間敏感,可以放到上半部。
③、如果要處理的任務(wù)與硬件有關(guān),可以放到上半部
④、除了上述三點(diǎn)以外的其他任務(wù),優(yōu)先考慮放到下半部。
上半部處理很簡單,直接編寫中斷處理函數(shù)就行了,關(guān)鍵是下半部該怎么做呢?Linux 內(nèi)核提供了多種下半部機(jī)制,接下來我們來學(xué)習(xí)一下這些下半部機(jī)制。
1、軟中斷
一開始Linux 內(nèi)核提供了“bottom half”機(jī)制來實(shí)現(xiàn)下半部,簡稱“BH”。后面引入了軟中斷和tasklet 來替代“BH”機(jī)制,完全可以使用軟中斷和tasklet 來替代BH,從2.5 版本的Linux內(nèi)核開始BH 已經(jīng)被拋棄了。Linux 內(nèi)核使用結(jié)構(gòu)體softirq_action 表示軟中斷,softirq_action結(jié)構(gòu)體定義在文件include/linux/interrupt.h 中,內(nèi)容如下:
在kernel/softirq.c 文件中一共定義了10 個(gè)軟中斷,如下所示:
static struct softirq_action softirq_vec[NR_SOFTIRQS];NR_SOFTIRQS 是枚舉類型,定義在文件include/linux/interrupt.h 中,定義如下:
enum { HI_SOFTIRQ=0, /* 高優(yōu)先級軟中斷*/ TIMER_SOFTIRQ, /* 定時(shí)器軟中斷*/ 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, /* 高精度定時(shí)器軟中斷*/ RCU_SOFTIRQ, /* RCU軟中斷*/ NR_SOFTIRQS };可以看出,一共有10 個(gè)軟中斷,因此NR_SOFTIRQS 為10,因此數(shù)組softirq_vec 有10 個(gè)元素。softirq_action 結(jié)構(gòu)體中的action 成員變量就是軟中斷的服務(wù)函數(shù),數(shù)組softirq_vec 是個(gè)全局?jǐn)?shù)組,因此所有的CPU(對于SMP 系統(tǒng)而言)都可以訪問到,每個(gè)CPU 都有自己的觸發(fā)和控制機(jī)制,并且只執(zhí)行自己所觸發(fā)的軟中斷。但是各個(gè)CPU 所執(zhí)行的軟中斷服務(wù)函數(shù)確是相同
的,都是數(shù)組softirq_vec 中定義的action 函數(shù)。要使用軟中斷,必須先使用open_softirq 函數(shù)注冊對應(yīng)的軟中斷處理函數(shù),open_softirq 函數(shù)原型如下:
函數(shù)參數(shù)和返回值含義如下:
nr:要開啟的軟中斷,在示例代碼51.1.2.3 中選擇一個(gè)。
action:軟中斷對應(yīng)的處理函數(shù)。
返回值:沒有返回值。
注冊好軟中斷以后需要通過raise_softirq 函數(shù)觸發(fā),raise_softirq 函數(shù)原型如下:
函數(shù)參數(shù)和返回值含義如下:
nr:要觸發(fā)的軟中斷,在示例代碼51.1.2.3 中選擇一個(gè)。
返回值:沒有返回值。
軟中斷必須在編譯的時(shí)候靜態(tài)注冊!Linux 內(nèi)核使用softirq_init 函數(shù)初始化軟中斷,softirq_init 函數(shù)定義在kernel/softirq.c 文件里面,函數(shù)內(nèi)容如下:
從示例代碼51.1.2.4 可以看出,softirq_init 函數(shù)默認(rèn)會打開TASKLET_SOFTIRQ 和HI_SOFTIRQ。
2、tasklet
tasklet 是利用軟中斷來實(shí)現(xiàn)的另外一種下半部機(jī)制,在軟中斷和tasklet 之間,建議大家使用tasklet。Linux 內(nèi)核使用tasklet_struct 結(jié)構(gòu)體來表示tasklet:
第489 行的func 函數(shù)就是tasklet 要執(zhí)行的處理函數(shù),用戶定義函數(shù)內(nèi)容,相當(dāng)于中斷處理函數(shù)。如果要使用tasklet,必須先定義一個(gè)tasklet,然后使用tasklet_init 函數(shù)初始化tasklet,taskled_init 函數(shù)原型如下:
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);函數(shù)參數(shù)和返回值含義如下:
t:要初始化的tasklet
func:tasklet 的處理函數(shù)。
data:要傳遞給func 函數(shù)的參數(shù)
返回值:沒有返回值。
也可以使用宏DECLARE_TASKLET 來一次性完成tasklet 的定義和初始化,
DECLARE_TASKLET 定義在include/linux/interrupt.h 文件中,定義如下:
其中name 為要定義的tasklet 名字,這個(gè)名字就是一個(gè)tasklet_struct 類型的時(shí)候變量,func就是tasklet 的處理函數(shù),data 是傳遞給func 函數(shù)的參數(shù)。
在上半部,也就是中斷處理函數(shù)中調(diào)用tasklet_schedule 函數(shù)就能使tasklet 在合適的時(shí)間運(yùn)行,tasklet_schedule 函數(shù)原型如下:
函數(shù)參數(shù)和返回值含義如下:
t:要調(diào)度的tasklet,也就是DECLARE_TASKLET 宏里面的name。
返回值:沒有返回值。
關(guān)于tasklet 的參考使用示例如下所示:
2、工作隊(duì)列
工作隊(duì)列是另外一種下半部執(zhí)行方式,工作隊(duì)列在進(jìn)程上下文執(zhí)行,工作隊(duì)列將要推后的工作交給一個(gè)內(nèi)核線程去執(zhí)行,因?yàn)楣ぷ麝?duì)列工作在進(jìn)程上下文,因此工作隊(duì)列允許睡眠或重新調(diào)度。因此如果你要推后的工作可以睡眠那么就可以選擇工作隊(duì)列,否則的話就只能選擇軟中斷或tasklet。
Linux 內(nèi)核使用work_struct 結(jié)構(gòu)體表示一個(gè)工作,內(nèi)容如下(省略掉條件編譯):
struct work_struct { atomic_long_t data; struct list_head entry; work_func_t func; /* 工作隊(duì)列處理函數(shù)*/ };這些工作組織成工作隊(duì)列,工作隊(duì)列使用workqueue_struct 結(jié)構(gòu)體表示,內(nèi)容如下(省略掉條件編譯):
struct workqueue_struct { struct list_head pwqs; struct list_head list; struct mutex mutex; int work_color; int flush_color; atomic_t nr_pwqs_to_flush; struct wq_flusher *first_flusher; struct list_head flusher_queue; struct list_head flusher_overflow; struct list_head maydays; struct worker *rescuer; int nr_drainers; int saved_max_active; struct workqueue_attrs *unbound_attrs; struct pool_workqueue *dfl_pwq; char name[WQ_NAME_LEN]; struct rcu_head rcu; unsigned int flags ____cacheline_aligned; struct pool_workqueue __percpu *cpu_pwqs; struct pool_workqueue __rcu *numa_pwq_tbl[]; };Linux 內(nèi)核使用工作者線程(worker thread)來處理工作隊(duì)列中的各個(gè)工作,Linux 內(nèi)核使用worker 結(jié)構(gòu)體表示工作者線程,worker 結(jié)構(gòu)體內(nèi)容如下:
struct worker { union { struct list_head entry; struct hlist_node hentry; }; struct work_struct *current_work; work_func_t current_func; struct pool_workqueue *current_pwq; bool desc_valid; struct list_head scheduled; struct task_struct *task; struct worker_pool *pool; struct list_head node; unsigned long last_active; unsigned int flags; int id; char desc[WORKER_DESC_LEN]; struct workqueue_struct *rescue_wq; };從示例代碼51.1.2.10 可以看出,每個(gè)worker 都有一個(gè)工作隊(duì)列,工作者線程處理自己工作隊(duì)列中的所有工作。在實(shí)際的驅(qū)動開發(fā)中,我們只需要定義工作(work_struct)即可,關(guān)于工作隊(duì)列和工作者線程我們基本不用去管。簡單創(chuàng)建工作很簡單,直接定義一個(gè)work_struct 結(jié)構(gòu)體變量即可,然后使用INIT_WORK 宏來初始化工作,INIT_WORK 宏定義如下:
#define INIT_WORK(_work, _func)_work 表示要初始化的工作,_func 是工作對應(yīng)的處理函數(shù)。
也可以使用DECLARE_WORK 宏一次性完成工作的創(chuàng)建和初始化,宏定義如下:
n 表示定義的工作(work_struct),f 表示工作對應(yīng)的處理函數(shù)。
和tasklet 一樣,工作也是需要調(diào)度才能運(yùn)行的,工作的調(diào)度函數(shù)為schedule_work,函數(shù)原型如下所示:
函數(shù)參數(shù)和返回值含義如下:
work:要調(diào)度的工作。
返回值:0 成功,其他值失敗。
關(guān)于工作隊(duì)列的參考使用示例如下所示:
設(shè)備樹中斷信息節(jié)點(diǎn)
如果使用設(shè)備樹的話就需要在設(shè)備樹中設(shè)置好中斷屬性信息,Linux 內(nèi)核通過讀取設(shè)備樹中的中斷屬性信息來配置中斷。對于中斷控制器而言,設(shè)備樹綁定信息參考文檔
Documentation/devicetree/bindings/arm/gic.txt。打開imx6ull.dtsi 文件,其中的intc 節(jié)點(diǎn)就是I.MX6ULL 的中斷控制器節(jié)點(diǎn),節(jié)點(diǎn)內(nèi)容如下所示:
第2 行,compatible 屬性值為“arm,cortex-a7-gic”在Linux 內(nèi)核源碼中搜索“arm,cortex-a7-gic”即可找到GIC 中斷控制器驅(qū)動文件。
第3 行,#interrupt-cells 和#address-cells、#size-cells 一樣。表示此中斷控制器下設(shè)備的cells大小,對于設(shè)備而言,會使用interrupts 屬性描述中斷信息,#interrupt-cells 描述了interrupts 屬性的cells 大小,也就是一條信息有幾個(gè)cells。每個(gè)cells 都是32 位整形值,對于ARM 處理的GIC 來說,一共有3 個(gè)cells,這三個(gè)cells 的含義如下:
第一個(gè)cells:中斷類型,0 表示SPI 中斷,1 表示PPI 中斷。
第二個(gè)cells:中斷號,對于SPI 中斷來說中斷號的范圍為0~ 987,對于PPI 中斷來說中斷號的范圍為0~15。
第三個(gè)cells:標(biāo)志,bit[3:0]表示中斷觸發(fā)類型,為1 的時(shí)候表示上升沿觸發(fā),為2 的時(shí)候表示下降沿觸發(fā),為4 的時(shí)候表示高電平觸發(fā),為8 的時(shí)候表示低電平觸發(fā)。bit[15:8]為PPI 中斷的CPU 掩碼。
第4 行,interrupt-controller 節(jié)點(diǎn)為空,表示當(dāng)前節(jié)點(diǎn)是中斷控制器。
對于gpio 來說,gpio 節(jié)點(diǎn)也可以作為中斷控制器,比如imx6ull.dtsi 文件中的gpio5 節(jié)點(diǎn)內(nèi)容如下所示:
第4 行,interrupts 描述中斷源信息,對于gpio5 來說一共有兩條信息,中斷類型都是SPI,觸發(fā)電平都是IRQ_TYPE_LEVEL_HIGH。不同之處在于中斷源,一個(gè)是74,一個(gè)是75,打開可以打開《IMX6ULL 參考手冊》的“Chapter 3 Interrupts and DMA Events”章節(jié),找到表3-1,有如圖50.1.3.1 所示的內(nèi)容:
從圖50.1.3.1 可以看出,GPIO5 一共用了2 個(gè)中斷號,一個(gè)是74,一個(gè)是75。其中74 對應(yīng)GPIO5_IO00~ GPIO5_IO15 這低16 個(gè)IO,75 對應(yīng)GPIO5_IO16~GPIOI5_IO31 這高16 位IO。
第8 行,interrupt-controller 表明了gpio5 節(jié)點(diǎn)也是個(gè)中斷控制器,用于控制gpio5 所有IO的中斷。
第9 行,將#interrupt-cells 修改為2。
打開imx6ull-alientek-emmc.dts 文件,找到如下所示內(nèi)容:
fxls8471 是NXP 官方的6ULL 開發(fā)板上的一個(gè)磁力計(jì)芯片,fxls8471 有一個(gè)中斷引腳鏈接到了I.MX6ULL 的SNVS_TAMPER0 因腳上,這個(gè)引腳可以復(fù)用為GPIO5_IO00。
第5 行,interrupt-parent 屬性設(shè)置中斷控制器,這里使用gpio5 作為中斷控制器。
第6 行,interrupts 設(shè)置中斷信息,0 表示GPIO5_IO00,8 表示低電平觸發(fā)。
簡單總結(jié)一下與中斷有關(guān)的設(shè)備樹屬性信息:
①、#interrupt-cells,指定中斷源的信息cells 個(gè)數(shù)。
②、interrupt-controller,表示當(dāng)前節(jié)點(diǎn)為中斷控制器。
③、interrupts,指定中斷號,觸發(fā)方式等。
④、interrupt-parent,指定父中斷,也就是中斷控制器。
獲取中斷號
編寫驅(qū)動的時(shí)候需要用到中斷號,我們用到中斷號,中斷信息已經(jīng)寫到了設(shè)備樹里面,因此可以通過irq_of_parse_and_map 函數(shù)從interupts 屬性中提取到對應(yīng)的設(shè)備號,函數(shù)原型如下:
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)函數(shù)參數(shù)和返回值含義如下:
dev:設(shè)備節(jié)點(diǎn)。
index:索引號,interrupts 屬性可能包含多條中斷信息,通過index 指定要獲取的信息。
返回值:中斷號。
如果使用GPIO 的話,可以使用gpio_to_irq 函數(shù)來獲取gpio 對應(yīng)的中斷號,函數(shù)原型如下:
函數(shù)參數(shù)和返回值含義如下:
gpio:要獲取的GPIO 編號。
返回值:GPIO 對應(yīng)的中斷號。
硬件原理圖分析
本章實(shí)驗(yàn)硬件原理圖參考15.2 小節(jié)即可。
實(shí)驗(yàn)程序編寫
本實(shí)驗(yàn)對應(yīng)的例程路徑為:開發(fā)板光盤-> 2、Linux 驅(qū)動例程-> 13_irq。
本章實(shí)驗(yàn)我們驅(qū)動I.MX6U-ALPHA 開發(fā)板上的KEY0 按鍵,不過我們采用中斷的方式,并且采用定時(shí)器來實(shí)現(xiàn)按鍵消抖,應(yīng)用程序讀取按鍵值并且通過終端打印出來。通過本章我們可以學(xué)習(xí)到Linux 內(nèi)核中斷的使用方法,以及對Linux 內(nèi)核定時(shí)器的回顧。
修改設(shè)備樹文件
本章實(shí)驗(yàn)使用到了按鍵KEY0,按鍵KEY0 使用中斷模式,因此需要在“key”節(jié)點(diǎn)下添加中斷相關(guān)屬性,添加完成以后的“key”節(jié)點(diǎn)內(nèi)容如下所示:
1 key { 2 #address-cells = <1>; 3 #size-cells = <1>; 4 compatible = "atkalpha-key"; 5 pinctrl-names = "default"; 6 pinctrl-0 = <&pinctrl_key>; 7 key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */ 8 interrupt-parent = <&gpio1>; 9 interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */ 10 status = "okay"; 11 };第8 行,設(shè)置interrupt-parent 屬性值為“gpio1”,因?yàn)镵EY0 所使用的GPIO 為
GPIO1_IO18,也就是設(shè)置KEY0 的GPIO 中斷控制器為gpio1。
第9 行,設(shè)置interrupts 屬性,也就是設(shè)置中斷源,第一個(gè)cells 的18 表示GPIO1 組的18號IO。IRQ_TYPE_EDGE_BOTH 定義在文件include/linux/irq.h 中,定義如下:
從示例代碼51.3.1.2 中可以看出,IRQ_TYPE_EDGE_BOTH 表示上升沿和下降沿同時(shí)有效,相當(dāng)于KEY0 按下和釋放都會觸發(fā)中斷。
設(shè)備樹編寫完成以后使用“make dtbs”命令重新編譯設(shè)備樹,然后使用新編譯出來的imx6ull-alientek-emmc.dtb 文件啟動Linux 系統(tǒng)。
按鍵中斷驅(qū)動程序編寫
新建名為“13_irq”的文件夾,然后在13_irq 文件夾里面創(chuàng)建vscode 工程,工作區(qū)命名為“imx6uirq”。工程創(chuàng)建好以后新建imx6uirq.c 文件,在imx6uirq.c 里面輸入如下內(nèi)容:
#include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_gpio.h> #include <linux/semaphore.h> #include <linux/timer.h> #include <linux/of_irq.h> #include <linux/irq.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> /*************************************************************** Copyright ? ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 文件名 : imx6uirq.c 作者 : 左忠凱 版本 : V1.0 描述 : Linux中斷驅(qū)動實(shí)驗(yàn) 其他 : 無 論壇 : www.openedv.com 日志 : 初版V1.0 2019/7/26 左忠凱創(chuàng)建 ***************************************************************/ #define IMX6UIRQ_CNT 1 /* 設(shè)備號個(gè)數(shù) */ #define IMX6UIRQ_NAME "imx6uirq" /* 名字 */ #define KEY0VALUE 0X01 /* KEY0按鍵值 */ #define INVAKEY 0XFF /* 無效的按鍵值 */ #define KEY_NUM 1 /* 按鍵數(shù)量 *//* 中斷IO描述結(jié)構(gòu)體 */ struct irq_keydesc {int gpio; /* gpio */int irqnum; /* 中斷號 */unsigned char value; /* 按鍵對應(yīng)的鍵值 */char name[10]; /* 名字 */irqreturn_t (*handler)(int, void *); /* 中斷服務(wù)函數(shù) */ };/* imx6uirq設(shè)備結(jié)構(gòu)體 */ struct imx6uirq_dev{dev_t devid; /* 設(shè)備號 */struct cdev cdev; /* cdev */struct class *class; /* 類 */struct device *device; /* 設(shè)備 */int major; /* 主設(shè)備號 */int minor; /* 次設(shè)備號 */struct device_node *nd; /* 設(shè)備節(jié)點(diǎn) */atomic_t keyvalue; /* 有效的按鍵鍵值 */atomic_t releasekey; /* 標(biāo)記是否完成一次完成的按鍵,包括按下和釋放 */struct timer_list timer;/* 定義一個(gè)定時(shí)器*/struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按鍵描述數(shù)組 */unsigned char curkeynum; /* 當(dāng)前的按鍵號 */ };struct imx6uirq_dev imx6uirq; /* irq設(shè)備 *//* @description : 中斷服務(wù)函數(shù),開啟定時(shí)器,延時(shí)10ms,* 定時(shí)器用于按鍵消抖。* @param - irq : 中斷號 * @param - dev_id : 設(shè)備結(jié)構(gòu)。* @return : 中斷執(zhí)行結(jié)果*/ static irqreturn_t key0_handler(int irq, void *dev_id) {struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;dev->curkeynum = 0;dev->timer.data = (volatile long)dev_id;mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); /* 10ms定時(shí) */return IRQ_RETVAL(IRQ_HANDLED); }/* @description : 定時(shí)器服務(wù)函數(shù),用于按鍵消抖,定時(shí)器到了以后* 再次讀取按鍵值,如果按鍵還是處于按下狀態(tài)就表示按鍵有效。* @param - arg : 設(shè)備結(jié)構(gòu)變量* @return : 無*/ void timer_function(unsigned long arg) {unsigned char value;unsigned char num;struct irq_keydesc *keydesc;struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;num = dev->curkeynum;keydesc = &dev->irqkeydesc[num];value = gpio_get_value(keydesc->gpio); /* 讀取IO值 */if(value == 0){ /* 按下按鍵 */atomic_set(&dev->keyvalue, keydesc->value);}else{ /* 按鍵松開 */atomic_set(&dev->keyvalue, 0x80 | keydesc->value);atomic_set(&dev->releasekey, 1); /* 標(biāo)記松開按鍵,即完成一次完整的按鍵過程 */ } }/** @description : 按鍵IO初始化* @param : 無* @return : 無*/ static int keyio_init(void) {unsigned char i = 0;int ret = 0;imx6uirq.nd = of_find_node_by_path("/key");if (imx6uirq.nd== NULL){printk("key node not find!\r\n");return -EINVAL;} /* 提取GPIO */for (i = 0; i < KEY_NUM; i++) {imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i);if (imx6uirq.irqkeydesc[i].gpio < 0) {printk("can't get key%d\r\n", i);}}/* 初始化key所使用的IO,并且設(shè)置成中斷模式 */for (i = 0; i < KEY_NUM; i++) {memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(name)); /* 緩沖區(qū)清零 */sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i); /* 組合名字 */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); #if 0imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio); #endifprintk("key%d:gpio=%d, irqnum=%d\r\n",i, imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].irqnum);}/* 申請中斷 */imx6uirq.irqkeydesc[0].handler = key0_handler;imx6uirq.irqkeydesc[0].value = KEY0VALUE;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;}}/* 創(chuàng)建定時(shí)器 */init_timer(&imx6uirq.timer);imx6uirq.timer.function = timer_function;return 0; }/** @description : 打開設(shè)備* @param - inode : 傳遞給驅(qū)動的inode* @param - filp : 設(shè)備文件,file結(jié)構(gòu)體有個(gè)叫做private_data的成員變量* 一般在open的時(shí)候?qū)rivate_data指向設(shè)備結(jié)構(gòu)體。* @return : 0 成功;其他 失敗*/ static int imx6uirq_open(struct inode *inode, struct file *filp) {filp->private_data = &imx6uirq; /* 設(shè)置私有數(shù)據(jù) */return 0; }/** @description : 從設(shè)備讀取數(shù)據(jù) * @param - filp : 要打開的設(shè)備文件(文件描述符)* @param - buf : 返回給用戶空間的數(shù)據(jù)緩沖區(qū)* @param - cnt : 要讀取的數(shù)據(jù)長度* @param - offt : 相對于文件首地址的偏移* @return : 讀取的字節(jié)數(shù),如果為負(fù)值,表示讀取失敗*/ 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) { /* 有按鍵按下 */ 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; }/* 設(shè)備操作函數(shù) */ static struct file_operations imx6uirq_fops = {.owner = THIS_MODULE,.open = imx6uirq_open,.read = imx6uirq_read, };/** @description : 驅(qū)動入口函數(shù)* @param : 無* @return : 無*/ static int __init imx6uirq_init(void) {/* 1、構(gòu)建設(shè)備號 */if (imx6uirq.major) {imx6uirq.devid = MKDEV(imx6uirq.major, 0);register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);} else {alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);imx6uirq.major = MAJOR(imx6uirq.devid);imx6uirq.minor = MINOR(imx6uirq.devid);}/* 2、注冊字符設(shè)備 */cdev_init(&imx6uirq.cdev, &imx6uirq_fops);cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);/* 3、創(chuàng)建類 */imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);if (IS_ERR(imx6uirq.class)) {return PTR_ERR(imx6uirq.class);}/* 4、創(chuàng)建設(shè)備 */imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);if (IS_ERR(imx6uirq.device)) {return PTR_ERR(imx6uirq.device);}/* 5、初始化按鍵 */atomic_set(&imx6uirq.keyvalue, INVAKEY);atomic_set(&imx6uirq.releasekey, 0);keyio_init();return 0; }/** @description : 驅(qū)動出口函數(shù)* @param : 無* @return : 無*/ static void __exit imx6uirq_exit(void) {unsigned int i = 0;/* 刪除定時(shí)器 */del_timer_sync(&imx6uirq.timer); /* 刪除定時(shí)器 *//* 釋放中斷 */for (i = 0; i < KEY_NUM; i++) {free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);}cdev_del(&imx6uirq.cdev);unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);device_destroy(imx6uirq.class, imx6uirq.devid);class_destroy(imx6uirq.class); }module_init(imx6uirq_init); module_exit(imx6uirq_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("zuozhongkai");第38~43 行,結(jié)構(gòu)體irq_keydesc 為按鍵的中斷描述結(jié)構(gòu)體,gpio 為按鍵GPIO 編號,irqnum為按鍵IO 對應(yīng)的中斷號,value 為按鍵對應(yīng)的鍵值,name 為按鍵名字,handler 為按鍵中斷服務(wù)函數(shù)。使用irq_keydesc 結(jié)構(gòu)體即可描述一個(gè)按鍵中斷。
第47~60 行,結(jié)構(gòu)體imx6uirq_dev 為本例程設(shè)備結(jié)構(gòu)體,第55 行的keyvalue 保存按鍵值,第56 行的releasekey 表示按鍵是否被釋放,如果按鍵被釋放表示發(fā)生了一次完整的按鍵過程。
第57 行的timer 為按鍵消抖定時(shí)器,使用定時(shí)器進(jìn)行按鍵消抖的原理已經(jīng)在19.1 小節(jié)講解過了。第58 行的數(shù)組irqkeydesc 為按鍵信息數(shù)組,數(shù)組元素個(gè)數(shù)就是開發(fā)板上的按鍵個(gè)數(shù),I.MX6U-ALIPHA 開發(fā)板上只有一個(gè)按鍵,因此irqkeydesc 數(shù)組只有一個(gè)元素。第59 行的curkeynum 表示當(dāng)前按鍵。
第62 行,定義設(shè)備結(jié)構(gòu)體變量imx6uirq。
第70~78 行,key0_handler 函數(shù),按鍵KEY0 中斷處理函數(shù),參數(shù)dev_id 為設(shè)備結(jié)構(gòu)體,也就是imx6uirq。第74 行設(shè)置curkeynum=0,表示當(dāng)前按鍵為KEY0,第76 行使用mod_timer函數(shù)啟動定時(shí)器,定時(shí)器周期為10ms。
第85~103,timer_function 函數(shù),定時(shí)器定時(shí)處理函數(shù),參數(shù)arg 是設(shè)備結(jié)構(gòu)體,也就是imx6uirq,在此函數(shù)中讀取按鍵值。第95 行通過gpio_get_value 函數(shù)讀取按鍵值。如果為0 的話就表示按鍵被按下去了,按下去的話就設(shè)置imx6uirq 結(jié)構(gòu)體的keyvalue 成員變量為按鍵的鍵值,比如KEY0 按鍵的話按鍵值就是KEY0VALUE=0。如果按鍵值為1 的話表示按鍵被釋放了,按鍵釋放了的話就將imx6uirq 結(jié)構(gòu)體的keyvalue 成員變量的最高位置1,表示按鍵值有效,也就是將keyvalue 與0x80 進(jìn)行或運(yùn)算,表示按鍵松開了,并且設(shè)置imx6uirq 結(jié)構(gòu)體的releasekey成員變量為1,表示按鍵釋放,一次有效的按鍵過程發(fā)生。
第110~159 行,keyio_init 函數(shù),按鍵IO 初始化函數(shù),在驅(qū)動入口函數(shù)里面會調(diào)用keyio_init來初始化按鍵IO。第131~142 行輪流初始化所有的按鍵,包括申請IO、設(shè)置IO 為輸入模式、從設(shè)備樹中獲取IO 的中斷號等等。第136 行通過irq_of_parse_and_map 函數(shù)從設(shè)備樹中獲取按鍵IO 對應(yīng)的中斷號。也可以使用gpio_to_irq 函數(shù)將某個(gè)IO 設(shè)置為中斷狀態(tài),并且返回其中斷
號。第144 和145 行設(shè)置KEY0 按鍵對應(yīng)的按鍵中斷處理函數(shù)為key0_handler、KEY0 的按鍵值為KEY0VALUE。第147~153 行輪流調(diào)用request_irq 函數(shù)申請中斷號,設(shè)置中斷觸發(fā)模式為IRQF_TRIGGER_FALLING 和IRQF_TRIGGER_RISING,也就是上升沿和下降沿都可以觸發(fā)中斷。最后,第156 行初始化定時(shí)器,并且設(shè)置定時(shí)器的定時(shí)處理函數(shù)。
第168~172 行,imx6uirq_open 函數(shù),對應(yīng)應(yīng)用程序的open 函數(shù)。
第182~207 行,imx6uirq_read 函數(shù),對應(yīng)應(yīng)用程序的read 函數(shù)。此函數(shù)向應(yīng)用程序返回按鍵值。首先判斷imx6uirq 結(jié)構(gòu)體的releasekey 成員變量值是否為1,如果為1 的話表示有一次有效按鍵發(fā)生,否則的話就直接返回-EINVAL。當(dāng)有按鍵事件發(fā)生的話就要向應(yīng)用程序發(fā)送按鍵值,首先判斷按鍵值的最高位是否為1,如果為1 的話就表示按鍵值有效。如果按鍵值有效
的話就將最高位清除,得到真實(shí)的按鍵值,然后通過copy_to_user 函數(shù)返回給應(yīng)用程序。向應(yīng)用程序發(fā)送按鍵值完成以后就將imx6uirq 結(jié)構(gòu)體的releasekey 成員變量清零,準(zhǔn)備下一次按鍵操作。
第210~214 行,按鍵中斷驅(qū)動操作函數(shù)集imx6uirq_fops。
第221~253 行,驅(qū)動入口函數(shù),第250 和251 行分別初始化imx6uirq 結(jié)構(gòu)體中的原子變量keyvalue 和releasekey,第252 行調(diào)用keyio_init 函數(shù)初始化按鍵所使用的IO。
第261~ 275 行,驅(qū)動出口函數(shù),第265 行調(diào)用del_timer_sync 函數(shù)刪除定時(shí)器,第268~270行輪流釋放申請的所有按鍵中斷。
編寫測試APP
測試APP 要實(shí)現(xiàn)的內(nèi)容很簡單,通過不斷的讀取/dev/imx6uirq 文件來獲取按鍵值,當(dāng)按鍵按下以后就會將獲取到的按鍵值輸出在終端上,新建名為imx6uirqApp.c 的文件,然后輸入如下所示內(nèi)容:
#include "stdio.h" #include "unistd.h" #include "sys/types.h" #include "sys/stat.h" #include "fcntl.h" #include "stdlib.h" #include "string.h" #include "linux/ioctl.h" /*************************************************************** Copyright ? ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 文件名 : imx6uirqApp.c 作者 : 左忠凱 版本 : V1.0 描述 : 定時(shí)器測試應(yīng)用程序 其他 : 無 使用方法 :./imx6uirqApp /dev/imx6uirq 打開測試App 論壇 : www.openedv.com 日志 : 初版V1.0 2019/7/26 左忠凱創(chuàng)建 ***************************************************************//** @description : main主程序* @param - argc : argv數(shù)組元素個(gè)數(shù)* @param - argv : 具體參數(shù)* @return : 0 成功;其他 失敗*/ int main(int argc, char *argv[]) {int fd;int ret = 0;int data = 0;char *filename;unsigned char data;if (argc != 2) {printf("Error Usage!\r\n");return -1;}filename = argv[1];fd = open(filename, O_RDWR);if (fd < 0) {printf("Can't open file %s\r\n", filename);return -1;}while (1) {ret = read(fd, &data, sizeof(data));if (ret < 0) { /* 數(shù)據(jù)讀取錯(cuò)誤或者無效 */} else { /* 數(shù)據(jù)讀取正確 */if (data) /* 讀取到數(shù)據(jù) */printf("key value = %#X\r\n", data);}}close(fd);return ret; }第45~53 行的while 循環(huán)用于不斷的讀取按鍵值,如果讀取到有效的按鍵值就將其輸出到終端上。
運(yùn)行測試
編譯驅(qū)動程序和測試APP
1、編譯驅(qū)動程序
編寫Makefile 文件,本章實(shí)驗(yàn)的Makefile 文件和第四十章實(shí)驗(yàn)基本一樣,只是將obj-m 變量的值改為imx6uirq.o,Makefile 內(nèi)容如下所示:
第4 行,設(shè)置obj-m 變量的值為imx6uirq.o。
輸入如下命令編譯出驅(qū)動模塊文件:
編譯成功以后就會生成一個(gè)名為“imx6uirq.ko”的驅(qū)動模塊文件。
2、編譯測試APP
輸入如下命令編譯測試imx6uirqApp.c 這個(gè)測試程序:
編譯成功以后就會生成imx6uirqApp 這個(gè)應(yīng)用程序。
運(yùn)行測試
將上一小節(jié)編譯出來imx6uirq.ko 和imx6uirqApp 這兩個(gè)文件拷貝到
rootfs/lib/modules/4.1.15 目錄中,重啟開發(fā)板,進(jìn)入到目錄lib/modules/4.1.15 中,輸入如下命令加載imx6uirq.ko 驅(qū)動模塊:
驅(qū)動加載成功以后可以通過查看/proc/interrupts 文件來檢查一下對應(yīng)的中斷有沒有被注冊上,輸入如下命令:
cat /proc/interrupts結(jié)果如圖51.4.2.1 所示:
從圖51.4.2.1 可以看出imx6uirq.c 驅(qū)動文件里面的KEY0 中斷已經(jīng)存在了,觸發(fā)方式為跳邊沿(Edge),中斷號為49。
接下來使用如下命令來測試中斷:
按下開發(fā)板上的KEY0 鍵,終端就會輸出按鍵值,如圖51.4.2.2 所示:
從圖51.4.2.2 可以看出,按鍵值獲取成功,并且不會有按鍵抖動導(dǎo)致的誤判發(fā)生,說明按鍵消抖工作正常。如果要卸載驅(qū)動的話輸入如下命令即可:
總結(jié)
以上是生活随笔為你收集整理的Linux 中断实验的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java第一个helloworld_Ja
- 下一篇: Linux 阻塞和非阻塞IO 实验