Автомобиль с видеокамерой. управление через wifi
Это небольшой рассказ о том, как построить робота в виде игрушечного автомобильчика с видео камерой, которым можно управлять с помощью компьютера или смартфона на ОС Android через WiFi. Устройство не имеет никакой автономности в смысле своего поведения (типа распознавания чего-либо), управление — только от человека, поэтому «робот» — это не совсем подходящее слово в названии.
Началось все с того, что идея управления игрушечными устройствами от первого лица (т.н. FPV) мне показалась чрезвычайно интересной с точки зрения собственно процесса. Ведь мы можем таким образом реализовать свое присутствие, не в виртуальном мире, а в реальном.Проще и быстрее всего применить эту идею на игрушечных или модельных автомобилях.
Текущие технологии должны давать такую возможность промышленности и предложить массу подобных вещей. Однако это предложение оказалось достаточно дорогим по сравнению тем что можно сделать самому. Так как это мой первый проект, я ни программировать, ни даже паять нормально не умел, и я решил сначала поискать в интернете единомышленников и их варианты решения данной задачи.
Начав изучение вариантов, как можно осуществить эту идею, я нашел очень подробное описание подобного проекта здесь. A его автор с радостью помог мне разобраться в проблемах, возникших при создании робота.Так я впервые и узнал что такое …duino. Так как это был уже готовый вариант микроконтроллера, где не нужно было паять обвязку к нему, я выбрал именно его.
Также очень понравилось присутствие бутлоадера, позволяющего прошивать микроконтроллер без программаторов.
Для реализации данного проекта понадобится:
- Микроконтроллер Arduino (любой: nano, uno, mega)
- Аккумулятор 9,6вольт
- Китайская машинка на радиоуправлении
- Роутер dir320 (или любой другой поддерживающий OPEN-WRT прошивку)
- Вебкамера Logitech c310 или любая другая с UVC потоком
Крупноблочная схема реализации проекта на базе WiFi роутера
Программ пять: на PC, на Android, на роутере (сервер управления и видеопоток), и в микропроцессоре.
Схема работы: соединяем настольный компьютер (ноутбук, далее — PC) с роутером по WiFi. На роутере при его включении автоматически загружаются 2е программы:1) сервер.
Эта программа открывает сокет (соединение) на определенном порту и ждет, когда по этому порту с ней соединится клиент (любая программа, которая обратится в этот порт и также, особым образом скажет серверу, что она готова работать через открытый сокет).
Далее, после установки соединения, все что придет от клиента, будет перенаправлено по определенному пути, для нас это COM-порт, на этом порту подключен микропроцессор. И наоборот, все что придет со стороны COM-порта, будет переслано клиенту.2) программа обработки видео, захватывает его с usb камеры и шлет на определенный порт.
Для его просмотра нужно всего лишь иметь соединение с роутером на этом порту.После того, как между компьютером и роутером установлено WiFi-соединение, запускаем на PC программу для управления роботом (тот самый клиент), эта программа соединяется с программой-сервером на роутере. Эта же или другая программа транслирует видео с WiFi роутера.
Далее, пользователь может управлять автомобильчиком и нажимает, например, кнопку «вперед». Программа на PC, отсылает команду «вперед» прямо на роутер, на его IP, но на определенный порт. На роутере, эта команда поступает в программу-сервер, т.к. выслана она на его порт, и в рамках открытого для этого сокета.
Программа-сервер, ничего не делая с этой командой, просто отправляет её в COM-порт. Таким образом, команда «вперед» оказывается в микропроцессоре, который в ответ на нее, дает сигнал «вперед» на один из своих выводов. К таким выводам процессора подсоединена схема управления двигателями, т.к. сам микропроцессор управлять ими не может в силу своей маломощности.
Управлять исполнительным устройством через роутер, без микропроцессора не получится, т.к. микропроцессор может формировать сигналы «1» (напряжение >2,5v) или «0» (меньше обозначенного) на любом из десятка-другого своих выводов. У роутера же выводов нет, есть только порты ввода/вывода, типа USB или COM (serial), в которых по 2-3 провода.Теперь часть практическая.
Заранее скажу, что несмотря на кажущиеся сложности, все на самом деле просто, если речь идет о простом копировании этого проекта – ведь все уже сделано и работает. Нужно просто выполнить в точности эту инструкцию.
Изначально микроконтроллером был freeduino maxserial у которого был com port, который был одним из немногих(как я тогда считал) для подключения к uart роутеру, для этого нужно было паять переходник с uarta на com чтобы соединить его с роутером. Его брать я не очень хотел, так как оригиналом есть все-таки Arduino, да и Freeduino в Украине нет.
Как я выяснил потом, все было это просто излишнее нагромождение схемы. Обойтись можно всего 1 проводком который будет идти от TX роутера(на рисунке) к RX (0 пин) микроконтроллера.
Непонятно почему но на фридуине оказалось для нормального подключения нужно tx на tx. Скорее всего просто неверно нанесено обозначение. (тут 0 пин tx) По этому лучше брать оригинальный Arduino.Машинку я купил хорошую, хоть и китайскую
Машинка оказалась очень мощная, 5 кг на ровной поверхности тянула очень уверено. Также у нее в комплекте шел аккумулятор на 6 вольт. Что касается электроники, то в машинке уже есть готовый драйвер двигателей, на который можно подать управляющие слаботочные выходы с микроконтроллера (если бы с машинкой не повезло — драйвер моторов можно было взять тоже от arduino )
Роутер требует прошивку openwrt и список пакетов указанных на рисунке.
Роутер можно настроить как точку доступа, которой могут подключится любые устройства, имеющие WiFi. И, даже если не будет программного обеспечения для управлении машинкой – использовать ее как беспроводную камеру видеонаблюдения.
Камера с310 просто подключается к порту usb на роутер и не требует пайки, требует небольших настроек в роутере. Проект имеет 2 цепи питания, 1 цепь питается от 9,6 вольт — роутер и микроконтроллер, 2 цепь питается от 6 вольт — привод и рулевое машинки. Можно обойтись всего 1 источником питания в 9,6 вольт, но более емкостным.
Роутер потребляет 2А, микроконтроллер потребляет почти незаметно, машинка 4А.Программа микроконтроллера обрабатывает сообщения, которые приходят с последовательного порта роутера, обработка происходит побайтово через portb arduino, например если пришло в роутер 2, то, переведя в двоичную систему получаем 00000010 – что соответствует 2 пину на portb.
Такое решение позволяет управлять одновременно несколькими пинами. Вот что получилось в итоге:
- Приложение для пк:
Данный проект еще не закончен и продолжает совершенствоваться.
В планах использовать arduino mega, роутер mr3020, вебакамеру оставить как есть(возможно добавить сферическую линзу для большего обзора), задействовать шим для плавного и точного управления, использовать сервопривод для поворотов, добавить дальномер.
Собирайте arduino своими руками — полный каталог плат
Аппаратная часть
Машинка
Добавление сетевой камеры, маршрутизатора, тяжелых батарей, дополнительных схем, и кучи проводов добавляют много лишнего веса, а большинство моделей для этого не предназначено. Поэтому из-за всех дополнений необходимо найти довольно большую радиоуправляемую машинку.
В интернете можно найти б/у радиоуправляемые транспортные средства без пульта за 200-300 руб. Я купил несколько машинок для того, чтобы вытащить из них детали. Транспорт размера 1:10 или больше вполне подойдет, и вероятно вы не захотите меньше.
Я купил эту машинку за 150 руб.
Я разобрал около 20 радиоуправляемых автомобилей. Почти в каждом из них используется чип Realtek RX2/TX2 или его полные аналоги с аналогичной цоколевкой. Документацию на них можно найти в интернете или по ссылкам.
Это означает, что машинкой очень легко управлять при помощи штатной электроники без добавления собственных схем. Можно подключить микроконтроллер непосредственно к выводам (вперед, назад, влево, вправо) и управлять автомобилем.
Возможность использования штатной электроники автомобиля экономит много сил и времени.
Маршрутизатор
Я модифицировал свой WRT54GL так, что он имеет 2 последовательных порта и SD карту на 1Гб (работает как жесткий диск на 1 Гб). В этом проекте SD карта не используется, но используется один из последовательных портов.
У моего маршрутизатора есть два последовательных порта: консольный порт и TTS/1, который мы и будем использовать. Для этого проекта я использую OpenWRT White Russian v0.9. Есть и более поздние версии, но для этого проекта нам они не нужны.
В руководстве по компиляции программ (смотрите ниже), используется эта версия, поэтому я выбрал её.
Ниже в этой статье будут ссылки и информация, которая поможет вам подключить последовательный порт и заставить его работать.
Выбор микроконтроллера
Я оценил три различных микроконтроллера для этого проекта. Ниже показано краткое изложение оценки.
Микроконтроллер | PIC16F628A | Arduino (ATmega168)Freeduino MaxSerial | AVR Butterfly (ATmega169) |
За | Цена. Уровень программного управления. | Очень прост в программировании (C со множеством встроенных библиотек). Интегрированный последовательный интерфейс.Готовый комплект разработчика, практически нет необходимости в пайке. | Легче для программирования, чем PIC. Интегрированный последовательный интерфейс.Пайка необходима редко. |
Против | Труден для программирования (ассемблер). Необходимо соединять цепи вручную. Необходимы дополнительные аппаратные средства(MAX232A).Требуется программатор. | Цена. | Ошибки загрузчика (см. ниже). Интегрированные периферийные устройства имеют странные выходные напряжения.Цена. |
Я выбирал PIC16F628A по нескольким причинам:
- У меня их было много.
- Я имею небольшой опыт работы с ними.
- Я хотел маленькую плату, и плата PIC оказалась самой маленькой из всех 3 вариантов.
- Я хотел иметь полный контроль над действиями программы, и это вполне возможно при программировании на ассемблере.
Arduino (Freeduino MaxSerial) стал моим вторым выбором, который мне очень понравился простотой сборки и запуска. Также есть хорошая поддержка сообщества, и простота использования.
Первоначально я использовал отладочную плату AVR Butterfly. Она работала хорошо, пока один раз не разрядились батарейки. В загрузчике AVR Butterfly существует ошибка, которая подробно описана тут. Она портит код и не позволяет перепрограммировать его другим загрузчиком. Машинка работала в один день, а на другой нет.
Потребовалось некоторое время, чтобы найти проблему и совсем немного времени, чтобы исправить её, поэтому я не стал дальше использовать эту плату в качестве системы управления.
Я также обнаружил, что выходное напряжение на выводах может быть непредсказуемыми, так как кроме управления периферийными устройствами, они управляют ЖК-экраном.
Ниже приведён исходный код для PIC и Arduino. Оба были протестированы, так что используйте то, в чем вы лучше разбираетесь и чувствуете себя комфортнее. Arduino (Freeduino MaxSerial) является наиболее оптимальным вариантом для быстрого старта. Я купил его.
Управляющая схема
На самом деле в моей машинке используется две управляющие платы. Так сделано потому, что я сжег управляющие транзисторы на штатной плате машинки. К счастью, я смог отпаять их и RX2 чип (который также сгорел) и сохранить схему управления.
Большинство игрушечных радиоуправляемых машинок используют около 6 проводов для управления двигателем. Это так, потому что в собранной машинке есть металлический скользящий контакт, который движется с двигателем и дополнительные провода используются для реле.
Каждая радиоуправляемая машинка имеет различные параметры этого контакта, так что намного лучше использовать штатную схему.
Я сжег транзисторы, подав напряжение питания 16В, вместо штатных 9.6В. Транзисторы рассчитаны на ток 5А, но вероятно я нагрузил их слишком сильно и они живописно задымились.
Я взял плату из другой радиоуправляемой машинки и использовал транзисторы оттуда. Я запустил эту схему от 12В, и это не вызвало никаких проблем, хотя транзисторы довольно сильно грелись.
Использование штатных плат машинки вместо изготовления собственного Н-моста экономит много времени и денег.
Аккумуляторы
Этот проект использует мощные аккумуляторы. Я купил их для радиоуправляемых машинок высокого класса за $ 50 доставка с eBay. Они имеют 3800 мАч и зарядное устройство 1,8А в комплекте. Их можно найти в поиске eBay.
Одна батарея заряжается примерно 1.5 часа (от полного разряда). Их напряжение 7.2В, однако, когда они только заряженные их напряжение около 8.
3В, а когда они полностью разряжены (уже не в состоянии питать машину) они дают примерно 7.1В.
Я заменил все разъемы на аккумуляторах на стандартные Molex ATX разъемы. Это сделано для того, чтобы использовать дешевые разъёмы которых у меня было много, которые позволяют легко сделать разветвитель для замера тока. Батареи соединённые последовательно дают около 16В при полной зарядке.
Питание
5В (7805 1A) | 9.2В (из 12V от 7812) | 12VВ (7812 1A) | 12В (Регулятор LT1083 7.5A) |
Микроконтроллер |
|
Wi-Fi | Драйвер двигателя |
Линия 9.6В получена путем установки 4 диодов последовательно с шиной 12В берущейся с 7812. Падение напряжения на диоде около 0.7В. Поставив 4 диода в ряд, мы теряем около 2.8В, и получаем 9В для устройств, которым необходимо меньше 12В.
После того как я сжег транзисторы, я решил питать схему более низким напряжением. 7812 рассчитан на 1А, а двигатели потребляют значительно больше. Digikey продает регулятор 7.5A 12В примерно за 14$, и я купил его.
Я прикрепил его к радиатору, потому что думал, что он может греться. После некоторого времени работы он даже не нагрелся, поэтому радиатор не требуется.
Я не хочу рисковать схемой управления, поэтому я питаю его напряжением максимально приближены к штатному.
Камера требует 9В, сигнал работает довольно тихо при питании от 5В, поэтому все эти устройства работают от линии 9.2В.
Вся силовая электроника собрана на макетной плате и находится в корпусе.
Делаем бюджетный чудо корабль с управлением по wifi на базе esp8266.
Подробное руководство о том как взять ESP8266, добавить немножко клея и пенопласта и подарить детям (и взрослым) массу удовольствий.
Руководство состоит из следующих частей:
Описание платы
Предыстория
Используемые материалы
Изготовление корабля
Программирование
Плавание
Описание платы.
Мозгом корабля является купленная на Алиэкспресс плата. Данная отладочная плата является удобным средством для начала работы с WiFi модулем ESP8266-12.
На плате распаян сам модуль, а на штыревые разъёмы выведены все выводы модуля. К 6-ти выводам через токоограничительные резисторы припаяны красные светодиоды. Ещё к трём выводам припаян RGB светодиод.
Также присутствует стабилизатор 3,3 В, распаяны резисторы обвязки модуля, на входе ADC висит фоторезистор, присутствует джампер для перевода модуля в режим программирования.
Отдельно выведены выходы USART, причём маркировка Rx и Tx перепутаны местами.
Ко входу питания модуля припаян батарейный отсек на 3 батарейки. А в модуль уже загружена тестовая прошивка под управлением которой, насколько я помню, создаётся новая точка доступа. Подключившись к этой точке доступа можно управлять свечением светодиодов на плате. Если как для первого раза — то это прям волшебство какое то.
Предыстория постройки кораблика.
В процессе вялотекущего самообразования в сторону построения умного дома, год назад был закуплена эта плата (магазин в котором была сделана покупка сейчас закрыт). Вместе с ней взял ещё два голых модуля ESP8266-12 и с помощью сайта запустил на них два термометра/измерителя влажности, которые до сих пор успешно работают.
Далее попытался состыковать их с MajorDoMo — открытой и бесплатной системой управления Умным Домом. Но как то не пошло это дело,- забросил до поры.
В середине лета я наткнулся на статью. Оказывается esp8266 можно программировать как любую ардуину!!! И ардуиновских библиотек под esp8266 портировано уже на все случаю жизни.
Для меня настал качественно новый этап освоения esp8266…
Вдоволь поигрался с библиотечными примерами и захотелось мне сделать нечто с практическим применением.
Решил собрать детям для дачного водоёма самоходный кораблик, да не простой, а такой чтоб управлять им можно было с любого смартфона!
В начало
Используемые материалы
На строительный материал корпуса были выбраны пылящиеся в кладовке куски пенопласта.
В качестве двигателей использованы коллекторные моторы от ДВД проигрывателя. Для управления судна было решено использовать двухмоторный привод — уменьшая обороты левого/правого двигателя заставляем совершать поворот на лево или на право.
Гребные винты — из куска жести.
На гребные валы замечательно подошли вязальные спицы (экспроприированные у супруги).
Для надёжного и гибкого соединения гребного вала с мотором хорошо подходят термоусадочные трубки разного диаметра.
В качестве подшипника скольжения гребного вала взят корпус простой шариковой ручки.
Также пригодились клей Dragon и клеевой термопистолет.
Для управления двигателями в загашнике нашлись два полевых транзистора выпаянных со старой материнской платы.
Источником питания послужили купленные в оффлайне аккумуляторы 18650.
Ещё понадобился маленький тумблер для подачи питания, несколько резисторов и макетная платка для соединения всего электричества между собой.
В начало
Изготовление корабля
Для резки пенопласта на скорую руку был собран ТермоЭлектроРезак состоящий из палки, двух длинных шурупов и куска тонкой проволоки. Проволоку лучше взять нихромовую, но у меня таковой не нашлось (а советский проволочный резистор ломать на это дело не хотелось) — поставил тоненькую стальную.
Запитал этот импровизированный станок от «народного блока питания» через DC-DC преобразователь. И тот и другой были куплены благодаря здешним обзорам.
Всего на этой фотографии пытливый взгляд может найти аж 10 товаров ранее обозреваемых на Муське.
Регулируя напряжение на выходе DC-DC преобразователя, опытным путём необходимо установить такой нагрев проволоки ТермоЭлектроРезака, при котором пенопласт разрезается легко и непринуждённо.
В итоге получаем более — менее ровные куски пенопласта удобные для последующей сборки.
На корпус кораблика пошли два самых больших куска, склееные между собой клеем Дракон.
Гребные винты изготавливаются в несколько операций:
— нарезать из жести квадратную заготовку (у меня 20*20 мм)
— соединив рисками диагональные углы найти центр
— просверлить по центру отверстие (диаметр 3,5 мм)
— затянуть подходящим винтом с гайкой (М 3*20)
— зажав винт в патрон дрели, на малых оборотах подходящим острым предметом начертить(нацарапать) окружность
— вырезать ровный круг
— надрезать по имеющимся диагональным рискам круг на 2/3 радиуса и изогнуть под углом (30-45 градусов)
Затем берём спицу. Отрезаем 25-30 мм кусок термоусадочной трубки диаметром чуть больше чем у спицы. Вращаем спицу с термоусадкой над маленьким пламенем газовой конфорки (или электрической) пока трубка не прогреется и плотно охватит спицу.
Далее отрезаем кусок трубки ещё большего диаметра,… и т.д. пока очередную трубку можно будет одеть на шестерню двигателя. Т.К. шестерня посажена на вал двигателя достаточно плотно, то она будет хорошо передавать крутящий момент гребному валу.
Берём последний кусок термоусадки и одеваем его одновременно на гребной вал и на двигатель.
После прогрева термоусадки получаем хорошее соединение.
Пора устанавливать двигатели в корпус.
Дрелью высверливаем в корпусе два отверстия под гребные валы. Со стороны днища вставляем подшипники скольжения.
Выставляем двигатели и прокручивая вал, по наименьшему сопротивлению вращения, находим для каждого двигателя оптимальное положение.
Обильно закрепляем полученный результат термоклеем.
Одеваем гребные винты и закрепляем термоклеем. Для лучшего сцепления с клеем кончики спиц слегка помяты кусачками, а поверхности винтов около центра исцарапаны до шершавого состояния.
Соединяем и спаиваем всё согласно схеме.
Питание с аккумуляторов подаётся через тумблер и дальше идёт на двигатели и на линейный стабилизатор 3,3 В. Второй вывод каждого двигателя подключен через полевой транзистор к минусу питания. Подавая с выхода ESP8266 за затвор полевого транзистора ШИМ сигнал различной скважности импульсов мы будем регулировать скорость вращения двигателя.
Напряжение питания также подаётся через резистивный делитель на вход ADC модуля ESP8266 для контроля состояния батареи.
Силовые транзисторы с резисторами обвязки, выключатель питания, стабилизатор, резистивный делитель для измерения напряжения батареи — всё размещено на макетной плате. К ней же припаяны плата с модулем ESP8266, выводы аккумулятора и двигателей.
К модулю подключен переходник USB-TTL.
Итак, всё готово для того чтобы вдохнуть в практически готовый кораблик искру жизни…
В начало
Программирование
О том как установить Arduino IDE и обеспечить в ней поддержку ESP8266 достаточно хорошо расписано в вышеупомянутой статье.
Для управления корабликом мы будем создавать на ESP8266 точку доступа и поднимать вэб сервер. Подключившись смартфоном к точке доступа и набрав в браузере адрес сервера (192.168.4.1) увидим страницу с элементами управления и телеметрией с борта нашего судна.
Дабы снизить нагрузку на ESP8266, уменьшить время отклика управляющих воздействий и повысить интерактивность я решил использовать технику AJAX запросов.
Помучав некоторое время Гугл в поисках подходящей реализации моих хотелок, я набрёл на
форум
в котором позаимствовал подходящий код
Первоначальный код
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
ESP8266WebServer server(80);
const char* ssid="yourSSID";
const char* password="yourPASSWORD";
String webSite,javaScript,XML;
unsigned long wait000=0UL,wait001=1000UL;
int LED=16;const int sliderMAX=10; // This sets the number of sliders you wantint sliderVal[sliderMAX]={60},ESPval[sliderMAX];
void buildWebsite(){
buildJavascript();
webSite="<!DOCTYPE HTML>n";
webSite ="<META name='viewport' content='width=device-width, initial-scale=1'>n";
webSite =javaScript;
webSite ="<BODY onload='process()'>n";
webSite ="
This is the ESP website ...
n";
webSite ="Runtime = <A ID='runtime'></A>
n";
webSite ="Сила сигнала a= n";
webSite = Sila ;// Сила сигнала WiFi
webSite ="
n";
webSite ="<TABLE BORDER=1 style='text-align:center;border-collapse:collapse'>n";
for(int i=0;i<sliderMAX;i ){
webSite ="<TR>n";
webSite ="<TD>
<INPUT ID='slider" (String)i "' TYPE='range' ONCHANGE='Slider(" (String)i ")'></TD>n"; //in Firefox, Chrome and Edge use ONINPUT
webSite ="<TD>Slidervalue" (String)i " = <A ID='Sliderval" (String)i "'></A>
n";
webSite ="ESPval" (String)i " = <A ID='ESPval" (String)i "'></A> milliseconds</TD>n";
webSite ="</TR>n";
}
webSite ="</TABLE>n";
webSite ="</BODY>n";
webSite ="</HTML>n";
}
void buildJavascript(){
javaScript="<SCRIPT>n";
javaScript ="xmlHttp=createXmlHttpObject();n";
javaScript ="function createXmlHttpObject(){n";
javaScript =" if(window.XMLHttpRequest){n";
javaScript =" xmlHttp=new XMLHttpRequest();n";
javaScript =" }else{n";
javaScript =" xmlHttp=new ActiveXObject('Microsoft.XMLHTTP');n";
javaScript =" }n";
javaScript =" return xmlHttp;n";
javaScript ="}n";
javaScript ="function process(){n";
javaScript =" if(xmlHttp.readyState==0||xmlHttp.readyState==4){n";
javaScript =" xmlHttp.onreadystatechange=function(){n";
javaScript =" if(xmlHttp.readyState==4&&xmlHttp.status==200){n";
javaScript =" xmlDoc=xmlHttp.responseXML;n";
javaScript =" xmlmsg=xmlDoc.getElementsByTagName('millistime')[0].firstChild.nodeValue;n";
javaScript =" document.getElementById('runtime').innerHTML=xmlmsg;n";
javaScript =" for(i=0;i<" (String)sliderMAX ";i ){n";
javaScript =" xmlmsg=xmlDoc.getElementsByTagName('sliderval' i)[0].firstChild.nodeValue;n";
javaScript =" document.getElementById('slider' i).value=xmlmsg;n";
javaScript =" document.getElementById('Sliderval' i).innerHTML=xmlmsg;n";
javaScript =" xmlmsg=xmlDoc.getElementsByTagName('ESPval' i)[0].firstChild.nodeValue;n";
javaScript =" document.getElementById('ESPval' i).innerHTML=xmlmsg;n";
javaScript =" }n";
javaScript =" }n";
javaScript =" }n";
javaScript =" xmlHttp.open('PUT','xml',true);n";
javaScript =" xmlHttp.send(null);n";
javaScript =" }n";
javaScript =" setTimeout('process()',1000);n";
javaScript ="}n";
javaScript ="function Slider(cnt){n";
javaScript =" sliderVal=document.getElementById('slider' cnt).value;n";
javaScript =" document.getElementById('Sliderval' cnt).innerHTML=sliderVal;n";
javaScript =" document.getElementById('ESPval' cnt).innerHTML=9*(100-sliderVal) 100;n";
javaScript =" if(xmlHttp.readyState==0||xmlHttp.readyState==4){n";
javaScript =" xmlHttp.open('PUT','setESPval?cnt=' cnt '&val=' sliderVal,true);n";
javaScript =" xmlHttp.send(null);n";
javaScript =" }n";
javaScript ="}n";
javaScript ="</SCRIPT>n";
}
void buildXML(){
Sila = WiFi.RSSI(); // Сила сигнала
XML="<?xml version='1.0'?>";
XML ="<xml>";
XML ="<millistime>";
XML =millis2time();
XML ="</millistime>";
for(int i=0;i<sliderMAX;i ){
XML ="<sliderval" (String)i ">";
XML =String(sliderVal[i]);
XML ="</sliderval" (String)i ">";
XML ="<ESPval" (String)i ">";
ESPval[i]=9*(100-sliderVal[i]) 100;
XML =String(ESPval[i]);
XML ="</ESPval" (String)i ">";
}
XML ="</xml>";
}
String millis2time(){
String Time="";
unsigned long ss;
byte mm,hh;
ss=millis()/1000;
hh=ss/3600;
mm=(ss-hh*3600)/60;
ss=(ss-hh*3600)-mm*60;
if(hh<10)Time ="0";
Time =(String)hh ":";
if(mm<10)Time ="0";
Time =(String)mm ":";
if(ss<10)Time ="0";
Time =(String)ss;
return Time;
}
void handleWebsite(){
buildWebsite();
server.send(200,"text/html",webSite);
}
void handleXML(){
buildXML();
server.send(200,"text/xml",XML);
}
void handleESPval(){
int sliderCNT=server.arg("cnt").toInt();
sliderVal[sliderCNT]=server.arg("val").toInt();
buildXML();
server.send(200,"text/xml",XML);
}
void setup() {
Serial.begin(115200);
pinMode(LED13,OUTPUT);
pinMode(LED14,OUTPUT);
pinMode(LED15,OUTPUT);
WiFi.begin(ssid,password);
while(WiFi.status()!=WL_CONNECTED)delay(500);
WiFi.mode(WIFI_STA);
Sila = WiFi.RSSI();
Serial.println("nnBOOTING ESP8266 ...");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("Station IP address: ");
Serial.println(WiFi.localIP());
server.on("/",handleWebsite);
server.on("/xml",handleXML);
server.on("/setESPval",handleESPval);
server.begin();
// HTTP_begin();
}
void loop() {
server.handleClient();
if(millis()>wait000){
buildXML();
wait000=millis() 1000UL;
}
if(millis()>wait001){
digitalWrite(LED13,!digitalRead(LED13));
wait001=millis() ESPval[0];
Serial.print(" Sl3 ");
Serial.print(ESPval[0]);
analogWrite(LED13, ESPval[0]);
Serial.print(" Sl4 ");
Serial.print(ESPval[1]);
analogWrite(LED14, ESPval[1]);
Serial.print(" Sl5 ");
Serial.println(ESPval[2]);
analogWrite(LED15, ESPval[2]);
Serial.print(WiFi.RSSI()); // Сила сигнала
// WiFi.printDiag(Serial);
}
}
Путём последовательных приближений код был доведён до нужного мне рабочего варианта.
окончательный код
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
ESP8266WebServer server(80);
const char* ssid="ABPOPA"; //название точки доступа
const char* password=""; // пароль не будем писать
//const char* ssid="HomeIoT"; // здесь пишем название домашней точки доступа
//const char* password="DDV987654321"; // и пароль
String webSite,javaScript,XML;
unsigned long wait000=0UL,wait001=1000UL;
int OUT1=14, OUT2=12, BAT=17;//Назначение выводов
int Sila, SilaLow = -43; // Контроль силы сигнала WiFi
int Batareya, BatLow = 400; //Контроль напряжения батареи
float Povorot = 0.8, Trimer; // Переменные для значений поворота и тримера двигателя
String Bat, SilaW;
const int sliderMAX=3; // This sets the number of sliders you want
int sliderVal[3]={100, 50, 50}; //Начальные значения 1 слайдера - скорость, 2-го - поворот, 3- тример
int ESPval[sliderMAX];
void Batare(){
Batareya = analogRead(BAT); // Меряем напряжение батареи
if (Batareya < BatLow){ // Если напряжение батареи меньше порогового значения
Bat = " Батарея села, ПОРА на ЗАРЯДКУ"; // Пишем предупреждение
}
else{ // Если норма
Bat = ""; // Ничего не пишем
}
}
void SilaWifi(){
Sila = WiFi.RSSI(); // Меряем силу сигнала WiFi
if (Sila < SilaLow){
SilaW = " Сигнал слабый, РАЗВОРАЧИВАЙ";
}
else{
SilaW = "";
}
}
void buildJavascript(){
javaScript="<SCRIPT>n";
javaScript ="xmlHttp=createXmlHttpObject();n";
javaScript ="function createXmlHttpObject(){n";
javaScript =" if(window.XMLHttpRequest){n";
javaScript =" xmlHttp=new XMLHttpRequest();n";
javaScript =" }else{n";
javaScript =" xmlHttp=new ActiveXObject('Microsoft.XMLHTTP');n";
javaScript =" }n";
javaScript =" return xmlHttp;n";
javaScript ="}n";
javaScript ="function process(){n";
javaScript =" if(xmlHttp.readyState==0||xmlHttp.readyState==4){n";
javaScript =" xmlHttp.onreadystatechange=function(){n";
javaScript =" if(xmlHttp.readyState==4&&xmlHttp.status==200){n";
javaScript =" xmlDoc=xmlHttp.responseXML;n";
javaScript =" xmlmsg=xmlDoc.getElementsByTagName('millistime')[0].firstChild.nodeValue;n";
javaScript =" document.getElementById('runtime').innerHTML=xmlmsg;n"; // Добавляем наши данные
javaScript =" xmlmsg=xmlDoc.getElementsByTagName('Sila')[0].firstChild.nodeValue;n";// Сила сигнала WiFi
javaScript =" document.getElementById('Sila').innerHTML=xmlmsg;n"; // Сила сигнала WiFi
javaScript =" xmlmsg=xmlDoc.getElementsByTagName('Batareya')[0].firstChild.nodeValue;n";// Напряжение батареи
javaScript =" document.getElementById('Batareya').innerHTML=xmlmsg;n"; // Напряжение батареи
javaScript =" for(i=0;i<" (String)sliderMAX ";i ){n";
javaScript =" xmlmsg=xmlDoc.getElementsByTagName('sliderval' i)[0].firstChild.nodeValue;n";
javaScript =" document.getElementById('slider' i).value=xmlmsg;n";
javaScript =" document.getElementById('Sliderval' i).innerHTML=xmlmsg;n";
javaScript =" xmlmsg=xmlDoc.getElementsByTagName('ESPval' i)[0].firstChild.nodeValue;n";
javaScript =" document.getElementById('ESPval' i).innerHTML=xmlmsg;n";
javaScript =" }n";
javaScript =" }n";
javaScript =" }n";
javaScript =" xmlHttp.open('PUT','xml',true);n";
javaScript =" xmlHttp.send(null);n";
javaScript =" }n";
javaScript =" setTimeout('process()',1000);n";
javaScript ="}n";
javaScript ="function Slider(cnt){n";
javaScript =" sliderVal=document.getElementById('slider' cnt).value;n";
javaScript =" document.getElementById('Sliderval' cnt).innerHTML=sliderVal;n";
javaScript =" document.getElementById('ESPval' cnt).innerHTML=9*(100-sliderVal) 100;n";
javaScript =" if(xmlHttp.readyState==0||xmlHttp.readyState==4){n";
javaScript =" xmlHttp.open('PUT','setESPval?cnt=' cnt '&val=' sliderVal,true);n";
javaScript =" xmlHttp.send(null);n";
javaScript =" }n";
javaScript ="}n";
javaScript ="</SCRIPT>n";
}
void buildWebsite(){ // Создаём страницу с элементами управления
buildJavascript();
webSite="<!DOCTYPE HTML>n";
webSite ="<META name='viewport' content='width=device-width, initial-scale=1', charset="utf-8">n";
webSite =javaScript;
webSite ="<BODY onload='process()'>n";
webSite ="
Кораблик
n";
webSite ="Время работы = <A ID='runtime'></A>
n";
webSite ="Сила сигнала <A ID='Sila'></A>
n"; // Сила сигнала WiFi
webSite ="Напряжение батареи <A ID='Batareya'></A>
n"; // Напряжение батареи
webSite ="<TABLE BORDER=1 width='700' height='200' style='text-align:center;border-collapse:collapse'>n";
// webSite ="<INPUT ' TYPE='range' width='600'> n"; // это пример слайдера
webSite ="<TR>n"; // 1 слайдер
webSite ="<TD>
<INPUT ID='slider" (String)0 "' TYPE='range' ONCHANGE='Slider(" (String)0 ")'></TD>n";
webSite ="<TD>Обороты = <A ID='Sliderval" (String)0 "'></A>
n";
webSite ="Моторов = <A ID='ESPval" (String)0 "'></A> * </TD>n";
webSite ="</TR>n";
webSite ="<TR>n"; // 2 слайдер
webSite ="<TD>
<INPUT ID='slider" (String)1 "' TYPE='range' ONCHANGE='Slider(" (String)1 ")'></TD>n";
webSite ="<TD>Направление = <A ID='Sliderval" (String)1 "'></A>
n";
webSite ="Движения = <A ID='ESPval" (String)1 "'></A></TD>n";
webSite ="</TR>n";
webSite ="<TR>n"; // 3 слайдер
webSite ="<TD>
<INPUT ID='slider" (String)2 "' TYPE='range' ONCHANGE='Slider(" (String)2 ")'></TD>n";
webSite ="<TD>Корекция = <A ID='Sliderval" (String)2 "'></A>
n";
webSite ="Моторов = <A ID='ESPval" (String)2 "'></A></TD>n";
webSite ="</TR>n";
webSite ="</TABLE>n";
webSite ="</BODY>n";
webSite ="</HTML>n";
}
String millis2time(){ // преобразование милисекунд в вид ч/м/с
String Time="";
unsigned long ss;
byte mm,hh;
ss=millis()/1000;
hh=ss/3600;
mm=(ss-hh*3600)/60;
ss=(ss-hh*3600)-mm*60;
if(hh<10)Time ="0";
Time =(String)hh ":";
if(mm<10)Time ="0";
Time =(String)mm ":";
if(ss<10)Time ="0";
Time =(String)ss;
return Time;
}
void buildXML(){
XML="<?xml version='1.0'?>";
XML ="<xml>";
XML ="<millistime>";
XML =millis2time();
XML ="</millistime>"; // Добавляем наши данные
SilaWifi(); // Здесь измеряем силу сигнала
XML ="<Sila>"; // Сила сигнала WiFi
XML =String(Sila) SilaW;// Сила сигнала WiFi
XML ="</Sila>"; // Сила сигнала WiFi
Batare(); // Здесь измеряем напряжение батареи
XML ="<Batareya>"; // Напряжение батареи
XML =String(Batareya) Bat;// Напряжение батареи
XML ="</Batareya>"; // Напряжение батареи
for(int i=0;i<sliderMAX;i ){
XML ="<sliderval" (String)i ">";
XML =String(sliderVal[i]);
XML ="</sliderval" (String)i ">";
XML ="<ESPval" (String)i ">";
ESPval[i]=9*(100-sliderVal[i]) 100;
XML =String(ESPval[i]);
XML ="</ESPval" (String)i ">";
}
XML ="</xml>";
}
void handleWebsite(){
buildWebsite();
server.send(200,"text/html",webSite);
}
void handleXML(){
buildXML();
server.send(200,"text/xml",XML);
}
void handleESPval(){
int sliderCNT=server.arg("cnt").toInt();
sliderVal[sliderCNT]=server.arg("val").toInt();
buildXML();
server.send(200,"text/xml",XML);
}
void setup() {
Serial.begin(115200);
pinMode(OUT1,OUTPUT);
pinMode(OUT2,OUTPUT);
pinMode(BAT,INPUT);
WiFi.softAP(ssid, password); // Создаём точку доступа
// WiFi.begin(ssid,password); //Это вариант для подключения к существующей точке
// while(WiFi.status()!=WL_CONNECTED)delay(500);
// WiFi.mode(WIFI_STA);
Serial.println("nnBOOTING ESP8266 ...");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("Station IP address: ");
Serial.println(WiFi.localIP()); // Вывод в монитор порта присвоенный IP
server.on("/",handleWebsite);
server.on("/xml",handleXML);
server.on("/setESPval",handleESPval);
server.begin();
}
void loop() {
server.handleClient();
if(millis()>wait000){
buildXML();
wait000=millis() 1000UL;
}
if(millis()>wait001){
wait001=millis() 300; //Обновляем значения раз в 300 милисекунд
Trimer = (ESPval[2]*.0015);
Serial.print(" Skorost] ");
Serial.print(ESPval[0]);
Serial.print(" Povorot ");
Serial.print(ESPval[1]);
Serial.print(" Trimer ");
Serial.print(ESPval[2]);
Serial.print(Trimer);
if (ESPval[0] > 200){ // Если слайдер скорости > 200
if ( ESPval[1] > 600) { // Если слайдер поворота > 600 то поворачиваем на лево
analogWrite(OUT1, ESPval[0]);
analogWrite(OUT2, int(ESPval[0] * Povorot*Trimer));
Serial.print(" Le ");
Serial.print(ESPval[0] * Povorot*Trimer);
}
else if (400 > ESPval[1] ){ // Если слайдер поворота < 400 то поворачиваем на право
analogWrite(OUT1, int(ESPval[0] * Povorot));
analogWrite(OUT2, int(ESPval[0]*Trimer));
Serial.print(" Pr ");
Serial.print(int(ESPval[0] * Povorot));
}
else { // Если слайдер поворота > 400 и < 600то едем прямо
analogWrite(OUT1, ESPval[0]);
analogWrite(OUT2, int(ESPval[0]*Trimer));
Serial.print(" OK ");
}
}
else { // Если слайдер скорости < 200 - выключить моторы
analogWrite(OUT1, 0);
analogWrite(OUT2, 0);
Serial.print(" Stop ");
}
Serial.print(" Batareya ");
Serial.println(Batareya);
}
}
В коде довольно много пояснений, надеюсь всё будет понятно.
Двигатели кораблика управляются путём изменения положения ползунков трёх слайдеров на странице.
— Первый слайдер отвечает за скорость вращения моторов (и скорость движения кораблика соответственно). Если изменять положение ползунка этого слайдера от 20 до 100%, то будет меняться значение связанной со слайдером переменной ESPval[0]. Значение переменной записывается в порты OUT1 и OUT2 ESP8266 (выводы которых идут на управляющие затворы полевых транзисторов) и обороты двигателей будут нарастать от 0 до максимума.
В диапазоне положений этого ползунка от 0 до 20% в порты записываются 0 и двигатели стоят.
— Второй слайдер отвечает за повороты (переменная ESPval[1]). Если его ползунок находится в правом или левом положении, то значение скорости соответствующего двигателя будет снижаться на коэффициент 0,8 (константа Povorot). Двигатель будет притормаживаться, а кораблик поворачивать в нужную сторону.
— Третий слайдер (переменные ESPval[2] и Trimer) нужен для нивелирования разности характеристик двигателей. В зависимости от положения слайдера можно притормаживать или ускорять один из двигателей.
К сожалению функционал измерения напряжения батареи довести до конца не удалось. В процессе наладки был сожжён вход ADC модуля (надо думать замкнул его на батареи)…
Измерение мощности сигнала WiFi работает, но требуется более точная калибровка.
Для отладки использовалось подключение к домашней WiFi сети и вывод информации в последовательный порт.
В рабочей же версии кода модулем поднимается отдельная точка доступа и вывода не нужен — соответствующие строки кода закомментированны.
Скетч компилировался в IDE версии 1.6.12.
Свободной памяти осталось более чем достаточно для воплощения в коде разных последующих хотелок.
Если у кого будут конструктивные дополнения/исправления по коду — прошу высказываться в комментариях.
В начало
Плавание
Сначала были пробные запуски в акватории ванной, по результатам которых были сделаны следующие доработки:
— 4 батарейки АА в источнике питания были безоговорочно заменены на 2 аккумулятора 18650
— к регулировкам скорости и поворота было добавлено триммирование одного из двигателей
— был увеличен шаг гребных винтов
Затем в ближайший выезд на дачу судно было было торжественно отправлено в плавание на большой воде.
Полевые испытания показали что в следующей версии необходимо:
— усилить мощность сигнала WiFi модуля путём добавления внешней антенны
— увеличить диаметр гребных винтов (или заказать винты на Алиэкспресс )
— увеличить притормаживание двигателей при поворотах с 20% до 40-50%
— доработать корпус для улучшения обтекания
— заменить двигатели на бесколлекторные
— сделать руль с приводом от сервомашинки
— прикрутить FPV
— добавить светодиодной иллюминации для ночных заплывов
— установить на палубу фейерверк и запускать его на середине пруда
— всё что душа пожелает…
Но эти доработки возможно воплотятся уже в новом сезоне.
В начало
Вот так, приложив немного времени и средств, любой рукодел средней продвинутости сможет собрать свою радиоуправляемую игрушку.
Без существенной переделки программы таким же нехитрым образом можно слепить управляемую машинку (или несколько машинок) для весёлых заездов по квартире долгими зимними вечерами.
Буду рад если этот опус вдохновит кого либо для занятий творчеством.
Самодельная wifi машинка на nodemcu. машина делает дрифт
В данной статье я расскажу вам про модификацию самодельной Wi-Fi машинки на NodeMCU. Как я сделал из нерабочей машинки на радио управлении, Wi fi машинку. Читайте и смотрите вот тут: Своимируками беспроводная Wifi машинка на NodeMCU и RemoteXY.
Как рассказываю в предыдущей статье, электроника машинки была подключена по вот такой схеме.
И потаилась от 4 батареек формата АА. Так как постоянно покупать батарейки надоело, я решил переделать питание от аккумулятора 18650 . А так как у меня аккумуляторы из старой батареи ноутбука , они плохо держат заряд. По этой причине я решил сделать питание от двух аккумуляторов.
Но как показала практика, данные аккумуляторы оказались еще хуже чем я думал. Даже 2 не держат нагрузку и Wi-Fi постоянно отваливался. Как я разбирал батарею от ноутбука смотрите тут. Позднее я разобрал второй батарею от ноутбука. Аккумуляторы были розового цвета. И держат они нагрузку неплохо.
Даже один аккумулятор 18650 справлялся с нагрузкой без проблем.
Собрал я машинку на NodeMCU без изменения управляющей программы и был удивлен машинка постоянно дрифтовала, ее разворачивала постоянно. Для дрифта это отлично, но нормально поездить можно только на улице по асфальту. Поэтому я решил переделать управление рулем и управляющую программу.
На схеме выше приведен принцип подключения аккумулятора через повышающий DC-DC преобразователя и контролера заряда. Благодаря данной схеме подключения можно заряжать аккумулятор не вынимая его из машинки, что достаточно удобно и практично.
Также по схеме видно что остался один двигатель и появился сервопривод. Сейчас управление поворотами происходит благодаря сервоприводу.
Скетч для NodeMCU в среде программирования Arduino IDE для приложения RemoteXY выглядит вот так:
//////////////////////////////////////////////// RemoteXY include library ////////////////////////////////////////////////// определение режима соединения и подключение библиотеки RemoteXY#define REMOTEXY_MODE__ESP8266WIFI_LIB_POINT#include#include// настройки соединения#define REMOTEXY_WIFI_SSID «PortalPk»#define REMOTEXY_WIFI_PASSWORD «»#define REMOTEXY_SERVER_PORT 6377// конфигурация интерфейса#pragma pack(push, 1)
uint8_t RemoteXY_CONF[] ={ 255,3,0,0,0,25,0,6,5,2,5,8,43,13,39,39,10,52,43,43,2,3,131,13,12,14,37,10,18,45,17,4 };// структура определяет все переменные вашего интерфейса управленияstruct {// input variableint8_t joystick_1_x; // =-100..
100 координата x положения джойстикаint8_t joystick_1_y; // =-100..100 координата y положения джойстикаuint8_t select_1; // =0 если переключатель в положении A, =1 если в положении B, =2 если в положении C, …// other variableuint8_t connect_flag;
// =1 if wire connected, else =0} RemoteXY;#pragma pack(pop)/////////////////////////////////////////////// END RemoteXY include ////////////////////////////////////////////////* определяем пины управления правым мотором */#define MOTOR_RIGHT_UP D1#define MOTOR_RIGHT_DN D2/* определяем пины управления левым мотором#define MOTOR_LEFT_UP D3#define MOTOR_LEFT_DN D4*/#include#define PIN_SERVO D4Servo servo;//светодиоды#define PIN_LED_STOP D0#define PIN_LED_5 D5#define PIN_LED_6 D6#define PIN_LED_7 D7#define PIN_LED_8 D8// переменныеboolean status = true; // флаг, что активна леваяboolean open = true; // флаг, включенияint interval[6]={50, 120, 90, 240, 150, 50}; // интервал включения/выключения LEDlong prestro1Millis = 0; // до мигания ( в целом переменная времени)int x=0;void setup()
{RemoteXY_Init ();pinMode (PIN_LED_STOP, OUTPUT);pinMode (PIN_LED_5, OUTPUT);pinMode (PIN_LED_6, OUTPUT);pinMode (PIN_LED_7, OUTPUT);pinMode (PIN_LED_8, OUTPUT);servo.attach(PIN_SERVO);}void loop(){RemoteXY_Handler ();
// Положение селектора 0 и 2 ///if (RemoteXY.select_1 == 2){Migalka ();// Мигалка}if (RemoteXY.select_1 == 1){digitalWrite(PIN_LED_STOP, HIGH); //digitalWrite(PIN_LED_5, HIGH); //digitalWrite(PIN_LED_6, HIGH); //}if (RemoteXY.select_1 == 0){digitalWrite(PIN_LED_STOP, LOW);
//digitalWrite(PIN_LED_5, LOW); //digitalWrite(PIN_LED_6, LOW); //digitalWrite(PIN_LED_7, LOW); //digitalWrite(PIN_LED_8, LOW); //}//END Положение селектора 0 и 2 /////* управляем мотором */int y = RemoteXY.joystick_1_y;if (y>100) y=100;if (y20)
{analogWrite(MOTOR_RIGHT_UP, y*10.23);digitalWrite(MOTOR_RIGHT_DN, LOW);}else if (y100) x=100;if (x10) {servo.writeMicroseconds(x*2.5 1600);}else if (x interval[x]) // проверяем интервал{if (open) // если truedigitalWrite(status ? PIN_LED_7 :
PIN_LED_8, HIGH); //else // иначеdigitalWrite(status ? PIN_LED_7 : PIN_LED_8, LOW); //x ;if (x == 6) // если последний проход по циклу{status = !status; // передаем слово (меняем текущий пин) меняем false на true и наоборот;x=0;}open = !open; //меняем false на true и наоборотprestro1Millis = curstro1Millis;}}
https://www.youtube.com/watch?v=J8gLejHrjbk
Повторить данную машинку может каждый желающий. Вам не нужно писать код или писать приложения для Android. Достаточно загрузить прошивку в NodeMCU. И на свое устройства установить приложение RemoteXY. Подключиться к Wi-fi сети с имением PortalPK. И ваше устройство станет пультом для машинки.