Linux下用文件IO的方式操作GPIO Linux无需开发底层驱动,从应用层获取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

[objc]  view plain  copy
  1.  文件读写例程:  
  2.   
  3. #include stdlib.h    
  4.   
  5. #include stdio.h    
  6.   
  7. #include string.h  
  8.   
  9. #include unistd.h  
  10.   
  11. #include fcntl.h     
  12. //define O_WRONLY and O_RDONLY    
  13.   
  14.    
  15.   
  16. //芯片复位引脚: CPIO4_21  
  17.   
  18. #define SYSFS_GPIO_EXPORT           "/sys/class/gpio/export"    
  19.   
  20. #define SYSFS_GPIO_RST_PIN_VAL      "117"     
  21.   
  22. #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"  
  23.   
  24. #define SYSFS_GPIO_RST_DIR_VAL      "OUT"    
  25.   
  26. #define SYSFS_GPIO_RST_VAL          "/sys/class/gpio/gpio117/value"  
  27.   
  28. #define SYSFS_GPIO_RST_VAL_H        "1"  
  29.   
  30. #define SYSFS_GPIO_RST_VAL_L        "0"  
  31.   
  32.    
  33.   
  34. int main()   
  35.   
  36. {   
  37.   
  38.     int fd;   
  39.   
  40.           
  41.   
  42.          //打开端口/sys/class/gpio# echo 117 > export  
  43.   
  44.          fd = open(SYSFS_GPIO_EXPORT, O_WRONLY);  
  45.   
  46.          if(fd == -1)  
  47.   
  48.          {  
  49.   
  50.                    printf("ERR: Radio hard reset pin open error.\n");  
  51.   
  52.                    return EXIT_FAILURE;  
  53.   
  54.          }  
  55.   
  56.          write(fd, SYSFS_GPIO_RST_PIN_VAL ,sizeof(SYSFS_GPIO_RST_PIN_VAL));   
  57.   
  58.          close(fd);   
  59.   
  60.    
  61.   
  62.          //设置端口方向/sys/class/gpio/gpio117# echo out > direction  
  63.   
  64.          fd = open(SYSFS_GPIO_RST_DIR, O_WRONLY);  
  65.   
  66.          if(fd == -1)  
  67.   
  68.          {  
  69.   
  70.                    printf("ERR: Radio hard reset pin direction open error.\n");  
  71.   
  72.                    return EXIT_FAILURE;  
  73.   
  74.          }  
  75.   
  76.          write(fd, SYSFS_GPIO_RST_DIR_VAL, sizeof(SYSFS_GPIO_RST_DIR_VAL));   
  77.   
  78.          close(fd);   
  79.   
  80.    
  81.   
  82.          //输出复位信号: 拉高>100ns  
  83.   
  84.          fd = open(SYSFS_GPIO_RST_VAL, O_RDWR);  
  85.   
  86.          if(fd == -1)  
  87.   
  88.          {  
  89.   
  90.                    printf("ERR: Radio hard reset pin value open error.\n");  
  91.   
  92.                    return EXIT_FAILURE;  
  93.   
  94.          }         
  95.   
  96.          while(1)  
  97.   
  98.          {  
  99.   
  100.                    write(fd, SYSFS_GPIO_RST_VAL_H, sizeof(SYSFS_GPIO_RST_VAL_H));  
  101.   
  102.                    usleep(1000000);  
  103.   
  104.                    write(fd, SYSFS_GPIO_RST_VAL_L, sizeof(SYSFS_GPIO_RST_VAL_L));  
  105.   
  106.                    usleep(1000000);  
  107.   
  108.          }  
  109.   
  110.          close(fd);  
  111.   
  112.    
  113.   
  114.          printf("INFO: Radio hard reset pin value open error.\n");  
  115.   
  116.          return 0;  
  117.   
  118.    
  119.   
  120. }  <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)也是可以的。




猜你喜欢

转载自blog.csdn.net/dragon101788/article/details/79446356
今日推荐