Русский
Русский
English
Статистика
Реклама

Gpio

Как перестать бояться и полюбить 1-wire

25.11.2020 22:06:19 | Автор: admin
Мне очень нравится протокол 1-wire своей простотой и удобством для применения в системах умный дом. Недавно я писал программную эмуляцию одной микросхемы и погрузился во внутренности этого протокола. Чтобы накопленные знания могли принести пользу не только мне, я решил написать данную статью. Но в статье я хочу рассказать не про абстрактные диаграммы сигналов и кодирование данных перепечаток такого материала есть достаточно, а хочу рассказать про более практические вещи. А именно: рассмотрю проверенные лично схемы адаптеров, собранные из простых и доступных деталей, и расскажу, как из Linux получить доступ к устройствам 1-wire. Попутно расскажу про сам протокол, будет и пример низкоуровневой работы, и пример доступа из JavaScript, а также рекомендации владельцам Raspberry Pi. Эта статья в первую очередь для тех, кто хочет разобраться с протоколом практически с нуля, чтобы начать его использовать в своих проектах. Возможно те, кто уже хорошо знаком с протоколом, также найдут что-то новое для себя.

Протокол 1-wire является проводным протоколом (в отличие от беспроводного), для кого-то это преимущество, а для кого-то недостаток. Сеть 1-wire состоит из шины, к которой подключаются устройства 1-wire. На шине может присутствовать множество подчиненных устройств и только один мастер. Подчиненные устройства могут общаться только с мастером и только по его запросу. Шина 1-wire состоит только из одной линии данных (отсюда и название однопроводный), но для работы также необходима линия земля, а для питания устройств еще и линия питания. При небольшом количестве подчиненных и их небольшом энергопотреблении возможно также так называемое паразитное питание от линии данных. Таким образом, для подключения устройства по 1-wire Вам понадобится 3 (или 2) провода и мастер сети. Мастером сети может быть, например, микроконтроллер или ПК + адаптер 1-wire.

Физический уровень


Как же по одной линии происходит взаимодействие с множеством устройств? На физическом уровне линия устроена по принципу монтажного И, это значит, что линия будет в состоянии логической 1 (уровень около 5V), если все устройства на линии перевели ее в состояние логической 1, а в состоянии логического 0 (около 0V), если хотя бы одно из устройств перевело ее в состояение логического 0. На практике это реализовывается подключением линии через резистор (называется резистором подтяжки) к напряжению питания, а каждое из устройств может только замыкать линию с землей при помощи встроенного транзистора. Такой тип выхода устройств называется открытый коллектор или открытый сток, англ. open-drain. Резистор подтяжки обычно расположен в мастере сети (адаптере).
image

Очевидно, что при паразитном питании ток поступает через резистор подтяжки, и чем больше ток потребления устройств, тем ниже будет проседать напряжение линии. Стандартом регламентировано напряжение лиии 4.5-5.5V, хотя многие устройства и могут работать при значительно более низких напряжениях, но не все. Для снижения проседания напряжения можно уменьшить сопротивление резистора подтяжки, но согласно стандарту, сопротивление не должно быть менее 500 Ом. На практике сопротивление резистора подтяжки можно взять 1кОм, если будет использоваться паразитное питание, и, например, 4.7кОм, если питание будет по отдельной линии.

GPIO адаптер


Простейший адаптер 1-wire можно построить с помощью GPIO:

По такой схеме устроен адаптер при использовании GPIO микроконтроллера, работающего от напряжения 5V, соответсвенно на его портах допустимо напряжение до 5V. Но на портах Raspberry Pi допустимо напряжение до 3.3V, поэтому резистор подтяжки можно подключать только к 3.3V. Такой адаптер будет работать с некоторыми устройствами, но, как уже говорилось, не все устройства могут работать с пониженным напряжением. Эту проблему легко решить с помощью двунаправленого преобразователя логических уровней:

Диод между истоком и стоком на схемах иногда не обозначают, но он всегда есть в реальных транзисторах.

Схема адаптера с использованием GPIO для Raspberry Pi будет выглядеть следующим образом:

В этой схеме убран резистор подтяжки, подключенный к истоку, с этим хаком схема продолжит работать. Т.к. напряжение на GPIO (и на истоке) станет меньше чем 3.3V (напряжение на затворе) на величину напряжения затвор-исток, при которой открывается транзистор. Напряжения на GPIO будет достаточно, чтобы GPIO его воспринимал как логическую 1.

Канальный уровень


Для понимания принципа работы следующего типа адаптера, разберемся, как работает мастер 1-wire на канальном уровне. В 1-wire есть всего четыре примитива канального уровня: отправка ресет, чтение присутствия, отправка бита 0, отправка бита 1 и она же по совместительству чтение бита. Для понимания принципа будет достаточно рассмотреть только два последних: отправка бита 0 реализуется путем установки на линии логического 0 в течение 60-120мкс, отправка бита 1 путем установки 0 в течение около 8мкс, чтение бита путем последующего чтения состояния линии, если линия вернулась в 1, значит подчиненное устройство передало бит 1, если задержалась в 0 не менее, чем на 15мкс значит подчиненное таким способом передало бит 0.

Пассивный адаптер


Следующий типа адаптера построен на использовании UART. Если по UART на скорости 115200 BAUD передать байт FFh, то на TX появится логический 0 только во время передачи стартового бита, это около 8мкс, подчиненные устройства это воспримут как отправку бита 1, а если передать байт 00h появится логический 0 примерно на 78мкс, подчиненные устройства это воспримут как отправку бита 0. А что если TX соединить с RX? Мы будем получать из UART ровно то, что передали: передали FFh получили FFh. А если эту линию принудительно задержать в логическом 0 чуть дольше, то получим уже не FFh, а, например, FEh, FCh, F8h, F0h и т.д. Таким простым способом реализуется чтение бита. Схема адаптера, построенного на использовании UART, он еще называется пассивным адаптером, будет такой:

По такой схеме также часто строится адаптер для микроконтроллера, имеющего аппаратный UART. В схеме необходимо использовать только диод Шоттки, т.к. важно малое падение напряжение на диоде. Диод в этой схеме это хак для преобразования выхода пуш-пулл (англ. push-pull) в подобие выхода открытый коллектор. В выходе пуш-пулл к выходу коммутируется транзисторами либо земля, либо напряжение питания, а в выходе открытый коллектор коммутируется только земля, а для обозначения логической 1, выход переводится в высокоимпедансное состояние, в этом состоянии выход можно подтянуть резистором к любому напряжению и он беспрепятственно приобретает это напряжение.

Здесь стоит упомянуть адаптер для RS232, хоть этот интерфейс уже практически и не встречается. На канальном уровне UART и RS232 это один и тот же протокол, но на физическом они отличаются: в UART используется напряжение 0V и 5V (или 3.3V, такой адаптер будет немного позже), а в RS232 +12V и -12V соответственно. Вот схема адаптера 1-wire для RS232, взятая из Dallas Application Note 74:


Как мы помним, в Raspberry Pi на всех портах допустимо напряжение до 3.3V, поэтому нужен вот такой пассивный адаптер для UART 3.3V:

В этой схеме также необходимо использовать только диод Шоттки.

Аппаратные адаптеры


Существует еще и третий тип адаптеров аппаратные, это микросхемы-конвертеры в 1-wire, выпускаются они для трех протоколов: USB, UART, i2c. У них есть преимущества по сравнению с предыдущими типами, например, в пассивном адаптере используется прием/передача целого байта для приема/передачи одного бита, а в аппаратном на этом получаем выигрыш в 8 раз (на самом деле аппаратно реализованы и другие функции: ресет, алгоритм поиска), кроме скорости это и упрощение, и сокращение кода, что очень актуально для микроконтроллеров.

Но главное преимущество аппаратных адаптеров это активная подтяжка. Разберемся с этим подробнее, для наглядности рассмотрим случай с паразитным питанием. При кратковременном нахождении линии в логическом 0, устройства питаются от своих встроенных конденсаторов, разряжая их, а при возвращении линии в логическую 1, конденсаторы начинают заряжаться, потребляя повышенный ток от линии. Как мы помним, ток идет через резистор подтяжки, имеем RC-цепь с переходным процессом с постоянной времени t=R*C, что проявляется в медленном нарастании напряжения на линии. Очевидным выходом в этой ситуации является кратковременное уменьшение сопротивления резистора подтяжки для сокращения постоянной времени переходного процесса. Эта техника и называется активной подтяжкой, реализовывается путем коммутации линии с напряжением питания на несколько микросекунд (вспоминаем тип выхода пуш-пулл). Это очень помогает на длинных или не очень качественных линиях, т.к. сами проводники также имеют сопротивление.

Конверторы из USB и i2c мне не доводилось использовать, а вот схему с применением микросхемы-конвертера из UART (DS2480B) я приведу, но позже и в чуть более продвинутом исполнении.

Гальваническая развязка


В предыдущих рассмотренных схемах линии земля, питание и данные прямо или косвенно соединяли внутренние схемы всех устройств. Такой вариант подойдет, если устройства будут под Вашим контролем и в безопасной среде, скажем в пределах квартиры или дома. Но если, например, датчик, работающий по 1-wire необходимо установить на крышу (метеостанция), то необходимо задуматься о защите. В данном случае речь идет о защите внутренних схем дорогостоящего оборудования от повышенного напряжения или статических разрядов. Защитить ПК или Raspberry Pi можно, отвязав его гальванически от всех линий 1-wire.

Развязывать необходимо две вещи: питание (и вместе с ним землю) и сигналы. Питание адаптера можно обеспечить от отдельного БП или использовав DC-DC преобразователь с гальванической развязкой (например B0505S). Сигналы можно развязать с помощью оптронов, это пара светодиод-фототранзистор в закрытом общем корпусе. Оптрон позволяет передавать сигнал только в одном направлении, а линия 1-wire является двунаправленной и, к сожалению, нет простых схем сделать двунаправленную гальваническую развязку. Это же касается GPIO, USB и i2c. А вот в UART обе линии RX и TX являются однонаправленными, поэтому получится сделать адаптер как пассивный, так и на аппаратном 1-wire-UART конвертере. Необходимо учесть, что в пассивном адаптере используется скорость UART 115200 BAUD, а аппаратный производит обмен данными на скорости 9600 BAUD. При выборе оптронов необходимо обращать внимание на параметры tON, tOFF, они должны быть существенно меньше 1/BAUD.

К сожалению у меня не нашлось быстрых оптронов, а PC817C, которые были в наличии, уверенно работали только до 19200 BAUD. Примерно таким образом я бы реализовал пассивный адаптер с гальванической развязкой, но, в отличие от остальных схем, эту схему я не проверял:

Полевой транзистор в этой схеме использован для создания большого входного сопротивления приемной части. Бонусом гальванической развязки является то, что схема одинаково будет работать и при напряжении UART 5V, и при 3.3V.

И, как было обещано, схема адаптера с применением 1-wire-UART конвертера (микросхема DS2480B) с гальванической развязкой:

Это самый совершенный из всех перечисленных адаптеров.

Приведу также свой вариант его реализации на макетной плате:

Две перемычки используются для подачи GND и +5V на DC-DC в случае UART 5V, а при подключении к UART 3.3V одна убирается и контакт подключается к +5V, второй перемычкой землю также можно сделать отдельной от земли UART.

Эмуляторы адаптеров


За неимением микросхемы DS2480B можно воспользоваться ее программной эмуляцией на любом микроконтроллере AVR, который имеет аппаратный UART. Такой МК установлен практически во всех Arduino. Здесь приведу схему, как из популярного программатора USBasp на микроконтроллере ATmega8A сделать 1-wire адаптер:

Для прошивки Вам, естественно, понадобится второй программатор. Код эмулятора и готовую прошивку можно скачать отсюда, код взят из проекта tinyowd, в эмуляторе не реализована активная подтяжка и некоторые функции.

Сетевой уровень


На сетевом уровне мастер при общении с подчиненными устройствами уже может оперировать байтами. Как мы помним, на канальном уровне при отправке бита 1 происходит и одновременное чтение, и в ответ можно получить бит 0. Таким образом при отправке байта, на его основе формируется байт ответа, в котором некоторые биты могут быть обнулены.

Все подчиненные устройства слышат мастера однвременно, но отвечать должно только одно. Для этого после начальной процедуры отправка ресет, чтение присутствия осуществляется процедура адресации подчиненного, после которой весь последующий обмен данными осуществляется только с выбранным подчиненным, а все остальные устройства эти данные игнорируют до следующего цикла отправки ресет.

Существует всего четыре команды сетевого уровня, которые понимают все без исключения подчиненные: CCh (SKIPROM), 33h (READROM), 55h (MATCHROM), F0h (SEARCHROM). Но есть и некоторые другие, например, ECh (ALARM), A5h (RESUME) и Overdrive-варианты предыдущих команд, которые понимают не все устройства. На самом деле две первые команды адресуют не одно а все устройства, первая (SKIPROM) используется для широковещательной рассылки команд, например запустить на всех устройствах измерение температуры, а сама температура будет прочитана позже по отдельности с каждого устройства. Вторая (READROM) используется только когда известно, что устройство на шине может быть только одно. Но для проверки на сетевом уровне есть еще контрольная сумма адреса, так что, если ответят несколько устройств, контрольная сумма это позволит выявить.

Адреса 1-wire, их еще называют идентификаторами, имеются только у подчиненных устройств. Адреса имеют длину 8 байт, из которых независымые только 7, а восьмой байт содержит контрольную сумму этих семи байтов адреса. Адреса всех устройств уникальны, они прошиваются при изготовлении и изменить их уже нельзя. Первый байт адреса всегда одинаков для одного типа микросхем, т.е. по нему можно однозначно определить название микросхемы. Этот байт носит название адрес семейства.

После процедуры адресации подчиненного последующий обмен данными уже относится к сеансовому и более высоким уровням модели ISO OSI и может отличаться для разных семейств устройств.

Низкоуровневая работа


Продемонстрирую, насколько просто происходит работа с DS2480B. Для этого в одном терминале из командной строки bash запускаем:
while IFS= read -rd "" -n1 c;do printf "%02x\n" "'$c";done </dev/ttyAMA0

Эта команда будет читать из UART и выводить на экран в шестнадцатеричном формате каждый прочитанный байт. В другом терминале будем отправлять байты в UART.

Первым делом отпрявляем RESET для сброса DS2480B, для этого снижаем скорость UART ниже 9600 BAUD и предаем 00h, DS2480B при чтении в стоп-бите обнаружит логический 0, воспримет это как команду сброса и приведет свое состояние в исходное (окажется в режиме команд):
stty -F /dev/ttyAMA0 raw pass8 4800echo -ne '\x00' > /dev/ttyAMA0

После чего возвращаем скорость на 9600 BAUD:
stty -F /dev/ttyAMA0 9600

Отправляем любую команду (в данном случае это команды DS2480B) для калибровки скорости DS2480B, на которую DS2480B никак не отреагирует, например, безобидную команду чтения параметра конфигурации 01h:
echo -ne '\x01' > /dev/ttyAMA0

Отправляем команду C1h, которая генерирует отправку ресет на шине 1-wire, в ответ получим CDh если на 1-wire последовал импульс присутствия, или EDh если присутствия не последовало (ответ наблюдаем в другом терминале):
echo -ne '\xc1' > /dev/ttyAMA0

Переключаем DS2480B в режим данных, в ответ ничего не получаем:
echo -ne '\xe1' > /dev/ttyAMA0

В режиме данных все байты, которые передаются в UART транслируются конвертером побитно в 1-wire и при передаче битов 1 автоматически происходит чтение бита, которое может обнулять биты в исходном байте. Модифицированный таким образом байт конвертер отправляет обратно в UART. Отправляем команду 33h, теперь это уже команда READROM сетевого уровня протокола 1-wire, в ответ получим 33h без изменений:
echo -ne '\x33' > /dev/ttyAMA0

В ответ на READROM устройство (для простоты подключаем на шину только одно устройство) отправит свой ID, для этого ему необходимо послать 64 единичных бита (идентификаторы состоят из 8 байт), которые в ответе будут модифицированы:
echo -ne '\xff\xff\xff\xff\xff\xff\xff\xff' > /dev/ttyAMA0


Вот что будет в другом терминале после выполнения всех вышеперечисленых команд:
cd33288df444020000a6

Идентификатор подчиненного устройства здесь будет 288df444020000a6, где 28 адрес семейства (датчики температуры DS18B20), a6 контрольная сумма адреса, проверять ее должны мы, а не DS2480B.

Естественно, в Linux так никто не работает с 1-wire. Но при программировании микроконтроллеров или драйверов, все происходит именно подобным образом.

Высокоуровневая работа


В ОС Linux есть два подхода к высокоуровневой работе с устройствами 1-wire:
  • с помощью приложений в пространстве пользователя,
  • с помощью драйверов ядра.

Первый способ исторически появился раньше и на сегодняшний день все еще является более предпочтительным. Самой полной реализацией является пакет owfs, в котором есть поддержка большого числа адаптеров и подчиненных устройств. Приложение, как правило, получает доступ к адаптеру через файл устройства в /dev/ и всю низкоуровневую работу с ним выполняет самостоятельно.

При работе с драйверами ядра, вся низкоуровневая работа с устройствами выполняется в ядре, которое предоставляет доступ приложениям к 1-wire через интерфейс netlink и дополнительно через специальную файловую систему /sys/.

Драйверы ядра


Драйверы ядра делятся на драйверы для адаптеров и драйвры для подчиненных устройств. Драйверы адаптеров создают уровень абстракции W1, объединяя шины 1-wire от всех доступных адаптеров. Далее есть два варианта использования уровня абстракции W1: использование его в приложениях, например, в owfs как еще один вид адаптера, или использование поверх него драйверов подчиненных устройств (тоже в ядре).

Для работы с 1-wire с помощью драйверов ядра достаточно загрузить драйвер адаптера. Например, для DS2490 (это аппаратный 1-wire-USB конвертер):
sudo modprobe ds2490

Для DS2482 (это аппаратный 1-wire-i2c конвертер) предварительно необходимо загрузить драйвер для конкретного i2c контроллера и затем сообщить ему адрес адаптера на шине i2c:
sudo modprobe i2c-<контроллер>sudo modprobe ds2482sudo echo 'ds2482 0x18' > /sys/bus/i2c/devices/i2c-0/new_device

Для GPIO:
sudo modprobe w1-gpio

В случае Raspberry Pi в файле /boot/config.txt добавить строку, или можно даже несколько:
dtoverlay=w1-gpio,gpiopin=4dtoverlay=w1-gpio,gpiopin=17dtoverlay=w1-gpio,gpiopin=27

Тогда при загрузке ядра модуль w1-gpio загрузится автоматически. К сожалению, в ядре (на момент написания статьи это linux-rpi-5.4.y) драйверы для DS2480B и пассивного адаптера не реализованы.

При загрузке любого драйвера адаптера автоматически загрузится модуль wire, который запустит периодический поиск подчиненных устройств на шине адаптера и автоматически загрузит модули для найденных подчиненных устройств. В каталоге /sys/bus/w1/devices/ появятся подкаталоги с именами, равными идентификаторам найденных устройств, в подкаталогах единственным полезным является файл w1_slave.

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

OWFS


Центральной частью пакета owfs является демон owserver, который работает с адаптерами через файлы устройств в /dev/ или через другие интерфейсы ядра и предоставляет доступ ко всем устройствам 1-wire для других приложений по собственному протоколу поверх TCP, мультиплексируя запросы от клиентов и кешируя ответы от устройств.

Для установки owfs необходимо выполнить аналоги следующих команд для своего дистрибутива:
sudo apt-get update -ysudo apt-get install -y owfs ow-shell

Или скомпилировать из исходных кодов отсюда: github.com/owfs/owfs/releases

Следующим шагом необходимо добавить адаптеры в файл конфигурации. Пример файла конфигурации /etc/owfs.conf:
## USB адаптер на микросхеме DS2490#server: usb## i2c адаптер на микросхеме DS2482#server: device /dev/i2c-0## UART адаптер на микросхеме DS2480Bserver: device /dev/ttyAMA0## Пассивный адаптерserver: passive /dev/ttyUSB0## доступ к адаптерам через драйверы ядра#server: w1## [адрес:]порт, на котором owserver принимает соединения от клиентовserver: port localhost:4304## [адрес:]порт, на котором owhttpd принимает соединения от клиентовhttp: port localhost:2121

В owfs нет поддержки адаптера, использующего GPIO, поэтому единственным способом получить доступ к GPIO из owfs является использование драйвера ядра w1-gpio и получение к нему доступа через уровень абстракции W1. Такой способ потребует запуска owserver от имени root. В любом случае, я не рекомендую адаптер на GPIO для серьезного использования, т.к. он реализован программно (техника bitbang) и грузит процессор.

После запуска owserver, к нему можно обращаться командами, например:
owdir /owwrite /1d.40420f484e59/pages/page.6 'Электроэнергия кв.1'

Как можно заметить, контрольная сумма адреса не указывается, owserver сам ее вычисляет. К owserver также можно обращаться и с другого хоста:
owget -s 10.0.0.1:4304 /1d.40420f484e59/counter.A

Естественно, для этого в /etc/owfs.conf должно быть:
server: port 10.0.0.1:4304

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

Для языков программирования C, python, perl, php есть библиотеки, из которых тем же способом через TCP можно получить доступ к owserver. Протокол у owserver очень простой, поэтому можно легко написать реализацию для любого другого языка.

Доступ из JavaScript


Еще один демон из пакета owfs, который может быть полезен owhttpd, он также обращается к owserver по TCP, а с другой стороны предоставляет доступ на порту 2121 по http. Но самое полезное находится в нем по адресу http://сервер:2121/json/, что открывает возможности для обращения к устройствам 1-wire из веб-страницы на JavaScript с помощью XMLHttpRequest.

var r = new XMLHttpRequest();r.open("GET", "http://127.0.0.1:2121/json/uncached/", true);r.responseType = "json";r.onload = function () {  var i, h = "";  if (this.status == 200 && typeof this.response === "object")    for (i in this.response)      h += i + "<br>";  document.getElementById("owlist").innerHTML = h;};r.send();

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

Полезные советы


Т.к. owserver обращается к адаптерам через файлы устройств в /dev/, то можно с помощью udev передать права на устройства другому пользователю, например, создать пользователя owfs и от его имени запускать owserver. Для этого в файле /etc/udev/rules.d/85-onewire.rules добавить строки примерно с таким содержимым:
KERNEL=="ttyAMA0", SUBSYSTEM=="tty", SYMLINK+="owbus0", OWNER="owfs"KERNEL=="ttyUSB[0-9]*", SUBSYSTEM=="tty", SUBSYSTEMS=="usb",  ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", ATTRS{serial}=="FTVNG262", SYMLINK+="owbus1", OWNER="owfs"

Если необходимо организовать несколько 1-wire шин, то потребуется несколько UART интерфейсов. Для этого можно восопользоваться несколькими USB-UART конвертерами. А в Raspberry Pi 4 можно активировать до пяти аппаратных UART, для этого в /boot/config.txt нужно добавить:
dtoverlay=disable-btdtoverlay=uart2dtoverlay=uart3dtoverlay=uart4dtoverlay=uart5

Файлы устройств после перезагрузки будут называться /dev/ttyAMA[0..4], эти UARTы будут работать на выводах:
AMA0 TX=GPIO14(пин  8) RX=GPIO15(пин 10)AMA1 TX=GPIO0 (пин 27) RX=GPIO1 (пин 28)AMA2 TX=GPIO4 (пин  7) RX=GPIO5 (пин 29)AMA3 TX=GPIO8 (пин 24) RX=GPIO9 (пин 21)AMA4 TX=GPIO12(пин 32) RX=GPIO13(пин 33)

Напомню, что так же, как и в случае с GPIO, если мы используем встроенный UART на Raspberry Pi, необходимо применение преобразователя логических уровней или гальванической развязки, т.к. все GPIO на Raspberry Pi работают с напряжением до 3.3V.

Сравнение с ModBus


Для понимания, какое положение занимает 1-wire в сравнении с другими подобными протоколами для автоматизации, я провел сравнение с популярным протоколом ModBus. ModBus также использует модель мастер-подчиненные и в связке с RS485 также может работать используя всего два провода.

+-----------------------+------------------+--------------------------+|                       | 1-wire           | ModBus                   |+-----------------------+------------------+--------------------------+| Проприетарный         | Да               | Нет                      || Документация          | Открыта          | Открыта                  || Физический уровень    | 1-wire           | RS485, RS232, TCP/IP,... || Канальный уровень     | 1-wire           | UART                     || Сетевой уровень       | 1-wire           | ModBus                   || Уровень приложения    | 1-wire           | ModBus                   || Только два провода    | да               | возможно в RS485         || Паразитное питание    | да               | нет                      || Напряжения            | 0..5 V           | до +/-12V в RS485        || Дифференциальный сигн.| нет              | да, в RS485              || Скорость обмена       | фикс. ~15 kbit/s | выбирается, до 10 Mbit/s || Фронты сигналов       | < 8 us           | < 1/BAUD                 || Точность тактирования | +/-50%           | +/-5%                    || Длина линии           | до 300м          | до 1200м (10^8/BAUD)     || Размер пакета         | не ограничен     | до 253 байт              || Размер адреса         | 56 бит           | 8 бит                    || Контрольная сумма     | 16 бит           | 16 бит                   |+-----------------------+------------------+--------------------------+

Как можно заметить, 1-wire более медленный, не такой гибкий и не такой помехозащищенный, что не дает ему права называться промышленным. Но его характеристик более чем достаточно для домашней автоматизации в системах умный дом. А используемые напряжения и простота адаптера для микроконтроллера делают его идеальным для разработки собственных устройств на МК.

Выводы


Протокол 1-wire на самом деле является очень легким в освоении. Я надеюсь, что статья поможет пролить свет на многие ранее не понятные моменты, которые могли быть препятствием на пути к использованию данного протокола.

Для знакомства с протоколом владельцам Raspberry Pi я рекомендую начать со сборки адаптера на GPIO. В файле /boot/config.txt добавить:
dtoverlay=w1-gpio,gpiopin=4

Установить owfs и записать в /etc/owfs.conf всего три строки:
server: w1server: port localhost:4304http: port localhost:2121

Владельцам ПК или ноутбука рекомендую приобрести USB-UART конвертер (он Вам еще не раз пригодится, они бывают на микросхемах FT232, PL2303, CP210X и др. подойдет любой) и собрать пассивный адаптер. Установить owfs и записать в /etc/owfs.conf тоже всего три строки:
server: passive /dev/ttyUSB0server: port localhost:4304http: port localhost:2121

И в том и в другом случае после перезагрузки выполнить:
owdir /

Следующим шагом рекомендую сборку адаптера на DS2480B с гальванической развязкой.

Приведу несколько полезных 1-wire подчиненных устройств, которые стоит попробовать:
  • DS18B20 самый популярный датчик температуры, диапазон измеряемых температур: -55C +125C, точность 0.5C, возвращает температуру уже в градусах цельсия: 8 бит на целую часть и 4 бит на дробную;
  • DS2450 четырехканальный АЦП, можно использовать для аналоговых датчиков, выводы также могут работать на выход, тип выхода открытый коллектор, можно использовать для управления реле;
  • DS2423 двухканальный счетчик импульсов (уже не производится, но есть программные эмуляторы), можно использовать, например, для дистанционного снятия показаний с бытовых приборов учета;
  • DS2438 контроллер аккумулятора, он идеально подходит для подключния датчиков влажности, имеет АЦП и датчик температуры, еще два вывода можно использовать для датчика освещенности, т.е. почти мини-метеостанция.


PS: Если некоторые очевидные моменты в статье расписаны излишне подробно, прошу отнестись к этому с пониманием, т.к. статья рассчитана на читателей с разным уровнем подготовки. И наоборот, если какие-то моменты освещены слишком скомкано или не понятно, прошу написать об этом в комментариях.
Подробнее..

Подключаем дисплей SPI LCD ILI9341 к одноплатному компьютеру Banana Pi BPI-M64 или любому другому на ОС Armbian

15.03.2021 20:13:27 | Автор: admin
LCD SPI ILI9341 Banana Pi BPI-M64

Пост содержит инструкцию как подключить TFT-LCD дисплей на популярном контроллере ILI9341 к одноплатному компьютеру на ОС Armbian с помощью дерева устройств (Device Tree overlays) без танцев с бубном. В сети Интернет много материала как подключать различные LCD экраны к Raspberry Pi. Но что если у вас нет Raspberry Pi, а хочется подключить недорогой LCD экран на SPI интерфейсе? Все что вам необходимо, это любая плата с поддержкой ОС Armbian. В каталог поддерживаемых плат ОС Armbian входят платы: Asus, Pine64, Hardkernel, Orange Pi, Banana Pi, и т.д. На данный момент в каталоге более 114 моделей плат, объявлена поддержка различного оборудования из коробки. Доступны для подключения: 4G/LTE модемы, USB Wi-Fi, USB Bluetooth, USB Ethernet, сканеры DVB-тюнеры и т.д. К всем этим платам можно легко подключить SPI LCD дисплей ILI9341, как это реализовать прошу под кат.

Преамбула


Многие одноплатные компьютеры снабжены HDMI выходом, но подключение полноценного дисплея с поддержкой HDMI входа достаточно дорогое удовольствие для небольшого проекта. В особенности, если необходимо реализовать минимальный функционал взаимодействия с пользователем, терминал распечатки документов, вывод текущего статуса работающего приложения. Для подобных задач можно использовать символьные дисплеи LCD HD44780 на интерфейсе I2C, они достаточно дешевы и удобны. Но в тоже время сильно ограничены в функциональности, на эти экраны невозможно вывести консоль Linux и нативный UI приложения, вдобавок площадь LCD экрана нельзя использовать как панель ввода информации. Для решения этих задач прекрасно подойдут LCD экраны на SPI интерфейсе, дисплей диагональю 3.5 дюйма с резистивным слоем можно приобрести за 9.57$ (включая доставку). На LCD экран можно выводить консоль Linux и подсистему X11. Таким образом, использование SPI LCD является лучшим вариантом по соотношению функциональности к стоимости.

Дисплей ILI9341 2.2 inch 2.2" SPI TFT


Контроллер ILI9341 предназначен для управления TFT панелью. Под контроллер ILI9341 поставляются панели диагональю от 2.2 до 3.2 дюймов, разрешение 240x320, к некоторым LCD добавляют резистивный слой.
К одноплатному компьютеру Banana Pi BPI-M64 будем подключать модуль SPI LCD ILI9341 диагональю 2.4 дюйма без резистивного слоя.

Рассмотрим характеристики и распиновку SPI LCD ILI9341 2.4 inch

  • 2,4-дюймовый цветной экран, поддержка 65K цветов
  • разрешение 320X240
  • интерфейс подключения SPI
  • доступен слот для SD-карты
  • питание модуля 3.3V~5V
  • Напряжение управления логикой 3.3V(TTL)

Контакты подключения LCD
Number Pin Label Description
1 VCC 5V/3.3V power input
2 GND Ground
3 CS LCD chip select signal, low level enable
4 RESET LCD reset signal, low level reset
5 DC/RS LCD register / data selection signal,high level: register, low level: data
6 SDI(MOSI) SPI bus write data signal
7 SCK SPI bus clock signal
8 LED Backlight control, high level lighting,if not controlled, connect 3.3V always bright
9 SDO(MISO) SPI bus read data signal, if you do not need to the read function, you can not connect it

Для управления подсветки используется контакт номер 8 LED. Максимальное напряжение 3.3V соответствует максимальной яркости от общего питания VCC. Если необходимо задать 50% яркости экрана, то на LED необходимо подать напряжение в 1.65V. Для программного управления яркости подсветки необходимо контакт LED подключать к аналоговому выходу GPIO на одноплатном компьютере. В случае наличия только цифровых выходов, доступна лишь возможности включить или полностью выключить подсветку экрана.

Исходя из характеристик LCD экрана предъявляются следующие требования к одноплатному компьютеру:
  • наличие SPI интерфейса
  • напряжение логики на контактах 3.3V (большинство плат)
  • потребуется еще два (RESET, DC/RS) свободных контактов GPIO

Что ты за зверь Armbian и какой одноплатный компьютер необходим


На странице armbian.com/download представлено большое количество разнообразных одноплатных компьютеров. С точки зрения удобства подключения, лучше выбирать плату с 40-контактным разъемом GPIO совместимым с Raspberry Pi 3. Например, если подключать SPI LCD ILI9341 2.4 inch к плате Banana Pi BPI-M64 и Orange Pi PC, то номера физически подключаемых контактов GPIO будут совпадать (не путать с названиями контактов процессора, они будут различны, далее потребуется для конфигурирования). В случае если одноплатный компьютер будет построена не на процессоре Allwinner, то возможно потребуется изменять больше параметров в файле: sun50i-a64-spi-ili9341-led-always-on.dts (будет далее по тексту).

Armbian это самый популярный дистрибутив Linux, предназначенный для одноплатных компьютеров построенных на ARM процессоре, список поддерживаемых плат огромен: Orange Pi, Banana Pi, Odroid, Olimex, Cubieboard, Roseapple Pi, Pine64, NanoPi и др. Дистрибутив Armbain основан на Debian и Ubuntu.

После явления миру Raspberry Pi, китайские производители решили тоже влиться в движение Open Hardware Source, и сделали много разнообразных плат. Но программная поддержка была крайне слабой, для решения данной проблемы зародился проект Armbian. На данный момент Armbian уже исполнилось 7 лет, поддерживается 114 моделей плат, объявлена поддержка различного оборудования из коробки. Доступны для подключения: 4G/LTE модемы, USB Wi-Fi, USB Bluetooth, USB Ethernet, сканеры DVB-тюнеры и т.д.

Для запуска Armbian на одноплатном компьютере необходимо загрузить образ с сайта, затем скопировать его на microSD карту, с которой в последствие нужно будет загрузиться. Если на плате размещена eMMC память достаточного объема, то через утилиту armbian-config, операционная система легко переносится с microSD карты на eMMC память вместе с загрузчиком.

Создание IoT-проекта с использованием Armbian в отличие от Raspberry Pi, позволяет выбирать платы различающие по производительности, и набора периферии. Например, на всех версиях Raspberry Pi размещен только один Ethernet порт. Но если требуется сделать маршрутизатор с несколькими Ethernet портами, то из списка поддерживаемых плат Armbian подойдут модели: Helios64, Espressobin, Bananapi R2, и т.д.

Поддерживаемые SoC
  • Allwinner A10, A20, A31, H2+, H3, H5, H6, A64
  • Amlogic S805 and S905 (Odroid boards), S802/S812, S805, S905, S905X and S912 (fork by @balbes150)
  • Actionsemi S500
  • Freescale / NXP iMx6
  • Marvell Armada A380
  • Rockchip RK3288/RK3328/RK3399
  • Samsung Exynos 5422

Схема подключения SPI LCD ILI9341 2.4 inch к Banana Pi BPI-M64 (порт GPIO Raspberry Pi 3)


SPI интерфейс LCD экрана подключаем к SPI1 на Banana Pi BPI-M64. Контакты CS, RESET, DC/RS можно подключать к любым цифровым выводам.

Таблица контактов подключения:
Номер LCD Метка LCD Номер контакта на Banana Pi BPI-M64 (порт GPIO Raspberry Pi 3)
1 VCC 1 или 2 (если необходима максимальная яркость, то контакт 2 на 5V)
2 GND 39, или любой другой Ground
3 CS 24
4 RESET 18
5 DC/RS 22
6 SDI(MOSI) 19
7 SCK 23
8 LED 1 или любой свободный GPIO на 3.3V
9 SDO(MISO) 21

Если контакт LED подключать к цифровому выводу GPIO, то для включения подсветки вручную потребуется подавать логическую 1 для включения или 0 для выключения экрана.

Схема подключения SPI LCD ILI9341:
LCD SPI ILI9341 Banana Pi BPI-M64

Одноплатный компьютер Banana Pi BPI-M64


Banana Pi BPI-M64 это 64-битный четырехъядерный мини-одноплатный компьютер, поставляемый как решение с открытым исходном кодом. Ядром системы является процессор Allwinner A64 с 4-мя ядрами Cortex-A53 с частотой 1.2 ГГц. На плате размещено 2 ГБ DDR3 SDRAM 733МГц оперативной памяти и 8 ГБ eMMC.

Самое главное для успешного подключения SPI LCD необходимо знать название контактов для SPI интерфейса, их номер и название зависит от модели процессора. Для решения этой задачи необходим Allwinner A64 Datasheet. На Wiki-странице Banana Pi BPI-M64 представлена распиновка 40-контактного разъема GPIO, из которого мы узнаем название контактов: PD2, PD3, и т.д.
40 PIN GPIO of Banana pi BPI-M64
GPIO Pin Name Default Function Function2GPIO Function3
CON2-P18 PD4 PD4
CON2-P19 SPI1-MOSI PD2 UART4-TX
CON2-P21 SPI1-MISO PD3 UART4-RX
CON2-P22 PC0 PC0
CON2-P23 SPI1-CLK PD1 UART3-RX
CON2-P24 SPI1-CS PD0 UART3-TX

Помимо название контакта, необходимо узнать порядковый номер этого контакта на ножке процессора, легко вычисляется по формуле: (позиция буквы в алфавите 1) * 32 + позиция вывода. Рассчитаем номер ножки для контакта PD2. Первая буква не учитывается т.к. P PORT, позиция буквы D в алфавите = 4, получаем (4-1) * 32 + 2 = 98. Контакту с меткой PD2 соответствует 98 ножка на процессоре, далее потребуется для конфигурирования дерева устройств.

Дерево устройств (Device Tree, DT) в Linux


Дерево устройств (Device Tree, DT) это структура данных в системе Linux, состоящая из именованных узлов и свойств, описывающих оборудование, которое невозможно обнаружить путем опроса оборудования. Дерево должно включать имя базового процессора, конфигурацию его памяти и любые периферийные устройства (внутренние и внешние). DT не используется для описания программного обеспечения, хотя перечисление аппаратных модулей вызывает загрузку модулей драйверов.

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

Allwinner V3s

Контакты могут быть объедены вместе для формирования интерфейса, например MIPI DSI(MIPI Display Serial Interface). Интерфейс MIPI DSI предназначен для подключения LCD панелей, активно используется в смартфонах и планшетах. Но если к устройству не планируется подключать дисплей по MIPI DSI, то эти линии можно использовать для других целей, путем изменения DT. В отличие от архитектуры x86 в системах построенных на SoC нет возможности произвести полностью опознание всех устройств в режиме Plug and Play. Поэтому необходимо явное декларирование какие контакты используются для интерфейсов и какие именно устройства подключены к этим интерфейсам.

До появления DT информация об устройствах в Linux являлась неотъемлемой частью ядра, и в случае изменения состава периферийных устройств требовалось пересобрать образ системы. Это было крайне неудобно, и поэтому описание периферийных устройств перенесли в конфигурационные файлы, которые собираются на логическом уровне в дерево. Где ветвь устройство с указанием драйвера необходимого для работы этого устройства.

После использования DT отпала необходимость в формирование индивидуального образа для каждого набора периферии устройств. Теперь достаточно сформировать один образ, включить в него набор драйверов для различных устройств, и для каждого устройства сформировать свой DT.

Наложения дерева устройств (Device Tree overlays)


Device Tree overlays (наложения дерева устройств) добавление к DT принципа наложения слоев устройств. Если конфигурация описывает интерфейс UART к которому был подключен Bluetooth, и необходимо Bluetooth заменить на GPS модуль, то можно не удалять существующие настройки Bluetooth а добавить дополнительный слой для GPS модуля который переопределит предыдущие настройки.

Для работы с DT используются следующие термины:
DT Дерево устройств
DTB (*.dtb) Бинарный файл дерева устройств
DTBO (*.dtbo) Бинарный файл дерева устройств для наложения
DTC Компилятор дерева устройств
DTO Наложения дерева устройств
DTS (*.dts) Исходный файл для дерева устройств
FDT Flattened Device Tree, двоичный формат, содержащийся в файле .dtb

Аппаратная конфигурация описывается в файлах исходниках DT ( .dts ) затем они компилируется в бинарные файлы DT ( .dtb ) уже для конечного использования в системе. Так же можно выполнить обратную процедуру декомпиляции файлов *. dtb в *. dts, компилятор/декомпилятор присутствует в системе.

Linux device tree bootloader

Реализация DTO включает разделение дерева устройств, построение, разбиение на разделы и исполнение.

Разделение DT


DT разделяются на две части:
  • Main DT (основное дерево устройств). Предоставляет разработчик SoC и является настройкой по умолчанию. В данном случае предоставляет компания Allwinner разработчик процессора Allwinner A64.
  • Overlay DT (Накладываемое дерево устройств). Специфическая конфигурация производителя платы, включает периферийные устройства которые размещены на плате. Для платыBanana Pi BPI-M64 предоставляет компания SinoVoip Co.,

Тема Device Tree overlays в Linux достаточно большая, чтобы не превращать пост в многотомное произведение Ленина, более детально можно почитать в публикации Работа с GPIO на примере Banana Pi BPI-M64. Часть 2. Device Tree overlays.

Формирование DTS для SPI LCD ILI9341 2.4 inch


Тестирование производилось на образе Armbian_20.08.2_Bananapim64_bionic_current_5.8.6_minimal.img.xz, на основе Ubuntu 18.04.5 LTS (Bionic Beaver), ядро Linux 5.8.6. uname: Linux bananapim64 5.8.6-sunxi64 #20.08.2 SMP Fri Sep 4 08:52:31 CEST 2020 aarch64 aarch64 aarch64 GNU/Linux.

В Armbian уже есть драйвер для ILI9341, поэтому все что требуется, это создать файл описания устройства в формате DTS, скомпилировать его в формат DTBO, и перезагрузить одноплатный компьютер. Как говорится Easy!

Для формирования файла DTS необходимо узнать ссылку на gpiochip в котором находится SPI интерфейс, для этого откроем терминал Armbian и выполним команду cat /sys/kernel/debug/gpio:

root@bananapim64:~# cat /sys/kernel/debug/gpiogpiochip1: GPIOs 0-255, parent: platform/1c20800.pinctrl, 1c20800.pinctrl: gpio-120 (                    |bananapi-m64:red:pwr) out hi gpio-142 (                    |bananapi-m64:green:u) out lo gpio-143 (                    |bananapi-m64:blue:us) out lo gpio-166 (                    |cd                  ) in  lo ACTIVE LOW gpio-233 (                    |usb0_id_det         ) in  hi IRQgpiochip0: GPIOs 352-383, parent: platform/1f02c00.pinctrl, 1f02c00.pinctrl: gpio-354 (                    |reset               ) out hi ACTIVE LOW gpio-356 (                    |shutdown            ) out hi gpio-357 (                    |host-wakeup         ) in  lo gpio-358 (                    |device-wakeup       ) out higpiochip2: GPIOs 510-511, parent: platform/axp20x-gpio, axp20x-gpio, can sleep:

Данная команда выведет все доступные устройства gpiochip и номера задействованных контактов в операционной системе. В предыдущем разделе для контакта SPI1-MOSI, название контакта PD2, определили номер ножки процессора 98. Исходя из полученного результата номер 98 приходится на диапазон GPIOs 0-255, который соответствует чипу gpiochip1: GPIOs 0-255, parent: platform/1c20800.pinctrl, 1c20800.pinctrl. Далее для формирования файла DTS потребуется узнать ссылку на 1c20800.pinctrl.

Создадим файл DTS с названием: sun50i-a64-spi-ili9341-led-always-on.dts (в основе dts файл для платы Orange Pi PC):
/dts-v1/;/plugin/;/ {compatible = "allwinner,sun8i-h3";  fragment@0 {    target = <&pio>;    __overlay__ {      ili9341_pins: ili9341_pins {        pins = "PD4", "PC0"; /*RESET, DC_RS*/        function = "gpio_out", "gpio_out" ;      };    };  };    fragment@1 {    target = <&spi1>;    __overlay__ {      status = "okay";            cs-gpios = <&pio 3 0 0>; /* PD0 */      ili9341: ili9341@0 {        compatible = "ilitek,ili9341";        reg = <0>;        pinctrl-names = "default";        pinctrl-0 = <&ili9341_pins>;        spi-max-frequency = <16000000>;        rotate = <90>;        bgr;        fps = <25>;        buswidth = <8>;        reset-gpios = <&pio 3 4 1>; /*RESET=PD4*/        dc-gpios = <&pio 2 0 0>; /*DC_RS=PC0*/        /*led-gpios = <&pio 2 4 0>; LED=PC4*/        debug = <0>;      };    };  };};

Рассмотрим содержимое:

  • fragment@0 является наложением на блок узла /soc/pinctrl@1c20800, ссылка этого узла &pio. Описывает название задействованных контактов GPIO PD4", PC0 и определяет функцию gpio_out для контактов.
  • &pio это ссылка в дереве устройств для контактов GPIO на узел /soc/pinctrl@1c20800, название определяли выше. Ссылка берется из файла в Armbian по пути: /boot/dtb-5.8.6-sunxi64/allwinner/sun50i-a64-bananapi-m64.dtb.Для компиляции в формат DTS, выполнить команду: $ dtc -I dtb -O dts sun50i-a64-bananapi-m64.dtb -o sun50i-a64-bananapi-m64.dts
  • fragment@1 является наложением на блок узла /soc/spi@1c69000, ссылка этого узла &spi1.
  • status = okay задействует SPI1 интерфейс на плате для подключения устройств
  • cs-gpios = <&pio 3 0 0>; /* PD0 */ номер контакта для CS интерфейса SPI1.
  • <&pio 3 0 0> параметры контакта, где &pio ссылка на gpioiochip1 в основном дереве устройств, буква P не учитывается, означает PORT, буква D кодируется в цифру 3 (формула: порядковый номер буквы в алфавите 1), 0 после цифры 3 это позиция вывода, 0 из PD0, и последний 0 означает полярность, по умолчанию всегда 0 (полярность 0 на логический ноль, выдается напряжение 0; полярность 1 на логический ноль, выдается напряжение 1).
  • compatible = ilitek,ili9341 идентификатор драйвера для данного устройства
  • pinctrl-0 = <&ili9341_pins> ссылка на используемые контакты из fragment@0
  • spi-max-frequency = <16000000> частота работы SPI интерфейса
  • rotate = <90> ориентация изображения, поворот на 90 градусов, в зависимости как необходимо расположить дисплей.
  • fps = <25> кадров в секунду
  • reset-gpios = <&pio 3 4 1> контакт RESET=PD4
  • dc-gpios = <&pio 2 0 0> контакт DC_RS=PC0

Разместим файл по пути /boot/dtb/allwinner/overlay. Затем компилирует файл .dts в .dtbo:

$ dtc -O dtb -o sun50i-a64-spi-ili9341-led-always-on.dtbo sun50i-a64-spi-ili9341-led-always-on.dts

Запустим утилиту конфигурирования платы: $ armbian-config. Перейдем по меню: System > Hardware, и включим слой (overlay): spi-ili9341-led-always-on. После перезагрузки платы, консоль Linux будет на SPI LCD экране:

LCD SPI ILI9341 Banana Pi BPI-M64


Midnight Commander и Htop на SPI LCD
Midnight Commander
LCD SPI ILI9341 Banana Pi BPI-M64

Htop
LCD SPI ILI9341 Banana Pi BPI-M64


Название контактов
Для всех процессоров Allwinner формат записи контакта, соответствует виду cs-gpios = <&pio 3 0 0>, для других процессоров формат записи контакта будет отличаться.

Решение проблем


Если изображение не появилось на LCD, выполните команду для проверки: $ dmesg | grep -E 'ili9341'.

В консоли должна быть следующая информация:
root@bananapim64:/boot/dtb-5.8.6-sunxi64/allwinner# dmesg | grep -E 'ili9341'[    5.733989] fb_ili9341: module is from the staging directory, the quality is unknown, you have been warned.[    5.734718] fb_ili9341 spi0.0: fbtft_property_value: buswidth = 8[    5.734731] fb_ili9341 spi0.0: fbtft_property_value: debug = 0[    5.734737] fb_ili9341 spi0.0: fbtft_property_value: rotate = 90[    5.734744] fb_ili9341 spi0.0: fbtft_property_value: fps = 25[    6.119287] graphics fb0: fb_ili9341 frame buffer, 320x240, 150 KiB video memory, 16 KiB buffer memory, fps=25, spi0.0 at 16 MHz


Настройка SPI LCD для графического интерфейса Xfce и подсистемы X11


Для вывода консоли Linux достаточно добавить файл DTS и все, но для вывода графики этого недостаточно.

1) Установим XORG и XFCE:

$ sudo apt-get update$ sudo apt-get install xorg$ sudo apt-get install xfce4

2) Для процессора Allwinner необходимо дополнительно устанавливать GPU драйвер fbdev:

$ sudo apt-get install xserver-xorg-video-fbdev

3) Удалить все конфигурационные файлы по пути /etc/X11/xorg.conf.d (если нет файлов, то отлично)

4) Создать конфигурационный файл по пути /usr/share/X11/xorg.conf.d/99-fbdev.conf и разместить в нем следующий фрагмент:

Section "Device"    Identifier "myfb"  Driver "fbdev"  Option "fbdev" "/dev/fb0"EndSection

Где /dev/fb0 устройство SPI LCD. Если к плате подключена HDMI панель, то может быть два устройства /dev/fb0 и /dev/fb1.

Запускаем графический интерфейс командой: startx или startxfсe4:

LCD SPI ILI9341 Banana Pi BPI-M64


Если необходимо сразу переходить в графический интерфейс, то необходимо дополнительно установить пакеты:
$ sudo apt-get remove tasksel$ sudo apt-get remove xubuntu-desktop

Для возвращения запуска только консоли необходимо отключить автозапуск службы display-manager.service

$ sudo systemctl disable display-manager.service

Решение проблем


Если графический интерфейс не запускается то для решение проблем необходимо посмотреть журнал событий X11, командой:

$ cat /var/log/Xorg.0.log


Итог


Установка и настройка SPI LCD не требует никаких компиляций модулей из исходного текста, что существенно упрощает установку. Главное внимательно выставить контакты и все заработает из коробки. Поставленные цели успешно решены.

RoadMap


  1. Подключение дисплея большего размера 3.5 дюйма на контроллере ILI9488.
  2. Настройка Touch интерфейса для Xfсe.
  3. Вывод на SPI LCD только одного графического приложения используя подсистему X11 из Docker контейнера (решение для публичных терминалов, киосков, POS-терминалов).

Файл sun50i-a64-spi-ili9341-led-always-on.dts и другие файлы наложения дерева доступны в каталоге GitHub Banana-Pi-BPI-M64/dt-overlays/
Подробнее..

Управляем контактами GPIO из C .NET 5 в Linux на одноплатном компьютере Banana Pi M64 (ARM64) и Cubietruck (ARM32)

10.05.2021 12:10:00 | Автор: admin
dotnet libgpiod

Когда заходит речь про программирование на C# .NET для одноплатных компьютеров, то разговоры крутятся только в основном вокруг Raspberry Pi на Windows IoT. А как же Banana/Orange/Rock/Nano Pi, Odroid, Pine64 и другие китайские одноплатные компьютеры работающие на Linux? Так давайте это исправим, установим .NET 5 на Banana Pi BPI-M64 (ARM64) и Cubietruck (ARM32), и будем управлять контактами GPIO из C# в Linux. В первой части серии постов, подключим светодиод и кнопку для отработки прерываний и рассмотрим библиотеку Libgpiod (спойлер, библиотеку так же можно использовать в C++, Python) для доступа к контактам GPIO.

Предисловие


Управление светодиодом и получение событий от кнопки будет реализовано через библиотеку Libgpiod, которая не является частью платформы .NET. Данная библиотека предоставляет доступ к GPIO из любого языка программирования, требуется лишь написание класса обертки.

Данный пост применим не только к платам Banana Pi BPI-M64 и Cubietruck, но и другим, основанных на процессоре ARM архитектуры armv71(32-bit) и aarch64 (64-bit). На Banana Pi BPI-M64 (ARM64) и Cubietruck (ARM32) установлена ОС Armbian версии 21.02.1, основанная на Ubuntu 18.04.5 LTS (Bionic Beaver), ядро Linux 5.10.12. uname: Linux bananapim64 5.10.12-sunxi64 #21.02.1 SMP Wed Feb 3 20:42:58 CET 2021 aarch64 aarch64 aarch64 GNU/Linux

Armbian это самый популярный дистрибутив Linux, предназначенный для одноплатных компьютеров построенных на ARM процессоре, список поддерживаемых плат огромен: Orange Pi, Banana Pi, Odroid, Olimex, Cubietruck, Roseapple Pi, Pine64, NanoPi и др. Дистрибутив Armbain основан на Debian и Ubuntu. Из большого перечня поддерживаемых одноплатных компьютеров можно выбрать то решение, которое лучше всего походит для вашего IoT проекта, от максимально энергоэффективных до высокопроизводительных плат с NPU. И на базе всех этих одноплатных компьютеров, вы сможете реализовать свое решения на платформе .NET и работать с периферийными устройствами из кода на C#.

Что такое GPIO


GPIO(general-purpose input/output) интерфейс ввода/вывода общего назначения. GPIOподключены напрямую к процессоруSoC (System-on-a-Chip Система на кристалле), и неправильное использование может вывести его из строя. Большинство одноплатных компьютеров, кроме обычных двунаправленных Input/Output портов, имеют один или более интерфейсов: UART,SPI,IC/TWI,PWM (ШИМ), но не имеютADC (АЦП). GPIO- порты обычно могут быть сконфигурированны на ввод или вывод (Input/Output), состояние по умолчанию обычноINPUT.

Некоторые GPIO-порты являются просто питающими портами 3.3V, 5V и GND, они не связаны сSoCи не могут использоваться как либо еще.

Порты сальтернативной функцией- могут быть мультиплексированны с одним изсоответствующих емуинтерфейсов.

Порты в режиме INPUTмогут генерироватьпрерывания по спаду, по фронту, по логическому уровню, по изменению сигнала и в асинхронном режиме по фронту и спаду. Порты в режиме INPUTимеют входную фильтрацию на триггереШмитта(преобразовывают аналоговый сигнал в цифровой с резкими переходами между состояниями).

Работа с контактами GPIOосуществляется через виртуальную файловую систему sysfs. стандартный интерфейс для работы с контактами sysfs впервые появился с версии ядра 2.6.26, в Linux. Работа с GPIO проходит через каталог /sys/class/gpio путём обращения к файлам-устройствам.

К портам GPIO подключаются:

  • светодиоды;
  • кнопки;
  • реле;
  • температурные и другие датчики;
  • различные периферийные устройства.

Для программирования GPIO существует несколько способов обращения:

  • Посредством файл-устройства GPIO;
  • Используя языки программирования:
    • Через прямое обращение к регистрам чипа;
    • Используя уже готовые библиотеки (libgpiod).


Одноплатный компьютер Banana Pi BPI-M64


Banana Pi BPI-M64 это 64-битный четырехъядерный мини-одноплатный компьютер, поставляемый как решение с открытым исходном кодом. Ядром системы является процессор Allwinner A64 с 4-мя ядрами Cortex-A53 с частотой 1.2 ГГц. На плате размещено 2 ГБ DDR3 SDRAM 733МГц оперативной памяти и 8 ГБ eMMC.

На плате размещен 40-контактный совместимый с Raspberry Pi разъем, который содержит: GPIO (x28), Power (+5V, +3.3V and GND), UART, I2C, SPI. И 40-контактный интерфейс MIPI DSI.

dotnet libgpiod
Banana Pi BPI-M64 и 40-контактный разъем типа Raspberry Pi 3

Наличие 40-контактного разъема типа Raspberry Pi 3 GPIO, существенно облегчает подключение датчиков из-за совпадение назначение контактов с Raspberry Pi 3. Не приходится гадать к какому контакту подключать тот или иной датчик. Указанные в посте датчики (светодиод и кнопка) подключенные к Banana Pi BPI-M64, можно подключать на те же самые контакты другого одноплатного компьютера, на котором тоже есть 40-контактный разъем, типа Raspberry Pi 3 (или к самой Raspberry Pi 3, разницы нет никакой). Единственное, необходимо изменить номера контактов (линий, ножка процессора) в программном коде, т.к. они зависят от используемого процессора. Но легко определяются но названию контакта. Плата Cubietruck (ARM32) приведена для проверки совместимости и работы кода на 32-разрядных ARM процессорах.

Banana Pi BPI-M64 GPIO Header Position
Позиция [1] 3V3 power соответствует позиции на плате со стрелочкой

Формула для вычисления номера GPIOXX
Для обращение к контактам из C# кода необходимо знать порядковый номер (линия, порт) физической ножки процессора SoC(для Allwinner). Эти данные в спецификациях отсутствую, т.к. порядковый номер получаем путем простого расчета. Например, из схемы возьмем 32-контакт на разъеме типа Raspberry Pi. Название контакта PB7, для получения номера контакта на процессоре произведем расчет по формуле:
(позиция буквы в алфавите 1) * 32 + позиция вывода.Первая буква не учитывается т.к. P PORT, позиция буквы B в алфавите = 2, получаем (2-1) * 32 + 7 = 39. Физический номер контакта PB7является номер 39. У каждого разработчика SoC может быть свой алгоритм расчета номера контактов, должен быть описан в Datasheet к процессору.

Banana Pi BPI-M64 GPIOXX
Контакт PB7 на процессоре Allwiner A64, номер ножки 39

Библиотеки .NET IoT


До того как напишем первую программу на C# по управления GPIO, необходимо рассмотреть пространство имен входящих в dotnet/iot. Все используемые библиотеки добавляются через Nuget пакеты. Подробно рассмотрим драйвера для получения доступа к контактам GPIO одноплатного компьютера. Код на C# взаимодействует с GPIO через специальный драйвер, который является абстракцией доступа к GPIO и позволяет переносить исходный код от одного одноплатного компьютера к другому, без изменений.

Пространства имен .NET IoT:

  • System.Device.Gpio. Пакет System.Device.Gpio поддерживает множество протоколов для взаимодействия с низкоуровневыми аппаратными интерфейсами:
    • General-purpose I/O (GPIO);
    • Inter-Integrated Circuit (I2C);
    • Serial Peripheral Interface (SPI);
    • Pulse Width Modulation (PWM);
    • Serial port.


  • Iot.Device.Bindings. Пакет Iot.Device.Bindings содержит:
    • Драйвера и обертки над System.Device.Gpio для различных устройств которые упрощают разработку приложений;
    • Дополнительные драйвера поддерживаемые сообществом (community-supported).


dotnet IoT Library
Стек библиотек .NET IoT

Рассмотрим первую программу типа Hello World, мигание светодиода (Blink an LED):

using System;using System.Device.Gpio;using System.Threading;Console.WriteLine("Blinking LED. Press Ctrl+C to end.");int pin = 18;using var controller = new GpioController();controller.OpenPin(pin, PinMode.Output);bool ledOn = true;while (true){    controller.Write(pin, ((ledOn) ? PinValue.High : PinValue.Low));    Thread.Sleep(1000);    ledOn = !ledOn;}

Разбор кода:

  • using System.Device.Gpio пространство имен для использования контроллера GpioController доступа к аппаратным ресурсам;
  • using var controller = new GpioController() создает экземпляр контроллера для управления контактами GPIO;
  • controller.OpenPin(pin, PinMode.Output) инициализирует контакт pin = 18 на вывод, к 18 контакту подключен светодиод;
  • controller.Write(pin, ((ledOn)? PinValue.High: PinValue.Low)) если ledOn принимает значение True, то PinValue.High присваивает высокое значение 18 контакту и светодиод загорается. На 18 контакт подается напряжение в 3.3V. Если ledOn принимает значение False, то PinValue.Low присваивает низкое значение контакту 18 и светодиод гаснет. На 18 контакт подается напряжение в 0V (или минимальное пороговое для значения 0, может быть немного выше 0V).

Далее остается компиляция под ARM архитектуру: dotnet publish -r linux-arm или dotnet publish -r linux-arm64. Но так работает просто только для Raspberry Pi. При использование одноплатных компьютерах отличных от Raspberry Pi необходимо при инициализации GpioController выбирать драйвер доступа к GPIO.

Драйвера доступа к GPIO из .NET


Классы драйверов доступа к GPIO находятся в пространстве имен System.Device.Gpio.Drivers. Доступны следующие драйвера-классы:

  • HummingBoardDriver GPIO драйвер для платы HummingBoard на процессоре NXP i.MX 6 Arm Cortex A9;
  • LibGpiodDriver этот драйвер использует библиотеку Libgpiod для получения доступа к портам GPIO, заменяет драйвер SysFsDriver. Библиотека Libgpiod может быть установлена на Linux и Armbian, не является аппаратно-зависимой, что позволяет ее использовать для различных одноплатных компьютерах ARM32 и ARM64;
  • RaspberryPi3Driver GPIO драйвер для одноплатных компьютеров Raspberry Pi 3 или 4;
  • SysFsDriver GPIO драйвер работающий поверх интерфейса SysFs для Linux и Unux систем, предоставляет существенно меньше возможностей, чем драйвер LibGpiodDriver, но не требует установки библиотеки Libgpiod. Тот случай, когда хочется просто попробовать помигать светодиодом из C# без дополнительных действий;
  • UnixDriver базовый стандартный класс доступа к GPIO для Unix систем;
  • Windows10Driver GPIO драйвер для ОС Windows 10 IoT. Из поддерживаемых плат только Raspberry Pi, весьма ограниченное применение.

В данном посте будет рассматриваться доступ к GPIO через драйвер LibGpiodDriver. Драйвер SysFsDriver базируется на устаревшем методе работы с GPIO через виртуальную файловую систему SysFs. Для решений IoT, SysFs не подходит по трем серьезным причинам:

  • Низкая скорость работы I/O;
  • Есть проблемы с безопасной работой с GPIO при совместном доступе;
  • При контейнеризации приложения на C# в контейнер придется пробрасывать много путей из файловой системы Linux, что создается дополнительные сложности. При использование библиотеки Libgpiod этого не требуется.

Библиотека Libgpiod предназначена для работы с GPIO не только из .NET кода, но и из Python, C++, и т.д. Поэтому ниже изложенная инструкция по установке библиотеки Libgpiod позволит разработчикам на Python реализовывать подобную функциональность, как и на C#. В состав пакета Libgpiod входят утилиты для работы с GPIO. До создание программы на C#, поработаем с датчиками через эти утилиты.

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


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

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. Так, например, для подачи сигнала 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(LibraryGeneralPurposeInput/Outputdevice) предоставляет набор API для вызова из своих программ и несколько утилит для управления линиями GPIO из пользовательского режима.

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

  • gpiodetect выведет список всех чипов GPIO, их метки и количество линий;
  • gpioinfo выведет информацию о линиях GPIO конкретного контроллера GPIO. В таблице вывода по колонкам будет указано: номер линии, название контакта, направление ввода/вывода, текущее состояние;
  • gpioget считает текущее состояние линии GPIO;
  • gpioset установит значение для линии GPIO;
  • gpiofind выполняет поиск контроллера GPIO и линии по имени;
  • gpiomon осуществляет мониторинг состояния линии GPIO и выводит значение при изменение состояния.

Например, следующая программа написанная на 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#, и т.д.

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

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


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

Библиотеку libgpiod можно установить из репозитария дистрибутива, но скорее всего будет доступна старая версия. Установка libgpiod:

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

Для установки последней актуальной версии необходимо выполнить скрипт установки, который возьмет последнюю версию библиотеки из исходного репозитария. В строке вызова скрипта установки setup-libgpiod-arm64.sh, в качестве первого параметра указать номер версии библиотеки (например: 1.6.3), второй параметр (необязательный) папка установки скрипта. По умолчанию библиотека установится по пути: /usr/share/libgpiod.

Скрипт установки из исходного текста библиотеки libgpiod и утилит для ARM32/ARM64:

$ cd ~/$ sudo apt-get update$ sudo apt-get install -y curl $ curl -SL --output setup-libgpiod-armv7-and-arm64.sh https://raw.githubusercontent.com/devdotnetorg/dotnet-libgpiod-linux/master/setup-libgpiod-armv7-and-arm64.sh$ chmod +x setup-libgpiod-armv7-and-arm64.sh$ sudo ./setup-libgpiod-armv7-and-arm64.sh 1.6.3

Для удаления библиотеки выполнить скрипт: remove-libgpiod-armv7-and-arm64.sh

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

root@bananapim64:~# gpiodetect -vgpiodetect (libgpiod) v1.6.3Copyright (C) 2017-2018 Bartosz GolaszewskiLicense: LGPLv2.1This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law.

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


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

root@bananapim64:~# gpiodetectgpiochip0 [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 --helpUsage: 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

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

root@bananapim64:~# gpioinfo 1gpiochip1 - 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 1 68=0, где 1 gpiochip1, 68 номер линии(контакта), 0 логическое значение, может быть 0 или 1. Результат выполнения команды:

root@bananapim64:~# gpioset 1 68=0gpioset: error setting the GPIO line values: Device or resource busyroot@bananapim64:~#

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

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

root@bananapim64:~# gpioset 1 36=1

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

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

root@bananapim64:~# gpioget 1 361

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

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

root@bananapim64:~# gpiomon 1 38event:  RISING EDGE offset: 38 timestamp: [     122.943878429]event: FALLING EDGE offset: 38 timestamp: [     132.286218099]event:  RISING EDGE offset: 38 timestamp: [     137.639045559]event: FALLING EDGE offset: 38 timestamp: [     138.917400584]

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

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

Установка .NET 5.0 для ARM


Одно из лучших нововведений в .NET 5.0 стало увеличение производительности для архитектуры ARM64. Поэтому переход на новую версию не только увеличит производительность решения на базе ARM64, но и увеличит время автономной работы в случае использования аккумуляторной батареи.

Определение архитектуры ARM32 и ARM64 для SoC


.NET 5 устанавливается на одноплатный компьютер в соответствие с архитектурой SoC:

  • ARM32, ARMv7, aarch32, armhf 32-разрядная архитектура ARM. Первые процессоры ARM для встраиваемых систем разрабатывались именно на этой архитектуре. По заявлению компании ARM Holding, в 2022 поддержка 32-битных платформ прекратится, и будет поддерживаться только 64-битная архитектура. Это означает, что компания не будет поддерживать разработку ПО для 32-битных систем. Если конечный производитель устройства пожелает установить 32-битную ОС, то ему придется самостоятельно заняться портированием драйверов с 64-битной архитектуры на 32-битную.
  • ARM64, ARMv8, aarch64 64-разрядная архитектура ARM. Ядра Cortex-A53 и Cortex-A57, поддерживающие ARMv8, были представлены компанией ARM Holding 30 октября 2012 года.

Плата Banana Pi BPI-M64 построена на основе процессора Allwinner A64, содержит в себе 64-битные ядра Cortex-A53, поэтому поддерживает 64-разрядные приложения. Для платы Banana Pi BPI-M64 используется 64-разрядный образ ОС Armbian, поэтому на плату будем устанавливать .NET для 64-разрядных систем ARM.

Плата Cubietruck построена на основе процессора Allwinner A20 содержит в себе 32-битные ядра Cortex-A7, поэтому поддерживает только 32-разрядные приложения. Соответственно на плату устанавливается .NET для 32-разрядных систем.

Если вы не знаете какую версию .NET установить на одноплатный компьютер, то необходимо выполнить команду для получения информации об архитектуре системы: uname -m.

Выполним команду на Banana Pi BPI-M64:

root@bananapim64:~# uname -maarch64

Строка aarch64 говорит о 64-разрядной архитектуре ARM64, ARMv8, aarch64, поэтому установка .NET для 64-х разрядных ARM систем.

Выполним команду на Cubietruck:

root@cubietruck:~# uname -marmv7l

Строка armv7l говорит о 32-разрядной архитектуре ARM32, ARMv7, aarch32, armhf, поэтому установка .NET для 32-разрядных ARM систем.

Редакции .NET 5.0 на ARM


.NET 5.0 можно устанавливать в трех редакциях:

  • .NET Runtime содержит только компоненты, необходимые для запуска консольного приложения.
  • ASP.NET Core Runtime предназначен для запуска ASP.NET Core приложений, так же включает в себя .NET Runtime для запуска консольных приложений.
  • SDK включает в себя .NET Runtime, ASP.NET Core Runtime и .NET Desktop Runtime. Позволяет кроме запуска приложений, компилировать исходный код на языках C# 9.0, F# 5.0, Visual Basic 15.9.

Для запуска .NET программ достаточно установки редакции .NET Runtime, т.к. компиляция проекта будет на компьютере x86.

Загрузить .NET с сайта Microsoft можно по ссылке Download .NET 5.0.

Установка .NET Runtime


На странице Download .NET 5.0. можно узнать текущую актуальную версию .NET. В первой колонке Release information будет указана версия: v5.0.5 Released 2021-04-06. Версия номер: 5.0.5. В случае выхода более новый версии .NET, ниже в скрипте в строке export DOTNET_VERSION=5.0.5, нужно будет заменить номер версии на последний. Выполним скрипт установки, в зависимости от разрядности системы ARM32 (Cubietruck) или ARM64(Banana Pi BPI-M64):

ARM64

$ cd ~/$ apt-get update && apt-get install -y curl$ export DOTNET_VERSION=5.0.5$ curl -SL --output dotnet.tar.gz https://dotnetcli.azureedge.net/dotnet/Runtime/$DOTNET_VERSION/dotnet-runtime-$DOTNET_VERSION-linux-arm64.tar.gz \&& mkdir -p /usr/share/dotnet \&& tar -ozxf dotnet.tar.gz -C /usr/share/dotnet \&& rm dotnet.tar.gz$ ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet

ARM32
$ cd ~/$ apt-get update && apt-get install -y curl$ export DOTNET_VERSION=5.0.5$ curl -SL --output dotnet.tar.gz https://dotnetcli.azureedge.net/dotnet/Runtime/$DOTNET_VERSION/dotnet-runtime-$DOTNET_VERSION-linux-arm.tar.gz \&& mkdir -p /usr/share/dotnet \&& tar -ozxf dotnet.tar.gz -C /usr/share/dotnet \&& rm dotnet.tar.gz$ ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet


Проверим запуск .NET, командой (результат одинаков для Banana Pi BPI-M64 и Cubietruck): dotnet --info

root@bananapim64:~# dotnet --infoHost (useful for support):  Version: 5.0.5  Commit:  2f740adc14.NET SDKs installed:  No SDKs were found..NET runtimes installed:  Microsoft.NETCore.App 5.0.5 [/usr/share/dotnet/shared/Microsoft.NETCore.App]To install additional .NET runtimes or SDKs:  https://aka.ms/dotnet-download

.NET установлен в системе, для запуска приложений в Linux необходимо воспользоваться командой: dotnet ConsoleApp1.dll

Обновление .NET 5.0


При выходе новых версий .NET необходимо сделать следующее:

  1. Удалить папку /usr/share/dotnet/
  2. Выполнить скрипт установки, указав новую версию .NET в строке export: DOTNET_VERSION=5.0.5. Номер последней версии .NET можно посмотреть на странице Download .NET 5.0. Строку скрипта создания символической ссылки выполнять не надо: ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet


Удаленная отладка приложения на .NET 5.0 в Visual Studio Code для ARM


Удаленная отладка в Visual Studio Code позволяет в интерактивном режиме видеть ошибки и просматривать состояние переменных, без необходимости постоянного ручного переноса приложения на одноплатный компьютер, что существенно облегчает разработку. Бинарные файлы копируются в автоматическом режиме с помощью утилиты Rsync. Для работы с GPIO, настройка удаленной отладки не является обязательной задачей. Более подробно можно почитать в публикации Удаленная отладка приложения на .NET 5.0 в Visual Studio Code для ARM на примере Banana Pi BPI-M64 и Cubietruck (Armbian, Linux).

Создание первого приложения для управления (вкл/выкл светодиода) GPIO на C#, аналог утилиты gpioset


Поздравляю тебя %habrauser%! Мы уже подходим к финалу, осталось буквально чуть-чуть. Разрабатывать и компилировать приложение будем на x86 компьютере в в Visual Studio Code. Находясь в этой точке, подразумевается, что на одноплатном компьютере уже установлена платформа .NET 5 и библиотека Libgpiod, а на компьютере x86 .NET 5 и Visual Studio Code. Итак приступаем:

Шаг 1 Создание приложения dotnet-gpioset


Действия выполняются на x86 компьютере. В командной строке создаем проект с названием dotnet-gpioset: dotnet new console -o dotnet-gpioset, где dotnet-gpioset название нового проекта. Результат выполнения команды:

D:\Anton\Projects>dotnet new console -o dotnet-gpiosetGetting ready...The template "Console Application" was created successfully.Processing post-creation actions...Running 'dotnet restore' on dotnet-gpioset\dotnet-gpioset.csproj...  Определение проектов для восстановления...  Восстановлен D:\Anton\Projects\dotnet-gpioset\dotnet-gpioset.csproj (за 68 ms).Restore succeeded.

После выполнения команды будет создана папка \Projects\dotnet-gpioset\, в этой папке будет расположен наш проект: папка obj, файл программы Program.cs и файл проекта dotnet-gpioset.csproj.

Шаг 2 Установка расширения C# for Visual Studio Code (powered by OmniSharp) для Visual Studio Code


Запустим Visual Studio Code и установим расширение C# for Visual Studio Code (powered by OmniSharp), для возможности работы с кодом на C#. Для этого нажмем на закладке: 1. Extensions, затем 2. в поле ввода напишем название расширения C# for Visual Studio Code, выберем пункт 3. C# for Visual Studio Code (powered by OmniSharp). 4. Перейдем на страницу описание расширения и нажмем на кнопку Install.

.NET Visual Studio Code ARM
C# for Visual Studio Code (powered by OmniSharp)

После установки можно выполнить настройку расширения.

.NET Visual Studio Code ARM
Настройка расширения C# for Visual Studio Code

После установки расширения, перезапустим Visual Studio Code.

Шаг 3 Открытие проекта в Visual Studio Code и добавление NuGet пакетов


Откроем проект в Visual Studio Code. Меню: File =>Open Folder, и выберем папку с проектом \Projects\dotnet-gpioset\

dotnet libgpiod
Проект в Visual Studio Code

Откроем файл dotnet-gpioset.csproj, убедимся что версия .NET выставлена верно, должно быть следующее содержание:

dotnet libgpiod
Содержание файла dotnet-gpioset.csproj

NuGet пакеты можно добавить через командную строку или расширение NuGet Package Manager. Установим данное расширение, и добавим пакеты: Iot.Device.Bindings и System.Device.Gpio. Для этого нажмем комбинацию Ctrl+Shift+P, затем в поле введем: Nuget, выберем Nuget Packet Managet: Add Package.

dotnet libgpiod
Запуск расширения NuGet Package Manager

В поле ввода укажем название пакета Iot.Device.Bindings, нажмем Enter, затем выберем версию 1.4.0 и нажмем Enter. Так же сделать и для пакета System.Device.Gpio. В результате добавление пакетов, содержимое файла dotnet-gpioset.csproj должно быть следующим:

dotnet libgpiod
Содержание файла dotnet-gpioset.csproj

Шаг 4 Добавление обработки аргументов в код


Утилита dotnet-gpioset как и оригинальная gpioset будет принимать на вход точно такие же аргументы. Вызов: dotnet-gpioset 1 36=1, включит светодиод на gpiochipX 1, номер линии 36, значение 1. В режиме отладки будут заданы значения по умолчанию int_gpiochip=1, int_pin=36, pin_value = PinValue.High. Подключим пространство имен System.Device.Gpio для использование структуры PinValue.

Обработка входящих аргументов:

static void Main(string[] args){  //run: dotnet-gpioset 1 36=1  //-----------------------------------------------                          int? int_gpiochip=null,int_pin=null;  PinValue? pin_value=null;    #if DEBUG    Console.WriteLine("Debug version");    int_gpiochip=1;    int_pin=36;    pin_value = PinValue.High;  #endif  if (args.Length==2)    {      //Read args      if (int.TryParse(args[0], out int output)) int_gpiochip = output;      Regex r = new Regex(@"\d+=\d+");//36=1      if (r.IsMatch(args[1])) //check: 36=1        {          var i = args[1].Split("=");          if (int.TryParse(i[0], out output)) int_pin = output;          if (int.TryParse(i[1], out output))            {              pin_value=(output != 0) ? PinValue.High : PinValue.Low;                                         }        }      }  Console.WriteLine($"Args gpiochip={int_gpiochip}, pin={int_pin}, value={pin_value}");  //next code  Console.WriteLine("Hello World!");}

Запускаем выполнение кода для проверки, меню Run => Start Debugging, все работает отлично!

Загружено "C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.5\System.Text.Encoding.Extensions.dll". Загрузка символов пропущена. Модуль оптимизирован, включен параметр отладчика "Только мой код".Debug versionArgs gpiochip=1, pin=36, value=HighHello World!Программа "[8528] dotnet-gpioset.dll" завершилась с кодом 0 (0x0).

Шаг 5 Добавление контроллера управления GPIO c драйвером LibGpiodDriver


Для управления GPIO необходимо создать объект GpioController и указать драйвер LibGpiodDriver, для этого добавим пространство имен System.Device.Gpio.Drivers.

Добавление контроллера:

//next codeGpioController controller;var drvGpio = new LibGpiodDriver(int_gpiochip.Value);            controller = new GpioController(PinNumberingScheme.Logical, drvGpio);

Описание кода:

  • GpioController класс контроллера для управления контактами GPIO;
  • LibGpiodDriver(int_gpiochip.Value) драйвер обертки библиотеки Libgpiod, в качестве аргумента указываем номер gpiochip;
  • GpioController(PinNumberingScheme.Logical, drvGpio) инициализация контроллера, PinNumberingScheme.Logical формат указания контактов. Есть два варианта, по названию контакта или по его номеру. Но т.к. названия контактов не заданы, то обращение будет только по номеру.

Шаг 6 Управление контактом GPIO


Добавление кода для задания значения контакту:

//set value            if(!controller.IsPinOpen(int_pin.Value))  {    controller.OpenPin(int_pin.Value,PinMode.Output);    controller.Write(int_pin.Value,pin_value.Value);                      } 

Описание кода:

  • controller.IsPinOpen проверка открытия контакта, может быть занят или недоступен;
  • controller.OpenPin открытие контакта и задание ему режима работы, PinMode.Output на вывод;
  • controller.Write(int_pin.Value,pin_value.Value) выставление контакту int_pin значение pin_value.

Шаг 7 Публикация для архитектуры ARM


Открыть командную строку, и перейти в папку \Projects\dotnet-gpioset\.

Для ARM32 выполнить команду:

  • параметр --runtime задает архитектуру выполнения программы (берется из списка Runtime Identifiers (RIDs));
  • параметр --self-contained указывает на необходимость добавление в каталог всех зависимых сборок .NET, при выставление значение в False, копируются только дополнительные сборки не входящие в .NET Runtime (в данном случае будут скопированы сборки из дополнительных NuGet пакетов).

dotnet publish dotnet-gpioset.csproj --configuration Release --runtime linux-arm --self-contained false

Файлы для переноса на одноплатный компьютер будут в папке: \Projects\dotnet-gpioset\bin\Release\net5.0\linux-arm\publish\.

Для ARM64 выполнить команду:

dotnet publish dotnet-gpioset.csproj --configuration Release --runtime linux-arm64 --self-contained false

Файлы для переноса на одноплатный компьютер будут в папке: \Projects\dotnet-gpioset\bin\Release\net5.0\linux-arm64\publish\.

Шаг 8 Перенос папки \publish\


Содержимое папки \publish\ необходимо перенести в домашний каталог Linux пользователя на одноплатном компьютере. Это можно сделать используя терминал MobaXterm.

Шаг 9 Запуск dotnet-gpioset на одноплатном компьютере


Содержимое папки \publish\ было скопировано в папку /root/publish-dotnet-gpioset. Исполняемым файлом будет файл с расширением *.dll. В самом начале, светодиод был подключен на контакт 33, 40-контактного разъема совместимого с Raspberry P, название контакта PB4, номер линии 36. Поэтому в качестве аргумента номера контакта указываем 36. Для запуска программы необходимо выполнить команду:

dotnet dotnet-gpioset.dll 1 36=1

Результат выполнения команды:

root@bananapim64:~# cd /root/publish-dotnet-gpiosetroot@bananapim64:~/publish-dotnet-gpioset# dotnet dotnet-gpioset.dll 1 36=1Args gpiochip=1, pin=36, value=HighOK

Светодиод включился!



Проект доступен на GitHub dotnet-gpioset.

Создание приложения обработки прерывания от кнопки


Теперь реализуем программу обработки прерываний от GPIO. Задача будет заключаться в переключение светодиода по нажатию кнопки. Первое нажатие кнопки включит светодиод, последующее, выключит светодиод, и так до бесконечности. Программа основана на примере Push button.

Светодиод подключен контакту с номером 36. Кнопка подключена на контакт с номером 38. Итак приступаем:

Шаг 1 Создание приложения dotnet-led-button


Действия выполняются на x86 компьютере. В командной строке создаем проект с названием dotnet-led-button: dotnet new console -o dotnet-led-button, где dotnet-led-button название нового проекта.

D:\Anton\Projects>dotnet new console -o dotnet-led-buttonGetting ready...The template "Console Application" was created successfully.Processing post-creation actions...Running 'dotnet restore' on dotnet-led-button\dotnet-led-button.csproj...  Определение проектов для восстановления...  Восстановлен D:\Anton\Projects\dotnet-led-button\dotnet-led-button.csproj (за76 ms).Restore succeeded.

После выполнения команды будет создана папка с файлами проекта \Projects\dotnet-led-button\.

Шаг 2 Открытие проекта в Visual Studio Code и добавление NuGet пакетов


Точно так же, как и в предыдущем проекте добавим Nuget пакеты: Iot.Device.Bindings и System.Device.Gpio.

Шаг 3 Добавление контроллера управления GPIO c драйвером LibGpiodDriver


Добавим контроллер для управления GPIO, и выставим режим работы контактов:

private const int GPIOCHIP = 1;private const int LED_PIN = 36;private const int BUTTON_PIN = 38;       private static PinValue ledPinValue = PinValue.Low;     static void Main(string[] args){                          GpioController controller;  var drvGpio = new LibGpiodDriver(GPIOCHIP);  controller = new GpioController(PinNumberingScheme.Logical, drvGpio);  //set value  if(!controller.IsPinOpen(LED_PIN)&&!controller.IsPinOpen(BUTTON_PIN))    {      controller.OpenPin(LED_PIN,PinMode.Output);      controller.OpenPin(BUTTON_PIN,PinMode.Input);    }  controller.Write(LED_PIN,ledPinValue); //LED OFF

Описание кода:

  • controller.OpenPin(LED_PIN,PinMode.Output) - открывает контакт светодиода, и выставляет режим работы на вывод;
  • controller.OpenPin(BUTTON_PIN,PinMode.Input) - открывает контакт кнопки, и выставляет режим работы на ввод (сигнал поступает от кнопки.

Шаг 4 Добавление обработки прерывания кнопки


Обработка прерывания реализуется путем добавление Callback на изменение состояние контакта. Callback регистрируется в контроллере GPIO:

controller.RegisterCallbackForPinValueChangedEvent(BUTTON_PIN,PinEventTypes.Rising,(o, e) =>  {    ledPinValue=!ledPinValue;    controller.Write(LED_PIN,ledPinValue);    Console.WriteLine($"Press button, LED={ledPinValue}");          });

Описание кода:

  • RegisterCallbackForPinValueChangedEvent регистрация Callback на контакт BUTTON_PIN, будет срабатывать при нажатие на кнопку Rising. Так же доступно срабатывание на событие отпускание кнопки.

Шаг 5 Публикация для архитектуры ARM


Открыть командную строку, и перейти в папку \Projects\dotnet-led-button\.

Для ARM32 выполнить команду:

dotnet publish dotnet-led-button.csproj --configuration Release --runtime linux-arm --self-contained false

Файлы для переноса на одноплатный компьютер будут в папке: \Projects\dotnet-led-button\bin\Release\net5.0\linux-arm\publish\.

Для ARM64 выполнить команду:

dotnet publish dotnet-led-button.csproj --configuration Release --runtime linux-arm64 --self-contained false

Файлы для переноса на одноплатный компьютер будут в папке: \Projects\dotnet-led-button\bin\Release\net5.0\linux-arm64\publish\.

Шаг 6 Перенос папки \publish\


Содержимое папки \publish\ необходимо перенести в домашний каталог Linux пользователя на одноплатном компьютере.

Шаг 7 Запуск dotnet-led-button на одноплатном компьютере


Содержимое папки \publish\ было скопировано в папку /root/publish-dotnet-led-button. Для запуска программы необходимо выполнить команду:

dotnet dotnet-led-button.dll

Результат выполнения команды:

root@bananapim64:~/publish-dotnet-led-button# dotnet dotnet-led-button.dllCTRL+C to interrupt the read operation:Press any key, or 'X' to quit, or CTRL+C to interrupt the read operation:Press button, LED=LowPress button, LED=HighPress button, LED=LowPress button, LED=HighPress button, LED=Low

Кнопка работает!

Проект доступен на GitHub dotnet-led-button.

Теперь поговорим о скорости


Замеры скорости управления GPIO на Banana Pi BPI-M64 не проводились из-за отсутствия осциллографа. Но не так давно, пользователь ZhangGaoxing опубликовал результаты замеров скорости на Orange Pi Zero: ОС Armbian buster, ядро Linux 5.10.16, .NET 5.0.3. Тест заключался в быстром переключение контакта GPIO с 0 на 1 и наоборот, по сути осуществлялась генерация сигнала ШИМ (в Arduino аналог SoftPWM). Чем больше частота, тем быстрее переключатся контакт. Для замера был разработан проект SunxiGpioDriver.GpioSpeed. ZhangGaoxing для доступа к контактам разработал драйвер SunxiDriver, который напрямую обращается к регистрам памяти для управления GPIO. Код этого драйвера так же можно адаптировать к любой плате, путем изменения адресов регистров памяти из datasheet к процессору. Минус такого подхода заключается в отсутствие контроля к GPIO со стороны ОС, можно влезть в контакт используемой ОС и вызвать сбой работы.

Таблица замеров:
Драйвер Язык Версия библиотеки Средняя частота
SunxiDriver C# - 185 KHz
SysFsDriver C# System.Device.Gpio 1.3.0 692 Hz
LibGpiodDriver C# System.Device.Gpio 1.3.0
libgpiod 1.2-3
81 KHz
wiringOP C 35de015 1.10 MHz

Результаты подтвердили, что самым медленным интерфейсом является SysFs, и его не стоит использовать для серьезных проектов. wiringOP является С оберткой доступа к GPIO. Непосредственно управление GPIO из C кода существенно быстрее, чем из приложения на .NET, разница скорости в ~13 раз. Это и есть плата за Runtime.

Итог


Управлять контактами GPIO в C# оказалось не сложнее чем на Arduino. В отличие от Arduino в нашем распоряжение Linux с поддержкой полноценной графики, звуком, и большими возможностями подключения различной периферии. В далеком 2014 году с хабровчанином prostosergik был спор о целесообразности использовании Raspberry Pi в качестве школьного звонка. Мною был реализован подобный функционал на C# .NET Micro Framework, отладочная плата FEZ Domino. С того времени многое что изменилось. Сейчас вариант использования для подобных индивидуальных задач, одноплатных компьютеров на Linux более оправдан, чем использование микроконтроллера. Первое существенное изменение это .NET теперь работает на Linux нативно. Второе появились библиотеки которые упрощают и скрывают под капотом все сложную работу. Третье цена, сейчас одноплатный компьютер с 256 Мб ОЗУ, Ethernet и Wi-Fi в известном китайском магазине можно приобрести за 18$. За такие деньги МК, с поддержкой полноценного Web-интерфейса и шифрования сетевого трафика, вряд ли найдешь. Платформа .NET IoT позволяет работать с GPIO на достаточно высоком уровне абстракции, что существенно снижает порог вхождения. В результате любой разработчик .NET платформы, может с легкостью реализовать свое решение для IoT не вдаваясь в детали как это работает внутри. Установка платформы .NET и библиотеки Libgpiod было приведено для понимания, как это работает, но такой подход не является самым удобным. Гораздо удобнее все разворачивать в Docker контейнере, тем более это mainstream для Linux. В продолжении посмотрим как упаковывать приложение на C# вместе с .NET 5 и Libgpiod в один контейнер, для дальнейшей удобной дистрибьюции нашего решения потенциальному клиенту, задействуем LCD для вывода информации из .NET кода.



На правах рекламы


Прямо сейчас вы можете заказать мощные серверы, которые используют новейшие процессоры AMD Epyc. Гибкие тарифы от 1 ядра CPU до безумных 128 ядер CPU, 512 ГБ RAM, 4000 ГБ NVMe.

Подписывайтесь на наш чат в Telegram.

Подробнее..

Создание терминала для СКУД и УРВ

21.06.2021 12:17:59 | Автор: admin

Вступление

Ранее, в предыдущих статьях, я рассказывал о бизнес-процессах, которые можно закрыть различными решениями нашего программного обеспечения, однако настало время рассказать о разработке железа, а именно терминале, который способен служить 2 целям - контролю доступа и учету рабочего времени в связке с нашей системой учета рабочего времени.

Историю можно начать с того, что наша компания очень долгое время сотрудничает со всемирно известной сетью фастфудов - KFC (на территории Беларуси и Украины). Головной болью такой сферы, как HoReCa, был и будет учет рабочего времени сотрудников. Учитывая огромную текучку кадров, в том числе и обычных студентов, которые пришли подработать на непродолжительное время, становится сложно проконтролировать, сколько часов отработано тем или иным сотрудником. Плюс немаловажным моментом стало то, что сотрудники часто перемещаются с ресторана на ресторан, а это требует дополнительного контроля. Как же быть?

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

Поэтому был придумал предельно простой и быстрый сценарий действий на терминале:

  1. Прийти на рабочее место, подойти к терминалу и пройти идентификацию (приложить палец, карту к считывателю или ввести PIN)

  2. Выбрать на тачскрине Работа, после чего отправиться на свое рабочее место и приступить к работе

  3. При необходимости перерыва подойти к терминалу, пройти идентификацию, выбрать Перерыв

  4. При возвращении на рабочее место - подойти к терминалу, пройти идентификацию, выбрать Работа

  5. По окончанию рабочей смены подойти к терминалу, пройти идентификацию и выбрать Завершить работу.

То есть все события должны были фиксироваться на терминале, после чего все данные залетали бы на облако системы рабочего времени TARGControl. И там, административным персоналом, формировались табели и отчеты по объектам (ресторанам), что позволило бы корректно начислять заработную плату сотрудникам.

Разработка

Имея на руках техническое задание, была разработана структурная и функциональная схема. Далее пошло самое интересное: мы начали рассматривать различные варианты одноплатных компьютеров, которые бы смогли обеспечить нужный функционал терминала. Среди вариантов были следующие компьютеры: Banana Pi M4, Orange Pi PC+, ODROID-C4, NanoPi M4 и Raspberry PI Computer Module 3+. Произведем небольшое сравнение данных моделей.

Banana Pi M4

Orange Pi PC+

ODROID-C4

NanoPi M4

Raspberry PI CM3+

Память

Слот MicroSD с поддержкой расширения до 256 ГБ и флэш-память eMMC 8 ГБ с поддержкой до 64 ГБ

TF-карта (макс. 32ГБ) / слот для карты eMMC

8 ГБ флэш-память EMMC

1x разъем EMMC (доступно 8/16/32/64 ГБ)

1 слот Micro SD

нет встроенной eMMC, но есть разъем eMMC,
1 слот для MicroSD до 128 GB

8 GB eMMc + поддержка 1 слота microSD

RAM

1 GB DDR4 (опционально 2 GB)

1GB DDR3

4GB DDR4

Двухканальный 2GB DDR3-1866

1GB LPDDR2 SDRAM

CPU

Realtek RTD1395 ARM Cortex-A53 Quad-Core 64 Bit 1.8 GHz

H3 Quad-coreCortex-A71.2 GHz

Amlogic S905X3 Quad-Core Cortex-A55 ARMv8.2-A 64-bit 1.5GHz

RK3399- Cortex-A72 + Quad Core Cortex-A531.8 GHz

Broadcom BCM2837B0 с четырьмя ядрами Cortex A53 1.2 GHz

GPU

Mali 470 MP4 GPU OpenGL ES 1.1/2.0

Mali400MP2 GPU 600MHz
с поддержкой OpenGL ES 2.0

Mali-G31, поддержка OpenGL ES 3.2 и API Vulkan последнего поколения

Mali-T864поддержка OpenGL ES1.1/2.0/3.0/3.1, OpenCL, DX11 и AFBC

Broadcom VideoCore IV

Сеть

Ethernet 10/100/1000 Мбит / с
Опциональный USB-ключ Wi-Fi. Поддержка PoE

10/100 Ethernet RJ45

RJ45 Ethernet порт (10/100/1000)

Порт Gbps Ethernet

10/100 для подключения маршрутизатора или коммутатора с функцией PoE

После детального изучения и анализа цены (все модели находились в примерно одном ценовом диапазоне на момент их анализа - 2019 год), мы все же пришли к выводу, что лучше всего подойдет Raspberry PI Computer Module 3+. Почему Raspberry ? Да, некоторые характеристики уступают конкурентам, однако главным преимуществом стало то, что по Raspberry банально больше поддерживаемых библиотек и лучше техническая поддержка, т.к Raspberry на рынке с 2012 года и вокруг него сформировалось активное комьюнити.

Решение со встроенной памятью eMMC, предусмотренное в CM3, позволяет не использовать флеш-карту в качестве носителя ОС. Большое количество циклов перезаписи eMMC повышает надежность и срок службы памяти по сравнению с флеш-картами. При этом мы зарезервировали разъем для SD карт. Сразу можно сказать, что заявленной памяти для терминала хватает с лихвой, ибо сохраняемые события весят от силы пару килобайт. Что касается хранения фотографий, то здесь все сложнее: программно мы поставили ограничение в 5000 фото, но так как у нас зарезервирована флеш-карта, то данный лимит можно расширить до приемлемого значения.

Разработку управляющей платы мы начали с организации необходимых питающих напряжений. На нашей плате нам необходимо было обеспечить 3 значения напряжений: 5.0В, 3.3В и 1.8В. Внешний источник питания у нас 12В 3А. Для получения 5В и 3.3В мы использовали схему на основе широтной импульсной модуляции. Для источника питания 1.8В мы задействовали линейный понижающий преобразователь. Это выглядит, примерно, следующим образом.

Схема питания терминалаСхема питания терминала

Исходя из схемы видно, что для электрической части терминала была организована схема защиты питания от короткого замыкания и переполюсовки.

Отметим также и схему защиты питания внешних считывателей, представленную ниже.

Схема защиты питания считывателя от диверсийСхема защиты питания считывателя от диверсий

То есть если закоротить питание, считыватель уйдет в защиту (питание считывателя отключится), а терминал продолжит работу. Номиналы резисторов R51 и R52 задают выходной ток. Изменяя номинал резистора R51 можно менять ток ограничения в значительных пределах (у нас стоят заданные на схеме номиналы исходя из рассчитанной нами нагрузки).

Питание мы организовали, самое время перейти к периферии.

К слову, Raspberry поддерживает UART-интерфейс и 2 USB версии 2.0. Через один из доступных USB мы решили организовать доступ к Ethernet с помощью микросхемы LAN9514 . Второй USB используется для подключения периферии, например индикатор алкоголя. Также задействовав GPIO для подключения кнопок, электромагнитных замков, защёлок, алкостестера в дискретном режиме работы и картаприемников.

Схема реализации Ethernet и USBСхема реализации Ethernet и USB

У CM3 на борту всего 2 UART. Один нам пригодится для организации интерфейса RS-485, а второй - debug. Поэтому мы использовали микросхему FT4232HL, для увеличения количества интерфейсов. У нее есть входной интерфейс USB, который поддерживает связь с LAN9514, он же в свою очередь коннектится с CM3.

Схема расширения количества UART-овСхема расширения количества UART-ов

Вот теперь у нас стало больше на целых 4 UARTa (задействуем всего 2). Один используется для подключения биометрического модуля отпечатков пальцев от южнокорейского производителя Suprema-SFM6020-OP6-8M/16M (8M - 5К отпечатков, 16М- 25К отпечатков).

Второй для подключения карточного модуля 7941D (поддерживает 2 частоты Emarine (125 кГц) и Mifare (13,56 МГц).

Suprema-SFM6020-OP6-8MSuprema-SFM6020-OP6-8M

Один из выделенных UART, как и планировали, мы задействовали под RS-485, позволяющий подключать внешние карточные или биометрические считыватели, а также передавать информацию во внешние системы при необходимости интеграции. Схема 485-го представлена ниже.

RS-485RS-485

В этой схеме мы прибегли к некоторым хитрым схемотехническим решениям, объединив и подключив линии RE и DE через транзистор, что позволило нам автоматизировать на аппаратном уровне переключение режимов приема/передачи.

Что касается интеграции с другими системами традиционным для СКУД способом, для передачи минимальной информации у нас организован выходной Wiegand. Есть и два входных Wiegand'а для подключения внешних считывателей. Ниже представлены их электрические принципиальные схемы.

Входной WiegandВходной WiegandВыходной WiegandВыходной Wiegand

Немаловажным моментом будет, что терминал имеет 2 реле для управления замком, турникетом или шлагбаумом. Рассмотрим данный момент на примере подключения турникета (стандартная ситуация для СКУД). Есть два режима управления турникетом, потенциальный режим и импульсный. При потенциальном режиме управления для разблокировки турникета в направлении А срабатывает выход L1 OUT (в направлении В выход L2 OUT). При окончании данного времени или при совершении прохода выходной сигнал возвращается в исходное состояние.

В импульсном режиме для разблокировки выхода L1 OUT и L2 OUT срабатывают кратковременно, посылая управляющий импульс на турникет (обычно 0,2-0,3 секунды). При получении импульса турникет разблокируется в соответствующем направлении на время 5 секунд либо пока не будет совершен проход в данном направлении.

Для контроля прохода в направлении А или направлении В используются две линии, на которые контроллер турникета выдает импульсные сигналы при совершении прохода в том либо другом направлении. Данные импульсные сигналы подключаются к входам SENS1 для прохода в направлении А и SENS2 для прохода в направлении В.

Например, для работы с турникетами PERCo в контроллере должен быть установлен импульсный режим управления. Для этого время срабатывания сигналов L1 OUT и L2 OUT должно быть установлено в пределах от 0,2 до 1 секунды.

Подключение турникета PERCoПодключение турникета PERCo

Как описано в самом начале статьи, по нашему техническому заданию, на терминале должна была быть организована фотофиксация, поэтому решили выбрать камеру от Raspberry, которая в терминалах имеет 2 исполнения:

  1. RPi D - 72.4 градуса обзор, 5 mpx, размер камеры 25x24 мм (старое исполнение).

  2. RPi G - 160 градусов обзор, 5 mpx, размер камеры 25x24 мм (теперь используем только этот вариант).

Схема организации интерфейсов DSI и CSIСхема организации интерфейсов DSI и CSI

Выбирая дисплей, выбор снова пал на знакомый бренд - 7-ми дюймовый touch-screen от Raspberry с разрешением 800x480 и DSI интерфейсом.

Исходя из размеров дисплея, считывателя и общей компоновки плат, были определены габариты корпуса для терминала - 210 x 173 x 44. Корпус решено было сделать цельнометаллическим алюминиевыми с гальваническим покрытием, что обеспечивало хотя бы минимальную вандалоустойчивость. Дисплей, конечно же, так не защищен.

Корпус терминалаКорпус терминала

Собрав все воедино мы получили терминал данного вида.

Знакомьтесь, терминал D1!Знакомьтесь, терминал D1!

Немного отклоняясь от темы, можно сказать, что мы были обмануты Raspberry, когда они в 4-ом поколении Compute Module поменяли форм-фактор и мы уже не смогли его просто заменить в нашей текущей плате и были вынуждены переходить на новое поколение путем разработки новой платы, но об этом в следующих статьях.

На данный момент с 2019 года нашей компаний было продано более 500 терминалов данной модели, а это показывает, что решение оказалось весьма успешным и надежным. В данной статье я коснулся лишь одной части - железа, однако большая работа была проделана и над прошивкой терминала. В следующей статье я расскажу, как реализована программная часть терминала. В случае возникновения вопросов или критических замечаний, прошу последовать в комментарии для бурного обсуждения!

Подробнее..

Добавляем поддержку Vendor-команд к USB3.0 устройству на базе FX3

02.02.2021 12:18:59 | Автор: admin
В предыдущих статьях мы сделали достаточно интересную железку, состоящую из контроллера FX3 и ПЛИС Cyclone IV. Мы научились гонять через шину USB 3.0 потоки данных с достаточно высокой скоростью (я доказал, что поток 120 МБ/с из ULPI будет проходить через эту систему без искажений и потерь). Всё хорошо, но система, которая просто гонит данные, не имеет смысла. Любую систему надо настраивать. То есть, хочешь не хочешь, а кроме скоростных данных надо слать не очень спешные команды.

У шины USB для передачи команд предназначена конечная точка EP0. Сегодня мы потренируемся дорабатывать прошивку FX3 так, чтобы она обрабатывала команды от PC, а также транслировала их через GPIO в сторону ПЛИС. Кстати, именно здесь проявляется преимущество контроллера над готовым мостом. Что меня в текущей реализации Redd сильно удручает я не могу посылать никаких команд. Их можно только упаковать в основной поток. В случае же с контроллером что хочу, то и делаю. Начинаем творить, что хотим



Предыдущие статьи цикла:
  1. Начинаем опыты с интерфейсом USB 3.0 через контроллер семейства FX3 фирмы Cypress
  2. Дорабатываем прошивку USB 3.0, используя анализатор SignalTap, встроенный в среду разработки Quartus
  3. Учимся работать с USB-устройством и испытываем систему, сделанную на базе контроллера FX3
  4. Боремся с таймаутами при использовании USB 3.0 через контроллер FX3, возникающими при определенных условиях

Введение


Осматривая исходники типовой прошивки, я нашёл знакомое имя функции в файле cyfxgpiftousb.c. Функцию зовут:
/* Callback to handle the USB setup requests. */CyBool_tCyFxApplnUSBSetupCB (        uint32_t setupdat0, /* SETUP Data 0 */        uint32_t setupdat1  /* SETUP Data 1 */    )

Имея за плечами опыт работы с кучкой USB-контроллеров, начиная от прямого предка нашего (это был FX2LP), через STM32 и далее со всеми остановками, я уже нутром чую, что нужная нам функциональность начинается здесь. Собственно, код этой функции как раз разбирает команды группы STANDARD Request. Осталось добавить туда свою группу VENDOR COMMANDS. Жаль только, что все команды, которые уже имеются в готовой функции, не передают данных. Они ограничиваются работой с полями wData и wIndex, Мне этого недостаточно. Я хочу передавать в ПЛИС байт и два 32-битных слова (команда, адрес, данные), либо передавать байт и DWORD, после чего принимать DWORD (передали команду и адрес, приняли данные). То есть, без фазы данных точно не обойтись. Начинаем разбираться, где черпать вдохновение и добавлять желаемую функциональность.

Участок в зоне ответственности шины USB


Итак. Добавить фазу данных. Гуглю по слову:
CyU3PUsbAckSetup

И первая же ссылка ответила на все мои вопросы. На всякий случай вот она.

В том коде данные гоняют и туда, и обратно. Хорошо. Начнём с малого. Сначала вставляем только прогон данных через USB, без их передачи в ПЛИС. Будем для самоконтроля отправлять данные в UART, а при приёме, чтобы не тратить время на сложный вспомогательный код, просто будем заполнять память константами 00, 01 02 03

Добавляем в конец функции CyFxApplnUSBSetupCB() такой блок:
    if (bType == CY_U3P_USB_VENDOR_RQT)    {    // Cut size if needif (wLength > sizeof(ep0_buffer)){wLength = sizeof (ep0_buffer);}    // Need send data to PC    if (bReqType & 0x80)    {    int i;    for (i=0;i<wLength;i++)    {    ep0_buffer [i] = (uint8_t) i;    }    CyU3PUsbSendEP0Data (wLength, ep0_buffer);            isHandled = CyTrue;    } else    {    CyU3PUsbGetEP0Data (wLength, ep0_buffer, NULL);    ep0_buffer [wLength] = 0;// Null terminated String            CyU3PDebugPrint (4, (char*)ep0_buffer);    CyU3PUsbAckSetup();            isHandled = CyTrue;    }    }

Волшебная константа 0x80 согласен, что некрасивая, но не нашлось ничего подходящего в заголовках в районе изучаемого участка, а дальше искать не хотелось. Но, наверное, все помнят, что именно старший бит задаёт направление. Мало того, я в терминологии USB вечно путаюсь, что значит IN, что значит OUT. Я просто запомнил, что, когда есть 0x80 данные бегут в PC. Остальное, вроде, всё красиво и понятно получилось, даже не требует комментариев.

Чтобы не писать своей тестовой программы, проверять я сегодня буду в сниффере BusHound. Если в нём дважды щёлкнуть по устройству, то появляется очень полезный диалог. Вот тут щёлкаем:



И вот такую красоту получаем:



Я заполнил тип команды 0xC0 (Vendor Specific, данные из устройства в PC). Код команды я сделал равным 23 просто так, чисто во время экспериментов. Сейчас туда можно вписать всё, что угодно, в функции это поле не проверяется. Не проверяются и поля Value и Index. А вот когда я вбил поле Length, у меня внизу появился дамп. Всё готово к посылке команды. Нажимаем Run, получаем:



Всё верно. Функция CyFxApplnUSBSetupCB() посылает из FX3 в USB инкрементирующиеся байты, мы их видим. Теперь пробуем передавать. Подключаем UART (как это сделать я рассказывал в одной из предыдущих статей), запускаем терминал. Меняем тип запроса на 0x40 (Vendos Specific Command, данные из PC в устройство). Заполняем поля данных ASCII символами:



Жмём Run получаем:



Прекрасно! Эта часть готова! Переходим к работе с аппаратурой.

Работа с GPIO


Грустная теория


В том же примере, который я нашёл на github, идёт и работа с GPIO. Вот как красиво выглядит это в пользовательской части:
CyU3PGpioSetValue (FPGA_SOFT_RESET,  !((ep0_buffer[0] & GPIO_FPGA_SOFT_RESET) > 0)); CyU3PGpioSetValue (FMC_POWER_GOOD_OUT, ((ep0_buffer[0] & GPIO_FMC_POWER_GOOD_OUT) > 0));

Красиво? Ну, конечно же, красиво! Но впору вспомнить, что я писал в одной из статей про нашу ОСРВ МАКС.

Я там рассказывал, что операторы new и delete по факту раскрываются в огромный кусок кода с непредсказуемым временем исполнения. Примерно так и тут. Функция CyU3PGpioSetValue() раскрывается в такую громаду, что я спрячу её под кат.
Смотреть текст функции CyU3PGpioSetValue().
CyU3PReturnStatus_tCyU3PGpioSetValue (                   uint8_t  gpioId,                   CyBool_t value){    uint32_t regVal;    uvint32_t *regPtr;    if (!glIsGpioActive)    {        return CY_U3P_ERROR_NOT_STARTED;    }    /* Check for parameter validity. */    if (!CyU3PIsGpioValid(gpioId))    {        return CY_U3P_ERROR_BAD_ARGUMENT;    }    if (CyU3PIsGpioSimpleIOConfigured(gpioId))    {        regPtr = &GPIO->lpp_gpio_simple[gpioId];    }    else if (CyU3PIsGpioComplexIOConfigured(gpioId))    {        regPtr = &GPIO->lpp_gpio_pin[gpioId % 8].status;    }    else    {        return CY_U3P_ERROR_NOT_CONFIGURED;    }           regVal = (*regPtr & ~CY_U3P_LPP_GPIO_INTR);    if (!(regVal & CY_U3P_LPP_GPIO_ENABLE))    {        return CY_U3P_ERROR_NOT_CONFIGURED;    }    if (value)    {        regVal |= CY_U3P_LPP_GPIO_OUT_VALUE;    }    else    {        regVal &= ~CY_U3P_LPP_GPIO_OUT_VALUE;    }    *regPtr = regVal;    regVal = *regPtr;    return CY_U3P_SUCCESS;}


Какое будет максимальное быстродействие у кода, вызывающего эту функцию в цикле, мне страшно подумать. У неё есть более компактный аналог, но и его я предпочту спрятать под кат.
Более компактный аналог.
CyU3PReturnStatus_tCyU3PGpioSimpleSetValue (                         uint8_t  gpioId,                         CyBool_t value){    uint32_t regVal;    if (!glIsGpioActive)    {        return CY_U3P_ERROR_NOT_STARTED;    }    /* Check for parameter validity. */    if (!CyU3PIsGpioValid(gpioId))    {        return CY_U3P_ERROR_BAD_ARGUMENT;    }    regVal = (GPIO->lpp_gpio_simple[gpioId] &        ~(CY_U3P_LPP_GPIO_INTR | CY_U3P_LPP_GPIO_OUT_VALUE));    if (value)    {        regVal |= CY_U3P_LPP_GPIO_OUT_VALUE;    }    GPIO->lpp_gpio_simple[gpioId] = regVal;    return CY_U3P_SUCCESS;}


Так что придётся написать что-то своё на скорую руку, выкинув лишние проверки. Эта функция обслуживает вызовы не от безвестных пользователей, которые в теории могут учудить всё, что угодно, а от меня. Про некоторых пользователей я наслышан от коллеги, разбирающего запросы поддержки одной библиотеки. Но я уж точно настроил порты при старте, зачем при каждом обращении к порту это проверять, тратя такты процессора?

Чуть более оптимистичная теория


Чтобы не хранить маску записанных в порт данных, а также обеспечить себе максимальную потокобезопасность, мы можем воспользоваться аппаратурой, дающей независимый доступ к каждому биту порта. Вдохновение мы будем искать в разделе 9.2 GPIO Register Interface документа FX3_Programmers_Manual.pdf.

Вот так выглядит блок GPIO:



Мы видим, что кроме классического двоичного представления, есть такое, где каждой линии (а их в контроллере 61 штука) соответствует собственное 32-разрядное слово. Формат его такой:



Собственно, всё ясно. Так как я собираюсь работать с конкретными линиями GPIO, я вполне могу обращаться к битам IN_VALUE и OUT_VALUE в этих регистрах. Больше мне ничего и не надо. Ну, и настройку направления можно произвести здесь же.

С какими линиями мы работаем


Хорошо. Как нам достукиваться до линий, понятно. А как они адресуются? Что за 61 линия GPIO, о которых говорится в документации? С чем предстоит работать мне? Плату для меня разводил знакомый, которому я поставил очень простую задачу: несколько свободных линий от FX3 завести на ПЛИС. Так как конкретные номера не были мною обозначены, он взял те, которые захотел. Вот участок ПЛИС, к которому подходят линии GPIO, именованные в той нотации, какая задана на шелкографии около разъёма макетки:



Я собираюсь программно реализовать шину SPI, значит, мне надо 4 линии (выбор кристалла, тактовый сигнал и данные туда-обратно). Возьмём линии от DQ24 до DQ27 по принципу А почему бы и нет?. В одной из прошлых статей, я уже показывал таблицу, при помощи которой мы можем быстро сопоставить эти имена с реальными линиями GPIO. Смотрим в неё:



Значит, нас интересуют линии GPIO 41, 42, 43 и 44. Вот с ними я и буду работать.

Инициализация GPIO


Все, кто хорошо знаком с архитектурой ARM, знают, что любые порты надо инициализировать. Как это сделать в нашем случае? Мы работаем с демонстрационным приложением, так что часть работы уже сделана за нас. Доработаем кое-что из готового кода. В функции main(), есть такой участок:
io_cfg.isDQ32Bit = CyTrue;
io_cfg.useUart = CyTrue;
io_cfg.useI2C = CyFalse;
io_cfg.useI2S = CyFalse;
io_cfg.useSpi = CyFalse;
io_cfg.lppMode = CY_U3P_IO_MATRIX_LPP_DEFAULT;

/* No GPIOs are enabled. */
io_cfg.gpioSimpleEn[0] = 0;
io_cfg.gpioSimpleEn[1] = 0;
io_cfg.gpioComplexEn[0] = 0;
io_cfg.gpioComplexEn[1] = 0;
status = CyU3PDeviceConfigureIOMatrix (&io_cfg);

Поправим его так:



То же самое текстом.
    io_cfg.isDQ32Bit = CyFalse;    io_cfg.useUart   = CyTrue;    io_cfg.useI2C    = CyFalse;    io_cfg.useI2S    = CyFalse;    io_cfg.useSpi    = CyFalse;    io_cfg.lppMode   = CY_U3P_IO_MATRIX_LPP_UART_ONLY;    /* No GPIOs are enabled. */    io_cfg.gpioSimpleEn[0]  = 0;    io_cfg.gpioSimpleEn[1]  = (1<<9)|(1<<10)|(1<<11)|(1<<12);    io_cfg.gpioComplexEn[0] = 0;    io_cfg.gpioComplexEn[1] = 0;    status = CyU3PDeviceConfigureIOMatrix (&io_cfg);


Биты 9, 10, 11 и 12 в коде это биты старшего слова. Поэтому физически они соответствуют битам GPIO 9+32=41, 10+32=42, 11+32=43 и 12+32=44. Тем самым, с которыми я собираюсь работать.
Зададим ещё им направления. Скажем, я раскидаю их так:

Бит Цепь Направление
41 SS OUT
42 CLK OUT
43 MOSI OUT
44 MOSI IN


Объявим для этого следующие макросы:
#define MY_BIT_SS    41#define MY_BIT_CLK   42#define MY_BIT_MOSI  43#define MY_BIT_MISO  44

А в функцию CyFxApplnInit() добавим такой код:
    CyU3PGpioClock_t     gpioClock;    gpioClock.fastClkDiv = 2;    gpioClock.slowClkDiv = 16;    gpioClock.simpleDiv  = CY_U3P_GPIO_SIMPLE_DIV_BY_2;    gpioClock.clkSrc     = CY_U3P_SYS_CLK;    gpioClock.halfDiv    = 0;    apiRetStatus = CyU3PGpioInit (&gpioClock, NULL);    if (apiRetStatus != CY_U3P_SUCCESS)    {        CyU3PDebugPrint (4, "GPIO Init failed, error code = %d\r\n", apiRetStatus);        CyFxAppErrorHandler (apiRetStatus);    }    GPIO->lpp_gpio_simple[MY_BIT_SS] = CY_U3P_LPP_GPIO_OUT_VALUE | CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE;    GPIO->lpp_gpio_simple[MY_BIT_CLK] = CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE;    GPIO->lpp_gpio_simple[MY_BIT_MOSI] = CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE;    GPIO->lpp_gpio_simple[MY_BIT_MISO] = CY_U3P_LPP_GPIO_INPUT_EN | CY_U3P_LPP_GPIO_ENABLE;

Всё, блок GPIO инициализирован, направления заданы. А линия SS ещё и взведена в единицу. Можно начинать пользоваться GPIO для реализации функциональности.

Участок в зоне ответственности аппаратуры


Запись в SPI я сделаю в виде макросов взвести в 1 и Сбросить в 0 (увы, именно макросов, перед нами же код на чистых Сях, в плюсах я бы сделал на шаблонных функциях) и одной функции, которая обращается к ним. Получилось так:
#define SET_IO_BIT(nBit) GPIO->lpp_gpio_simple[nBit] |= CY_U3P_LPP_GPIO_OUT_VALUE#define CLR_IO_BIT(nBit) GPIO->lpp_gpio_simple[nBit] &= ~CY_U3P_LPP_GPIO_OUT_VALUEvoid SPI_Write (unsigned int data, int nBits){   while (nBits)   {   if (data&1)   {   SET_IO_BIT (MY_BIT_MOSI);   } else   {   CLR_IO_BIT (MY_BIT_MOSI);   }   SET_IO_BIT (MY_BIT_CLK);   data >>= 1;   nBits -= 1;   CLR_IO_BIT (MY_BIT_CLK);   }}

Соответственно, вместо вывода в UART в ранее написанном обработчике USB-команд, я сделаю вывод в SPI, но по очень хитрому алгоритму. Сначала байт USB-команды. Затем слова wData и wIndex, и потом DWORD, пришедший в фазе данных. При такой солянке сборной, удобнее всё передавать младшим битом вперёд (именно так работает функция SPI_Write()).

Чтение я пока делать не буду. Сейчас проверяется сама идея. Чтобы проверить чтение, надо делать прошивку и для ПЛИС, а запись я могу проконтролировать и при помощи осциллографа.

В результате, код обработчика Vendor-команды трансформируется следующим образом:
    // Need send data to PC    if (bReqType & 0x80)    {    int i;    for (i=0;i<wLength;i++)    {    ep0_buffer [i] = (uint8_t) i;    }    CyU3PUsbSendEP0Data (wLength, (uint8_t*)ep0_buffer);            isHandled = CyTrue;    } else    {    CyU3PUsbGetEP0Data (wLength, (uint8_t*)ep0_buffer, NULL);    ep0_buffer [wLength] = 0;// Null terminated String            CyU3PDebugPrint (4, (char*)ep0_buffer);            CLR_IO_BIT(MY_BIT_SS);            SPI_Write(bRequest,8);            SPI_Write(wValue,16);            SPI_Write(wIndex,16);            SPI_Write(ep0_buffer[0],32);            SET_IO_BIT(MY_BIT_SS);    CyU3PUsbAckSetup();            isHandled = CyTrue;    }

Итого


Итого, даём такой запрос:



И получаем такой результат:



Немного оптимизации


Видно, что данные передаются младшим битом вперёд, хорошо видны байт 0x23 и начало байта 0x55. Всё верно. Правда, частота, конечно, не ахти (её можно разглядеть, если кликнуть по рисунку и посмотреть его в увеличенном виде). Примерно 1.2 мегагерца. В целом, меня сейчас это сильно не беспокоит, но здесь скорее важен сам принцип. Не люблю, когда всё совсем медленно, и всё тут! Смотрим, во что превратилась функция записи, в этом нам поможет файл GpifToUsb.lst:
40003404 <SPI_Write>:40003404:ea00000d b40003440 <SPI_Write+0x3c>40003408:e59f303c ldrr3, [pc, #60]; 4000344c <SPI_Write+0x48>4000340c:e3100001 tstr0, #140003410:e59321ac ldrr2, [r3, #428]; 0x1ac40003414:e1a000a0 lsrr0, r0, #140003418:13822001 orrner2, r2, #14000341c:03c22001 biceqr2, r2, #140003420:e58321ac strr2, [r3, #428]; 0x1ac40003424:e59321a8 ldrr2, [r3, #424]; 0x1a840003428:e2411001 subr1, r1, #14000342c:e3822001 orrr2, r2, #140003430:e58321a8 strr2, [r3, #424]; 0x1a840003434:e59321a8 ldrr2, [r3, #424]; 0x1a840003438:e3c22001 bicr2, r2, #14000343c:e58321a8 strr2, [r3, #424]; 0x1a840003440:e3510000 cmpr1, #040003444:1affffef bne40003408 <SPI_Write+0x4>40003448:e12fff1e bxlr4000344c:e0001000 .word0xe0001000

16 строк. Вполне компактно Я уже много раз писал, что не собираюсь становиться гуру FX3. Поэтому решил не вчитываться в километры документов, а поиграть с кодом на практике. Само собой, несколько часов опытов я опущу, и приведу только итоговый результат. Так что немножко младшим учеником старшего помощника второго заместителя гуру побыть пришлось Но так или иначе. Я изучил вопрос настройки тактирования GPIO и пришёл к выводу, что оно вполне оптимальное.

Но напишем такой тестовый блок кода (первый макрос роняет значение в порту, второй взводит, а дальше идёт чреда взлётов и падений):
#define DOWN GPIO->lpp_gpio_simple[MY_BIT_CLK] = CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE#define UP GPIO->lpp_gpio_simple[MY_BIT_CLK] = CY_U3P_LPP_GPIO_OUT_VALUE | CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;

Ему соответствует участок ассемблерного кода, оптимизировать который в целом, невозможно. Он идеален:
400036c4:e58421a8 strr2, [r4, #424]; 0x1a8400036c8:e58431a8 strr3, [r4, #424]; 0x1a8400036cc:e58421a8 strr2, [r4, #424]; 0x1a8400036d0:e58431a8 strr3, [r4, #424]; 0x1a8400036d4:e58421a8 strr2, [r4, #424]; 0x1a8400036d8:e58431a8 strr3, [r4, #424]; 0x1a8

Результат прогона (получаем меандр с частотой 12.5 МГц):



А теперь заменим запись констант с прямой записи на чтение модификацию запись, как это реализовано в моих макросах для SPI:
#define UP GPIO->lpp_gpio_simple[MY_BIT_CLK] |= CY_U3P_LPP_GPIO_OUT_VALUE#define DOWN GPIO->lpp_gpio_simple[MY_BIT_CLK] &= ~CY_U3P_LPP_GPIO_OUT_VALUE    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;    UP;    DOWN;

В ассемблерном коде покажу только одну итерацию вверх-вниз

400036e4: e59431a8 ldr r3, [r4, #424]; 0x1a8
400036e8: e3c33001 bic r3, r3, #1
400036ec: e58431a8 str r3, [r4, #424]; 0x1a8
400036f0: e59431a8 ldr r3, [r4, #424]; 0x1a8
400036f4: e3833001 orr r3, r3, #1
400036f8: e58431a8 str r3, [r4, #424]; 0x1a8

Вместо пары строк получаем шесть. Частота упадёт втрое? Делаем прогон



12.5/1.9=6.6
Более, чем в шесть раз частота упала! Получается, что чтение из порта довольно медленная операция. Значит, чуть переписываем мои макросы записи в порт, убирая из них операции чтения:
#define SET_IO_BIT(nBit) GPIO->lpp_gpio_simple[nBit] = CY_U3P_LPP_GPIO_OUT_VALUE | CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE#define CLR_IO_BIT(nBit) GPIO->lpp_gpio_simple[nBit] = CY_U3P_LPP_GPIO_DRIVE_LO_EN | CY_U3P_LPP_GPIO_DRIVE_HI_EN | CY_U3P_LPP_GPIO_ENABLE

Делаем прогон записи в SPI



4 мегагерца. Ну вот. Не особо напрягаясь, разогнали систему почти вчетверо. Меня не покидает ощущение, что всё можно разогнать ещё сильнее, но оставим это на потом. Сейчас особо это не требуется.

Заключение


Мы освоили механизм добавления VENDOR команд в USB-устройство на базе FX3. При этом мы испытали работу с командами, передающими данные через конечную точку EP0 в обоих направлениях. Также мы освоили работу с GPIO у этого контроллера. Теперь, кроме скоростной передачи через конечные точки типа BULK и GPIF, мы можем передавать команды в свою прошивку ПЛИС.

А для чего я хочу это применять, будет рассказано в следующей статье.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru