Здравствуйте! Перебирая вещи, я наткнулась на сломанную радиоуправляемую машинку и задумалась: почему бы не переделать её и не дать ей второй шанс с новыми функциями? У меня есть интересные платы, которые в последнее время лежат без дела, и я решила, что это отличная возможность их использовать.
Конечно, проект не идеален и требует доработок, но это всего лишь первая проба. В скором времени я планирую превратить этот набросок в полноценный рабочий вариант.
Подключение:
HC-SR04:
-
Trig (триггер) подключите к пину D3 (пин 3) Elbear ACE-UNO.
-
Echo (эхо) подключите к пину D2 (пин 2) Elbear ACE-UNO.
-
VCC (питание) подключите к 5V.
-
GND (земля) подключите к GND.
Светодиод:
подключите к пину D12 (пин 12) Elbear ACE-UNO.
Сервопривод:
-
Провод управления (обычно желтый или оранжевый) подключите к пину D9 (пин 9) Elbear ACE-UNO.
-
Провод питания (обычно красный) подключите к 5V.
-
Провод земли (обычно черный или коричневый) подключите к GND.
ESP8266:
-
TX (передача) ESP8266 подключите к RX (прием) Elbear ACE-UNO (например, D0).
-
RX (прием) ESP8266 подключите к TX (передача) Elbear ACE-UNO (например, D1).
-
GND ESP8266 подключите к GND Elbear ACE-UNO.
-
VCC ESP8266 подключите к 3.3V (обратите внимание, что ESP8266 работает на 3.3V).
Описание проекта
Проект «МАШИНА» представляет собой систему, которая может выполнять команды через последовательный порт и веб-интерфейс. Он включает в себя:
-
Сервопривод для управления движением.
-
Светодиоды для визуальной индикации состояния.
-
Ультразвуковой датчик HC-SR04 для измерения расстояния.
-
ESP8266 для создания веб-сервера, позволяющего управлять устройством через браузер.
Компоненты
-
Elbear ACE-UNO (Российская Arduino-совместимая плата на отечественном микроконтроллере MIK32 АМУР (Микрон)): Основной контроллер, который управляет сервоприводом и светодиодами.
-
ESP8266: Модуль Wi-Fi, который позволяет подключать устройство к сети и управлять им удаленно.
-
HC-SR04: Датчик расстояния, который измеряет расстояние до объекта.
-
Сервоприводы и светодиоды: Для управления движением и индикации.
Код 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:
-
Измерение расстояния: Когда команда MEASURE_DISTANCE получена, устройство отправляет команду на выполнение и ждет ответа. После получения расстояния оно выводит результат в последовательный порт и сохраняет команду.
-
Установка скорости мотора: Если команда начинается с SET_SPEED_, функция извлекает значение скорости и проверяет, находится ли оно в допустимых пределах (0-50 и кратно 10). Если да, то устанавливает скорость мотора и сохраняет команду.
-
Движение мотора: Команды FORWARD, BACKWARD и STOP обрабатываются отдельно, где выполняется соответствующее действие и выводится подтверждение в последовательный порт.
-
Обработка неизвестных команд: Если команда не распознана, выводится сообщение об ошибке.
Объяснение функций в handleRoot:
-
Создание HTML-страницы: Функция формирует HTML-код, который будет отправлен клиенту. Включает заголовок, стили и скрипты для взаимодействия с сервером.
-
Подключение jQuery: Библиотека jQuery используется для упрощения AJAX-запросов.
-
JavaScript функции:
-
sendCommand(): Получает текст из поля ввода, отправляет его на сервер и отображает ответ на странице.
-
measureDistance(): Отправляет запрос на сервер для измерения расстояния и отображает результат.
-
-
Структура страницы: HTML включает заголовок, поле ввода для команд, кнопки для выполнения команд и блоки для отображения вывода и результатов измерения.
-
Отправка HTML: В конце функции HTML-код отправляется клиенту с кодом состояния 200 (OK).
Объяснение handleSendCommand: Эта функция отвечает за обработку команды, которую пользователь отправляет через веб. Если команда есть, она убирает лишние пробелы, показывает, какую команду мы получили, и выполняет ее. Если команды нет, она отправляет сообщение об ошибке.
Объяснение handleSendSerialInput: Эта функция похожа на первую, но она обрабатывает ввод для серийного порта. Она также убирает лишние пробелы, показывает ввод в консоли и выполняет его. Если ввода нет, она отправляет сообщение об ошибке.
Объяснение handleSendMeasureDistance: Эта функция отправляет команду для измерения расстояния. После выполнения команды она возвращает пользователю информацию о том, какое расстояние было измерено.
Объяснение handleConnectedDevices: Эта функция собирает информацию о всех устройствах, которые подключены к нашему Wi-Fi. Она создает список и показывает, сколько устройств подключено, а также их MAC-адреса. Затем она отправляет этот список пользователю в виде веб-страницы.
Объяснение storeCommand: Эта функция сохраняет последние команды, которые были отправлены. Она добавляет новую команду в специальный список и следит за тем, чтобы не сохранять больше пяти команд. Если команд больше пяти, она начинает заменять самые старые.
Объяснение macToString: Эта функция преобразует MAC-адрес (уникальный адрес устройства в сети) в строку, чтобы его было легче читать. Она разбивает адрес на части и добавляет двоеточия между ними.
Итог
Если интересно, подписывайтесь на мой ТГ
ссылка на оригинал статьи https://habr.com/ru/articles/880578/
Добавить комментарий