Projekte

DDK9 RTTY – Finale Dimensionierung & Begründung

Ist-Dimensionierung aller Komponenten — Werte, Herleitung, Begründung aus der Verifikation mit ddk9_2.wav.

Diese Dokumentation beschreibt die vollständige Implementierung der DDK9 Demodulation.


1. Signalfluss

12 kHz 1200 Hz (complex) WAV ──► Throttle ──► Freq-Xlating-FIR ──► AGC² ──► Quadratur-Demod (÷10, ±400 Hz, │ │ 1200 Hz (float) 289 Taps) ▼ ▼ ▲ Spektrum-Sink Post-Detection-TP ┆ freq (50 Hz, 117 Taps) ┆ (Message) │ ┆ ▼ ┆ Auto-Polarity (y = k·x) ┆ │ │ │ ┆ │ ▼ ▼ ┆ │ SNR-Gate AFC ┄┄┄► freq ┆ │ ┆ signal (Message) ┆ │ ├┄┄┄┄► Auto-Polarity.gate └┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┘ ├┄┄┄┄► AFC.gate │ └┄┄┄┄► Decoder.gate ▼ Matched Filter (Boxcar 24) │ ▼ ATC (adaptiv) ──► Augen-Sink │ ▼ Symbol Sync (M&M, sps 24) ──► Slicer │ ▼ Baudot-Decoder (Flywheel) ─── Stream ┄┄┄ Message-Port
Architektur-Grundsatz: Alle Regelkreis-Kopplungen laufen über GNU-Radio-Message-Ports (AFC→Mischer, Gate→3 Verbraucher) statt über Python-Blockreferenzen.

2. Systemparameter

Parameter Wert Begründung
samp_rate 12 000 Hz durch die WAV-Aufnahme vorgegeben
baud 50 Bd DDK9/DWD-Sendenorm; $T_b$ = 20 ms
shift 225 Hz wirkt als halber Hub / Normierungsgröße — siehe Kasten
decim 10 1200 Hz = kleinste Rate mit ganzzahligem sps = 1200/50 = 24 und genug Reserve für das $\pm225$-Hz-Basisband
mf_len (= sps) 24 exakt eine Bitdauer bei 1200 Hz — Grundlage für MF-Länge, Symbol-Sync und ATC
center_freq 1000 Hz NF-Lage des Signals; AFC führt per Message nach
Messbefund: Die Amplitudenverteilung am Diskriminatorausgang zeigt die Cluster bei ±1,0 — nicht ±0,0. Der reale Hub der Aufnahme ist also 450 Hz Mark-Space-Abstand (Töne bei $\pm225$ Hz um die Mitte); der Parameter shift = 225 fungiert in der Kette als halber Hub bzw. als Normierungsgröße des Demodulators ($y = f_{inst}/225$ Hz). Folgen:
  • Unkritisch für AFC (Differenzrechnung: midpoint·225 = $\delta$f gilt unabhängig von der Cluster-Lage), Slicer und Decoder (vorzeichenbasiert), ATC (trackt die realen Pegel).
  • Kritisch für jede feste Amplitudenannahme — deshalb ist das SNR-Gate pegel-adaptiv dimensioniert (Abschnitt 3.7).
  • Modulationsindex real: $h = 450/50 = 9$ (nicht 4,5) — noch gutmütigere Breitband-FSK.
  • Carson-Bandbreite real: $B \approx 450 + 2 \cdot 50 = 550 \text{ Hz}$ ($\pm275$ Hz) — dadurch ist die $\pm400$-Hz-Kanalfilterung enger begründet, als der nominale halbe Hub (225 Hz) vermuten lässt (Abschnitt 3.2).

3. Ist-Dimensionierung je Komponente

3.1 WAV-Quelle + Throttle

Wert Warum so
ddk9_2.wav, repeat, Throttle 12 kSa/s Throttle nur als Echtzeit-Taktgeber für Datei-Wiedergabe; bei SDR-Quelle ersatzlos entfernen (zwei Taktgeber $\Rightarrow$ Puffer-Konflikte).

3.2 Freq-Xlating-FIR (fcc) — Mischer + Kanalfilter + Dezimator

Parameter Ist-Wert Warum so dimensioniert
Taps low_pass(1, 12000, 400, 100) $\rightarrow$ 289 Taps Durchlass $\pm400$ Hz: Nutzsignal belegt real $\pm275$ Hz (Töne $\pm225$ + Datenseitenbänder $\pm50$). Die Reserve von $\pm125$ Hz ist der AFC-Fangbereich — das Signal darf um bis zu ~125 Hz verstimmt sein und bleibt im Filter sichtbar. Übergang 100 Hz hält die Tap-Zahl klein (289) bei genug Nachbarkanaldämpfung (53 dB ab 500 Hz).
decim 10 Dezimation an der frühestmöglichen Stelle: Das Filter ist ohnehin da, polyphas kostet die Ratenreduktion nichts extra. Alias-Schutz passt: neue Nyquist 600 Hz, Sperrbereich ab 500 Hz. Alle Folgeblöcke rechnen mit 1200 statt 12 000 Hz $\rightarrow$ Gesamtrechenlast $\div$ ~10.
Message-Port freq von AFC gespeist Nativer Port des Blocks: Frequenznachführung ohne Python-Blockreferenz; GRC generiert msg_connect selbst.

3.3 AGC² (complex)

Parameter Ist-Wert Warum so
attack / decay 0,1 / 0,01 Bei 1200 Hz Taktrate: $\tau_{att} \approx 10 \text{ Sa} \approx \mathbf{8,3\text{ ms}}$, $\tau_{dec} \approx 100 \text{ Sa} \approx \mathbf{83\text{ ms}}$. Die Decay-Zeit liegt über 4 Bitdauern, die AGC „pumpt" nicht innerhalb eines Bits. Für den amplitudenunabhängigen Diskriminator ohnehin nur numerische Konditionierung.

3.4 Quadratur-Demodulator

$$\text{gain} = \frac{f_s / \text{decim}}{2\pi \cdot \text{shift}} = \frac{1200}{2\pi \cdot 225} \approx 0{,}849$$

Warum so
Der Gain skaliert mit der Abtastrate — bei 1200 Hz muss er auf 0,849 eingestellt sein, sonst wäre der Ausgang 10× zu groß und alle nachgelagerten Schwellen (AFC $\pm0,05$, Gate) falsch kalibriert. Die Normierung $y = f_{inst}/225 \text{ Hz}$ ist die gemeinsame Bezugsgröße für AFC-Mathematik und alle Pegelbetrachtungen; mit dem realen Hub liegen Mark/Space bei $\mp1,0$ (nach Polarity: Mark = +1,0).

3.5 Post-Detection-Tiefpass

Parameter Ist-Wert Warum so
Taps low_pass(1, 1200, 50, 25) $\rightarrow$ 117 Taps, decim = 1 $f_g = 50 \text{ Hz} = \text{Baudrate}$: klassische Post-Detection-Wahl — die NRZ-Hauptkeule (bis 50 Hz) passiert, Diskriminator-Rauschen darüber wird abgeschnitten. Entwurf direkt bei 1200 Hz: 117 Taps statt der 1157 Taps, die bei 12 kHz nötig wären — Rechenlast $\div$ 10.
Gruppenlaufzeit 58 Sa / 1200 Hz $\approx$ 48 ms Die Gruppenlaufzeit hängt nur vom Frequenzgang ab (50 Hz / 25 Hz Übergang), nicht von der Abtastrate, bei der das Filter entworfen wurde. Für die trägen Regelkreise (AFC-Fenster 5 s, Gate-Fenster 1 s) sind 48 ms belanglos.

3.6 Auto-Polarity (epy_block_1)

Parameter Ist-Wert Warum so
Struktur Detektor + Multiplikation in einem Block ($y = k \cdot x$) Vereint in einem Block statt über eine separate Blockreferenz auf multiply_const. Gemessen wird am eigenen Eingang (vor der Korrektur) — offener Regelkreis, keine Rückkopplungs-Oszillation.
initial_polarity −1 Bekannter DDK9-Wert (Mark = tiefe Frequenz). Der Empfänger arbeitet damit ab Sample 1 korrekt; der Detektor bestätigt nur noch. Bei unbekanntem Sender wäre +1/−1 gleichwertig — der Detektor korrigiert nach 2 s.
Messfenster 2 s = 2400 Sa = 100 Bit RTTY ist Mark-lastig (Stoppbits, Idle, LTRS) — aber Textpassagen (RY-Schleifen!) sind fast balanciert. 100 Bit mitteln die Datenstatistik zuverlässig heraus (gemessen: mean = −0,035…−0,079, sicher vom Totbereich unterscheidbar).
Totbereich |mean| < 0,01 Rauschen hat Mittelwert $\approx$ 0 — keine Zufallsentscheidung. Zusätzlich abgesichert durch das Gate (nächste Zeile).
gate-Eingang Erkennung nur bei OPEN, Puffer-Reset bei CLOSED Der One-Shot-Lock (rastet dauerhaft ein) darf niemals auf Rauschen fallen — das Gate garantiert, dass die Entscheidung nur auf echtem Signal basiert.

3.7 SNR-Gate (epy_block_3)

Parameter Ist-Wert Warum so
Metrik Anteil von |y| im Band $\pm40$ % um median(|y|) Pegel-adaptiv statt festem Band — ein festes Band um einen angenommenen Pegel wäre hub- und normierungsabhängig und würde bei abweichenden Aufnahmen versagen. Der Median von |y| ist bei bimodalem Signal ein robuster Schätzer des Cluster-Pegels; das relative Band funktioniert bei jedem Hub und jeder Normierung. Physik dahinter: RTTY-Samples liegen fast alle in den Clustern (nur Flanken außerhalb), Diskriminator-Rauschen ist breit verteilt.
Schwellen OPEN > 0,6 / CLOSE < 0,45 (Hysterese) Aus der Messung durch die echte Kette: Signal $q = 0,68{-}0,86$, reines Rauschen $q = 0,28{-}0,40$. Die Hysterese liegt zentriert in der Lücke mit $\ge 0,05$ Abstand zu beiden Verteilungen; zwei Schwellen verhindern Flattern an der Grenze.
Fenster 1 s = 1200 Samples Genug Statistik für stabilen Median und Bandanteil (Fenster-Streuung gemessen $\pm0,09$), aber schnelle Reaktion: 1 s nach Signalverlust ist der Decoder stumm.
Meldung Message (intern('signal'), bool) nur bei Zustandswechsel + einmal initial Verbraucher halten den Zustand selbst — kein Message-Dauerfeuer. Die Initialmeldung definiert den Zustand der Verbraucher (die im Flowgraph mit gate_default = False starten, standalone mit True weiterbenutzbar bleiben).

3.8 AFC (epy_block_2)

Parameter Ist-Wert Warum so
Messprinzip getrennte Cluster-Mittel, Schwellen $\pm0,05$, min. 50 Sa/Cluster Getrennte Mittelung positiver/negativer Samples macht die $\delta$f-Schätzung unabhängig vom Mark/Space-Tastverhältnis; $\pm0,05$ verwirft Flankensamples; 50 Sa Mindestbelegung $\Rightarrow$ keine Korrektur, wenn ein Ton fehlt.
Fenster / Warmup 5 s / 3 s 5 s = 250 Bit $\Rightarrow$ beide Cluster sicher belegt, Schätzvarianz klein. Warmup 3 s > Polarity-Fenster 2 s: Die AFC-Vorzeichenlogik setzt korrekte Polarität voraus.
loop_gain 0,4 Verstärkung 1 korrigiert jeden Messfehler voll $\Rightarrow$ Grenzzyklen (gemessen: $\pm1,4$-Hz-Dithering). Mit 0,4 klingt ein Fehler geometrisch ab (Rest $\approx 0,6^k$): 5 Hz $\rightarrow$ <1 Hz in ~4 Updates (20 s) — für einen quarzstabilen Broadcast-Sender mehr als schnell genug, dafür robust gegen Ausreißer. Verifiziert: Korrekturen $\pm0,2\dots0,5$ Hz, sanft konvergierend.
EWMA $\alpha = 0,5$ (~2 Fenster), nach Korrektur um corr/shift bereinigt Glättet Einzelmessfehler weg. Die Bereinigung ist zwingend: alte Messwerte im EWMA stammen von vor der Korrektur — ohne Bereinigung regelt die Schleife gegen ihren eigenen Eingriff.
Totbereich 0,5 Hz auf dem geglätteten Fehler Läge er auf der Korrektur, bliebe mit gain 0,4 ein Restfehler bis 1,25 Hz stehen. 0,5 Hz = 0,2 % des halben Hubs — augendiagrammatisch unsichtbar, verhindert aber Mikro-Nachstellungen.
Begrenzung $\pm50$ Hz pro Update Slew-Limit gegen Ausreißer; zusammen mit dem $\pm125$-Hz-Fangbereich des Kanalfilters (3.2) konsistent.
gate-Eingang bei CLOSED: Messung stoppen, Zähler + Warmup zurücksetzen Rauschen kann zufällig beide Cluster „belegen" und die Mitte wegziehen. Warmup-Neustart nach Wiederöffnen stellt die Reihenfolge Polarity$\rightarrow$AFC sicher.
Ausgang Message (intern('freq'), double), absolute Frequenz Dokumentiertes PMT-Format des freq_xlating-Ports; absolute Frequenz statt Delta macht die Schleife idempotent gegen verlorene Messages.

3.9 Matched Filter (Boxcar)

Parameter Ist-Wert Warum so
Länge / Skalierung 24 Taps = 1 Bit, scale 1/24 $h(t) = s(T-t)$: für Rechteckpulse ist der Boxcar über exakt eine Bitdauer der Matched Filter. $\text{ENBW} = 1200/24 = 50 \text{ Hz}$. Hauptnutzen in dieser Kette: dreieckförmiges Auge mit Maximum in Bitmitte — die Signalform, die der M&M-TED für stabiles Timing braucht. (Der SNR-Zusatzgewinn ist klein, da der 50-Hz-Tiefpass das Rauschen schon auf dieselbe Bandbreite begrenzt.)

3.10 ATC (epy_block_4)

Parameter Ist-Wert Warum so
Prinzip $y' = y - (\hat{m}{\text{Mark}} + \hat{m}{\text{Space}})/2$, Pegel-Tracking getrennt nach Vorzeichen Der Slicer behält seine feste 0-Schwelle, aber das Signal wird um die halbe Pegelsumme zentriert — effektiv eine nachgeführte Entscheidungsschwelle (Stand der Technik: MMTTY/Fldigi). Getrenntes Tracking je Vorzeichen ist der Kernpunkt: Lange Mark-Phasen (LTRS-Idle!) aktualisieren nur $\hat{m}_{\text{Mark}}$ — der Space-Schätzer bleibt stehen statt wegzudriften. Ein gemeinsamer Mittelwert-Tracker würde durch die Mark-Lastigkeit systematisch verfälscht.
Zeitkonstante $\tau = 20 \text{ Bit} \times 24 \text{ sps} = 480 \text{ Sa} = \mathbf{0,4\text{ s}}$ Schnell genug für KW-Fading (folgt Pegeländerungen bis ~0,4 Hz — typische Fadingraten liegen darunter), langsam genug, dass Rauschen den Pegelschätzer kaum moduliert (Mittelung über ~480 Samples).
Startwerte $\hat{m} = +0,5$ / $\hat{s} = -0,5$ Symmetrisch $\Rightarrow$ Startkorrektur = 0, kein Einschaltfehler; die Schätzer konvergieren in ~$\tau$ (0,4 s) auf die realen $\pm1,0$. (Bewusst kein fester $\pm1,0$-Start: der Pegel ist hub-/normierungsabhängig, siehe Abschnitt 2.)
Position zwischen MF und Symbol Sync Nach dem MF ist das Rauschen minimal (bester Pegelschätzer), vor dem Sync profitiert auch der entscheidungsgestützte M&M-TED von der Zentrierung. Augen-Sink hängt am ATC-Ausgang — man sieht das korrigierte Auge.

3.11 Symbol Sync (Mueller & Müller)

Parameter Ist-Wert Warum so
TED / sps / osps M&M, 24, 1 M&M passt zum Dreieck-Auge des Boxcar-MF (ein Zero-Crossing-TED versagt an dieser Augenform). osps = 1 liefert exakt 1 Sample/Bit an den Decoder, dessen Zustandsmaschine darauf ausgelegt ist.
loop_bw / damping 0,05 / 1,0 $\rightarrow$ $B_n = 2,5 \text{ Hz}$, kritisch gedämpft Einschwingen in ~0,4 s; kein Überschwingen. Bewusst nicht enger gewählt: 0,015 würde Timing-Jitter reduzieren, verlängert aber das Einschwingen — eine BER-Messung sollte diesen Effekt vor einer solchen Änderung quantifizieren.
max_dev 1,0 Sa/Symbol $\approx$ $\pm4,2$ % Baudraten-Toleranz Sender ist taktstabil; $\pm4$ % deckt jede realistische Abweichung plus Sync-Transienten ab, begrenzt aber Weglaufen bei Rauschen.

3.12 Slicer + Baudot-Decoder (Flywheel)

Parameter Ist-Wert Warum so
Slicer-Schwelle fest 0 korrekt, weil die ATC davor zentriert — die Adaptivität steckt jetzt in 3.10, der Slicer bleibt trivial.
LOCK_THRESHOLD 5 gute Rahmen (= 0,75 s) Zufallsbits liefern ein gültiges Stoppbit mit $p \approx 0,5$; fünf in Folge $\Rightarrow$ Falsch-Lock-Wahrscheinlichkeit ~3 % pro Versuch — zusammen mit dem SNR-Gate (das Rauschen gar nicht erst durchlässt) doppelt abgesichert. Schneller Lock ist wichtig, damit das Flywheel Sendepausen früh überbrücken kann.
UNLOCK_THRESHOLD 8 Fehler in Folge (= 1,2 s) tolerant gegen Störbursts (Flywheel hält durch), aber endlich: nach 1,2 s Dauerfehlern ist der Takt real verloren $\rightarrow$ zurück in HUNT, Shift auf LETTERS.
Framing-Reset 3 Fehler $\rightarrow$ Voll-Reset nach mehreren Fehlrahmen ist auch der LTRS/FIGS-Zustand unglaubwürdig — konservativer Neustart der Zeichenebene.
Flywheel-Bedingung _idle_marks ≥ 2 Der 1,5-Bit-Stopp erzeugt beim 1-Sa/Bit-Sync sporadisch ein Extra-Mark-Sample zwischen Rahmen; ohne die 2-Mark-Hürde feuert das Flywheel darauf und verschiebt den Folgerahmen um 1 Bit. Nicht ändern.
gate-Eingang CLOSED $\Rightarrow$ stumm + Voll-Reset inkl. Flywheel Keine Zufallszeichen aus Rauschen; nach Signalwiederkehr sauberer Neuaufbau (Lock in 0,75 s).

4. Einschalt-Sequenz der Regelkreise

Die Zeitstaffelung ist bewusst so dimensioniert, dass jede Stufe auf einer bereits eingeschwungenen Vorstufe aufsetzt:

Zeit Ereignis Abhängigkeit
~0,1 s Filter/AGC eingeschwungen
~0,4 s ATC-Pegel konvergiert; Symbol-Sync gelockt $\tau_{\text{ATC}} = 0,4\text{ s}$; $B_n = 2,5\text{ Hz}$
1 s SNR-Gate: erste Bewertung $\rightarrow$ OPEN (gemessen q = 0,76) Gate-Fenster 1 s
3 s Auto-Polarity-Lock 2-s-Fenster ab Gate-OPEN
4 s AFC-Warmup abgelaufen 3 s ab Gate-OPEN > Polarity-Fenster $\checkmark$
~9 s erste AFC-Korrektur + 5-s-Messfenster
< 2 s Decoder: Flywheel-LOCK, sauberer Text 5 gute Rahmen nach Gate-OPEN
Die ersten ~2 s nach Gate-OPEN können einzelne Fehlzeichen liefern (Sync-/ATC-Einschwingen, ggf. eingefangener FIGS-Shift) — das ist der erwartete Transient, kein Fehler. Bei geschlossenem Gate (Rauschen) kommt gar kein Text mehr.

5. Raten- und Rechenlastplan

Stufe Rate Taps Last ($\approx$ MAC/s)
Freq-Xlating ($\div 10$, polyphas) 12 k $\rightarrow$ 1,2 k $\mathbb{C}$ 289 0,35 M
AGC² + Quad-Demod 1,2 kHz < 0,01 M
Post-Detection-TP 1,2 kHz 117 0,14 M
Polarity / Gate / AFC / MF / ATC 1,2 kHz < 0,05 M
Symbol Sync $\rightarrow$ Decoder 1,2 k $\rightarrow$ 50 Hz 8 < 0,01 M
Summe $\approx$ 0,6 MMAC/s

Latenz: ~70 ms (Kanalfilter 12 ms + Post-Detection-TP 48 ms + MF 10 ms). Für die Regelkreise (Fenster $\ge 1\text{ s}$) irrelevant.


Fazit der Ist-Dimensionierung: Jeder Zahlenwert ist auf eine von drei Quellen zurückführbar: (1) die Signalnorm (50 Bd, Hub, 7,5-Bit-Rahmen) — bestimmt sps, MF-Länge, Filtergrenzen; (2) Messungen an der echten Aufnahme — bestimmen Gate-Schwellen, Cluster-Pegel, Polarity-Statistik; (3) Regelungstechnik — bestimmt Schleifenverstärkung, Zeitstaffelung und Hysteresen. Einzige Ausnahme ohne eigene Messabsicherung sind die Symbol-Sync-Parameter — eine BER-Messung könnte hier noch Feinjustierung ermöglichen.