Projekte
Die PZF-Sequenz von DCF77
Aufbau, Erzeugung und Eigenschaften der pseudozufälligen Phasenmodulation (PZF), die DCF77 seit 1983 zusätzlich zur AM-Zeitinformation ausstrahlt.
Dieser Artikel beschreibt die Grundlagen der PZF-Sequenz selbst — als Ergänzung zur Korrelativen Phasenauswertung, die erklärt, wie man diese Sequenz per Matched Filter auswertet. Hier geht es darum, was die Sequenz eigentlich ist.
0x110) sind unten als lauffähige Referenzimplementierung angegeben. Der exakte Phasenhub sowie die Übereinstimmung dieser Implementierung mit der tatsächlich gesendeten PTB-Folge sind hier nicht unabhängig verifiziert — vor dem Einsatz in einem echten Korrelator gegen die offizielle PTB-Spezifikation zu DCF77 gegenprüfen.Was PZF ist — und was sie nicht ist
PZF steht für Pseudozufalls-Phasenmodulation* (auch als „Pseudo-Random Phase Modulation" bezeichnet). Die Physikalisch-Technische Bundesanstalt (PTB) strahlt sie seit 1983 zusätzlich zur bekannten Amplitudenmodulation (AM, die 100/200-ms-Absenkungen für den BCD-Zeitcode) auf demselben 77,5-kHz-Träger aus.
Wichtig zur Einordnung:
- PZF ersetzt nicht die AM-Zeitinformation. Die Minute, Stunde, das Datum etc. werden nach wie vor ausschließlich über die Amplitudenabsenkungen übertragen (siehe DCF77-Signal demodulieren). PZF liefert keine zusätzlichen Dateninhalte, sondern ein zusätzliches, hochpräzises Zeitreferenzsignal.
- PZF ist amplitudenneutral. Die Phasenmodulation ändert nicht die Hüllkurve des Signals — ein normaler AM-Empfänger (wie in Stufe 1/2 dieses Projekts) sieht PZF überhaupt nicht und wird durch sie nicht gestört.
- PZF wird nicht von gewöhnlichen Funkuhren ausgewertet. Die meisten handelsüblichen DCF77-Empfänger (Funkuhren, Wetterstationen) demodulieren ausschließlich die AM-Information. PZF-Auswertung erfordert einen Korrelationsempfänger wie den in diesem Projekt entwickelten.
Grundidee: eine deterministische „Signatur" pro Sekunde
Der Träger wird nicht kontinuierlich zufällig phasenmoduliert, sondern mit einer festen, vorab bekannten Bitfolge (einer Pseudozufallsfolge, PRBS — Pseudo-Random Binary Sequence), die sich exakt einmal pro Sekunde wiederholt, synchron zur Sekundenmarke. „Pseudozufällig" bedeutet dabei: Die Folge sieht statistisch aus wie Rauschen (keine erkennbaren Muster, ausgeglichene Verteilung von 0 und 1), ist aber vollständig deterministisch und für jeden Empfänger exakt reproduzierbar — genau diese Reproduzierbarkeit ist die Voraussetzung dafür, im Empfänger eine identische Referenzkopie zu erzeugen und per Matched Filter zu korrelieren.
Jedes Bit der Folge wird als Chip bezeichnet (Terminologie aus der Spreizspektrumtechnik, der PZF technisch verwandt ist) — nicht als „Bit" im Sinne von Nutzdaten, um die Verwechslung mit den AM-codierten Zeitinformations-Bits zu vermeiden.
Erzeugung: rückgekoppeltes Schieberegister (LFSR)

Pseudozufallsfolgen dieser Art werden üblicherweise mit einem linear rückgekoppelten Schieberegister (LFSR, Linear Feedback Shift Register) erzeugt: Ein Schieberegister fester Länge, dessen Eingangsbit sich aus der XOR-Verknüpfung bestimmter Ausgangs-Bitpositionen (den „Tap"-Positionen, festgelegt durch ein primitives Polynom) ergibt. Bei geeigneter Wahl des Rückkopplungspolynoms durchläuft das Register alle möglichen internen Zustände außer dem Nullzustand, bevor es sich wiederholt — das Ergebnis ist eine Maximalfolge, deren Autokorrelationseigenschaften (siehe Matched Filter) nahezu ideal deltaförmig sind: ein scharfer Peak bei richtiger Ausrichtung, praktisch keine Korrelation bei jeder Verschiebung dagegen.
Referenzimplementierung in Python
Das folgende Skript erzeugt die 512-Chip-Folge aus einem 9-Bit-LFSR mit dem PTB-Rückkopplungspolynom (Taps auf Bit-Position 9 und 5, hexadezimal 0x110). Der Parameter am_bit bildet die XOR-Verknüpfung mit dem aktuellen AM-Zeitcode-Bit ab, mit der die PTB die Sequenz je nach gesendetem Datenbit invertiert:
def generate_ptb_pzf_sequence(am_bit=0):
"""
Generiert die 512 Bit lange PTB-PZF-Sequenz für den DCF77-Sender.
Parameters:
am_bit (int): Das aktuelle Zeitcode-Datenbit der Amplitudenmodulation (0 oder 1).
Ein AM-Bit von 1 invertiert die gesamte Sequenz via XOR.
Returns:
list: Eine Liste mit 512 Binärwerten (0 und 1).
"""
# Startzustand des 9-Bit-Registers (Seed ist 0)
lfsr = 0
sequence = []
# Das Rückkopplungspolynom der PTB (Hex 0x110)
# Entpricht den Taps auf Bit-Position 9 und 5
polynomial = 0x110
for _ in range(512):
# Das unterste Bit (Bit 0) bestimmt den aktuellen Ausgangs-Chip
chip = lfsr & 1
# Logische Verknüpfung mit dem aktuellen AM-Datenbit (XOR-Invertierung)
final_chip = chip ^ am_bit
sequence.append(final_chip)
# Schiebe das Register um 1 Bit nach rechts
lfsr >>= 1
# Wenn der Ausgangs-Chip ungleich dem neuen "leeren" Zustand ist,
# greift die XOR-Rückkopplung des Polynoms
if chip ^ (not lfsr & 1):
lfsr ^= polynomial
return sequence
# --- Skript ausführen und Ergebnisse anzeigen ---
if __name__ == "__main__":
# Sequenz für ein AM-Bit von '0' generieren
pzf_sequence = generate_ptb_pzf_sequence(am_bit=0)
print(f"--- PTB DCF77 PZF-Sequenz (512 Bit) ---")
print(f"Länge der generierten Sequenz: {len(pzf_sequence)} Bits\n")
print("Die ersten 40 Bits der Sequenz:")
# Konvertiert die ersten 40 Zahlen in einen String zur einfachen Ansicht
bits_string = "".join(map(str, pzf_sequence[:40]))
print(bits_string)
print("...")
print("\nKomplette Sequenz im 64-Bit Blockraster:")
for i in range(0, 512, 64):
block = "".join(map(str, pzf_sequence[i:i + 64]))
print(f"Block {i // 64 + 1}: {block}")
def generate_ptb_pzf_sequence(am_bit=0):
"""
Generiert die 512 Bit lange PTB-PZF-Sequenz für den DCF77-Sender.
Parameters:
am_bit (int): Das aktuelle Zeitcode-Datenbit der Amplitudenmodulation (0 oder 1).
Ein AM-Bit von 1 invertiert die gesamte Sequenz via XOR.
Returns:
list: Eine Liste mit 512 Binärwerten (0 und 1).
"""
# Startzustand des 9-Bit-Registers (Seed ist 0)
lfsr = 0
sequence = []
# Das Rückkopplungspolynom der PTB (Hex 0x110)
# Entpricht den Taps auf Bit-Position 9 und 5
polynomial = 0x110
for _ in range(512):
# Das unterste Bit (Bit 0) bestimmt den aktuellen Ausgangs-Chip
chip = lfsr & 1
# Logische Verknüpfung mit dem aktuellen AM-Datenbit (XOR-Invertierung)
final_chip = chip ^ am_bit
sequence.append(final_chip)
# Schiebe das Register um 1 Bit nach rechts
lfsr >>= 1
# Wenn der Ausgangs-Chip ungleich dem neuen "leeren" Zustand ist,
# greift die XOR-Rückkopplung des Polynoms
if chip ^ (not lfsr & 1):
lfsr ^= polynomial
return sequence
# --- Skript ausführen und Ergebnisse anzeigen ---
if __name__ == "__main__":
# Sequenz für ein AM-Bit von '0' generieren
pzf_sequence = generate_ptb_pzf_sequence(am_bit=0)
print(f"--- PTB DCF77 PZF-Sequenz (512 Bit) ---")
print(f"Länge der generierten Sequenz: {len(pzf_sequence)} Bits\n")
print("Die ersten 40 Bits der Sequenz:")
# Konvertiert die ersten 40 Zahlen in einen String zur einfachen Ansicht
bits_string = "".join(map(str, pzf_sequence[:40]))
print(bits_string)
print("...")
print("\nKomplette Sequenz im 64-Bit Blockraster:")
for i in range(0, 512, 64):
block = "".join(map(str, pzf_sequence[i:i + 64]))
print(f"Block {i // 64 + 1}: {block}")
Ausgabe des Skripts (am_bit=0):
--- PTB DCF77 PZF-Sequenz (512 Bit) ---
Länge der generierten Sequenz: 512 Bits
Die ersten 40 Bits der Sequenz:
0000011111000110011100011101101001000001
...
Komplette Sequenz im 64-Bit Blockraster:
Block 1: 0000011111000110011100011101101001000001011111011111001110101101
Block 2: 1100100111111101001110011111010111111111000010000100101010010010
Block 3: 0110111011000011100001100001010011110110010110100000011101001010
Block 4: 0001111000000001000110001101111110110110101100110100010010001010
Block 5: 0011110100011010111011100000100111011110001000100000001100101001
Block 6: 0110000011011011110101011100110110011110010001110010111100110010
Block 7: 0001101001100010011001100000010101111011101000010110110001111110
Block 8: 0101011010100010110010010111000101010110001011101010011010101010
--- PTB DCF77 PZF-Sequenz (512 Bit) ---
Länge der generierten Sequenz: 512 Bits
Die ersten 40 Bits der Sequenz:
0000011111000110011100011101101001000001
...
Komplette Sequenz im 64-Bit Blockraster:
Block 1: 0000011111000110011100011101101001000001011111011111001110101101
Block 2: 1100100111111101001110011111010111111111000010000100101010010010
Block 3: 0110111011000011100001100001010011110110010110100000011101001010
Block 4: 0001111000000001000110001101111110110110101100110100010010001010
Block 5: 0011110100011010111011100000100111011110001000100000001100101001
Block 6: 0110000011011011110101011100110110011110010001110010111100110010
Block 7: 0001101001100010011001100000010101111011101000010110110001111110
Block 8: 0101011010100010110010010111000101010110001011101010011010101010
lfsr = 0 markiert den Registerzustand zu Beginn der Sekunde. Damit die lokal erzeugte Referenz bitidentisch mit dem gesendeten Signal ist, muss die Generierung exakt auf die Sekundenmarke (aus der AM-Grobsynchronisation, siehe Korrelative Phasenauswertung) ausgerichtet neu gestartet werden — sonst korreliert die Referenz gegen eine falsch rotierte Version der tatsächlichen Folge, und der Matched Filter findet keinen Peak.Warum das für die Korrelation wichtig ist
Für den in der Korrelativen Phasenauswertung beschriebenen Matched Filter ist nur eines entscheidend: Die im Empfänger lokal erzeugte Referenzfolge muss bitidentisch mit der tatsächlich gesendeten Folge sein — jede Abweichung (falsches Polynom, falsche Startphase, falsche Länge) zerstört die scharfe Autokorrelationsspitze und degradiert den Verarbeitungsgewinn $G_p \approx 10 \cdot \log_{10}(N),\text{dB}$ drastisch, da eine nicht perfekt passende Referenz effektiv wie zusätzliches Rauschen wirkt statt wie ein angepasstes Signal.
Korrelative Phasenauswertung
Wie die PZF-Folge per Matched Filter ausgewertet wird — inklusive Latenzbilanz.
Matched Filter
Autokorrelation, Verarbeitungsgewinn und Peak-Interpolation im Detail.
DCF77-Signal demodulieren
Die AM-Zeitinformation, die PZF ergänzt, aber nicht ersetzt.
* Die Abkürzung PZF ist in der deutschsprachigen Fachliteratur zu DCF77 gebräuchlich; eine englischsprachige Entsprechung ist „PRN modulation" bzw. „pseudo-random phase modulation".