Как стать автором
Обновить

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

Уровень сложностиСредний
Время на прочтение14 мин
Количество просмотров892

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

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

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

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

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-адрес (уникальный адрес устройства в сети) в строку, чтобы его было легче читать. Она разбивает адрес на части и добавляет двоеточия между ними.

Итог

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

Теги:
Хабы:
+1
Комментарии3

Публикации

Истории

Ближайшие события

11 – 13 февраля
Epic Telegram Conference
Онлайн
27 марта
Deckhouse Conf 2025
Москва
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань