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.

Zu den Zahlenwerten in diesem Artikel: Die grundlegenden Eigenschaften (Existenz seit 1983, Zweck, prinzipielle Konstruktion als deterministische Bitfolge) sind gut dokumentiert. Sequenzlänge (512 Chips) und Erzeugungsvorschrift (9-Bit-LFSR, Polynom 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.

Eine Sekunde (1000 ms) ├── Chip 0 ├── Chip 1 ├── Chip 2 ├── ... └── Chip N-1 │ └──► Folge beginnt exakt zur nächsten Sekundenmarke von Neuem

Erzeugung: rückgekoppeltes Schieberegister (LFSR)

Schieberegister

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.

Die tatsächliche PTB-Folge hat 512 Chips — kein Wert der Form $2^k-1$. Das 9-Bit-Register durchläuft hier also nicht alle 511 möglichen Nichtnull-Zustände einer echten Maximalfolge, sondern wird nach exakt 512 Takten für die nächste Sekunde neu gestartet. Die Autokorrelationseigenschaften bleiben dabei sehr gut (siehe erzeugte Sequenz unten), auch wenn es sich damit nicht um eine Maximalfolge im strengen mathematischen Sinn handelt.

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
Wichtig für den eigenen Korrelator: Der Seed 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.

* Die Abkürzung PZF ist in der deutschsprachigen Fachliteratur zu DCF77 gebräuchlich; eine englischsprachige Entsprechung ist „PRN modulation" bzw. „pseudo-random phase modulation".