По мере роста производительности и возможностей все больше оборотов набирает практика использования интерпретируемых языков высокого уровня, таких как Lua, Python, JS для разработки приложений. Некоторые языки постепенно проникают и в “младших братьев”, микроконтроллеры, правда, в очень ограниченном варианте.
Причин тому несколько:
- быстрое прототипирование — при всем моем уважении к языку Си, на котором, в основном, производится разработка для микроконтроллеров, назвать его лаконичным очень сложно. Языки высокого уровня позволяют писать меньше кода и упрощают внесение изменений в уже написанное, что на стадии создания прототипа очень важно;
- автоматическое управление памятью и абстрагирование механизмов сложных вычислений — думаю, не нуждается в комментариях, оба процесса в ручном исполнении при достаточном объеме проекта превращаются в источник большого количества головной боли;
- упрощение отладки и тестирования — интерпретируемый код проще проверить на рабочей станции до момента полевых испытаний;
- борьба со сложностью — зачастую, большая производительность порождает естественное прагматичное желание запихнуть на устройство побольше предварительной обработки и анализа, что отнюдь не добавляет простоты в разработке.
Увы, за все удобства приходится платить. В данном случае цена за удобства — самые ценные ресурсы, производительность и размер кода (во многих случаях приходится носить с собой довольно объемную среду исполнения). Поэтому полевое применение языков высокого уровня в SoC и SoM — штука неоднозначная и, местами, компромиссная.
Мы используем для разработки язык Erlang, применяя его как по прямому назначению (создание серверных приложений и control plane), так и весьма непривычно — web-приложения. Поэтому идея использовать инфраструктуру этого языка для создания IoT-решений возникла задолго до появления плат, на которых среда выполнения Erlang-а могла бы работать без проблем.
Причин использовать Erlang было множество, самые весомые:
- Erlang очень удобен для разбора и создания бинарных последовательностей. Сопоставление с образцом (pattern matching) в паре с обработкой битовых данных позволяет реализовывать бинарные протоколы весьма быстро и немногословно;
- отсутствие глобальных переменных и иммутабельность для большинства данных — позволяет писать и, что не менее важно, поддерживать надежные приложения, в котором сложно случайно поменять что-то не то;
- легкие процессы с обработкой сообщений, очень похожие на то, с чем разработчикам встраиваемых систем приходится иметь дело. По сути они представляют собой процедуру инициализации и процедуру, которая в бесконечном цикле обрабатывает входящие сообщения, изменяя внутреннее состояние. Очень похоже на Arduino, только процессов может быть много и работают они параллельно, кроме того, в промежутке между сообщениями процедуру обработки можно поменять на лету (hot code reload), что бывает весьма удобно в случае, когда надо исправить незначительные ошибки или скорректировать поведение;
- изолированное окружение и автоматическое выделение памяти, думаю, в пояснении не нуждаются;
- кросс-платформеность — байт-код для среды выполнения ERTS может быть собран на машине разработчика, а затем без проблем перенесен на целевое устройство;
- отличные средства интроспекции — возможность подключиться к работающему приложению по сети и посмотреть, что оно так подтормаживает, нередко бывает очень полезно.
Первой рабочей платой, на которой мы попробовали Erlang, была Carambola 2 литовских разработчиков 8devices, собранной на популярном чипе AR9331. Первая версия этой платы, увы, имела недостаточный объем flash-памяти, чтобы поместить среду выполнения. А вот вторая версия уже вполне себе позволяла вместить как ERTS, так и небольшое приложение.
Установка производилась классическим для такого рода устройств методом — сборкой образа OpenWRT, содержащего Erlang, с последующей прошивкой его в flash-память устройства. Первый запуск среды, увы, привел к разочарованию — все повисло. Причины этого я уже рассказывал на конференции InoThings 2018, но, увы, как потом оказалось, ввел коллег в заблуждение, ошибочно назвав источник такого поведения.
Вкратце перескажу. При работе с файлами виртуальная машина ERTS использует тип off_t, размер которого в дистрибутиве вычисляется при автоконфигурировании сборки (если она идет на целевой платформе) или же подставляется из среды кросс-компиляции, как то и случилось в случае OpenWRT. Непонятно почему, но в настройках для процессоров MIPS и производных в файле конфигурации сборки стоит вдвое больший размер, чем есть по факту. Проблемы бы не возникало, если бы код виртуальной машины использовал не директивы препроцессора типа
#if SIZEOF_OFF_T == 4
а банальную проверку в коде (подозреваю, что итоговый результат компиляции был бы одинаковым, но проверять не хватило запала):
if (sizeof(off_t) == 4) {
В результате собранная на первой итерации ERTS при попытке прочитать при старте файл ~/.erlang.cookie (своеобразный пароль для идентификации при сетевом взаимодействии) благополучно получал мусор в старших разрядах и с треском зависал.
Исправляющий патч и пакет с предпоследней версией ERTS для OpenWRT можно скачать с GitHub. Помимо этого проблем пока не наблюдалось, все работало как и ожидается.
Второй аппаратной платформой, на которой мы попробовали Erlang, стал конструктор LinkIt Smart 7688 от компаний Mediatek и SeeedStudio, специально созданный для быстрого прототипирования и изучения основ. Эта плата просто апофеоз разврата в плане ресурсов — подросшая в полтора раза частота MIPS-ядра, больше оперативной памяти (для ERTS это важно, GC не дремлет) и больше flash-памяти, а также наличие MicroSD-карты и возможности использования со-процессора Atmel Atmega 32U4 в версии Duo для работы с периферией.
В общем, платформа очень даже подходила для продолжения банкета, а наличие дополнительных подключаемых устройств, соединяющихся без пайки, позволяет быстро и условно красиво собрать на коленке стенд для испытаний.
В комплекте с платформой идет ПО с собственным web-интерфейсом, а также с Python и NodeJS-библиотеками для разработки. Экосистема для сборки не изменилась — это, по-прежнему, OpenWRT. Если по каким-то причинам вы посчитаете лишним все это многообразие, то на вышеупомянутом репозитории есть пакеты, содержащие минимальный набор обязательных компонентов. После сборки образ flash-памяти записывается на устройство и после перезагрузки можно смело пользоваться REPL-ом.
Для построения приложений на Erlang-е для IoT необходимо решить один архитектурный вопрос.
Язык проектировался и разрабатывался с прицелом на то, что будет служить control plane, т.е. управляющим слоем, в то время как работать с железом предполагалось посредством FFI. Для этого предусмотрены три типа взаимодействия:
- порты (ports) — отдельно работающие процессы, написанные на любом языке, взаимодействие с которыми происходит через потоки ввода/вывода. Могут перезапускаться в случае падения, но из-за метода взаимодействия их производительность в плане коммуникации невелика (впрочем, нам будет достаточно);
- NIF-функции — выглядят как стандартные функции языка, но их вызов порождает выполнение скомпилированного кода в пространстве среды исполнения. В случае ошибки могут утянуть за собой виртуальную машину целиком.
- C-node — когда работа целиком производится в отдельном процессе, а взаимодействие осуществляется как с отдельно запущенной средой выполнения по сети. Этот вариант мы рассматривать не будем в силу достаточно больших накладных расходов в рамках одного слабого устройства.
Дилемма заключается в следующем — мы можем вынести в FFI только транспортные вещи (т.е. поддержку GPIO, I2C, SPI, PWM, UART и т.д.), а взаимодействие непосредственно с датчиками и прочими устройствами реализовать на Erlang-е, или же, наоборот, вынести драйвера устройств целиком в код внешних модулей, оставив приложению получение сырых данных и их обработку, в этом случае, возможно, имеет смысл воспользоваться уже написанным кодом.
Мы решили использовать первый вариант. Причин тому несколько:
- потому что можем;
- как я уже упоминал, Erlang имеет в комплекте достаточно мощные средства для сборки и разборки бинарных последовательностей, при этом достаточно простую математику, позволяющую не заботится о защите от переполнений и прочей магии, применяемой для обработки результатов. Не то, чтобы эта магия пугала, но на неофитов она действует шокирующе;
- интуитивно казалось, что драйвера на языке высокого уровня будут проще и надежнее (упавший процесс перезапустит супервизор, что приведет к реинициализации контролируемого устройства).
Поэтому довольно быстро была найдена библиотека ErlangALE, которая содержала в себе уже реализованную поддержку GPIO, I2C и SPI через интерфейсы ядра, а о них, в свою очередь, уже позаботились разработчики аппаратной платформы. Библиотека для работы с UART у нас уже была проверенная, плюс довеском подключили erlexec — приложение, позволяющее создавать и управлять процессами ОС.
Все перечисленные приложения использовали порты (отдельно запускаемые бинарные процессы) для работы с оборудованием и ОС, что требовало поддержки кросс-компиляции для языков С и С++, для чего был написан достаточно вычурный shell-скрипт, конфигурирующий среду сборки на применение нужных компиляторов.
Для тестирования принятых решений мы собрали простое устройство из LinkIt Smart 7866, двух I2C устройств (датчика температуры и давления BMP280 и OLED-дисплея 128 на 64 точки) и USB GPS-модуля, отдающего данные по UART. GPIO был проверен на светодиоде на плате, он работает, а SPI-дисплей подключать показалось ненужным на данном этапе усложнением.
Получилось достаточно компактное и простое приложение, исходный код можно посмотреть на Github-е.
Я не буду углубляться в фрагменты кода, а постараюсь обзорно описать, как работает приложение.
Драйвера всех устройств выполнены в виде gen_server-процессов, это удобно, потому что позволяет складывать в состояние дополнительные параметры и, иногда, состояние устройства. Последнее можно видеть на примере uart_gps — данные с UART-а приходят асинхронно, разбираются парсером NMEA0183 и результаты записываются в состояние процесса, откуда достаются по запросу.
Основной цикл приложения описан в модуле gps_temp_display — ежесекундно процесс вычитывает данные GPS и запрашивает состояние температуры и давления у BMP280 и выдает их на OLED-дисплей. Ради интереса можно посмотреть на драйвера дисплея и датчика BMP280 — все получилось достаточно лаконично, 150-170 строк на модуль.
В общем и целом на вышеуказанную задачу (написание кода драйверов, объединение все в одно приложение, сборка и тестирование) ушло порядка четырех вечеров по два часа в среднем, т.е. строго говоря — пара рабочих дней. Как мне лично кажется, это хороший показатель, чтобы попробовать применить Erlang в более сложных и серьезных встраиваемых приложениях, не требующих строгих ограничений реального времени.
Разумеется, наши попытки применения Erlang-а для встраиваемых систем отнюдь не единственные. Существует несколько интересных проектов на этот счет:
- nerves-project.org — нацелен на создание встраиваемой экосистемы на базе Elixir;
- www.grisp.org — еще более радикальный подход, предполагающий запуск ERTS напрямую на железе (bare metal Erlang system);
- github.com/bettio/AtomVM и github.com/cloudozer/ling — попытки создать компактную версию ERTS, пригодную для применения в микроконтроллерах и слабых SoC/SoM.
Комментарии (37)
afiskon
16.07.2018 12:40Спасибо, интересно. Скажите, а что с энергопотреблением?
mkrentovskiy Автор
16.07.2018 12:43Замеры, увы, не делал.
Была шальная мысль взять 18650 и покататься по городу, посмотреть, насколько хватит. Пока не воплотилась.afiskon
16.07.2018 12:46Зачем так сложно. Мультиметр в режиме амперметра включаем последовательно с источником питания, умножаем ток на напряжение и получаем ватты.
mkrentovskiy Автор
16.07.2018 12:52+1Ну, я, конечно, дурак, но не настолько, чтобы не знать, как это делается. :)
Про батарейку мысль была исключительно с экспериментальной точки зрения, ради посмотреть.
mkrentovskiy Автор
16.07.2018 13:35+1Померял. 220-250 мА с пиками до 270 мА. Но это со включенным WiFi.
vvzvlad
16.07.2018 14:49И это ничего не дает, если у нас внутри приложение, которое хз как работает и иногда уходит в сон.
latonita
16.07.2018 15:14укажите, пожалуйста, минимальные требования к flash и ram железки, чтобы вообще запуститься и какой объем flash/ram занимает ваше приложение, как пример. спасибо
mkrentovskiy Автор
16.07.2018 15:50Можно танцевать от Carambola 2 — 16 MB Flash and 64 MB DDR2 RAM. Основная беда — ERTS весит почти 10 МБ в пакетах, т.е. флешку оно ест хорошо. Само приложение весит 2 МБ.
По оперативной памяти — у нас иммутабельный язык с GC, т.е. чем больше, тем лучше, но в теории на 32 МБ должно все запускаться и работать.
У меня в статье только MIPS-устройства, но на ARM-овых SoC проблем быть не должно, т.е. можно брать Raspberry Pi, Orange Pi, Banana Pi и т.п.erlyvideo
17.07.2018 09:40угу.
Меньше 10 мегабайт — надо писать код, который будет раздирать сами исходники бимов и выбрасывать то, что не используется.
Т.е. надо не просто каждый файл проверять глазами, а каждую строчку кода.
Ты debug_info то вырезал?
jonic
16.07.2018 17:28+1Если вдруг кому то покажется что Linkit дороговат, то есть отличные модули с таким же процессором (да все тоже самое), они на базе mt7688 и отлично находятся на али и заводится с пол оборота.
Googlist
16.07.2018 20:13Linkit 18 баксов, а на али что-то ценьі на голую плату без ног и атмеги 14 евро. Есть волшебньій поиск где дешевле?
jonic
16.07.2018 20:49+1есть, вводим mt7688 и смотрим где отправляют лотами у меня выходило порядка 6-7 EUR за штуку с учетом быстрой доставки. Что бы не сочли жуликом:
Скриншотser-mk
17.07.2018 00:14Пробовали запускать ту плату что на картинке у вас? Подойдет ли SDK от linkit?
mkrentovskiy Автор
17.07.2018 00:25Да, прикольно. Только там флешка на 8 МБ по умолчанию. С OpenWRT потренироваться хватит, а вот то, что я описал — уже нет.
jonic
17.07.2018 00:47Разные производители модулей — разные блоки памяти, цена особо не варьируется от этого. Которые я заказывал ровно с такой же по размеру флеш памятью. К тому же, никто не запрещает подключить sd карту по spi, заводится в пару команд в openwrt, можно даже смонтировать как rootfs
mkrentovskiy Автор
17.07.2018 08:51Ну, это понятно, я просто упредил, что именно в данном случае так по умолчанию.
n12eq3
17.07.2018 08:26добрый день. можно ли описать пример(ы), где востребована подобного уровня начинка? для термометра/трекера/погодной станции — великовато и много ест. для сложного есть малины, апельсины и бананы + внешние «шилды» либо отдельные платы сопряжения.
есть ли реальные случаи, где протестированное железо пошло в использование (не важно, массовое или нет), именно в том виде, в котором «отлаживалось»?mkrentovskiy Автор
17.07.2018 08:58Ну, если учесть, что описанный подход заработал неделю назад, то ответ будет — пока нигде. Планы имеются, но пока рано говорить что-то конкретное.
Ранее я применял Erlang в таких устройствах для отображения картины проходящего трафика и прототипа сигнализации с ретрансляцией видео с камер и получением показателей с кучи датчиков и детекторов.
QDeathNick
18.07.2018 13:49Можно у вас проконсультроваться, хоть и не совсем по теме?
Есть задача прокинуть удалённо звук, хочу тоже на openwrt это сделать, и вот вопрос возник по поводу железа, у меня есть TL-MR2030 ver1.9, c 4мя мегабайтами памяти, думаете его хватит для такой задачи или надо что-то более серьёзное?
Я только начинаю разбираться, но стало понятно, что надо собирать свою сборку в Image Builder, так как на готовых не поставить совершенно ничего дополнительного.mkrentovskiy Автор
18.07.2018 14:55В теории должно хватить. Звуковые утилиты не сильно прожорливые, можно повыкидывать кучу сетевого софта, если не собираетесь его использовать как роутер.
QDeathNick
18.07.2018 15:48Как роутер не нужен, а вот модем должен работать.
А повыкидывать можно только через Image Builder? Ничего попроще нет?mkrentovskiy Автор
18.07.2018 16:29Можно попробовать минимальный образ с сайта OpenWRT, а утилиты и модули ядра доставить пакетами.
QDeathNick
18.07.2018 16:32А минимальный образ он же всё равно к каждому железу свой?
mkrentovskiy Автор
18.07.2018 16:48Ага. Но они уже собранные на сайте OpenWRT.
downloads.lede-project.org/releases/17.01.5/targets/ar71xx/generic/lede-17.01.5-ar71xx-generic-tl-mr3020-v1-squashfs-sysupgrade.bin
ser-mk
18.07.2018 15:39Можно и USB флешку подключить.
QDeathNick
18.07.2018 15:58Вы наверное мне ответили. Я то подключил, но примонтировать её не удаётся, не хватает памяти. Видимо надо совсем по другому организовать загрузку прошивки.
JC_IIB
Я так понял (отсюда) что там все-таки не trueЪ bare-metal Erlang, а есть RTEMS.
А так все конечно очень круто. Статья интересная, спасибо, побольше бы таких!
mkrentovskiy Автор
Ага, обвязки они понатаскали. В свое время тоже думалось за железку, в которой после ядра Линукса сразу init-процессом запускается ERTS и поднимает все остальное.