2015年5月13日 星期三

在Raspberry Pi B+ 上面學 Linux 驅動程式開發 -- GPIO

實驗名稱

    Raspberry Pi B+ , Linux 驅動程式開發 -- GPIO --

實驗目的

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

使用材料及設備

    硬體: Raspberry Pi B+

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

原理介紹

GPIO 是嵌入式系統中極為重要的功能,但是在一般的桌機與筆電系統之中卻很少見。一般MCU在操作GPIO時是直接透過寫入資料到對應的暫存器來控制其輸出入。然而在 Raspberry Pi 的 Linux 系統底下因為虛擬記憶體空間的機制,使得無法如此直接使用,而須再透過系統函式得到一組記憶體映射位置之後,再透過該記憶體映射間接控制 GPIO。

本篇文章會使用艾鍗科技出產的教學子板來做示範-- 在模組之中控制GPIO點亮板子上的LED燈。無子板的話也可以使用麵包板自行實作電路達到相同的效果。


Pi 40 Pin Header

程式說明

程式碼內容

---------------- 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  映射記憶體位置


因為 Raspberry pi B+ 和 Raspberry pi 2 使用的CPU是不同的,所以很多暫存器記憶體位置並不相同,而此範例裡面所使用到的是 Raspberry Pi B+ 的暫存器記憶體位置,故此範例並不能在 Raspberry Pi 2 的板子上面使用,這點是要注意的部份。

範例程式碼中,利用註解標示出 "GPIO DEFINE" 的部份,包裝了此範例中經常會使用到的一些操作GPIO的巨集:

INP_GPIO() : 設定指定的 GPIO port 為 輸入
OUT_GPIO(): 設定指定的 GPIO port 為 輸出
GPIO_SET(): 設定指定的 GPIO port 的輸出為 high-level
GPIO_CLR(): 設定指定的 GPIO port 的輸出為 low-level
GPIO_READ(): 讀取指定的 GPIO port 的電位狀態

BCM2708_PERI_BASE : 為 Raspberry Pi 的周邊裝置暫存器在CPU中的起始位置。
GPIO_BASE : 為 Raspberry Pi 的 GPIO 暫存器相對於BCM2708_PERI_BASE的起始位置。

自訂資料型態 struct bcm2835_peripheral 用來儲存 GPIO 記憶體的資訊。此模組中用此資料型態宣告了一個全域變數 gpio 來儲存設定好的記憶體映射相關資訊。

我們可以看到在模組進入點 init_module() 裡面的兩行程式碼:

gpio.map = ioremap(GPIO_BASE, 4096);
gpio.addr = (volatile unsigned int * ) gpio.map;

ioremap()函式將CPU中的硬體暫存器的記憶體位置( 查CPU的 datasheet可得知 ),從GPIO_BASE 開始 4096 byte 連續記憶體空間,對應到模組的虛擬記憶體空間之中的一個4096 byte 連續記憶體空間。後後回傳在模組中對應的起始位置。之後讀寫 gpio.map 就等同於讀寫 CPU 硬體暫存器的記憶體位置 GPIO_BASE。

相對應函式 ioremap() ,在模組離開點函式 cleanup_module() 裡面,要關閉模組時需要
函式 iounmap() 來釋放這片記憶體映射空間。

設定 GPIO 為輸出入


自訂巨集INP_GPIO(g),g 值為指定的GPIO port 號碼。該巨集為一特定的位元操作,其功能為設定指定 GPIO port 為輸入。例如指定 GPIO.22 為輸入:

INP_GPIO(22);

自訂巨集OUT_GPIO(g),g 值為指定的GPIO port 號碼。該巨集為一特定的位元操作,其功能為設定指定 GPIO port 為輸出。例如指定 GPIO.22 為輸出:

OUT_GPIO(22);

讀取 GPIO 狀態


自訂巨集GPIO_READ(g) ,g 值為指定的GPIO port 號碼。該巨集為一特定的位元操作,其結果為回傳指定 GPIO port 號碼的電位狀態。所以下面這行程式碼會回傳GPIO.22 的電位狀態:

int level = GPIO_READ(22);

level 值為 1 表示為 high-level,為 0 表示為 low-level。

操作 GPIO 狀態


自訂巨集GPIO_SET 會得到所有 GPIO port 的設定輸出 high-level 暫存器位置。將對應的位元值設為一,會使其位元所對應的GPIO port 輸出 high-level 電位。假設要使 GPIO.22 輸出為high-level:

GPIO_SET = 1 << 22;

自訂巨集GPIO_CLR 會得到所有 GPIO port 的設定輸出 low-level 暫存器位置。將對應的位元值設為一,會使其位元所對應的GPIO port 輸出 low-level 電位。假設要使 GPIO.22 輸出為 low-level:

GPIO_CLR = 1 << 22;

在了解到如何操作這些 GPIO 之後,可以看到對應到使用者空間程式檔案系統呼叫函式 write() 在模組中所對應的函式 my_write()。我們先將用來傳遞訊息的字串空間 msg 用memset() 函式進行填零的

執行結果





沒有留言 :

張貼留言