새로운 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)
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
08 | #include <linux/gpio.h> |
13 | int main( int argc, char *argv[]) |
15 | int gpio1, gpio2, gpio3; |
22 | printf ( "Usage: new-gpio-api /dev/<gpiochipN> " |
23 | "<in-gpio> <out-gpio> <event-gpio>\n" ); |
28 | fd = open(file, O_RDWR); |
30 | printf ( "open \"%s\" failed\n" , file); |
34 | gpio1 = atoi (argv[2]); |
35 | gpio2 = atoi (argv[3]); |
36 | gpio3 = atoi (argv[4]); |
38 | if (chip_info(fd) < 0) |
41 | if (line_info(fd, gpio1) < 0) |
44 | if (request_gpio(fd, gpio1, GPIOHANDLE_REQUEST_INPUT, |
45 | "foo_input" , &req_fd) < 0) |
48 | if (get_value(req_fd, gpio1, &value) < 0) |
51 | if (request_gpio(fd, gpio2, GPIOHANDLE_REQUEST_OUTPUT, |
52 | "foo_output" , &req_fd) < 0) |
55 | if (set_value(req_fd, gpio2, 1) < 0) |
58 | if (request_event(fd, gpio3, "foo_event" , &req_fd) < 0) |
62 | if (recv_event(req_fd, gpio3) < 0) |
GPIO chip 정보 알아오기
gpio chip 디바이스명, 라벨명, gpio 라인 수를 알아와서 출력한다.
04 | struct gpiochip_info cinfo; |
06 | ret = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &cinfo); |
08 | printf ( "GPIO_GET_CHIPINFO_IOCTL failed.\n" ); |
12 | printf ( "GPIO chip: %s, \"%s\", %u GPIO lines\n" , |
13 | cinfo.name, cinfo.label, cinfo.lines); |
GPIO line 정보 알아오기
요청한 gpio 번호로 지정되어 있는 라인명 등을 알아온다.
01 | int line_info( int fd, int gpio) |
04 | struct gpioline_info linfo; |
06 | memset (&linfo, 0, sizeof (linfo)); |
07 | linfo.line_offset = gpio; |
09 | ret = ioctl(fd, GPIO_GET_LINEINFO_IOCTL, &linfo); |
11 | printf ( "GPIO_GET_LINEINFO_IOCTL failed.\n" ); |
15 | printf ( "line %2d: %s\n" , linfo.line_offset, linfo.name); |
GPIO line 사용 요청
요청한 gpio 번호에 해당하는 라인을 플래그 값에 해당하는 모드로 설정한다. 라벨명도 지정할 수 있다.
- flags
- GPIOHANDLE_REQUEST_INPUT
- GPIOHANDLE_REQUEST_OUTPUT
01 | int request_gpio( int fd, int gpio, int flags, const char *label, int *req_fd) |
03 | struct gpiohandle_request req; |
08 | req.lineoffsets[0] = gpio; |
09 | req.default_values[0] = 0; |
10 | strcpy (req.consumer_label, label); |
12 | ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req); |
14 | printf ( "GPIO_GET_LINEHANDLE_IOCTL failed.\n" ); |
GPIO line 읽기(Read)
요청한 gpio 번호에 해당하는 라인에서 값(high/low)을 읽어 출력한다.
01 | int get_value( int req_fd, int gpio, int *value) |
03 | struct gpiohandle_data data; |
06 | memset (&data, 0, sizeof (data)); |
08 | ret = ioctl(req_fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data); |
10 | printf ( "GPIO_GET_LINE_VALUES_IOCTL failed.\n" ); |
13 | printf ( "line %d is %s\n" , gpio, data.values[0] ? "high" : "low" ); |
15 | *value = data.values[0]; |
GPIO line 쓰기(Write)
요청한 gpio 번호에 해당하는 라인에 값(high/low)을 출력한다.
01 | int set_value( int req_fd, int gpio, int value) |
03 | struct gpiohandle_data data; |
06 | memset (&data, 0, sizeof (data)); |
07 | data.values[0] = value; |
09 | ret = ioctl(req_fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data); |
11 | printf ( "GPIO_SET_LINE_VALUES_IOCTL failed.\n" ); |
14 | printf ( "line %d is %s\n" , gpio, data.values[0] ? "high" : "low" ); |
GPIO line 이벤트 요청
요청한 gpio 번호에 해당하는 라인을 입력 모드로 설정하고 이벤트를 수신할 수 있도록 준비한다.
- 아래 예에서는 핀에 open drain 설정을 하였고, rising edge 및 falling edge 이벤트에 반응하도록 설정하였다.
01 | int request_event( int fd, int gpio, const char *label, int *req_fd) |
03 | struct gpioevent_request req; |
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; |
12 | ret = ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &req); |
14 | printf ( "GPIO_GET_LINEEVENT_IOCTL failed.\n" ); |
GPIO line 이벤트 대기
요청한 gpio 번호에 해당하는 라인에서 이벤트를 수신 대기한다.
03 | int recv_event( int req_fd, int gpio) |
05 | struct gpioevent_data event; |
12 | pfd.events = POLLIN | POLLPRI; |
14 | rd = poll(&pfd, 1, 1000); |
17 | printf ( "poll failed.\n" ); |
21 | ret = read(req_fd, &event, sizeof (event)); |
23 | printf ( "read failed.\n" ); |
27 | printf ( "GPIO EVENT @%" PRIu64 ": " , event.timestamp); |
29 | if (event.id == GPIOEVENT_EVENT_RISING_EDGE) |
30 | printf ( "RISING EDGE" ); |
32 | printf ( "FALLING EDGE" ); |
동작 확인
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
1 | LIBGPIOD_DIR= /root/libgpiod/include |
4 | gcc -I. -I$LIBGPIOD_DIR -lgpiod new-gpio-api2.c -o new-gpio-api2 |
new-gpio-api2.c
04 | int main( int argc, char *argv[]) |
06 | int gpio1, gpio2, gpio3; |
13 | struct gpiod_chip *chip; |
14 | struct gpiod_line *line; |
16 | struct timespec ts = { 0, 1000000 }; |
17 | struct gpiod_line_event event; |
18 | struct gpiod_line_request_config config; |
21 | printf ( "Usage: new-gpio-api2 /dev/<gpiochipN> " |
22 | "<in-gpio> <out-gpio> <event-gpio>\n" ); |
28 | gpio1 = atoi (argv[2]); |
29 | gpio2 = atoi (argv[3]); |
30 | gpio3 = atoi (argv[4]); |
32 | chip = gpiod_chip_open(file); |
37 | line = gpiod_chip_get_line(chip, gpio1); |
41 | rv = gpiod_line_request_input(line, "foo-input2" ); |
45 | value = gpiod_line_get_value(line); |
48 | line = gpiod_chip_get_line(chip, gpio2); |
52 | rv = gpiod_line_request_output(line, "foo-output2" , 0); |
56 | gpiod_line_set_value(line, 1); |
58 | line = gpiod_chip_get_line(chip, gpio3); |
62 | rv = gpiod_line_request_rising_edge_events_flags(line, "foo-event2" , |
63 | GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN); |
70 | rv = gpiod_line_event_wait(line, &ts); |
73 | rv = gpiod_line_event_read(line, &event); |
77 | gpiod_chip_close(chip); |
참고