1. 程式人生 > >imx6用檔案io操作gpio

imx6用檔案io操作gpio

要讓linux支援檔案io方式操作gpio,首先驅動必須得支援,也就是說裝置樹上必須先配置好gpio模式,然後參照以上鍊接去實現gpio操作

這裡舉例來說:hud專案中(imx6dl平臺),有一個藍芽電源的使能受GPIO1_IO30的控制,所以我們必須得在裝置樹上配置這個pad為GPIO模式

1.配置gpio模式

現在需要在裝置樹上配置GPIO1_IO32用於gpio, 在配置之前需要確定該pad為哪個pad, 經原理圖可知為ENET_TXD0/GPIO1_IO30, 然後在imx6dl規格書上第四章External Signals and Pin Multiplexing章節查詢到ENET_TXD0, 可以看出該pad可以複用成三種功能(ENET_TX_DATA0, ESAI_TX4_RX1, GPIO1_IO30), 這些巨集定義在imx6dl-pinfunc.h檔案中, 然後在用到的裝置樹上(imx6dl-hud.dtsi)上查詢是否已經有用到其他功能(現在要配置成gpio功能, 若已經配置成ENET_TX_DATA0則要刪除它)

MX6QDL_PAD_ENET_TXD0__GPIO1_IO30    0x80000000  /* bluetooth power enable */

這樣就配置好了gpio模式

然後使用檔案io操作gpio:

2.計算gpio號:

nr=(P -1)* 32 + N; gpioP_N;

這裡nr=(1-1)*32 + 30=30

3.io檔案操作gpio

echo 30 > /sys/class/gpio/export

echo out > /sys/class/gpio/gpio30/direction

echo 1 > /sys/class/gpio/gpio30/value    ---> 寫高電平

echo 0 > /sys/class/gpio/gpio30/value    ---> 寫低電平

Linux下用檔案IO的方式操作GPIO


通過sysfs方式控制GPIO,先訪問/sys/class/gpio目錄,向export檔案寫入GPIO編號,使得該GPIO的操作介面從核心空間暴露到使用者空間,GPIO的操作介面包括directionvalue等,direction控制GPIO方向(是控制輸出還是輸入),而value可控制GPIO輸出或獲得GPIO輸入(通過它設定引腳的值是寫入1或0)。檔案IO方式操作GPIO,使用到了4個函式openclosereadwrite

首先,看看系統中有沒有“/sys/class/gpio”這個資料夾。如果沒有請在編譯核心的時候加入   Device Drivers-> GPIO Support ->/sys/class/gpio/… (sysfs interface)

/sys/class/gpio 的使用說明:

程式設計步驟:

 控制GPIO的目錄位於/sys/class/gpio

 /sys/class/gpio/export檔案用於通知系統需要匯出控制的GPIO引腳編號

 /sys/class/gpio/unexport 用於通知系統取消匯出

 /sys/class/gpio/gpiochipX目錄儲存系統中GPIO暫存器的資訊,包括每個暫存器控制引腳的起始編號base,暫存器名稱,引腳總數 匯出一個引腳的操作步驟

 首先計算此引腳編號,引腳編號 = 控制引腳的暫存器基數 + 控制引腳暫存器位數(例如GPIO4_21的引腳編號計算:首先查你使用的開發板的使用者手冊我的是i.MX 6UltraLite Applications Processor Reference Manual。在GPIO那章節中找到GPIO1_0與/sys/class/gpio/gpiochipX的對應關係,我的是GPIO1_0對應/sys/class/gpio/gpiochip0,所以CPIO4_21對應/sys/class/gpio/gpiochip96,即GPIO4的基數是96,GPIO4_21=96+21=117引腳編號

 /sys/class/gpio/export寫入此編號,比如GPIO4_21號引腳,在shell中可以通過以下命令實現,命令成功後生成/sys/class/gpio/gpio117目錄,如果沒有出現相應的目錄,說明此引腳不可匯出

 direction檔案,定義輸入輸入方向,可以通過下面命令定義為輸出。direction接受的引數:in, out, high, lowhigh/low同時設定方向為輸出,並將value設定為相應的1/0

 value檔案是埠的數值,為10

幾個例子:

操作GPIO4_21引腳

1. 匯出

/sys/class/gpio# echo 117 > export

2. 設定方向

/sys/class/gpio/gpio117# echo out > direction

3. 檢視方向

/sys/class/gpio/gpio117# cat direction

out

4. 設定輸出

/sys/class/gpio/gpio117# echo 1 > value

5. 檢視輸出值

/sys/class/gpio/gpio117# cat value

1

6. 取消匯出

/sys/class/gpio# echo 117 > unexport

  1.  檔案讀寫例程:  
  2. #include stdlib.h  
  3. #include stdio.h  
  4. #include string.h
  5. #include unistd.h
  6. #include fcntl.h   
  7. //define O_WRONLY and O_RDONLY  
  8. //晶片復位引腳: CPIO4_21
  9. #define SYSFS_GPIO_EXPORT           "/sys/class/gpio/export"  
  10. #define SYSFS_GPIO_RST_PIN_VAL      "117"   
  11. #define SYSFS_GPIO_RST_DIR          "/sys/class/gpio/gpio117<span style="word-wrap:normal; word-break:normal"><span style="word-wrap:normal; word-break:normal; line-height:24px; font-size:16px"></span></span>/direction"
  12. #define SYSFS_GPIO_RST_DIR_VAL      "OUT"  
  13. #define SYSFS_GPIO_RST_VAL          "/sys/class/gpio/gpio117/value"
  14. #define SYSFS_GPIO_RST_VAL_H        "1"
  15. #define SYSFS_GPIO_RST_VAL_L        "0"
  16. int main()   
  17. {   
  18.     int fd;   
  19.          //開啟埠/sys/class/gpio# echo 117 > export
  20.          fd = open(SYSFS_GPIO_EXPORT, O_WRONLY);  
  21.          if(fd == -1)  
  22.          {  
  23.                    printf("ERR: Radio hard reset pin open error.\n");  
  24.                    return EXIT_FAILURE;  
  25.          }  
  26.          write(fd, SYSFS_GPIO_RST_PIN_VAL ,sizeof(SYSFS_GPIO_RST_PIN_VAL));   
  27.          close(fd);   
  28.          //設定埠方向/sys/class/gpio/gpio117# echo out > direction
  29.          fd = open(SYSFS_GPIO_RST_DIR, O_WRONLY);  
  30.          if(fd == -1)  
  31.          {  
  32.                    printf("ERR: Radio hard reset pin direction open error.\n");  
  33.                    return EXIT_FAILURE;  
  34.          }  
  35.          write(fd, SYSFS_GPIO_RST_DIR_VAL, sizeof(SYSFS_GPIO_RST_DIR_VAL));   
  36.          close(fd);   
  37.          //輸出復位訊號: 拉高>100ns
  38.          fd = open(SYSFS_GPIO_RST_VAL, O_RDWR);  
  39.          if(fd == -1)  
  40.          {  
  41.                    printf("ERR: Radio hard reset pin value open error.\n");  
  42.                    return EXIT_FAILURE;  
  43.          }         
  44.          while(1)  
  45.          {  
  46.                    write(fd, SYSFS_GPIO_RST_VAL_H, sizeof(SYSFS_GPIO_RST_VAL_H));  
  47.                    usleep(1000000);  
  48.                    write(fd, SYSFS_GPIO_RST_VAL_L, sizeof(SYSFS_GPIO_RST_VAL_L));  
  49.                    usleep(1000000);  
  50.          }  
  51.          close(fd);  
  52.          printf("INFO: Radio hard reset pin value open error.\n");  
  53.          return0;  
  54. }  <span style="word-wrap:normal; word-break:normal"></span>  

GPIO應用開發方法

在Linux的應用層程式中,可以使用系統中的GPIOLIB模組在使用者空間提供的sysfs介面,實現應用層對GPIO的獨立控制。本節介紹的GPIO的這種操作方式是在Linux 2.6.35核心之後引入的一種GPIOLIB的管理機制,GPIOLIB提供了很好的使用者介面封裝,為使用者提供了一個動態匯出的介面。

在實驗箱中執行的Linux系統的/sys/class/gpio目錄下,共有5個檔案,其中有3個檔案為符號連結(gpiochip0、gpiochip5、gpiochip244),指向管理對應裝置的目錄。這3個符號連結分別對應實驗箱中能夠分別控制對應GPIO的3個管理晶片。上一節中涉及的GPIO都是從與gpiochip244對應的晶片中引出的。因此,本節主要討論gpiochip244對應晶片的GPIO管理,但是此處介紹的操作方法同樣也適用於gpiochip0和gpiochip5。

在/sys/class/gpio/gpiochip244目錄下,共有3個檔案和3個資料夾,主要作用見表4-2。

表4-2  gphichip244目錄下檔案的作用

文 件 名類    型屬    性作    用
label檔案只讀裝置資訊
base檔案只讀裝置所管理的GPIO初始編號
ngpio檔案只讀裝置所管理的GPIO總數
power目錄裝置供電方面的相關資訊
subsystem目錄符號連結,指向父目錄
uevent檔案讀寫核心與udev(自動裝置發現程式)之間的通訊介面

在這個目錄下,base和ngpio這兩個檔案為開發人員提供了重要的資訊。在gpiochip244目錄下,base檔案中的內容為"244"(字串型別),ngpio檔案中的內容為"12"。這兩條資訊說明,該外設管理了編號從244到255的12個GPIO介面。在實驗箱中只有編號在248到255之間的8個GPIO被引出。

對其中某個GPIO介面的控制主要需要進行如下包含3個步驟的操作:

(1) 匯出GPIO介面

在/sys/class/gpio目錄中有兩個只具有寫屬性的檔案:export和unexport。通過對這兩個檔案進行操作可以實現對GPIO介面的匯出。

下面舉例說明,為了對255號GPIO介面進行匯出,可以在終端中通過下面的操作來完成:

  1. cd  /sys/class/gpio   //進入相應的目錄  
  2. echo 255 > export   //將"255"(字串型別)寫入檔案export 

將"255"寫入export檔案後,系統會自動在/sys/class/gpio下建立gpio255目錄。這說明對編號為255的GPIO介面匯出成功。

(2) 設定GPIO屬性

在gpio255目錄下,系統會自動產生6個檔案。其中,power、subsystem和uevent這3個檔案的功能與表4-2中描述的功能相同。

其他的3個檔案--value、direction和active_low都具有讀寫屬性,用於完成對GPIO介面的控制。

value:具有讀寫屬性,表示當前GPIO介面的電平狀態。當GPIO的方向為輸入時,可以通過value讀出當前GPIO介面的電平狀態高低("1"/"0",均以ASCII碼錶示);當GPIO方向為輸出時,可以向該檔案寫入"1"/"0",控制當前GPIO介面的高/低電平。

direction:具有讀寫屬性,控制GPIO介面的輸入輸出方向。如果將"out"寫入該檔案,該GPIO介面為輸出狀態;如果將"in"寫入該檔案,該GPIO介面為輸入狀態;如果將"high"寫入該檔案,那麼在將GPIO介面置為輸出狀態的同時,也將value的值置為"1";如果將"low"寫入value檔案,那麼在將GPIO介面置為輸出狀態的同時,將"0"寫入value檔案。通過對direction檔案的讀操作還可以判斷當前GPIO介面的輸入/輸出狀態("in"/"out")。

active_low:具有讀寫屬性,值為"0"或"1",用於決定value中的值是否進行翻轉。當值為"0"時,value中的"0"表示低電平,"1"表示高電平;當值為"1"時,value中的"1"表示低電平,"0"表示高電平。

(3) GPIO介面匯出的取消

將取消匯出的GPIO編號寫入檔案unexport中,對應的GPIO介面將會被取消匯出。相對的,在檔案系統中建立的目錄也會消失。

例如,取消255號GPIO介面的匯出:

  1. echo 255 > unexport   //將"255"(字串型別)寫入檔案unexport  

Linux無需開發底層驅動,從應用層獲取GPIO中斷


獲取中斷

  1. GPIO中斷在嵌入式開發中經常用到,到了linux下,處理GPIO的中斷就沒有裸機那麼簡單了。 Linux核心中有一套GPIO框架,管理和控制晶片上的GPIO管教,包括配置輸入輸出,配置電平高低(輸出)和獲取電平高低(輸入),中斷管理。
  2. CPU廠家需要按照GPIO框架的介面,實現底層的具體控制。一般的話,廠家提供的SDK都已經開發好了,不需要客戶去開發。
  3. 應用層上控制GPIO網上有許多資料,但是獲取中斷,網上的大部分資料都是需要開發底層驅動,由底層驅動獲取到GPIO的中斷然後再通知應用層。
  4. 對於不想開發驅動的我來說,還有另外一種方法可以直接從應用層上獲取到GPIO的中斷,Linux核心文件Documentation/gpio/gpio-legacy.txt:691中提到的:
691     "value" ... reads as either 0 (low) or 1 (high).  If the GPIO
692         is configured as an output, this value may be written;
693         any nonzero value is treated as high.
694 
695         If the pin can be configured as interrupt-generating interrupt
696         and if it has been configured to generate interrupts (see the
697         description of "edge"), you can poll(2) on that file and
698         poll(2) will return whenever the interrupt was triggered. If
699         you use poll(2), set the events POLLPRI and POLLERR. If you
700         use select(2), set the file descriptor in exceptfds. After
701         poll(2) returns, either lseek(2) to the beginning of the sysfs
702         file and read the new value or close the file and re-open it
703         to read the value.
704 
705     "edge" ... reads as either "none", "rising", "falling", or
706         "both". Write these strings to select the signal edge(s)
707         that will make poll(2) on the "value" file return.
708 
709         This file exists only if the pin can be configured as an
710         interrupt generating input pin.

就是說可以通過讀取/sys/class/gpio/gpioN/value的值來獲取中斷。 
5. 但是不是簡單的read,而是通過epoll、poll、select等這些IO複用函式來控制,對於epoll或者poll,需要監聽的事件是EPOLLPRI或POLLPRI,而不是EPOLLIN或POLLIN,對於select,需要將檔案描述符放在exceptfds中,而且檔案描述符被觸發時需要通過呼叫read讀取資料,還要通過lseek將檔案流指標置迴文件開頭。

例如

對於epoll來說,虛擬碼如下

...
#include <sys/epoll.h>
...
int main(int argc,char * argv[]){

    struct epoll_event evd;
    struct epoll_event * events;
    ...
    int epollfd = epoll_create(10);
    ...
    events = calloc (10, sizeof(struct epoll_event));
    evd.data.fd = fd;  //fd 即為open /sys/class/gpio/gpioN/value返回的控制代碼
    evd.events = EPOLLPRI;
    epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&evd); 
    while (1) {
        n = epoll_wait(epollfd,events,10,-1);
        for (i = 0;i < n;i++) {
            if (events[i].events & EPOLLPRI) {
                memset(buf,0x00,sizeof(buf));
                read(events[i].data.fd,buf,sizeof(buf));
                lseek(events[i].data.fd,0,SEEK_SET);
                //do yourself
            }
        }
    }
}

對於poll,虛擬碼如下

...
#include <sys/poll.h>
...
int main(int argc,char* argv[]){
    struct pollfd fdset;
    unsigned char buf[128];
    while (1) {
        memset(&fdset,0x00,sizeof(struct pollfd));
        fdset.fd = fd; //fd 即為open /sys/class/gpio/gpioN/value返回的控制代碼
        fdset.events = POLLPRI;
        poll(&fdset,1,3000);
        if (fdset.events & POLLPRI) {
            read(fdset.fd,buf,sizeof(buf));
            lseek(fdset.fd,0,SEEK_SET);
            //do yourself
        }
    }
}

select的話我就不寫了,一樣的做法。 
另,我實際測試,使用epoll或者poll時監聽事件(POLLIN | POLLET)也是可以的。