ESP32:Примеры/Машинка-робот с дистанционным WiFi-управлением: различия между версиями

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску
 
Нет описания правки
 
(не показаны 3 промежуточные версии 2 участников)
Строка 1: Строка 1:
{{ESP32 панель перехода}}
{{ESP32 панель перехода}}
{{Перевод от Сubewriter}}
{{Перевод от Сubewriter}}
{{Myagkij-редактор}}
{{Myagkij-редактор}}
{{Черновик}}


=Машинка-робот с дистанционным WiFi-управлением=
=Машинка-робот с дистанционным WiFi-управлением=
Строка 29: Строка 26:
[[File:wifi_robot_esp32_web_controls_1.PNG|center]]
[[File:wifi_robot_esp32_web_controls_1.PNG|center]]


'''Примечание:''' О том, как создавать кнопки, читайте в руководстве [[ESP32:Примеры/Веб-сервер_на_базе_ESP32:_управление_выходными_контактами|«Веб-сервер на базе ESP32: управление выходными контактами»]], а о создании ползунка – в руководстве [[ESP32:Примеры/Веб-сервер_на_базе_ESP32:_удаленное_управление_сервоприводом|«Веб-сервер на базе ESP32: удаленное управление сервоприводом»]].
{{Примечание1|1=О том, как создавать кнопки, читайте в руководстве [[ESP32:Примеры/Веб-сервер_на_базе_ESP32:_управление_выходными_контактами|«Веб-сервер на базе ESP32: управление выходными контактами»]], а о создании ползунка – в руководстве [[ESP32:Примеры/Веб-сервер_на_базе_ESP32:_удаленное_управление_сервоприводом|«Веб-сервер на базе ESP32: удаленное управление сервоприводом»]].}}


=== Шасси ===
=== Шасси ===
Строка 74: Строка 71:
Внизу находится еще один клеммник, но уже на 3 порта: '''+12V''', '''GND''' и '''+5V'''. Порт '''+12V''' используется для питания моторов. Порт '''+5V''' используется для питания чипа [[L298N]]. Но если поставить перемычку (на изображении выше она помечена оранжевым цветом), чип будет питаться от питания для [[DC-мотор]]ов. В этом случае подавать 5-вольтовое питание на порт '''+5V''' не нужно.  
Внизу находится еще один клеммник, но уже на 3 порта: '''+12V''', '''GND''' и '''+5V'''. Порт '''+12V''' используется для питания моторов. Порт '''+5V''' используется для питания чипа [[L298N]]. Но если поставить перемычку (на изображении выше она помечена оранжевым цветом), чип будет питаться от питания для [[DC-мотор]]ов. В этом случае подавать 5-вольтовое питание на порт '''+5V''' не нужно.  


'''Примечание:''' Если напряжение питания превышает 12 вольт, перемычку нужно снять и подать 5-вольтовое питание на порт '''+5V'''.
{{Примечание1|Если напряжение питания превышает 12 вольт, перемычку нужно снять и подать 5-вольтовое питание на порт '''+5V'''.}}


Важно отметить, что хотя порт называется '''+12V''', для нашего проекта (и с поставленной перемычкой) подойдет напряжение в диапазоне между 6 и 12 вольтами. Мы будем питать проект от четырех 1.5-вольтовых АА-батареек общим напряжением примерно 6 вольт.
Важно отметить, что хотя порт называется '''+12V''', для нашего проекта (и с поставленной перемычкой) подойдет напряжение в диапазоне между 6 и 12 вольтами. Мы будем питать проект от четырех 1.5-вольтовых АА-батареек общим напряжением примерно 6 вольт.
Строка 152: Строка 149:
Начинаем с того, что задаем в двух переменных ниже [[SSID]] и пароль для локальной [[WiFi]]-сети, к которой нужно подключить [[ESP32]].
Начинаем с того, что задаем в двух переменных ниже [[SSID]] и пароль для локальной [[WiFi]]-сети, к которой нужно подключить [[ESP32]].


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
Строка 161: Строка 158:
Далее создаем переменные для входных контактов (IN1, IN2, IN3, IN4), а также для контактов управления скоростью вращения моторов (ENA и ENB) драйвера моторов [[L298N]].
Далее создаем переменные для входных контактов (IN1, IN2, IN3, IN4), а также для контактов управления скоростью вращения моторов (ENA и ENB) драйвера моторов [[L298N]].


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
// мотор 1:
// мотор 1:
int motor1Pin1 = 27;
int motor1Pin1 = 27;
Строка 177: Строка 174:
Если мы хотим управлять скоростью вращения моторов, нам нужно будет отправлять [[ШИМ-сигнал]] на контакты '''ENA''' и '''ENB'''. Следовательно, нам нужно создать переменные для свойств [[ШИМ-сигнал]]а вроде частоты, канала [[ШИМ]], разрешения и коэффициента заполнения.
Если мы хотим управлять скоростью вращения моторов, нам нужно будет отправлять [[ШИМ-сигнал]] на контакты '''ENA''' и '''ENB'''. Следовательно, нам нужно создать переменные для свойств [[ШИМ-сигнал]]а вроде частоты, канала [[ШИМ]], разрешения и коэффициента заполнения.


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
// переменные для свойств широтно-импульсной модуляции (ШИМ):
// переменные для свойств широтно-импульсной модуляции (ШИМ):
const int freq = 30000;
const int freq = 30000;
Строка 185: Строка 182:
</syntaxhighlight>
</syntaxhighlight>


'''Примечание:''' Более подробно об использовании [[ШИМ]] вместе с [[ESP32]] читайте в руководстве[[ESP32:Примеры/Изменение_яркости_светодиода_при_помощи_ШИМ|«Изменение яркости светодиода при помощи ШИМ»]].
{{Примечание1|1=Более подробно об использовании [[ШИМ]] вместе с [[ESP32]] читайте в руководстве[[ESP32:Примеры/Изменение_яркости_светодиода_при_помощи_ШИМ|«Изменение яркости светодиода при помощи ШИМ»]].}}


=== setup() ===
=== setup() ===
Строка 191: Строка 188:
В блоке [[setup()]] делаем выходными контакты для моторов.
В блоке [[setup()]] делаем выходными контакты для моторов.


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
// переключаем контакты моторов в режим «OUTPUT»:
// переключаем контакты моторов в режим «OUTPUT»:
pinMode(motor1Pin1, OUTPUT);
pinMode(motor1Pin1, OUTPUT);
Строка 201: Строка 198:
Затем настраиваем [[ШИМ-канал]] при помощи заданных ранее свойств.
Затем настраиваем [[ШИМ-канал]] при помощи заданных ранее свойств.


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
ledcSetup(pwmChannel, freq, resolution);
ledcSetup(pwmChannel, freq, resolution);
</syntaxhighlight>
</syntaxhighlight>
Строка 207: Строка 204:
В следующих двух строчках подключаем этот [[ШИМ-канал]] к контактам '''ENA''' и '''ENB'''. Таким образом, на контакты '''ENA''' и '''ENB''' будет идти один и тот же [[ШИМ-сигнал]].
В следующих двух строчках подключаем этот [[ШИМ-канал]] к контактам '''ENA''' и '''ENB'''. Таким образом, на контакты '''ENA''' и '''ENB''' будет идти один и тот же [[ШИМ-сигнал]].


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
ledcAttachPin(enable1Pin, pwmChannel);  
ledcAttachPin(enable1Pin, pwmChannel);  
ledcAttachPin(enable2Pin, pwmChannel);
ledcAttachPin(enable2Pin, pwmChannel);
Строка 214: Строка 211:
Наконец, при помощи функции [[ledcWrite()]] генерируем [[ШИМ-сигнал]] с заранее заданным коэффициентом заполнения.
Наконец, при помощи функции [[ledcWrite()]] генерируем [[ШИМ-сигнал]] с заранее заданным коэффициентом заполнения.


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
ledcWrite(pwmChannel, dutyCycle);
ledcWrite(pwmChannel, dutyCycle);
</syntaxhighlight>
</syntaxhighlight>
Строка 220: Строка 217:
Код ниже подключает [[ESP32]] к локальной [[WiFi]]-сети и печатает в монитор порта ее [[IP-адрес]].
Код ниже подключает [[ESP32]] к локальной [[WiFi]]-сети и печатает в монитор порта ее [[IP-адрес]].


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
// подключаемся к WiFi-сети при помощи заданных выше SSID и пароля:
// подключаемся к WiFi-сети при помощи заданных выше SSID и пароля:
Serial.print("Connecting to ");  //  "Подключаемся к "
Serial.print("Connecting to ");  //  "Подключаемся к "
Строка 242: Строка 239:
В блоке [[loop()]] [[ESP32]] всегда будет прослушивать входящих клиентов и при получении запроса сохранять входящие данные.
В блоке [[loop()]] [[ESP32]] всегда будет прослушивать входящих клиентов и при получении запроса сохранять входящие данные.


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
WiFiClient client = server.available();  // Запускаем прослушку  
WiFiClient client = server.available();  // Запускаем прослушку  
                                         // входящих клиентов.
                                         // входящих клиентов.
Строка 286: Строка 283:
Например, чтобы повернуть робота влево, всем контактам нужно отправить значение '''«[[LOW]]»''' (кроме '''«motor2Pin2»''', которому нужно отправить '''«[[HIGH]]»''').
Например, чтобы повернуть робота влево, всем контактам нужно отправить значение '''«[[LOW]]»''' (кроме '''«motor2Pin2»''', которому нужно отправить '''«[[HIGH]]»''').


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
// Этот код отвечает за управление контактами моторов
// Этот код отвечает за управление контактами моторов
// согласно тому, какие нажаты кнопки на веб-странице:
// согласно тому, какие нажаты кнопки на веб-странице:
Строка 328: Строка 325:
Фрагмент кода ниже показывает в вашем браузере веб-страницу с интерфейсом для управления роботом. Он состоит из 5 кнопок (вперед, назад, влево, вправо, стоп) и ползунка для управления скоростью.  
Фрагмент кода ниже показывает в вашем браузере веб-страницу с интерфейсом для управления роботом. Он состоит из 5 кнопок (вперед, назад, влево, вправо, стоп) и ползунка для управления скоростью.  


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
// Показываем веб-страницу:
// Показываем веб-страницу:
client.println("<!DOCTYPE HTML><html>");
client.println("<!DOCTYPE HTML><html>");
Строка 365: Строка 362:
</syntaxhighlight>
</syntaxhighlight>


'''Примечание:''' О создании веб-страниц для веб-сервера подробно рассказывается в руководстве [[ESP32:Примеры/Веб-сервер_на_базе_ESP32:_управление_выходными_контактами|«Веб-сервер на базе ESP32: управление выходными контактами»]].
{{Примечание1|1=О создании веб-страниц для веб-сервера подробно рассказывается в руководстве [[ESP32:Примеры/Веб-сервер_на_базе_ESP32:_управление_выходными_контактами|«Веб-сервер на базе ESP32: управление выходными контактами»]].}}


=== Управляем скоростью мотора ===
=== Управляем скоростью мотора ===
Строка 371: Строка 368:
Во фрагменте кода ниже мы считываем значение ползунка и, исходя из него, меняем скорость мотора. Такой же метод используется в руководстве [[ESP32:Примеры/Веб-сервер_на_базе_ESP32:_удаленное_управление_сервоприводом|«Веб-сервер на базе ESP32: удаленное управление сервоприводом»]].
Во фрагменте кода ниже мы считываем значение ползунка и, исходя из него, меняем скорость мотора. Такой же метод используется в руководстве [[ESP32:Примеры/Веб-сервер_на_базе_ESP32:_удаленное_управление_сервоприводом|«Веб-сервер на базе ESP32: удаленное управление сервоприводом»]].


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
// Пример HTTP-запроса: «GET /?value=100& HTTP/1.1»;
// Пример HTTP-запроса: «GET /?value=100& HTTP/1.1»;
// Он задает коэффициент заполнения ШИМ на 100% (255):
// Он задает коэффициент заполнения ШИМ на 100% (255):
Строка 398: Строка 395:
Если ползунок стоит на значении '''«0»''', моторы не будут работать. То есть мы задаем [[ШИМ-канал]]у коэффициент заполнения '''«0»''' и отправляем всем контактам моторов значения '''«[[LOW]]»'''.
Если ползунок стоит на значении '''«0»''', моторы не будут работать. То есть мы задаем [[ШИМ-канал]]у коэффициент заполнения '''«0»''' и отправляем всем контактам моторов значения '''«[[LOW]]»'''.


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
if (valueString == "0") {
if (valueString == "0") {
  ledcWrite(pwmChannel, 0);
  ledcWrite(pwmChannel, 0);
Строка 410: Строка 407:
Если на ползунке стоит ненулевое значение, то моторы начнут вращаться. Расчет коэффициента заполнения на основе значения ползунка осуществляется при помощи [[Arduino-функции]] [[Arduino:Справочник_языка_Arduino/Функции/Математические_функции/map()|map()]]. В ней начальным значением коэффициента заполнения задано '''«200»''', т.к. если задать меньше, робот просто не будет двигаться (моторы лишь будут издавать странное жужжание).
Если на ползунке стоит ненулевое значение, то моторы начнут вращаться. Расчет коэффициента заполнения на основе значения ползунка осуществляется при помощи [[Arduino-функции]] [[Arduino:Справочник_языка_Arduino/Функции/Математические_функции/map()|map()]]. В ней начальным значением коэффициента заполнения задано '''«200»''', т.к. если задать меньше, робот просто не будет двигаться (моторы лишь будут издавать странное жужжание).


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
else {
else {
  dutyCycle = map(valueString.toInt(), 25, 100, 200, 255);
  dutyCycle = map(valueString.toInt(), 25, 100, 200, 255);
Строка 438: Строка 435:
Теперь, если все настроено правильно, в браузере должен появиться веб-интерфейс с кнопками и ползунком, с помощью которого можно будет управлять машинкой-роботом.
Теперь, если все настроено правильно, в браузере должен появиться веб-интерфейс с кнопками и ползунком, с помощью которого можно будет управлять машинкой-роботом.


'''Важное примечание:''' Если моторы крутятся в неправильном направлении, нужно просто поменять местами провода, подключенные к моторам. Например, провода, подключенные к контактам '''OUT1''' и '''OUT2''' или '''OUT3''' и '''OUT4'''. Это должно решить проблему.
{{Внимание1|1=Если моторы крутятся в неправильном направлении, нужно просто поменять местами провода, подключенные к моторам. Например, провода, подключенные к контактам '''OUT1''' и '''OUT2''' или '''OUT3''' и '''OUT4'''. Это должно решить проблему.}}


Поздравляем! Вы своими руками сделали робота с дистанционным WiFi-управлением! Он может перемещаться вперед, назад, влево и вправо. Вы также можете остановить его, нажав на кнопку '''«STOP»'''. Более того, вы даже можете менять скорость движения робота при помощи ползунка.  
Поздравляем! Вы своими руками сделали робота с дистанционным WiFi-управлением! Он может перемещаться вперед, назад, влево и вправо. Вы также можете остановить его, нажав на кнопку '''«STOP»'''. Более того, вы даже можете менять скорость движения робота при помощи ползунка.  
Строка 467: Строка 464:
==Схема==
==Схема==


{{Спойлер|На этой схеме изображена 36-контактная версия платы [[ESP32 DEVKIT DOIT V1]], поэтому если у вас какая-то другая модель, обязательно сверьтесь с ее распиновкой.}}
{{Примечание1|На этой схеме изображена 36-контактная версия платы [[ESP32 DEVKIT DOIT V1]], поэтому если у вас какая-то другая модель, обязательно сверьтесь с ее распиновкой.}}


[[File:wifi_robot_esp32_1.PNG|center]]
[[File:wifi_robot_esp32_1.PNG|center]]
Строка 498: Строка 495:
==Код==
==Код==


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
/*********
/*********
   Руи Сантос
   Руи Сантос
Строка 733: Строка 730:


<references />
<references />
{{Навигационная таблица/Портал/ESP32}}


[[Категория:ESP32]]
[[Категория:ESP32]]

Текущая версия от 17:03, 17 июня 2023

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


Машинка-робот с дистанционным WiFi-управлением

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

В этом проекте затрагивается несколько тем, о которых уже рассказывалось в других руководствах по ESP32 на нашем сайте. Поэтому для лучшего понимания того, что будет написано ниже, советуем с ними ознакомиться (если еще не ознакомились). Вот они:

Обзор проекта

Перед тем, как начать, давайте рассмотрим главные функции и компоненты, при помощи которых этот проект будет воплощен в жизнь.

WiFi

Робот будет управляться через WiFi при помощи ESP32. Мы создадим веб-интерфейс, с помощью которого можно будет управлять роботом, и доступ к этому интерфейсу можно будет получить с любого устройства (ПК, ноутбука, планшета и т.д.), подключенного к вашей локальной сети.

Веб-интерфейс для управления роботом

Веб-интерфейс будет состоять из 6 компонентов: 5 кнопок вверху (вперед, назад, влево, вправо и стоп) и одного ползунка внизу (для изменения скорости движения, на нем можно выбрать скорость 0%, 25%, 50%, 75% и 100%).

Примечание

О том, как создавать кнопки, читайте в руководстве «Веб-сервер на базе ESP32: управление выходными контактами», а о создании ползунка – в руководстве «Веб-сервер на базе ESP32: удаленное управление сервоприводом».

Шасси

Мы будем использовать набор, показанный на картинке ниже. Он называется Smart Robot Chassis Kit. Его можно купить в большинстве онлайн-магазинов. Он стоит около 10 долларов и очень легко собирается. Подойдет и любой другой набор, но в нем также должны быть два DC-мотора.

Драйвер моторов L298N

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

Питание

Моторам требуется много электроэнергии, поэтому нам также понадобится внешний источник питания. Это значит, что нам будут нужны два разных источника питания – один для DC-моторов, и один для ESP32. Плату ESP32 мы будем питать от пауэрбанка (вроде тех, что используются для зарядки смартфонов).

Моторы будут питаться от четырех 1.5-вольтовых АА-батареек. Подойдут, впрочем, и перезаряжаемые батарейки, и любой другой подходящий источник питания.

Справочная информация

Краткое введение в драйвер моторов L298N

Как уже говорилось выше, есть много разных способов для управления DC-моторами. Наш метод подходит для большинства моторов, используемых в любительских проектах и требующих 6-12 вольт. Мы воспользуемся драйвером моторов L298N, который может управлять нагрузкой до 3 ампер при 35 вольтах, что как раз подходит для нашего проекта. Он также позволяет одновременно управлять двумя DC-моторами, что идеально для создания робота на колесах.

Распиновка драйвера моторов L298N

Давайте взглянем на контакты драйвера моторов L298N и разберемся, как он работает.

Драйвер моторов L298N оснащен двумя клеммниками, которые находятся по разные стороны платы. Левый клеммник состоит из портов OUT1 и OUT2, а правый – из портов OUT3 и OUT4.

  • OUT1 – плюсовой порт для DC-мотора А;
  • OUT2 – минусовой порт для DC-мотора А;
  • OUT3 – плюсовой порт для DC-мотора B;
  • OUT4 – минусовой порт для DC-мотора B;

Внизу находится еще один клеммник, но уже на 3 порта: +12V, GND и +5V. Порт +12V используется для питания моторов. Порт +5V используется для питания чипа L298N. Но если поставить перемычку (на изображении выше она помечена оранжевым цветом), чип будет питаться от питания для DC-моторов. В этом случае подавать 5-вольтовое питание на порт +5V не нужно.

Примечание

Если напряжение питания превышает 12 вольт, перемычку нужно снять и подать 5-вольтовое питание на порт +5V.

Важно отметить, что хотя порт называется +12V, для нашего проекта (и с поставленной перемычкой) подойдет напряжение в диапазоне между 6 и 12 вольтами. Мы будем питать проект от четырех 1.5-вольтовых АА-батареек общим напряжением примерно 6 вольт.

Итого:

  • +12V: Это порт, к которому нужно подключить питание;
  • GND: Это порт для заземления;
  • +5V: Если перемычка снята, на этот порт нужно подать 5-вольтовое питание. Если перемычка стоит, этот порт будет источником 5-вольтового питания;
  • Перемычка: Если перемычка на месте, чип питается от питания моторов. Если перемычка снята, на порт +5V нужно подать 5-вольтовое питание. Если напряжение питания превышает 12 вольт, перемычку нужно снять.

В правом нижнем углу платы располагается 4 входных контакта, а по бокам от них – еще 2 контакта. 4 входных контакта предназначены для управления направлением вращения DC-моторов, а 2 контакта по бокам – для управления скоростью каждого мотора.

  • IN1 – входной контакт 1 для мотора А;
  • IN2 – входной контакт 2 для мотора А;
  • IN3 – входной контакт 1 для мотора B;
  • IN4 – входной контакт 2 для мотора B;
  • ENA – контакт для управления скоростью мотора A;
  • ENB – контакт для управления скоростью мотора B;

Рядом с контактами ENA и ENB находится по одному контакту +5V, и по умолчанию на них стоят перемычки. Чтобы управлять скоростью моторов, эти перемычки нужно снять.

Управление DC-моторами с помощью L298N

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

Контакты ENA и ENB

Контакты ENA и ENB (для управления скоростью моторов) используются как переключатели между режимами «вкл» и «выкл», но также могут принимать более вариативные ШИМ-значения.

Например:

  • Если подать на контакт ENA значение «HIGH», это разрешит управление мотором А на максимальной скорости;
  • Если подать на контакт ENA значение «LOW», это выключит мотор А;
  • Если подать ШИМ-сигнал на контакт ENA, это позволит управлять скоростью мотора. Скорость мотора пропорциональна коэффициенту заполнения ШИМ. Но помните, что если задать маленький коэффициент заполнения, моторы, возможно, просто не будут вращаться, а лишь постоянно жужжать.
Сигнал, подаваемый на ENA или ENB Как ведет себя мотор
HIGH Мотор работает на максимальной скорости
LOW Мотор не работает
ШИМ Мотор работает (скорость пропорциональна коэффициенту заполнения)
Входные контакты

Входные контакты предназначены для управления направлением вращения моторов. Контакты IN1 и IN2 управляют мотором А, а контакты IN3 и IN4мотором B.

Если подать на контакт IN1 значение LOW, а на IN2 – значение HIGH, мотор A будет крутиться вперед. Если инвертировать значения (на IN1 подать HIGH, а на IN2LOW), мотор A будет крутиться назад. Мотор B управляется аналогичным образом.

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

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

НАПРАВЛЕНИЕ INPUT 1 INPUT 2 INPUT 3 INPUT 4
Вперед 0 1 0 1
Назад 1 0 1 0
Вправо 0 1 0 0
Влево 0 0 0 1
Стоп 0 0 0 0

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

Мы подробно рассказывали о коде для создания веб-сервера на базе ESP32 в других руководствах. Поэтому здесь мы затронем лишь те фрагменты, что касаются нашего проекта.

Задаем настройки для подключения к WiFi

Начинаем с того, что задаем в двух переменных ниже SSID и пароль для локальной WiFi-сети, к которой нужно подключить ESP32.

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

Создаем переменные для контактов драйвера моторов L298N

Далее создаем переменные для входных контактов (IN1, IN2, IN3, IN4), а также для контактов управления скоростью вращения моторов (ENA и ENB) драйвера моторов L298N.

// мотор 1:
int motor1Pin1 = 27;
int motor1Pin2 = 26;
int enable1Pin = 14;

// мотор 2:
int motor2Pin1 = 33;
int motor2Pin2 = 25;
int enable2Pin = 32;

Задаем свойства ШИМ

Если мы хотим управлять скоростью вращения моторов, нам нужно будет отправлять ШИМ-сигнал на контакты ENA и ENB. Следовательно, нам нужно создать переменные для свойств ШИМ-сигнала вроде частоты, канала ШИМ, разрешения и коэффициента заполнения.

// переменные для свойств широтно-импульсной модуляции (ШИМ):
const int freq = 30000;
const int pwmChannel = 0;
const int resolution = 8;
int dutyCycle = 0;
Примечание

Более подробно об использовании ШИМ вместе с ESP32 читайте в руководстве«Изменение яркости светодиода при помощи ШИМ».

setup()

В блоке setup() делаем выходными контакты для моторов.

// переключаем контакты моторов в режим «OUTPUT»:
pinMode(motor1Pin1, OUTPUT);
pinMode(motor1Pin2, OUTPUT);
pinMode(motor2Pin1, OUTPUT);
pinMode(motor2Pin2, OUTPUT);

Затем настраиваем ШИМ-канал при помощи заданных ранее свойств.

ledcSetup(pwmChannel, freq, resolution);

В следующих двух строчках подключаем этот ШИМ-канал к контактам ENA и ENB. Таким образом, на контакты ENA и ENB будет идти один и тот же ШИМ-сигнал.

ledcAttachPin(enable1Pin, pwmChannel); 
ledcAttachPin(enable2Pin, pwmChannel);

Наконец, при помощи функции ledcWrite() генерируем ШИМ-сигнал с заранее заданным коэффициентом заполнения.

ledcWrite(pwmChannel, dutyCycle);

Код ниже подключает ESP32 к локальной WiFi-сети и печатает в монитор порта ее IP-адрес.

// подключаемся к 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();

loop()

В блоке loop() ESP32 всегда будет прослушивать входящих клиентов и при получении запроса сохранять входящие данные.

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();

Управляем роботом

В следующем фрагменте задано несколько операторов if() и else(), с помощью которых проверяется, какая команда отправляется роботу – «вперед», «назад», «влево», «вправо» или «стоп». Затем контактам моторов отправляются значения, соответствующие выбранной команде.

Например, чтобы повернуть робота влево, всем контактам нужно отправить значение «LOW» (кроме «motor2Pin2», которому нужно отправить «HIGH»).

// Этот код отвечает за управление контактами моторов
// согласно тому, какие нажаты кнопки на веб-странице:
if (header.indexOf("GET /forward") >= 0) {
  Serial.println("Forward");  //  "Вперед"
  digitalWrite(motor1Pin1, LOW);
  digitalWrite(motor1Pin2, HIGH); 
  digitalWrite(motor2Pin1, LOW);
  digitalWrite(motor2Pin2, HIGH);
}  else if (header.indexOf("GET /left") >= 0) {
  Serial.println("Left");  //  "Влево"
  digitalWrite(motor1Pin1, LOW); 
  digitalWrite(motor1Pin2, LOW); 
  digitalWrite(motor2Pin1, LOW);
  digitalWrite(motor2Pin2, HIGH);
}  else if (header.indexOf("GET /stop") >= 0) {
  Serial.println("Stop");  //  "Стоп"
  digitalWrite(motor1Pin1, LOW); 
  digitalWrite(motor1Pin2, LOW); 
  digitalWrite(motor2Pin1, LOW);
  digitalWrite(motor2Pin2, LOW);             
} else if (header.indexOf("GET /right") >= 0) {
  Serial.println("Right");  //  "Вправо"
  digitalWrite(motor1Pin1, LOW); 
  digitalWrite(motor1Pin2, HIGH); 
  digitalWrite(motor2Pin1, LOW);
  digitalWrite(motor2Pin2, LOW);    
} else if (header.indexOf("GET /reverse") >= 0) {
  Serial.println("Reverse");  //  "Назад"
  digitalWrite(motor1Pin1, HIGH);
  digitalWrite(motor1Pin2, LOW); 
  digitalWrite(motor2Pin1, HIGH);
  digitalWrite(motor2Pin2, LOW);          
}

Как видите, каждая команда – предназначенная для того, чтобы переместить робота в каком-либо направлении или остановить его – представляет собой уникальную комбинацию сигналов «HIGH» и «LOW». Более подробно об этом рассказывалось выше, в разделе «Входные контакты».

Показываем веб-страницу

Фрагмент кода ниже показывает в вашем браузере веб-страницу с интерфейсом для управления роботом. Он состоит из 5 кнопок (вперед, назад, влево, вправо, стоп) и ползунка для управления скоростью.

// Показываем веб-страницу:
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:,\">");
// При помощи CSS задаем стиль кнопок.
// Попробуйте поэкспериментировать
// с атрибутами «background-color» и «font-size»,
// чтобы стилизовать кнопки согласно своим предпочтениям: 
client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");
client.println(".button { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; background-color: #4CAF50;");
client.println("border: none; color: white; padding: 12px 28px; text-decoration: none; font-size: 26px; margin: 1px; cursor: pointer;}");
client.println(".button2 {background-color: #555555;}</style>");
client.println("<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js\"></script></head>");
            
// веб-страница:        
client.println("<p><button class=\"button\" onclick=\"moveForward()\">FORWARD</button></p>");
client.println("<div style=\"clear: both;\"><p><button class=\"button\" onclick=\"moveLeft()\">LEFT </button>");
client.println("<button class=\"button button2\" onclick=\"stopRobot()\">STOP</button>");
client.println("<button class=\"button\" onclick=\"moveRight()\">RIGHT</button></p></div>");
client.println("<p><button class=\"button\" onclick=\"moveReverse()\">REVERSE</button></p>");
client.println("<p>Motor Speed: <span id=\"motorSpeed\"></span></p>");          
client.println("<input type=\"range\" min=\"0\" max=\"100\" step=\"25\" id=\"motorSlider\" onchange=\"motorSpeed(this.value)\" value=\"" + valueString + "\"/>");
            
client.println("<script>$.ajaxSetup({timeout:1000});");
client.println("function moveForward() { $.get(\"/forward\"); {Connection: close};}");
client.println("function moveLeft() { $.get(\"/left\"); {Connection: close};}");
client.println("function stopRobot() {$.get(\"/stop\"); {Connection: close};}");
client.println("function moveRight() { $.get(\"/right\"); {Connection: close};}");
client.println("function moveReverse() { $.get(\"/reverse\"); {Connection: close};}");
client.println("var slider = document.getElementById(\"motorSlider\");");
client.println("var motorP = document.getElementById(\"motorSpeed\"); motorP.innerHTML = slider.value;");
client.println("slider.oninput = function() { slider.value = this.value; motorP.innerHTML = this.value; }");
client.println("function motorSpeed(pos) { $.get(\"/?value=\" + pos + \"&\"); {Connection: close};}</script>");
client.println("</html>");
Примечание

О создании веб-страниц для веб-сервера подробно рассказывается в руководстве «Веб-сервер на базе ESP32: управление выходными контактами».

Управляем скоростью мотора

Во фрагменте кода ниже мы считываем значение ползунка и, исходя из него, меняем скорость мотора. Такой же метод используется в руководстве «Веб-сервер на базе ESP32: удаленное управление сервоприводом».

// Пример HTTP-запроса: «GET /?value=100& HTTP/1.1»;
// Он задает коэффициент заполнения ШИМ на 100% (255):
if(header.indexOf("GET /?value=")>=0) {
  pos1 = header.indexOf('=');
  pos2 = header.indexOf('&');
  valueString = header.substring(pos1+1, pos2);
  // Задаем скорость мотора:
  if (valueString == "0") {
    ledcWrite(pwmChannel, 0);
    digitalWrite(motor1Pin1, LOW); 
    digitalWrite(motor1Pin2, LOW); 
    digitalWrite(motor2Pin1, LOW);
    digitalWrite(motor2Pin2, LOW);   
  }
  else { 
    dutyCycle = map(valueString.toInt(), 25, 100, 200, 255);
    ledcWrite(pwmChannel, dutyCycle);
    Serial.println(valueString);
  } 
}

В переменную «valueString» сохраняется текущее значение ползунка.

Если ползунок стоит на значении «0», моторы не будут работать. То есть мы задаем ШИМ-каналу коэффициент заполнения «0» и отправляем всем контактам моторов значения «LOW».

if (valueString == "0") {
 ledcWrite(pwmChannel, 0);
 digitalWrite(motor1Pin1, LOW);
 digitalWrite(motor1Pin2, LOW);
 digitalWrite(motor2Pin1, LOW);
 digitalWrite(motor2Pin2, LOW);
}

Если на ползунке стоит ненулевое значение, то моторы начнут вращаться. Расчет коэффициента заполнения на основе значения ползунка осуществляется при помощи Arduino-функции map(). В ней начальным значением коэффициента заполнения задано «200», т.к. если задать меньше, робот просто не будет двигаться (моторы лишь будут издавать странное жужжание).

else {
 dutyCycle = map(valueString.toInt(), 25, 100, 200, 255);
 ledcWrite(pwmChannel, dutyCycle);
 Serial.println(valueString);
}

После этого мы задаем коэффициент заполнения для ШИМ-канала, с его помощью и управляя скоростью моторов.

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

Теперь давайте протестируем веб-сервер. Убедитесь, что вставили в код SSID и пароль для своей WiFi-сети. Также проверьте, правильные ли в IDE Arduino выбраны COM-порт и плата. После этого жмите на кнопку «Загрузка». Когда загрузка завершится, откройте монитор порта на скорости 115200 бод.

Чтобы узнать IP-адрес ESP32, нажмите на ней на кнопку EN.

Отключите ESP32 от компьютера и запитайте от пауэрбанка.

Убедитесь, что все четыре АА-батарейки стоят на месте, а движковый переключатель стоит в положении «вкл».

Откройте браузер и впишите в адресную строку IP-адрес ESP32, чтобы получить доступ к веб-серверу. Этот веб-сервер можно открыть на любом устройстве (ПК, ноутбуке, смартфоне и т.д.), подключенном к вашей локальной сети, и с его помощью управлять роботом.

Теперь, если все настроено правильно, в браузере должен появиться веб-интерфейс с кнопками и ползунком, с помощью которого можно будет управлять машинкой-роботом.

Внимание!

Если моторы крутятся в неправильном направлении, нужно просто поменять местами провода, подключенные к моторам. Например, провода, подключенные к контактам OUT1 и OUT2 или OUT3 и OUT4. Это должно решить проблему.

Поздравляем! Вы своими руками сделали робота с дистанционным WiFi-управлением! Он может перемещаться вперед, назад, влево и вправо. Вы также можете остановить его, нажав на кнопку «STOP». Более того, вы даже можете менять скорость движения робота при помощи ползунка.

Робот работает превосходно и мгновенно отвечает на команды.

Итого

Ну что, нравится робот? Советуем не останавливаться на достигнутом и попробовать оснастить машинку новыми фичами – например, так:

  • Подключите к ней RGB-светодиод и запрограммируйте ESP32 так, чтобы его цвет менялся в зависимости от направления, в котором едет машинка;
  • Подключите к ней ультразвуковой датчик, чтобы она останавливалась, увидев препятствие.

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

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

Схема

Примечание

На этой схеме изображена 36-контактная версия платы ESP32 DEVKIT DOIT V1, поэтому если у вас какая-то другая модель, обязательно сверьтесь с ее распиновкой.

Начинаем с подключения ESP32 к драйверу моторов. Для размещения ESP32 и построения цепи можно воспользоваться либо небольшой контактной, либо печатной макетной платой. В таблице ниже показано, с помощью каких контактов ESP32 и драйвер моторов L298N необходимо подключить друг к другу.

Драйвер моторов L298N ESP32
IN1 GPIO27
IN2 GPIO26
ENA GPIO14
IN3 GPIO33
IN4 GPIO25
ENB GPIO32

После этого подключите оба мотора к клеммникам драйвера моторов L298N. В целях ослабления скачков напряжения мы рекомендуем припаять к плюсовому и минусовому контактам каждого мотора керамический конденсатор на 100 нФ (как показано на схеме выше).

Кроме того, можно припаять движковый переключатель к красному проводу, идущему от батареек. Благодаря ему вы сможете включать/отключать питание, идущее к моторам и драйверу моторов.

Наконец, запитайте моторы при помощи четырех АА-батареек, подключив их к портам питания драйвера моторов. Так как наш робот должен быть портативным, ESP32 будет питаться от пауэрбанка. Прикрепите его к шасси, например, при помощи ленты-липучки.

Код

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

// загружаем библиотеку для WiFi:
#include <WiFi.h>

// вставляем ниже SSID и пароль для своей WiFi-сети:
const char* ssid     = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// создаем объект сервера и задаем ему порт «80»:
WiFiServer server(80);

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

// мотор 1:
int motor1Pin1 = 27; 
int motor1Pin2 = 26; 
int enable1Pin = 14; 

// мотор 2:
int motor2Pin1 = 33; 
int motor2Pin2 = 25; 
int enable2Pin = 32;

// переменные для свойств широтно-импульсной модуляции (ШИМ):
const int freq = 30000;
const int pwmChannel = 0;
const int resolution = 8;
int dutyCycle = 0;

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

void setup() {
  Serial.begin(115200);
  
  // переключаем контакты моторов в режим «OUTPUT»:
  pinMode(motor1Pin1, OUTPUT);
  pinMode(motor1Pin2, OUTPUT);
  pinMode(motor2Pin1, OUTPUT);
  pinMode(motor2Pin2, OUTPUT);

  // задаем настройки ШИМ-канала:
  ledcSetup(pwmChannel, freq, resolution);
  
  // подключаем ШИМ-канал 0 к контактам ENA и ENB,
  // т.е. к GPIO-контактам для управления скоростью вращения моторов:
  ledcAttachPin(enable1Pin, pwmChannel);
  ledcAttachPin(enable2Pin, pwmChannel);

  // подаем на контакты ENA и ENB 
  // ШИМ-сигнал с коэффициентом заполнения «0»:
  ledcWrite(pwmChannel, dutyCycle);
  
  // подключаемся к 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();
            
            // Этот код отвечает за управление контактами моторов
            // согласно тому, какие нажаты кнопки на веб-странице:
            if (header.indexOf("GET /forward") >= 0) {
              Serial.println("Forward");  //  "Вперед"
              digitalWrite(motor1Pin1, LOW);
              digitalWrite(motor1Pin2, HIGH); 
              digitalWrite(motor2Pin1, LOW);
              digitalWrite(motor2Pin2, HIGH);
            }  else if (header.indexOf("GET /left") >= 0) {
              Serial.println("Left");  //  "Влево"
              digitalWrite(motor1Pin1, LOW); 
              digitalWrite(motor1Pin2, LOW); 
              digitalWrite(motor2Pin1, LOW);
              digitalWrite(motor2Pin2, HIGH);
            }  else if (header.indexOf("GET /stop") >= 0) {
              Serial.println("Stop");  //  "Стоп"
              digitalWrite(motor1Pin1, LOW); 
              digitalWrite(motor1Pin2, LOW); 
              digitalWrite(motor2Pin1, LOW);
              digitalWrite(motor2Pin2, LOW);             
            } else if (header.indexOf("GET /right") >= 0) {
              Serial.println("Right");  //  "Вправо"
              digitalWrite(motor1Pin1, LOW); 
              digitalWrite(motor1Pin2, HIGH); 
              digitalWrite(motor2Pin1, LOW);
              digitalWrite(motor2Pin2, LOW);    
            } else if (header.indexOf("GET /reverse") >= 0) {
              Serial.println("Reverse");  //  "Назад"
              digitalWrite(motor1Pin1, HIGH);
              digitalWrite(motor1Pin2, LOW); 
              digitalWrite(motor2Pin1, HIGH);
              digitalWrite(motor2Pin2, LOW);          
            }
            // Показываем веб-страницу:
            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:,\">");
            // При помощи CSS задаем стиль кнопок.
            // Попробуйте поэкспериментировать
            // с атрибутами «background-color» и «font-size»,
            // чтобы стилизовать кнопки согласно своим предпочтениям: 
            client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");
            client.println(".button { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; background-color: #4CAF50;");
            client.println("border: none; color: white; padding: 12px 28px; text-decoration: none; font-size: 26px; margin: 1px; cursor: pointer;}");
            client.println(".button2 {background-color: #555555;}</style>");
            client.println("<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js\"></script></head>");
            
            // веб-страница:        
            client.println("<p><button class=\"button\" onclick=\"moveForward()\">FORWARD</button></p>");
            client.println("<div style=\"clear: both;\"><p><button class=\"button\" onclick=\"moveLeft()\">LEFT </button>");
            client.println("<button class=\"button button2\" onclick=\"stopRobot()\">STOP</button>");
            client.println("<button class=\"button\" onclick=\"moveRight()\">RIGHT</button></p></div>");
            client.println("<p><button class=\"button\" onclick=\"moveReverse()\">REVERSE</button></p>");
            client.println("<p>Motor Speed: <span id=\"motorSpeed\"></span></p>");          
            client.println("<input type=\"range\" min=\"0\" max=\"100\" step=\"25\" id=\"motorSlider\" onchange=\"motorSpeed(this.value)\" value=\"" + valueString + "\"/>");
            
            client.println("<script>$.ajaxSetup({timeout:1000});");
            client.println("function moveForward() { $.get(\"/forward\"); {Connection: close};}");
            client.println("function moveLeft() { $.get(\"/left\"); {Connection: close};}");
            client.println("function stopRobot() {$.get(\"/stop\"); {Connection: close};}");
            client.println("function moveRight() { $.get(\"/right\"); {Connection: close};}");
            client.println("function moveReverse() { $.get(\"/reverse\"); {Connection: close};}");
            client.println("var slider = document.getElementById(\"motorSlider\");");
            client.println("var motorP = document.getElementById(\"motorSpeed\"); motorP.innerHTML = slider.value;");
            client.println("slider.oninput = function() { slider.value = this.value; motorP.innerHTML = this.value; }");
            client.println("function motorSpeed(pos) { $.get(\"/?value=\" + pos + \"&\"); {Connection: close};}</script>");
           
            client.println("</html>");
            
            // Пример HTTP-запроса: «GET /?value=100& HTTP/1.1»;
            // Он задает коэффициент заполнения ШИМ на 100% (255):
            if(header.indexOf("GET /?value=")>=0) {
              pos1 = header.indexOf('=');
              pos2 = header.indexOf('&');
              valueString = header.substring(pos1+1, pos2);
              // Задаем скорость мотора:
              if (valueString == "0") {
                ledcWrite(pwmChannel, 0);
                digitalWrite(motor1Pin1, LOW); 
                digitalWrite(motor1Pin2, LOW); 
                digitalWrite(motor2Pin1, LOW);
                digitalWrite(motor2Pin2, LOW);   
              }
              else { 
                dutyCycle = map(valueString.toInt(), 25, 100, 200, 255);
                ledcWrite(pwmChannel, dutyCycle);
                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("");
  }
}

См.также

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