Хабрахабр

Как поговорить с микроконтроллером из JS

Зачем нужен микроконтроллер? Например, чтобы устроить дома пивоварню. Если своего пивного заводика мало, то можно и что-то масштабнее: построить квест-комнату, оформить презентацию, интерактивный фонтан, который рисует картину каплями, или выставочный стенд для большой компании. С микроконтроллером можно сделать что угодно — все зависит от фантазии.

Когда-то так и было, но технологии развиваются и сейчас для полноценной реализации своего проекта достаточно только JS! Есть заблуждение, что для создания своих железок требуется знать ассемблер, C/C++, уметь управлять памятью и глубоко понимать электричество.

Какое вообще бывает железо и что умеет? Ок, JavaScript мы знаем, а как соединить его с железом? В расшифровке доклада Виктор Накорякова на FrontendConf узнаем: как, с помощью одного лишь JS, управлять сервоприводами, как физически объединить систему с PC, и о вариантах коммуникации приложения на JS. Как настроить всю систему? Обсудим пакеты serialport и Firmata, serialport с самописной прошивкой на C++, Espruino и программирование контроллера прямо на JS, Raspberry Pi, HTTP в локальной сети и HTTP и MQTT через облако.

Любит передовые технологии разработки, функциональное программирование и physical computing. Виктор Накоряков (nailxx) — технический директор, сооснователь компании «Амперка». «Амперка» производит и продает электронные модули, чтобы непрофессионалы создавали умные устройства своими руками, обучающие наборы и отдельные строительные кубики, которые можно добавлять к своему устройству — моторы, GPS, SMS.

Куда писать JavaScript

В Espruino — автономный микроконтроллер с JavaScript. Платформа Espruino позволяет писать JS прямо в микроконтроллер. Это автономная вещь в себе: подключили к компьютеру, прошили, и дальше работает самостоятельно.

В Raspberry Pi — маленький компьютер с GPIO.

В веб-приложение — на фронтенд или бэкенд.

Заходите с телефона на toad.amperka.ru — появится нехитрый веб-интерфейс. Работу системы покажу на примере лягушки.

Принцип простой — нажал на кнопку, сервомотор крутит глаз. Левая колонка управляет левым глазом, правая — правым.

Видео демонстрации стенда с лягушкой во время доклада.

Как работает лягушка

При открытии toad.amperka.ru, вы получаете статичную веб-страницу со статичным JS, который использует библиотеку MQTT.js. Эта библиотека связывается с брокером MQTT в облаке.

MQTT — это брокер сообщений Machine-to-Machine. Если знакомы Redis, Publish/Subscribe, RabbitMQ или другие очереди сообщений, то сразу поняли о чем речь. Легковесный и простой, чтобы даже слабые железки могли им пользоваться.

Но вам даже не нужно его поднимать — арендуйте. Для MQTT требуется брокер на сервере. Бэкенд не нужен. За пару долларов в месяц у вас будет свой собственный MQTT-брокер.

Существует много разных устройств с этой ролью, например, Wi-Fi Slot. С другой стороны от брокера MQTT находится контроллер. Это контроллер, который умеет соединяться с интернетом.

К чипу добавлена возможность питания через микро-USB и подключение внешней периферии через тройные контакты для соединения с другими устройствами. Он работает на базе популярного чипа ESP8266.

Как программировать железо на JS

Ничего необычного, JS обычный — почти полный ES6. В стандартную библиотеку добавлены некоторые функции и объекты для работы на низком уровне с электрическими сигналами. Например, функции для простого цифрового считывания и записи.

Это вопрос контроллеру: «Есть три вольта на пине №4?». digitalRead — цифровое считывание. Так реализуется считывание простых бинарных датчиков: кнопок, переключателей, герконовых замков и инфракрасных датчиков движения. Если напряжение есть, он вернет TRUE, если нет — FALSE.

Говорим digitalWrite TRUE — с pin уходит 3V. digitalWrite — простая цифровая запись. С помощью этого простого принципа можно зажечь/погасить светодиодную ленту или запустить ядерную ракету. Говорим digitalWrite FALSE — 0V. Мы посылаем слабый сигнал на реле, оно коммутирует большую нагрузку и ракета полетела.

Также есть функции для работы с промежуточными значениями между 0 и 3V:

  • analogRead;
  • analogWrite;
  • setWatch;
  • digitalPulse.

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

Если в вебе нам понятны и знакомы HTTP, WebSocket, TCP, то для микроконтроллера это: Дальше идут объекты для работы с интерфейсами, которые приняты в микроконтроллерном мире.

  • Serial — последовательный порт;
  • шина I2C;
  • шина SPI;
  • шина OneWire.

Как пинать сервомотор

Для примера расскажу, как управлять мотором, который стоит в гипножабе. Протокол мотора простой. На контрольный пин подается 0V — нижняя граница. Раз в 20 мкс его нужно пинать, давая стабильную единичку — 3V, а через некоторое время — сбрасывать до 0.

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

Как программировать

Программирование платформы Espruino производится в одноименной среде Espruino IDE. Плата микроконтроллера подсоединяется к компьютеру микро-USB кабелем. Единственное, придется поставить драйвер, что занимает 1,5 минуты. Драйвер ставится для MAC и Windows, а на Linux всё работает из коробки.

Она мигает светодиодом раз в секунду: Вот пример программы, которая загружается в среду одной кнопкой.

var on = false;
setInterval(function() { on = !on; LED1.write(on);
},500);

Слева в среде находится REPL-интерпретатор. Вводим «1+1». Программа выдаёт ответ «2». Чудо!

При нажатии «ENTER», выражение исполнилось внутри микроконтроллера, а не компьютера. Чудо в том, что при нажатии кнопок, цифра «1», знак «+» и следующая единичка ушли по кабелю на контроллер. На мониторе высветилось «2». Микроконтроллер получил результат «2» и вернул его обратно по кабелю на компьютер.

JavaScript исполняется внутри железа.

Кроме развлечений с арифметикой, можно крутить моторы. Понадобится функция «analogWrite», которая посылает квадратную волну. Говорим на какой пин выдавать волну. Например, у меня на плате он подписан как A7. Затем указываем длительность — например, 1300 мкс из 20 000 мкс будем подавать единицу. Также требуется опция, которая задает частоту этого пинания — 50 раз в секунду, это и есть 20 000 мкс.

>analogWrite(A7, 1300 / 2000, }
=undefined
>

Перевалим за 1500 — заставим крутиться в другую сторону с большей скоростью.

>analogWrite(A7, 2300 / 2000, {freq: 50}}
=undefined
>

Или скажем остановиться.

>digitalWrite(A7, 0)
=undefined
>

Используя те же функции, можно написать целую программу, которая в зависимости от внешних факторов — нажатия кнопок, показаний сенсоров — выполнит то, что вам хочется.

Библиотеки

Не всегда удобно вспоминать детали реализации протокола. Поэтому для всевозможного железа создана масса библиотек, от маленьких до гигантских. Они содержат все технические особенности. В библиотеках используются понятные методы: считать данные с nfc-метки, прочитать концентрацию углекислого газа в промилле, или отправить сообщение в Telegram.

  • servo.write;
  • barometer.init;
  • barometer.read;
  • barometer.temperature;
  • nfc.listen;
  • nfc.on('tag', ...);
  • nfc.readPage;
  • nfc.writePage;
  • relay.turnOn;
  • relay.turnOff;
  • gas.calibrate;
  • gas.read('CO2');
  • telegram.sendMessage.

Полезно понимать низкоуровневые команды, но, чтобы начать творить — знать не обязательно.

Код на клиенте

Итак, когда нажимаем одну из кнопок левого или правого столбика в нашей лягушке, на топик toad/eye/left или right отправляется значение, которое мы хотим установить на соответствующий сервопривод.

<button class="toad__eye__control" data-queue="left" data-payload="1300"> -1
</button>

1300 — это длительность импульса. Откуда берется left и 1300? Я их просто добавил в HTML в виде data-attributes.

В JS мы пишем простой код.

import mqtt from 'mqtt'; const client = mqtt.connect(`ws://${location.hostname}:9001`); function onEyeControlClick() { const { queue, payload } = this.dataset; client.publish(`toad/eye/${queue}`, payload);
} document.querySelectorAll(".toad__eye__control") .forEach(e => e.addEventListener('click', onEyeControlClick));

Разберем код по частям. На старте подключаемся к брокеру, который по умолчанию работает на порту 9001: const client = mqtt.connect(`ws://${location.hostname}:9001`);.

При нажатии на любую из кнопок публикуем новый message с payload, который достали из data-attribute: client.publish(`toad/eye/${queue}`, payload);.

Это весь наш JS-код в браузере. Дальше публикуем на топик, который сформировали тоже на основе data-attribute.

Код на плате

Когда стартует Wi-fi Slot, он подписывается на интересующие его топики и принимает данные. Когда они приходят, слот реагирует и заставляет работать моторы.

Для начала мы подключаем библиотеки. Код на плате условно разбит на несколько частей. Они находятся в скоупе «amperka». В частности, это как раз библиотека, которая управляет Servo, чтобы не вспоминать детали.

const ssid = "Droidxx";
const password = "****"; const brokerHostname = "toad.amperka.ru"; const leftEye = require("@amperka/servo").connect(A5); const rightEye = require("@amperka/servo").connect(A7).

Каждый может создавать и публиковать свои библиотеки. Мы сделали несколько десятков для наших собственных и других популярных модулей. Все Open Source — заходите, пользуйтесь.

Затем нам понадобятся библиотеки для работы с Wi-Fi и MQTT-брокерами.

const wifi = require("Wifi");
const mqtt = require("tinyMQTT").create(brokerHostname);

Здесь нет ОС, поэтому даже подключение к Wi-Fi — ручная операция. Забота о подключении лежит на вас, но это не так уж сложно. Чтобы подключиться к сети просто вызываем метод «connect», который в случае успеха или неудачи вызовет «callback» и сообщит об итогах операции.

wifi.connect(ssid, { password: password }, function(e) { if (e) { console.log("Error connecting:", e); wifi.disconnect(); } else { console.log("Wi-Fi OK, connecting to broker ..."); mqtt.connect(); }
});

Если все хорошо — подключимся к MQTT-брокеру.

При успешном подключении подпишемся на интересные топики, то есть на все, что начинается с toad/eye.

mqtt.on("connected", function() { mqtt.subscribe("toad/eye/*"); console.log("Connected to broker", brokerHostname);
});

Когда получаем какое-либо сообщение — разбираемся, куда оно пришло. Это очень похоже на простой разбор URL. В зависимости от топика решаем, на какой глаз будем влиять. Если получили что-то осознанное, то на этот глаз пишем то, что пришло в «payload» в микросекундах.

mqtt.on("message", function(msg) { const eye = (msg.topic === "toad/eye/left") ? leftEye : (msg.topic === "toad/eye/right") ? rightEye : null; if (eye) { eye.write(Number(msg.message), "us"); }
});

Вот и вся магия лягушки.

Ограничения Espruino JS

Мы пробежались по платформе Espruino. Крутить глаза лягушкам — не единственная ее опция. Но у платформы есть свои косяки, которые заставляют рассматривать другие варианты.

Есть ограничение по оперативной памяти. «Всего» 1–4 Mb RAM. Может показаться, что в эру, когда оперативка измеряется гигами, это мало. На код и data одновременно дается всего несколько мегабайт. На 2 Mb можно делать шикарные вещи — на фонтан хватит. Но для группы небольших устройств этого достаточно.

Эта проблема относится к Espruino IDE. Не всё так просто с NPM. В этот момент может тормозить. Если пишем «require», Espruino смотрит в одном месте, в другом, и если не нашла — производит fallback на NPM. Ее мощь в этом смысле гораздо ниже, чем у того же Webpack или Parcel. Espruino не всегда может разобраться со сложными пакетами. Это боль, но если вы настроите toolchain самостоятельно, с пониманием того, что происходит внутри железа, то проблемы нет.

По меркам мира микроконтроллеров, Espruino прожорлива — ей нужно от 500 Kb оперативной памяти. Ассортимент железа. Не каждая железка, которая называется платформой для разработки, потянет Espruino. У канонических Arduino Uno или Arduino Nano всего 2 Kb RAM — поэтому там нельзя. Такую память даст не любая железка. Работать с Espruino возможно на железе, которое делаем мы и на официальном железе от Espruino.

Если хотите поддержать платформу — покупайте официально. Espruino — это компания одного человека, который часто выходит на Kickstarter с новыми продуктами и всегда успешно проводит сбор.

Например, у компании ST, которая производит «Nuclear» и «Discovery». Есть третий вариант — взять достаточно мощный, но сторонний devboard. Железо с надписью STM32, скорее всего, потянет Espruino.

Первый — Raspberry Pi. Пробежимся по двум альтернативным вариантам.

Raspberry Pi

Это полноценный компьютер с Linux размером с визитку.

Если у вас в распоряжении Raspberry Pi — используйте следующую топологию устройства. Дополнительно есть пины ввода/вывода.

У меня дома есть умный телевизор и винчестер для бэкапа. Здесь облако опционально — мобильное устройство может подключаться непосредственно к устройству через hostname, API или систему автоматического определения устройству в сети. Они доступны со всех других устройств просто потому, что они находятся в том же LAN.

Ставите устройство на Raspberry Pi в сеть и строите клиент так, что он через HTTP или WebSocket непосредственно соединяется с ним, минуя посредников. Принцип тот же самый. Облако для своих целей использует само приложение на Raspberry Pi: протоколирует сенсоры или транслирует прогноз погоды.

Следующая возможная топология с полноценным бэкендом.

Его клиент — то же мобильное приложение, которое держит соединение через HTTP или WebSocket. В облаке поднимается сервер. С другой стороны соединение держится уже через Raspberry Pi, используя HTTP или тот же самый MQTT.

При этом всемирная доступность — устройство в Москве, а коммуникация с ним доступна из Владивостока. В таком подходе преимущество в полном контроле: валидация, авторизация, отказ клиентам.

Ограничения Raspberry Pi

Длинная загрузка. Raspberry Pi — полноценный компьютер и на старт требуется время. В качестве винчестера используется SD-карта. Даже самая быстрая карта все равно проигрывает обычным HDD, не говоря о SSD. По времени загрузка приближается к минуте.

В плане энергоэффективности, рассуждайте о Raspberry Pi как о мобильном устройстве. Следующий недостаток — это энергопотребление. Чтобы устройство работало полгода или год, требуется грамотная программа для микроконтроллера и комплект батареек. От аккумулятора она протянет недолго — счет идет на часы.

Фичи подачи волн, считывание волн, работа с аналоговыми сигналами у Raspberry Pi гораздо слабее, чем у микроконтроллеров, для которых это основная задача. Бедный GPIO — general-purpose input/output. Например, из коробки на Raspberry Pi не удастся управлять сервоприводом в аппаратном режиме.

Например, железкой Troyka Cap, которая берет все низкоуровневое управление работы с сигналами на себя. Ограничение условно, потому что его можно обойти расширением. Отдает команды покрутить сервопривод и он работает. Raspberry Pi общается с ней с помощью высокоуровневого пакета. С помощью таких плат расширения можно подключать все, что угодно.

Arduino

Можно взять обычную Arduino, которая всем надоела. Но JavaScript придется перенести куда-нибудь, что может крутить Node JS — старый компьютер или та же Raspberry Pi.

В Arduino один раз заливаем стандартную прошивку Firmata. Соединяем старый ненужный компьютер с Arduino через USB-кабель. Мастер говорит передать на такой-то пин такой-то сигнал, и Arduino передает. Она превращает Arduino в зомби, который выполняет указания мастера — старого компьютера.

Так вы опять в мире JS, пишете высокоуровневую программу, которую отправляете на свой сервер. Для коммуникации используются библиотеки с NPM — SerialPort и Firmata с понятным API. Работу с сигналами доверяете Arduino, и она ее исполняет.

Она способна обеспечить взаимодействие с тем железом, для которого предназначена: сервоприводы, светодиодные ленты, медиа-контроллеры. Не всегда с Firmata получится управлять всем. Тогда придётся на обычную Arduino писать программу на C. Но если возможность считывать вполне конкретный гироскоп или акселерометр не заложена — не подойдет.

Но это еще не все — если писать на C нет желания, воспользуйтесь Open Source инструментом XOD.io.

XOD.io позволяет людям, которые слабо знакомы с тонкостями и нюансами микроконтроллерного программирования, быстро создавать простые программы. Это визуальная среда программирования, где граф, который вы строите, превращается в сишный код, и уже его можно компилировать и заливать. Если вы выросли из Firmata, но пока не чувствуете сил писать на низком уровне — используйте XOD.io.

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

Интересные доклады в программе, новости конференции, видео и статьи собираем в регулярную рассылку — подписывайтесь.

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»