一、gpio用途
General Purpose Input Output (通用輸入/輸出)簡稱為GPIO,或總線擴展器,人們利用工業標準I2C、SMBus或SPI接口簡化了I/O口的擴展。當微控制器或芯片組沒有足夠的I/O端口,或當系統需要采用遠端串行通信或控制時,GPIO產品能夠提供額外的控制和監視功能。
每個GPIO端口可通過軟件分別配置成輸入或輸出。Maxim的GPIO產品線包括8端口至28端口的GPIO,提供推挽式輸出或漏極開路輸出。提供微型3mm x 3mm QFN封裝。
不同系統間的GPIO的確切作用不同。通用常有下面幾種:
1.輸出值可寫(高=1,低=0)。一些芯片也可以選擇驅動這些值的方式,以便支持“線-或”或類似方案(開漏信號線)。
2.輸入值可讀(1,0)。一些芯片支持輸出管腳回讀,這在線或的情況下非常有用(以支持雙向信號線)。GPIO控制器可能具有一個輸入防故障/防反跳邏輯,有時還會有軟件控制。
3.輸入經常被用作中斷信號,通常是邊沿觸發,但也有可能是電平觸發。這些中斷可以配置為系統喚醒事件,從而將系統從低功耗模式喚醒。
4.一個GPIO經常被配置為輸入/輸出雙向,根據不同的產品單板需求,但也存在單向的情況。
5.大多是GPIO可以在獲取到spinlock自旋鎖時訪問,但那些通過串行總線訪問的通常不能如此操作(休眠的原因)。一些系統中會同時存在這兩種形式的GPIO。
6.在一個給定單板上,每個GPIO用于一個特定的目的,如監控MMC/SD卡的插入/移除,檢查卡寫保護狀態,驅動LED,配置發送器,串行總線位拆,觸發一個硬件看門狗,觸發一個開關之類的。
二、gpio優點
低功耗:GPIO具有更低的功率損耗(大約1μA,μC的工作電流則為100μA)。
集成IIC從機接口:GPIO內置IIC從機接口,即使在待機模式下也能夠全速工作。
小封裝:GPIO器件提供最小的封裝尺寸 ― 3mm x 3mm QFN!
低成本:您不用為沒有使用的功能買單。
快速上市:不需要編寫額外的代碼、文檔,不需要任何維護工作。
靈活的燈光控制:內置多路高分辨率的PWM輸出。
可預先確定響應時間:縮短或確定外部事件與中斷之間的響應時間。
更好的燈光效果:匹配的電流輸出確保均勻的顯示亮度。
布線簡單:僅需使用2條就可以組成IIC總線或3條組成SPI總線。
與ARM 的幾組GPIO引腳,功能相似,GPxCON 控制引腳功能,GPxDAT用于讀寫引腳數據。另外,GPxUP用于確定是否使用上拉電阻。 x為A,B,,H/J,
GPAUP 沒有上拉電阻。
三、GPIO系統結構
如前面提醒的一樣,一個可選的實現結構使得平臺支持不同種類的GPIO控制器使用同一個編程接口變得簡單。這個結構稱為gpiolib。
作為一個調試目的,如果debugfs有效,一個/sys/kernel/debug/gpio文件在那里將被找到。它列出了所有的通過這個結構注冊的GPIO控制器,和GPIO當前的使用狀態。
Controller Drivers: gpio_chip
控制器驅動:gpio_chip
在這個架構中,每個GPIO控制器被封裝為一個“gpio_chip”結構體,此結構體中包含了每個控制器的通用信息:
--確定GPIO方向的方法
--存取GPIO值的方法
--聲明方法是否休眠的flag
--可選的debugfs dump方法(展現附加的狀態如上拉配置等)
--用于診斷目的的標簽
每個實例也有自己的私有數據,可能來自device.platform_data:它的第一個GPIO和它暴露幾個GPIO.
實現一個gpio_chip的代碼應該支持多個控制器的實例,可能使用驅動模型。代碼會配置每個gpio_chip并且執行gpiochip_add()。移除一個GPIO控制器是少見的,使用gpio_remove()移除一個不再有效的GPIO控制器。
大多數時候,一個gpio_chip是一個實例獨有的結構,它的一些狀態值不暴露給GPIO接口,如編址、電源管理等等。編碼解碼器之類的芯片會有復雜的非GPIO狀態。
所有的debugfs dump 方式通常應該忽略那些未作為GPIO請求的信號。他們可以使用gpiochip_is_requested(),此函數返回與GPIO相關的label或是NULL。
平臺支持
為了支持這個結構,一個平臺的Kconfig需要選擇ARCH_REQUIRE_GPIOLIB或是ARCH_WANT_OPTIONAL_GPIOLIB之一,且安排的它的《asm/gpio.h》包含《asm-generic/gpio.h》并且定義3個函數gpio_get_value(), gpio_set_value(), 和 gpio_cansleep()。
他也可以提供一個自定義的值:ARCH_NR_GPIOS,以便能更好的反映平臺實際使用的GPIO數目,并不浪費靜態區域空間。(它應該計數 內建/SOC GPIO和GPIO擴展器擴展的數目)
ARCH_REQUIRE_GPIOLIB意味著此平臺上gpiolib代碼將永久編譯進內核
ARCH_WANT_OPTIONAL_GPIOLIB意味著gpiolib代碼默認是關閉的,用于可以使能它并且將它可選的編譯進內核。
如果這些選項都未被選上,平臺不能通過GPIO-lib支持GPIO,這些代碼也不能被用戶使能。
那些函數瑣細的實現可以直接使用架構代碼,它們經常通過gpio_chip分配:
#define gpio_get_value__gpio_get_value
#define gpio_set_value__gpio_set_value
#define gpio_cansleep__gpio_cansleep
愛好者實現可以代替定義他們使用內聯函數,使用邏輯優化存取特定的基于SOC的GPIO。例如,如果 引用的GPIO是常數“12”,getting或setting它的值可能只需要2個或3個指令,且從不休眠。如果這樣一個優化是不可能的話,這些調用實現必須委托給架構代碼,它會耗費至少幾十個指令。為了位拆型I/O,這些指令的節約是有相當大的意義的。
對于SOC來說,平臺特定的代碼為每個bank的片上GPIO定義和注冊了gpio_chip實例。那些GPIO應該被編號和打上標簽以匹配芯片廠商文檔,且直接匹配單板設計圖。他們可以從零開始一直到平臺特定的限制。這些GPIO通常集成到單板初始化過程中以使得它們總是有效的,從arch_initcall()到更早,它們總是可以為中斷服務。
板級支持
對于外部GPIO控制器(如I2C或SPI擴展)、ASIC、多功能器件、FPGA或是CPLD,通常單板私有代碼例程注冊控制器器件且確定它們的驅動使用什么GPIO號來調用gpiochip_add。它們的號碼經常在平臺特定GPIO之后開始。
例如,單板setup代碼可以創建結構標示芯片想要暴露的GPIO的范圍,且使用platform_data傳遞它們到每個GPIO擴展器芯片。這樣芯片驅動的probe()歷程可以傳遞這些數據到gpiochip_add()。
初始化順序是很重要的。例如當一個依賴于基于I2C的GPIO的設備,它的probe()例程應該僅能在GPIO有效后調用。這意味著設備不能在GPIO可以工作之前注冊。一個解決這樣依賴的方法是在板級特定代碼中,對于這種gpio_chip控制器來提供setup()和teardown()回調,這些板級特定的回調將注冊設備一旦所有的需要資源有效時,并且在GPIO控制器無效時將它們移除。
用戶空間的Sysfs接口(可選)
使用gpiolib實現結構的平臺可以選擇為GPIO配置一個sysfs用戶接口。這與debugfs接口不同,因為它提供了覆蓋GPIO方向和值的控制而不只是顯示一個gpio狀態信息摘要。另外,它可以在產品系統中提供而不需要調試支持。
為系統給出對應的硬件文檔,用戶空間可以知道例如GPIO#23控制著保護線,用于保護flash中的boot區域。系統升級程序可能需要臨時移除這個保護,首先引入一個GPIO,然后改變它的輸出狀態,接下來在重新使能寫保護之前升級代碼。通常用法中,GPIO#23將不會被觸碰,并且內核也不需知道它的信息。
同樣依靠一個合適的硬件文檔,在一些系統用戶空間,GPIO可以被用于決定那些內核并不關心的系統配置數據。對于一些任務,簡單用戶空間GPIO驅動是系統真正需要的
注意,針對通用“LED和按鈕”的標準內核驅動存在對應的GPIO任務“leds-gpio”和“gpio-keys”。使用它們代替直接與GPIO通話,它們集成在內核架構比你的用戶態代碼可能更好。
Paths in Sysfs
Sysfs路徑
There are three kinds of entry in /sys/class/gpio:
/sys/class/gpio有3個入口條目:
-控制接口用于用戶空間獲取GPIO控制
-GPIO自己
-GPIO控制器(“gpio_chip”實例)
這是對于標準文件的補充,包括“device”符號
控制接口是只寫的:
/sys/class/gpio/
“export” ————通過寫GPIO的號碼到此文件,用戶空間可以要求內核導出一個GPIO的控制到用戶空間
例如:“echo 19 》 export”將創建一個GPIO #19的“gpio19”節點(假設內核代碼未申請此GPIO號)。
“unexport”————與“export”效果相反
例如:“echo 19 》 unexport”將移除一個由“export”文件導出的“gpio19”節點。
GPIO信號擁有如/sys/class/gpio/gpio42/(對應于GPIO#42)的路徑,并且具有下列讀寫屬性:
/sys/class/gpio/gpioN/
“direction”————讀為“in”或是“out”。這個值通常可寫。寫“out”默認初始化此值為低。為了確定無障礙操作,值“low”和“high”可以被寫入以配置GPIO的輸出初始化值。
注意這個屬性“將不存在”如果內核不支持改變一個GPIO的方向,或者它不能被內核代碼導出(不能顯式的允許用戶空間來重新配置GPIO的選項。)
“value”—————讀作“0”(低)或“1”(高)。如果GPIO被配置為一個輸出,這個值可寫;任何非零值均被視為高。
如果管腳可以被配置為中斷產生中斷管腳,且如果它已經被配置為產生中斷(參考“edge”描述),你可以poll(2)此文件并且當中斷觸發時poll(2)將返回。如果你使用了poll(2),設置POLLPRI和POLLERR事件。如果你使用select(2),在exceptfds中設置文件描述符。在poll(2)返回之后,有兩個選擇一是lseek(2)到sysfs文件的開始且讀新的值,另一個是關閉文件且重打開它來讀取新的值。(為何這樣設置?)
“edge”————讀作“none”、“rising”、“falling”或是“both”。寫這些字符串以選擇邊沿信號,他將使得“value”文件上的poll(2)操作返回。
這個文件只在管腳可以配置為中斷產生輸入管腳時存在。
“active_low”————讀為0(false)或1(true)。寫任何非零值都會反轉讀或寫的值。目前和后來的poll(2)支持經由edge屬性配置為“rising”或“falling”上升沿或下降沿將遵循這個設置。
GPIO控制器具有如/sys/class/gpio/gpiochip42/(針對控制器,實現GPIO開始于#42)的路徑,且具有下列制度屬性:
/sys/class/gpio/gpiochipN/
“base”————與N相等,是第一個被此芯片管理的GPIO
“label”————提供用于診斷(并不總是獨一無二的)
“ngpio”————管理的GPIO數(N到N+ngpio-1)
大多數情況下,單板文檔應該提供GPIO的使用目的。雖然如此這些號碼并非總是固定的,一個子板上的GPIO可能與基礎板使用的不同。此種情況下,你可能需要使用gpiochip節點(可能與設計結合)來為每個信號決定正確的GPIO號碼。
從內核代碼中導出
內核代碼可以顯式管理那些使用gpio_request()申請的GPIO的導出
/* export the GPIO to userspace */
int gpio_export(unsigned gpio, bool direction_may_change);
/* reverse gpio_export() */
void gpio_unexport();
/* create a sysfs link to an exported GPIO node */
int gpio_export_link(struct device *dev, const char *name,
unsigned gpio)
/* change the polarity of a GPIO node in sysfs */
int gpio_sysfs_set_active_low(unsigned gpio, int value);
一個內核驅動申請一個GPIO后,它可以使用gpio_export()使得sysfs接口有效。驅動可以控制信號方向是否可以改變。這使得驅動可以防止用戶空間代碼不小心沖擊重要的系統狀態。
明確的exporting有助于調試(使得一些實驗更簡單),或是提供一個總是可以使用的接口,適合于bsp文檔。
GPIO被導出后,gpio_export_link()允許在sysfs的任何地方創建GPIO sysfs節點的符號鏈接。驅動可以用此在它們自己設備sysfs目錄下提供指定名字的接口(鏈接到GPIO節點)
驅動可以使用gpio_sysfs_set_active_low()隱藏GPIO在用戶空間和單板之間的線極性不同。這僅影響sysfs接口。極性變換可以在gpio_export()之前和之后完成,并且前面使能的poll(2) (支持上升沿或下降沿事件)將被重新配置為遵循此設置。
四、GPIO使用方法
要使用GPIO,系統首先要分配一個GPIO,使用gpio_request() 為系統分配一個GPIO。
接下來要做的一件事是標示GPIO的方向,通常在使用GPIO建立一個platform_device時(位于單板的setup代碼中):
/* set as input or output, returning 0 or negative errno */
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
返回0標示成功,或是一個負的errno錯誤碼。它應該被檢查,因為get/set調用沒有錯誤返回,且可能會有錯誤配置。你通常應該在線程上下文中使用這些調用。雖然如此,對于spinlock-safe的GPIO,在tasking使能之前使用也是可以的,作為一個早期的單板建立。
對于輸出GPIO,value參數提供了初始輸出值。這有助于避免系統啟動過程中的信號干擾。
為了與GPIO早期的接口兼容,設置一個GPIO的方向,隱性要求申請GPIO。這個兼容性從可選的gpiolib架構中移除了。
如果GPIO號碼無效或是指定的GPIO不能使用對應模式操作的話,設置方向會失敗。依靠boot固件設置好GPIO的方向通常不是一個好主意,因為boot的功能可能沒有通過驗證(除了boot linux)。(類似的,單板setup代碼可能需要將管腳復用為一個GPIO,和配置為合適的上拉/下拉。)
Spinlock-Safe GPIO訪問
-------------------------
大多數GPIO控制器可以使用內存讀寫指令訪問。它們不需要休眠,且可以從內部硬件中斷處理(非線程)和類似的上下文環境安全完成。
使用下列調用訪問這些GPIO,此時gpio_cansleep將總是返回錯誤
/* GPIO INPUT: return zero or nonzero */
int gpio_get_value(unsigned gpio);
/* GPIO OUTPUT */
void gpio_set_value(unsigned gpio, int value);
其中,value是一個布爾型參數,零表示低,非零表示高。當讀一個輸出管腳的值時,返回的值應該是在管腳上看到的值。。。這并不總是與指定輸出值相匹配的,因為存在開漏信號和輸出延遲問題。
get/set調用沒有錯誤返回,因為“無效GPIO”應該已經由gpio_direction_*()提早報告了。雖然如此,并非所有的平臺都可以讀取輸出管腳的值,那些不能讀的應該總是返回零。同時,對那些可能導致睡眠的GPIO使用這些接口是一個錯誤。
平臺的特定實現被鼓勵優化這兩個調用以獲取GPIO值。在那些GPIO號碼是常量的情況下,它們通常只需一對指令(讀或寫一個硬件寄存器)訪問,且不需要spinlock。這樣的優化可以使位拆分應用更有效率(在時間和空間上)(相比較于花費一堆指令在子例程調用來說)。
評論
查看更多