GPIO Subsystem -4- (new Interface)

 

새로운 GPIO character 디바이스 for User-space

기존 Sysfs 클래스 디바이스 인터페이스

기존 커널의 gpio 디바이스는 클래스 디바이스에 등록되어 관리되었다.

  • “/sys/class/gpio” 디렉토리의 virtual 디렉토리와 파일을 통해 유저 스페이스에서 인터페이스 할 수 있도록 제공한다.
  • 이 인터페이스는 GPIO subsystem core에서 제공한다.
  • gpio 핀 하나 하나에 대응하는 exprot된 가상 디렉토리 및 제어용 파일들이 만들어진다.
  • 추후 사용되지 않을 인터페이스이다. (deprecated)

 

새 character 디바이스 인터페이스

기존 sysfs 클래스 디바이스 기반의 ABI를 사용하는 user-space 인터페이스는 앞으로 character 디바이스로 대체될 예정이다.

  • 매직 넘버를 사용하지 않는 디스커버리(Discovery) 메커니즘을 적용하였다.
  • character 디바이스를 open하여 ioctl()을 통해 gpio 핀 제어가 가능하다.
    • 디바이스 트리에 정의된 line 명에 해당하는 gpio 핀을 ioctl()로 접근하여 제어할 수 있다.
  • 이에 대한 접근 방법은 gpio 유저 application 툴인 커널 디렉토리의 tools/lsgpio.c 파일을 보면 알 수 있다.
  • “/sys/bus/gpio”, “/dev/gpiochipN”
  • 이 새로운 방식은 커널 v4.8부터 사용된다.
    • 관련 유틸리티
      • lsgpio
      • gpio-hammer
      • gpio-event-mon
    • 관련 User 라이브러리 (option)
      • gpiodlib

 

lsgpio

“gpiochip0″명을 가진 gpio controller 디바이스를 open한 후 소속된 gpio 라인명과 사용 상태를 보여준다.

GPIO chip: gpiochip0, "pinctrl-bcm2835", 54 GPIO lines
 line 0: "[SDA0]" unused
 line 1: "[SCL0]" unused
 (...)
 line 16: "STATUS_LED_N" unused
 line 17: "GPIO_GEN0" unused
 line 18: "GPIO_GEN1" unused
 line 19: "NC" unused
 line 20: "NC" unused
 line 21: "CAM_GPIO" unused
 line 22: "GPIO_GEN3" unused
 line 23: "GPIO_GEN4" unused
 line 24: "GPIO_GEN5" unused
 line 25: "GPIO_GEN6" unused
 line 26: "NC" unused
 line 27: "GPIO_GEN2" unused

 

gpio-hammer
$ gpio-hammer --help
gpio-hammer: invalid option -- '-'
Usage: gpio-hammer [options]...
Hammer GPIO lines, 0->1->0->1...
  -n <name>  Hammer GPIOs on a named device (must be stated)
  -o <n>     Offset[s] to hammer, at least one, several can be stated
 [-c <n>]    Do <n> loops (optional, infinite loop if not stated)
  -?         This helptext

Example:
gpio-hammer -n gpiochip0 -o 4
$ gpio-hammer -n gpiochip0 -o 5 -c 2
Hammer lines [5] on gpiochip0, initial states: [1]
[\] [5: 1]

 

gpio-event-mon
$ gpio-event-mon --help
gpio-event-mon: invalid option -- '-'
Usage: gpio-event-mon [options]...
Listen to events on GPIO lines, 0->1 1->0
  -n <name>  Listen on GPIOs on a named device (must be stated)
  -o <n>     Offset to monitor
  -d         Set line as open drain
  -s         Set line as open source
  -r         Listen for rising edges
  -f         Listen for falling edges
 [-c <n>]    Do <n> loops (optional, infinite loop if not stated)
  -?         This helptext

Example:
gpio-event-mon -n gpiochip0 -o 4 -r -f
$ gpio-event-mon -n gpiochip0 -o 6 -r -c 2
Monitoring line 6 on gpiochip0
Initial line value: 1
GPIO EVENT 1527053280817909942: rising edge
GPIO EVENT 1527053282195388044: rising edge

 

New GPIO character 디바이스에 대한 유저 application 샘플 -1-

GPIO character 디바이스 Open 메인

/dev/gpiochipN gpio 디바이스로 open한 후 요청한 gpio 핀 3개를 input, output, input+event 모드로 설정하고 이벤트 입력을 기다린다.

  • 예)  new-gpio-api /dev/gpiochip0 4 5 6

 

new-gpio-api.c

01#include <stdio.h>
02#include <stdlib.h>
03#include <string.h>
04#include <sys/types.h>
05#include <sys/stat.h>
06#include <sys/ioctl.h>
07#include <fcntl.h>
08#include <linux/gpio.h>
09#include <poll.h>
10#include <inttypes.h>
11#include <unistd.h>
12 
13int main(int argc, char *argv[])
14{
15        int gpio1, gpio2, gpio3;
16        int fd;
17        int req_fd;
18        int value;
19        char * file;
20 
21        if (argc < 5) {
22                printf("Usage: new-gpio-api /dev/<gpiochipN> "
23                                "<in-gpio> <out-gpio> <event-gpio>\n");
24                return -1;
25        }
26 
27        file = argv[1];
28        fd = open(file, O_RDWR);
29        if (fd < 0) {
30                printf("open \"%s\" failed\n", file);
31                return -1;
32        }
33 
34        gpio1 = atoi(argv[2]);
35        gpio2 = atoi(argv[3]);
36        gpio3 = atoi(argv[4]);
37 
38        if (chip_info(fd) < 0)
39                goto err;
40 
41        if (line_info(fd, gpio1) < 0)
42                goto err;
43 
44        if (request_gpio(fd, gpio1, GPIOHANDLE_REQUEST_INPUT,
45                "foo_input", &req_fd) < 0)
46                goto err;
47 
48        if (get_value(req_fd, gpio1, &value) < 0)
49                goto err;
50 
51        if (request_gpio(fd, gpio2, GPIOHANDLE_REQUEST_OUTPUT,
52                "foo_output", &req_fd) < 0)
53                goto err;
54 
55        if (set_value(req_fd, gpio2, 1) < 0)
56                goto err;
57 
58        if (request_event(fd, gpio3, "foo_event", &req_fd) < 0)
59                goto err;
60 
61        while (1)
62                if (recv_event(req_fd, gpio3) < 0)
63                        break;
64 
65err:
66        close(fd);
67        return -1;
68}

 

GPIO chip 정보 알아오기

gpio chip 디바이스명, 라벨명, gpio 라인 수를 알아와서 출력한다.

01int chip_info(int fd)
02{
03        int ret;
04        struct gpiochip_info cinfo;
05 
06        ret = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &cinfo);
07        if (ret < 0) {
08                printf("GPIO_GET_CHIPINFO_IOCTL failed.\n");
09                return -1;
10        }
11 
12        printf("GPIO chip: %s, \"%s\", %u GPIO lines\n",
13                cinfo.name, cinfo.label, cinfo.lines);
14 
15        return 0;
16}

 

GPIO line 정보 알아오기

요청한 gpio 번호로 지정되어 있는 라인명 등을 알아온다.

01int line_info(int fd, int gpio)
02{
03        int ret;
04        struct gpioline_info linfo;
05 
06        memset(&linfo, 0, sizeof(linfo));
07        linfo.line_offset = gpio;
08 
09        ret = ioctl(fd, GPIO_GET_LINEINFO_IOCTL, &linfo);
10        if (ret < 0) {
11                printf("GPIO_GET_LINEINFO_IOCTL failed.\n");
12                return -1;
13        }
14 
15        printf("line %2d: %s\n", linfo.line_offset, linfo.name);
16 
17        return fd;
18}

 

GPIO line 사용 요청

요청한 gpio 번호에 해당하는 라인을 플래그 값에 해당하는 모드로 설정한다. 라벨명도 지정할 수 있다.

  • flags
    • GPIOHANDLE_REQUEST_INPUT
    • GPIOHANDLE_REQUEST_OUTPUT
01int request_gpio(int fd, int gpio, int flags, const char *label, int *req_fd)
02{
03        struct gpiohandle_request req;
04        int ret;
05 
06        req.flags = flags;
07        req.lines = 1;
08        req.lineoffsets[0] = gpio;
09        req.default_values[0] = 0;
10        strcpy(req.consumer_label, label);
11 
12        ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req);
13        if (ret < 0) {
14                printf("GPIO_GET_LINEHANDLE_IOCTL failed.\n");
15                return -1;
16        }
17 
18        *req_fd = req.fd;
19 
20        return 0;
21}

 

GPIO line 읽기(Read)

요청한 gpio 번호에 해당하는 라인에서 값(high/low)을 읽어 출력한다.

01int get_value(int req_fd, int gpio, int *value)
02{
03        struct gpiohandle_data data;
04        int ret;
05 
06        memset(&data, 0, sizeof(data));
07 
08        ret = ioctl(req_fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data);
09        if (ret < 0) {
10                printf("GPIO_GET_LINE_VALUES_IOCTL failed.\n");
11                return -1;
12        }
13        printf("line %d is %s\n", gpio, data.values[0] ? "high" : "low");
14 
15        *value = data.values[0];
16 
17        return 0;
18}

 

GPIO line 쓰기(Write)

요청한 gpio 번호에 해당하는 라인에 값(high/low)을 출력한다.

01int set_value(int req_fd, int gpio, int value)
02{
03        struct gpiohandle_data data;
04        int ret;
05 
06        memset(&data, 0, sizeof(data));
07        data.values[0] = value;
08 
09        ret = ioctl(req_fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data);
10        if (ret < 0) {
11                printf("GPIO_SET_LINE_VALUES_IOCTL failed.\n");
12                return -1;
13        }
14        printf("line %d is %s\n", gpio, data.values[0] ? "high" : "low");
15 
16        return 0;
17}

 

GPIO line 이벤트 요청

요청한 gpio 번호에 해당하는 라인을 입력 모드로 설정하고 이벤트를 수신할 수 있도록 준비한다.

  • 아래 예에서는 핀에 open drain 설정을 하였고, rising edge 및 falling edge 이벤트에 반응하도록 설정하였다.
01int request_event(int fd, int gpio, const char *label, int *req_fd)
02{
03        struct gpioevent_request req;
04        int ret;
05 
06        req.lineoffset = gpio;
07        strcpy(req.consumer_label, label);
08        req.handleflags = GPIOHANDLE_REQUEST_OPEN_DRAIN;
09        req.eventflags = GPIOEVENT_REQUEST_RISING_EDGE |
10                GPIOEVENT_REQUEST_FALLING_EDGE;
11 
12        ret = ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &req);
13        if (ret < 0) {
14                printf("GPIO_GET_LINEEVENT_IOCTL failed.\n");
15                return -1;
16        }
17 
18        *req_fd = req.fd;
19 
20        return 0;
21}

 

GPIO line 이벤트 대기

요청한 gpio 번호에 해당하는 라인에서 이벤트를 수신 대기한다.

01#define USE_POLL
02 
03int recv_event(int req_fd, int gpio)
04{
05        struct gpioevent_data event;
06        struct pollfd pfd;
07        ssize_t rd;
08        int ret;
09 
10#ifdef USE_POLL
11        pfd.fd = req_fd;
12        pfd.events = POLLIN | POLLPRI;
13 
14        rd = poll(&pfd, 1, 1000);
15        if (rd < 0)
16        if (ret < 0) {
17                printf("poll failed.\n");
18                return -1;
19        }
20#endif
21        ret = read(req_fd, &event, sizeof(event));
22        if (ret < 0) {
23                printf("read failed.\n");
24                return -1;
25        }
26 
27        printf( "GPIO EVENT @%" PRIu64 ": ", event.timestamp);
28 
29        if (event.id == GPIOEVENT_EVENT_RISING_EDGE)
30                printf("RISING EDGE");
31        else
32                printf("FALLING EDGE");
33        printf ("\n");
34 
35        return 0;
36}

 

동작 확인

gpio 4, 5, 6번에 대해 다음과 같은 동작을 하는 것을 확인한다.

  • gpio 4번을 입력 모드로 설정하고 low 갑을 읽었다.
  • gpio 5번을 출력 모드로 설정하고 high 출력하였다.
  • gpio 6번을 입력 모드 및 이벤트 수신 대기를 한다.
$ ./new-gpio-api /dev/gpiochip0 4 5 6
GPIO chip: gpiochip0, "0000:00:01.0", 32 GPIO lines
line  4: 
line 4 is low
line 5 is high
GPIO EVENT @1527042559726500338: RISING EDGE                        <- 6번 gpio에 연결된 버튼(high 시그널)을 눌렀을 때

 

아래와 같이 gpio line 4~6 번이 설정되어 동작하는 것을 lsgpio 툴로 확인할 수 있다.

$ lsgpio
GPIO chip: gpiochip0, "0000:00:01.0", 32 GPIO lines
	line  0: unnamed "sysfs" [kernel]
	line  1: unnamed "sysfs" [kernel]
	line  2: unnamed "sysfs" [kernel]
	line  3: unnamed "sysfs" [kernel]
	line  4: unnamed "foo_input" [kernel]
	line  5: unnamed "foo_output" [kernel output]
	line  6: unnamed "foo_event" [kernel open-drain]
	line  7: unnamed unused
	line  8: unnamed unused
	line  9: unnamed unused

 

New GPIO character 디바이스에 대한 유저 application sample -2-

gpiodlib를 사용하는 유저 application 샘플

이 샘플 소스 역시 샘플 1 소스와 동일한 결과를 수행한다.

  • 이 샘플을 빌드하기 위해서는 libgpiod의 설치가 필요하다.

 

libgpiod 다운 및 설치 (for debian)

debian 계열을 사용하는 임베디드에서 테스트하기 위해 root 계정에서 다음과 같이 libgpiod를 설치한다.

$ cd ~
$ apt-get install libtool pkg-config autoconf autoconf-archive
$ git clone https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git
$ cd libgpiod
$ mkdir -p include/linux
$ cp /usr/src/linux-headers-$(uname -r)/include/linux/compiler_types.h include/linux/.
$ ./autogen.sh --enable-tools=yes --prefix=/usr/local CFLAGS="-I/usr/src/linux-headers-$(uname -r)/include/uapi -Iinclude"
$ make
$ make install
$ ldconfig

 

Utility

libgpiod 설치시 같이 제공되는 유틸리티는 다음과 같다.

  • gpiofind
  • gpiodetect
  • gpioinfo
  • gpioget
  • gpiomon
  • gpioset

 

gpiodetect
$ gpiodetect --help
Usage: gpiodetect [OPTIONS]
List all GPIO chips, print their labels and number of GPIO lines.
Options:
  -h, --help:		display this message and exit
  -v, --version:	display the version and exit
$ gpiodetect
gpiochip0 [0000:00:01.0] (32 lines)

 

gpioinfo
$ gpioinfo --help
Usage: gpioinfo [OPTIONS] <gpiochip1> ...
Print information about all lines of the specified GPIO chip(s) (or all gpiochips if none are specified).
Options:
  -h, --help:		display this message and exit
  -v, --version:	display the version and exit

$ gpioinfo gpiochip0
gpiochip0 - 32 lines:
	line   0:      unnamed      "sysfs"   input  active-high [used]
	line   1:      unnamed      "sysfs"   input  active-high [used]
	line   2:      unnamed      "sysfs"   input  active-high [used]
	line   3:      unnamed      "sysfs"   input  active-high [used]
	line   4:      unnamed       unused   input  active-high 
	line   5:      unnamed       unused  output  active-high 
	line   6:      unnamed       unused   input  active-high 
        ...

 

gpioget
$ gpioget --help
Usage: gpioget [OPTIONS] <chip name/number> <offset 1> <offset 2> ...
Read line value(s) from a GPIO chip
Options:
  -h, --help:		display this message and exit
  -v, --version:	display the version and exit
  -l, --active-low:	set the line active state to low

$ gpioget gpiochip0 4
0

 

gpioset
$ gpioset --help
Usage: gpioset [OPTIONS] <chip name/number> <offset1>=<value1> <offset2>=<value2> ...
Set GPIO line values of a GPIO chip
Options:
  -h, --help:		display this message and exit
  -v, --version:	display the version and exit
  -l, --active-low:	set the line active state to low
  -m, --mode=[exit|wait|time|signal] (defaults to 'exit'):
		tell the program what to do after setting values
  -s, --sec=SEC:	specify the number of seconds to wait (only valid for --mode=time)
  -u, --usec=USEC:	specify the number of microseconds to wait (only valid for --mode=time)
  -b, --background:	after setting values: detach from the controlling terminal

Modes:
  exit:		set values and exit immediately
  wait:		set values and wait for user to press ENTER
  time:		set values and sleep for a specified amount of time
  signal:	set values and wait for SIGINT or SIGTERM
$ gpioset gpiochip0 5=1

 

gpiomon
$ gpiomon --help
Usage: gpiomon [OPTIONS] <chip name/number> <offset 1> <offset 2> ...
Wait for events on GPIO lines
Options:
  -h, --help:		display this message and exit
  -v, --version:	display the version and exit
  -l, --active-low:	set the line active state to low
  -n, --num-events=NUM:	exit after processing NUM events
  -s, --silent:		don't print event info
  -r, --rising-edge:	only process rising edge events
  -f, --falling-edge:	only process falling edge events
  -F, --format=FMT	specify custom output format

Format specifiers:
  %o:  GPIO line offset
  %e:  event type (0 - falling edge, 1 rising edge)
  %s:  seconds part of the event timestamp
  %n:  nanoseconds part of the event timestamp
$ gpiomon gpiochip0 6
event:  RISING EDGE offset: 6 timestamp: [1527052928.061198582]

 

샘플 소스 파일

Makefile

1LIBGPIOD_DIR=/root/libgpiod/include
2 
3all:
4    gcc -I. -I$LIBGPIOD_DIR -lgpiod new-gpio-api2.c -o new-gpio-api2
5 
6clean:
7    rm -f *.o
8    rm -f new-gpio-api2

 

new-gpio-api2.c

01#include <stdio.h>
02#include "gpiod.h"
03 
04int main(int argc, char *argv[])
05{
06        int gpio1, gpio2, gpio3;
07        int fd;
08        int req_fd;
09        int value;
10        char * file;
11    int rv;
12 
13    struct gpiod_chip *chip;
14    struct gpiod_line *line;
15 
16    struct timespec ts = { 0, 1000000 };
17    struct gpiod_line_event event;
18    struct gpiod_line_request_config config;
19 
20        if (argc < 5) {
21                printf("Usage: new-gpio-api2 /dev/<gpiochipN> "
22                                "<in-gpio> <out-gpio> <event-gpio>\n");
23                return -1;
24        }
25 
26        file = argv[1];
27 
28        gpio1 = atoi(argv[2]);
29        gpio2 = atoi(argv[3]);
30        gpio3 = atoi(argv[4]);
31 
32    chip = gpiod_chip_open(file);
33    if (!chip)
34        return -1;
35 
36    /* foo-input */
37    line = gpiod_chip_get_line(chip, gpio1);
38    if (!line)
39        goto err;
40 
41    rv = gpiod_line_request_input(line, "foo-input2");
42    if (rv)
43        goto err;
44 
45    value = gpiod_line_get_value(line);
46 
47    /* foo-output */
48    line = gpiod_chip_get_line(chip, gpio2);
49    if (!line)
50        goto err;
51 
52    rv = gpiod_line_request_output(line, "foo-output2", 0);
53    if (rv)
54        goto err;
55 
56    gpiod_line_set_value(line, 1);
57 
58    line = gpiod_chip_get_line(chip, gpio3);
59    if (!line)
60        goto err;
61 
62    rv = gpiod_line_request_rising_edge_events_flags(line, "foo-event2",
63            GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN);
64    if (rv)
65        goto err;
66 
67    while (1)
68    {
69        do {
70            rv = gpiod_line_event_wait(line, &ts);
71        } while (rv <= 0);
72 
73        rv = gpiod_line_event_read(line, &event);
74    }
75 
76err:
77    gpiod_chip_close(chip);
78 
79    return -1;
80}

 

참고

 

 

댓글 남기기