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

Aktuelle Wellenform Exakte direkte Referenz Umkehr Linker Flügel Rechter Flügel

Jede Hälfte auf gleiche Breite gestreckt

Abschlag-Hälfte Aufschlag-Hälfte, Richtung ausgerichtet Exakte Diagonale Linker Flügel Rechter Flügel

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:

  1. Nullen Sie jeden Gain. Fliegen Sie nur manuell; die Zelle muss stabil und vorhersagbar sein, bevor der Gyro eine Stimme verdient.
  2. 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.
  3. 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.
  4. 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.
  5. Heftigkeit P (optional) ab 5 — mischt P-Autorität ins Verweilen.
  6. Balance (I→Asymmetrie) zuletzt, niedrig. Das ist der destabilisierendste Kanal; nur 5–15. Er wirkt anhaltender Drift entgegen, nie transienten Stößen.
  7. SSFF ab 5, erst wenn Kadenz und Heftigkeit halten. Zu hoch = Überkorrektur und Halbschlag-Flattern; richtig = sichtbare Dämpfung der Nick-Oszillationsamplitude.
  8. 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:

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.