- BrainTools - https://www.braintools.ru -

Как сделать робота из того, что нашлось в кладовке

Пара слов в своё оправдание

В статье Я выпустил нейросеть в реальный мир — и стало не смешно [1] в одном из комментариев попросили рассказать про железо и этот комментарий был поддержан плюсиками. Поэтому не могу не рассказать.

Почему этот робот собран так, а не иначе? Главный мой интерес [2] был не в том, чтобы сделать качественную тележку, а в том, чтобы дать ИИ хоть какое-то тело — и посмотреть, как модели ведут себя в реальном, физическом мире. О том, что из этого вышло, и была первая статья. Эта — про само тело: как оно собрано и обо что я при этом бился.

Метод сборки — дендро-фекальный. Робот буквально собран из того, что нашлось у меня в кладовке. Отдельно докуплены только две вещи: пневматический пистолет и серва к нему. Всё остальное уже лежало и пылилось.

Поэтому это не туториал «как собрать робота дома». Это, скорее, карта граблей: на какие из них я наступил, чтобы вы могли наступить на свои. Особого смысла расписывать соединение каждого провода тоже нет смысла, это всё есть в документации к плате или датчику.

Ссылка на код будет в конце.

Робот в сборе

Робот в сборе

Из чего это собрано

Тележка — DF Robot Pirate. Дифференциальная платформа: четыре колеса, четыре мотора, рулится как танк — разностью скоростей бортов. Примерно 22 см в длину, 18 в ширину. В принципе заменяется на любую другую; я одно время всерьёз думал пустить на проект свой старый робот-пылесос.

Arduino Leonardoмозг [3] тележки. Мозг тупенький: почти ничего на нём не происходит, и дальше я объясню, почему так и задумано. Про саму Arduino писать не буду — вряд ли скажу что-то новое.

Дальше — то, что в разное время приехало с Амперки:

Motor Shield Plus — драйвер моторов. Управляет парой моторов; правый и левый борта у меня запараллелены. Умеет читать потребляемый моторами ток — пригодится.

Motor Shield Plus

Motor Shield Plus

Troyka Shield — продвинутая макетка. Удобно втыкать модули, есть место распаять своё.

Troyka Shield

Troyka Shield

IMU 10 DOF v2 — на борту акселерометр, гироскоп, магнетометр и барометр. Из всего набора сначала пригодился компас (магнетометр), потом от него пришлось отказаться в пользу гироскопа — отдельная история, к ней ещё вернёмся. Гироскоп L3G4250D так себе, бывают точнее.

IMU 10 DOF

IMU 10 DOF

Дальномер — лазерный Benewake TFmini-S LiDAR. Выбор был между ультразвуком и лазером. Лазер показался модным — взял его. Зря: в опытах с зеркалами ультразвук выручил бы там, где лазер откровенно врал. Но об этом позже.

TFmini-S LiDAR

TFmini-S LiDAR

Радиомодуль на 433 МГц неизвестного происхождения. Подключается к Arduino по серийному порту, в компьютер втыкается ответная часть в USB и выглядит как обычный COM-порт. Работает на 57600 бод.

Радиомодуль

Радиомодуль

Камера — Tapo C200. Особого выбора не было: тоже лежала в кладовке. Качество так себе, но для задачи хватает. Важная деталь: видео с неё идёт отдельным каналом — по Wi-Fi, мимо радиомодуля. Почему так — в следующем разделе.

Питание — пара LiPo-аккумуляторов, на 1.3 и 2.2 Ач, оба достались от каких-то игрушек.

Пистолет — Borner PM-X, реплика Макарова. Красивый, небольшой и недорогой. К нему — серва MG996R с заявленным моментом 13 кгс·см.

Косметика — напечатанная на принтере передняя панелька с матовым стеклом (вырезано из бака сломанного увлажнителя) и гирлянда из десятка светодиодов, синих и красных, на куске того, что когда-то было макетной платой.

Узлы по отдельности

Узлы по отдельности

Тупое тело, умный хост

Главный принцип всей конструкции: тело делает как можно меньше, мозг — на компьютере.

На Arduino — только то, что обязано жить рядом с железом и реагировать [4] быстро: крутить моторы, читать датчики, плавно разгонять, отдавать телеметрию. Никакого интеллекта [5]. Всё «думание» — зрение [6], навигация, решения — на хосте, на обычном ПК.

Связь устроена двумя независимыми каналами. По радио — управление и телеметрия: лёгкий поток коротких команд и чисел в обе стороны. Видео же идёт своим путём, по Wi-Fi прямо с камеры, мимо радиомодуля. Лёгкое — по радио, тяжёлое — по Wi-Fi.

Про то, что происходит на стороне ПК, я здесь почти не рассказываю — это тема для отдельного разговора. Но один кусочек хоста показать всё-таки придётся: протокол, на котором тело и мозг разговаривают.

На каком языке они говорят

Протокол я сделал текстовым и человекочитаемым — чтобы можно было подцепиться терминалом и глазами увидеть, что робот про себя думает.

Тело раз в ~100 мс выплёвывает кадр телеметрии:

---
SQ:1240      # номер пакета (счётчик с переполнением)
LS:50        # скорости левого/правого моторов
RS:50  
LC:312       # ток левого/правого  
RC:298  
GX:-120.4    # магнетометр 
GY:88.1
V:11.8       # напряжение аккумулятора
RF:610       # дальномер вперёд, мм
***

В обратную сторону — такие же короткие команды: L/R — моторам, P — биппер, F — выстрел.

Один скромный на вид параметр окажется важнее, чем кажется, — SQ, номер пакета. Он просто монотонно растёт. Но если он вдруг скакнул или обнулился — значит, плата только что перезагрузилась. Этот счётчик ещё спасёт мне не один час отладки. Полная спецификация — в src/docs/protocol.md [7], здесь её разворачивать не буду.

Тележка поехала

Дальше — месяц кодирования. Прошивка, аппаратные тесты, плавный разгон моторов (резко давать полный газ нельзя — мотор-редукторы дёргаются, тележка прыгает). Подбор максимальной скорости, делителя для замера напряжения.

Но тележка поехала. Вперёд, назад, повороты.

А потом начались чудеса.

Зависания

Робот ни с того ни с сего замолкал и перезагружался. Случайно, без системы. Поймать такое — худшее, что бывает: оно не воспроизводится по команде.

Спас тот самый SQ. Я смотрел в поток телеметрии и видел: номер пакета спокойно растёт, растёт — и вдруг обнуляется. Плата перезагрузилась. Не зависла в коде, не потеряла связь — именно перезагрузилась, на ровном месте.

Виноваты оказались драйверы моторов. Изначально вся электроника была собрана в “бутерброд” из четырёх плат. Ардуино, далее в нее воткнут драйвер моторов, в драйвер моторов воткнута макетка, в макетку воткнут гироскоп и товарищи. Драйвер моторов стоял слишком близко к Arduino, и наводки от моторных токов сбивали плату. Лечение нашлось грубое и физическое: разнести драйверы подальше. Помогло. Десять часов на то, чтобы передвинуть железку на пару сантиметров, — нормальный размен.

Параллельно был эпизод с искрами. Перепутал провода, откуда-то прилетели искры и появился приятный запах [8] горелых микросхем. Хорошо, что плат Leonardo у меня было две.

Хромота

А однажды робот захромал.

Поехал — и заметно повело влево. Не чуть-чуть, а так, что мимо не пройдёшь. Я покрутил колёса руками: заднее левое идёт ощутимо туже остальных. Внутри всё опустилось — приехали. Мотор или редуктор под замену, а раз менять, то, наверное, все четыре сразу, чтоб одинаковые. Плюс заново настраивать управление под новые мотор-редукторы. Неделя коту под хвост, не меньше.

Полез внутрь — глянуть на моторы перед похоронами. А там провода лежат комом, как попало. И один проводок немного касается вала мотора. Чуть-чуть, но достаточно, чтобы притормаживать колесо.

Отодвинул провод. Колесо завертелось как новенькое.

С тех пор проводка внутри уложена аккуратно, по местам. И мораль, которую я каждый раз забываю и каждый раз вспоминаю: делай сразу хорошо.

Проводка после разбора полётов

Проводка после разбора полётов

Куда я еду?

С тем, чтобы ехать, разобрались. Дальше — куда ехать и насколько. И тут выяснилось, что в реальном мире все датчики немножко врут, и каждый — по-своему.

Компас-предатель

Дистанцию вперёд я мерил лидаром, повороты — компасом. По компасу всё выглядело логично [9]: вот тебе магнитное поле Земли, вот стороны света, держи курс. На столе работало.

В квартире компас превратился в генератор случайных чисел.

Сначала я грешил на моторы — они и правда наводят своё поле. Но дело было глубже. Я написал скрипт, который строил карту магнитного поля (ну почти карту). Карта вышла прекрасная: поле гуляло как хотело — арматура в стенах, проводка. Никакого ровного «на север» там не было и в помине. Для абсолютной навигации по компасу квартира оказалась непригодна в принципе.

Компас отправился в отставку.

Гироскоп и его капризы

На замену пришёл гироскоп. Он не знает, где север, зато честно говорит, с какой скоростью ты поворачиваешься. Интегрируешь угловую скорость по времени — получаешь, на сколько повернул. Сначала я подмешивал к нему компас комплементарным фильтром, потом плюнул и оставил чистый гироскоп: в квартире от компаса было больше вреда, чем пользы.

Гироскоп L3G4250D тоже с характером. Во-первых, спайки — случайные выбросы на ровном месте, которые при интегрировании превращаются в накопленную ошибку [10]. Лечится медианным фильтром. Во-вторых, дрейф нуля: даже стоя на месте гироскоп считает, что чуть-чуть поворачивается. Перед каждым стартом робот теперь пару секунд стоит неподвижно и замеряет этот фон, чтобы потом его вычитать. Сначала я брал на калибровку 20 сэмплов — оказалось мало, датчику нужно время устаканиться. Поднял до 200, и повороты стали стабильными — точность около трёх градусов.

И классика жанра, без которой никуда: робот упорно поворачивал не в ту сторону. Просишь направо — едет налево, и LLM этому удивляется. Знак угловой скорости был перепутан. Поменял знак — поехал куда просили.

Ковёр против ламината

Казалось бы, поворот на 90° — он и есть 90°. Но на ковре робот ворочается тяжелее, чем на ламинате, инерция и проскальзывание разные. Пришлось добавить защиту от застревания (если робот уже стоит, а угол не добран — поддать газу) и подобрать скорости так, чтобы ошибка держалась в пределах трёх градусов на любой поверхности.

С ездой по прямой — та же физика. Робот незаметно уводило в сторону, и я включил коррекцию по гироскопу: если в процессе движения накопилось отклонение от курса — подравниваю моторы разностью скоростей. Поехал заметно ровнее.

Чтобы всё это не зависело от задержек радиоканала, управление поворотами и движением я в итоге перенёс на саму плату — пусть крутит свой регулятор 50 раз в секунду локально. Запомнился баг, когда из регулятора по ошибке выпала одна проверка, и робот вместо плавной доводки угла срывался в неуправляемое вращение. Выглядело эффектно.

Дальномер и его обманы

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

Первый обман был технический. Лидар висел на программном последовательном порту, и данные приходили с заметной задержкой. Данные иногда застревали, иногда приходили “битые”. Бороться с этим в одиночку Arduino не успевала. В итоге я отдал лидар отдельной маленькой плате — Arduino Pro Micro, — которая только и делает, что опрашивает дальномер на полной скорости и по запросу отдаёт основному мозгу свежее число по шине I2C. Лаг ушёл.

Лидар и его отдельная плата Pro Micro

Лидар и его отдельная плата Pro Micro

Второй обман был коварнее — геометрический.

Лидар стоял сантиметров на десять ниже камеры. Камера смотрит вперёд и видит кухонный шкаф. А луч лидара, пущенный горизонтально с уровня пониже, проходит под этим шкафом и утыкается в стену где-то за ним. Камера говорит: впереди препятствие. Лидар говорит: свободно, метра два. Робот верит цифре — и упорно лезет под шкаф.

Особенно показательно это вышло у Grok: он был железно уверен, что путь открыт, и раз за разом таранил шкаф, не понимая, что происходит. Лечится переносом лидара вверх, к уровню камеры, — чтобы они смотрели в одну точку.

А третий обман я предвидел заранее, ещё на этапе выбора. Лазер не дружит с зеркалами: луч уходит «за» зеркало и возвращается длиной вдвое больше реальной. В опытах с зеркалами это постоянно сбивало картину. Вот тут ультразвуковой дальномер был бы честнее лазерного — он бы просто отразился от стекла. Но лазер показался модным. Запомним.

Пистолет, который съел неделю

Роботу нужно не только смотреть и говорить, но и как-то влиять на мир. Манипулятор — сложно и долго. Пистолет — просто: спустить курок может и серва. Так я думал.

Планировал на всё про всё один день. Потратил пять.

Первая засада — усилие на курке. Оно оказалось неожиданно большим, и имевшаяся серва не тянула его вообще. Заказал серву помощнее, MG996R, с моментом 13 кгс·см — этой хватило.

Дальше — механика передачи. Первый вариант: ось сервы совпадает с осью курка, тянем через качалку. Качалка под нагрузкой изгибается, усилие уходит в её прогиб — ненадёжно. Второй вариант, он же финальный: серва уезжает назад, а курок тянет вилка-тяга, симметрично, на растяжение. Заработало.

Но тут вылез сюрприз посерьёзнее. При выстреле алюминиевая пластина шасси заметно изгибалась — усилие сервы было велико. Пришлось добавить ребро жёсткости, а из-за него — переставлять и перекомпоновывать камеру с лидаром. И всё это с бесконечными итерациями 3D-печати: напечатал, примерил, не сошлось, поправил модель, напечатал заново.

В прошивке пистолет живёт тихо: серва сидит на отдельном таймере, чтобы не драться с моторами за железо, и срабатывает по короткой команде, не блокируя основной цикл. Это как раз было просто. Сложным оказалось железо.

Ребро жёсткости

Ребро жёсткости

Лицо и подсветка

Чистая косметика, но без неё робот — просто этажерка на колёсах.

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

Передняя панелька

Передняя панелька

Подсветку — десяток светодиодов, синие и красные — я ставил поначалу просто для красивых фотографий: пусть робот в кадре светится. А потом подумал: чего я сам ими управляю? Отдал управление роботу. В одной из сессий из первой статьи это сыграло неожиданно — модель сама выбрала себе цвет и сама же объяснила почему («холодный, как мой металлический характер»). Но это уже про мозг, не про тело.

Камеру я уже поминал — Tapo, из кладовки, качество так себе. Зато круглая белая голова неплохо смотрится и придаёт всей конструкции что-то живое.

Камера

Камера

Что я из этого вынес

Если коротко.

Половина проблем была не в коде, а в физике: наводки, питание, провод, прилипший к валу. Датчики врут, и врут по-разному — компас в квартире бесполезен, лазер не видит зеркал и лезет под шкафы. Механика съедает время непропорционально обещанному: то, что на бумаге один день, в железе оборачивается неделей. И реальный мир всегда жёстче любой модели у тебя в голове.

Всё это, в итоге, — просто рабочее место для ИИ. Тело, чтобы было чем смотреть, ехать и дотягиваться до мира. А о том, что в этом теле делает сам ИИ, как он себя в нём ведёт и что из этого вышло, — была первая статья [1].

Осталась третья сторона — мозг на компьютере: зрение, навигация, агентный цикл, монтаж записей сессий. Про неё я здесь сознательно почти промолчал. Вопрос к вам: интересно ли про эту, хостовую часть? Если да — напишите в комментариях, расскажу.

Робот в сборе

Робот в сборе

Исходный код прошивки [11]

Там же лежит написанный Клодм файл CLAUDE.md с пояснениями, что и как устроено.

Автор: stg34

Источник [12]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/31389

URLs in this post:

[1] Я выпустил нейросеть в реальный мир — и стало не смешно: https://habr.com/ru/articles/1042282/

[2] интерес: http://www.braintools.ru/article/4220

[3] мозг: http://www.braintools.ru/parts-of-the-brain

[4] реагировать: http://www.braintools.ru/article/1549

[5] интеллекта: http://www.braintools.ru/article/7605

[6] зрение: http://www.braintools.ru/article/6238

[7] src/docs/protocol.md: https://raw.githubusercontent.com/stg34/robo-llm/refs/heads/main/src/docs/protocol.md

[8] запах: http://www.braintools.ru/article/9870

[9] логично: http://www.braintools.ru/article/7640

[10] ошибку: http://www.braintools.ru/article/4192

[11] Исходный код прошивки: https://github.com/stg34/robo-llm/tree/main/src

[12] Источник: https://habr.com/ru/articles/1044572/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1044572

www.BrainTools.ru

Rambler's Top100