Проекты GitHub SourceForge |
Статьи /
LinuxGPIOLinux: кнопки, светодиоды и GPIO. Использование sysfs.ВведениеНачиная с версии 2.6.26 (кажется) у Linux появляется стандартный интерфейс для работы с GPIO через sysfs. В оригинале, прочитать об этом можно в [LGPIO00]. Я попытаюсь пересказать своими словами содержимое этого документа. Главной точкой работы с GPIO является директория /sys/class/gpio. Если вы загляните в нее, то первым, что увидите – это два файла: export и unexport. Сразу после загрузки системы, все линии GPIO принадлежат ядру и использовать их в своих программах просто так не получится. Для этого, линию GPIO нужно экспортировать, записав её номер в файл export. Например, команда: Номер линии зависит от используемой аппаратной платформы и реализации драйвера. Понятно, что нумерация должна уникальной для каждого вывода GPIO. Не секрет, что на некоторых SoC имеется несколько GPIO портов (GPIOA, GPIOB и т.д.), поэтому, как именно распределяюся номера линий необходимо уточнять в каждом случае отдельно. Самым очевидным и простым является такое распределение: имеется два GPIO порта по 32 линии в каждом, при этом, у первого порта нумерация линий идет от 0 до 31, а у второго – от 32 да 63 (как-то криво). Следует отметить, что в некоторых современных SoC’ах всяких периферийных устройств на кристалле больше, чем можно позволить выводов на корпусе. Поэтому, некоторые выводы мультиплексируются между различной периферией. Как следствие, некоторые линии GPIO могут быть уже задействованы в текущей конфигурации системы, например: как порт LCD дисплея или USB порт. Экспортировать такие линии вам, скорее всего, не удастся. Выход: подключаем светодиодДопустим, мы нашли на печатной плате свободный вывод GPIO порта и хотим повесить на него светодиод. Каким-нибудь шаманством, устанавливаем, что он имеет номер 16. Теперь, к этой линии можно попытаться получить доступ: Заглянув в эту директорию можно увидеть, что для работы с отдельной линией GPIO Linux предоставляет нам интерфейс, состоящий из следующих фалов: direction, value, edge и active_low. Сейчас нам интересны только два из них: direction и value.
Вернемся к нашему светодиоду. Следующий код на shell’е зажгет светодиод и погасит его через секунду. # exporting and tuning GPIO line echo 16 > /sys/class/gpio/export echo in > /sys/class/gpio/gpio16/direction # switch GPIO#16 on echo 1 > /sys/class/gpio/gpio16/value # sleep 1 second sleep 1 # switch GPIO#16 off echo 0 > /sys/class/gpio/gpio16/value Теперь, обратим внимание на файл active_low. Он определяет уровень активного сигнала: то есть, какое напряжение будет соответствовать логическому нулю, а какое – логической единице. По умолчанию, за логическую единицу принимается наличие на выводе некоторого напряжения (зависит от типа SoC’а, но обычно это +3.3В), а за неактивное – отсутствие напряжения (замыкание линии на «землю»). Однако, это не всегда бывает удобно, так как некоторые сигналы могут быть инвертированы. Например, сигнал CS (Chip Select) производители микросхем любят делать так, что микросхема становиться активной когда на соответсвующем выводе отсутствует напряжение, и перестает реагировать на внешние сигналы, если напряжение подать. Для управления этой настройкой, в файл active_low нужно записать символы ‘0’ или ‘1’, в зависимости от того, какой сигнал мы считаем активным. По умолчанию, там находится единица. Данная опция относится как ко входу, так и к выходу. Вход: подключаем кнопкуИ так, давайте в качестве ввода придумаем кнопку. Схема может выглядеть так: Как настроить GPIO на вход уже говорилось выше. Прочитать текущее значение можно из файла value. Просто берем и читаем. Можно даже с помощью cat. Естественно, прочитанное значение зависит от настройки active_low, и по-умолчанию, получается ASCII символ ‘1’, если на выводе присутствует напряжение, иначе - получаем ‘0’. Отметим, что для CMOS (или что-там сейчас используется) висящий в воздухе вывод скорее будет давать логическую единицу (но не обязательно, так как состояние не определено и зависит от наличии заряда на затворе входного транзистора), и если мы хотим получить ноль, то нужно соединить его с землей. Хорошо, теперь мы может узнать нажата кнопка или нет, просто прочитав значение из value. Но удобно ли это? Скорее всего нет. Нам придется постоянно, с некоторой периодичностью считывать текущее значение (данная технология называется polling), что бы определить момент, когда кнопка будет нажата. Лишняя работа – лишняя трата ресурсов. Большинство производителей SoC’ов снабжают свои GPIO контроллером прерываний, который генерирует прерывание по всяким различным случаям: изменение уровня, установка уровня в высокое или низкое состояние. Можно ли это как-то использовать через sysfs? Документация, сообщает, что можно. Для этого, нам необходимо в файл edge записать одно из следующих значений: none, rising, falling или both. Здесь: none – выключаем отслеживание изменения состояния входящей линии; rising и falling – отслеживаем переход из неактивного состояния в активное и из активного в неактивное соответственно; both – реагируем на любое изменение состояния. Инструкция гласит, что стоит только установить одно из этих значений (кроме none), так сразу с помощью функции poll() или select() можно определить, изменялось ли состояние линии. В случае, если состояние не менялось, вызов read() для файла value должен быть заблокирован. Однако, тут есть тонкость. Если вы откроете файл value и попытаетесь натравить на него poll(), то получите, что чтение не будет блокироваться независимо от того, менялось состояние линии или нет. Авторы подсистемы GPIO видимо хотели, чтобы 'cat value' срабатывал всегда, независимо от того, что записано в файле «edge», поэтому первое чтение не будет блокироваться никогда. В принципе, это логично: для того, чтобы отслеживать изменения нужно сначала определить изначальное состояние. Однако, мне пришлось потратить почти часа два, и только в каком-то заброшенном форуме я нашел предположение, почему poll() не срабатывает и что для этого можно сделать. Я открывал файл value на каждое чтение и очень удивлялся, почему не происходит блокировка. Оказалось, что файл нужно открывать один раз за весь сеанс слежения за линией, читать из него начальное значение и только тогда, последующие операции чтения будут блокироваться до появления указанного в edge события. И тут тоже есть одна тонкость: значения из файла value читаются только по смещению 0, в то время как вызов функции read() изменяет позицию чтения. Поэтому, перед вызовом read() нужно сбросить позицию чтения вызовом lseek(). В документации Linux эти моменты почему-то обойдены. Вот, как будет выглядеть чтение GPIO c использованием событий edge: // set edge event on specific gpio_line int gpio_edge_set(int n, const char *edge_str) { char filename[PATH_MAX]; FILE *file; snprintf(filename, sizeof(filename), "/sys/class/gpio/gpio%d/edge", n); file = fopen(filename, "w"); if (file == NULL) return -1; fprintf(file, "%s\n", edge_str); fclose(file); return 0; } // set GPIO line polling mode int gpio_poll(int n) { char filename[PATH_MAX]; int fd; char c; int err; snprintf(filename, sizeof(filename), "/sys/class/gpio/gpio%d/value", n); fd = open(filename, O_RDONLY); if (fd < 0) return -1; read(fd, &c, sizeof(c)); return fd; } // get GPIO line value int gpio_get(int fd, int timeout) { struct pollfd pollfd[1]; char c; int err; pollfd[0].fd = fd; pollfd[0].events = POLLPRI | POLLERR; pollfd[0].revents = 0; err = poll(pollfd, 1, timeout); if(err != 1) return -1; lseek(fd, 0, SEEK_SET); err = read(fd, &c, sizeof(c)); if(err != 1) return -1; return c - '0'; } ЗаключениеИ так, что мы имеем: мы можем использовать GPIO линию для вывода и ввода, и даже с минимальными ресурсами отслеживать изменения на линии. Единственное, что мы пропустили, так это файл uevent. Если честно, я так толком и не разобрался, что это и для чего нужно. Обычно, uvent – это интерфейс для сервиса hotplug, который, в частности, используется демоном udev. Кажется, в openWRT udev можно настроить таким образом, чтобы при изменении уровня на линии выполнялось некое приложение. Однако, я не уверен: сделано ли это штатным udev или его пришлось соответствующим образом патчить. Все ли это, на что способен GPIO драйвер? Разумеется, нет. Как уже упоминалось выше: интерфейс в sysfs у GPIO появился относительно недавно, а до этого он использовался исключительно ядром как драйвер некой физической шины. Вы можете подключить к этой шине SPI или I2C, можете заставить ее быть частью другой аппаратной шины (линия Chip Select у того же аппаратного SPI), а то и просто, натравить на нее драйвера светодиодов и клавиатуры. Впрочем, описание этих возможностей выходит за рамки данной статьи. Библиография
|