嵌入式Linux设备驱动程序:用户空间中的设备驱动程序
嵌入式Linux設備驅動程序:用戶空間中的設備驅動程序
Embedded Linux
device drivers: Device drivers in user space
Interfacing with Device Drivers
Device drivers in user space
用戶空間中的設備驅動程序
在開始編寫設備驅動程序之前,請暫停片刻,考慮是否確實有必要。對于許多常見類型的設備,有通用的設備驅動程序,允許您直接從用戶空間與硬件交互,而不必編寫一行內核代碼。用戶空間代碼當然更容易編寫和調試。它也不包括在GPL中,盡管我覺得這本身并不是一個很好的理由來這樣做。
這些驅動程序分為兩大類:通過sysfs中的文件(包括GPIO和led)進行控制的驅動程序,以及通過設備節點(如I2C)公開通用接口的串行總線。
GPIO
通用輸入/輸出(GPIO)是最簡單的數字接口形式,因為它可以直接訪問各個硬件引腳,每個引腳可以處于兩種狀態之一:高或低。在大多數情況下,您可以將GPIO管腳配置為輸入或輸出。你甚至可以使用一組GPIO管腳,通過操作軟件中的每個位來創建更高級的接口,比如I2C或SPI,這種技術被稱為位碰撞。主要的限制是軟件循環的速度和精度,以及您希望專用于它們的CPU周期數。一般來說,除非配置一個實時內核,否則很難達到比毫秒更好的計時器精度。GPIO更常見的用例是讀取按鈕和數字傳感器以及控制led、電機和繼電器。
大多數soc有很多GPIO位,這些位被組合在GPIO寄存器中,通常每個寄存器32位。片上GPIO位通過多路復用器(稱為pinmux)路由到芯片封裝上的GPIO管腳。在電源管理芯片和專用的GPIO擴展器中,可能有額外的GPIO引腳,通過I2C或SPI總線連接。所有這些多樣性都由一個名為gpiolib的內核子系統來處理,它實際上不是一個庫,而是GPIO驅動程序用來以一致的方式公開I/O的基礎設施。在Documentation/gpio的內核源代碼中有關于gpiolib實現的詳細信息,驅動程序本身的代碼在drivers/gpio中。
應用程序可以通過/sys/class/gpio目錄中的文件與gpiolib交互。下面是一個典型的嵌入式板(BeagleBone Black)的示例:
ls /sys/class/gpio
export
gpiochip0 gpiochip32 gpiochip64 gpiochip96
unexport
名為gpiochip0到gpiochip96的目錄代表四個GPIO寄存器,每個寄存器有32個GPIO位。如果您查看其中一個gpiochip目錄,您將看到以下內容:
ls
/sys/class/gpio/gpiochip96
base
label ngpio power subsystem uevent
名為base的文件包含寄存器中第一個GPIO引腳的編號,ngpio包含寄存器中的位數。在本例中,gpiochip96/base是96,gpiochip96/ngpio是32,這說明它包含GPIO位96到127。在一個寄存器中的最后一個GPIO和下一個寄存器中的第一個GPIO之間可能存在間隙。 要從用戶空間控制GPIO位,首先必須從內核空間導出它,這是通過將GPIO編號寫入/sys/class/GPIO/export來完成的。此示例顯示GPIO 53的處理過程,它連接到BeagleBone Black上的用戶LED 0:
echo 53 >
/sys/class/gpio/export
ls
/sys/class/gpio
export gpio53 gpiochip0
gpiochip32 gpiochip64 gpiochip96 unexport
現在,有一個新的目錄gpio53,其中包含控制pin所需的文件。
如果GPIO位已經被內核聲明,您將無法以這種方式導出它。
目錄gpio53包含以下文件:
ls
/sys/class/gpio/gpio53
active_low direction
power uevent
device
edge subsystem value
管腳從輸入開始。若要將其更改為輸出,請寫入方向文件。文件值包含管腳的當前狀態,0表示低,1表示高。如果是輸出,則可以通過將0或1寫入值來更改狀態。有時,在硬件中,低和高的含義是相反的(硬件工程師喜歡做這種事情),所以寫1到active_low會顛倒值的含義,這樣低電壓報告為1,高電壓報告為0。
通過將GPIO編號寫入到/sys/class/gpio/unexport,可以從用戶空間控件中刪除GPIO。
處理來自GPIO的中斷
在許多情況下,可以將GPIO輸入配置為在狀態改變時生成中斷,這允許您等待中斷,而不是在效率低下的軟件循環中輪詢。如果GPIO位可以生成中斷,則存在名為edge的文件。最初,它的值稱為none,這意味著它不會生成中斷。要啟用中斷,可以將其設置為以下值之一:
上升:上升沿中斷
下降:下降沿中斷
兩個:在上升和下降邊緣都中斷
無:無中斷(默認)
可以使用poll()函數和POLLPRI作為事件等待中斷。如果要等待GPIO 48上的上升沿,請首先啟用中斷:
echo 48 >
/sys/class/gpio/export
echo falling
/sys/class/gpio/gpio48/edge
然后,使用poll(2)等待更改,如下面的代碼示例所示,您可以在 MELP/chapter_09/gpio-int/gpio-int.c中的代碼存檔一書中看到
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <poll.h> int main(int argc, char *argv[]) { int f; struct pollfd poll_fds[1]; int ret; char value[4]; int n; f = open("/sys/class/gpio/gpio48/value", O_RDONLY); if (f == -1) { perror(“Can’t open gpio48”); return 1; } n = read(f, &value, sizeof(value)); if (n > 0) { printf(“Initial value=%cn”, value[0]); lseek(f, 0, SEEK_SET); } poll_fds[0].fd = f; poll_fds[0].events = POLLPRI | POLLERR; while (1) { printf(“Waitingn”); ret = poll(poll_fds, 1, -1); if (ret > 0) { n = read(f, &value, sizeof(value)); printf(“Button pressed: value=%cn”, value[0]); lseek(f, 0, SEEK_SET); } } return 0; }
發光二極管
led通常通過GPIO管腳進行控制,但是還有另一個內核子系統提供了更專門的控制。LED內核子系統增加了設置亮度的功能,如果LED有這種能力,它可以處理以其他方式連接的LED,而不是簡單的GPIO管腳。它可以配置為觸發事件,如阻止設備訪問或只是一個心跳信號,以顯示設備正在工作。您必須使用選項CONFIG_LEDS_CLASS和適合您的LED觸發器操作來配置內核。有關文檔/led/的詳細信息,驅動程序位于drivers/led/中。
與GPIOs一樣,led是通過sysfs中目錄為/sys/class/led的接口控制的。在BeagleBone Black的情況下,led的名稱以d的形式編碼在設備樹中設備名稱:顏色:函數devicename:colour:function,如下所示:
ls /sys/class/leds
beaglebone:green:heartbeat beaglebone:green:usr2
beaglebone:green:mmc0
beaglebone:green:usr3
現在,我們可以查看其中一個指示燈的屬性,注意shell要求路徑名中的冒號字符“:”前面必須有一個反斜杠轉義符“”:
cd
/sys/class/leds/beaglebone:green:usr2
ls
brightness max_brightness
subsystem uevent
device power trigger
亮度文件控制LED的亮度,可以是0(關閉)和最大亮度(完全打開)之間的數字。如果LED不支持中等亮度,任何非零值都會將其打開。名為trigger的文件列出了觸發LED亮起的事件。觸發器列表取決于實現。下面是一個例子:
cat trigger
none mmc0 mmc1 timer oneshot
heartbeat backlight gpio [cpu0]
default-on
當前選定的觸發器顯示在方括號中。您可以通過將其他觸發器之一寫入文件來更改它。如果要完全通過亮度控制LED,請選擇“無”。如果將觸發器設置為計時器,則會出現兩個額外文件,允許您以毫秒為單位設置開關時間:
echo timer > trigger
ls
brightness delay_on max_brightness
subsystem uevent
delay_off device power trigger
cat delay_on
500
cat
/sys/class/leds/beaglebone:green:heartbeat/delay_off
500
如果LED具有片上定時器硬件,則閃爍不會中斷CPU。
I2C
I2C是一種簡單的低速2線總線,在嵌入式板上很常見,通常用于訪問不在SoC上的外圍設備,如顯示控制器、攝像頭傳感器、GPIO擴展器等。在PCs上有一個稱為系統管理總線(SMBus)的相關標準,用于訪問溫度和電壓傳感器。SMBus是I2C的一個子集。
I2C是一種主從協議,主機是SoC上的一個或多個主機控制器。從機有一個由制造商分配的7位地址(請閱讀數據表),每個總線最多允許128個節點,但保留了16個節點,因此實際上只允許112個節點。主機可以與其中一個從機啟動讀或寫事務。通常,第一個字節用于指定從機上的寄存器,其余字節是從該寄存器讀取或寫入的數據。
每個主機控制器有一個設備節點,例如,該SoC有四個:
ls -l /dev/i2c*
crw-rw—- 1 root
i2c 89, 0 Jan 1 00:18 /dev/i2c-0
crw-rw—- 1 root i2c 89, 1 Jan 1 00:18 /dev/i2c-1
crw-rw—- 1 root i2c 89, 2 Jan 1 00:18 /dev/i2c-2
crw-rw—- 1 root i2c 89, 3 Jan 1 00:18 /dev/i2c-3
設備接口提供一系列ioctl命令,用于查詢主機控制器并將讀寫命令發送到I2C從機。有一個名為i2c-tools的包,它使用這個接口提供與i2c設備交互的基本命令行工具。工具如下:
· i2cdetect : This lists the I2C adapters, and probes the bus
· i2cdump : This dumps data from all the registers of an I2C
peripheral
· i2cget : This reads data from an I2C slave
· i2cset : This writes data to an I2C slave
i2c工具包可以在Buildroot和Yocto項目以及大多數主流發行版中獲得。所以,只要你知道從機的地址和協議,編寫一個用戶空間程序來與設備對話是很簡單的。下面的示例顯示如何從安裝在I2C總線0上BeagleBone Black上的從機地址0x50上的AT24C512B EEPROM讀取前四個字節(代碼在MELP/章節U 09/I2C示例中):
#include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <sys/ioctl.h> #include <linux/i2c-dev.h> #define I2C_ADDRESS 0x50 int main(void) { int f; int n; char buf[10]; f = open("/dev/i2c-0", O_RDWR); /* Set the address of the i2c slave device / ioctl(f, I2C_SLAVE, I2C_ADDRESS); / Set the 16-bit address to read from to 0 / buf[0] = 0; / address byte 1 / buf[1] = 0; / address byte 2 / n = write(f, buf, 2); / Now read 4 bytes from that address */ n = read(f, buf, 4); printf(“0x%x 0x%x0 0x%x 0x%xn”, buf[0], buf[1], buf[2], buf[3]); close(f); return 0; }
在Documentation/I2C/dev interface中有關于I2C的Linux實現的更多信息。主控制器驅動程序位于drivers/i2c/總線中。
串行外設接口(SPI)
SPI總線類似于I2C,但速度更快,最高可達數十兆赫。在雙工線路中使用四條獨立的雙工線路進行操作??偩€上的每個芯片都通過專用芯片選擇線進行選擇。它通常用于連接觸摸屏傳感器、顯示控制器和串行或閃存設備。
與I2C一樣,它是主從協議,大多數SOC實現一個或多個主主機控制器。有一個通用的SPI設備驅動程序,您可以通過內核配置CONFIG_SPI峎SPIDEV來啟用它。它為每個SPI控制器創建一個設備節點,允許您從用戶空間訪問SPI芯片。設備節點被命名為spidev[bus].[chip select]:
ls -l /dev/spi*
crw-rw—- 1 root root 153, 0 Jan 1 00:29 /dev/spidev1.0
有關使用spidev接口的示例,請參閱Documentation/spi中的示例代碼。
總結
以上是生活随笔為你收集整理的嵌入式Linux设备驱动程序:用户空间中的设备驱动程序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 嵌入式Linux设备驱动程序:发现硬件配
- 下一篇: 嵌入式Linux设备驱动程序:在运行时读