Projekte

DCF77-Demodulation in GNU Radio

Vom dateibasierten Python-Skript zur echtzeitfähigen Blockverschaltung im GNU Radio Companion.

Dieser Artikel beschreibt die Umsetzung der DCF77-Zeitzeichen-Demodulation mit GNU Radio — der Übergang von dateibasierter Python-Verarbeitung zu einer echtzeitfähigen Streaming-Architektur.

Dateiverarbeitung vs. Echtzeitsystem

Dateibasierte Ansätze laden den gesamten Datensatz in den Speicher und ermöglichen wahlfreien Zugriff über Array-Indizes. Echtzeitsysteme verarbeiten dagegen einen kontinuierlichen Datenstrom ohne definierten Anfang oder Ende. In GNU Radio gibt es „keine Variable t für die Zeit" — nur einen Sample-Zähler. Blöcke erhalten das aktuelle Sample, kennen aber keine absolute Zeit.

Verarbeitungskette in GNU Radio

Throttle-Block – wirkt als künstlicher Flaschenhals, begrenzt den Sample-Fluss, um reale Antennenbedingungen zu simulieren und eine CPU-Überlastung bei der Dateiwiedergabe zu verhindern.

GNU-Radio-Verarbeitungskette: WAV Source, Throttle, Hilbert, Complex to Mag und Tiefpassfilter

Signalaufbereitung:

  • Hilbert-Transformation erzeugt IQ-Daten aus dem WAV-Eingang
  • der Complex-to-Mag-Block extrahiert die Hüllkurve durch Berechnung der Sample-Beträge
  • ein Tiefpassfilter glättet das Signal und ermöglicht die Dezimierung (Faktor 10, von 12.000 auf 1.200 Samples/Sekunde)
  • der Threshold-Block wandelt die analogen Werte in ein binäres (0/1) Signal um

Rohsignal vs. Hüllkurve: Die blaue Kurve zeigt den heruntergemischten 77,5-kHz-Träger, die rote Kurve die durch Hilbert-Transformation und Complex-to-Mag extrahierte Hüllkurve — deutlich sichtbar die Amplitudenabsenkung in der Mitte.

Rohsignal (blau) und extrahierte Hüllkurve (rot) nach Hilbert-Transformation

Über mehrere Sekunden betrachtet zeigt die noch ungeglättete Hüllkurve die periodischen Absenkungen im Sekundentakt — deutlich verrauschter als nach der finalen Tiefpassfilterung.

Hüllkurve über mehrere Sekunden vor der Tiefpassglättung

Der GRC-Threshold-Block wandelt das geglättete, analoge Hüllkurvensignal mit den hier gezeigten Schwellwerten in ein binäres (0/1) Signal um.

Parameter des GRC-Threshold-Blocks

DCF77-Bit-Erkennungsblock

Der individuelle Python-Block implementiert eine dreistufige Verarbeitung:

Stufe 1 – Schmitt-Trigger mit Hysterese: nutzt zwei Schwellwerte (0,4 und 0,6), um Rauschen zu unterdrücken. Ein Zustandswechsel erfolgt erst nach stabiler Bestätigung über mehrere Samples (Debounce).

Stufe 2 – Pulsdauermessung: fallende Flanken markieren den Pulsbeginn, steigende Flanken das Pulsende. Die Dauer berechnet sich als Dauer[ms] = Anzahl_Samples / samp_rate × 1000.

Stufe 3 – Bit-Klassifikation:

  • 70–150 ms → logische „0"
  • 160–300 ms → logische „1"
  • Timeout-Schutz bei 400 ms

Die Ergebnisse werden als Metadaten-Tags direkt an den Stream-Samples der steigenden Flanken angehängt.

Die gemessenen Pulsdauern (204 ms bzw. 98 ms) werden direkt den Bit-Werten 1 bzw. 0 zugeordnet und als dcf77_bit-Tag an das Sample der steigenden Flanke (pulse_start) angehängt.

Geglättetes Signal mit Threshold-Verlauf und annotierten dcf77_bit-Tags

DCF77-Decoder-Block

Sammelt die getaggten Bits und decodiert die Protokollinformation. Die vollständige Kette ab dem Threshold-Block: Ein eigener „DCF77 Debug Tagger" führt die Schmitt-Trigger-Hysterese (Threshold_Low 0,4 / Threshold_High 0,6) und das Debounce aus Stufe 1 aus, bevor der DCF77-Decoder die Zeitinformation extrahiert und per Message Debug sowie ZMQ-Sink ausgibt.

GRC-Verarbeitungskette nach dem Threshold-Block: DCF77 Debug Tagger, DCF77 Decoder, Message Debug und ZMQ PUB Sink

BCD-Dekodierung: jedes Bit wird mit gewichteten Positionswerten multipliziert (z.B. Minuten-Einer: 1, 2, 4, 8; Zehner: 10, 20, 40).

Protokollelemente (Bitpositionen):

Feld Bitpositionen
Zeitzone (MESZ/MEZ) 17–18
Minute 21–28
Stunde 29–35
Tag 36–42
Wochentag 42–45
Monat 45–50
Jahr 50–58

Frames synchronisieren sich über die 59-Sekunden-Lücke; ein Message Port gibt formatierte Zeitstrings aus.

Datenausgabe

Nachrichten werden per ZMQ an localhost:5555 übertragen und von einem externen Python-Anzeigeclient empfangen, der die dekodierte Uhrzeit darstellt.

Python-GUI mit der dekodierten DCF77-Uhrzeit