實驗名稱
Raspberry Pi B+ & Pi2, Linux 驅動程式開發 -- GPIO -- 使用 gpio library實驗目的
簡單介紹在Raspberry Pi Linux 系統底下,如何使用GPIO來撰寫模組。使用材料及設備
硬體: Raspberry Pi B+ or Pi2軟體: 可編譯 Linux Module 的 Raspbian 系統。
原理介紹
GPIO 是嵌入式系統中極為重要的功能,但是在一般的桌機與筆電系統之中卻很少見。一般MCU在操作GPIO時是直接透過寫入資料到對應的暫存器來控制其輸出入。然而在 Raspberry Pi 的 Linux 系統底下因為虛擬記憶體空間的機制,使得無法如此直接使用,而須再透過系統函式得到一組記憶體映射位置之後,再透過該記憶體映射間接控制 GPIO。本篇文章會使用艾鍗科技出產的教學子板來做示範-- 在模組之中控制GPIO點亮板子上的LED燈。無子板的話也可以使用麵包板自行實作電路達到相同的效果。
程式說明
程式碼內容
---------------- CODE --------------------------------- CODE -----------------
程式架構
此範例模組主要架構實作了一個字元裝置驅動程式模組-- 支援使用者空間 open()、read()、write()、close() 四個系統呼叫函式。而使用此四個系統呼叫實作控制GPIO 來點亮對應針腳的LED燈。LED0 為 GPIO.22 (針腳第 15 針) ,LED1 為 GPIO.27 (針腳第 13 針)。此範例的字元裝置驅動程式檔為 /dev/myLED0 和 /dev/myLED1 。透過對此兩個檔案使用系統呼叫函式 write() 可寫入一個字串,模組會判斷字串的第一個字元,如果其值為 '1' 則點亮相對應的LED燈(GPIO輸出為high-level ),其值為 '0' 則關掉相對應的 LED 燈(GPIO輸出為 low-level)。
整個模組為一個字元裝置驅動程式,故模組一個進入點 init_module() 一個離開點 cleanup_moudle()。
使用者空間的程式使用到 open() 來呼叫字元裝置驅動檔時會得到一個檔案描述子並開始操作。而同時,模組會呼叫註冊進系統的函式 my_open() 。
使用者空間程式使用 close() 代入檔案描述子則結束和模組的溝通。而同時,模組會呼叫註冊進系統的函式 my_close()來做處理 。
使用者空間程式使用write()時,可以寫入字串資料進去模組之中。而同時,系統會呼叫 my_write() 函式。在 my_write() 之中,可以用 copy_from_user() 函式來得到使用者空間程式所傳進來的字串。得到字串之後就可以進行後續的處理。
使用者空間程式使用read()時,可以由模組讀取字串資料。而同時,系統會呼叫 my_read() 函式。在 my_read() 之中,可以用 copy_to_user() 函式來傳遞相對應的字串給使用者空間程式。
程式碼說明
此程式碼有兩個重點,一個是在模組進入點 init_module() 函式裡面,要如得到GPIO的映射記憶體位置。另一個則是在 my_write()和 my_read() 函式之中如何讀取與操作 GPIO。
使用 GPIO API
因為 Raspberry pi B+ 和 Raspberry pi 2 使用的CPU是不同的,所以很多暫存器記憶體位置並不相同,因此模組撰寫時直接使用到特定暫存器位置的程式碼,往往在平台升級改版之後就失去功用。因此此範例使用了Linux 核心 GPIO工具函式。下面就先介紹此範例中會用到的部份。
設定 GPIO
在模組內使用核心提供的函式使用GPIO是需要用"申請"的方式來做,而且在向系統申請之前,還需要先確認該GPIO是否已經被其他模組申請占用了。下面這個函式就確認是否指定的GPIO號碼是否尚未被占用。如果該GPIO是可以用的,則回傳1,否則回傳一個負值 :
int gpio_is_valid(GPIO_number);
此次範例中,用到 BCM22 (第15根針)做為 LED0 而 BCM27 (第13根針)為 LED1 。不過因為網路上很多 library 會自行定義 GPIO 腳位號碼,但模組裡面所用的是官方文件的定義:
http://pi.gadgetoid.com/pinout
所以模組一開始就定義 LED0_GPIO 為 22 其實就是上面連結裡的 BCM22 ,而相同的LED1_GPIO 為 27 就是指官網裡面的 BCM27 。所以我們會先確認LED0_GPIO和LED1_GPIO是否有人占用:
gpio_is_valid(LED0_GPIO);
gpio_is_valid(LED1_GPIO);
確認回傳值為 1 之後,就提出使用申請,否則顯示錯誤訊息並結束模組。提出使用申請的函式:
int gpio_request(GPIO_NUM, "GPIO_LABEL");
如果申請失敗會回傳 負值,而成功的話會傳回 0 ,不成功就傳回其他值。而裡面的"GPIO_LABEL"的部份就是你幫這個GPIO取的名字的字串,平時不會用到,但如果遇到需要 debug 的時候可能會有用,所以建議還是取個名字比較好。
申請到 GPIO 之後,第一件事情是,設定該 GPIO 是輸入還是輸出? 輸出的話,起始狀態是要設 high-level 還是 low-level ?。先說設定 GPIO 為輸入的函式:
gpio_direction_input(GPIO_NUM);
如果要設定 GPIO 為輸出,並順便設定輸出狀態。
gpio_direction_output(GPIO_NUM, level );
傳入的參數 level 如果為 1 則輸出為 high-level,如果為 0 則輸出為 low-level。所以此範例的GPIO設定可以簡單歸納如下:
if(gpio_is_valid(LED0_GPIO)){
gpio_request(LED0_GPIO);
gpio_direction_output(LED0_GPIO, 0);
} else{
return -1;
}
讀取 GPIO 狀態
讀取指定 GPIO 的狀態的函式如下:
int level = gpio_get_value(GPIO_NUM);
如果 GPIO 的電位為 high-level 則回傳值為 1 ,為low-level 則回傳值為 0。注意的是,就算不是設定該GPIO為輸入也可以進行讀取的動作。
而此次範例的讀取動作是在使用者空間程式進行檔案操作函式 read() 時,系統去呼叫對應函式 my_read() 時,回報給使用者空間GPIO狀態用的。
控制 GPIO 輸出狀態
控制 GPIO的輸出所使用的函式:
gpio_set_value(GPIO_NUM, level);
level 值為 1 則輸出為 high-level,值為 0 則輸出為 low-level。此範例在跟據使用者空間程式之中用 write() 傳到 my_write() 之中的字串資料來設定 LED0 或 LED1 的亮暗。 my_write() 函式一開始就要先得到次要裝置號碼(minor number),來判斷使用者空間程式是使用 myLED0 還是 myLED1,然後再判斷傳進來的字串的第一個字元是 '1' 還是 '0' 來切換 對應LED燈的亮暗。
釋放 GPIO 資源
當模組結束的時候,記得要把申請來的 GPIO 給釋放掉,不然之後有模組要申請的時候會過不了。而釋放 GPIO 所用的函式為:
gpio_free(GPIO_NUM);
一般來說會在模組離開點函式 clearup_moudle()裡面進行。
執行結果
gpio_set_value(GPIO_NUM, level);
level 值為 1 則輸出為 high-level,值為 0 則輸出為 low-level。此範例在跟據使用者空間程式之中用 write() 傳到 my_write() 之中的字串資料來設定 LED0 或 LED1 的亮暗。 my_write() 函式一開始就要先得到次要裝置號碼(minor number),來判斷使用者空間程式是使用 myLED0 還是 myLED1,然後再判斷傳進來的字串的第一個字元是 '1' 還是 '0' 來切換 對應LED燈的亮暗。
釋放 GPIO 資源
當模組結束的時候,記得要把申請來的 GPIO 給釋放掉,不然之後有模組要申請的時候會過不了。而釋放 GPIO 所用的函式為:
gpio_free(GPIO_NUM);
一般來說會在模組離開點函式 clearup_moudle()裡面進行。
執行結果
沒有留言 :
張貼留言