К написанию статьи меня подтолкнула возня с BetaFPV Matrix 1S, одним из немногих полётных контроллеров (тут и далее – ПК), специально созданных для DJI O4. На его основе делался микродрон, умеющий в 4К, и поскольку цифровой модуль – вещь сравнительно дорогая, а цифровая картинка имеют свойство резко (РЕЗКО!) отваливаться, то ключевым требованием стала безотказная работа навигации. Точное определение местоположения сильно поможет если не вернуть дрон, то хотя бы найти его после падения.
Ранее я имел дело только с аналоговыми системами на основе JHEMCU GHF435AIO. Донастроенный приёмник ГНСС (тут и далее – приёмник) прекрасно работал с ним, считывая 25 (!) спутников за 2 минуты:
C Матриксом же случился облом. По непонятной причине спутники ловились из рук вон плохо: минимально необходимые 6 появлялись в среднем за 4-5 минут, и это при том, что использовался тот же приёмник с теми же настройками (я буквально выпаял его и переставил на другой дрон). Сначало мне казалось, что это особенности цифры: в сети полно жалоб именно на цифровое видео, душащее навигацию. Убрав аналоговый VTX и цифрровой модуль, я поставил опыт второй раз с тем же неутешительным итогом. Последними проверил настройки самого Бетафлайта и они оказались одинаковыми в части, касающейся ГНСС. Уведеть их все можно в консоли с помощью
get gps
Стало понятно, что качество навигации складывается из нескольких составляющих:
спутниковая обстановка (это дано нам свыше)
настройка/оптимизация приёмника (это уже сделано)
настройка/оптимизация ПО (это ещё не сделано)
настройка/оптимизация одной неочевидной вещи (об этом в конце)
Дальше шаг за шагом мы поймём, как улучшить работу навигации и повысить надёжность вашей сборки.
Матчасть
BetaFPV Matrix собран на микроконтроллере STM32G47X, который хоть и уступает AT32F435, лежащему в основе GHF435, но не разгромно:
JHEMCU GHF435AIO |
BetaFPV Matrix |
|
|---|---|---|
Микроконтроллер |
AT32F435 |
STM32G473 |
Ядро |
Cortex-M4F |
Cortex-M4F |
Частота |
288 МГц |
170 МГц |
FPU |
Есть |
Есть |
DSP-инструкции |
Есть |
Есть |
SRAM |
до 512 КБ |
до 128 КБ |
Т. е. если мы грубо примем, что GHF435 по частоте в 1,7 раз мощнее STM32G473, то можно было бы ожидать захват 25 спутников не за 2, а за 3-4 минуты. В действительности получалось от силы 10 за неприличные 8-9 минут. Кроме того, была ещё одна проблема: неправильное определение высоты.
Первые шаги: возврат домой и посадка "по приборам" (неисправным)


Точность определения высоты можете оценить самостоятельно.
И если с ожиданием ещё можно смириться, то кривое определение высоты в худшем случае приводит к поломке: если при посадке полётник считает, что до земли 1-2 м, а на самом деле 15-20 м (как в примере выше), то ничем хорошим это не закончится.
Прокачиваем прошивку
Выкачав исходники я открыл проект в CLIon-е и начал разбираться с кодом gps.c (более 3 тыс. строк!). Под капотом у нас оказался конечный автомат, который побайтно читает и обрабатывает данные из UART. С подробным описанием его работы можно ознакомиться по ссылке, общая схема выглядит так:

Несколько упрощает задачу то обстоятельство, что протокол NMEA в нашем случае выключен на уровне приёмника и отвечающий за него код мы пропускаем.
Доделываем начатое
В предыдущей статье за кадром осталась пара полезных функций, доступных в новых версиях приёмников, в частности AssistNow Online и AssistNow Autonomous. Обе являются вариациями A-GNSS (Assisted GNSS), решающей проблему крайне медленной (~50 бит/с) передачи альманаха и эфемерид со спутников. Суть A-GNSS заключается в получении данных из внешнего источника и их сохранении на устройстве для уменьшения TTFF (Time To First Fix). AssistNow Online рассматривать не будем, т. к. являясь собственным решением u-blox в настоящее время он требует учётной записи и доступен через
Thingstream API
u-center 2
HTTPS
Напротив, AssistNow Autonomous включается простым выставлением флага CFG-ANA-USE_ANA, при чём делается это с помощью стандартного вызова CFG-VALSET() уже реализованного в коде Бетафлайта:
UBX-CFG-VALSET key: CFG-ANA-USE_ANA value: 1
Сам по себе AssistNow Autonomous работает как локальное предсказание орбит спутников, осуществляемое самим приемником.
Принцип действия:
Приемник наблюдает спутники и получает эфемериды.
Эти данные сохраняются в энергонезависимой памяти приемника (если она имеется) либо в резервируемой памяти.
На основе предыдущих наблюдений приёмник сам предсказывает орбиты спутников.
Предсказанные орбиты используются при следующем включении.
В итоге:
приемник не ждет загрузки эфемерид со спутника, а использует свои
ускоряется захват спутников
Такие предсказанные данные называются AOP – AssistNow Autonomous Orbit Predictions.
Оказалось, что в исходном коде уже находилась закомментированная функция ubloxSendNavX5Message() с нужным функционалом. Оставалось только слегка причесать её, пробросить в CLI и проверить работоспособность. Для включения выполняем в консоли
set gps_ublox_enable_ana = ON save
Запомните: AssistNow Autonomous улучшает работу навигации только при периодическом использовании, т. к. приёмнику необходимо накапливать и обновлять данные, устаревающие со временем. Обычно предсказанные орбиты протухают через 3-6 дней, поэтому не стоит ожидать прорыва при самом первом включении и после длительного перерыва.
Основной код
Точка входа находится в функции gpsUpdate(). Выше было отмечено, что обработчик данных представляет собой конечный автомат. Чтобы лучше понять его работу, назовём его машиной состояний (дословный перевод state machine). Мы получаем на вход байты, обрабатываем их и каждый байт влияет на состояние системы. Приёмник отдаёт программе сообщения только двух видов – NAV-PVT и NAV-SAT, а после взведения остаются только NAV-PVT. Состояния машины переключает функция gpsNewFrameUBLOX() и для её оптимизации рассмотрим устройство типичного сообщения:
PREAMBLE_SYNC_1 1 byte PREAMBLE_SYNC_2 1 byte MESSAGE_CLASS 1 byte MESSAGE_ID 1 byte PAYLOAD_LENGTH_LSB 1 byte PAYLOAD_LENGTH_MSB 1 byte PAYLOAD_CONTENT 92 bytes for NAV-PVT, up to ~392 bytes for NAV-SAT CHECKSUM_A 1 byte CHECKSUM_B 1 byte
Таким образом, NAV-PVT содержит 100 байт, из которых 92 – это полезная нагрузка, а 8 – служебные данные. В gpsNewFrameUBLOX() есть switch-case, меняющий состояние в зависимости от полученных данных, горячей в нём является ветка case UBX_PARSE_PAYLOAD_CONTENT, выполняющая запись полезных байтов и вычисление контрольной суммы. Вынесем её в отдельный if-блок перед switch-case.
Этот же трюк можно повторить в UBLOX_parse_gps(). Внутри находится switch-case, проверяющий вид сообщения через диапазон разреженных значений вроде:
MSG_POSLLH = 0x02 MSG_STATUS = 0x03 MSG_SOL = 0x06 MSG_VELNED = 0x12 MSG_DOP = 0x04 MSG_PVT = 0x07 MSG_SAT = 0x35 //...
Обратите внимание, что если в прошлом примере переключатель обрабатывал лишь 9 возможных состояний, описанных в перечислении ubxFrameParseState_e, то в UBLOX_parse_gps() этих состояний уже 12 и они вычисляются динамически вызовом CLSMSG():
switch (CLSMSG(ubxRcvMsgClass, ubxRcvMsgID)) { case CLSMSG(CLASS_MON, MSG_MON_VER): {/*...*/} case CLSMSG(CLASS_NAV, MSG_NAV_POSLLH): {/*...*/} }
С большой вероятностью GCC превратит этот switch-case не в таблицу переходов (jump table), а в дерево сравнений, что делает выгодным перенос case CLSMSG(CLASS_NAV, MSG_NAV_PVT) на самый верх, ведь в этом случае CLSMSG() вызывается в лучшем случае дважды, в худшем – трижды, а не 6 и 8 раз, как было ранее.
Потом я подумал: раз код в gps.c густо обмазан #ifdef USE_GPS_NMEA, а сам NMEA выключен, то зачем вообще компилировать и загружать эти участки? В нашем сценарии этот код всё равно никогда не выполняется. И действительно: отключение компиляции отвечающих за NMEA участков позволяет уменьшить размер обрабатывающего ГНСС кода на 2 кБ. Мелочь, а приятно. Кстати, собрать прошивку из своей ветки со своими флагами совсем несложно. Заходим на https://app.betaflight.com, загружаемся в режиме DFU и делаем как на картинке:

Здесь #15282 – это номер запроса на слияние, он берётся из адреса страницы или сообщения от бота, а флаг GPS_DISABLE_NMEA указывается в поле "Пользовательские настройки".
Если хочется поиграть со сборкой локально, то перейдя по ссылке можно увидеть лог сборки и команду make (здесь флаг выставляется как -DUSE_GPS_DISABLE_NMEA):
$ make BETAFPVG473 EXTRA_FLAGS="-D'BUILD_KEY=9d950d66b39cae6133d174a5df5e62d7' -D'RELEASE_NAME=2026.6.0-alpha' -DCLOUD_BUILD -DUSE_DSHOT -DUSE_GPS -DUSE_GPS_DISABLE_NMEA -DUSE_GPS_PLUS_CODES -DUSE_OSD_HD -DUSE_SERIALRX -DUSE_SERIALRX_CRSF -DUSE_TELEMETRY -DUSE_TELEMETRY_CRSF"
Просто копируете её и запускаете из корня проекта, на выходе будет файл вроде ~/betaflight/obj/betaflight_2026.6.0-alpha_STM32G47X_BETAFPVG473.hex – это и есть прошивка, которую конфигуратор накатит на ваш полётник.
На этом мои собственные мысли кончились и я просто скормил код ИИ (Claude) прямо в Силайоне и спросил, можно ли как-то улучшить производительность обработки сообщений UBLOX? На удивление Клод выдал вполне вменяемый патч.
Внутри по большому счёту два улучшения:
1) кэширование количества байтов, ожидающих на выходе последовательного порта (насколько я понимаю, в микроконтроллере это сравнительно дорогое действие т. к. оно предусматривает обращение к периферии), и если раньше было
while (serialRxBytesWaiting(gpsPort)) { //... // Add every byte to _buffer, when enough bytes are received, convert data to values gpsNewData(serialRead(gpsPort)); }
то стало
uint32_t rxBytesWaiting = serialRxBytesWaiting(gpsPort); while (rxBytesWaiting-- > 0) { // читаем байт uint8_t b = serialRead(gpsPort); // обрабатываем полученный байт }
2) вынос переключения между UBLOX/NMEA на уровень выше (раньше выполнялся на каждый байт) вместе с вызовом DEBUG_SET():
//было while (serialRxBytesWaiting()) { uint8_t c = serialRead(); //... gpsNewData(serialRead(gpsPort)); } static void gpsNewData(uint16_t c) { DEBUG_SET(DEBUG_GPS_CONNECTION, 1, gpsSol.navIntervalMs); if (!gpsNewFrame(c)) { // no new nav solution data return; } //... } bool gpsNewFrame(uint8_t c) { switch (gpsProvider) { // фактически выполняется для каждого байта case GPS_UBLOX: return gpsNewFrameUBLOX(c); case GPS_NMEA: return gpsNewFrameNMEA(c); } } //---------------------------------------------------------------------- //стало примерно так if (gpsProvider == GPS_UBLOX) { while (rxBytesWaiting-- > 0) { { //... if (gpsNewFrameUBLOX(serialRead(gpsPort))) { gpsHandleFrameComplete(); //здесь вызывается DEBUG_SET(); } } } else { // NMEA }
Это позволило сократить цепочку вызовов, а также не компилировать код для NMEA при сборке с флагом -DUSE_GPS_DISABLE_NMEA:
было gpsUpdate └─ gpsNewData └─ gpsNewFrame └─ gpsNewFrameUBLOX/NMEA стало gpsUpdate └─ gpsNewFrameUBLOX/NMEA
Конечно, после ИИ код пришлось допиливать руками, и тем не менее я был впечатлён, ведь даже такое относительно простое улучшение сразу дало ощутимый прирост в скорости обработки и количестве спутников:
Если раньше минимально необходимые 6 спутников появлялись за 4+ минуты, то теперь время сократилось до 3, а максимальное количество выросло с 9 до 14! Этого всё равно было мало, тем более что сохранялась проблема неправильно определяемой высоты.
Дальше ИИ предложил изменить подход к обработке времени. Вместо многократного преобразования через календарь время теперь считывается прямо из NAV-PVT.
После всего этого стало сильно лучше. Главное: увеличение количества спутников позволило точнее определять высоту. Это ожидаемо, ведь вертикальная составляющая решения GNSS обычно имеет худшую геометрию, чем горизонтальная, и особенно чувствительна к количеству и взаимному расположению наблюдаемых спутников.
Если раньше было вот так:



То стало так:

К этому моменту стало ясно, что оптимизация программной части улучшила ситуацию, но не объяснила её полностью. Значит, узкое место следует искать уже не в коде, а в аппаратной части.
Там обнаружился неочевидный способ улучшить качество навигации и он потребует знания принципов работы импульсных преобразователей постоянного напряжения.
Углубляемся в схемотехнику
Выше я грубо сравнивал STM32G473 с AT32F435: последний мощнее в 1,7 раза, однако качество его работы даже на прошивке без оптимизаций значительно выше первого в тех же условиях. После отключения видеосигнала возникло предположение, что мы имеем дело с неким неявным, но мощным фактором. Этот фактор – питание.
В случае AT32F435 приёмник запитан от линии +4,5 В, во втором – от +5 В. Первый работает на батарее 4S (напряжение 16,8 В), второй – на 1S (4,2 В). Очевидно, что в обоих случаях для получения пониженного/повышенного напряжения нам необходим импульсный DC-DC преобразователь. Их врождённым пороком является пульсация выходного напряжения.
Для прочей периферии (камеры, поисковые пищалки, светодиодные ленты и т. п.) пульсации были бы неважны, однако, руководство по интеграции чипов М10 в разделе 3.3.1.1 прямо указывает:
Note that the supply voltage must be clean, as any noise could directly couple into the RF part of the GNSS receiver which affecting the overall GNSS performance.
Кроме того, в разделе 4.1.1 (описание вывода VCC) говорится:
Do not add series resistance greater than 0.2 Ω on the supply line to avoid voltage ripple due to the dynamic current conditions.
Производитель прозрачно намекает, что работоспособность приёмника прямо зависит от качества питания. Обращаю внимание, что это касается не модуля HGLRC M100 Mini, а самой микросхемы MAX-M10S. Мне неизвестно, находится ли на плате модуля фильтр или стабилизатор (подозреваю, что нет, а производитель на мой запрос не ответил), u-blox также не спешит делиться конкретными рекомендациями.
Плата приёмника крупным планом (нижний металлический экран снят)


В Сети нашлось несколько документов, описывающих качество постоянного напряжения:
MIL-STD-704
DO-160
однако, ни один из них не даёт конкретных указаний по предельному размаху пульсаций напряжения и уровню шума для чувствительных устройств. Только ATX12V (отраслевой стандарт компьютерных блоков питания), в разделе 3.2.6 Output Ripple/Noise говорит о допустимых колебаниях ±50 мВ для +5 В (5% от номинала). Ниже мы увидим, насколько это применимо к нашему случаю.
Зададим в качестве планки показатель GHF435 и будем стараться уменьшить пульсации насколько возможно. В конце концов, мне сложно представить сценарий, в котором сглаживание ухудшит работу датчика.
Начнём с замеров питания невзведённого полётника (обязательно подключаем полностью заряженную батарею, т. к. размах прямо зависит от входного напряжения). Измерения проводились осцилографом FNIRSI DSO-TC4 (неплохое общее руководство по работе с осциллографом на линиях постоянного напряжения можно посмотреть здесь).

На рисунке слева наблюдаем пульсацию напряжения на входе невзведённого GHF435. Размах колебаний составляет ~10 мВ при постоянном напряжении 16,8 В, что весьма неплохо. Здесь и далее нас интересует именно абсолютный размах, а не его отношение к величине постоянной составляющей.
Теперь измерим размах колебаний на входе приёмника (напоминаю, что он запитан от выводов +4,5 В):

Здесь он чуть ниже и составляет в среднем 5 мВ. Похоже, в этом и заключается секрет качественной работы приёмника с этим ПК.
Итак, с GHF435 разобрались, теперь нужно сделать те же замеры для Матрикса.

Слева наблюдаем колебания напряжения на входе питания ПК BetaFPV Matrix 1S. Видим, что они чуть меньше, чем у GHF435, т. к. входное напряжение также меньше (4,2 В против 16,8 В).

А вот что мы видим на входе приёмника, подключенного к невзведённому Матриксу. Размах составляет 40-50 мВ, в 8-10 (!) раз больше, чем в предыдущем примере. Похоже, мы раскрыли это дело.
Причин столь существенной разницы в качестве питания +4,5/5 В указанных контроллеров две:
устройство и принцип действия понижающего и повышающего преобразователей постоянного напряжения (DC-DC), из-за чего понижающие преобразователи иногда отдают менее пульсирующее напряжение
схемотехнические особенности обоих ПК
В первом случае (GHF435) имеем дело с понижающим преобразователем (DC-DC buck converter) MPS MP9943 (микросхема с обозначением AMGN052, отмечена красным) с рабочей частотой 410 кГц.

Возле неё находится LC-фильтр (отмечен зелёным), который состоит из индуктивности 1,5 мкГн и двух конденсаторов, судя по типоразмеру, это 0603 CL10A226MQ8NRNC ёмкостью 22 мкФ или аналог. Используя формулу
несложно определить, что частота среза составляет около 19,6 кГц, что почти в 21 раз ниже рабочей частоты преобразователя. Следовательно, большая часть пульсаций оказывается значительно выше полосы пропускания фильтра и эффективно подавляется. Обратите внимание на грамотную реализацию: вход дросселя размещён предельно близко к выходу преобразователя, а конденсаторы – предельно близко к выходу дросселя.
Что касается Матрикса, то я не смог установить, какой именно импульсный преобразователь используется для +5В.
Возможно, получится у вас


Здесь, насколько я вижу, LC-фильтр отсутствует, поэтому и размах пульсации напряжения в разы выше.
Также обратите внимание, что в первом случае производитель выделил для ГНСС отдельный ЮАРТ и линию +4,5 В и у меня есть подозрение, что небольшая чёрная миросхема сразу под выходом питания – это линейный стабилизатор + ферритовые бусины, которые ещё больше сглаживают возможные скачки напряжения и шум.

Во втором случае у нас есть только общая линия +5 В:

Что ж, добавим фильтр самостоятельно. Для начала припаяем конденсатор, например GRM31CR71C475MA01L, к выводам питания приёмника:


И это сразу даёт положительный эффект: размах уменьшился до 20 мВ.
Вместо дросселя добавим ферритовую бусину BLM21BD222TN1D, что сглаживает всплески напряжения ещё сильнее:

Здесь размах пульсаций уменьшился уже до примерно 13 мВ. Конечно, это не полноценный LC-фильтр, и тем не менее сочетание конденсатора и феррита в данном случае оказалось достаточно.
Очищение питания имело ещё одно неожиданное и приятное последствие: начали устойчиво ловиться спутники Бэйдоу:

Вдвойне приятно, что принимается сигнал В1С, т. е. более современная и продвинутая версия, которая в т. ч. может работать на одном приёмнике вместе с ГЛОНАСС (см. https://habr.com/ru/articles/920540/).
Краткая справка
Главное отличие между навигационными сигналами Beidou B1C и B1I заключается в том, что B1I – это устаревший (региональный) сигнал, унаследованный от поколения BeiDou-2, тогда как B1C – это новый международный гражданский сигнал, внедренный в глобальной системе BeiDou-3 для полной совместимости с GPS и Galileo.
В B1C используется продвинутая модуляция BOC(1,1) для данных и QMBOC для пилот-канала. Она «разносит» спектральную энергию по краям полосы частот. Это кардинально улучшает фильтрацию шумов, защиту от интерференции и отражений от зданий в плотной городской застройке.
Сигнал B1C разделен на два независимых компонента:
B1C_data: передает системную информацию.
B1C_pilot: чистый код без наложенных данных. Это позволяет ГНСС-приемнику цепляться за спутник и удерживать его («сопровождать фазу несущей») в экстремальных условиях (густой лес, туннели, плохой обзор неба), когда уровень полезного сигнала падает ниже критического.
В общем, одни плюсы :)
Ну что же, пришло время итоговых испытаний:
Конечно, данное решение всё ещё уступает GHF435, но в сравнении с обычной прошивкой 2025.12.1 и приёмником без фильтра стало сильно лучше.
Выводы
даже из не самого мощного железа можно выжать максимум, прокачав периферию и прошивку
ИИ способен писать код даже для микроконтроллеров
качество питания является одним из определяющих факторов устойчивой работы чувствительных датчиков
если на плате ПК есть ЮАРТ и питание, выделенные для ГНСС, то припаивайтесь туда
если хочется навигацию, а на плате отсутствует фильтрация линии питания ГНСС, то нужен либо самодельный фильтр, либо другой ПК (для O4 можно посмотреть в сторону GOKU F405 HD 1-2S/3-4S, у него есть отдельная шина 4,5 В для приёмника ГНСС и 9 В для видео, а также барометр, при этом он всего на 1 грамм тяжелее Матрикса)
На сегодня всё, спасибо за внимание и до новых встреч!
Комментарии (6)

NutsUnderline
01.07.2026 06:43Заходим на https://app.betaflight.com
захождение туда это тема отдельной статьи, хорошо что не УК (на текущий момент)
NutsUnderline
а я все равно не очень верю что мощность процессора и особенно поток передаваемой информации так сильно влияет на число увиденных спутников, в отличии от включения asisted технологий. Если там настолько плохой обработчик кода, то он может даже терять именно инфу именно про спутники и они не отображаются, но обработка то происходит в первую очередь внутри чипа gnss, а координаты - небольшой пакет данных.
поэтому вангую что маленькая батаречка (ионистор) непосредственно на модуле gnss позволила "кешировать" нужные данные и некоторые запуски получились довольно "теплыми"
tsypanov Автор
Я проверял это предположение, принудительно выполняя холодный пуск через u-center, а также выдерживая паузу в несколько дней между включениями.
NutsUnderline
еще возможно интересный вариант проверить это дело перепрошивкой в ardupilot/inav на то же самое железо (и на том же самом месте), код то там всяко другой
faIke5
Мощность стороннего процессора не влияет от слова совсем, модуль GPS полностью самодостаточен, все эти обработки которые автор дёргает в коде лишь углублённый парсинг nmea ublox, не понятно зачем кстати. Вообще вся статья просто кричит о том что автор плохо понимает схемотехнику и принципы функционирования базовых элементов, либо это нейросеть вовсе. Снижать пульсации на выходе DC конвертера конденсатором это мягко говоря, кхм, не умно что ли
tsypanov Автор
Там есть видео, на котором показано сравнение производительности датчика до/после, вы можете проверить самостоятельно, влияет ли оптимизация парсинга.
Во-первых, конденсатор справляется с этой задачей, что показывает измерение размаха осцилографом.
Во-вторых, в статье я указал, что высокая производительность полётника от Джемку объясняется в т.ч. наличием полноценного LC-фильтра и, возможно, стабилизатора, который разместить на Матриксе/модуле затруднительно, поэтому был сделан эрзац, который опять же со своей задачей справляется.
В-третьих, целью было разобраться с конкретной проблемой, и с ней разобрались. Неканоничность решения меня мало волнует.