ESP32:Примеры/Веб-сервер на базе ESP32: удаленное управление сервоприводом

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.


Веб-сервер на базе ESP32: удаленное управление сервоприводом

В этом примере мы покажем, как создать на базе ESP32 веб-сервер, показывающий в браузере ползунок, с помощью которого пользователь может управлять осью сервопривода.

Итак, веб-сервер, который мы создадим:

  • Будет иметь ползунок со значениями от «0» до «180», с помощью которого пользователь будет управлять осью сервопривода;
  • Когда пользователь двигает ползунок, его текущее значение (число рядом со словом "Position:") автоматически обновляется на веб-странице (одновременно с этим автоматически начинает двигаться ось сервопривода), что избавляет от необходимости обновлять веб-страницу вручную.
  • Обновление веб-страницы не поменяет ни значение ползунка, ни позицию оси сервопривода;

Подключение сервопривода к ESP32

Сервомотор оснащен 3 проводами: для питания, заземления и передачи сигнала. Провод для питания, как правило, красный, для заземления – черный или коричневый, а для передачи сигнала – обычно желтый, оранжевый или белый.

Провод Цвет
Питание Красный
Заземление Черный или коричневый
Сигнал Желтый, оранжевый или белый

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

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

Если вы используете маленький сервопривод вроде S0009, его провода нужно подключить так:

  • Заземление (сервопривод) – к контакту GND (ESP32)
  • Питание (сервопривод) – к контакту VIN (ESP32)
  • Передача сигнала (сервопривод) – к GPIO13 или любому другому ШИМ-контакту (ESP32)
Примечание

В этом проекте можно использовать любой GPIO-контакт ESP32, т.к. все они способны генерировать ШИМ-сигнал. Но мы не рекомендуем использовать GPIO-контакты 9, 10 и 11, т.к. они подключены ко встроенной памяти SPI flash, и для других целей их использовать не рекомендуется.

Как управлять сервомотором?

Ось сервомотора можно расположить под углом от 0 до 180 градусов. Сервомоторы управляются с помощью широтно-импульсной модуляции (ШИМ). То есть ШИМ-сигнал, отправляемый сервомотору, будет определять позицию его оси.

Для управления сервоприводом используется способность ESP32 генерировать ШИМ-сигнал с 50 Гц и соответствующей шириной импульса.

Устанавливаем библиотеку «ESP32 Arduino Servo»

Библиотека «ESP32 Arduino Servo» упрощает управление сервоприводом при помощи ESP32 и IDE Arduino. Чтобы установить ее в IDE Arduino, проделайте следующее:

  1. Кликните тут, чтобы загрузить ZIP-архив библиотеки
  2. Распакуйте скачанный ZIP-архив. У вас должна получиться папка «ESP32-Arduino-Servo-Library-Master»
  3. Переименуйте ее на «ESP32_Arduino_Servo_Library»
  4. Переместите папку «ESP32_Arduino_Servo_Library» в папку «libraries», которая находится в папке, куда установлена ваша IDE Arduino
  5. Наконец, снова откройте IDE Arduino

Создаем веб-страницу

Давайте начнем с разбора HTML-кода, который ESP32 будет отсылать вашему браузеру. Этот HTML-код также можно найти по этой ссылке.

<!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 {
      width: 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="180" 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>

Создаем ползунок

При создании этой веб-страницы нам нужно будет, помимо прочего, создать веб-компонент, известный как «ползунок». Для этого нам понадобится тег <input>. Он задает поле, в которое пользователь может ввести какие-либо данные.

Компоненты типа <input> бывают разными. Чтобы сделать ползунок, нам нужно воспользоваться атрибутом «type» и задать ему значение «range». Кроме того, для ползунка нужно задать минимальное и максимальное значения диапазона при помощи атрибутов «min» и «max».

<input type="range" min="0" max="180" class="slider" id="servoSlider" onchange="servo(this.value)"/>

Для ползунка также нужно задать следующие атрибуты:

  • Атрибут «class», задающий CSS-стиль для ползунка
  • Атрибут «id», который обновляет текущую позицию, показываемую на веб-странице
  • Атрибут «onchange», при перемещении ползунка вызывающий функцию servo() для отправки HTTP-запроса на ESP32

Добавляем JavaScript-код в HTML-файл

Далее при помощи тегов <script> и </script> добавляем JS-код в HTML-файл. Фрагмент кода ниже обновляет позицию ползунка на веб-странице:

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;
}

Фрагмент кода ниже делает HTTP-запрос GET на IP-адрес ESP32 со ссылкой «/?value=[SLIDER_POSITION]&».

$.ajaxSetup({timeout:1000});
function servo(pos) {
$.get("/?value=" + pos + "&");
{Connection: close};
}

К примеру, если ползунок будет на «0», мы сделаем HTTP-запрос GET с такой URL-ссылкой:

http://192.168.1.135/?value=0&

А если переместить ползунок на 180 градусов, URL-ссылка будет такой:

http://192.168.1.135/?value=180&

Таким образом, получая запрос GET, ESP32 также получает ссылку со значением для сервопривода, а затем перемещает ось сервопривода на позицию, заданную в этой ссылке.

Как работает этот код

Во-первых, подключаем библиотеку «Servo» и создаем экземпляр класса «Servo» под названием «myservo»:

#include <Servo.h>
Servo myservo;

Также создаем переменную, в которой будет храниться номер GPIO-контакта, к которому подключен сервопривод. В данном случае это GPIO13.

static const int servoPin = 13;

Не забудьте отредактировать две строчки кода ниже, вставив туда свои SSID и пароль:

const char* ssid = "";
const char* password = "";

Затем создаем несколько переменных, которые будут использоваться для считывания позиции ползунка из HTTP-запроса:

// несколько переменных для расшифровки значения в HTTP-запросе GET:
String valueString = String(5);
int pos1 = 0;
int pos2 = 0;

setup()

В блоке setup() привязываем объект «servo» к GPIO-контакту, к которому подключен сервопривод. Делаем это при помощи функции myservo.attach().

myservo.attach(servoPin);   // привязываем сервопривод,
                            // подключенный к контакту «servoPin»,
                            // к объекту «myservo»

loop()

Вы должны быть уже знакомы с частью блока loop(), которая создает веб-сервер и отправляет HTML-код для показа веб-страницы, так что мы ее пропустим.

Фрагмент кода ниже извлекает из HTTP-запроса значение ползунка:

//GET /?value=180& HTTP/1.1
if(header.indexOf("GET /?value=")>=0) {
  pos1 = header.indexOf('=');
  pos2 = header.indexOf('&');
  valueString = header.substring(pos1+1, pos2);

Перемещая ползунок, вы делаете HTTP-запрос с URL-ссылкой, содержащей позицию ползунка между символами «=» и «&»:

http://ip-адрес-вашей-esp32/?value=[SLIDER_POSITION]&

Позиция ползунка сохраняется в переменную «valueString». Затем мы при помощи метода myservo.write() – и указав в его параметре значение «valueString» – задаем для сервомотора нужную позицию. Переменная «valueString» – это строка, поэтому нам также необходим метод toInt(), чтобы преобразовать ее в целое число, т.к. это тип данных, принимаемых методом write().

myservo.write(valueString.toInt());

Тестируем веб-сервер

Теперь проверяем, правильные ли в IDE Arduino выставлены COM-порт и плата, а также вписаны ли в код учетные данные (SSID и пароль) вашей сети. Затем загружаем код на ESP32 и открываем монитор порта на скорости 115200 бод.

Нажимаем на кнопку EN на ESP32, чтобы перезапустить плату, а затем копируем локальный IP-адрес, напечатанный платой в мониторе порта:

Открываем браузер, вставляем в адресную строку скопированный IP-адрес ESP32 и нажимаем на  ↵ Enter . В браузере должна появиться веб-страница, созданная нами ранее. Подвигайте ползунок, чтобы изменить положение оси сервопривода.

Примечание

Справа я открыл инструменты разработчика браузера, чтобы вы могли увидеть, что будет отправлять скрипт плате ESP32.

Кроме того, в мониторе порта должен появиться HTTP-запрос, отправляемый ESP32 после перемещения ползунка.

Поэкспериментируйте немного с этим проектом, чтобы проверить, правильно ли он работает.

Итак, в этом примере мы научились управлять сервоприводом при помощи ESP32 и тому, как создать веб-сервер с ползунком для управления осью сервопривода.

Это лишь самый простой пример создания веб-страницы для управления сервомотором. Вместо ползунка можно использовать, к примеру, поле для ввода текста, несколько кнопок с предзаданными значениями или какие-то другие веб-элементы для ввода данных.

Необходимое оборудование

Схема

Примечание

На данной схеме используется плата ESP32S-HiLetgo, если вы используете другую, сверьтесь с вашей распиновкой.

В нашем случае мы подключаем провод для передачи данных к контакту GPIO13. Схема подключения всех остальных компонентов показана ниже.

Код

/*********
  Руи Сантос
  Более подробно о проекте на: http://randomnerdtutorials.com  
*********/

#include <WiFi.h>
#include <Servo.h>

Servo myservo;  // создаем экземпляр класса «Servo»,
                // чтобы с его помощью управлять сервоприводом;
                // большинство плат позволяют
                // создать 12 объектов класса «Servo»

// GPIO-контакт, к которому подключен сервопривод:
static const int servoPin = 13;

// вставьте здесь учетные данные своей сети:
const char* ssid     = "";
const char* password = "";

// создаем веб-сервер на порте «80»:
WiFiServer server(80);

// переменная для хранения HTTP-запроса:
String header;

// несколько переменных для расшифровки значения в HTTP-запросе GET:
String valueString = String(5);
int pos1 = 0;
int pos2 = 0;

void setup() {
  Serial.begin(115200);

  myservo.attach(servoPin);  // привязываем сервопривод,
                             // подключенный к контакту «servoPin»,
                             // к объекту «myservo»

  // подключаемся к WiFi при помощи заданных выше SSID и пароля: 
  Serial.print("Connecting to ");  //  "Подключаемся к "
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // печатаем локальный IP-адрес и запускаем веб-сервер:
  Serial.println("");
  Serial.println("WiFi connected.");  //  "WiFi подключен."
  Serial.println("IP address: ");     //  "IP-адрес: "
  Serial.println(WiFi.localIP());
  server.begin();
}

void loop(){
  // начинаем прослушивать входящих клиентов:
  WiFiClient client = server.available();

  if (client) {                     // если подключился новый клиент,     
    Serial.println("New Client.");  // печатаем сообщение
                                    // «Новый клиент.»
                                    // в мониторе порта;
    String currentLine = "";        // создаем строку для хранения
                                    // входящих данных от клиента;
    while (client.connected()) {    // цикл while() будет работать
                                    // все то время, пока клиент
                                    // будет подключен к серверу;
      if (client.available()) {     // если у клиента есть данные,
                                    // которые можно прочесть, 
        char c = client.read();     // считываем байт, а затем    
        Serial.write(c);            // печатаем его в мониторе порта
        header += c;
        if (c == '\n') {            // если этим байтом является
                                    // символ новой строки
          // если получили два символа новой строки подряд,
          // то это значит, что текущая строчка пуста;
          // это конец HTTP-запроса клиента,
          // а значит – пора отправлять ответ:
          if (currentLine.length() == 0) {
            // HTTP-заголовки всегда начинаются
            // с кода ответа (например, «HTTP/1.1 200 OK»)
            // и информации о типе контента
            // (чтобы клиент понимал, что получает);
            // в конце пишем пустую строчку:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
                       //  "Соединение: отключено"
            client.println();

            // показываем веб-страницу:
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            client.println("<style>body { text-align: center; font-family: \"Trebuchet MS\", Arial; margin-left:auto; margin-right:auto;}");
            client.println(".slider { width: 300px; }</style>");
            client.println("<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js\"></script>");
                     
            // веб-страница:
            client.println("</head><body><h1>ESP32 with Servo</h1>");
                                        //  "Управление сервомотором
                                        //   с помощью платы ESP32"
            client.println("<p>Position: <span id=\"servoPos\"></span></p>");          
            client.println("<input type=\"range\" min=\"0\" max=\"180\" class=\"slider\" id=\"servoSlider\" onchange=\"servo(this.value)\" value=\""+valueString+"\"/>");
            
            client.println("<script>var slider = document.getElementById(\"servoSlider\");");
            client.println("var servoP = document.getElementById(\"servoPos\"); servoP.innerHTML = slider.value;");
            client.println("slider.oninput = function() { slider.value = this.value; servoP.innerHTML = this.value; }");
            client.println("$.ajaxSetup({timeout:1000}); function servo(pos) { ");
            client.println("$.get(\"/?value=\" + pos + \"&\"); {Connection: close};}</script>");
           
            client.println("</body></html>");     
            
            //GET /?value=180& HTTP/1.1
            if(header.indexOf("GET /?value=")>=0) {
              pos1 = header.indexOf('=');
              pos2 = header.indexOf('&');
              valueString = header.substring(pos1+1, pos2);
              
              // вращаем ось сервомотора:
              myservo.write(valueString.toInt());
              Serial.println(valueString); 
            }         
            // конец HTTP-ответа задается 
            // с помощью дополнительной пустой строки:
            client.println();
            // выходим из цикла while(): 
            break;
          } else { // если получили символ новой строки,
                   // очищаем текущую строку «currentLine»:
            currentLine = "";
          }
        } else if (c != '\r') {  // если получили любые данные,
                                 // кроме символа возврата каретки,
          currentLine += c;      // добавляем эти данные 
                                 // в конец строки «currentLine»
        }
      }
    }
    // очищаем переменную «header»:
    header = "";
    // отключаем соединение:
    client.stop();
    Serial.println("Client disconnected.");
               //  "Клиент отключился."
    Serial.println("");
  }
}

См.также

Внешние ссылки