Архитектура: ядра и потоки данных
Как поток CRSF превращается во взмах крыла и хвост: границы модулей, два ядра микшера и конечный автомат, который держит птерозавра в живых при потере связи.
Сигнальный тракт
PteronautOS — это форк ExpressLRS 4.x, заменивший «сквозную передачу PWM» в конце приёмника выделенным слоем управления полётом. Путь данных намеренно короткий:
CRSF radio link
│ 2.4 GHz SX1280
▼
rx_main / CRSF decoder → ChannelData[] (raw 172…1811 µs-window values)
▼
Ornithopter::update() → _readChannels() → voice* fields
▼ kernel select: activeProfile ≥ GEARBOX_2VTAIL_1RUD ?
_computeServoMixer() OR _computeGearboxMixer()
▼
_f[] (indexed by ServoFunc tag, µs)
▼
PWM output stage → funcMap channels → wing / rudder / motor servos
Четыре структурные идеи держат машину стабильной, а код — компактным:
- Каналы нормализуются сразу. Каждое сырое значение в окне CRSF 172–1811 один раз отображается в −1…+1 (
_crsfToNorm) или в проценты, и дальше через микшеры течёт только эта величина — никаких повторяющихся магических констант. -
Выходы помечаются тегами, а не привязываются. Микшеры пишут в
_f[ServoFunc]. Каким физическим PWM-выходом управляет тег, решается позже черезfuncMapпрофиля — см. Справочник микшеров .Переназначение выводов модели никогда не трогает математику микшера. - Ядро — это семейство на этапе компиляции и выбор в рантайме. И волновой махатель, и контроллер редуктора живут в бинарнике; активный профиль выбирает ядро на каждом тике обновления.
- Фейлсейф — это не событие, а состояние. Потеря связи, стендовый режим и переопределение стиков — явные шлюзы в
update(), поэтому сервы никогда не «убегут», пока радио молчит.
Два ядра: волновое и редукторное
Ядро — это блок, превращающий нормализованные каналы в µs серво-функций. У PteronautOS две семьи, различаемые номером профиля микшера:
| Семья | Профили | Приводы | Архетип модели |
|---|---|---|---|
| Волновое (серво) ядро | 0 SERVO_2WING · 1 SERVO_2WING_1RUD · 2 SERVO_4WING | Сервы машущего крыла (2–4), гребневой руль | Орнитоптер с прямым сервоприводом — классический PteronautOS |
| Редукторное ядро | 3 GEARBOX_2VTAIL_1RUD · 4 GEARBOX_1MOT_2VTAIL · 5 GEARBOX_1MOT_2VTAIL_1RUD · 6 GEARBOX_1ELE_1RUD · 7 GEARBOX_1MOT_1ELE_1RUD | Моторный ESC, V-хвост или руль высоты + руль | Редукторный / моторизованный махатель с обычным хвостовым оперением |
Выбор происходит в update() единственной проверкой PROFILE_IS_GEARBOX (activeProfile ≥ 3). Сам профиль — величина рантайма: он стартует из флага сборки MIXER_PROFILE (устаревший ORNITHOPTER_GEARBOX=1 всё ещё отображается на профиль 5), но может переключаться на лету из WebUI через setOrnithopterProfile() — прошивка перенацеливается на новый планер без перепрошивки. Всего восемь профилей; их точные раскладки funcMap сведены в Справочнике микшеров.
Волновое ядро (сервоприводный мах)
Волновое ядро — это то, что большинство понимает под «PteronautOS»: два крыльевых серво выполняют фазонепрерывное колебание, форма которого вытачивается каждый цикл. Его анатомия:
- Шлюз взмаха с гистерезисом. Газ должен пересечь порог взмаха (сырое ≈303 по умолчанию, ≈1,08 мс PWM-эквивалент), чтобы войти в махание; выходит он только ниже порога − 50 гистерезиса, так что пограничный газ никогда не «дребезжит» крыльями.
- Частота. FlappingOscillator продвигается по реальному времени (такты µs, dt ограничен 100 мс). Целевой темп смешивает канал CH6 частоты взмаха и газ через
throttleFrequencyMixпрофиля, в пределах 0,5 Гц…flapBaseFreq/10. - Амплитуда. Процент газа от потолка скорости серво
ampMax = degPerSec / (2·f)(гдеdegPerSec = 60°/servoSpeed_ms), жёстко ограничена 55° до множителя. Поэтому амплитуда естественно падает на высокой частоте — крыло никогда не требует больше, чем может дать серво. - Свирепость. Свирепости нисходящего (мощность) и восходящего (восстановление) взмаха формируются на каждый взмах, асимметрично модулируются рулём высоты (подъём ужесточает нисходящий, толчок — восходящий) и связью газа, затем ограничиваются 0…8 до функции формы.
- Дифференциалы рыскания. Руль добавляет противоположную Л/П свирепость (
rudderFerocityRange) и противоположную Л/П амплитуду (rudderAmplitudeDifferential), при этом фаза реверса взмаха остаётся общей для крыльев — власть поворота идёт от асимметричного удержания, а не от рассинхронизации крыльев. - Геометрия и выход. Сформированная волна −1…+1 масштабируется в градусы, смешивается с управлением элеронами/рулём высоты (авторитет ±60° × масштаб), смещается триммером центра планирования/взмаха, ограничивается 0…180° и преобразуется в µs через настраиваемый конверт
servoMinUs…servoMaxUs(по умолчанию 988…2012) с тримом на функцию. - Ветвь планирования. Ниже порога взмаха осциллятор затухает, и крылья удерживают статический угол планирования — идеальное состояние для стенда и запуска.
Редукторное ядро (мотор + хвосты)
Редукторное ядро меняет чистый взмах на моторизованный привод плюс обычное хвостовое оперение. Поскольку мотор вращается непрерывно, это ядро без состояния на каждый тик — без осциллятора — и вместо этого:
- отображает газ (при вооружении) в команду ESC 1000…2000 мкс (
SF_MOTOR), холостой ход при снятии с вооружения; - микширует хвост: V-профили используют элевонную смесь (элерон+руль высоты / элерон−руль высоты), классические — отдельную поверхность руля высоты;
- позволяет Zephyrus (при включении) вводить ПИД крена и тангажа в хвостовые сервы, ограниченные ±250 мкс (
ZEPHYR_GEARBOX_CLAMP_US), чтобы сбой датчика никогда не застопорил серво; - сохраняет тот же микшер руля и конверт µs/трим/ограничение, что и волновое ядро.
Один орнитоптер может даже нести обе личности: взлёт маханием на волновом ядре, крейсер на редукторе — переключение профиля в рантайме и есть та единственная строка в update().
Состояния: связь, стенд и failsafe
update() вызывается с фиксированным периодом ~10 мс из цикла WiFi/серво и перед запуском любого микшера проверяет три флага:
| Состояние | Условие | Поведение |
|---|---|---|
| Нормальный полёт | linkUp + канал вооружения активен | Полный выход ядра следует за стиками |
| Стендовый / панельный режим | benchMode (панель WebUI, без RC-связи) |
Вводятся нейтральные стики, газ удерживается ниже порога взмаха — правки планирования + трима видны вживую |
| Переопределение стиков | stickOverride (виртуальные каналы) |
Значения каналов, вводимые прошивкой, полностью заменяют CRSF (прогоны серво, тесты) |
| Фейлсейф | связь потеряна (onLinkDown / enterFailsafe) |
Все функции центрируются на 1500 мкс, осциллятор сброшен, мотор → мин.; потеря пакетов CRSF также центрирует на уровне RX |
Поведение «центрирование при потере» намеренно продублировано в двух слоях: стек CRSF фейлсейфит данные каналов, а слой Ornithopter фейлсейфит геометрию (включая состояние осцилляции). Мёртвое радио поэтому оставляет планирующий, управляемый планер — никогда не бьющийся в конвульсиях.
Карта исходников
| Модуль | Путь | Ответственность |
|---|---|---|
| Слой Ornithopter |
src/lib/Ornithopter/
|
Выбор ядра, математика микшера, волновой осциллятор, профили, трим |
| Полётные профили и константы |
src/lib/Ornithopter/OrnithopterConfig.h
|
Перечисление MixerProfile, теги ServoFunc, funcMap, все числовые значения по умолчанию |
| Осциллятор / форма |
src/lib/Ornithopter/OrnithopterWaveform.h
|
Интегрирование фазы, формирование свирепости, логика реверса |
| Zephyrus (опционально) |
src/lib/Zephyrus/
|
MPU6050 + Mahony AHRS, двойной ПИД, коррекция гребня/хвоста |
| Каскад PWM-выхода |
src/lib/ServoOutput/devServoOutput.cpp
|
Записывает µs синхронно с тиком CRSF |
| Конфигурация WebUI |
src/lib/WIFI/devWIFI.cpp
|
Профиль рантайма + сохранение настроек (/pteronautos) |
Следующий слой вниз — какая именно форма волны получается из чисел и как каждый профиль подключает функции к PWM-выходам — живёт в Справочник микшеров , а конверт тайминга серво описан в статье Серво-ядро статье.