Как управлять сервоприводом с помощью ESP32 через Wi-Fi
Сервоприводы — это универсальные моторы, которые можно точно позиционировать на определённый угол, что делает их идеальными для самых разных проектов. Если вы новичок в работе с сервоприводами и ESP32, мы рекомендуем ознакомиться с нашим предыдущим руководством, где мы рассмотрели основы управления сервоприводом с помощью ESP32.
Хобби-сервоприводы — это удобные и полезные устройства для самых разных проектов. Хотя изначально они использовались в радиоуправляемых автомобилях и самолётах…
В этом руководстве мы пойдём дальше. Мы воспользуемся возможностями Wi-Fi ESP32 для создания удобного веб-интерфейса. Этот интерфейс позволит вам удалённо регулировать положение сервопривода с любого устройства, имеющего веб-браузер. Это открывает возможности для таких проектов, как роботизированные руки, умная домашняя автоматизация (для таких задач, как открытие и закрытие окон, жалюзи или кормушек для домашних животных) и реалистичная аниматроника с дистанционно управляемыми движениями.
К концу этого руководства у вас будет базовое понимание того, как объединить мощь возможностей Wi-Fi ESP32 с точностью сервоприводов для создания увлекательных и практичных проектов.
Давайте начнём!
Что вам понадобится
Для этого проекта вам понадобятся следующие компоненты:
Плата разработки ESP32: Она послужит «мозгом» вашего проекта, обеспечивая работу веб-интерфейса и управление сервоприводом.
Сервопривод: Вы можете использовать любой стандартный сервопривод, например, популярные модели SG90 или MG996R.
Макетная плата и перемычки: Для подключения ESP32, сервопривода и источника питания.
Компьютер с Arduino IDE: Вы будете использовать его для написания и загрузки кода в ESP32.
(Опционально) Источник питания 5 В: Если вашему сервоприводу требуется больше энергии, чем может обеспечить ESP32, вы можете использовать внешний источник питания.
Подключение сервопривода к ESP32
Теперь давайте подключим оборудование.
Для этого эксперимента мы будем использовать микро-сервопривод SG90. Этот маленький, но мощный мотор работает от 5 В (может работать при напряжении от 4,8 В до 6 В постоянного тока) и может поворачиваться на 180 градусов — по 90 градусов в каждую сторону.
Важно отметить, что когда сервопривод находится в покое, он потребляет всего около 10 мА тока. Но когда он движется, ему требуется гораздо больше — от 100 мА до 250 мА. Это означает, что для большинства простых проектов мы можем питать сервопривод непосредственно от вывода VIN ESP32. Но если вашему сервоприводу требуется более 250 мА, следует использовать отдельный источник питания, чтобы не повредить ESP32.
Для подключения сервопривода SG90 к ESP32:
Подключите красный провод к выводу VIN на ESP32.
Подключите чёрный или коричневый провод к выводу GND (земля).
Подключите оранжевый или жёлтый провод к GPIO13 на ESP32. На самом деле вы можете использовать любой GPIO ESP32, так как каждый из них способен генерировать сигнал ШИМ. Однако рекомендуется избегать использования GPIO 9, 10 и 11, поскольку они подключены к встроенной SPI-флеш-памяти и не рекомендуются для других целей. Пожалуйста, обратитесь к Справочник по распиновке ESP32 для получения дополнительной информации.
Вот краткая справочная таблица подключения выводов:
Сервопривод |
ESP32 |
|---|---|
5V |
VIN |
GND |
GND |
Управление (Control) |
GPIO13 |
Пожалуйста, обратитесь к изображению ниже, чтобы увидеть правильную схему подключения.
Настройка Arduino IDE
Мы будем использовать Arduino IDE для программирования ESP32, поэтому, пожалуйста, убедитесь, что у вас установлено дополнение ESP32, прежде чем продолжить:
Микроконтроллер ESP32 быстро стал одной из самых популярных плат среди любителей, инженеров и людей, интересующихся Интернетом вещей (IoT)…
Установка библиотеки
Arduino IDE поставляется со встроенной библиотекой Servo для управления сервоприводами, но, к сожалению, она не совместима с ESP32. Тем не менее, существует несколько библиотек, специально разработанных для ESP32, многие из которых эмулируют библиотеку Arduino Servo, добавляя при этом дополнительную функциональность.
Для этого руководства мы выбрали библиотеку ESP32Servo от Kevin Harrington. Эта библиотека не только повторяет функции оригинальной библиотеки Arduino Servo, но и предлагает некоторые дополнительные возможности.
Для установки библиотеки:
Сначала откройте программу Arduino IDE. Затем нажмите на значок Library Manager (Менеджер библиотек) на левой боковой панели.
Введите « ESP32Servo» в поле поиска для фильтрации результатов.
Найдите библиотеку « ESP32Servo», созданную Kevin Harrington.
Нажмите кнопку Install (Установить), чтобы добавить её в Arduino IDE.
Пример кода
Приведённый ниже код настраивает простой веб-сервер на вашем ESP32. Когда вы подключаетесь к нему с помощью веб-браузера, вы увидите веб-страницу с ползунком. Когда этот ползунок перемещается, браузер отправляет AJAX GET-запрос к ESP32. ESP32, в свою очередь, интерпретирует запрос, определяет желаемое положение и отправляет соответствующий сигнал ШИМ на сервопривод, эффективно регулируя его угол.
Скопируйте приведённый ниже код в Arduino IDE. Но перед загрузкой кода убедитесь, что имя (SSID) и пароль вашей Wi-Fi сети правильно указаны в коде.
/*Put your SSID & Password*/
const char* ssid = "YourNetworkName"; // Enter SSID here
const char* password = "YourPassword"; //Enter Password here
После внесения этих изменений загрузите код.
#include <WiFi.h>
#include <WebServer.h>
#include <ESP32Servo.h>
/*Put your SSID & Password*/
const char* ssid = "YourNetworkName"; // Enter SSID here
const char* password = "YourPassword"; //Enter Password here
Servo myservo; // create servo object to control a servo
// Define servo pin
const int servoPin = 13; // Change this to your actual servo pin
WebServer server(80);
void setup() {
// Allow allocation of all timers for servo library
ESP32PWM::allocateTimer(0);
ESP32PWM::allocateTimer(1);
ESP32PWM::allocateTimer(2);
ESP32PWM::allocateTimer(3);
// Set servo PWM frequency to 50Hz
myservo.setPeriodHertz(50);
// Attach to servo and define minimum and maximum positions
myservo.attach(servoPin, 500, 2400);
// Start serial
Serial.begin(115200);
Serial.println("Connecting to ");
Serial.println(ssid);
//connect to your local wi-fi network
WiFi.begin(ssid, password);
//check wi-fi is connected to wi-fi network
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected..!");
Serial.print("Got IP: ");
Serial.println(WiFi.localIP());
server.on("/", handle_OnConnect);
server.onNotFound(handle_NotFound);
server.begin();
Serial.println("HTTP server started");
}
void loop() {
server.handleClient();
}
void handle_OnConnect() {
int servoPos;
// Check if value parameter exists
if (server.hasArg("value")) {
String valueString = server.arg("value");
servoPos = valueString.toInt();
// Send a simple response for AJAX requests
server.send(200, "text/plain", "OK");
} else {
// Move servo to 90 deg initially
servoPos = 90;
// Send the main webpage
server.send(200, "text/html", createHTML());
}
// Move servo into position
myservo.write(servoPos);
// Print value to serial monitor
Serial.print("Servo position: ");
Serial.println(servoPos);
}
void handle_NotFound() {
server.send(404, "text/plain", "Not found");
}
String createHTML() {
String html = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
html {font-family: sans-serif; text-align: center;}
h1 {margin: 80px auto 40px; font-weight: 300; font-size: 2.5em;}
.slider-container {padding-top: 30px; margin: 40px auto; max-width: 500px;}
.field {display: flex; justify-content: space-between; align-items: center; gap: 20px; margin: 20px 0;}
.field .value {font-size: 18px; width: 50px;}
.slider {appearance: none; width: 100%; height: 8px; border-radius: 25px; background: rgba(0, 0, 0, 0.1); outline: none; position: relative; cursor: pointer;}
.slider-input {position:relative; width: 100%; height: 18px;}
.slider::-webkit-slider-thumb {appearance: none; width: 28px; height: 28px; border-radius: 50%; background: #fff; cursor: pointer; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3), 0 0 0 3px rgba(255, 255, 255, 0.2); transition: all 0.2s ease; position: relative;}
.slider::-webkit-slider-thumb:hover {transform: scale(1.1);}
.position-display {padding: 20px;}
.position-value {font-size: 2.5em;}
.slider-fill {position: absolute; height: 8px; background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); border-radius: 25px; pointer-events: none; top: 7px; margin: 0 2px;}
</style>
</head>
<body>
<h1>Servo Control</h1>
<div class="slider-container">
<div class="field">
<div class="value">0°</div>
<div class="slider-input">
<div class="slider-fill" id="sliderFill"></div>
<input type="range" min="0" max="180" class="slider" id="servoSlider" onchange="servo(this.value)" value="90"/>
</div>
<div class="value">180°</div>
</div>
<div class="position-display">
<div>Current Position</div>
<div class="position-value"><span id="servoPos">90</span>°</div>
</div>
</div>
<script>
// Get DOM elements
var slider = document.getElementById("servoSlider");
var servoP = document.getElementById("servoPos");
var sliderFill = document.getElementById("sliderFill");
// Initialize display
servoP.innerHTML = slider.value;
updateSliderFill();
// Update display when slider moves
slider.oninput = function() {
servoP.innerHTML = this.value;
updateSliderFill();
}
// Function to update the visual fill of the slider
function updateSliderFill() {
var percentage = (slider.value / 180) * 100;
sliderFill.style.width = percentage + '%';
}
// Function to send servo position to ESP32
function servo(pos) {
// Create XMLHttpRequest object
var xhr = new XMLHttpRequest();
// Set timeout
xhr.timeout = 1000;
// Handle timeout
xhr.ontimeout = function() {
console.log('Request timed out');
};
// Handle errors
xhr.onerror = function() {
console.log('Request failed');
};
// Send GET request to ESP32
xhr.open('GET', '/?value=' + pos, true);
xhr.send();
}
</script>
</body>
</html>
)rawliteral";
return html;
}
Демонстрация
После загрузки скетча откройте Монитор порта (Serial Monitor) и убедитесь, что скорость передачи данных установлена на 115200. Затем нажмите кнопку EN (сброс) на ESP32. Если всё работает правильно, ESP32 подключится к вашей Wi-Fi сети и выведет сообщение «WiFi connected..!» вместе с IP-адресом, который ваш роутер назначил вашему ESP32. Вы также увидите надпись «HTTP server started» в Мониторе порта.
Теперь возьмите устройство, например, телефон или ноутбук, подключённое к той же Wi-Fi сети, что и ESP32. Откройте веб-браузер (например, Chrome или Firefox) и введите IP-адрес, который появился в Мониторе порта. Нажмите Enter, и вы должны увидеть веб-страницу! На этой странице будет отображаться ползунок.
Попробуйте переместить ползунок. Ваш сервопривод должен повторять движение ползунка!
Разбор кода
Мы начинаем с подключения трёх важных библиотек: WiFi.h, WebServer.h и ESP32Servo.h. Библиотека WiFi.h помогает ESP32 подключаться к Wi-Fi сети. Библиотека WebServer.h позволяет ESP32 работать в качестве веб-сервера, чтобы он мог обрабатывать входящие запросы веб-страниц от браузера. Библиотека ESP32Servo.h используется для управления сервоприводами на ESP32, так как стандартная библиотека Arduino Servo не работает на ESP32 «из коробки».
#include <WiFi.h>
#include <WebServer.h>
#include <ESP32Servo.h>
Поскольку мы подключаем ESP32 к вашей существующей Wi-Fi сети, мы определяем две переменные для хранения имени сети (SSID) и пароля. Обязательно обновите их, указав данные вашей собственной сети, чтобы ESP32 мог подключиться.
/* Put your SSID & Password */
const char* ssid = "YourNetworkName"; // Enter SSID here
const char* password = "YourPassword"; //Enter Password here
Затем мы создаём объект Servo с именем myservo. Этот объект будет отвечать за отправку команд позиционирования на реальный сервопривод, подключённый к ESP32. Далее мы определяем вывод GPIO, к которому физически подключён сервопривод. В данном случае это вывод 13, хотя вы можете изменить его в зависимости от вашей схемы.
Servo myservo; // create servo object to control a servo
// Define servo pin
const int servoPin = 13; // Change this to your actual servo pin
После этого мы создаём экземпляр объекта WebServer с именем server и указываем ему слушать HTTP-запросы на порту 80, который является стандартным портом для веб-трафика. Это означает, что при посещении IP-адреса ESP32 в браузере вам не нужно будет добавлять номер порта.
// declare an object of WebServer library
WebServer server(80);
Внутри функции Setup()
Внутри функции setup() мы начинаем с выделения таймеров для библиотеки сервоприводов. Это необходимо, потому что ESP32 использует аппаратные таймеры для генерации сигналов ШИМ, которые понимают сервоприводы. Явно выделяя таймеры, мы обеспечиваем корректную работу библиотеки по управлению сервоприводом без конфликтов с другими операциями.
// Allow allocation of all timers for servo library
ESP32PWM::allocateTimer(0);
ESP32PWM::allocateTimer(1);
ESP32PWM::allocateTimer(2);
ESP32PWM::allocateTimer(3);
Затем мы устанавливаем частоту сигнала ШИМ на 50 Гц. Это стандартная частота, ожидаемая большинством хобби-сервоприводов. Обычно вам не нужно менять это значение, если у вашего сервопривода нет очень специфических требований.
// Set servo PWM frequency to 50Hz
myservo.setPeriodHertz(50);
Далее мы привязываем объект сервопривода к выводу, который определили ранее, с помощью myservo.attach(). Мы также указываем минимальную и максимальную длительности импульсов в микросекундах — 500 мкс и 2400 мкс в данном случае — которые примерно соответствуют положениям 0 градусов и 180 градусов качалки сервопривода. Эти значения длительности импульсов, возможно, потребуется подстроить в зависимости от конкретной модели используемого сервопривода.
// Attach to servo and define minimum and maximum positions
myservo.attach(servoPin, 500, 2400);
После этого мы инициируем подключение к Wi-Fi сети, вызывая WiFi.begin() с указанными SSID и паролем. Пока соединение устанавливается, мы продолжаем проверять статус подключения в цикле. Мы ждём одну секунду между проверками и выводим точку для индикации прогресса. Как только ESP32 успешно подключается, мы выводим подтверждающее сообщение, а также отображаем IP-адрес, полученный от роутера. Этот IP-адрес важен, потому что вы будете использовать его для доступа к веб-странице, размещённой на ESP32.
Serial.println("Connecting to ");
Serial.println(ssid);
//connect to your local wi-fi network
WiFi.begin(ssid, password);
//check wi-fi is connected to wi-fi network
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected..!");
Serial.print("Got IP: ");
Serial.println(WiFi.localIP());
После настройки сети мы переходим к конфигурированию веб-сервера. Мы определяем, что должно происходить, когда кто-то посещает определённый веб-адрес. Мы делаем это с помощью функции server.on(). Этот метод позволяет нам сказать: «Когда кто-то посещает этот веб-адрес, выполни этот конкретный фрагмент кода.» Например, когда кто-то посещает корневой URL (обозначаемый /), ESP32 выполнит функцию handle_OnConnect().
server.on("/", handle_OnConnect);
Но что если кто-то случайно введёт неправильный или случайный веб-адрес, которого не существует? Для обработки этого случая мы используем server.onNotFound(). Это сообщает нашему ESP32, что если он не может найти запрашиваемую страницу, он должен показать сообщение об ошибке «404 Not Found», что является стандартным способом сообщить «Страница не найдена».
server.onNotFound(handle_NotFound);
Наконец, мы вызываем server.begin(), чтобы указать веб-серверу начать прослушивание входящих запросов.
server.begin();
Serial.println("HTTP server started");
Внутри функции Loop()
Внутри функции loop() есть только одна строка: server.handleClient(). Эта строка непрерывно проверяет, не пытается ли какой-либо клиент (например, браузер) отправить запрос к ESP32. Если да, ESP32 отвечает, вызывая соответствующую функцию, которую мы определили ранее.
void loop() {
server.handleClient();
}
Обработка веб-запросов
Теперь давайте рассмотрим функцию handle_OnConnect(). Эта функция обрабатывает два типа случаев: когда браузер первоначально загружает веб-страницу и когда он отправляет обновлённые положения сервопривода через JavaScript.
Сначала мы проверяем, содержит ли запрос параметр с именем «value», используя server.hasArg("value"). Это происходит, когда JavaScript-код в браузере отправляет значение угла сервопривода через GET-запрос. Если этот параметр существует, мы преобразуем значение в целое число и сохраняем его в servoPos. Затем мы отправляем простой текстовый ответ «OK», чтобы браузер знал, что запрос получен.
Если запрос не содержит параметра «value», это означает, что пользователь просто обычным образом посещает страницу. В этом случае мы устанавливаем servoPos на 90 градусов, что служит нейтральной начальной позицией. Затем мы отправляем полную HTML-веб-страницу в браузер с помощью server.send(), а содержимое берётся из функции createHTML().
В обоих случаях — поступил ли запрос от обновления ползунка AJAX или от первоначальной загрузки страницы — мы перемещаем сервопривод в новое положение с помощью myservo.write(servoPos). Наконец, мы выводим текущее положение в Монитор порта, чтобы можно было отслеживать изменения в реальном времени.
void handle_OnConnect() {
int servoPos;
// Check if value parameter exists
if (server.hasArg("value")) {
String valueString = server.arg("value");
servoPos = valueString.toInt();
// Send a simple response for AJAX requests
server.send(200, "text/plain", "OK");
} else {
// Move servo to 90 deg initially
servoPos = 90;
// Send the main webpage
server.send(200, "text/html", createHTML());
}
// Move servo into position
myservo.write(servoPos);
// Print value to serial monitor
Serial.print("Servo position: ");
Serial.println(servoPos);
}
Если пользователь пытается получить доступ к странице, которая не существует на сервере, ESP32 выполняет функцию handle_NotFound(), которая просто отправляет сообщение об ошибке 404 — так же, как любой обычный веб-сайт, когда вы вводите неправильный URL.
void handle_NotFound() {
server.send(404, "text/plain", "Not found");
}
Отображение HTML-веб-страницы
Теперь давайте поговорим о функции createHTML(). Эта пользовательская функция возвращает полный HTML-документ в виде строки. Это та веб-страница, которую вы видите в своём браузере при посещении IP-адреса ESP32.
В начале HTML мы включаем <!DOCTYPE html>, чтобы сообщить браузеру, что это документ HTML5. Мы также включаем тег <meta>, который обеспечивает корректное отображение страницы на всех размерах экранов, таких как телефоны и планшеты.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
Стилизация веб-страницы
Секция <style> содержит весь CSS-код, который придаёт нашей странице аккуратный макет. Мы устанавливаем шрифт sans-serif, центрируем всё на экране и оформляем ползунок с красивой градиентной заливкой и плавными анимациями ползунка. Ползунок имеет диапазон от 0 до 180 градусов, а текущее значение отображается рядом крупным шрифтом.
<style>
html {font-family: sans-serif; text-align: center;}
h1 {margin: 80px auto 40px; font-weight: 300; font-size: 2.5em;}
.slider-container {padding-top: 30px; margin: 40px auto; max-width: 500px;}
.field {display: flex; justify-content: space-between; align-items: center; gap: 20px; margin: 20px 0;}
.field .value {font-size: 18px; width: 50px;}
.slider {appearance: none; width: 100%; height: 8px; border-radius: 25px; background: rgba(0, 0, 0, 0.1); outline: none; position: relative; cursor: pointer;}
.slider-input {position:relative; width: 100%; height: 18px;}
.slider::-webkit-slider-thumb {appearance: none; width: 28px; height: 28px; border-radius: 50%; background: #fff; cursor: pointer; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3), 0 0 0 3px rgba(255, 255, 255, 0.2); transition: all 0.2s ease; position: relative;}
.slider::-webkit-slider-thumb:hover {transform: scale(1.1);}
.position-display {padding: 20px;}
.position-value {font-size: 2.5em;}
.slider-fill {position: absolute; height: 8px; background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); border-radius: 25px; pointer-events: none; top: 7px; margin: 0 2px;}
</style>
Установка заголовка веб-страницы
Далее мы добавляем основной заголовок веб-страницы.
<h1>Servo Control</h1>
Отображение ползунка
Тело HTML включает контейнер, содержащий элемент ввода — ползунок. Ползунок обрамлён метками, показывающими 0° и 180°, и по мере перемещения пользователем ползунка визуальная полоса заполнения под ним обновляется, показывая текущее положение. Ниже текущий угол отображается динамически с помощью JavaScript.
<div class="slider-container">
<div class="field">
<div class="value">0°</div>
<div class="slider-input">
<div class="slider-fill" id="sliderFill"></div>
<input type="range" min="0" max="180" class="slider" id="servoSlider" onchange="servo(this.value)" value="90"/>
</div>
<div class="value">180°</div>
</div>
<div class="position-display">
<div>Current Position</div>
<div class="position-value"><span id="servoPos">90</span>°</div>
</div>
</div>
Теперь давайте поговорим о части JavaScript. В нижней части HTML мы включаем блок <script>. Этот скрипт отвечает за обновление положения сервопривода и отражение этих изменений в пользовательском интерфейсе.
Сначала мы используем document.getElementById() для получения ссылок на ползунок, метку, показывающую угол, и элемент визуальной заливки. Затем мы инициализируем пользовательский интерфейс, чтобы отображаемый угол и заливка ползунка были синхронизированы с начальным значением ползунка.
Когда ползунок перемещается, срабатывает событие oninput. Оно немедленно обновляет отображаемый угол, а также обновляет полосу заполнения в соответствии с новым положением. Эта визуальная обратная связь помогает пользователю точно знать, какой угол он выбирает.
Ключевой частью является функция servo(), которая вызывается всякий раз, когда ползунок изменяется. Эта функция создаёт объект XMLHttpRequest, который отправляет асинхронный GET-запрос к серверу ESP32 с новым углом в качестве параметра URL — например, /value=135. Это AJAX в действии: он обновляет положение сервопривода без перезагрузки всей веб-страницы.
Подход AJAX позволяет веб-интерфейсу оставаться плавным и интерактивным. Между браузером и ESP32 обмениваются только необходимыми данными, в то время как остальная часть страницы остаётся неизменной. Пользователь может продолжать перетаскивать ползунок вперёд и назад, и сервопривод обновляется в реальном времени без полной перезагрузки страницы.
<script>
// Get DOM elements
var slider = document.getElementById("servoSlider");
var servoP = document.getElementById("servoPos");
var sliderFill = document.getElementById("sliderFill");
// Initialize display
servoP.innerHTML = slider.value;
updateSliderFill();
// Update display when slider moves
slider.oninput = function() {
servoP.innerHTML = this.value;
updateSliderFill();
}
// Function to update the visual fill of the slider
function updateSliderFill() {
var percentage = (slider.value / 180) * 100;
sliderFill.style.width = percentage + '%';
}
// Function to send servo position to ESP32
function servo(pos) {
// Create XMLHttpRequest object
var xhr = new XMLHttpRequest();
// Set timeout
xhr.timeout = 1000;
// Handle timeout
xhr.ontimeout = function() {
console.log('Request timed out');
};
// Handle errors
xhr.onerror = function() {
console.log('Request failed');
};
// Send GET request to ESP32
xhr.open('GET', '/?value=' + pos, true);
xhr.send();
}
</script>
Наконец, функция правильно закрывает все HTML-теги и возвращает полную HTML-строку.
</body>
</html>
)rawliteral";
return html;
}