Flix: разработка полетного контроллера с нуля
СтатистикаПо всем вопросам @okalachev Чат обсуждения: @opensourcequadcopterchat GitHub: https://github.com/okalachev/flix Учебник: https://quadcopter.dev
- Последний пост
- 15 авг.
- Последнее чтение
- 18:12
- Постов за неделю
- 3
- Всего постов
- 30
- Тип
- открытый
- Язык
- русский
- Категория
- Образование
- В каталоге с
- 13 авг.
- 1/24сутки в ленте
- 629
- 1/48двое суток
- 720
- 1/72трое суток
- 777
Оценка по просмотрам недавних постов: пост набирает почти всё за первые сутки.
Посты
Где перед у дрона?
Где перед? У моей платы уже целых два пользователя (в качестве альфа-тестеров), и в принципе, она летает. Сейчас делаю третью ревизию, и вот думаю: а не стоит ли мне ее перевернуть? Конечно, ориентацию можно задать софтверно, но рисунки и надписи на плате софтверно не задашь, они физические. Мне интересно узнать ваше мнение. Как вам кажется, где у этого дрона перед?
Уменьшаю степень гиковости прошивки Наконец-то доделал несколько фич, которые хотел сделать еще до Роболагеря. Они делают прошивку проще для тех, кто просто хочет собрать дрон, а не разбираться в коде (таких оказалось довольно много). 1️⃣ Полная конфигурация IMU из QGroundControl (commit) IMU теперь конфигурируется параметрами: 🔹Выбор модели IMU (IMU_MODEL) и шины подключения (IMU_BUS). 🔹Настройка кастомных пинов для подключения (IMU_PIN_SCK, IMU_PIN_MISO, IMU_PIN_MOSI, IMU_PIN_CS, IMU_PIN_SCL, IMU_PIN_SDA, IMU_PIN_INT). Это последнее критичное, что можно было настроить только изменением исходника. До этого я уже добавил параметры для настройки пинов моторов, пина RC-приемника и пина измерения напряжения — теперь все это меняется динамически. Прошивка Flix стала универсальной, и поэтому теперь есть... 2️⃣ Предсобранные бинарники (commit) Бинарники выкладываются автоматически на каждый коммит и теперь всегда будут доступны по таким красивым ссылкам: 🔹quadcopter.dev/flix.esp32.merged.bin — для классической ESP32. 🔹quadcopter.dev/flix.esp32c3.merged.bin — для ESP32-C3. 🔹quadcopter.dev/flix.esp32s3.merged.bin — для любого ESP32-S3. 🔹quadcopter.dev/flix.esp32s3.opi.merged.bin — ESP32-S3 с OPI PSRAM (8/16MB). 🔹quadcopter.dev/flix.esp32s3.qspi.merged.bin — ESP32-S3 с QSPI PSRAM (2MB). Тип подключения PSRAM, увы, можно выбрать только на этапе компиляции, поэтому это минимальный набор сборок, который я смог родить, чтобы покрыть все более-менее популярные платы на ESP32. Теперь любой может накатить прошивку прямо из браузера, используя вот этот веб‑прошивальщик, вообще без установки каких-либо программ — это реально работает, я проверил. Использовать Arduino IDE/arduino-cli не обязательно. Инструкция. Конечно, на ивентах мы по-прежнему будем собирать прошивку, это важная часть философии, но наличие бинарников понизит порог входа. 3️⃣ А еще собирается бинарник для платы Flix2: quadcopter.dev/flix.flix2.merged.bin. Он полностью преднастроен, ready-to-fly. Я добавил файл config.h, где можно менять дефолтные значения параметров и реализовывать что-то вроде таргетов. Все таргеты будут лежать в одном файле, так гораздо проще, а параметры задаются обычными переменными. Вообще, я хочу сделать таргеты через FQBN Arduino, и добавить прямо официальный FQBN для Flix2 (вроде это не так сложно), но пока это простой define.
Каково же было мое удивление, когда в поисках вдохновения я загуглил 8520 quadcopter frame и на первой же странице увидел собственную раму для Flix, продающуюся на индийском Амазоне (!), по цене 215 рупий (с 64-процентной скидкой). Не знаю, зачем это кому-то может быть нужно, ведь ее очень легко распечатать, но, видимо, покупают, раз продается. Причем они даже взяли мою фотографию, но зачем-то перекрасили ее нейронкой в черный цвет. Ссылку на оригинал они оставить забыли. Еще я как-то нашел такой китайский проект: https://github.com/songge8/CF-Drone. Это коптер на печатной плате, прошивка полностью базируется на моей, только переведена на китайский. Из кода убраны все мои копирайты, и им даже хватило наглости добавить туда свои. Вообще, меня удивляет насколько непринужденно люди убирают копирайты из кода, который они берут, как будто они там стоят для красоты. Я создал issue с просьбой вернуть копирайты обратно и добавить ссылку на оригинал, но спустя месяц автор так и не отреагировал.
Результаты Роболагеря 1️⃣ Я сменой доволен. Дети за девять дней сделали реально работающую систему для position control квадрокоптера. Я даже не знаю, чтобы где-то было что-то аналогичное в рамках образовательного мероприятия. И получился первый осмысленный автоматический полет Flix. 2️⃣ Нашли баги/проблемы, это хорошо. Многое было починено по ходу. Переделаны некоторые части кода, добавлен фейлсейф по перевороту. 3️⃣ Я специально поменял Wi-Fi на ESP-NOW, учитывая какие проблемы с связью были в прошлом году, но это как-то не очень помогло. Связь с дроном все равно работала нестабильно. Ожидал от ESP-NOW большего. Возможно, это из-за большого количества Wi-Fi точек, а может что-то мы не так делали. Тут надо думать. С такой связью разрабатывать сложно. 4️⃣ В день соревнований между участниками завязалась настоящая полемика. Те, кто не выиграл, обвинили победителей в использовании вайбкодинга. Мол, это неспортивно и нечестно. Победители действительно активно использовали ИИ, и мы это никак не регламентировали. Результаты остались в силе. А как вы думаете, допустимо ли использование ИИ/агентов на подобных соревнованиях или нет?
без подписи
Дизайн простого position control для квадрокоптера @edkaya2320 выложил свою реализацию position control на GitHub, и можно посмотреть, насколько она проста по дизайну. Код там слегка сумбурный, но он сделан в рамках смены, так что это нормально. В целом, это обычный трехмерный PID-регулятор. ↔️ Горизонтальная позиция дрона определяется по камере, закрепленной сверху. Поиск делается классическим методом: обнаружение дрона по цвету (дрон обклеен зеленой лентой), поиск контуров, выбор наибольшего контура, расчет центра масс контура для определения координат. Затем эти координаты передаются на дрон в MAVLink-сообщении VISION_POSITION_ESTIMATE. Это делается через библиотеку pyflix и ESP-NOW. Дрон принимает эти координаты и передает их в два PID-регулятора, который сразу рассчитывает требуемые углы по pitch и roll. Эти углы ограничиваются, чтобы дрон мог перелетать из точки в точку без слишком резких наклонов. При запуске дрон должен быть ориентирован так, чтобы оси координат изображения совпадали с его собственными осями координат, иначе удержание позиции работать не будет. ↕️ Удержание высоты работает при помощи лазерного дальномера VL53L1X. При обновлении данных расстояние отправляется в PID. Чтобы получить итоговый уровень газа, к выходу PID добавляется константа heightC (газ висения). Чего здесь нет: 🔹Фильтра Калмана. 🔹Использования акселерометра для расчета скорости и позиции. 🔹Вообще расчета скорости и velocity control в каком-либо виде. 🔹Калибровки камеры. И даже с таким простым алгоритмом получается довольно хорошее удержание позиции. Конечно, на видео я выбрал удачный фрагмент, но тем не менее. Видимо, очень многое зависит от качества полета дрона в принципе, а не только от сложности алгоритма. Теперь я собираюсь добавить position control в основную прошивку. Возможно, именно такой простой вариант, раз уж он работает.
Сегодня были финальные испытания. И целых два дрона смогли стабильно удерживать позицию дольше 2 минут. Победил @edkaya2320 с дополнительными баллами за крутейшее техническое решение: реализована полноценная навигация с выбором целевой точки по клику. В итоге дети смогли за 9 дней собрать дроны, разработать систему компьютерного зрения, передачу данных на дрон и простейший position control, причем он работает довольно неплохо. Думаю, что это реально крутой результат. Позже напишу более подробные выводы по смене.
Первый полет с удержанием позиции Задача автоматического удержания позиции оказалась совсем не простой для ребят (и связь отваливается, и esp’шки сгорают, и еще всякое), несколько человек уже были близки, но сегодня, в последний день перед финальными испытаниями, один из участников смог ее решить! На видео самый первый успешный полет. Используется верхняя камера, дальномер, связь через библиотеку pyflix, простые пиды по трем координатам. Осталась еще одна пара, посмотрим, у кого что получится. Эра автономки на Flix открыта.
без подписи
без подписи
без подписи
Смена в Роболагере в самом разгаре — большая часть дронов уже летает. Вообще, такое мероприятие это идеальный способ тестирования Flix. В каком-то смысле это даже лучше, чем профессиональные тестировщики. Мы намеренно не даем участникам доскональную схему сборки, даем возможность экспериментировать с компонентами, дизайном рамы, электроникой и, конечно, модификацией прошивки. Для проекта это большой плюс, потому что позволяет проверить сразу много самых разных вариантов использования. Для детей это тоже плюс. Я думаю, что самому решать неожиданные возникающие проблемы интереснее, чем собирать готовый выверенный комплект. По ходу смены я сделал много правок в документации, исправил баги в прокси для ESP‑NOW и добавил туда функцию автоматического поиска по каналам. Еще я добавил новый фейл-сейф: отключение пропеллеров в случае переворота дрона, он действительно оказался нужен. Обнаружили периодические креши из-за системы логирования, пока ее отключили. Один из участников уже смог реализовать удержание высоты! Теперь будем экспериментировать с удержанием позиции. Установили сверху полигона камеру, которая будет находить дрон и передавать ему координаты. Итоговая задача, которую мы придумали для соревнования: автоматический взлет и удержание позиции (с использованием любых доступных датчиков). Чей дрон дольше провисит в заданной области, тот и выиграет.
без подписи
Flix снова в Роболагере ⛺️ Мы с коллегами @ina_tix и @dipopovit начали новый курс в Роболагере: «создание мини-квадрокоптера: проектирование, сборка, алгоритмы». Это детский робототехнический лагерь под Питером, который организует Лицей 239. В этом году попробуем сконцентрироваться на навигации — для позиционирования используем дальномеры, внешнюю камеру. Будем использовать ESP32S3 Super Mini. Еще у меня есть несколько плат Flix2, комплект бесколлекторных моторов 0802 с регулями (попробуем на них что-нибудь собрать) и куча другого оборудования. Если есть идеи, что еще можно сделать — пишите!
1000 звезд у Flix на GitHub 🎉 Это столько же, сколько было у ArduPilot в 2015-м и у PX4 и Betaflight — в 2017-м. Конечно, мой проект пока далеко не такой популярный, но все равно круто, я считаю.
без подписи
Причина бага с уплыванием дрона Новая система логирования позволила мне проводить частотный анализ, чтобы искать аномалии и причину проблем с полетом. Я могу смотреть на спектр как на входе системы (данные с гироскопа), так и на выходе (сигналы на моторы). Если есть какая-то паразитная частота, то она обычно проходит сквозь весь тракт управления (включая пиды и другие обработки), только если ее специально не подавить фильтром. А на алгоритм управления такая паразитная частота влияет плохо: это можно представить себе как если бы водитель при езде по булыжной дороге пытался бы объезжать каждый камешек, вместо того, чтобы сосредоточиться на реальных изгибах дороги. Довольно быстро стало понятно, что диаграммы «плохих» полетов отличаются большим пиком в районе 380 Гц. А «хорошие» имеют почти плоский, спокойный график, у них нет сильных пиков нигде, кроме области возле нуля, где собственно, и находятся сами полетные маневры. Поскольку пик проявляется с определенным типом аккумулятора, я предположил, что именно форма и размер аккумулятора создает какой-то дополнительный резонанс. Я попробовал подвесить аккумулятор на резинке, вместо того, чтобы устанавливать его в кейс рамы, и — о чудо — качество полета сильно улучшилось и баг с уплывающим гироскопом перестал проявляться! Дополнительно, я добавил в обработку гироскопа notch-фильтр, который вырезает полосу частот из сигнала, на 382 Гц и с полосой 40 Гц. Субъективно, это тоже сказалось положительно на управлении, а диаграмма стала еще более плоской. Конечно, по идее полет дрона не должен так сильно зависеть от расположения аккумулятора, так что тут надо еще разбираться. Возможно, я переделаю держатель аккумулятора или напечатаю раму из другого пластика. А может, резонанс вызывается комбинацией механических и электрических причин, а не только механическими? Но в любом случае, сейчас у меня есть сетап с Flix2, который нормально летает на любом типе аккумулятора, и я могу продолжать экспериментировать дальше.
Новая система логирования Для глубокой отладки полета нужен полетный лог, но моя реализация логирования была слишком уж примитивной. Я решил полностью ее переделать (ветка new-logging). Вообще существует два подхода для логирования состояния робота: упрощенный и обычный. Упрощенный — это простая таблица значений, где в первом столбце хранится время, а в последующих — всё остальное. Именно такой вариант и был реализован у меня. Но минус такого подхода довольно очевиден: память для лога ограничена, а далеко не все значения мы хотим записывать с одинаковой частотой. Например, гироскоп отдает данные с частотой 1000 Гц, и для анализа нам важно запомнить каждый сэмпл. А пульт работает на 50 Гц, и писать его на 1000 Гц крайне расточительно. С таблицей это не решается. Обычное решение это запись лога в виде последовательности событий. Событие — это обновление какой-то части параметров, ассоциированное с определенным моментом времени. Такой подход позволяет писать разные данные с разной частотой. Я долго думал, как проще и удобнее всего реализовать второй вариант. В итоге придумал вариант с системой топиков для лога. Для каждого топика задается набор его значений (полей) и ограничение по частоте (throttling). Если throttling не задан, он будет записываться при любом изменении его значений (получается что-то вроде публикации в топик). Для каждого поля топика задается его имя и значение, причем значение это указатель на любую переменную (float/int/bool) или даже на функцию! Лямбды тоже поддерживаются. В результате конфигурация лога получилась крайне гибкой. Выглядит она примерно так: // Топик с временем, обязательный: LogTopic({"t", &t}), // Топик гироскопа, без ограничения по частоте: LogTopic( {"gyro.x", &gyro.x}, {"gyro.y", &gyro.y}, {"gyro.z", &gyro.z}), // Топик акселерометра, частота 50 Гц: LogTopic(50, {"acc.x", &acc.x}, {"acc.y", &acc.y}, {"acc.z", &acc.z}), // Топик с использованием лямбд: LogTopic(50, {"attitude.roll", []() { return attitude.getRoll(); }}, {"attitude.pitch", []() { return attitude.getPitch(); }}, {"attitude.yaw", []() { return attitude.getYaw(); }}), // Топик с обычной функцией: LogTopic(5, {"temp", &temperatureRead}), Лог записывается в RAM или PSRAM (выбирается параметром LOG_MEMORY), в виде кольцевого буфера. Каждый блок данных состоит из номера топика (1 байт) и значений топика (4 байта на каждое значение). Поскольку это кольцевой буфер, парсеру необходимо как-то понять, где начало первого блока данных, для этого перед блоком данных иногда записывается специальная магическая последовательность: 1A 91 4F F6 7F. Фичи: ▶️ Я поддержал протокол MAVLink для логов, поэтому файл лога скачивается прямо из QGroundControl. После этого специальными утилитами он преобразуется в формат CSV (удобно для анализа ИИ) или MCAP (для анализа программой Foxglove). ▶️ Любое логируемое значение можно вывести в консоль командой l, например: l gyro.x. Также любое значение можно вывести в MAVLink командой log expose, например, log expose temp. Оно появляется в виде сообщения NAMED_VALUE_FLOAT, по нему можно строить график. ▶️ Throttling топика можно поменять «на живую» командой log <name> <rate>. Это сохраняется в флеш. В итоге, конечно, получилось довольно сложно, не совсем в духе Flix. Еще подумаю о том, что можно упростить. Но теперь у уже меня появилась возможность глубоко исследовать, что происходит в дроне при полете. И самое главное — в комментариях я выложу свежеполученные полетные логи, с хорошим и с плохим полетом (с багом). Любой желающий сможет их и проанализировать, возможно, получится понять, что именно вызывает баг в полете дрона на плате!
Траблы с платой. В целом, дрон на моей плате летает. Но не совсем. Главная проблема — это странный баг с уплывом гироскопа. Сразу же после начала полета гироскоп может резко начать уплывать, причем почти всегда именно вправо. Этот баг плавающий, иногда он не проявляется, а особенно он проявляется на одном конкретном аккуме (меньше токоотдача?). Обиднее всего, что обычная версия Flix и версия с внешней IMU на этом же аккуме летает без проблем. Попытался воспроизвести этот баг с лабораторным источником питания, пробовал разные входные напряжения, но пока не смог увидеть закономерности. Также пробовал найти проблему в прошивке, даже ставил разные режимы DLPF гироскопа, очевидной ошибки не увидел. Есть еще одно наблюдение: звук моторов на плате отличается от звука моторов в обычной версии. На определенной мощности они как будто входят в некий резонанс, звук становится более резким, появляются вибрации. На старой версии такого нет, хотя рама там такая же. Возможно, уплыв гироскопа как-то связан с этими вибрациями. Пока я не очень глубоко все это исследовал, буду рад любым вашим советам, в какую сторону покопать.