Zephyrus: Stabilisierung & Ondas
Das Idealbild der Brücke vom Gyro zum Flügelschlag: Ondas-Atemmodulation, schlag-synchrones Feed-Forward, phasenverriegelte Resonanz und aeroelastische PID-Verstärkung — und eine ehrliche Karte dessen, was die aktuelle Firmware wirklich verdrahtet.
Herkunft: OrniFlight und ein vereinfachtes Ondas
Ondas ist keine PteronautOS-Erfindung. Die Wellenmodulations-Familie — Atem-Pausen-Kadenzsteuerung, Schlagform-Heftigkeit, asymmetrisches Verweilen und die aeroelastische Kopplung von Flügellast an PID-Autorität — lebt und entwickelt sich im OrniFlight Projekt, dessen Wiki und Simulator die fortgeschrittenen Algorithmen tragen (inklusive der servo-efficiency table ).
PteronautOS bettet eine teilweise vereinfachte Implementierung ein: dieselbe mathematische Atemgeometrie, skaliert auf einen ESP8285 mit einem MPU6050 und bis zu neun Servos. Wo OrniFlight kontinuierliche Aeroelastik simuliert und offline ganze Hüllkurven-Familien erkundet, muss PteronautOS in 4-ms-Ticks, auf einem 80-MHz-Kern, entscheiden — mit dem ExpressLRS-Funkstapel auf demselben Silizium. Jeder Gain in diesem Artikel existiert in beiden Projekten; die Firmware-Konstanten unten sind die fliegende Teilmenge.
Signalpfad — von Zephyrus zu Ornithopter
Die Brücke vom Gyro zum Flügelschlag ist bewusst kurz — vier Dateien, keine dynamische Allokation, keine Warteschlangen:
MPU6050 raw (I2C, 1 kHz register reads)
▼
Zephyrus::update() Mahony AHRS → roll°, pitch°, yawRate °/s
▼ dual PID: roll→0°, pitch→0°, yawRate→0°/s
4 raw pitch terms exposed pitchPTerm · pitchITerm · pitchDTerm · pitchErrorRate
▼
ZephyrusFilter.h (250 Hz) copies terms onto ornithopter.gyroPitch*
▼
Ornithopter::_computeServoMixer() / _computeGearboxMixer()
▼ Ondas P → phase · PD → dwell · I → asymmetry · SSFF → next stroke
_f[] → funcMap → wing / crest-rudder / tail servos (µs)
Die vier Roh-Terme werden in Zephyrus::update() unmittelbar nach der Nick-PID-Korrektur geschrieben:
pitchPTerm = ZEPHYR_PID_PITCH_KP * pitchErr; // raw proportional
pitchITerm = _pidPitch.integrator; // accumulated error
pitchDTerm = ZEPHYR_PID_PITCH_KD * _pidPitch.lastDerivative; // low-pass filtered D
pitchErrorRate = _pidPitch.lastDerivative; // °/s, for SSFF
Alle vier sind durch eine NaN-Falle geschützt (ist irgendein Eingang NaN, werden alle vier genullt), damit ein korrupter I²C-Frame nie die Mixer-Arithmetik erreicht. Sieben Felder von Ornithopter empfangen die Brückendaten:
| Feld | Zephyrus-Quelle | Verbraucht von |
|---|---|---|
gyroPitchPTerm
|
pitchPTerm
|
Ondas P → Phasenvorlauf (Kadenzkanal) |
gyroPitchITerm
|
pitchITerm
|
Ondas I → Schlagasymmetrie (Balance-Kanal) |
gyroPitchDTerm
|
pitchDTerm
|
Ondas D → Verweil-Verhältnis (Heftigkeitskanal) |
gyroPitchErrorRate
|
pitchErrorRate
|
SSFF-Halbschlag-Akkumulation |
gyroRudderCorrection
|
rudderCorrection
|
Kamm-Ruder-µs-Offset (Servo- & Getriebe-Kernel) |
gyroAileronCorrection
|
rollCorrection
× gearbox gain
|
Getriebe: Roll-PID → V-Leitwerk-Querruderflächen |
gyroElevatorCorrection
|
pitchCorrection
× gain ×
aeroGainScale
|
Getriebe: Nick-PID → Elevon-/Höhenruderflächen |
Ondas-Modulation — Drei-Kanal-Atem
Ondas (portugiesisch „Wellen“) ist das dreikanalige Atemsystem, das die rohen P-, I- und D-Terme des Nick-PID auf drei orthogonale Dimensionen der Schlagwellenform abbildet. Es ist eine propriozeptive Schleife: Das Nickgefühl des Gyros formt direkt die Flügelbewegung, und die Flügelbewegung ändert das Nick, das der Gyro fühlt.
Kadenz — P → Phasenvorlauf
Ein positiver Nickfehler (Nase hoch) treibt die Oszillatorphase voran und verkürzt den aktuellen Halbschlag; ein negativer verzögert sie. Das Idealgesetz skaliert die Oszillatorfeder:
_osc.kGainMod = 1.0f + gyroPitchPTerm * aeroGainScale * cadenceGain * 0.00005f;
// clamped to [0.5, 2.0] — phase can at most double or halve
Das advance() des Oszillators verbraucht es als error = kGain · kGainMod · cadenceTarget − kDamp · cadence. Tuningbereich 0–100, Standard 20.
Heftigkeit — PD-Mischung ins Verweilen
Die P- und D-Terme werden zu einem einzigen Heftigkeitssignal gemischt, das das Verweilen der Wellenform schärft oder weicht:
ferocitySignal = (gyroPitchPTerm * ferocityPGain * 0.00015f
+ gyroPitchDTerm * ferocityDGain * 0.0003f) * aeroGainScale;
// clamped to [-0.5, 0.5], added to both stroke and return ferocity
Mit ferocityPGain = 0 (Standard) ist das Verhalten rein D — schnelle Nickänderungen erzeugen schärfere Schläge, während der stetige Flug sinusförmig bleibt.
Balance — I in Asymmetrie
Der Integralterm verschiebt das Schlagzentrum: eine anhaltende Nase-hoch-Neigung stärkt den Abschlag, eine anhaltende Nase-tief-Neigung den Aufschlag.
float iBias = gyroPitchITerm * aeroGainScale * balanceGain * 0.0001f;
// clamped to [-3.0, 3.0]; added to strokeFer, subtracted from returnFer
Balance ist bei hohem Wert der destabilisierendste Kanal — der ideale Tuning-Guide hält ihn niedrig (5–15) und fügt ihn zuletzt hinzu. Die Heftigkeits-Anwendung, die Pilotkommando, Ondas- und SSFF-Bias kombiniert, hält die Autorität des Piloten über das volle [1.0, 8.0]-Fenster:
strokeFer = pilot(stroke) + ferocitySignal + iBias + _ssffFerocityUpBias
returnFer = pilot(return) + ferocitySignal − iBias + _ssffFerocityDownBias
Wellenformung & PD-Mischung
Die Wellenform selbst folgt der OrniFlight-Schlagfamilie: Jede Hälfte nutzt ihre eigene Heftigkeit, eine geteilte Umkehrschwelle hält linke/rechte Flügel in Phase, und ein Shape-Mix pro Profil verwandelt kontinuierlich einen flachen Dwell/Plateau-Schlag in einen gerundeten Pyramidenpfad. Der interaktive Explorer unten rendert die befohlene Geometrie für jedes Ab-/Auf-Heftigkeitspaar — dieselbe Kurve, die der Mixer jeden Tick anfordert.
Heftigkeit steuert sowohl den Charakter als auch das Timing eines Halbschlags. Intern wird die 0–100-Einstellung der WebUI auf 0–8 abgebildet. Höhere Abschlag-Heftigkeit verkürzt den Abschlagsanteil des Zyklus; höhere Rücklauf-Heftigkeit verkürzt den Rücklaufanteil. Die verbleibende Zeit wird auf die gegenüberliegende Hälfte übertragen — es gibt keine Pause bei null.
Die profilbezogene Heftigkeitsform-Steuerung wählt den Hoch-Heftigkeits-Charakter. Bei 0 % bevorzugt sie eine endliche Plateau-/Rechteckform. Bei 100 % bevorzugt sie einen abgerundeten pyramidalen Verlauf. Jede Hälfte nutzt weiterhin ihre eigene Heftigkeit, sodass eine Hälfte mit niedriger Heftigkeit gestreckt und sinusförmig bleibt, während eine Hälfte mit hoher Heftigkeit sich einer direkten diagonalen Traverse nähert.
Vorkrümmung und Rückkrümmung (±100) verschieben das Zentrum jedes Halbschlags entlang seiner eigenen Start–Ende-Achse — dieselbe halbschlagweise Spiegelung wie bei der Heftigkeit. Positiv krümmt den Schlag nach vorn: Der Flügel erreicht die Spitzengeschwindigkeit früher und verlängert das vordere Plateau, was den Schub verstärkt. Negativ krümmt ihn nach hinten: Der Schlag verbraucht seine Bewegung spät und verlängert das hintere Plateau, was den Schub mindert. Die Verzerrung ist monoton und erhält die Endpunkte, also exakt eine asymmetrische Quadrat↔Dreieck-Mischung — niemals ein Positionssprung.
Die Throttle-Schubform-Kopplung vereint Schlag-Verweildauer und Vor-Rück-Zentrum in einem Regler. Vollgas klammert den Schlag UND krümmt beide Halbschläge nach vorn — die Spitzengeschwindigkeit kommt im Abschlag wie im Aufschlag früher; Leerlauf lässt beide neutral. Die eine Schub-Aggressionsgröße treibt Verweildauer und Zentrum gemeinsam, sodass beide Flügel und beide Halbschläge im Gleichklang wandern — Schub-Autorität, nicht Roll oder Pitch.
Die Aileron→Krümmungs-Kopplung krümmt den einen Flügel nach vorn, während sie den anderen nach hinten krümmt — Roll-Drehmoment auf der Krümmungs-Achse, der Spiegelzwilling der Throttle-Krümmung. Stick links addiert Krümmung am linken Flügel und entfernt sie rechts; Stick rechts umgekehrt. Nutzbar neben oder statt des Ruder-Amplituden-Differentials.
Slew ist ein transienter Boost oder eine Bremse auf die Rate eines Sticks. Gasgeben front-loadet kurz BEIDE Halbschläge, Gaswegnehmen late-loadet kurz BEIDE; die tiefpassgefilterte Rate (τ = 0,10 s) injiziert den Kick, der dann abklingt, sobald der Stick ruht. Zwei Kopplungen: Throttle-Rate-Slew (symmetrisch, Schub) und Aileron-Rate-Slew (differentiell, Roll).
Der Zephyrus-Slew-Gain überträgt dasselbe transiente Prinzip aufs Ruder: Eine schnelle Lageänderung verstärkt kurz die Ruderkorrektur (µs), tiefpassgefiltert (τ = 0,12 s) und hart auf ±80 µs begrenzt. Er schärft die Antwort auf schnelle Lageänderungen ohne statischen Gain zu erhöhen.
Steuerachsen-Matrix
Der Ruder-Stick hat im Flügelmixer keine Gier-Autorität: Seine Differentialknöpfe (Amplitude und Heftigkeit) erzeugen dieselbe Links/Rechts-Asymmetrie wie die Aileron-Krümmung — und diese Asymmetrie ist Roll. Wahres Gieren existiert nur im autonomen Kopfruder (Zephyrus, rein gyroskopisch).
| Steuereingabe | Wellenparameter | Flugwirkung |
|---|---|---|
| Aileron-Stick | Krümmung (skew) — differentiell, links/rechts |
Roll |
| Ruder-Stick | Amplitude + Heftigkeit — differentiell, links/rechts |
Roll |
| Gieren — Zephyrus-Gyro | Kopfruder | Gieren (autonom) |
Fluglenkung & Slew
Ganzer Zyklus
Jede Hälfte auf gleiche Breite gestreckt
Extreme Kombinationen lesen
Eine 7 / 1-Kombination erzeugt klar einen kurzen, spitzen Abschlag und einen langen sinusförmigen Rücklauf. Exakte und nahezu maximale Werte erfordern Sorgfalt: Das aktuelle Dauergesetz nutzt den unter Heftigkeit 8 verbleibenden Betrag. Folglich weist 8 / 0 dem Abschlag nur etwa 0,125 % des Zyklus zu, während selbst 8 / 6 etwa 0,5 % zuweist. Die Position bleibt kontinuierlich, aber die angeforderte Geschwindigkeit und Beschleunigung können physikalisch unerreichbar werden.
Anker-Dämpfung (k₂)
Die Dämpfung des Oszillators ist als stimmbarer Anker exponiert. Das Grundgesetz ist eine gedämpfte Feder zum Kadenzziel:
kDamp = 10.0f + anchorGain; // anchorGain 0 → k₂=10 (tight)
error = kGain * kGainMod * cadenceTarget
- kDamp * cadence;
Bei anchorGain = 0 (Standard) ist die Dämpfung straff — schnelle Konvergenz zur befohlenen Kadenz. Höhere Werte heben k₂ bei Gain 100 Richtung 110, verriegeln den Rhythmus härter und weisen Störungen zurück. Tuningbereich 0–100.
Resonanz — phasenverriegelte Pumpe
Resonanz ist ein phasengekoppelter Lock-in-Verstärker: Er multipliziert die Nickfehlerrate mit sin(Oszillatorphase) und akkumuliert — er verstärkt den Schlag genau dann, wenn eine Korrektur am wirksamsten ist, und dämpft ihn, wenn er gegen den Flügel kämpfen würde.
_resonanceAccum += gyroPitchErrorRate * sinf(_osc.phase)
* resonanceGain * 0.01f * dt;
_resonanceAccum *= expf(-dt / 0.15f); // leaky integrator, τ = 0.15 s
clamp(_resonanceAccum, -2.0f, 2.0f);
Der akkumulierte Wert wird für den nächsten Frame in Schlag- und Rückkehr-Heftigkeit eingespeist. Bei resonanceGain = 0 (Standard) ist der ganze Pfad deaktiviert. Der leckende Zerfall verhindert unkontrollierte Akkumulation bei anhaltender Störung.
SSFF — schlag-synchrones Feed-Forward
Schlagsynchrones Feed-forward ist philosophisch anders als jeder PID-Term: Es korrigiert den nächsten Halbschlag aus dem, was der Gyro während des aktuellen maß. Bei jedem Nulldurchgang des Flattersinus (Flügelumkehr) wird die mittlere Nickfehlerrate des beendeten Halbschlags zum Bias auf die Heftigkeit der gegenüberliegenden Hälfte.
// zero-cross detected: finished one half-stroke, starting the next
meanError = _ssffAccumError / count;
bias = meanError * ssffGain * 0.00001f; // clamped ±2.0
// upstroke just ended → bias feeds the downstroke (power stroke)
// downstroke just ended → bias feeds the upstroke (recovery)
Auf-Bias wird auf den Abschlag angewendet (korrigiert Fehler, die sich im vorherigen Aufschlag angesammelt haben); Ab-Bias auf den Aufschlag. Fällt ssffGain auf null, werden die Biases explizit gelöscht, damit eine WebUI-Deaktivierung nie veraltete Werte in der Wellenform hinterlässt. Der Akkumulator ist 32-bit und pro Halbschlag zurückgesetzt — bei 250 Hz bräuchte er ~24 Tage kontinuierliches Flattern zum Überlauf, was praktisch unerheblich ist.
Aeroelastische PID-Verstärkungsmodulation
Die gesamte Ondas- und SSFF-Modulation wird durch einen Koeffizienten skaliert, der weiß, ob der Ornithopter flattert oder gleitet — aeroelastische Gain-Modulation. Im Servo-(Wellenform-)Kernel ist der Selektor das Flatter-Gate mit seiner 50-µs-Hysterese:
aeroGainScale = isFlapping ? (aeroFlapCoeff * 0.01f)
: (aeroGlideCoeff * 0.01f);
Im Getriebe-Kernel ist der Selektor der Motorzustand (armed && throttleNorm > 0.1f). Standardwerte:
| Koeffizient | Standard | Bereich | Wenn aktiv |
|---|---|---|---|
aeroGlideCoeff
|
40 | 0–100 | Flügel still — Gleiten, oder Getriebemotor aus |
aeroFlapCoeff
|
40 | 0–100 | Flügel flattern — oder Getriebemotor läuft |
Die Skalierung auf 0–1 macht den 0–100-Slider zur Autorität des Gyros über den Flügel: Bei 100 besitzt der Gyro die Modulation vollständig, bei 0 ist er aus. Autorität je Kernel:
| Autorität | Servo-(Wellenform-)Kernel | Getriebe-Kernel |
|---|---|---|
| Kamm-Ruder | Roll + Gieren kombiniert | nur Gieren (Roll/Nick gehen an die Beine) |
| Flügel-Wellenform | Volle Ondas-/SSFF-Atemmodulation | — |
| Bein-Querruder (V-Leitwerk) | — | Roll-PID → Querruder-Seite der Elevon-Mischung |
| Bein-Elevons / Höhenruder | — | Nick-PID × aeroGainScale → Höhenruder-Seite |
Getriebe-PID-Korrekturen werden vor dem Mixen auf ±250 µs (ZEPHYR_GEARBOX_CLAMP_US) begrenzt; der Kamm-Ruder-Pfad auf ±200 µs. Ein Frame Verzögerung ist inhärent: aeroGainScale wird im Getriebe-Mixer berechnet, aber vom nächsten zephyrusUpdate() verbraucht — 4 ms bei 250 Hz, vernachlässigbar für mechanische Servos.
WebUI-Panel & Konfigurations-Endpunkte
Das Ornithopter-Panel zeigt zwei Stabilisierungsgruppen, wenn der Build Zephyrus enthält. Ondas- & SSFF-Slider (0–100): Kadenz P→Phase, Heftigkeit D→Verweilen, Heftigkeit P→Verweilen, Balance I→Asymmetrie, Anker k₂, Resonanz, SSFF-Gain. Aeroelastik-Gruppe: Gleit-Koeffizient und Flatter-Koeffizient. Alle Werte persistieren über POST /pteronautos/config und strömen über GET /pteronautos/state zurück:
{
"ornithopter": {
"cadence_gain": 20.0, "ferocity_d_gain": 20.0, "balance_gain": 10.0,
"ferocity_p_gain": 0.0, "anchor_gain": 0.0, "resonance_gain": 0.0,
"ssff_gain": 0.0,
"aero_glide_coeff": 40.0, "aero_flap_coeff": 40.0,
"aero_gain_scale": 0.4
}
}
aero_gain_scale wird berechnet und ist schreibgeschützt. POST akzeptiert Teil-Updates und klemmt jedes Feld serverseitig auf [0, 100].
Abstimmungs-Leitfaden
Die ideale Flugtest-Sequenz — jeder Schritt ist ein Flug, kein Slider-Ziehen:
- Nullen Sie jeden Gain. Fliegen Sie nur manuell; die Zelle muss stabil und vorhersagbar sein, bevor der Gyro eine Stimme verdient.
- Aktivieren Sie Zephyrus' Kamm-Ruder (Roll+Gier-PID). Prüfen Sie, dass das Ruder einem sanften Flügelwippen widersteht, bevor irgendetwas die Wellenform berührt.
- Kadenz (P→Phase) ab 10 aufwärts. Zu hoch = ruckartige Flügel, Phasenjagd; zu niedrig = nichts. Richtig = Flügel fühlen sich mit dem Nick „verbunden“ an.
- Heftigkeit D (D→Verweilen) ab 10. Zu hoch = die Wellenform oszilliert zwischen scharf und weich; richtig = schnelle Nickänderungen schärfen den Schlag, stetiger Flug bleibt sinusförmig.
- Heftigkeit P (optional) ab 5 — mischt P-Autorität ins Verweilen.
- Balance (I→Asymmetrie) zuletzt, niedrig. Das ist der destabilisierendste Kanal; nur 5–15. Er wirkt anhaltender Drift entgegen, nie transienten Stößen.
- SSFF ab 5, erst wenn Kadenz und Heftigkeit halten. Zu hoch = Überkorrektur und Halbschlag-Flattern; richtig = sichtbare Dämpfung der Nick-Oszillationsamplitude.
- Aeroelastik-Koeffizienten zuletzt. Stimmen Sie zuerst das Gleiten (Flügel still), dann das Flattern. Halten Sie den Gyro als autoritativen Kopiloten, nicht als nervösen Piloten.
Implementierungsstand & offene Inkonsistenzen
Ehrliche Karte, aktuelle Firmware (Ornithopter.cpp @ 72c63020). Die idealen Gesetze oben sind das Design; der Code enthält die volle Parameterfläche, die Konstanten, die WebUI-Slider und den Bridge-Shim — aber ein Flugtest-Gerüstblock trennt derzeit den Großteil des Gyros vom Servo-Kernel:
// Ornithopter.cpp:269-275 (inside the flapping branch)
#ifdef ZEPHYRUS_ENABLED
// STEP 7: ALL gyro values hard-zeroed (NaN guard test)
_osc.kGainMod = 1.0f; // cadence channel OFF
gyroRudderCorrection = 0.0f; // crest rudder OFF while flapping
gyroAileronCorrection = 0.0f;
gyroElevatorCorrection= 0.0f;
#endif
Mit der i18n-Arbeit als „STEP 7“-NaN-Guard-Test committet und nie zurückgerollt, bedeutet dieser Block, dass das heutige Servo-Kernel-Verhalten ist:
- Kadenz, Heftigkeit-PD, Balance, Resonanz, SSFF — alle als Parameter und in der Brücke vorhanden, aber ihre Verbrauchsstellen (
kGainMod,ferocitySignal,iBias,resonanceBias) sind auf null gepinnt oder werden jeden Frame zurückgesetzt (Ornithopter.cpp:282–284, 311–313, 354). - Die Kamm-Ruder-Stabilisierung wird innerhalb des Flatter-Zweigs genullt, sodass die Ruderkorrektur derzeit nur im Gleit-Zweig wirkt — das Gegenteil dessen, was der Flug braucht (die meisten Störungen treffen mitten im Schlag ein).
- Das Getriebe-Kernel ist die lebende Ausnahme. Es liest
gyroAileronCorrection/gyroElevatorCorrection/gyroRudderCorrectionund wendet sie mit Klemmen an (Zeilen 436–451) — der STEP-7-Nullungsblock lebt nur im Servo-Kernel.
Das ist keine Kritik an der Mathematik — es ist ein bekannter, benannter Zustand: Die Stabilisierungsmodule sind einkompiliert, parametrisiert, gebrückt und warten darauf, dass das STEP-7-Gerüst entfernt und durch eine echte NaN-Politik ersetzt wird (die Validatio-Guards geben den MPU-Daten bereits einen sauberen Pfad). Bis dahin behandeln Sie den Ondas/SSFF/Resonanz-Tuning-Guide oben als flugbereite Spezifikation, geprüft gegen Zephyrus.cpp:729–737 und OrnithopterWaveform.h, aber noch nicht in der Luft.
Der maßgebliche Design-Record bleibt STABILIZATION.md ; sein Schwesterprojekt OrniFlight hält die kontinuierlich fortgeschrittene ONDAS-Forschung.