概述
以前學習了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);
}
}
}