天天看點

Linux系統GPIO應用程式設計應用層如何操控GPIOGPIO 應用程式設計之輸出GPIO 應用程式設計之輸入GPIO 應用程式設計之中斷在開發闆上測試

目錄

  • 應用層如何操控GPIO
  • GPIO 應用程式設計之輸出
  • GPIO 應用程式設計之輸入
  • GPIO 應用程式設計之中斷
  • 在開發闆上測試
    • GPIO 輸出測試
    • GPIO 輸入測試
    • GPIO 中斷測試

本章介紹應用層如何控制GPIO,譬如控制GPIO 輸出高電平、或輸出低電平。

應用層如何操控GPIO

與LED 裝置一樣,GPIO 同樣也是通過sysfs 方式進行操控,進入到/sys/class/gpio 目錄下,如下所示:

Linux系統GPIO應用程式設計應用層如何操控GPIOGPIO 應用程式設計之輸出GPIO 應用程式設計之輸入GPIO 應用程式設計之中斷在開發闆上測試

可以看到該目錄下包含兩個檔案export、unexport 以及5 個gpiochipX(X 等于0、32、64、96、128)命名的檔案夾。

⚫ gpiochipX:目前SoC 所包含的GPIO 控制器,我們知道I.MX6UL/I.MX6ULL 一共包含了5 個GPIO控制器,分别為GPIO1、GPIO2、GPIO3、GPIO4、GPIO5,在這裡分别對應gpiochip0、gpiochip32、gpiochip64、gpiochip96、gpiochip128 這5 個檔案夾,每一個gpiochipX 檔案夾用來管理一組GPIO。随便進入到其中某個目錄下,可以看到這些目錄下包含了如下檔案:

Linux系統GPIO應用程式設計應用層如何操控GPIOGPIO 應用程式設計之輸出GPIO 應用程式設計之輸入GPIO 應用程式設計之中斷在開發闆上測試

在這個目錄我們主要關注的是base、label、ngpio 這三個屬性檔案,這三個屬性檔案均是隻讀、不可寫。

base:與gpiochipX 中的X 相同,表示該控制器所管理的這組GPIO 引腳中最小的編号。每一個GPIO引腳都會有一個對應的編号,Linux 下通過這個編号來操控對應的GPIO 引腳。

Linux系統GPIO應用程式設計應用層如何操控GPIOGPIO 應用程式設計之輸出GPIO 應用程式設計之輸入GPIO 應用程式設計之中斷在開發闆上測試

label:該組GPIO 對應的标簽,也就是名字。

Linux系統GPIO應用程式設計應用層如何操控GPIOGPIO 應用程式設計之輸出GPIO 應用程式設計之輸入GPIO 應用程式設計之中斷在開發闆上測試

ngpio:該控制器所管理的GPIO 引腳的數量(是以引腳編号範圍是:base ~ base+ngpio-1)。

Linux系統GPIO應用程式設計應用層如何操控GPIOGPIO 應用程式設計之輸出GPIO 應用程式設計之輸入GPIO 應用程式設計之中斷在開發闆上測試

對于給定的一個GPIO 引腳,如何計算它在sysfs 中對應的編号呢?其實非常簡單,譬如給定一個GPIO引腳為GPIO4_IO16,那它對應的編号是多少呢?首先我們要确定GPIO4 對應于gpiochip96,該組GPIO 引腳的最小編号是96(對應于GPIO4_IO0),是以GPIO4_IO16 對應的編号自然是96 + 16 = 112;同理

GPIO3_IO20 對應的編号是64 + 20 = 84。

⚫ export:用于将指定編号的GPIO 引腳導出。在使用GPIO 引腳之前,需要将其導出,導出成功之後才能使用它。注意export 檔案是隻寫檔案,不能讀取,将一個指定的編号寫入到export 檔案中即可将對應的GPIO 引腳導出,譬如:

echo 0 > export # 導出編号為0 的GPIO 引腳(對于I.MX6UL/I.MX6ULL 來說,也就是
GPIO1_IO0)
           
Linux系統GPIO應用程式設計應用層如何操控GPIOGPIO 應用程式設計之輸出GPIO 應用程式設計之輸入GPIO 應用程式設計之中斷在開發闆上測試

導出成功之後會發現在/sys/class/gpio 目錄下生成了一個名為gpio0 的檔案夾(gpioX,X 表示對應的編号),如圖16.1.7 所示。這個檔案夾就是導出來的GPIO 引腳對應的檔案夾,用于管理、控制該GPIO 引腳,稍後再給大家介紹。

Linux系統GPIO應用程式設計應用層如何操控GPIOGPIO 應用程式設計之輸出GPIO 應用程式設計之輸入GPIO 應用程式設計之中斷在開發闆上測試

⚫ unexport:将導出的GPIO 引腳删除。當使用完GPIO 引腳之後,我們需要将導出的引腳删除,同樣該檔案也是隻寫檔案、不可讀,譬如:

echo 0 > unexport # 删除導出的編号為0 的GPIO 引腳
           

删除成功之後,之前生成的gpio0 檔案夾就會消失!

以上就給大家介紹了/sys/class/gpio 目錄下的所有檔案和檔案夾,控制GPIO 引腳主要是通過export 導出之後所生成的gpioX(X 表示對應的編号)檔案夾,在該檔案夾目錄下存在一些屬性檔案可用于控制GPIO引腳的輸入、輸出以及輸出的電平狀态等。

Tips:需要注意的是,并不是所有GPIO 引腳都可以成功導出,如果對應的GPIO 已經在核心中被使用了,那便無法成功導出,列印如下資訊:

Linux系統GPIO應用程式設計應用層如何操控GPIOGPIO 應用程式設計之輸出GPIO 應用程式設計之輸入GPIO 應用程式設計之中斷在開發闆上測試

那也就是意味着該引腳已經被核心使用了,譬如某個驅動使用了該引腳,那麼将無法導出成功!

gpioX

将指定的編号寫入到export 檔案中,可以導出指定編号的GPIO 引腳,導出成功之後會在/sys/class/gpio目錄下生成對應的gpioX(X 表示GPIO 的編号)檔案夾,以前面所生成的gpio0 為例,進入到gpio0 目錄,該目錄下的檔案如下所示:

Linux系統GPIO應用程式設計應用層如何操控GPIOGPIO 應用程式設計之輸出GPIO 應用程式設計之輸入GPIO 應用程式設計之中斷在開發闆上測試

我們主要關心的檔案是active_low、direction、edge 以及value 這四個屬性檔案,接下來分别介紹這四個屬性檔案的作用:

⚫ direction:配置GPIO 引腳為輸入或輸出模式。該檔案可讀、可寫,讀表示檢視GPIO 目前是輸入還是輸出模式,寫表示将GPIO 配置為輸入或輸出模式;讀取或寫入操作可取的值為"out"(輸出模式)和"in"(輸入模式),如下所示:

Linux系統GPIO應用程式設計應用層如何操控GPIOGPIO 應用程式設計之輸出GPIO 應用程式設計之輸入GPIO 應用程式設計之中斷在開發闆上測試

⚫ value:在GPIO 配置為輸出模式下,向value 檔案寫入"0"控制GPIO 引腳輸出低電平,寫入"1"則控制GPIO 引腳輸出高電平。在輸入模式下,讀取value 檔案擷取GPIO 引腳目前的輸入電平狀态。譬如:

# 擷取GPIO 引腳的輸入電平狀态
echo "in" > direction
cat value

# 控制GPIO 引腳輸出高電平
echo "out" > direction
echo "1" > value
           

⚫ active_low:這個屬性檔案用于控制極性,可讀可寫,預設情況下為0,譬如:

# active_low 等于0 時
echo "0" > active_low
echo "out" > direction
echo "1" > value 	#輸出高
echo "0" > value 	#輸出低

# active_low 等于1 時
$ echo "1" > active_low
$ echo "out" > direction
$ echo "1" > value 	#輸出低
$ echo "0" > value 	#輸出高
           

由此看出,active_low 的作用已經非常明顯了,對于輸入模式來說也同樣适用。

⚫ edge:控制中斷的觸發模式,該檔案可讀可寫。在配置GPIO 引腳的中斷觸發模式之前,需将其設定為輸入模式:

非中斷引腳:echo “none” > edge

上升沿觸發:echo “rising” > edge

下降沿觸發:echo “falling” > edge

邊沿觸發:echo “both” > edge

當引腳被配置為中斷後可以使用poll()函數監聽引腳的電平狀态變化,在後面的示例中将向大家介紹。

GPIO 應用程式設計之輸出

上一小節已經向大家介紹了如何通過sysfs 方式控制開發闆上的GPIO 引腳,本小節我們編寫一個簡單地測試程式,控制開發闆上的某一個GPIO 輸出高、低不同的電平狀态,其示例代碼如下所示:

本例程源碼對應的路徑為:開發闆CD光牒->11、Linux C 應用程式設計例程源碼->16_gpio->gpio_out.c。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

static char gpio_path[100];

static int gpio_config(const char *attr, const char *val)
{
    char file_path[100];
    int len;
    int fd;

    sprintf(file_path, "%s/%s", gpio_path, attr);
    if (0 > (fd = open(file_path, O_WRONLY))) {
        perror("open error");
        return fd;
    }

    len = strlen(val);
    if (len != write(fd, val, len)) {
        perror("write error");
        close(fd);
        return -1;
    }

    close(fd);  //關閉檔案
    return 0;
}

int main(int argc, char *argv[])
{
    /* 校驗傳參 */
    if (3 != argc) {
        fprintf(stderr, "usage: %s <gpio> <value>\n", argv[0]);
        exit(-1);
    }

    /* 判斷指定編号的GPIO是否導出 */
    sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]);

    if (access(gpio_path, F_OK)) {//如果目錄不存在 則需要導出

        int fd;
        int len;

        if (0 > (fd = open("/sys/class/gpio/export", O_WRONLY))) {
            perror("open error");
            exit(-1);
        }

        len = strlen(argv[1]);
        if (len != write(fd, argv[1], len)) {//導出gpio
            perror("write error");
            close(fd);
            exit(-1);
        }

        close(fd);  //關閉檔案
    }

    /* 配置為輸出模式 */
    if (gpio_config("direction", "out"))
        exit(-1);

    /* 極性設定 */
    if (gpio_config("active_low", "0"))
        exit(-1);

    /* 控制GPIO輸出高低電平 */
    if (gpio_config("value", argv[2]))
        exit(-1);

    /* 退出程式 */
    exit(0);
}
           

執行程式時需要傳入兩個參數,argv[1]指定GPIO 的編号、argv[2]指定輸出電平狀态(0 表示低電平、1 表示高電平)。

上述代碼中首先使用access()函數判斷指定編号的GPIO 引腳是否已經導出,也就是判斷相應的gpioX目錄是否存在,如果不存在則表示未導出,則通過"/sys/class/gpio/export"檔案将其導出;導出之後先配置了GPIO 引腳為輸出模式,也就是向direction 檔案中寫入"out";接着再配置極性,通過向active_low 檔案中寫入"0"(不用配置也可以);最後再控制GPIO 引腳輸出相應的電平狀态,通過對value 屬性檔案寫入"1"或"0"來使其輸出高電平或低電平。

使用交叉編譯工具編譯應用程式,如下所示:

Linux系統GPIO應用程式設計應用層如何操控GPIOGPIO 應用程式設計之輸出GPIO 應用程式設計之輸入GPIO 應用程式設計之中斷在開發闆上測試

GPIO 應用程式設計之輸入

本小節我們編寫一個讀取GPIO 電平狀态的測試程式,其示例代碼如下所示:

本例程源碼對應的路徑為:開發闆CD光牒->11、Linux C 應用程式設計例程源碼->16_gpio->gpio_in.c。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

static char gpio_path[100];

static int gpio_config(const char *attr, const char *val)
{
    char file_path[100];
    int len;
    int fd;

    sprintf(file_path, "%s/%s", gpio_path, attr);
    if (0 > (fd = open(file_path, O_WRONLY))) {
        perror("open error");
        return fd;
    }

    len = strlen(val);
    if (len != write(fd, val, len)) {
        perror("write error");
        close(fd);
        return -1;
    }

    close(fd);  //關閉檔案
    return 0;
}

int main(int argc, char *argv[])
{
    char file_path[100];
    char val;
    int fd;

    /* 校驗傳參 */
    if (2 != argc) {
        fprintf(stderr, "usage: %s <gpio>\n", argv[0]);
        exit(-1);
    }

    /* 判斷指定編号的GPIO是否導出 */
    sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]);

    if (access(gpio_path, F_OK)) {//如果目錄不存在 則需要導出

        int len;

        if (0 > (fd = open("/sys/class/gpio/export", O_WRONLY))) {
            perror("open error");
            exit(-1);
        }

        len = strlen(argv[1]);
        if (len != write(fd, argv[1], len)) {//導出gpio
            perror("write error");
            close(fd);
            exit(-1);
        }

        close(fd);  //關閉檔案
    }

    /* 配置為輸入模式 */
    if (gpio_config("direction", "in"))
        exit(-1);

    /* 極性設定 */
    if (gpio_config("active_low", "0"))
        exit(-1);

    /* 配置為非中斷方式 */
    if (gpio_config("edge", "none"))
        exit(-1);

    /* 讀取GPIO電平狀态 */
    sprintf(file_path, "%s/%s", gpio_path, "value");

    if (0 > (fd = open(file_path, O_RDONLY))) {
        perror("open error");
        exit(-1);
    }

    if (0 > read(fd, &val, 1)) {
        perror("read error");
        close(fd);
        exit(-1);
    }

    printf("value: %c\n", val);

    /* 退出程式 */
    close(fd);
    exit(0);
}
           

執行程式時需要傳入一個參數,argv[1]指定要讀取電平狀态的GPIO 對應的編号。

上述代碼中首先使用access()函數判斷指定編号的GPIO 引腳是否已經導出,若未導出,則通過

“/sys/class/gpio/export"檔案将其導出;導出之後先配置了GPIO 引腳為輸入模式,也就是向direction 檔案中寫入"in”;接着再配置極性、設定GPIO 引腳為非中斷模式(向edge 屬性檔案中寫入"none")。

最後打開value 屬性檔案,讀取GPIO 的電平狀态并将其列印出來。

使用交叉編譯工具編譯應用程式,如下所示:

Linux系統GPIO應用程式設計應用層如何操控GPIOGPIO 應用程式設計之輸出GPIO 應用程式設計之輸入GPIO 應用程式設計之中斷在開發闆上測試

GPIO 應用程式設計之中斷

在應用層可以将GPIO 配置為中斷觸發模式,譬如将GPIO 配置為上升沿觸發、下降沿觸發或者邊沿觸發,本小節我們來編寫一個測試程式,将GPIO 配置為邊沿觸發模式并監測中斷觸發狀态。其示例代碼如下所示:

本例程源碼對應的路徑為:開發闆CD光牒->11、Linux C 應用程式設計例程源碼->16_gpio->gpio_intr.c。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <poll.h>

static char gpio_path[100];

static int gpio_config(const char *attr, const char *val)
{
    char file_path[100];
    int len;
    int fd;

    sprintf(file_path, "%s/%s", gpio_path, attr);
    if (0 > (fd = open(file_path, O_WRONLY))) {
        perror("open error");
        return fd;
    }

    len = strlen(val);
    if (len != write(fd, val, len)) {
        perror("write error");
        return -1;
    }

    close(fd);  //關閉檔案
    return 0;
}

int main(int argc, char *argv[])
{
    struct pollfd pfd;
    char file_path[100];
    int ret;
    char val;

    /* 校驗傳參 */
    if (2 != argc) {
        fprintf(stderr, "usage: %s <gpio>\n", argv[0]);
        exit(-1);
    }

    /* 判斷指定編号的GPIO是否導出 */
    sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]);

    if (access(gpio_path, F_OK)) {//如果目錄不存在 則需要導出

        int len;
        int fd;

        if (0 > (fd = open("/sys/class/gpio/export", O_WRONLY))) {
            perror("open error");
            exit(-1);
        }

        len = strlen(argv[1]);
        if (len != write(fd, argv[1], len)) {//導出gpio
            perror("write error");
            exit(-1);
        }

        close(fd);  //關閉檔案
    }

    /* 配置為輸入模式 */
    if (gpio_config("direction", "in"))
        exit(-1);

    /* 極性設定 */
    if (gpio_config("active_low", "0"))
        exit(-1);

    /* 配置中斷觸發方式: 上升沿和下降沿 */
    if (gpio_config("edge", "both"))
        exit(-1);

    /* 打開value屬性檔案 */
    sprintf(file_path, "%s/%s", gpio_path, "value");

    if (0 > (pfd.fd = open(file_path, O_RDONLY))) {
        perror("open error");
        exit(-1);
    }

    /* 調用poll */
    pfd.events = POLLPRI; //隻關心高優先級資料可讀(中斷)

    read(pfd.fd, &val, 1);//先讀取一次清除狀态
    for ( ; ; ) {

        ret = poll(&pfd, 1, -1);    //調用poll
        if (0 > ret) {
            perror("poll error");
            exit(-1);
        }
        else if (0 == ret) {
            fprintf(stderr, "poll timeout.\n");
            continue;
        }

        /* 校驗高優先級資料是否可讀 */
        if(pfd.revents & POLLPRI) {
            if (0 > lseek(pfd.fd, 0, SEEK_SET)) {//将讀位置移動到頭部
                perror("lseek error");
                exit(-1);
            }

            if (0 > read(pfd.fd, &val, 1)) {
                perror("read error");
                exit(-1);
            }

            printf("GPIO中斷觸發<value=%c>\n", val);
        }
    }

    /* 退出程式 */
    exit(0);
}
           

執行程式時需要傳入一個參數,argv[1]指定要讀取電平狀态的GPIO 對應的編号。

上述代碼中首先使用access()函數判斷指定編号的GPIO 引腳是否已經導出,若未導出,則通過

"/sys/class/gpio/export"檔案将其導出。

對GPIO 進行配置:配置為輸入模式、配置極性、将觸發方式配置為邊沿觸發。

打開value 屬性檔案,擷取到檔案描述符,接着使用poll()函數對value 的檔案描述符進行監視,這裡為什麼要使用poll()監視、而不是直接對檔案描述符進行讀取操作?這裡簡單的描述一下。

13.2.3 小節給大家詳細介紹了poll()函數,這裡不再重述!poll()函數可以監視一個或多個檔案描述符上的I/O 狀态變化,譬如POLLIN、POLLOUT、POLLERR、POLLPRI 等,其中POLLIN 和POLLOUT 表示普通優先級資料可讀、可寫,而POLLPRI 表示有高優先級資料可讀取,中斷就是一種高優先級事件,當中斷觸發時表示有高優先級資料可被讀取。當然,除此之外還可使用13.4 小節所介紹的異步I/O 方式來監視

GPIO 中斷觸發。

使用交叉編譯工具編譯應用程式,如下所示:

Linux系統GPIO應用程式設計應用層如何操控GPIOGPIO 應用程式設計之輸出GPIO 應用程式設計之輸入GPIO 應用程式設計之中斷在開發闆上測試

在開發闆上測試

前面我們編寫了3 個測試程式,并編譯得到了對應的可執行檔案,本小節一個一個進行測試。在測試之前,選擇一個測試引腳,這裡筆者以闆子上的GPIO1_IO01 引腳為例,該引腳在底闆上已經引出,如下所示:

Linux系統GPIO應用程式設計應用層如何操控GPIOGPIO 應用程式設計之輸出GPIO 應用程式設計之輸入GPIO 應用程式設計之中斷在開發闆上測試

Mini 開發闆可以通過背面絲印标注的名稱或原理圖進行确認。

GPIO 輸出測試

将示例代碼16.2.1 編譯的到的可執行檔案拷貝到開發闆Linux 系統使用者家目錄下,執行該應用程式控制開發闆上的GPIO1_IO01 引腳輸出高或低電平:

./testApp 1 1 		#控制GPIO1_IO01 輸出高電平
./testApp 1 0 		#控制GPIO1_IO01 輸出低電平
           
Linux系統GPIO應用程式設計應用層如何操控GPIOGPIO 應用程式設計之輸出GPIO 應用程式設計之輸入GPIO 應用程式設計之中斷在開發闆上測試

執行相應的指令後,可以使用萬用表或者連接配接一個LED 小燈進行檢驗,以驗證明驗結果!

GPIO 輸入測試

将示例代碼16.3.1 編譯的到的可執行檔案拷貝到開發闆Linux 系統使用者家目錄下,執行該應用程式以讀取GPIO1_IO01 引腳此時的電平狀态,是高電平還是低電平?

首先通過杜邦線将GPIO1_IO01 引腳連接配接到闆子上的3.3V 電源引腳上,接着執行指令讀取GPIO 電平狀态:

Linux系統GPIO應用程式設計應用層如何操控GPIOGPIO 應用程式設計之輸出GPIO 應用程式設計之輸入GPIO 應用程式設計之中斷在開發闆上測試

列印出的value 等于1,表示讀取到GPIO 的電平确實是高電平;接着将GPIO1_IO01 引腳連接配接到闆子上的GND 引腳上,執行指令:

Linux系統GPIO應用程式設計應用層如何操控GPIOGPIO 應用程式設計之輸出GPIO 應用程式設計之輸入GPIO 應用程式設計之中斷在開發闆上測試

列印出的value 等于0,表示讀取到GPIO 的電平确實是低電平;測試結果與實際相符合!

GPIO 中斷測試

将示例代碼16.4.1 編譯的到的可執行檔案拷貝到開發闆Linux 系統使用者家目錄下,執行該應用程式可以監測GPIO 的中斷觸發。

執行應用程式監測GPIO1_IO01 引腳的中斷觸發情況,如下所示:

./testApp 1 	# 監測GPIO1_IO01 引腳中斷觸發
           
Linux系統GPIO應用程式設計應用層如何操控GPIOGPIO 應用程式設計之輸出GPIO 應用程式設計之輸入GPIO 應用程式設計之中斷在開發闆上測試

當執行指令之後,我們可以使用杜邦線将GPIO1_IO01 引腳連接配接到GND 或3.3V 電源引腳上,來回切換,使得GPIO1_IO01 引腳的電平狀态發生由高到低或由低到高的狀态變化,以驗證GPIO 中斷的邊沿觸發情況;當發生中斷時,終端将會列印相應的資訊,如上圖所示。

Tips:測試完成後按Ctrl+C 退出程式!

繼續閱讀