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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/gpio.h>
#include <poll.h>
#include <inttypes.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
        int gpio1, gpio2, gpio3;
        int fd;
        int req_fd;
        int value;
        char * file;

        if (argc < 5) {
                printf("Usage: new-gpio-api /dev/<gpiochipN> "
                                "<in-gpio> <out-gpio> <event-gpio>\n");
                return -1;
        }

        file = argv[1];
        fd = open(file, O_RDWR);
        if (fd < 0) {
                printf("open \"%s\" failed\n", file);
                return -1;
        }

        gpio1 = atoi(argv[2]);
        gpio2 = atoi(argv[3]);
        gpio3 = atoi(argv[4]);

        if (chip_info(fd) < 0)
                goto err;

        if (line_info(fd, gpio1) < 0)
                goto err;

        if (request_gpio(fd, gpio1, GPIOHANDLE_REQUEST_INPUT,
                "foo_input", &req_fd) < 0)
                goto err;

        if (get_value(req_fd, gpio1, &value) < 0)
                goto err;

        if (request_gpio(fd, gpio2, GPIOHANDLE_REQUEST_OUTPUT,
                "foo_output", &req_fd) < 0)
                goto err;

        if (set_value(req_fd, gpio2, 1) < 0)
                goto err;

        if (request_event(fd, gpio3, "foo_event", &req_fd) < 0)
                goto err;

        while (1)
                if (recv_event(req_fd, gpio3) < 0)
                        break;

err:
        close(fd);
        return -1;
}

 

GPIO chip 정보 알아오기

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

int chip_info(int fd)
{
        int ret;
        struct gpiochip_info cinfo;

        ret = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &cinfo);
        if (ret < 0) {
                printf("GPIO_GET_CHIPINFO_IOCTL failed.\n");
                return -1;
        }

        printf("GPIO chip: %s, \"%s\", %u GPIO lines\n",
                cinfo.name, cinfo.label, cinfo.lines);

        return 0;
}

 

GPIO line 정보 알아오기

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

int line_info(int fd, int gpio)
{
        int ret;
        struct gpioline_info linfo;

        memset(&linfo, 0, sizeof(linfo));
        linfo.line_offset = gpio;

        ret = ioctl(fd, GPIO_GET_LINEINFO_IOCTL, &linfo);
        if (ret < 0) {
                printf("GPIO_GET_LINEINFO_IOCTL failed.\n");
                return -1;
        }

        printf("line %2d: %s\n", linfo.line_offset, linfo.name);

        return fd;
}

 

GPIO line 사용 요청

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

  • flags
    • GPIOHANDLE_REQUEST_INPUT
    • GPIOHANDLE_REQUEST_OUTPUT
int request_gpio(int fd, int gpio, int flags, const char *label, int *req_fd)
{
        struct gpiohandle_request req;
        int ret;

        req.flags = flags;
        req.lines = 1;
        req.lineoffsets[0] = gpio;
        req.default_values[0] = 0;
        strcpy(req.consumer_label, label);

        ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req);
        if (ret < 0) {
                printf("GPIO_GET_LINEHANDLE_IOCTL failed.\n");
                return -1;
        }

        *req_fd = req.fd;

        return 0;
}

 

GPIO line 읽기(Read)

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

int get_value(int req_fd, int gpio, int *value)
{
        struct gpiohandle_data data;
        int ret;

        memset(&data, 0, sizeof(data));

        ret = ioctl(req_fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data);
        if (ret < 0) {
                printf("GPIO_GET_LINE_VALUES_IOCTL failed.\n");
                return -1;
        }
        printf("line %d is %s\n", gpio, data.values[0] ? "high" : "low");

        *value = data.values[0];

        return 0;
}

 

GPIO line 쓰기(Write)

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

int set_value(int req_fd, int gpio, int value)
{
        struct gpiohandle_data data;
        int ret;

        memset(&data, 0, sizeof(data));
        data.values[0] = value;

        ret = ioctl(req_fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data);
        if (ret < 0) {
                printf("GPIO_SET_LINE_VALUES_IOCTL failed.\n");
                return -1;
        }
        printf("line %d is %s\n", gpio, data.values[0] ? "high" : "low");

        return 0;
}

 

GPIO line 이벤트 요청

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

  • 아래 예에서는 핀에 open drain 설정을 하였고, rising edge 및 falling edge 이벤트에 반응하도록 설정하였다.
int request_event(int fd, int gpio, const char *label, int *req_fd)
{
        struct gpioevent_request req;
        int ret;

        req.lineoffset = gpio;
        strcpy(req.consumer_label, label);
        req.handleflags = GPIOHANDLE_REQUEST_OPEN_DRAIN;
        req.eventflags = GPIOEVENT_REQUEST_RISING_EDGE |
                GPIOEVENT_REQUEST_FALLING_EDGE;

        ret = ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &req);
        if (ret < 0) {
                printf("GPIO_GET_LINEEVENT_IOCTL failed.\n");
                return -1;
        }

        *req_fd = req.fd;

        return 0;
}

 

GPIO line 이벤트 대기

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

#define USE_POLL

int recv_event(int req_fd, int gpio)
{
        struct gpioevent_data event;
        struct pollfd pfd;
        ssize_t rd;
        int ret;

#ifdef USE_POLL
        pfd.fd = req_fd;
        pfd.events = POLLIN | POLLPRI;

        rd = poll(&pfd, 1, 1000);
        if (rd < 0)
        if (ret < 0) {
                printf("poll failed.\n");
                return -1;
        }
#endif
        ret = read(req_fd, &event, sizeof(event));
        if (ret < 0) {
                printf("read failed.\n");
                return -1;
        }

        printf( "GPIO EVENT @%" PRIu64 ": ", event.timestamp);

        if (event.id == GPIOEVENT_EVENT_RISING_EDGE)
                printf("RISING EDGE");
        else
                printf("FALLING EDGE");
        printf ("\n");

        return 0;
}

 

동작 확인

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

LIBGPIOD_DIR=/root/libgpiod/include

all:
	gcc -I. -I$LIBGPIOD_DIR -lgpiod new-gpio-api2.c -o new-gpio-api2 

clean:
	rm -f *.o
	rm -f new-gpio-api2

 

new-gpio-api2.c

#include <stdio.h>
#include "gpiod.h"

int main(int argc, char *argv[])
{
        int gpio1, gpio2, gpio3;
        int fd;
        int req_fd;
        int value;
        char * file;
	int rv;

	struct gpiod_chip *chip;
	struct gpiod_line *line;

	struct timespec ts = { 0, 1000000 };
	struct gpiod_line_event event;
	struct gpiod_line_request_config config;

        if (argc < 5) {
                printf("Usage: new-gpio-api2 /dev/<gpiochipN> "
                                "<in-gpio> <out-gpio> <event-gpio>\n");
                return -1;
        }

        file = argv[1];

        gpio1 = atoi(argv[2]);
        gpio2 = atoi(argv[3]);
        gpio3 = atoi(argv[4]);

	chip = gpiod_chip_open(file);
	if (!chip)
		return -1;

	/* foo-input */
	line = gpiod_chip_get_line(chip, gpio1);
	if (!line) 
		goto err;

	rv = gpiod_line_request_input(line, "foo-input2");
	if (rv)
		goto err;

	value = gpiod_line_get_value(line);

	/* foo-output */
	line = gpiod_chip_get_line(chip, gpio2);
	if (!line)
		goto err;

	rv = gpiod_line_request_output(line, "foo-output2", 0);
	if (rv)
		goto err;

	gpiod_line_set_value(line, 1);

	line = gpiod_chip_get_line(chip, gpio3);
	if (!line)
		goto err;

	rv = gpiod_line_request_rising_edge_events_flags(line, "foo-event2", 
			GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN);
	if (rv) 
		goto err;

	while (1)
	{
		do {
			rv = gpiod_line_event_wait(line, &ts);
		} while (rv <= 0);

		rv = gpiod_line_event_read(line, &event);
	}

err:
	gpiod_chip_close(chip);

	return -1;
}

 

참고

 

 

댓글 남기기