Начиная с версии ядра 2.6.26, в Linux появился стандартный интерфейс для работы с контактами GPIO(general-purpose input/output — интерфейс ввода/вывода общего назначения) через виртуальную файловую систему sysfs. Работа с GPIO проходит через каталог /sys/class/gpio путём обращения к файлам-устройствам. В публикации приводится пример включение и выключение светодиода, и получение значения температуры от датчика DS18B20 по 1-Wire интерфейсу.
Что такое GPIO
GPIO (general-purpose input/output) — Интерфейс ввода/вывода общего назначения. GPIO подключены напрямую к «процессору» SoC (System-on-a-Chip — Система на кристалле), и неправильное использование может вывести его из строя.
Большинство одноплатных компьютеров, кроме обычных двунаправленных Input/Output портов, имеют один или более интерфейсов: UART, SPI, I²C/TWI, PWM (ШИМ), но не имеют ADC (АЦП).
GPIO — порты обычно могут быть сконфигурированны на ввод или вывод (Input/Output), состояние по умолчанию обычно INPUT.
Некоторые «GPIO»-порты — являются просто питающими портами 3.3V, 5V и GND, они не связаны с SoC и не могут использоваться как либо еще.
Порты с альтернативной функцией — могут быть мультиплексированны с одним из соответствующих ему интерфейсов.
Некоторые выводы могут иметь подтягивающие pullup и pulldown резисторы, на некоторых портах они могут быть включены по умолчанию. Во время загрузки ОС и инициализации драйверов — состояния портов могут меняться.
Порты в режиме INPUT могут генерировать прерывания — по спаду, по фронту, по логическому уровню, по изменению сигнала и в асинхронном режиме по фронту и спаду. Порты в режиме INPUT имеют входную фильтрацию на триггере Шмитта (преобразовывают аналоговый сигнал в цифровой с резкими переходами между состояниями).
Электрические характеристики GPIO (Electrical Specification)
У любого порта — есть «ограничения» по току и напряжению. А т.к. GPIO — это просто выводы чипа SoC — очень важно знать и соблюдать эти характеристики. Вы всегда можете узнать их в документации на SoC (ссылка на документацию Allwinner A64).
GPIO на Allwinner A64 имеет следующие характеристики:
- 20 mA — рекомендуемый максимальный ток GPIO на один цифровой контакт (digital pin);
- 40 mA — максимально допустимый ток GPIO на один цифровой контакт (digital pin);
- 3.3V — максимальное рекомендуемое входное/выходное напряжение GPIO;
- 3.6V — максимальное предельное входное/выходное напряжение GPIO;
- 1.08V — максимальный уровень логического 0 для линии 3.3V;
- 2.1V — минимальный уровень логической 1 для линии 3.3V;
- 3.6V — максимальный уровень логической 1 для линии 3.3V;
Выводы 3.3V — могут давать ток до 100mA, т.к. они фактически не являются GPIO портами, а практически напрямую соединены с питанием всей платы (а не чипа SoC). Максимальный ток вывода на 5V в спецификации к плате не был найден.
Все электрические характеристики можно найти в Allwinner_A64_Datasheet_V1.1 разделы 5. Electrical Characteristics. Основные таблицы с 5.1. Absolute Maximum Ratings по 5.5. Electrical Characteristics for Power Supply.
При проектировании устройств в которых используется большое количество датчиков нужно обязательно делать развязку через дополнительные буферные схемы, преобразователи уровня напряжений (логических уровней), электронные ключи и т.д.
Плата Banana Pi BPI-M64
Для дальнейшей работы с GPIO необходимо получить datasheet на SoC и плату. Рассмотрим datasheet Banana Pi BPI-M64 и SoC AllWinner A64, затем поработает с GPIO через sysfs.
Banana Pi BPI-M64 — это 64-битный четырехъядерный мини-одноплатный компьютер, поставляемый как решение с открытым исходном кодом. Ядром системы является процессор Allwinner A64 с 4-мя ядрами Cortex-A53 с частотой 1.2 ГГц. На плате размещено 2 ГБ DDR3 SDRAM 733МГц оперативной памяти и 8 ГБ eMMC. Также есть встроенный WiFi (AP6212) и BT4.0 (AP6212), Ethernet 10/100/1000 Мбит (Realtek RTL8211E/D). Видеоускоритель GPU Mali 400 MP2 поддерживает видео 1080P 4K. Более подробно можно почитать по ссылке.
Из спецификации представляет интерес, то что на плате размещен 40-контактный совместимый с Raspberry Pi разъем который содержит: GPIO (x28), Power (+5V, +3.3V and GND), UART, I2C, SPI. И 40-контактный интерфейс MIPI DSI.
SoC AllWinner A64
AllWinner A64 — встречается очень часто в бюджетных смартфонах, построен на ядре Cortex-A53. Более подробно можно почитать по ссылке. Из документации представляет интерес информация об интерфейсах: UART, I2C, SPI, PWM.
Процессор содержит:
PWM (ШИМ)
- Поддержка вывода двух видов сигнала: непрерывного сигнала и импульсного сигнала.
- Регулируемый рабочий цикл от 0% до 100%
- Выходная частота до 24 МГц
UART
- Шесть контроллеров UART
- Два из шести контроллеров UART поддерживают 2-проводную схему, а другие — 4-проводную
- 64-байтовые FIFO для передачи и приема данных для всех UART
- Соответствует отраслевым стандартам 16550 UART
- Поддержка Infrared Data Association (IrDA) 1.0 SIR
- Поддержка скорости до 3 МГц
SPI
- Два контроллера SPI
- Полнодуплексный синхронный последовательный интерфейс
- Настраиваемый ведущий/ведомый
- Mode0 ~ 3 поддерживаются как для передачи, так и для приема.
- Два 64-байтовых FIFO для работы SPI-TX и SPI-RX
- Работа на основе DMA или прерываний
- Полярность и фаза выбора кристалла (SPI_SS) и SPI_Clock (SPI_SCLK) настраиваются
- Поддержка одиночного и двойного режима ввода-вывода
Two Wire Interface(TWI)/I2C
- Четыре контроллеров TWI/I2C
- Поддержка стандартного режима (до 100 Кбит/с) и быстрого режима (до 400 Кбит/с)
- Настраиваемый Master/Slave
- Позволяет 10-битные транзакции адресации
- Выполнять арбитраж и синхронизацию часов
- Может работать в широком диапазоне входных тактовых частот
Распиновка контактов GPIO
Большой плюс плат Banana Pi является наличие совместимого 40-контактного разъема Raspberry Pi 3.
Позиция [1] 3V3 power соответствует позиции на плате со стрелочкой.
На 40-контактом разъеме Raspberry Pi определим какие из перечисленных интерфейсов нам доступны. На странице wiki опубликована спецификация 40-контактного разъема совместимого с Raspberry Pi 3. Совместим с данными WiringPi [BPI-WiringPi2], и получим сводную диаграмму:
Формула для вычисления номера GPIOXX
В схеме обозначены порядковые номера физических ножек процессора SoC, эти данные в спецификациях отсутствую, т.к. порядковый номер получаем путем простого расчета. Например, из схемы возьмем 32-контакт на разъеме типа Raspberry Pi. Название контакта PB7, для получения номера контакта на процессоре произведем расчет по формуле:
(позиция буквы в алфавите — 1) * 32 + позиция вывода. Первая буква не учитывается т.к. P — PORT, позиция буквы B в алфавите = 2, получаем (2-1) * 32 + 7 = 39. Физический номер контакта PB7 является номер 39, его будем в дальнейшем использовать для управления GPIO через sysfs.
Программирование GPIO
Существует несколько способов обращения к GPIO:
- Посредством файл-устройством GPIO;
- Используя языки программирования:
- Через прямое обращение к регистрам чипа;
- Используя уже готовые библиотеки.
Каждый из методов имеет свои преимущества и недостатки.
Работа с GPIO как файл-устройство через интерфейс sysfs
Существует три типа записей в /sys/class/gpio:
- Интерфейсы управления пользовательскими GPIO;
- Сами GPIO;
- Порты ввода-вывода (gpio_chip).
Для доступа к контакту GPIO необходимо его зарегистрировать в системе, для этого используются интерфейсы управления по пути /sys/class/gpio, доступны только на запись:
export — запрашивает экспорт управления GPIO пользователю с записью его номера в файл.
Например, команда echo 364 > /sys/class/gpio/export создаст файл-устройство gpio364.
unexport — отменяет экспорт.
Например, команда echo 364 > /sys/class/gpio/unexport удалит файл-устройство gpio364.
Контакты GPIO для управления доступны по пути /sys/class/gpio/gpioN, где N — номер контакта GPIO на процессоре и содержат атрибуты чтения/записи:
- direction — направление, может принимать значения in (вход) или out (выход), как правило, пишется. Например, команда echo out > /sys/class/gpio/gpio364/direction установит направление на выход файл-устройство gpio364.
- value — состояние, может принимать значение 0 (низкий) или 1 (высокий). Если GPIO настраивается на выход, то значение пишется. Ненулевое значение считается высоким. Например, команда echo 1 > /sys/class/gpio/gpio364/value установит состояние 1 Higth на выход файл-устройство gpio364.
- edge — реакция на изменение состояния, может принимать значения none (нет), rising (фронт) falling (спад) или both (перепад), необходима поддержка прерывания (interrupt). Не все контакты GPIO поддерживают прерывания.
- active_low Инвертирование значения value, может принимать значение 0 (нет) или 1 (да). Ненулевое значение считается единицей.
Порты GPIO располагаются по пути /sys/class/gpio/gpiochipN (для порта GPIO начиная с вывода #N) и имеют атрибуты только для чтения:
- base — номер первого GPIO управляемого портом.
- label — диагностическая метка, но здесь нет стандарта.
- ngpio — сколько выводов GPIO управляется портом.
Практика
Для работы будем использовать дистрибутив Armbian. Все дальнейшие действия выполнялись в версии Armbian_20.08.2_Bananapim64_bionic_current_5.8.6_minimal.img.xz, основанной на Ubuntu 18.04.5 LTS Linux bananapim64 5.8.6-sunxi64 #20.08.2 SMP Fri Sep 4 08:52:31 CEST 2020 aarch64 aarch64 aarch64 GNU/Linux.
Возьмем:
- Обычный светодиод, в данном случае используется Gravity: Digital Blue LED Light Module, лучше брать с отдельным питанием для снижение нагрузки на GPIO. Потребление светодиода по линии управления составляет 4.5 mA . Если подобного светодиода нет в наличие, то можно взять любой другой с небольшой яркостью, и подключить его обязательно вместе с резистором в цепь, как на примере в Использование библиотеки WiringPi [BPI-WiringPi2] на Banana Pi BPI-M64.
- Цифровой температурный датчик DS18B20 от DFRobot.com
на 1-Wire интерфейсе, диапазон температур от -55 до +125 градусов Цельсия.
Схема подключения датчиков:
Подключим оба датчика к линии питания 3.3 V и земли GND. Контакт управления LED будет подключен к 32-контакту разъема типа Raspberry Pi. Контакт 1-Wire датчика DS18B20 подключим к 18-контакту разъема типа Raspberry Pi. Изобразим на схеме:
Управление светодиодом LED
Для получение ссылки управление необходимо экспортировать GPIO для создания файл-устройства т.к. для оперирования контактом через sysfs нужен не порядковый номер 32-pin на разъеме типа Raspberry Pi, а номер ножки на процессоре. Для этого воспользуемся THE UNOFFICIAL BANANA PI BPI-M64 (r.23.01.2021) PINOUT DIAGRAM. Исходя из данной схемы 32-контакт соответствует 39-ножке процессора:
Поэтому необходимо экспортировать 39-контакт на процессоре, выполним команду:
echo 39 > /sys/class/gpio/export
В результате в каталоге /sys/class/gpio должно появиться файл-устройство gpio39:
Это означает, что виртуальный GPIO39 теперь доступен для работы. Поскольку нужно управлять светодиодом, нужно порт настроить его на выход:
echo out > /sys/class/gpio/gpio39/direction
Теперь виртуальный GPIO39 настроен на выход и можно включить светодиод, отправляя в порт единицу:
echo 1 > /sys/class/gpio/gpio39/value
Работает! Светодиод загорелся:
Также можно погасить светодиод, просто отправив логический 0, командой:
echo 0 > /sys/class/gpio/gpio39/value
echo 1 > /sys/class/gpio/gpio39/active_low
Теперь управление светодиодом логически соответствует задаваемым уровням.
Теперь можно вернуть контроль GPIO39 операционной системе, и освободить все ресурсы связанные с GPIO39:
echo 39 > /sys/class/gpio/unexport
К выводам GPIO можно подключить через транзисторные ключи обычные электромагнитные реле, и управлять нагрузкой через сеть Интернет. Работать напрямую с GPIO через терминал не всегда бывает удобно, поэтому для демонстрации автоматизации создадим простейший bash-скрипт который выполнит регистрацию контакта, помигает 5-раз с интервалом раз в 0.5 секунды, и обратно вернет управление используемым контактом ОС.
Скрипт bash
1) Создадим скрипт в домашней папке пользователя root, путь /root, для редактирования файлом используется редактор mcedit (apt-get install mc):
mcedit /root/blink.sh
2) Поместим в редактор код:
#!/bin/bash echo 39 > /sys/class/gpio/export echo out > /sys/class/gpio/gpio39/direction echo 1 > /sys/class/gpio/gpio39/active_low echo 0 > /sys/class/gpio/gpio39/value echo "Start blink" for ((a=1; a<=5 ; a++)) do echo 1 > /sys/class/gpio/gpio39/value sleep 0.5 echo 0 > /sys/class/gpio/gpio39/value sleep 0.2 done echo 0 > /sys/class/gpio/gpio39/value echo 39 > /sys/class/gpio/unexport echo "End blink"
3) Сохраним файл(F2) и выйдем из редактора(F10).
4) Установим права выполнения на файл:
chmod +x /root/blink.sh
5) Запустим скрипт:
/root/blink.sh
Светодиод 5 раз будет мигать.
Реализуем тоже самое, только на языке С:
1) Создадим файл:
mcedit /root/blink.c
2) Поместим в редактор код:
#include #include #define GPIO_PATH "/sys/class/gpio" #define GPIO_PIN_PATH "/sys/class/gpio/gpio39" unsigned long c=0; unsigned long i=0; int main() { FILE *fp; fp=fopen(GPIO_PATH"/export","w"); fprintf(fp,"39"); fclose(fp); fp=fopen(GPIO_PIN_PATH"/direction","w"); fprintf(fp,"out"); fclose(fp); //***for LED DFRobot.com fp=fopen(GPIO_PIN_PATH"/active_low","w"); fprintf(fp,"1"); fclose(fp); //*** fp=fopen(GPIO_PIN_PATH"/value","w"); fprintf(fp,"0"); fclose(fp); printf("Набери количество циклов\n"); scanf("%d",&c); for (i=c;i>0;i--) { fp=fopen(GPIO_PIN_PATH"/value","w"); fprintf(fp,"1"); fclose(fp); usleep(500000); fp=fopen(GPIO_PIN_PATH"/value","w"); fprintf(fp,"0"); fclose(fp); usleep(500000); } fp=fopen(GPIO_PIN_PATH"/direction","w"); fprintf(fp,"in"); fclose(fp); fp=fopen(GPIO_PATH"/unexport","w"); fprintf(fp,"39"); fclose(fp); printf("PROFIT!\n"); return EXIT_SUCCESS; }
3) Сохраним файл(F2) и выйдем из редактора(F10).
4) Теперь скомпилируем программу:
gcc -Wall blink.c -o blink
5) И так же как и в предыдущем примере запустим скомпилированную программу:
./blink
Если укажем значение «5», то светодиод моргнет 5 раз.
Получение значения температуры от датчика DS18B20
Датчик DS18B20 подключен к 18-контакту разъема типа Raspberry Pi. Получение значения температуры осуществляется путем конфигурирования контактов для протокола 1-Wire с указанием драйвера обработчика. Для этого необходимо внести изменения в Device Tree overlays. Device Tree — это описание оборудования в системе. Дерево должно включать имя базового процессора, конфигурацию его памяти и любые периферийные устройства (внутренние и внешние). DT не используется для описания программного обеспечения, хотя перечисление аппаратных модулей вызывает загрузку модулей драйверов. В DT описываются контакты и им назначается обработчик в виде драйвера для DS18B20. Драйвер создает файл-устройство в sysfs, при чтение которого получим значение температуры. В данной публикации не раскрывается принцип построения DT. Armbian уже содержит необходимый драйвер обработчик и шаблон конфигурации для DT. После подключения датчика DS18B20 необходимо применить шаблон DT 1-Wire протокола. Для этого необходимо запустить утилиту конфигурирования платы:
armbian-config
И выбрать пункт System из списка:
Затем выбрать пункт Hardware:
И отметить w1-gpio:
Затем сохранить <Save> и < Back >. Будет предложено перезагрузить палату, соглашаемся и ждем перезагрузки:
После перезагрузки выполняем команду:
dmesg | grep -E 'w1|wire'
В результате должны получить примерно такой результат:
Из которого следует что драйвер поддержки включен и был обнаружен один датчик 28.0000034aa0ba. Если установлена библиотека WiringPi [BPI-WiringPi2], то выполнив команду
gpio readall
, увидим что в строке с 18-контактом изменился режим работы на IN (input):
Это означает, что на 18-контакт добавился драйвер обработчик протокола 1-Wire. Драйвер поддерживает работу с несколькими датчиками DS18B20. Перейдем по пути: /sys/bus/w1/devices и посмотрим какие новый устройства появились:
tree /sys/bus/w1/devices
В списке как раз есть один датчик DS18B20 — 28-0000034aa0ba. В разных системах название может отличаться. Для получения значения температуры выполним чтение, командой:
cat /sys/bus/w1/devices/28-0000034aa0ba/w1_slave
Текущее значение температуры составляет: 24.5 градусов Цельсия. Задача выполнена!
Но если понаблюдать за изменением температуры, то можно заметить, что точность показаний составляет 0.5 градусов. Драйвер устройства позволяет получать температуру с разной точностью. Согласно таблицы из спецификации к датчику, следует:
- 9 бит: точность 0.5 °C, время получения данных 93.75 ms
- 10 бит: точность 0.25 °C, время получения данных 187,5 ms
- 11 бит: точность 0.125 °C, время получения данных 375 ms
- 12 бит: точность 0.0625 °C, время получения данных 750 ms
Чем выше точность, тем больше требуется времени на получение значения температуры. Для установки точности необходимо выполнить команду:
echo 9..12 > /sys/bus/w1/devices/28-xxxxxxx/w1_slave
где 9..12 — точность, 28-xxxxxxx — название датчика.
Установим максимальную точность, и получим значение температуры, команда:
echo 12 > /sys/bus/w1/devices/28-0000034aa0ba/w1_slave
В результате, температура составила: 24.437 °C
Вывод использование интерфейса sysfs
Недостатки:
- Не максимальная возможная скорость работы с GPIO;
- Невозможно включить выключить сразу несколько линий одним вызовом;
- В случае краха приложения GPIO линии остаются в «инициализированном» состоянии.
На деле область работы с GPIO через sysfs ограничивается управлением электромагнитными и твердотельными реле, светодиодной иллюминацией и тому подобными задачам, не требующими высокого быстродействия и точной привязки по времени. Невысокая скорость работы — плата за простоту управления через файловую систему.
Преимущества:
Будет работать на любом Linux-дистрибутиве и любом устройстве (GPIO имеют не только одноплатные компьютеры, но и некоторые роутеры и т.д. ) — если имеется драйвер GPIO в ядре Linux для данного SoC.
Существует некоторые готовые утилиты: https://github.com/torvalds/linux/tree/master/tools/gpio
Ссылки
- Работа с портами GPIO через стандартный драйвер SYSFS
- QSG_3: Работа с GPIO (на примере Orange Pi)
- How to use GPIO signals
- Считывание температуры с помощью датчика DS18B20 и Orange Pi PC (ARMBIAN 5.35)
- DS18B20 on OrangePi with Armbian
- Подключение датчика температуры DS18B20 к Orange Pi, Banana Pi, Raspberry Pi
- DS18B20. Programmable Resolution 1-Wire Digital Thermometer
- Gravity: DS18B20 Temperature Sensor (Arduino Compatible)