EDA365电子论坛网

标题: linux下对/sys/class/gpio中的gpio的控制 [打印本页]

作者: haidaowang    时间: 2021-3-29 10:58
标题: linux下对/sys/class/gpio中的gpio的控制

2 J. x( P6 u- y4 w; e, d在嵌入式设备中对GPIO的操作是最基本的操作。一般的做法是写一个单独驱动程序,网上大多数的例子都是这样的。其实linux下面有一个通用的GPIO操作接口,那就是我要介绍的 “/sys/class/gpio” 方式。
& z% j5 U4 N/ w$ Q7 R$ S. H% n6 D% \
首先,看看系统中有没有“/sys/class/gpio”这个文件夹。如果没有请在编译内核的时候加入   Device Drivers  —>  GPIO Support  —>     /sys/class/gpio/… (sysfs interface)。
+ J! t( d; P' n7 z, }! j* ^- v" L3 I% C: T( K
/sys/class/gpio 的使用说明:4 J9 _1 F% h0 |$ _; ~
/ [$ A* W; n- e  s3 T9 T
01 gpio_operation 通过/sys/文件接口操作IO端口 GPIO到文件系统的映射0 e% h- u3 j; Q$ F; Q
! n! a5 a; w8 l8 `/ t
02 * 控制GPIO的目录位于/sys/class/gpio
- c" M" b4 H9 L' i! A& `* b: A# e" a% }( A: h+ n, F' x
03 * /sys/class/gpio/export文件用于通知系统需要导出控制的GPIO引脚编号
. O7 ?. \# x6 P2 x/ N( G5 }  M4 a8 y6 K; W+ T( \$ t  W/ J+ w
04 * /sys/class/gpio/unexport 用于通知系统取消导出
: U  Z/ C+ Z' p7 e
& Q& m& c3 |& x9 b$ ~, ]05 * /sys/class/gpio/gpiochipX目录保存系统中GPIO寄存器的信息,包括每个寄存器控制引脚的起始编号        base,寄存器名称,引脚总数 导出一个引脚的操作步骤9 S  f/ {- r: ^
6 t: p  U7 O* @& Q$ I7 P
06 * 首先计算此引脚编号,引脚编号 = 控制引脚的寄存器基数 + 控制引脚寄存器位数5 h: U: N& R, X6 {7 `6 K

( t; b" ~* V. n( j$ ^% Z07 * 向/sys/class/gpio/export写入此编号,比如12号引脚,在shell中可以通过以下命令实现,- S; h7 G* n: ~# ?* x% Z
1 D! Y+ M* _& W! Y- a8 H- h2 i2 b
echo 12 > /sys/class/gpio/export. l( }1 d1 a1 ~7 ]& n) y

( K/ y3 T3 l+ j- }8 K' Y  命令成功后生成/sys/class/gpio/gpio12目录,如果没有出现相应的目录,说明此引脚不可导出:
- m! B5 r  `5 Q0 m& P
8 D' ~4 W1 y5 k08  
, _. [$ [9 k" a' ^1 k/ P$ `! i1 @) ?6 u. H# C" V2 H; g: `6 s# K
09 * direction文件,定义输入输入方向,可以通过下面命令定义为输出7 c) Q2 l) m5 X

1 W  B2 G1 Q6 r3 ]! V10   echo out > /sys/class/gpio/gpio12/direction0 q8 P+ M0 `% @: j

' J- c, @7 {; u( w11 * direction接受的参数:in, out, high, low。high/low同时设置方向为输出,$ s$ l7 U& s. @7 g& N8 X( q
  K) u) b/ }' |0 n5 f
    并将value设置为相应的1/0。! L* G# P1 Q2 M( t5 h$ j' w
- h5 a6 D7 s5 E: @* F) m# {3 i( `
12 * value文件是端口的数值,为1或0.$ m- @: _$ Z3 F% g! M$ A$ o
$ |$ I  `1 s% P$ W
13   echo 1 >/sys/class/gpio/gpio12/value- O+ H% d" J, v9 R$ `
) r  v/ S0 @( R7 @
- l. M) h; F! }8 |

3 T! m2 e8 E; g8 [6 q编写控制程序
9 g# G8 C  P4 c, ?' `- t7 B2 J% y& _. {. C. P
GPIO的配置文件在/sys/class/gpio目录下,控制程序可以分为四个步骤:
. ?0 i6 y) c( ~- P; C; d+ y; a& @: F: |
配置GPIO:在/sys/class/gpio目录下可以看到文件export,调用该文件以实现配置。该文件对所有GPIO编号,从0开始。GPIOn_x的编号为32*n+x,例如此处用的GPIO1_6的编号为32*1+6=38。在终端输入:# echo "38" > /sys/class/gpio/export,在此回到目录/sys/class/gpio下,可以看到产生了一个新的目录./gpio38,里面包含了该IO口的输入输出设置等配置文件。注意:export文件只有root写权限,执行上述命令或者以后用C编写的可执行文件要以ROOT身份执行。
' d8 _: I0 w! E! {! Z
& \, n( X+ z7 P8 S5 F7 A) C. V* t设置GPIO的方向(输入输出):在终端输入:# echo "out" > /sys/class/gpio/gpio38/direction,即设置该GPIO为输出。
9 ~+ O' q# H" J2 s' V/ r! e8 h( F1 V/ b# M
设置GPIO的输出电平:在终端输入:#echo "1" > /sys/class/gpio/gpio38/value,即设置GPIO输出高电平,输入echo "0" > /sys/class/gpio/gpio38/value设置GPIO输出低电平。
" S9 L( S/ i' P; f' b- L1 g, B% R4 e$ o8 n* D8 Y: \
关闭GPIO:在终端输入:#echo "38" > /sys/class/gpio/unexport,即删除GPIO配置文件,可以看到目录gpio38已经被删除。
! x4 U; K0 O$ q$ J% l0 F5 q: P+ C9 G0 H' l) K( p- s
下面是C语言编写的GPIO控制例程,实现LED的每隔一秒闪烁一次。
' z$ e3 {( I: p3 Z1 Z
# ~6 \' X8 [. r" R: {6 o9 p4 I#include <stdio.h>
1 }8 R6 A/ Y& n#include <stdlib.h>
2 d( q# v' w) d" R  ?6 `* l% c1 y3 @#include <unistd.h>
  H4 W1 f8 s  ^+ R# n#include <string.h>
* ?7 ^/ J3 z& [* P9 ]int main(void)
( ?8 p) t3 B6 s6 m$ K5 h{
. u. }- V7 H. i: Z4 hFILE *p=NULL;- E/ T+ e: V5 M" F, f
int i=0;) h) |! p& }, W  L+ ]  ]
& \5 `; X+ b, }, [9 Y2 U2 z" ]+ H) z
p = fopen("/sys/class/gpio/export","w");
/ X" @3 U7 h/ \3 @& u" h: a# N" tfprintf(p,"%d",38);1 M; `$ c: J9 X2 `
fclose(p);1 N5 J; c/ J  y0 Z# x
+ Z5 W+ m) h( H9 z& q
p = fopen("/sys/class/gpio/gpio38/direction","w");
( r9 h0 d3 ]; [fprintf(p,"out");: X+ Y8 I: n8 h& {" n- `# _
fclose(p);" T* N" {% C) A5 u1 v1 ^# X
( o  X# {0 S8 e% M- c, s! [" T7 p) r

) B- t% T/ c# J) D( q  [  }$ nfor(i=0;i<100;i++)
( Q8 Q+ X0 O" J) X) J{4 a3 j' [! t/ x9 P
p = fopen("/sys/class/gpio/gpio38/value","w");
2 e% b, S8 b/ D' l/ gfprintf(p,"%d",1);
. K, L% O1 x$ b$ U- T3 z: Dsleep(1);
  S6 [; w' Z( B9 r) L' C# q1 n  k4 Yfclose(p);
. g$ J6 r' b2 T  n1 n- f2 \1 Np = fopen("/sys/class/gpio/gpio38/value","w");
$ q$ G% J2 M" a& H' k0 B( }fprintf(p,"%d",0);* u7 E, P4 r1 y) H  \; J
sleep(1);
- c" y0 t% R, K$ U7 r, i: T* [+ _* Dfclose(p);& E2 l% ?: j6 j  p
}  ]- o% x" B/ G  B: u
. K$ F1 e/ b) _  |9 d0 ?) \* Y3 J
2 Y; I$ L& d4 E5 k& p. v; M) r5 c
p = fopen("/sys/class/gpio/unexport","w");
) r/ |2 G, k' ]" \fprintf(p,"%d",38);
& A: U8 }8 K, Q4 O# ~; Pfclose(p);- Q9 N3 `' V/ c3 v: Z. S# s
return 0;
" K! d* J3 d; S9 X9 ?% U3 L}
' z" n6 m4 o0 A. M$ i
$ g% M# l! d+ c6 D7 d6 M
# o( y+ m* Y% |$ a8 ]" A+ _" P7 g& S% M
. B- b% I) V3 z5 i9 e+ N9 E- Z1 `
% a1 R/ A: w5 |: w
下面实现按键输入的读取操作# q5 y4 o9 Z7 N' H5 {) W, A6 w2 U9 h
+ w) t* u4 n0 S4 d/ g
#include <stdio.h>( K" U' |5 V9 w& |8 ~+ I

) y" y* O5 W9 ?#include <stdlib.h>$ r1 l: O- _  J3 b% t3 n: N0 r
#include <unistd.h>
' D- {, x9 A# [#include <string.h>
7 u: S( t1 i, e( t  }& f, ]( g3 ?  J/ b4 I5 q. g/ \/ U; q

+ J& |2 A1 w3 l! y5 v7 E5 Wint main(void)3 z- z& h& e4 p0 F0 E
{
" n6 c" S; q* y2 n8 PFILE *p=NULL;
! P+ f# Z" W1 w7 U" q0 ychar i[100]={0,};! ?' Y- _0 B% {! \9 D, L
, E& L% V  x- f4 `  B
p = fopen("/sys/class/gpio/export","w");$ [! N4 z/ o9 T1 b3 {  a* D
fprintf(p,"%d",161);
+ l" \! R. [/ o  U- J* ?) _3 E8 w9 efclose(p);$ j& ~* C3 `: \5 u! A& }
p = fopen("/sys/class/gpio/gpio161/direction","w");
& ?( }+ m/ Y9 _1 @3 ffprintf(p,"in");  //配置成输入
1 l* t* `. C( A) j# W1 s4 ~: f8 o% i7 wfclose(p);. V) h! a# ~$ J% e

$ x( o2 u8 e+ I1 k; K- Twhile(1)
/ A4 O' P! u: X0 f2 I  k{) E/ y; ?# ]6 ?& |7 d  ~
//以只读方式打开0 S; k7 \. @1 I8 Q& c, W
p = fopen("/sys/class/gpio/gpio161/value","r");% O6 Q% U+ W  g0 j" Q
//使文件读写定位到0位置$ `! n" t: X7 q7 F7 J, U( \
fseek(p , 0 , 0);
- h# b% L( i. p* E4 z+ K9 `#if 06 k! j7 N. e% H. g4 v& o
//将文件内容输出到存储器i中,注意要以字符串的方式,否则会出错/ P3 G5 G$ r8 s# n
fscanf(p,"%s",i);/ u7 F+ X5 r3 p3 M
#else
( }# r+ D/ ?: h//从文件中读出数据到存储器i中
6 X& z% J! |8 p4 Qfread(i , 1, 1 ,p);
: g# ]3 V9 D% x) I: N. n: G& S" A#endif( \- j) w& |( D$ Q
//以字符的方式将读到的值打印出3 ~* s: E* `8 J, W, B; q9 d
printf("key = %c \r\n",i[0]);
, n" V# d1 ^! `4 Q1 M7 c1 Rsleep(1);5 g+ O/ X# a( E- C. _6 ~( }( R! F
//注意这里必须要关闭,然后再次读时再重新打开,这样只面的内容才会更新
: G, L2 E" d* S9 I2 |! h* C' dfclose(p);+ N( t, @9 W7 H; ]
}
6 R+ \, W/ G9 E. y2 h% Ureturn 0;! [* f% }. s, L6 c1 _( {9 Y) [3 R
}
/ _: X+ k# D+ x" l3 o
0 h8 y( y$ u4 ^ , |% E& d% H( q# f7 p7 {) c

/ V5 G( C( Z) W1 s: Q$ X) F1 ^/*********************************************************************************/- U. G+ c* N5 p) v, R  z

4 Q1 Z/ L1 J4 r% ]( h+ b查看当前系统下已使用的GPIO:
" Q2 L  j' e. k9 C0 e2 i5 |" m0 K2 I1 Q% C$ ]
# cat /sys/kernel/debug/gpio
( O0 C7 {$ P; Y" e# ]7 e6 p- ?: d/ M5 b( D  x; x5 _3 U; t

  ~' \1 M, F( T* P: a/ J+ O+ W/ s% U- o: I
用户态使用gpio监听中断      
, O% D1 j9 D0 j6 R* h" S& v  c首先需要将该gpio配置为中断3 w& h, q1 p4 b3 d

6 T( c, Z8 J, Qecho  "rising" > /sys/class/gpio/gpio12/edge       ) R( U  V  k! q) T  H2 v1 D3 B

6 ?/ e$ p; s8 y% s% \. T以下是伪代码9 x# ^7 i9 ?$ J+ K3 L' L/ ~0 [

8 [6 N! _# z. ?' ~2 bint gpio_id;
# L0 [7 E/ _( @3 `# ^5 _1 ?/ X1 ^6 e , ^6 P$ p# M0 C$ x1 n  ?
struct pollfd fds[1];
3 [2 W) J; f. Y8 w" v( _. @
' e% Z. J- k5 N
  s6 e$ n$ C0 C5 L8 O, n0 c  @ # f$ Z$ m% q& h7 a5 n- d
gpio_fd = open("/sys/class/gpio/gpio12/value",O_RDONLY);4 i5 N+ G) \# W* b/ Y7 e2 c  c
8 ~- H& T0 k" [; l
if( gpio_fd == -1 )
. p1 R3 j% u2 j+ x& c5 |" w  j
' k) ~$ \$ h$ B& u. x, {   err_print("gpio open");
, \0 s4 Q  V# i* `( U5 D# [ ; l4 U) U0 v8 C1 _$ O6 ?
fds[0].fd = gpio_fd;7 C- H) Q7 V* U, p/ t1 \
8 J, y& I: o! m7 V+ ^0 P% s
fds[0].events  = POLLPRI;
' \- _$ O8 }. S0 T! w7 \ , D5 R# g3 v# d# J7 I( ?
ret = read(gpio_fd,buff,10);! e! G1 y( I; K0 E' C/ r* g
$ F1 Z8 E3 `% a4 R& G$ I, E
if( ret == -1 )
! @0 a) j1 T) j# ]5 m4 j- t
; d: p! v( g. g    err_print("read");
5 @' N# X% o. R: g # b- Y1 k* B7 o9 N
while(1){
6 E9 \% M# P' N+ Q: J   R5 E* Q3 X5 _
     ret = poll(fds,1,-1);
; K0 C# u  @2 x+ P* \
8 Z  `2 _) n1 d  }9 e     if( ret == -1 )
$ N+ ~  @2 X& [$ `" I# k- t
' N; e3 ]3 u6 n. ?         err_print("poll");
/ a% k( \. E, ]% o2 U 0 i" b! I5 A8 A. N$ X
       if( fds[0].revents & POLLPRI){- U/ z1 h5 H: R$ H
6 w; S( M+ X6 y- O/ o3 w* @0 [- y
           ret = lseek(gpio_fd,0,SEEK_SET);  q4 ~3 K" H% R* \+ u% z0 X4 B! s' z
* ^9 S- h/ ?% U- C8 C
           if( ret == -1 )8 i' B) f3 x. x# b! p
  X. O! t9 E+ F) B( w' Y5 |  K) t0 ^
               err_print("lseek");3 V5 E/ u# A3 n

$ W9 i) |! o9 z" _7 V* ~  g4 b           ret = read(gpio_fd,buff,10);
& ?; Y4 V+ O& ~2 r: } ' l" C8 ^+ c8 a; ~- J
           if( ret == -1 )) ]% \" f5 w9 Z) ^

% |" `4 ~+ D) J% a' O/ j* C               err_print("read");9 X, A* M# U6 G5 L9 F
6 ]/ _, C4 P7 t" i: r- y% W& b
            /*此时表示已经监听到中断触发了,该干事了*/4 b) b/ d' E9 I( u4 X& }
% F# A" b5 E; h) h) C
            ...............% J* ?- ]0 w/ z" s
; ^2 P9 o2 e- S& ?
    }. m2 L+ q4 H: R9 L0 M+ e3 {7 T$ v

! S$ j4 q! T, o, w7 U}) i$ D0 I0 G1 \' o
记住使用poll()函数,设置事件监听类型为POLLPRI和POLLERR在poll()返回后,使用lseek()移动到文件开头读取新的值或者关闭它再重新打开读取新值。必须这样做否则poll函数会总是返回。
: o4 B3 n" I7 s. _& s
3 [6 {9 W  i9 l5 b9 U
作者: regngfpcb    时间: 2021-3-29 13:11
linux下对/sys/class/gpio中的gpio的控制




欢迎光临 EDA365电子论坛网 (https://bbs.eda365.com/) Powered by Discuz! X3.2