Architektur: Kernel & Datenfluss
Wie ein CRSF-Datenstrom zu Flügelschlag und Leitwerk wird: Modulgrenzen, die zwei Misch-Kernel und die Zustandsmaschine, die den Pterosaurier am Leben hält, wenn der Link stirbt.
Signalpfad
PteronautOS ist ein ExpressLRS-4.x-Fork, der den „PWM-Durchreichen“-Schwanz des Empfängers durch eine eigene Flugsteuer-Schicht ersetzt hat. Der Datenweg ist bewusst kurz:
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
Vier strukturelle Ideen halten das Biest stabil und den Code klein:
- Kanäle werden früh normalisiert. Jeder Rohwert im CRSF-Fenster 172–1811 wird einmal auf −1…+1 (
_crsfToNorm) oder auf einen Prozentwert abgebildet, und nur diese Einheit fließt durch die Mixer — keine wiederholten magischen Konstanten. -
Ausgänge werden getaggt, nicht verdrahtet. Die Mixer schreiben in
_f[ServoFunc]. Welchen physischen PWM-Ausgang ein Tag treibt, entscheidet später dasfuncMapdes Profils — siehe Mixer-Referenz .Das Neu-Verdrahten eines Modells berührt nie die Mixer-Mathematik. - Der Kernel ist eine Compilezeit-Familie, eine Laufzeit-Entscheidung. Sowohl der Wellenform-Flatterer als auch der Getriebe-Controller leben im Binärbild; das aktive Profil wählt den Kernel jeden Update-Tick.
- Failsafe ist kein Ereignis, sondern ein Zustand. Link-Verlust, Bank-Modus und Stick-Override sind explizite Gates in
update(), damit Servos nie durchgehen, solange das Radio schweigt.
Zwei Kernel: Waveform & Getriebe
Ein Kernel ist der Block, der normalisierte Kanäle in Servo-Funktions-µs verwandelt. PteronautOS hat zwei Familien, unterschieden anhand der Mixer-Profilnummer:
| Familie | Profile | Aktoren | Modell-Archetyp |
|---|---|---|---|
| Wellenform-(Servo-)Kernel | 0 SERVO_2WING · 1 SERVO_2WING_1RUD · 2 SERVO_4WING | Schlagflügel-Servos (2–4), Kamm-Ruder | Ornithopter mit Direktantrieb-Servos — das klassische PteronautOS |
| Getriebe-Kernel | 3 GEARBOX_2VTAIL_1RUD · 4 GEARBOX_1MOT_2VTAIL · 5 GEARBOX_1MOT_2VTAIL_1RUD · 6 GEARBOX_1ELE_1RUD · 7 GEARBOX_1MOT_1ELE_1RUD | Motor-ESC, V-Leitwerk oder Höhenruder + Ruder | Getriebe-/motorisierter Flatterer mit konventionellem Leitwerk |
Die Auswahl geschieht in update() mit dem einzigen Test PROFILE_IS_GEARBOX (activeProfile ≥ 3). Das Profil selbst ist ein Laufzeitwert: Es startet aus dem Build-Flag MIXER_PROFILE (das alte ORNITHOPTER_GEARBOX=1 mappt weiter auf Profil 5), lässt sich aber live über die WebUI per setOrnithopterProfile() umschalten — die Firmware zielt ohne Reflash auf eine neue Zelle um. Insgesamt gibt es acht Profile; ihre exakten funcMap-Layouts stehen in der Mixer-Referenz.
Waveform-Kernel (Servo-Flatterflügel)
Das Wellenform-Kernel ist, was die meisten unter „PteronautOS“ verstehen: zwei Flügel-Servos führen eine phasenkontinuierliche Oszillation aus, deren Form jeden Zyklus gemeißelt wird. Seine Anatomie:
- Flatter-Gate mit Hysterese. Der Schub muss die Flatter-Schwelle (Standard roh ≈303, ≈1,08 ms PWM-Äquivalent) überschreiten, um ins Flattern zu kommen; er verlässt sie erst unterhalb Schwelle − 50 Hysterese, damit grenzwertiger Schub die Flügel nie flattern lässt.
- Frequenz. Ein FlappingOscillator schreitet mit realer Ablaufzeit fort (µs-Takt, dt auf 100 ms begrenzt). Das Kadenzziel mischt den CH6-Flatterfrequenzkanal und Schub über das
throttleFrequencyMixdes Profils, innerhalb 0,5 Hz…flapBaseFreq/10. - Amplitude. Schub-Prozentsatz des Servo-Geschwindigkeitsdecke
ampMax = degPerSec / (2·f)(mitdegPerSec = 60°/servoSpeed_ms), hart auf 55° vor dem Multiplikator begrenzt. Die Amplitude kollabiert daher bei hoher Frequenz natürlich — der Flügel verlangt nie mehr, als der Servo liefern kann. - Heftigkeit. Die Heftigkeiten von Abschlag (Kraft) und Aufschlag (Erholung) werden pro Schlag geformt, asymmetrisch durch Höhenruder moduliert (Ziehen härtet den Abschlag, Drücken den Aufschlag) und durch Schub-Kopplung, dann vor der Formfunktion auf 0…8 begrenzt.
- Gier-Differentiale. Das Ruder addiert L/R-entgegengesetzte Heftigkeit (
rudderFerocityRange) und L/R-entgegengesetzte Amplitude (rudderAmplitudeDifferential), während die Schlagumkehrphase zwischen den Flügeln geteilt bleibt — die Kurvenautorität kommt vom asymmetrischen Verweilen, nicht von desynchronisierten Flügeln. - Geometrie & Ausgang. Die geformte −1…+1-Welle wird in Grad skaliert, gegen Quer-/Höhenruder-Steuerung (±60° Autorität × Skala) gemischt, um den Gleit-/Flatterzentrum-Trim versetzt, auf 0…180° begrenzt und dann über die konfigurierbare Hülle
servoMinUs…servoMaxUs(Standard 988…2012) mit Funktions-Trim in µs gewandelt. - Gleit-Zweig. Unterhalb der Flatter-Schwelle klingt der Oszillator ab, und die Flügel halten einen statischen Gleitwinkel — ein idealer Bank-und-Start-Zustand.
Getriebe-Kernel (Motor + Leitwerke)
Das Getriebe-Kernel tauscht reinen Flügelschlag gegen einen motorisierten Antrieb plus konventionelles Leitwerk. Da ein Motor kontinuierlich dreht, ist dieses Kernel pro Tick zustandslos — kein Oszillator — und stattdessen:
- mappt Schub (scharf) auf einen 1000…2000 µs-ESC-Befehl (
SF_MOTOR), Leerlauf bei entschärft; - mischt das Leitwerk: V-Profile nutzen eine Elevon-Mischung (Quer+Höhe / Quer−Höhe), klassische Profile eine separate Höhenruderfläche;
- lässt Zephyrus (wenn aktiviert) Roll- und Nick-PID-Korrekturen in die Leitwerk-Servos einspeisen, begrenzt auf ±250 µs (
ZEPHYR_GEARBOX_CLAMP_US), damit ein Sensor-Glitch nie einen Servo blockiert; - behält denselben Ruder-Mixer und dieselbe µs/Trim/Clamp-Hülle wie das Wellenform-Kernel.
Ein Ornithopter-Rig kann sogar beide Persönlichkeiten tragen: Flatterstart mit dem Wellenform-Kernel, Reiseflug mit dem Getriebe — der Laufzeit-Profilwechsel ist jene eine Zeile in update().
Zustände: Link, Bench & Failsafe
update() wird mit festem ~10 ms-Takt aus der WiFi/Servo-Schleife aufgerufen und prüft vor jedem Mixer drei Flags:
| Zustand | Bedingung | Verhalten |
|---|---|---|
| Normalflug | linkUp + Scharf-Kanal aktiv | Voller Kernel-Ausgang folgt den Sticks |
| Bank-/Panel-Modus | benchMode (WebUI-Panel, keine RC-Verbindung) |
Neutrale Sticks werden eingespeist, Schub unter der Flatter-Schwelle geparkt — Gleit- und Trim-Änderungen sind live sichtbar |
| Stick-Override | stickOverride (virtuelle Kanäle) |
Firmware-eingespeiste Kanalwerte ersetzen CRSF vollständig (Servo-Sweeps, Tests) |
| Failsafe | Link verloren (onLinkDown / enterFailsafe) |
Alle Funktionen zentrieren auf 1500 µs, Oszillator zurückgesetzt, Motor → min; CRSF-Paketverlust zentriert auch auf RX-Ebene |
Das Zentrieren-bei-Verlust-Verhalten ist absichtlich in zwei Schichten dupliziert: Der CRSF-Stack sichert die Kanaldaten ab, und die Ornithopter-Schicht sichert die Geometrie ab (inklusive Oszillationszustand). Ein totes Radio hinterlässt daher eine gleitende, steuerbare Zelle — niemals eine zappelnde.
Quellcode-Karte
| Modul | Pfad | Zuständigkeit |
|---|---|---|
| Ornithopter-Schicht |
src/lib/Ornithopter/
|
Kernel-Auswahl, Mixer-Mathematik, Wellenform-Oszillator, Profile, Trim |
| Flugprofile & Konstanten |
src/lib/Ornithopter/OrnithopterConfig.h
|
MixerProfile-Enum, ServoFunc-Tags, funcMap, alle numerischen Standards |
| Oszillator / Form |
src/lib/Ornithopter/OrnithopterWaveform.h
|
Phasenintegration, Heftigkeits-Formung, Umkehrlogik |
| Zephyrus (optional) |
src/lib/Zephyrus/
|
MPU6050 + Mahony-AHRS, Dual-PID, Kamm-/Leitwerk-Korrektur |
| PWM-Ausgangsstufe |
src/lib/ServoOutput/devServoOutput.cpp
|
Schreibt µs synchron zum CRSF-Tick |
| WebUI-Konfiguration |
src/lib/WIFI/devWIFI.cpp
|
Laufzeit-Profil + Tuning-Persistenz (/pteronautos) |
Die nächsttiefere Schicht — exakt welche Wellenform die Zahlen erzeugen und wie jedes Profil Funktionen auf PWM-Ausgänge verdrahtet — lebt in der Mixer-Referenz , und die Servo-Timing-Hülle ist im Artikel Servo-Kernel dokumentiert.