Зачем нужен микроконтроллер? Например, чтобы устроить дома пивоварню. Если своего пивного заводика мало, то можно и что-то масштабнее: построить квест-комнату, оформить презентацию, интерактивный фонтан, который рисует картину каплями, или выставочный стенд для большой компании. С микроконтроллером можно сделать что угодно — все зависит от фантазии.
Есть заблуждение, что для создания своих железок требуется знать ассемблер, C/C++, уметь управлять памятью и глубоко понимать электричество. Когда-то так и было, но технологии развиваются и сейчас для полноценной реализации своего проекта достаточно только JS!
Ок, JavaScript мы знаем, а как соединить его с железом? Какое вообще бывает железо и что умеет? Как настроить всю систему? В расшифровке доклада Виктор Накорякова на FrontendConf узнаем: как, с помощью одного лишь JS, управлять сервоприводами, как физически объединить систему с PC, и о вариантах коммуникации приложения на JS. Обсудим пакеты serialport и Firmata, serialport с самописной прошивкой на C++, Espruino и программирование контроллера прямо на JS, Raspberry Pi, HTTP в локальной сети и HTTP и MQTT через облако.
Виктор Накоряков (nailxx) — технический директор, сооснователь компании «Амперка». Любит передовые технологии разработки, функциональное программирование и physical computing. «Амперка» производит и продает электронные модули, чтобы непрофессионалы создавали умные устройства своими руками, обучающие наборы и отдельные строительные кубики, которые можно добавлять к своему устройству — моторы, GPS, SMS.
Куда писать JavaScript
В Espruino — автономный микроконтроллер с JavaScript. Платформа Espruino позволяет писать JS прямо в микроконтроллер. Это автономная вещь в себе: подключили к компьютеру, прошили, и дальше работает самостоятельно.
В Raspberry Pi — маленький компьютер с GPIO.
В веб-приложение — на фронтенд или бэкенд.
Работу системы покажу на примере лягушки. Заходите с телефона на toad.amperka.ru — появится нехитрый веб-интерфейс.
Левая колонка управляет левым глазом, правая — правым. Принцип простой — нажал на кнопку, сервомотор крутит глаз.
Видео демонстрации стенда с лягушкой во время доклада.
Как работает лягушка
При открытии toad.amperka.ru, вы получаете статичную веб-страницу со статичным JS, который использует библиотеку MQTT.js. Эта библиотека связывается с брокером MQTT в облаке.
Если знакомы Redis, Publish/Subscribe, RabbitMQ или другие очереди сообщений, то сразу поняли о чем речь. MQTT — это брокер сообщений Machine-to-Machine. Легковесный и простой, чтобы даже слабые железки могли им пользоваться.
Для MQTT требуется брокер на сервере. Но вам даже не нужно его поднимать — арендуйте. За пару долларов в месяц у вас будет свой собственный MQTT-брокер. Бэкенд не нужен.
С другой стороны от брокера MQTT находится контроллер. Существует много разных устройств с этой ролью, например, Wi-Fi Slot. Это контроллер, который умеет соединяться с интернетом.
Он работает на базе популярного чипа ESP8266. К чипу добавлена возможность питания через микро-USB и подключение внешней периферии через тройные контакты для соединения с другими устройствами.
Как программировать железо на JS
Ничего необычного, JS обычный — почти полный ES6. В стандартную библиотеку добавлены некоторые функции и объекты для работы на низком уровне с электрическими сигналами. Например, функции для простого цифрового считывания и записи.
digitalRead — цифровое считывание. Это вопрос контроллеру: «Есть три вольта на пине №4?». Если напряжение есть, он вернет TRUE, если нет — FALSE. Так реализуется считывание простых бинарных датчиков: кнопок, переключателей, герконовых замков и инфракрасных датчиков движения.
digitalWrite — простая цифровая запись. Говорим digitalWrite TRUE — с pin уходит 3V. Говорим 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». Чудо!
Чудо в том, что при нажатии кнопок, цифра «1», знак «+» и следующая единичка ушли по кабелю на контроллер. При нажатии «ENTER», выражение исполнилось внутри микроконтроллера, а не компьютера. Микроконтроллер получил результат «2» и вернул его обратно по кабелю на компьютер. На мониторе высветилось «2».
JavaScript исполняется внутри железа.
Кроме развлечений с арифметикой, можно крутить моторы. Понадобится функция «analogWrite», которая посылает квадратную волну. Говорим на какой пин выдавать волну. Например, у меня на плате он подписан как A7. Затем указываем длительность — например, 1300 мкс из 20 000 мкс будем подавать единицу. Также требуется опция, которая задает частоту этого пинания — 50 раз в секунду, это и есть 20 000 мкс.
>analogWrite(A7, 1300 / 2000, {freq: 50}}
=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);
.
Дальше публикуем на топик, который сформировали тоже на основе data-attribute. Это весь наш JS-код в браузере.
Код на плате
Когда стартует Wi-fi Slot, он подписывается на интересующие его топики и принимает данные. Когда они приходят, слот реагирует и заставляет работать моторы.
Код на плате условно разбит на несколько частей. Для начала мы подключаем библиотеки. В частности, это как раз библиотека, которая управляет Servo, чтобы не вспоминать детали. Они находятся в скоупе «amperka».
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 можно делать шикарные вещи — на фонтан хватит.
Не всё так просто с NPM. Эта проблема относится к Espruino IDE. Если пишем «require», Espruino смотрит в одном месте, в другом, и если не нашла — производит fallback на NPM. В этот момент может тормозить. Espruino не всегда может разобраться со сложными пакетами. Ее мощь в этом смысле гораздо ниже, чем у того же Webpack или Parcel. Это боль, но если вы настроите toolchain самостоятельно, с пониманием того, что происходит внутри железа, то проблемы нет.
Ассортимент железа. Не каждая железка, которая называется платформой для разработки, потянет Espruino. По меркам мира микроконтроллеров, Espruino прожорлива — ей нужно от 500 Kb оперативной памяти. Такую память даст не любая железка. У канонических Arduino Uno или Arduino Nano всего 2 Kb RAM — поэтому там нельзя. Работать с Espruino возможно на железе, которое делаем мы и на официальном железе от Espruino.
Espruino — это компания одного человека, который часто выходит на Kickstarter с новыми продуктами и всегда успешно проводит сбор. Если хотите поддержать платформу — покупайте официально.
Есть третий вариант — взять достаточно мощный, но сторонний devboard. Например, у компании ST, которая производит «Nuclear» и «Discovery». Железо с надписью 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 как о мобильном устройстве. От аккумулятора она протянет недолго — счет идет на часы. Чтобы устройство работало полгода или год, требуется грамотная программа для микроконтроллера и комплект батареек.
Бедный GPIO — general-purpose input/output. Фичи подачи волн, считывание волн, работа с аналоговыми сигналами у Raspberry Pi гораздо слабее, чем у микроконтроллеров, для которых это основная задача. Например, из коробки на Raspberry Pi не удастся управлять сервоприводом в аппаратном режиме.
Ограничение условно, потому что его можно обойти расширением. Например, железкой Troyka Cap, которая берет все низкоуровневое управление работы с сигналами на себя. Raspberry Pi общается с ней с помощью высокоуровневого пакета. Отдает команды покрутить сервопривод и он работает. С помощью таких плат расширения можно подключать все, что угодно.
Arduino
Можно взять обычную Arduino, которая всем надоела. Но JavaScript придется перенести куда-нибудь, что может крутить Node JS — старый компьютер или та же Raspberry Pi.
Соединяем старый ненужный компьютер с Arduino через USB-кабель. В Arduino один раз заливаем стандартную прошивку Firmata. Она превращает Arduino в зомби, который выполняет указания мастера — старого компьютера. Мастер говорит передать на такой-то пин такой-то сигнал, и Arduino передает.
Для коммуникации используются библиотеки с NPM — SerialPort и Firmata с понятным API. Так вы опять в мире JS, пишете высокоуровневую программу, которую отправляете на свой сервер. Работу с сигналами доверяете Arduino, и она ее исполняет.
Не всегда с Firmata получится управлять всем. Она способна обеспечить взаимодействие с тем железом, для которого предназначена: сервоприводы, светодиодные ленты, медиа-контроллеры. Но если возможность считывать вполне конкретный гироскоп или акселерометр не заложена — не подойдет. Тогда придётся на обычную Arduino писать программу на C.
Но это еще не все — если писать на C нет желания, воспользуйтесь Open Source инструментом XOD.io.
Это визуальная среда программирования, где граф, который вы строите, превращается в сишный код, и уже его можно компилировать и заливать. XOD.io позволяет людям, которые слабо знакомы с тонкостями и нюансами микроконтроллерного программирования, быстро создавать простые программы. Если вы выросли из Firmata, но пока не чувствуете сил писать на низком уровне — используйте XOD.io.
Следующая конференция FrontendConf пройдет в октябре. Знаете о JavaScript такое, чего не знает большинство фронтенд-разработчиков, разрабатываете интерфейсы, которыми удобно пользоваться или нашли грааль производительности и готовы рассказать, где он находится, — ждем заявки на доклады.
Интересные доклады в программе, новости конференции, видео и статьи собираем в регулярную рассылку — подписывайтесь.
Автор: Глеб Михеев