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.