Работа с GPIO в Linux. Часть 6. Библиотека Libgpiod [обновлено 25.07.2024]

В 2017 году Bartosz Golaszewski начал разработку библиотеки Libgpiod и утилит для работы с GPIO. Библиотека libgpiod инкапсулирует вызовы ioctl и структуры данных за простым API в Linux, предназначена для замены доступа к GPIO через виртуальную файловую систему sysfs. Используемый в библиотеке новый интерфейс chardev гарантирует, что все выделенные ресурсы будут освобождены после закрытия файлового дескриптора устройства, и добавляет несколько новых функций, которых нет в устаревшем интерфейсе sysfs (например, опрос событий, установка/чтение нескольких значений одновременно). В публикации будет установка библиотеки Libgpiod и работа с GPIO используя утилиты, на примере светодиода и кнопки. Практическая часть выполняется на одноплатном компьютере Banana Pi BPI-M64, ОС Armbain.

Оглавление

  1. Постановка задачи
  2. Схема подключения светодиода (LED) и кнопки
  3. Интерфейс GPIO ядра Linux
  4. Старый путь: использование виртуальной файловой системы sysfs для доступа к GPIO
  5. Новый путь: интерфейс chardev
  6. Библиотека libgpiod и инструменты управления GPIO
  7. Литература

Постановка задачи

Установить библиотеку libgpiod и утилиты для работы с GPIO. Выполнить демонстрацию работы утилит на примере включения/включения светодиода и получение событий нажатия на кнопку.

Схема подключения светодиода (LED) и кнопки

Подключим светодиод и кнопку на 40-контактный разъем совместимый с Raspberry Pi 3. Светодиод подключаем к №33 контакту разъема, название контакта «PB4», номер линии — 36. Кнопка подключим к №35 контакту разъема, название контакта «PB6», номер линии — 38. Необходимо обратить внимание на поддержку прерывания на контакте «PB6» для кнопки. Поддержка прерывания необходима для исключения постоянного опроса линии с помощью CPU. На контакте «PB6» доступно прерывание «PB_EINT6», поэтому кнопку к этому контакту и подключим. Например, соседний контакт «PL12» не имеет прерывание, поэтому подключать кнопку к нему кнопку не будем. К разъему  совместимого с Raspberry Pi 3, подключается светодиод и кнопка от DFROBOT. Если вы подключаете кнопку и светодиод напрямую, то не забывайте в цепь добавлять резистор для сопротивления во избежания выгорания порта! Схема распиновки контактов доступна в публикации Распиновка GPIO для Banana Pi BPI-M64.

libgpiod Armbian
Схема подключения светодиода (LED) и кнопки к 40-контактному разъему совместимый с Raspberry Pi 3.

libgpiod Armbian
Схема назначения контактов к которым подключается светодиод (LED) и кнопка.

Интерфейс GPIO ядра Linux

GPIO (General-Purpose Input/Output) является одним из наиболее часто используемых периферийных устройств во встраиваемых системах (embedded system) Linux.

Во внутренней архитектуре ядро Linux реализует доступ к GPIO через модель производитель/потребитель. Существуют драйверы, которые предоставляют доступ к линиям GPIO (драйверы контроллеров GPIO) и драйверы, которые используют линии GPIO (клавиатура, сенсорный экран, датчики и т. д.).

В ядре Linux система gpiolib занимается регистрацией и распределением GPIO. Эта структура доступна через API как для драйверов устройств, работающих в пространстве ядра (kernel space), так и для приложений пользовательского пространства (user space).

libgpiod Armbian
Схема работы gpiolib

Старый путь: использование виртуальной файловой системы sysfs для доступа к GPIO

До версии ядра Linux 4.7 для управления GPIO в пользовательском пространстве использовался интерфейс sysfs. Линии GPIO были доступны при экспорте по пути  /sys/class/gpio . Более подробно почитать про интерфейс sysfs в публикации Работа с GPIO на примере Banana Pi BPI-M64. Часть 1. Интерфейс sysfs LED и DS18B20. Так, например, для подачи сигнала «0» или «1» на линию GPIO, необходимо:

  1. Определить номер линии (или номер ножки процессора) GPIO;
  2. Экспортировать номер GPIO, записав его номер в  /sys/class/gpio/export ;
  3. Конфигурировать линию GPIO как вывод, указав это в  /sys/class/gpio/gpioX/direction ;
  4. Установить значение «1» или «0» для линии GPIO  /sys/class/gpio/gpioX/value ;

Для наглядности установим для линии GPIO 36 (подключен светодиод) из пользовательского пространства, значение «1». Для этого необходимо выполнить команды:

$ echo 36 > /sys/class/gpio/export
$ echo out > /sys/class/gpio/gpio36/direction
$ echo 1 > /sys/class/gpio/gpio36/value

Этот подход очень простой как и интерфейс sysfs, он неплохо работает, но имеет некоторые недостатки:

  1. Экспорт линии GPIO не связан с процессом, поэтому если процесс использующий линию GPIO аварийно завершит свою работу, то эта линия GPIO так и останется  экспортированной;
  2. Учитывая первый пункт, возможен совместный доступ к одной и той же линии GPIO, что приведет к проблеме совместного доступа. Процесс не может «узнать» у ОС используется ли та или иная линия GPIO в настоящий момент;
  3. Для каждой линии GPIO приходится выполнять множество операций open()/read()/write()/close(), а так же указывать параметры (export, direction, value, и т.д.) используя методы работы с файлами. Это усложняет программный код, и замедляет скорость работы программы;
  4. Невозможно включить/выключить сразу несколько линий GPIO одним вызовом;
  5. Процесс опроса для перехвата событий (прерываний от линий GPIO) ненадежен;
  6. Нет единого интерфейса (API) для конфигурирования линий GPIO;
  7. Номера, присвоенные линиям GPIO непостоянны, их приходится каждый раз экспортировать;
  8. Низкая скорость работы с линиями GPIO;

Новый путь: интерфейс chardev

Начиная с ядра Linux версии 4.8 интерфейс GPIO sysfs объявлен как «deprecated» и не рекомендуется к использованию. На замену sysfs появился новый интерфейс API, основанный на символьных устройствах для доступа к линиям GPIO из пользовательского пространства.

Каждый контроллер GPIO (gpiochip) будет иметь символьное устройство в разделе  /dev , и мы можем использовать файловые операции (open(), read(), write(), ioctl(), poll(), close()) для управления и взаимодействия с линиями GPIO. контроллеры GPIO доступны по путям  /dev/gpiochipN  или  /sys/bus/gpiochipN , где N — порядковый номер чипа. Просмотр доступных контроллеров GPIO (gpiochip) на Banana Pi BPI-M64:

root@bananapim64:~# ls /dev/gpiochip*
/dev/gpiochip0  /dev/gpiochip1  /dev/gpiochip2

libgpiod Armbian
Стек работы библиотеки libgpiod.

Несмотря на то, что новый API предотвращает управление линиями  GPIO с помощью стандартных инструментов командной строки, таких как echo и cat, он обладает весомыми преимуществами по сравнению с интерфейсом sysfs, а именно:

  • Выделение линий GPIO связано с процессом, который он его использует. При завершении процесса, так же в случае аварийного завершения, линии GPIO используемые процессом автоматически освобождаются;
  • Дополнительно, можно всегда определить какой процесс в данное время использует определенную линию GPIO;
  • Можно одновременно читать и писать в несколько линий GPIO одновременно;
  • Контроллеры GPIO и линии GPIO можно найти по названию;
  • Можно настроить состояние вывода контакта (open-source, open-drain и т. д.);
  • Процесс опроса для перехвата событий (прерывания от линий GPIO) надежен.

Библиотека libgpiod и инструменты управления GPIO

Для использования нового интерфейса символьного устройства есть библиотека и набор инструментов, предоставляемых проектом libgpiod.

Libgpiod (Library General Purpose Input/Output device) предоставляет набор API для вызова из своих программ и несколько утилит для управления линиями GPIO из пользовательского режима.

В состав libgpiod входят следующие утилиты:

  • gpiodetect — выведет список всех чипов GPIO, их метки и количество линий;
  • gpioinfo — выведет информацию о линиях GPIO конкретного контроллера GPIO. В таблице вывода по колонкам будет указано: номер линии, название контакта, направление ввода/вывода, текущее состояние;
  • gpioget — считает текущее состояние линии GPIO;
  • gpioset — установит значение для линии GPIO;
  • gpiomon — осуществляет мониторинг состояния линии GPIO и выводит значение при изменение состояния;
  • gpionotify — ожидает определенное состояние линии GPIO и выводит статус изменение в консоль.
Пример вывода результата работы утилит для Raspberry Pi 4B
# Detect the available gpiochips.
$ gpiodetect
gpiochip0 [pinctrl-bcm2711] (58 lines)
gpiochip1 [raspberrypi-exp-gpio] (8 lines)

# Read the info for all the lines on a gpiochip.
$ gpioinfo -c 1
gpiochip1 — 8 lines:
line 0: «BT_ON» output
line 1: «WL_ON» output
line 2: «PWR_LED_OFF» output active-low consumer=»led1″
line 3: «GLOBAL_RESET» output
line 4: «VDD_SD_IO_SEL» output consumer=»vdd-sd-io»
line 5: «CAM_GPIO» output consumer=»cam1_regulator»
line 6: «SD_PWR_ON» output consumer=»sd_vcc_reg»
line 7: «SD_OC_N» input

# Read the info for particular lines.
$ ./gpioinfo PWR_LED_OFF STATUS_LED_G_CLK GLOBAL_RESET
gpiochip0 42 «STATUS_LED_G_CLK» output consumer=»led0″
gpiochip1 2 «PWR_LED_OFF» output active-low consumer=»led1″
gpiochip1 3 «GLOBAL_RESET» output

# Read the value of a single GPIO line by name.
$ gpioget RXD1
«RXD1″=active

# Read the value of a single GPIO line by chip and offset.
$ gpioget -c 0 15
«15»=active

# Read the value of a single GPIO line as a numeric value.
$ gpioget —numeric RXD1
1

# Read two values at the same time. Set the active state of the lines
# to low and without quoted names.
$ gpioget —active-low —unquoted GPIO23 GPIO24
GPIO23=active GPIO24=active

# Set the value of a line and hold the line until killed.
$ gpioset GPIO23=1

# Set values of two lines, then daemonize and hold the lines.
$ gpioset —daemonize GPIO23=1 GPIO24=0

# Set the value of a single line, hold it for 20ms, then exit.
$ gpioset —hold-period 20ms -t0 GPIO23=1

# Blink an LED on GPIO22 at 1Hz
$ gpioset -t500ms GPIO22=1

# Blink an LED on GPIO22 at 1Hz with a 20% duty cycle
$ gpioset -t200ms,800ms GPIO22=1

# Set some lines interactively (requires —enable-gpioset-interactive)
$ gpioset —interactive —unquoted GPIO23=inactive GPIO24=active
gpioset> get
GPIO23=inactive GPIO24=active
gpioset> toggle
gpioset> get
GPIO23=active GPIO24=inactive
gpioset> set GPIO24=1
gpioset> get
GPIO23=active GPIO24=active
gpioset> toggle
gpioset> get
GPIO23=inactive GPIO24=inactive
gpioset> toggle GPIO23
gpioset> get
GPIO23=active GPIO24=inactive
gpioset> exit

# Wait for three rising edge events on a single GPIO line, then exit.
$ gpiomon —num-events=3 —edges=rising GPIO22
10002.907638045 rising «GPIO22»
10037.132562259 rising «GPIO22»
10047.179790748 rising «GPIO22»

# Wait for three edge events on a single GPIO line, with time in local time
# and with unquoted line name, then exit.
$ gpiomon —num-events=3 —edges=both —localtime —unquoted GPIO22
2022-11-15T10:36:59.109615508 rising GPIO22
2022-11-15T10:36:59.129681898 falling GPIO22
2022-11-15T10:36:59.698971886 rising GPIO22

# Wait for falling edge events with a custom output format.
$ gpiomon —format=»%e %c %o %l %S» —edges=falling -c gpiochip0 22
2 gpiochip0 22 GPIO22 10946.693481859
2 gpiochip0 22 GPIO22 10947.025347604
2 gpiochip0 22 GPIO22 10947.283716669
2 gpiochip0 22 GPIO22 10947.570109430

# Block until an edge event occurs. Don’t print anything.
$ gpiomon —num-events=1 —quiet GPIO22

# Monitor multiple lines, exit after the first edge event.
$ gpiomon —quiet —num-events=1 GPIO5 GPIO6 GPIO12 GPIO17

# Monitor a line for changes to info.
$ gpionotify GPIO23
11571.816473718 requested «GPIO23»
11571.816535124 released «GPIO23»
11572.722894029 requested «GPIO23»
11572.722932843 released «GPIO23»
11573.222998598 requested «GPIO23»

# Monitor a line for requests, reporting UTC time and unquoted line name.
$ gpionotify —utc —unquoted GPIO23
2022-11-15T03:05:23.807090687Z requested GPIO23
2022-11-15T03:05:23.807151390Z released GPIO23
2022-11-15T03:05:24.784984280Z requested GPIO23
2022-11-15T03:05:24.785023873Z released GPIO23

# Monitor multiple lines, exit after the first is requested.
$ gpionotify —quiet —num-events=1 —event=requested GPIO5 GPIO6 GPIO12 GPIO17

# Block until a line is released.
$ gpionotify —quiet —num-events=1 —event=released GPIO6

Например, следующая программа написанная на C использует libgpiod для чтения строки GPIO:

void main() {
	struct gpiod_chip *chip;
	struct gpiod_line *line;
	int req, value;

	chip = gpiod_chip_open("/dev/gpiochip0");
	if (!chip)
		return -1;

	line = gpiod_chip_get_line(chip, 3);
	if (!line) {
		gpiod_chip_close(chip);
		return -1;
	}

	req = gpiod_line_request_input(line, "gpio_state");
	if (req) {
		gpiod_chip_close(chip);
		return -1;
	}

	value = gpiod_line_get_value(line);

	printf("GPIO value is: %d\n", value);

	gpiod_chip_close(chip);
}

Библиотеку можно вызывать так же и из кода на C++, Python, C#, Rust и т.д.

Для управления линиями GPIO из терминала необходимо использовать инструменты командной строки, предоставляемые libgpiod. Библиотеку libgpiod и инструменты управления GPIO можно установить из пакетов, бинарников, или скомпилировать из исходного текста.

Установка библиотеки libgpiod и инструментов управления GPIO

Репозитарий библиотеки libgpiod доступен по адресу libgpiod/libgpiod.git. В разделе Download опубликованы релизы библиотеки. На 25.07.2024 последний релиз: v2.1.2

Начиная с версии 2.0 библиотеки libgpiod требуется интерфейс uAPI v2 GPIO символьных устройств, который впервые был добавлен в ядро Linux 5.10.

Библиотека libgpiod установливается из репозитария дистрибутива, но скорее всего, будет доступна старая версия (в большинстве случаев это будет версия v1.6.3):

$ sudo apt-get update
$ sudo apt-get install -y libgpiod-dev gpiod

Для установки последней актуальной версии необходимо выполнить скрипт установки. Доступна установки из скомпилированных бинарников для вашей ОС и установка из исходного текста.

В репозитарии devdotnetorg/docker-libgpiod присутствуют два скрипта:

  • setup-libgpiod.sh — установка библиотеки libgpiod из репозитория, бинарников, исходного текста;
  • remove-libgpiod.sh — удаление библиотеки libgpiod в не зависимости от варианта установки.

Вызов скрипта setup-libgpiod.sh без указания аргументов позволит установить библиотеку libgpiod в интерактивном режиме.

Скрипт установки библиотеки libgpiod и утилит для x86/ARM32/ARM64/RISC-V:

$ sudo apt-get update
$ sudo apt-get install -y curl
$ curl -SL --output setup-libgpiod.sh https://raw.githubusercontent.com/devdotnetorg/docker-libgpiod/HEAD/setup-libgpiod.sh
$ chmod +x setup-libgpiod.sh
$ sudo ./setup-libgpiod.sh

Рекомендуется вариан установки из бинарников  (4) Installation from binaries . Для вашей ОС загружается архив *.zip, затем содержимое архива распаковывается в системные директории. Все архивы располагаются в папке /out.

Бинарники скомпилированы для следующих ОС:

  • Alpine 3.15, 3.16, 3.17, 3.18, 3.19, 3.20;
  • Debian 11, 12;
  • Ubuntu 18.04, 20.04, 22.04, 23.10, 24.04.

Библиотека libgpiod компилируется со следущими аргументами:

--enable-tools=yes \
--enable-bindings-cxx \
--enable-bindings-python \
ac_cv_func_malloc_0_nonnull=yes

Что включается в себя поддержку вызова функций из C++ и Python. Поддержка Rust не включена в компиляцию, будет добавлена позже.

Как самостоятельно собрать библиотеку для разных ОС рассказано в публикации Кросс-компиляция проекта в Docker используя Buildx на примере сборки shadowsocks-rust и библиотеки Libgpiod.

Если архив для вашей системы имеет название типа «libgpiod-bin-2.0-ubuntu-18.04-aarch64-without_support_python_and_cxx», то это говорит о том, что в данной версии отсутствует поддержка Python и C++, в основном это старые версии Ubuntu 18.04 и 20.04. Если название архива заканчивается на «-without_support_python.zip», то поддержка C++ присутствует без поддержки Python.

По итогу выполнения скрипта должна появиться надпись «Successfully», это означает что библиотека и утилиты успешно установлены. Дополнительно для проверки, можно вызвать команду с выводом номера версии библиотеки:

root@bananapim64:~# gpiodetect -v
gpiodetect (libgpiod) v2.1.2
Copyright (C) 2017-2023 Bartosz Golaszewski
License: GPL-2.0-or-later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Для удаления библиотеки предназначен скрипт: remove-libgpiod.sh.

Скрипт удаления библиотеки libgpiod и утилит
$ curl -SL --output remove-libgpiod.sh https://raw.githubusercontent.com/devdotnetorg/docker-libgpiod/HEAD/remove-libgpiod.sh
$ chmod +x remove-libgpiod.sh
$ sudo ./remove-libgpiod.sh

Дополнительные аргументы скрипта setup-libgpiod.sh

Если необходимо установить конкретную версию библиотеки libgpiod из бинарников без каких либо вопросов, например подойдет для создания контейнера, то необходимо выполнить команду:

$ sudo ./setup-libgpiod.sh --type binary --version 2.1 --canselect no

Остальные аргументы описаны в заголовке скрипта установки.

Инструменты библиотеки libgpiod

Команда gpiodetect выведет список всех чипов GPIO, их метки и количество линий. Результат выполнения команды:

root@bananapim64:~# gpiodetect
gpiochip0 [1f02c00.pinctrl] (32 lines)
gpiochip1 [1c20800.pinctrl] (256 lines)
gpiochip2 [axp20x-gpio] (2 lines)

gpiochip0 и gpiochip1, это чипы входящие в состав SoC Allwinner A64. gpiochip1 имеет выход на 40-контактный разъем совместимый с Raspberry Pi. Чип gpiochip2 — отдельная микросхема управления электропитанием axp209 подключенная по интерфейсу I2C.

Для вывода справки к вызываемой команде необходимо добавлять параметр «—help». Вызов справки для команды gpiodetect. Результат выполнения команды:

root@bananapim64:~# gpiodetect --help
Usage: gpiodetect [OPTIONS] [chip]...

List GPIO chips, print their labels and number of GPIO lines.
Chips may be identified by number, name, or path.
e.g. '0', 'gpiochip0', and '/dev/gpiochip0' all refer to the same chip.
If no chips are specified then all chips are listed.

Options:
  -h, --help            display this help and exit
  -v, --version         output version information and exit

Команда gpioinfo выведет информацию о линиях GPIO конкретного контроллера GPIO (или всех контроллеров GPIO, если они не указаны). Результат выполнения команды:

root@bananapim64:~# gpioinfo --chip 1
gpiochip1 - 256 lines:
        line   0:      unnamed       unused   input  active-high
...
        line  64:      unnamed         "dc"  output  active-high [used]
...
        line  68:      unnamed "backlightlcdtft" output active-high [used]
...
        line  96:      unnamed   "spi0 CS0"  output   active-low [used]
        line  97:      unnamed       unused   input  active-high
        line  98:      unnamed       unused   input  active-high
        line  99:      unnamed       unused   input  active-high
        line 100:      unnamed      "reset"  output   active-low [used]
...
        line 120:      unnamed "bananapi-m64:red:pwr" output active-high [used]
...
        line 254:      unnamed       unused   input  active-high
        line 255:      unnamed       unused   input  active-high

В таблице по колонкам указано: номер линии, название контакта, направление ввода/вывода, текущее состояние. Сейчас к Banana Pi BPI-M64 подключен LCD экран ILI9341 на SPI интерфейсе, для подключения используется вариант с управляемой подсветкой, файл DTS sun50i-a64-spi-ili9341-backlight-on-off.dts. В DTS файле контакт «PC4» GPIO68 обозначен для управления подсветкой, название «backlightlcdtft». Соответственно в выводе команды, указан номер линии 68, название «backlightlcdtft», направление — вывод, текущее состояние — active-high (включено).

Команда gpioset установит значение для линии GPIO. Например, следующая команда попытается выключит подсветку на LCD ILI9341. Команда:  gpioset —hold-period 200ms -t0 —chip 1 68=0 , где 1 — gpiochip1, 68 — номер линии(контакта), 0 — логическое значение, может быть «0» или «1», 200ms — время удержания линии. Результат выполнения команды:

root@bananapim64:~# gpioset --banner --hold-period 200ms -t0 --chip 1 68=0
gpioset: unable to request lines on chip '/dev/gpiochip1': Device or resource busy

В результате мы получим ошибку — линия занята, т.к. данная линия занята драйвером «gpio-backlight».

Попробуем включить подключенный светодиод на линии 36, название «PB4», номер контакта на 40-контактный разъем совместимый с Raspberry Pi №33. Результат выполнения команды:

root@bananapim64:~# gpioset --chip 1 36=1
gpioset --banner --hold-period 200ms -t0 --chip 1 36=1

В результате выполнения команды, светодиод включится.

Команда gpioget считывает текущее состояние линии GPIO. Результат выполнения команды:

root@bananapim64:~# gpioget --chip 1 36
1

Получили значение «1», т.к. до этого включили светодиод командой gpioset.

Команда gpiomon осуществляет мониторинг состояния линии GPIO и выводит значение при изменение состояния. Будем мониторить состояние кнопки, которая подключена на линию 38, название «PB4», номер контакта на 40-контактный разъем совместимый с Raspberry Pi №35. Команда:  gpiomon —chip 1 38 , где 1 — gpiochip1, 38 — номер линии (контакта). Результат выполнения команды:

root@bananapim64:~# gpiomon --chip 1 38
631.682345039   rising  gpiochip1 38
632.836326153   falling gpiochip1 38
634.400444509   rising  gpiochip1 38
635.585806741   falling gpiochip1 38

Кнопка несколько раз нажималась. RISING — повышение, изменение напряжения с 0V до 3.3V, кнопка нажата и удерживается состояние. FALLING — понижение, изменение напряжения с 3.3V до 0V, происходит отпускание кнопки, и кнопка переходит в состояние «не нажата».

С механической кнопкой возникла проблема из-за дребезга контакта, регистрировались множественные нажатия вместо одного. Поэтому механическая кнопка была заменена на емкостную (touch) кнопку.

GitHub — devdotnetorg/docker-libgpiod.

Читайте также:

Литература

Вам также может понравиться

About the Author: Anton

Programistik