Embedded HandbookПриложения
Все разделы › XXIV. Встраиваемый Linux
· уровень средний · 10 мин чтения
Открыть в интерактивном справочнике

192. Периферия из программы: gpiochip, i2c-dev, spidev, sysfs

На микроконтроллере вывод переключают записью в регистр: GPIOC->BSRR и всё (раздел 8). На приборе с Linux регистрами владеет ядро, а программа просит его об услуге через файл в /dev. Разница не в сложности, а в том, кто отвечает за железо — и этот раздел о том, как выглядит та же работа с другой стороны.

Числа взяты из заголовков ядра Debian 12 прогоном _verify/linux191/uapi.c в образе stm-linux:1; сверяет _verify/linux191_check.py.

Как это называется в документации

Оригинал Перевод
userspace пользовательское пространство: ваши процессы, не ядро
character device символьное устройство: файл в /dev, к которому обращаются через ioctl
ioctl команда устройству вне read/write
line (GPIO) линия: один вывод контроллера GPIO
consumer кто занял линию: имя, видное в системе
line bias (GPIO) подтяжка линии внутри контроллера

192.1 Кто владеет выводом

Писать в регистры GPIO из программы нельзя, и это не запрет ради запрета. Ядру нужно знать, кто занял вывод: иначе два процесса одновременно включат один и тот же светодиод, а драйвер шины I²C обнаружит, что его ножку кто-то переключил вручную.

Путь от программы до вывода: ядро владеет регистрами, программа просит через символьное устройство

Что нужно Файл Чем обращаются
вывод, вход, прерывание по фронту /dev/gpiochipN ioctl, API версии 2
шина I²C напрямую /dev/i2c-N ioctl I2C_SLAVE и I2C_RDWR
шина SPI напрямую /dev/spidevB.C ioctl SPI_IOC_MESSAGE(n)
ШИМ, светодиоды, АЦП /sys/class/pwm, leds, iio чтение и запись текстовых файлов

Правило, которое экономит дни: если у устройства есть драйвер в ядре — пользуйтесь драйвером, а не прямой работой с шиной. Часы ds1307 из дерева устройств (раздел 191.3) сами появятся как /dev/rtc0, и читать их через i2c-dev не нужно. Прямой доступ — для того, чего в ядре нет: своей микросхемы, отладки, разовой проверки.


192.2 GPIO: /dev/gpiochipN

Старый способ через /sys/class/gpio (запись номера в export) объявлен устаревшим: номер линии там глобальный и разъезжается от ядра к ядру, а освободить забытую линию некому. Нынешний способ — символьное устройство на каждый контроллер и запрос линий через ioctl.

Порядок такой: открыть /dev/gpiochipN, узнать, что это за контроллер, запросить нужные линии одним запросом, получить обратно файловый дескриптор на сами линии и дальше работать с ним. Закрыли дескриптор — линии освободились сами, даже если программа упала.

#include <linux/gpio.h>

int chip = open("/dev/gpiochip1", O_RDWR);

struct gpiochip_info info;
ioctl(chip, GPIO_GET_CHIPINFO_IOCTL, &info);      /* имя, метка, сколько линий */

struct gpio_v2_line_request req = {0};
req.offsets[0] = 21;                               /* линия, которую дерево назвало, но не отдало драйверу */
req.num_lines  = 1;
req.config.flags = GPIO_V2_LINE_FLAG_OUTPUT;
strncpy(req.consumer, "handbook", sizeof(req.consumer) - 1);
ioctl(chip, GPIO_V2_GET_LINE_IOCTL, &req);         /* req.fd — дескриптор линий */

struct gpio_v2_line_values v = { .mask = 1, .bits = 1 };
ioctl(req.fd, GPIO_V2_LINE_SET_VALUES_IOCTL, &v);  /* зажечь */
v.bits = 0;
ioctl(req.fd, GPIO_V2_LINE_SET_VALUES_IOCTL, &v);  /* погасить */
close(req.fd);                                     /* линия свободна */

Что за этим стоит — из заголовков ядра:

Значение Что это
GPIO_GET_CHIPINFO_IOCTL = 0x8044B401 спросить имя контроллера и число линий
GPIO_V2_GET_LINEINFO_IOCTL = 0xC100B405 спросить про одну линию: занята ли и кем
GPIO_V2_GET_LINE_IOCTL = 0xC250B407 запросить линии и получить дескриптор
GPIO_V2_LINE_GET_VALUES_IOCTL = 0xC010B40E прочитать значения
GPIO_V2_LINE_SET_VALUES_IOCTL = 0xC010B40F записать значения
struct gpiochip_info 68 байт
struct gpio_v2_line_info 256 байт
struct gpio_v2_line_request 592 байта
struct gpio_v2_line_event 48 байт
GPIO_V2_LINES_MAX 64 линии за один запрос

Флаги настройки — те же понятия, что и в регистрах микроконтроллера (раздел 8.2): GPIO_V2_LINE_FLAG_INPUT = 0x4, GPIO_V2_LINE_FLAG_OUTPUT = 0x8, GPIO_V2_LINE_FLAG_EDGE_RISING = 0x10, GPIO_V2_LINE_FLAG_BIAS_PULL_UP = 0x100.

Значения передаются не по одной линии, а маской: mask — какие линии трогаем, bits — что в них писать. Вся структура — 16 байт, и одним вызовом переключается до 64 линий сразу.

Прерывание по фронту выглядит как чтение из того же дескриптора: линию запрашивают с флагом EDGE_RISING (или FALLING), а дальше poll и read возвращают по событию структуру на 48 байт — с номером линии и отметкой времени от монотонных часов ядра. Это прямой аналог EXTI из раздела 7, только время проставляет ядро, а не вы.

На практике всё это пишут не руками, а через libgpiod: библиотека прячет ioctl, а её команды gpiodetect, gpioinfo, gpioget, gpioset позволяют проверить проводку до написания кода — как раз то, чего на микроконтроллере обычно не хватает.

Линия GPIO из программы по шагам: открыть /dev/gpiochip1, спросить контроллер через GPIO_GET_CHIPINFO_IOCTL, запросить линии через GPIO_V2_GET_LINE_IOCTL и получить дескриптор req.fd, задать значения маской через GPIO_V2_LINE_SET_VALUES_IOCTL, закрыть дескриптор — линии свободны; внизу номера команд по полям: размер структуры 68, 592 и 16 байт, тип 0xB4


192.3 I²C: /dev/i2c-N

Шина открывается как файл. Дальше два пути: простой — сказать ядру адрес ведомого и пользоваться read/write; правильный — передать пару сообщений одним вызовом.

#include <linux/i2c.h>
#include <linux/i2c-dev.h>

int fd = open("/dev/i2c-0", O_RDWR);

uint8_t reg = 0x75, val = 0;
struct i2c_msg msgs[2] = {
    { .addr = 0x68, .flags = 0,        .len = 1, .buf = &reg },  /* записали номер регистра */
    { .addr = 0x68, .flags = I2C_M_RD, .len = 1, .buf = &val },  /* прочитали байт */
};
struct i2c_rdwr_ioctl_data tr = { .msgs = msgs, .nmsgs = 2 };
ioctl(fd, I2C_RDWR, &tr);

Это та же транзакция с повторным стартом, что и в разделе 17.4, только старт, адрес и подтверждения выставляет драйвер контроллера.

Значение Что это
I2C_SLAVE = 0x703 назначить адрес ведомого для read/write
I2C_SLAVE_FORCE = 0x706 то же, даже если адрес занят драйвером
I2C_TENBIT = 0x704 десятибитная адресация (раздел 17.2)
I2C_RDWR = 0x707 несколько сообщений одной транзакцией
I2C_RETRIES = 0x701, I2C_TIMEOUT = 0x702 сколько раз повторять и сколько ждать
struct i2c_msg 16 байт на 64-разрядной системе, 12 — на 32-разрядном ARM прибора
I2C_RDWR_IOCTL_MAX_MSGS 42 сообщения за вызов
I2C_M_RD = 0x1, I2C_M_TEN = 0x10 чтение; десятибитный адрес

Адрес — семибитный. В addr кладут 0x68, а не 0xD0: сдвиг и бит направления добавляет драйвер. Это ровно та путаница, о которой раздел 17.2, — но здесь ядро решает её за вас.

⚠️ I2C_SLAVE откажет, если адрес уже занят драйвером в ядре. Это не ошибка, а защита: часы ds1307 из дерева устройств держит драйвер rtc. Либо пользуйтесь драйвером, либо уберите устройство из дерева, либо (для отладки) I2C_SLAVE_FORCE — понимая, что двое будут говорить с одной микросхемой.


192.4 SPI: /dev/spidevB.C

Имя файла — номер шины и номер выбранного кристалла: spidev0.0 — первая шина, первый CS. Передача всегда обмен: сколько байт отправили, столько и приняли (раздел 18.1).

#include <linux/spi/spidev.h>

int fd = open("/dev/spidev0.0", O_RDWR);
uint8_t mode = SPI_MODE_0, bits = 8;
uint32_t speed = 24000000;
ioctl(fd, SPI_IOC_WR_MODE, &mode);
ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);

uint8_t tx[3] = { 0x9F, 0, 0 }, rx[3] = { 0 };     /* JEDEC ID памяти, раздел 41 */
struct spi_ioc_transfer t = {
    .tx_buf = (unsigned long)tx,
    .rx_buf = (unsigned long)rx,
    .len = sizeof(tx),
    .speed_hz = speed,
    .bits_per_word = bits,
};
ioctl(fd, SPI_IOC_MESSAGE(1), &t);
Значение Что это
struct spi_ioc_transfer 32 байта
SPI_IOC_WR_MODE = 0x40016B01, SPI_IOC_RD_MODE = 0x80016B01 задать и прочитать режим
SPI_IOC_WR_BITS_PER_WORD = 0x40016B03 бит в слове
SPI_IOC_WR_MAX_SPEED_HZ = 0x40046B04 верхняя частота
SPI_IOC_MESSAGE(1) = 0x40206B00 одна передача
SPI_IOC_MESSAGE(2) = 0x40406B00 две подряд, без отпускания CS между ними
SPI_CPHA = 0x1, SPI_CPOL = 0x2 те же два бита, что и в CR1 микроконтроллера
SPI_MODE_0 = 0x0, SPI_MODE_3 = 0x3 режимы из таблицы раздела 18.2
SPI_3WIRE = 0x10 полудуплекс по одной линии данных

Обратите внимание на номера SPI_IOC_MESSAGE(1) и SPI_IOC_MESSAGE(2): они различаются — 0x40206B00 и 0x40406B00. В номер ioctl входит размер передаваемых данных, а он равен 32 × n: 0x20 и 0x40. Отсюда правило — число в SPI_IOC_MESSAGE(n) обязано совпадать с длиной массива структур. Драйвер читает ровно n структур: при меньшем n лишние передачи молча не выполнятся, при большем он прочтёт память за массивом, и итог зависит от мусора — EINVAL, EFAULT или странная передача.


192.5 Что осталось в sysfs

Не всё переехало в символьные устройства. Часть периферии управляется текстовыми файлами, и это удобно: работает из командной строки и из любого языка.

Куда Что делает
/sys/class/pwm/pwmchip0/ export, затем period и duty_cycle в наносекундах, enable
/sys/class/leds/<имя>/ brightness, а trigger умеет мигать сам: heartbeat, timer
/sys/bus/iio/devices/ АЦП, датчики: сырое значение и множитель для перевода в вольты
/sys/class/thermal/ температура кристалла в тысячных долях градуса

ШИМ здесь задают периодом и временем высокого уровня в наносекундах, а не значениями PSC, ARR и CCR (раздел 13.5): пересчёт делает драйвер. Светодиод из дерева устройств раздела 191.3 с linux,default-trigger = "heartbeat" начнёт мигать сам, без единой строки кода, — удобный признак «система жива».


192.6 Грабли

Открыли /dev/gpiochip0 и не нашли нужную линию. Контроллеров несколько, нумерация внутри каждого своя. gpiodetect показывает контроллеры, gpioinfo — линии с именами и тем, кто их занял.

Линия занята. Её держит драйвер (светодиод, шина, сенсор) по дереву устройств. Освободить можно, только убрав узел из дерева, — а не «форсом».

Значение записали, а вывод не изменился. В gpio_v2_line_values забыли mask: без бита в маске ядро не трогает линию.

Линия гаснет сразу после записи. Закрыли дескриптор — ядро вернуло линию в исходное состояние. Держать дескриптор открытым столько, сколько нужен уровень.

ioctl на SPI возвращает EINVAL или шлёт не то. Число в SPI_IOC_MESSAGE(n) не совпадает с числом структур: номер ioctl считается из размера, и драйвер читает ровно n структур.

Адрес I²C не тот. В addr идёт семибитный адрес; восьмибитный из документации на датчик даст обращение по другому адресу.

Права. По умолчанию /dev/gpiochip* и /dev/spidev* часто принадлежат root, а групп gpio и spi может не быть вовсе. Группу создают, отдают ей устройство правилом udev (раздел 193.5), а пользователя службы добавляют в неё — usermod -aG или строкой SupplementaryGroups=. Запускать прибор от root ради светодиода не нужно.


192.7 Итоги

Дальше: раздел 193 «Служба прибора: systemd, журнал, права и сторожевой таймер».

Открыть в интерактивном справочнике