Прошлый учебный год я вёл занятия в школе робототехники. Класс состоял из подростков 12-13 лет, способных и дисциплинированных. В моих подопечных меня устраивало всё, кроме одного маленького нюанса поведения: они растворялись в своих смартфонах, стоило мне отвернуться к доске.

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

Надо было каким-то образом включить смартфоны в учебный процесс. И это удалось. У одного из учащихся была сломанная «роборука» meArm. Решено было её отремонтировать, а затем написать для неё web-интерфейс.

Сразу попрошу прощения за излишнюю в некоторых местах «разжёванность» материала. Как оказалось, некоторые очевидные для профессионалов моменты представляют неодолимые трудности для новичков.

▍ Ремонт манипулятора meArm


Манипулятор meArm достаточно популярен в мире. Многие его продают за деньги, но это изначально open source.

Необходимые ссылки:


На процессе установки библиотеки в среду Arduino IDE придётся остановиться особо.


Библиотека была скачана из репозитория как обычно, но установить её в Arduino IDE из zip-файла стандартным способом не удалось. Пришлось создать в папке libraries каталога установки Arduino IDE подпапку meArm и скопировать туда из скачанного архива файлы как на рисунке ниже:


В архиве также содержатся примеры с управлением манипулятором потенциометрами и джойстиками, но нам они не понадобятся.

«Роборуку» мы сначала разобрали, а затем собрали строго по инструкции. Серводвигатели подключили к Arduino Uno. Цепи питания серводвигателей «усилили» лабораторным блоком питания.

Проверка работоспособности манипулятора проводилась скетчем:

#include "meArm.h"
meArm arm;
void setup() {
  // подключение сервоприводов:
  // Base (центральный), Shoulder (правый), Elbow (левый), Gripper (захват)
  arm.begin (9, 10, 11, 12);
  delay (1000);
  arm.gotoPoint (0, 130, 0);   // начальное положение: вперед на 130 мм, на высоте оси
  delay (1000);
  arm.gotoPoint (-50, 130, 0); // влево на 50 мм, вперед на 130 мм, на высоте оси
  delay (1000);
  arm.gotoPoint (50, 130, 0);  // вправо на 50 мм, вперед на 130 мм, на высоте оси
  delay (1000);
  arm.gotoPoint (0, 130, 0);   // вперед на 130 мм, на высоте оси
  delay (1000);
  arm.gotoPoint (0, 200, 50);  // вперед на 200 мм, выше оси на 50 мм
  delay (1000);
  arm.openGripper ();          // открыть захват
  delay (1000);
  arm.gotoPoint (0, 200, -40); // вперед на 200 мм, ниже оси на 40 мм
  delay (1000);
  arm.closeGripper ();         // закрыть захват
  delay (1000);
  arm.gotoPoint (0, 130, 0);   // вперед на 130 мм, на высоте оси
}

void loop() {
}

Проверка показала, что манипулятор собран правильно, и управляется из библиотеки как надо. «Роборуку» мы, таким образом, отремонтировали, можно было переходить к разработке контроллера для управления meArm по web-интерфейсу.

▍ Разработка контроллера


Городить контроллер из Arduino Uno и внешнего модуля Wi-Fi было совсем неинтересно, в результате выбор пал на ESP32. По цене это сопоставимо, но решение на базе ESP32 более компактно и производительно. В качестве источника питания решили использовать три аккумулятора 18650.

На макетную плату был смонтирован понижающий преобразователь DC-DC, какой был в наличии, и отладочная плата с ESP32-WROOM на борту. Схема подключения оборудования контроллера к meArm приведена ниже:


Нужно отметить, что плата контроллера разрабатывалась для использования в нескольких проектах, поэтому на установленную на плату микросхему КР1128КТ3А (функциональный аналог L293) внимание можно не обращать. Для управления meArm она не используется.


Преобразователь DC-DC на микросхеме LM2596 понижает напряжение батареи из трёх аккумуляторов 18650 до 5 В. Напряжение 5 В подаётся в цепи питания серводвигателей из состава манипулятора, а также на вход питания отладочной платы ESP32-WROOM.

Контроллер готов, настроим Arduino IDE для работы с ним.

▍ Поддержка ESP32 в Arduino IDE


Поддержка ESP32 в Arduino IDE включается через Менеджер плат. Для этого сначала надо открыть в Arduino IDE пункт меню Файл – Настройки и вставить в поле «Дополнительные ссылки для Менеджера плат» ссылку: «https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json»


Открываем Менеджер плат, как на рисунке ниже:


Находим поддержку ESP32 и устанавливаем последнюю стабильную версию ПО:


Подключаем отладочную плату ESP32-WROOM к компьютеру и определяем номер COM-порта. Если требуется драйвер CP2102, то он устанавливается из папки drivers каталога установки Arduino.

Выбираем COM-порт, выбираем тип платы «ESP32 Dev Module» и ничего больше в настройках не изменяем:


Поддержка ESP32 в Arduino IDE включена, но есть нюанс. Для работы ESP32 c сервоприводами нужно ещё скачать и установить соответствующую библиотеку, например, эту.


Библиотека устанавливается в Arduino IDE штатно:


Итак, поддержку ESP32 в Arduino IDE включили, рабочую среду для разработки web-интерфейса для управления манипулятором meArm настроили. Можно переходить к разработке web-интерфейса.

▍ Разработка web-интерфейса


До этого места в конструкции использовались только готовые решения, а при разработке web-интерфейса появились уже некоторые «элементы новизны».

Как «поднять» средствами Arduino IDE web-сервер, есть в стандартных примерах. Как создать на странице этого сервера органы управления сервоприводами, в стандартных примерах нет. Найденные в сети примеры реализации в Arduino управления серводвигателем через web-интерфейс, в конечном счёте вели сюда.

За основу был взят следующий код:

<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,">
  <style>
    body    { text-align: center; font-family: "Trebuchet MS", Arial; margin-left:auto; margin-right:auto;}
    .slider { height: 300px; }
  </style>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js">
  </script>
</head>
<body>
  <h1>ESP32 with Servo</h1>
  <p>Position: <span id="servoPos"></span></p>
  <input type="range" min="0"  max="256" class="slider" id="servoSlider" onchange="servo(this.value)"/>           
  <script>
    var slider = document.getElementById("servoSlider");
    var servoP = document.getElementById("servoPos");
    servoP.innerHTML = slider.value;            
    slider.oninput = function() { slider.value = this.value; servoP.innerHTML = this.value; }            
    $.ajaxSetup({timeout:1000}); 
    function servo(pos) { $.get("/?value=" + pos + "&"); {Connection: close};}
  </script>
</body>
</html>

С помощью этого кода создавался один «слайдер», при установке «движка» которого в положение «100», например, формировался ответ вида «/?value=100&».

Нам же таких «слайдеров» было нужно четыре. JavaScript из нас до сих пор никто не знает. Методом проб и ошибок мы привели исходный код к такому состоянию:

<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta charset="utf-8">
  <link rel="icon" href="data:,">
  <style>
    body    { text-align: center; font-family: "Trebuchet MS", Arial; margin-left:auto; margin-right:auto;}
    .slider { height: 50px; width: 66%; }
  </style>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js">
  </script>
</head>
<body>
  <h3>Позиция X: <span id="xPos"></span></h3>
  <input type="range" min="-100" max="100" class="slider" id="xSlider" onchange="servo(xS.value,yS.value,zS.value,gS.value)"/>
  <h3>Позиция Y: <span id="yPos"></span></h3>
  <input type="range" min="60" max="200" value="130" class="slider" id="ySlider" onchange="servo(xS.value,yS.value,zS.value,gS.value)"/>
  <h3>Позиция Z: <span id="zPos"></span></h3>
  <input type="range" min="-60"  max="60" class="slider" id="zSlider" onchange="servo(xS.value,yS.value,zS.value,gS.value)"/>
  <h3>Захват   : <span id="gPos"></span></h3>
  <input type="range" min="0"  max="1" value="0" class="slider" id="gSlider" onchange="servo(xS.value,yS.value,zS.value,gS.value)"/>           
  <script>
    var xS = document.getElementById("xSlider");
    var xP = document.getElementById("xPos"); xP.innerHTML = xS.value;            
    var yS = document.getElementById("ySlider");            
    var yP = document.getElementById("yPos"); yP.innerHTML = yS.value;            
    var zS = document.getElementById("zSlider");
    var zP = document.getElementById("zPos"); zP.innerHTML = zS.value;   
    var gS = document.getElementById("gSlider");
    var gP = document.getElementById("gPos"); gP.innerHTML = gS.value;            
    xS.oninput = function() { xP.innerHTML = xS.value; }
    yS.oninput = function() { yP.innerHTML = yS.value; }
    zS.oninput = function() { zP.innerHTML = zS.value; }
    gS.oninput = function() { gP.innerHTML = gS.value; }            
    $.ajaxSetup({timeout:1000}); 
    function servo(pos_x,pos_y,pos_z,pos_g) {
      $.get("/?value=" + pos_x + "X" + pos_y + "Y" + pos_z + "Z" + pos_g + "L&");
    {Connection: close};}
  </script>
</body>
</html> 

С помощью этого кода мы получили web-интерфейс для управления манипулятором meArm такого вида:


Скетч находится в свободном доступе здесь.

Перед использованием его нужно настроить: ввести в текст программы имя точки доступа Wi-Fi и пароль для подключения к ней.

При подключении к точке доступа контроллер передаёт по COM-порту свой IP-адрес. Он понадобится для подключения к web-интерфейсу. Для работы web-интерфейса необходим доступ к интернет.

Для тех, кто дочитал публикацию до конца, небольшое видео c результатами нашей разработки:

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

Комментарии (8)


  1. Tarson
    01.09.2021 18:07

    Хе-хе, ровно четыре года назад написал на хабр про роборуку похожий пост... Только там с компуктера по Wi-Fi шло управление.


  1. usa_habro_user
    01.09.2021 21:45

    Дмитрий, есть вопрос: когда вы пишете, "мы собрали", "мы решили", "мы написали" - какова реальная вовлеченность детей в создание этого проекта?

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

    Ну, а, касательно размещения html кода в скетче, мне кажется, что ваш метод не самый удачный; лично я пользуюсь вот такой техникой (ссылка на мой DIY проект). Обратите внимание, кстати, на теги meta и ссылки на иконки - благодаря этому можно превратить html страничку в "приложение", которое удобно добавить на home screen телефона.


    1. dmitriyrudnev Автор
      02.09.2021 06:40
      +1

      Спасибо за рекомендации, коллега!

      когда вы пишете, "мы собрали", "мы решили", "мы написали" - какова реальная вовлеченность детей в создание этого проекта?

      Ребята работали двумя командами по три человека над двумя разными проектами. Вовлечённость детей при такой организации проектной деятельности - максимальная. Современный подросток, умеющий искать в интернете, способен на многое. Мне оставалось только задавать наводящие вопросы.

      В публикации приведена общая часть этих двух проектов. Т.к. проекты пересекались, мне удалось убедить подопечных разработать контроллер, чтобы он подходил для обоих решений. Ещё удалось их убедить использовать в контроллере ESP32 вместо ESP8266. На этом мое вмешательство и закончилось.


      1. usa_habro_user
        02.09.2021 08:54

        Ну, это просто здорово, "зачОт"! Даю вам идею для следующего проекта: сделайте поливочного робота для школьной теплицы (если такие, конечно, существуют в нынешнее время - в мое время были почти в каждой школе, вот только с компьютерами и роботами тогда была "напряженка" - точнее, их не было вовсе).


      1. drWhy
        02.09.2021 10:25

        Если пофантазировать — можно предложить формат ВУЗовской лабораторной работы, где группа учащихся проводят изыскания с определёнными целями, учатся использовать определённые приборы и по результатам оформляют письменный отчёт — тогда и преподавателю и учащимся понятно, кто чем занимался и насколько был вовлечён. Отчёты можно хранить на сайте — и как отражение проводимой работы, и как пример для заинтересованных лиц, да и учащимся может пригодиться позднее.


  1. djarik
    02.09.2021 11:02

    Не понял зачем сделана отправка положений серв по событию onchange, а не oninput? Так же работает не отзывчиво. Запрос отправляется только после того как убрали палец от экрана.


    1. dmitriyrudnev Автор
      02.09.2021 11:48

      Спасибо большое! Обязательно испытаю oninput!

      В исходном примере был onchange, а школьникам и мне просто знаний JavaScript не хватило.


  1. zoldaten
    02.09.2021 15:36

    Когда-то делал похожую руку, взяв каркас из алюминия с aliexpress.
    Самое сложное было сделать синхронизацию в «плече», в котором сервы смотрят друг на друга и должны вращаться в противоположные стороны синхронно. Там какой-то жуткий костыль получился.
    Кроме того, было сильное падение напряжения во время наличия веса в руке. От этого не спасла ни установка «достойного» понижающего DC-DC, ни вынесение питания серв (там MG996 вместо SG90 как в этом проекте, в основе серва с оборотом 360 град) отдельно на более мощный блок питания. Использовалась pca9685. Может в ней дело…


    p.s. могу описать проект в отдельном посте