2015年5月25日 星期一

在Raspberry Pi B+ & Pi2 上面學 Linux 驅動程式開發 -- GPIO Button 中斷 --

實驗名稱

    Raspberry Pi & Pi2, Linux 驅動程式開發 -- GPIO --

實驗目的

    簡單介紹在Raspberry Pi Linux 系統底下,如何使用GPIO。

使用材料及設備

    硬體: Raspberry Pi & Pi2, Ittraining Edu Kit 子板

    軟體: 可編譯 Linux Module 的 Raspbian 系統。

原理介紹

GPIO 是嵌入式系統中極為重要的功能,但是在一般的桌機與筆電系統之中卻很少見。一般MCU在操作GPIO時是直接透過寫入資料到對應的暫存器來控制其輸出入。但是在Raspberry pi 上則是透過 Linux 系統提供的 API 來操作GPIO。

本篇文章會使用艾鍗科技出產的教學子板來做示範-- 利用教學子板上的GPIO 按鍵來實作驅動程之中GPIO 中斷的使用方式。GPIO 按鍵按下後會去點亮GPIO LED。

無子板的話也可以使用麵包板自行實作電路達到相同的效果。

程式說明

程式碼內容

---------------- CODE -----------------

---------------- CODE -----------------

程式架構

此模組除了最基本的進入點函式 init_module() 和離開點函式cleanup_module()之外,還有這次的主題中斷處理函式 button_isr()。

中斷點處理函式 button_isr()被核心呼叫的時機為指定中斷訊號發生的時候。這樣的機制會在指定中斷處理函式和指定中斷訊號做連結(使用核心提供的函式)之後生效。

就此模組而言,按鈕的 GPIO 輸入電位產生 Rising-Edge (上緣觸發) 的變化時系統會發出一個特定中斷訊號給核心,然後核心就會去呼叫和此特定中斷訊號相對應的中斷處理函式 button_isr(); 切換 LED 的 GPIO 輸出電位,藉此控制LED的亮暗。

程式碼說明

程式碼一開始定義了指定按鈕使用 GPIO23,而LED 使用 GPIO27。所以在程式進入點使用了gpio_request(LED) 對系統提出使用該GPIO 的申請,成功的話回傳0,失敗的話回傳一個負值錯誤碼。再指定 LED 為輸出並初始輸出電位為 low : gpio_direction_output(LED, 0 ),回傳值為0表示成功。這兩個API函式的介面如下

int gpio_request(unsigned int gpio, const char *label);
int gpio_direction_output(unsigned int gpio, int value);

按鍵的 GPIO 部份,同樣要先向系統申請
使用: gpio_request(BUTTON)。如果該GPIO有相對應的中斷號碼,則可以使用函式 gpio_to_irq(BUTTON) ,會回傳對應該GPIO的中斷號碼,如果失敗的話會回傳一個負值。

int gpio_to_irq(unsigned int gpio);

知道中斷號碼之後,我們需要使用稍微複雜一點的函式註冊指定的中斷處理函式給指定的中斷號碼。

註冊中斷處理函式給對應的中斷號碼

request_irq( button_irq, button_isr ,IRQF_TRIGGER_RISING, MY_GPIO_INT_NAME, MY_DEV_NAME);

button_irq 為中斷號碼,是由函式 gpio_to_irq() 回傳得到,而 button_isr 是中斷處理函式的函式指標,經過註冊之後硬體引發該中斷號碼給核心,核心就會去執行button_isr() 函式。

設定值 IRQF_TRIGGER_RISING 是指定設定該 GPIO 當輸入電位由低向高變化的時候引發中斷(上緣觸發),當然還有其他的設定值如下緣觸發: IRQF_TRIGGER_FALLING ,設定時可以用符號 "|" 來做成上緣下緣都會觸發中斷的設定值: 

IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING

而 MY_GPIO_INT_NAME 為字串 "my_button_int",代表中斷名稱,在註冊成功之後會出現在系統檔案之中 /proc/interrupts。 可以用下列指令來顯示系統中目前所有的中斷名稱。系統裡面所有的中斷名稱可以使用指令 "cat /proc/interrupts" 顯示出來,裡面會有系統中所有的中斷訊號的資訊,包括引發的資數

而 MY_DEV_NAME 為字串 "my_device"。其實request_irq()的最後一個參數的資料形態為 ( void * ),這個指標會在呼叫中斷處理函式的時候從第二個參數傳入。如果你設計的中斷處理函式有需要用到一個自行維護的資料的話,可以透過這個指標傳入。沒有用到的話傳入NULL也行。只是這邊我們傳入該模組自行維護的模組名稱的字串進去。

而 request_irq() 註冊中斷處理函式成功的話會回傳 0 。之後在按下按鍵之後,放開按鍵的時候會讓 GPIO23 引發中斷訊號,該中斷訊號會有一個特定的中斷號碼,系統會依照接收到的中斷號碼去呼叫對應的中斷處理函式 button_isr()。

中斷處理函式

中斷處理函式的介面形式如下:

irqreturn_t button_isr(int irq, void * data);

系統在呼叫該中斷處理函式傳入的參數 irq 為對應的中斷號碼,Debug的時候有機會用到。而如前面所述 data 其實就是在註冊中斷處理函式給對應中斷號碼時最後一個傳入的指標(void *),此模組是傳入 MY_DEV_NAME 裝置名稱的字串指標,從範例碼來看,其實並沒有用到...。

呼叫 button_isr() 時,所做的事情就只是用 gpio_set_value() 來切換 LED 亮暗狀態。但是在中斷處理函式在運行的期間,會不希望因為其他中斷訊號引發造成程式中斷,所以會使用 local_irq_save(flags) 來關掉CPU受理其他中斷訊號的功能,等到函式要結束之時再使用 local_irq_restore(flags)來重新使CPU可以受理中斷訊號。

執行結果

因為按鍵會有開關彈跳的問題,所以一般處理按鍵開關類的輸入都會進行所謂 debounce 的處理。但是在這個範例之中並沒有特別去做這件事情,所以按鍵每按一下時,其實都會在短時間內引發隨機次數的中斷。處理這樣的問題有很多種手法,不過為了練習Linux Kernel API,所以下一篇文章會用 timer 的方式來處理。


沒有留言 :

張貼留言