Проект «МАШИНА»: Управление с помощью Elbear ace-uno и ESP8266

от автора

Здравствуйте! Перебирая вещи, я наткнулась на сломанную радиоуправляемую машинку и задумалась: почему бы не переделать её и не дать ей второй шанс с новыми функциями? У меня есть интересные платы, которые в последнее время лежат без дела, и я решила, что это отличная возможность их использовать.

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

Проект "МАШИНА"

Проект «МАШИНА»

Подключение:

HC-SR04:

  1. Trig (триггер) подключите к пину D3 (пин 3) Elbear ACE-UNO.

  2. Echo (эхо) подключите к пину D2 (пин 2) Elbear ACE-UNO.

  3. VCC (питание) подключите к 5V.

  4. GND (земля) подключите к GND.

Светодиод:

подключите к пину D12 (пин 12) Elbear ACE-UNO.

Сервопривод:

  1. Провод управления (обычно желтый или оранжевый) подключите к пину D9 (пин 9) Elbear ACE-UNO.

  2. Провод питания (обычно красный) подключите к 5V.

  3. Провод земли (обычно черный или коричневый) подключите к GND.

ESP8266:

  1. TX (передача) ESP8266 подключите к RX (прием) Elbear ACE-UNO (например, D0).

  2. RX (прием) ESP8266 подключите к TX (передача) Elbear ACE-UNO (например, D1).

  3. GND ESP8266 подключите к GND Elbear ACE-UNO.

  4. VCC ESP8266 подключите к 3.3V (обратите внимание, что ESP8266 работает на 3.3V).

Описание проекта

Проект «МАШИНА» представляет собой систему, которая может выполнять команды через последовательный порт и веб-интерфейс. Он включает в себя:

  1. Сервопривод для управления движением.

  2. Светодиоды для визуальной индикации состояния.

  3. Ультразвуковой датчик HC-SR04 для измерения расстояния.

  4. ESP8266 для создания веб-сервера, позволяющего управлять устройством через браузер.

Компоненты

  1. Elbear ACE-UNO (Российская Arduino-совместимая плата на отечественном микроконтроллере MIK32 АМУР (Микрон)): Основной контроллер, который управляет сервоприводом и светодиодами.

  2. ESP8266: Модуль Wi-Fi, который позволяет подключать устройство к сети и управлять им удаленно.

  3. HC-SR04: Датчик расстояния, который измеряет расстояние до объекта.

  4. Сервоприводы и светодиоды: Для управления движением и индикации.

Код Elbear ace-uno

#include <Servo.h> // Подключаем библиотеку, которая поможет управлять сервоприводом  // Указываем, к каким пинам подключены наши детали const int trigPin = 3; // Триггер (для отправки сигнала) подключен к пину D3 const int echoPin = 2; // Эхо (для получения сигнала) подключено к пину D2 const int ledPinD12 = 12; // Внешний светодиод подключен к пину D12 const int servoPin = 9; // Пин, через который будем управлять сервоприводом  Servo myServo; // Создаем "умную" машинку для управления сервоприводом  void setup() {   pinMode(LED_BUILTIN, OUTPUT); // Настраиваем встроенный светодиод как выход (можем включать/выключать)   pinMode(ledPinD12, OUTPUT); // Настраиваем D12 как выход для внешнего светодиода   Serial.begin(9600); // Начинаем общение с компьютером   Serial.println("Ready to receive commands..."); // Сообщаем, что мы готовы получать команды    myServo.attach(servoPin); // Подключаем наш сервопривод к указанному пину    // Настраиваем пины для ультразвукового датчика HC-SR04   pinMode(trigPin, OUTPUT); // Триггер будет отправлять сигнал   pinMode(echoPin, INPUT); // Эхо будет получать сигнал    // Устанавливаем сервопривод в положение 90 градусов (это значит, что он "стоит на месте")   myServo.write(90);   delay(1000); // Ждем 1 секунду, чтобы все стабилизировалось }  void loop() {   // Проверяем, есть ли новые команды от компьютера   if (Serial.available()) {     String command = Serial.readStringUntil('\n'); // Читаем команду до новой строки     command.trim(); // Убираем лишние пробелы     Serial.println("Received command: " + command); // Показываем, какую команду мы получили          // Включаем или выключаем светодиоды в зависимости от команды     if (command == "LED_ON") {       digitalWrite(LED_BUILTIN, HIGH); // Включаем встроенный светодиод       Serial.println("Built-in LED is ON"); // Подтверждаем, что светодиод включен     } else if (command == "LED_OFF") {       digitalWrite(LED_BUILTIN, LOW); // Выключаем встроенный светодиод       Serial.println("Built-in LED is OFF"); // Подтверждаем, что светодиод выключен     } else if (command == "EXTERNAL_LED_ON") {       digitalWrite(ledPinD12, HIGH); // Включаем внешний светодиод на D12       Serial.println("External LED on D12 is ON"); // Подтверждаем, что внешний светодиод включен     } else if (command == "EXTERNAL_LED_OFF") {       digitalWrite(ledPinD12, LOW); // Выключаем внешний светодиод на D12       Serial.println("External LED on D12 is OFF"); // Подтверждаем, что внешний светодиод выключен     } else if (command == "STOP") {       myServo.write(110); // Устанавливаем угол 110 (это значит, что сервопривод останавливается)       Serial.println("Servo angle set to STOP (110)"); // Подтверждаем, что сервопривод остановлен     } else if (command == "BACKWARD") {       myServo.write(0); // Устанавливаем угол 0 (это значит, что сервопривод движется назад)       Serial.println("Servo angle set to BACKWARD (0)"); // Подтверждаем, что сервопривод движется назад     } else if (command == "FORWARD") {       myServo.write(180); // Устанавливаем угол 180 (это значит, что сервопривод движется вперед)       Serial.println("Servo angle set to FORWARD (180)"); // Подтверждаем, что сервопривод движется вперед     } else if (command == "MEASURE_DISTANCE") {       long distance = measureDistance(); // Измеряем расстояние       Serial.println("Distance: " + String(distance) + " cm"); // Выводим расстояние в сантиметрах     } else {       Serial.println("Unknown command"); // Если команда не распознана, сообщаем об этом 

Код esp8266

#include <ESP8266WiFi.h> // Подключаем библиотеку для работы с Wi-Fi #include <ESP8266WebServer.h> // Подключаем библиотеку для создания веб-сервера  const char* ssid = "******"; // Имя Wi-Fi сети const char* password = "********"; // Пароль от Wi-Fi сети  // Настройки статического IP-адреса и сети IPAddress local_IP(192, 168, 1, 109);  // Устанавливаем желаемый статический IP IPAddress gateway(192, 168, 1, 1);      // Шлюз (обычно это IP вашего роутера) IPAddress subnet(255, 255, 255, 0);     // Подсеть  ESP8266WebServer server(80); // Создаем веб-сервер на порту 80 String lastCommands[5]; // Массив для хранения последних 5 команд int commandIndex = 0;   // Индекс для добавления новых команд String serialOutput;     // Строка для хранения вывода в последовательный порт long measuredDistance;   // Переменная для хранения измеренного расстояния const int motorPin = D1; // Пин для управления мотором  void setup() {   Serial.begin(9600);   // Настраиваем последовательный порт для общения с устройством   pinMode(LED_BUILTIN, OUTPUT); // Настраиваем встроенный светодиод как выход   pinMode(motorPin, OUTPUT); // Настраиваем пин для мотора как выход    // Настраиваем Wi-Fi в режиме клиента   WiFi.mode(WIFI_AP_STA); // Устанавливаем режим точки доступа и станции   WiFi.config(local_IP, gateway, subnet); // Устанавливаем статический IP   WiFi.begin(ssid, password); // Подключаемся к Wi-Fi сети    // Проверяем подключение к Wi-Fi   while (WiFi.status() != WL_CONNECTED) {     delay(1000); // Ждем 1 секунду, пока не подключимся   }    // Настраиваем точку доступа   WiFi.softAP("**********", "******"); // Создаем точку доступа с именем и паролем    // Регистрируем маршруты (команды)   server.on("/", handleRoot); // Главная страница   server.on("/sendCommand", handleSendCommand); // Маршрут для отправки команды   server.on("/sendSerialInput", handleSendSerialInput); // Маршрут для отправки ввода в последовательный порт   server.on("/sendMeasureDistance", handleSendMeasureDistance); // Маршрут для измерения расстояния   server.on("/connectedDevices", handleConnectedDevices); // Маршрут для получения информации о подключенных устройствах   server.begin(); // Запускаем сервер }  void loop() {   server.handleClient(); // Обрабатываем запросы от клиентов }  void processCommand(String command) {   // Проверяем, начинается ли команда с "MEASURE_DISTANCE"   if (command == "MEASURE_DISTANCE") {     Serial.println("Команда MEASURE_DISTANCE отправлена."); // Подтверждение отправки команды     while (!Serial.available()); // Ждем, пока не будет доступен ответ     int measuredDistance = Serial.parseInt(); // Читаем расстояние из последовательного порта     Serial.println("Измеренное расстояние: " + String(measuredDistance) + " см"); // Выводим измеренное расстояние     storeCommand("DISTANCE " + String(measuredDistance)); // Сохраняем команду с измеренным расстоянием   }    // Проверяем, начинается ли команда с "SET_SPEED_"   else if (command.startsWith("SET_SPEED_")) {     int speed = command.substring(10).toInt(); // Извлекаем скорость из команды     if (speed >= 0 && speed <= 50 && (speed % 10 == 0)) { // Проверяем, что скорость в допустимых пределах       analogWrite(motorPin, speed); // Устанавливаем скорость мотора       Serial.println("Скорость мотора установлена на: " + String(speed)); // Подтверждение       storeCommand("MOTOR SPEED " + String(speed)); // Сохраняем команду     } else {       Serial.println("Получена неверная команда скорости: " + command); // Сообщение об ошибке     }   }    // Проверяем команды для движения мотора   else if (command == "FORWARD") {     // Код для движения мотора вперед     Serial.println("Мотор движется ВПЕРЕД"); // Подтверждение     storeCommand("FORWARD"); // Сохраняем команду   }    else if (command == "BACKWARD") {     // Код для движения мотора назад     Serial.println("Мотор движется НАЗАД"); // Подтверждение     storeCommand("BACKWARD"); // Сохраняем команду   }    else if (command == "STOP") {     // Код для остановки мотора     Serial.println("Мотор ОСТАНОВЛЕН"); // Подтверждение     storeCommand("STOP"); // Сохраняем команду   }    else {     Serial.println("Получена неизвестная команда: " + command); // Сообщение о неизвестной команде   } }  void handleRoot() {   // Начинаем создание HTML-страницы   String html = "<html lang='en'><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width, initial-scale=1.0'>";   html += "<title>Измерение расстояния</title>"; // Заголовок страницы   html += "<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css'>"; // Подключаем стиль иконок   html += "<style>"; // Начинаем стили   html += "body { font-family: Arial, sans-serif; background-color: #f4f4f4; color: #333; margin: 0; padding: 20px; }"; // Стили для фона и текста   html += "h1 { text-align: center; color: #007BFF; }"; // Стили для заголовка   html += ".container { max-width: 600px; margin: auto; padding: 20px; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }"; // Стили для контейнера   html += ".grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin-bottom: 30px; }"; // Стили для сетки кнопок   html += "button { padding: 10px 15px; background: #007BFF; color: white; border: none; border-radius: 4px; cursor: pointer; }"; // Стили для кнопок   html += "button:hover { background: #0056b3; }"; // Стили для кнопок при наведении   html += "#output { margin-top: 20px; padding: 10px; background: #e9ecef; border-radius: 4px; }"; // Стили для вывода сообщений   html += "#measuredDistance { font-size: 1.5em; text-align: center; margin-top: 10px; }"; // Стили для отображения расстояния   html += "</style>"; // Закрываем стили   html += "<script src='https://code.jquery.com/jquery-3.6.0.min.js'></script>"; // Подключаем библиотеку jQuery для работы с JavaScript   html += "<script>"; // Начинаем скрипты   html += "function sendCommand() {"; // Функция для отправки команды   html += "  var command = $('#commandInput').val();"; // Получаем текст из поля ввода   html += "  $.post('/sendCommand', { command: command }, function(response) {"; // Отправляем команду на сервер   html += "    $('#output').html(response);"; // Отображаем ответ на странице   html += "    $('#commandInput').val('');"; // Очищаем поле ввода   html += "  });"; // Закрываем функцию   html += "}";   html += "function measureDistance() {"; // Функция для измерения расстояния   html += "  $.post('/sendMeasureDistance', function(response) {"; // Отправляем запрос на измерение   html += "    $('#measuredDistance').html(response);"; // Обновляем отображение расстояния   html += "  });"; // Закрываем функцию   html += "}";   html += "</script></head><body>"; // Закрываем заголовок и открываем тело страницы      html += "<div class='container'>"; // Начинаем контейнер для содержимого   html += "<h1>Интерфейс измерения расстояния</h1>"; // Заголовок интерфейса    // Кнопки для управления светодиодами   html += "<h2>Управление светодиодами:</h2>";   html += "<div class='grid'>"; // Начинаем сетку для кнопок   html += "<button onclick=\"$.post('/sendCommand', { command: 'LED_ON' }, function(response) { $('#output').html(response); });\">Включить встроенный светодиод</button>";   html += "<button onclick=\"$.post('/sendCommand', { command: 'LED_OFF' }, function(response) { $('#output').html(response); });\">Выключить встроенный светодиод</button>";   html += "<button onclick=\"$.post('/sendCommand', { command: 'EXTERNAL_LED_ON' }, function(response) { $('#output').html(response); });\">Включить внешний светодиод</button>";   html += "<button onclick=\"$.post('/sendCommand', { command: 'EXTERNAL_LED_OFF' }, function(response) { $('#output').html(response); });\">Выключить внешний светодиод</button>";   html += "</div>"; // Закрываем сетку кнопок для светодиодов    // Кнопки для управления сервоприводом   html += "<h2>Управление сервоприводом:</h2>";   html += "<div class='grid'>"; // Начинаем сетку для кнопок   html += "<button onclick=\"$.post('/sendCommand', { command: 'FORWARD' }, function(response) { $('#output').html(response); });\">Двигаться вперед</button>";   html += "<button onclick=\"$.post('/sendCommand', { command: 'BACKWARD' }, function(response) { $('#output').html(response); });\">Двигаться назад</button>";   html += "<button onclick=\"$.post('/sendCommand', { command: 'STOP' }, function(response) { $('#output').html(response); });\">Остановиться</button>";   html += "</div>"; // Закрываем сетку кнопок для сервопривода    // Кнопки и отображение расстояния   html += "<h2>Измерение расстояния:</h2>";   html += "<div class='grid'>"; // Начинаем сетку для кнопок измерения   html += "<button onclick='measureDistance()'>Измерить расстояние</button>"; // Кнопка для измерения расстояния   html += "<div id='measuredDistance'>" + String(measuredDistance) + " см</div>"; // Отображаем измеренное расстояние   html += "</div>"; // Закрываем сетку для измерения расстояния    // Поле ввода для команды и кнопка для отправки   html += "<h2>Отправить команду:</h2>";   html += "<div class='grid'>"; // Начинаем сетку для ввода команды   html += "<input type='text' id='commandInput' placeholder='Введите команду'>"; // Поле ввода для команды   html += "<button onclick='sendCommand()'>Отправить команду</button>"; // Кнопка для отправки команды   html += "</div>"; // Закрываем сетку для ввода команды    // Отображение последних 5 команд   html += "<h2>Последние команды:</h2><ul id='lastCommands'>"; // Заголовок для списка последних команд   for (int i = 0; i < 5; i++) { // Цикл для отображения последних 5 команд     if (lastCommands[i] != "") { // Проверяем, есть ли команда       html += "<li>" + lastCommands[i] + "</li>"; // Добавляем команду в список     }   }   html += "</ul>"; // Закрываем список последних команд    // Отображение выходных сообщений   html += "<h2>Вывод:</h2><div id='output'></div>"; // Блок для отображения сообщений    // Отображение подключенных устройств   html += "<h2>Подключенные устройства:</h2><button onclick='refreshConnectedDevices()'>Обновить</button>"; // Кнопка для обновления списка устройств   html += "<div id='connectedDevices'></div>"; // Блок для отображения подключенных устройств   html += "</div></body></html>"; // Закрываем контейнер, тело и HTML-документ      server.send(200, "text/html", html); // Отправляем HTML-код клиенту с кодом состояния 200 (успех) }  void handleSendCommand() {   // Проверяем, есть ли в запросе команда   if (server.hasArg("command")) {     // Получаем команду из запроса     String command = server.arg("command");      // Убираем лишние пробелы в начале и в конце     command.trim();      // Показываем в консоли, какую команду мы получили     Serial.println("Команда с веба: " + command);      // Добавляем команду в вывод, чтобы показать пользователю     serialOutput += "Команда с веба: " + command + "<br>";      // Обрабатываем команду (например, выполняем какое-то действие)     processCommand(command);      // Отправляем обратно сообщение, что команда была отправлена     server.send(200, "text/plain", "Команда отправлена: " + command);   } else {     // Если команда не найдена, отправляем сообщение об ошибке     server.send(400, "text/plain", "Команда не предоставлена");   } }  void handleSendSerialInput() {   // Проверяем, есть ли в запросе ввод для серийного порта   if (server.hasArg("serialInput")) {     // Получаем ввод из запроса     String serialInput = server.arg("serialInput");      // Убираем лишние пробелы     serialInput.trim();      // Показываем в консоли, что мы получили     Serial.println("Серийный ввод с веба: " + serialInput);      // Обрабатываем ввод (выполняем какое-то действие)     processCommand(serialInput);      // Отправляем обратно сообщение, что команда была отправлена     server.send(200, "text/plain", "Серийная команда отправлена: " + serialInput);   } else {     // Если ввод не найден, отправляем сообщение об ошибке     server.send(400, "text/plain", "Серийная команда не предоставлена");   } }  void handleSendMeasureDistance() {   // Отправляем команду для измерения расстояния   processCommand("MEASURE_DISTANCE");   // Отправляем обратно сообщение с измеренным расстоянием   server.send(200, "text/plain", "Измеренное расстояние: " + String(measuredDistance) + " см"); }  void handleConnectedDevices() {   // Начинаем создавать HTML-список   String html = "<ul>";   // Получаем количество подключенных устройств   int numDevices = WiFi.softAPgetStationNum();    // Добавляем информацию о количестве подключенных устройств в список   html += "<li>Количество подключенных устройств: " + String(numDevices) + "</li>";      // Получаем информацию о каждом подключенном устройстве   struct station_info *stationList = wifi_softap_get_station_info();   while (stationList != NULL) {     // Добавляем MAC-адрес устройства в список     html += "<li>MAC-адрес устройства: " + String(macToString(stationList->bssid)) + "</li>";     // Переходим к следующему устройству в списке     stationList = STAILQ_NEXT(stationList, next);   }   html += "</ul>"; // Закрываем список   // Отправляем HTML-страницу с информацией о подключенных устройствах   server.send(200, "text/html", html); }  void storeCommand(String command) {   // Сохраняем команду в массив последних команд   lastCommands[commandIndex] = command;    // Увеличиваем индекс, чтобы сохранять следующую команду, и сбрасываем его, если он достиг 5   commandIndex = (commandIndex + 1) % 5;  }  // Функция для преобразования MAC-адреса в строку String macToString(uint8_t *mac) {   String macStr = ""; // Создаем пустую строку для MAC-адреса   for (int i = 0; i < 6; i++) {     // Добавляем каждый байт MAC-адреса в строку в шестнадцатеричном формате     macStr += String(mac[i], HEX);     // Добавляем двоеточие между байтами, кроме последнего     if (i < 5) macStr += ":";   }   return macStr; // Возвращаем строку с MAC-адресом } 

Где

Объяснение функций в processCommand:

  1. Измерение расстояния: Когда команда MEASURE_DISTANCE получена, устройство отправляет команду на выполнение и ждет ответа. После получения расстояния оно выводит результат в последовательный порт и сохраняет команду.

  2. Установка скорости мотора: Если команда начинается с SET_SPEED_, функция извлекает значение скорости и проверяет, находится ли оно в допустимых пределах (0-50 и кратно 10). Если да, то устанавливает скорость мотора и сохраняет команду.

  3. Движение мотора: Команды FORWARD, BACKWARD и STOP обрабатываются отдельно, где выполняется соответствующее действие и выводится подтверждение в последовательный порт.

  4. Обработка неизвестных команд: Если команда не распознана, выводится сообщение об ошибке.

Объяснение функций в handleRoot:

  1. Создание HTML-страницы: Функция формирует HTML-код, который будет отправлен клиенту. Включает заголовок, стили и скрипты для взаимодействия с сервером.

  2. Подключение jQuery: Библиотека jQuery используется для упрощения AJAX-запросов.

  3. JavaScript функции:

    • sendCommand(): Получает текст из поля ввода, отправляет его на сервер и отображает ответ на странице.

    • measureDistance(): Отправляет запрос на сервер для измерения расстояния и отображает результат.

  4. Структура страницы: HTML включает заголовок, поле ввода для команд, кнопки для выполнения команд и блоки для отображения вывода и результатов измерения.

  5. Отправка HTML: В конце функции HTML-код отправляется клиенту с кодом состояния 200 (OK).

Объяснение handleSendCommand: Эта функция отвечает за обработку команды, которую пользователь отправляет через веб. Если команда есть, она убирает лишние пробелы, показывает, какую команду мы получили, и выполняет ее. Если команды нет, она отправляет сообщение об ошибке.

Объяснение handleSendSerialInput: Эта функция похожа на первую, но она обрабатывает ввод для серийного порта. Она также убирает лишние пробелы, показывает ввод в консоли и выполняет его. Если ввода нет, она отправляет сообщение об ошибке.

Объяснение handleSendMeasureDistance: Эта функция отправляет команду для измерения расстояния. После выполнения команды она возвращает пользователю информацию о том, какое расстояние было измерено.

Объяснение handleConnectedDevices: Эта функция собирает информацию о всех устройствах, которые подключены к нашему Wi-Fi. Она создает список и показывает, сколько устройств подключено, а также их MAC-адреса. Затем она отправляет этот список пользователю в виде веб-страницы.

Объяснение storeCommand: Эта функция сохраняет последние команды, которые были отправлены. Она добавляет новую команду в специальный список и следит за тем, чтобы не сохранять больше пяти команд. Если команд больше пяти, она начинает заменять самые старые.

Объяснение macToString: Эта функция преобразует MAC-адрес (уникальный адрес устройства в сети) в строку, чтобы его было легче читать. Она разбивает адрес на части и добавляет двоеточия между ними.

Итог

Если интересно, подписывайтесь на мой ТГ


ссылка на оригинал статьи https://habr.com/ru/articles/880578/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *