company_banner

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

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

    Есть заблуждение, что для создания своих железок требуется знать ассемблер, 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 такое, чего не знает большинство фронтенд-разработчиков, разрабатываете интерфейсы, которыми удобно пользоваться или нашли грааль производительности и готовы рассказать, где он находится, — ждем заявки на доклады.

    Интересные доклады в программе, новости конференции, видео и статьи собираем в регулярную рассылку — подписывайтесь.
    Конференции Олега Бунина (Онтико)
    759,83
    Конференции Олега Бунина
    Поделиться публикацией

    Комментарии 11

      0
      Как-то ставил другу на малинку веб сервер. Работало на ура, однако с gpio так и не нашел подходящей библиотеки — js не тянул по скорости переключения пинов. Зато питон справился, а связал их как раз через MQTT.
        0

        Может кто знает как в Espruino подключить модуль из другого модуля, лежащего в стандартной папке modules? Почему-то не получается использовать ни require по умолчанию, ни относительные пути… При заливке кода на чип — модуль не найден...

          –1
          Интересно, а есть в IT такое место, куда еще не добрался этот долбаный JS?
            +3
            Так в эмбед и не добрался. Ну помигают на нем светодиодами и успокоятся, 90% задач на нем не возможно решить в принципе.
              +1
              Так в эмбед и не добрался.

              Эх, если бы…

              Ну помигают на нем светодиодами и успокоятся, 90% задач на нем не возможно решить в принципе.

              Тут, конечно, смотря что считать эмбедом, но, например, интерфейс автомобильной медиасистемы уже вполне может быть наляпан на HTML+JS. Или меню телевизоров, судя по тормозам, тоже.

              Конечно, впихнуть интерпретатор в attiny будет сложновато, но вот в arm вполне реально, и, уверен, что уже впихнули. Боюсь самое страшное еще впереди.
                0
                Микропитон лучше, или только хардкор C/Rust?
                +2
                Мне больше интересно, как JS собирается управлять двигателями с *микросекундной* точностью.
                  0

                  Наверное, само управление написано на С, а js только вызывает нужное

                    +1
                    Тогда можно на STM32 поднять примитивный веб-сервер, написать статическую страничку с html + css + кнопкой и написать статью «Как поговорить с микроконтроллером из HTML»
                0
                (del)
                  0
                  Как поговорить с микроконтроллером из JS

                  Поговорите с ним через Node-RED, а на самом контроллере оставьте родной код. Лучший вариант.

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

                  Самое читаемое