Linux下对GPIO的操作控制(基于GPIO子系统)

发布时间 2023-11-06 17:35:04作者: DoubleLi

 

 

概述

以前学习了LED和按键驱动,实际上,在Linux中实现这些设备驱动,有一种更为推荐的方法,就是GPIO子系统和Input子系统。GPIO子系统可以控制IO的初始化、输出高低电平值,读取IO的输入电平;Input子系统处理输入事务,任何输入设备的驱动程序都可以通过Input输入子系统提供的接口注册到内核,利用子系统提供的功能来与用户空间交互。例如控制LED、读取按键、触摸屏、鼠标都可以通过这些子系统接口实现。

GIPO子系统介绍

gpio子系统相关描述可在内核源码 Documentation/gpio 了解。
在/sys/class/gpio目录下展示该子系统设备。如果没有请在编译内核的时候加入 Device Drivers-> GPIO Support ->/sys/class/gpio/… (sysfs interface)。

export 和 unexport:把某个GPIO导出用户空间和取消导出用户空间。

  • 例如:echo 19 > export,把GPIO19导出到用户空间。
  • GPION,其中N值的计算方式:
    假设一组端口有32个GPIO,N=index = GPIOx_y= (x-1)*32 +y;
    例如一:GPIOB_6,对应的N值为(2-1)*32+6=38;
    例如二:GPIOD_3,对应的N值为(4-1)*32+3=99。

进入子系统设备目录,包含一些常用属性:

direction:gpio端口的方向,内容可以是 in 或者 out;

  • 示例:echo out > /sys/class/gpio/pioD3/direction
  • 如果写入 low 或者 hight,则不仅设置为输出,还同时设置了输出电平(本小点要了解当前内核是否支持)

value:gpio引脚的电平,0为低电平,非0为高电平。

  • 如果配置为输入,则该文件可写;
  • 如果配置为中断,则可以调用poll(2)函数监听该中断,中断触发后poll(2)函数就会返回。

edge: 中断的触发方式,该文件有4个值。

  • none:输入,非中断
  • rising:中断输入,上升沿触发
  • falling:中断输入,下降沿触发
  • both:中断输入,边沿触发
    gpio子系统与led子系统是不同,但是类似。
  • led只能输出,gpio能输出也能输入
  • gpio输入还支持中断功能
  • 使用gpio前,需要从内核空间暴露到用户空间
    注意:在用户空间控制使用gpio时,注意不要滥用和内核一些绑定好、已经有合适内核启动的引脚冲突。参考 Documentation/driver-api/gpio/drivers-on-gpio.rst

示例:通过GPIO子系统控制LED(GPIOA29)

system函数进行操作:

void main(void)
{
	if(access("/sys/class/gpio/pioA29/value",F_OK) != 0)
	{
		/*使能LED设备*/
		system("echo 29 > /sys/class/gpio/export"); 	
	}
	/*设置LED输出*/
	system("echo out > /sys/class/gpio/pioA29/direction");
	/*打开LED*/
	system("echo '1' > /sys/class/gpio/pioA29/value");	
	sleep(1);
	/*关闭LED*/
	system("echo '0' > /sys/class/gpio/pioA29/value");
}

文件操作实现:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(void)
{
	FILE *p=NULL;
	int i=0;
	p = fopen("/sys/class/gpio/export","w");
	fprintf(p,"%d",29);
	fclose(p);
	p = fopen("/sys/class/gpio/pioA29/direction","w");
	fprintf(p,"out");
	fclose(p);
	for(i=0;i<100;i++)
	{
		p = fopen("/sys/class/gpio/pioA29/value","w");
		fprintf(p,"%d",1);
		sleep(1);
		fclose(p);
		p = fopen("/sys/class/gpio/pioA29/value","w");
		fprintf(p,"%d",0);
		sleep(1);
		fclose(p);
	}
	p = fopen("/sys/class/gpio/unexport","w");
	fprintf(p,"%d",38);
	fclose(p);
	return 0;
}

示例:通过GPIO子系统中断读取IO值(GPIOC2)

#include <stdio.h>
#include <fcntl.h>
#include <poll.h>
#include <unistd.h>
 /*按键按下时,打按键值*/
int main()
{
	FILE *fp=NULL; /*定义一个文件指针*/
	if(access("/sys/class/gpio/pioC2",F_OK) != 0)
	{
		/*使能GPIO子系统设备*/
		fp=fopen("/sys/class/gpio/export","w");
		if(fp == NULL)
		{
			perror("open export fail\r\n");
			return -1;
		}
		fprintf(fp,"%d",66);
		fclose(fp);
	}
	/*设置为输入*/
	fp=fopen("/sys/class/gpio/pioC2/direction","w");
	if(fp == NULL)
	{
		perror("open direction fail\r\n");
		return -1;
	}
	fprintf(fp,"in");
	fclose(fp);
	
	/*设置中断触发边沿*/
	fp=fopen("/sys/class/gpio/pioC2/edge","w");
	if(fp == NULL)
	{
		perror("open edge fail\r\n");
		return -1;
	}
	fprintf(fp,"both");
	fclose(fp);
	
	/*对IO值进行poll侦听*/
	int fd=open("/sys/class/gpio/pioC2/value",O_RDONLY);
    if(fd<0)
    {
        perror("open '/sys/class/gpio/pioC2/value' failed!\n");  
        return -1;
    }
    struct pollfd fds[1];
    fds[0].fd=fd;
    fds[0].events=POLLPRI;
    while(1)
    {
        if(poll(fds,1,0)==-1)
        {
            perror("poll failed!\n");
            return -1;
        }
        if(fds[0].revents&POLLPRI)
        {
            if(lseek(fd,0,SEEK_SET)==-1)
            {
                perror("lseek failed!\n");
                return -1;
            }
            char buffer[16];
            int len;
            if((len=read(fd,buffer,sizeof(buffer)))==-1)
            {
                perror("read failed!\n");
                return -1;
            }
            buffer[len]=0;
            printf("%s",buffer);
        }
    }
}