Phase Locked Loop (PLL)

Wie ein Regelkreis aus Phasendetektor, Loop-Filter und NCO den lokalen Oszillator exakt auf Frequenz und Phase des Eingangssignals einrastet.

Eine Phase-Locked Loop (PLL) sorgt in der digitalen Signalverarbeitung (DSP) dafĂŒr, dass ein lokaler Oszillator exakt synchron zu einem Eingangssignal lĂ€uft – sowohl in der Frequenz als auch in der Phase.

Grundprinzip

Eine PLL ist ein klassischer RĂŒckkopplungskreis (Regler). Sie besteht aus drei Hauptkomponenten:

  1. Phasendetektor (PD): Vergleicht die Phase des Eingangssignals mit der des lokalen Oszillators.
  2. Loop Filter (LF): GlĂ€ttet das Fehlersignal (entscheidend fĂŒr die StabilitĂ€t).
  3. Numerically Controlled Oscillator (NCO): Erzeugt das lokale Signal, dessen Frequenz basierend auf dem gefilterten Fehler angepasst wird.

Die Frequenzdifferenz zwischen dem Eingangssignal und dem lokalen Oszillator (NCO) Ă€ußert sich mathematisch als eine Phase, die sich ĂŒber die Zeit verĂ€ndert.

Wenn der TrÀger bei 1001 Hz schwingt und dein NCO bei 1000 Hz, dann lÀuft die Phase deines NCOs dem TrÀger jede Sekunde um 360° hinterher.

Der Phasendetektor sieht also einen Fehler, der stetig grĂ¶ĂŸer wird (eine Rampe). Das Loop-Filter erkennt diese Steigung und erhöht die Frequenz des NCO entsprechend, bis das Delta 0 ist. Die Frequenz des NCO entspricht nun im eingerasteten Zustand der des Eingangssignals.

Wenn die Frequenzen am Anfang zu weit auseinanderliegen (z.B. TrĂ€ger bei 1000 Hz, NCO bei 2000 Hz), ist der Phasenfehler so schnell wechselnd, dass das Loop-Filter ihn einfach wegfiltert (als wĂ€re es Rauschen). Die PLL „sieht" das Signal dann gar nicht und kann nicht einrasten.

Simulation in Python

Das folgende Python-Programm zeigt die prinzipielle Funktionsweise einer PLL in der diskreten Signalverarbeitung.

Im ersten Schritt erzeugen wir ein Signal mit einer Frequenz von 1000 Hz mit einer Abtastrate von 10 kHz und ein zufĂ€lliges Rauschen. Beide Abtastwerte werden zu einem verrauschten Input-Signal zusammengefĂŒhrt.

fs = 10000  # Abtastrate in Hz
T = 0.4  # Dauer in Sekunden
t = np.arange(0, T, 1 / fs)
f_input = 1000.0
clean_signal = np.exp(1j * 2 * np.pi * f_input * t)

noise = (np.random.randn(len(t)) + 1j * np.random.randn(len(t))) * 0.3
input_signal = clean_signal + noise

base_f_nco = 930.0
pll = PLL(sample_rate=fs, loop_bandwidth=0.03, damping_factor=0.707)
fs = 10000  # Abtastrate in Hz
T = 0.4  # Dauer in Sekunden
t = np.arange(0, T, 1 / fs)
f_input = 1000.0
clean_signal = np.exp(1j * 2 * np.pi * f_input * t)

noise = (np.random.randn(len(t)) + 1j * np.random.randn(len(t))) * 0.3
input_signal = clean_signal + noise

base_f_nco = 930.0
pll = PLL(sample_rate=fs, loop_bandwidth=0.03, damping_factor=0.707)

Die PLL wird mit diesem Signal „gefĂŒttert".

Parameter der PLL

Die Loop-Bandwidth bestimmt, wie schnell die PLL auf Änderungen reagiert. Sie legt fest, welche Frequenzanteile des Phasenfehlers das Filter passieren dĂŒrfen.

  • Kleine Bandbreite → trĂ€ge PLL, lange Einrastzeit, aber gutes Verhalten bei schlechtem SNR
  • Große Bandbreite → schnelle Reaktion, aber mehr Rauschen im Ausgang

Typische Werte liegen zwischen 0.01 und 0.05.

Der DĂ€mpfungsfaktor bestimmt das dynamische Verhalten der PLL:

  • Werte > 1 → sehr stabil, aber langsam
  • Werte < 1 → schneller, aber mit Überschwingen

Ein Wert von 0.707 ist ein hÀufig verwendeter Kompromiss.

PLL-Klasse

Die PLL-Klasse enthĂ€lt die Logik fĂŒr die Verarbeitung.

class PLL:
    def __init__(self, sample_rate, loop_bandwidth=0.05, damping_factor=0.707):
        self.fs = sample_rate

        # Berechnung der Loop-Filter Koeffizienten (PI-Regler) nach Gardner
        denom = (1 + 2 * damping_factor * loop_bandwidth + loop_bandwidth ** 2)
        self.kp = (4 * damping_factor * loop_bandwidth) / denom
        self.ki = (4 * loop_bandwidth ** 2) / denom

        # Zustandsspeicher der PLL
        self.integrator = 0.0
        self.phase_nco = 0.0
        self.frequency_offset = 0.0  # In Hz

    def process_sample(self, input_sample, base_freq):
        """
        Verarbeitet ein einzelnes komplexes Sample.
        """
        # 1. NCO Signal generieren
        nco_sample = np.exp(1j * self.phase_nco)

        # 2. Phasendetektor (Multiplikation mit konjugiert Komplexem)
        phase_error = np.angle(input_sample * np.conj(nco_sample))

        # 3. Loop Filter (PI-Regler)
        self.integrator += self.ki * phase_error
        filter_out = self.kp * phase_error + self.integrator

        # 4. Frequenz-Update (NCO)
        # Umrechnung von normierter Frequenz (Radiant/Sample) in Hertz
        self.frequency_offset = (filter_out * self.fs) / (2 * np.pi)
        current_freq = base_freq + self.frequency_offset

        # 5. Phasenintegration fĂŒr das nĂ€chste Sample
        self.phase_nco += 2 * np.pi * current_freq / self.fs

        return nco_sample, phase_error, current_freq
class PLL:
    def __init__(self, sample_rate, loop_bandwidth=0.05, damping_factor=0.707):
        self.fs = sample_rate

        # Berechnung der Loop-Filter Koeffizienten (PI-Regler) nach Gardner
        denom = (1 + 2 * damping_factor * loop_bandwidth + loop_bandwidth ** 2)
        self.kp = (4 * damping_factor * loop_bandwidth) / denom
        self.ki = (4 * loop_bandwidth ** 2) / denom

        # Zustandsspeicher der PLL
        self.integrator = 0.0
        self.phase_nco = 0.0
        self.frequency_offset = 0.0  # In Hz

    def process_sample(self, input_sample, base_freq):
        """
        Verarbeitet ein einzelnes komplexes Sample.
        """
        # 1. NCO Signal generieren
        nco_sample = np.exp(1j * self.phase_nco)

        # 2. Phasendetektor (Multiplikation mit konjugiert Komplexem)
        phase_error = np.angle(input_sample * np.conj(nco_sample))

        # 3. Loop Filter (PI-Regler)
        self.integrator += self.ki * phase_error
        filter_out = self.kp * phase_error + self.integrator

        # 4. Frequenz-Update (NCO)
        # Umrechnung von normierter Frequenz (Radiant/Sample) in Hertz
        self.frequency_offset = (filter_out * self.fs) / (2 * np.pi)
        current_freq = base_freq + self.frequency_offset

        # 5. Phasenintegration fĂŒr das nĂ€chste Sample
        self.phase_nco += 2 * np.pi * current_freq / self.fs

        return nco_sample, phase_error, current_freq

PI-Regler in der PLL

Die PLL arbeitet intern als PI-Regler (Proportional-Integral-Regler). Ziel ist es, den Fehler möglichst schnell auf Null zu bringen und dort zu halten.

P-Anteil: reagiert auf den aktuellen Signalwert und gibt einen Korrekturimpuls auf den NCO. Je kleiner der Fehler wird, desto schwÀcher wird die Korrektur des P-Anteils. Er allein schafft es fast nie, den Fehler exakt auf Null zu bringen (es bleibt eine sogenannte bleibende Regelabweichung).

Der Wert self.kp ist dabei der Proportional Gain. Er bestimmt, wie schnell der Regler auf eine Änderung reagiert.

I-Anteil: Der Integrations-Anteil summiert (integriert) alle Fehlerwerte ĂŒber die Zeit auf. Er berĂŒcksichtigt die Vergangenheit im Regler. Er ist der Teil, der die Frequenzdifferenz „lernt" und den Frequenz-Offset (Drift) dauerhaft ausgleicht.

Der Wert self.ki ist der Integral-Gain. Er bestimmt die Gewichtung des aufsummierten Phasenfehlers im Loop-Filter und ist maßgeblich dafĂŒr verantwortlich, eine dauerhafte Frequenzabweichung (Drift) zwischen Eingangssignal und Oszillator vollstĂ€ndig zu eliminieren.

In GNU Radio werden die Koeffizienten oft Alpha (α) und Beta (ÎČ) genannt, wobei Alpha dem Kp-Wert und Beta dem Ki-Wert entspricht.

Die folgenden Diagramme zeigen, wie die PLL das verrauschte Ausgangssignal verÀndert.

Simulation der PLL: verrauschtes Eingangssignal und eingerasteter NCO

Typische Einsatzgebiete von PLLs in Software Defined Radio

In SDR-Systemen ist die PLL ein zentrales Werkzeug, da viele Probleme auf Frequenz- und Phasenunsicherheiten zurĂŒckzufĂŒhren sind. Typische Anwendungen sind:

1. TrÀgersynchronisation (Carrier Recovery)

Eine der wichtigsten Anwendungen ist die Wiederherstellung des TrÀgersignals bei modulierten Signalen (z.B. PSK, QAM).

Der EmpfÀnger kennt die exakte TrÀgerfrequenz oft nicht (z.B. durch Doppler-Effekt oder Oszillatorabweichungen). Die PLL synchronisiert den lokalen Oszillator mit dem empfangenen Signal, sodass:

  • die Konstellation stabil wird
  • Phasendrehungen korrigiert werden
  • Demodulation ĂŒberhaupt erst möglich ist

2. Frequenzkorrektur (Frequency Offset Correction)

Selbst kleine Frequenzabweichungen zwischen Sender und EmpfĂ€nger fĂŒhren zu einer kontinuierlichen Phasendrehung.

Die PLL gleicht diese Differenz aus, indem sie:

  • den Frequenzoffset schĂ€tzt
  • den NCO entsprechend nachregelt

Typisch z.B. bei:

  • gĂŒnstigen SDR-Sticks (Temperaturdrift)
  • Kommunikation ĂŒber große Distanzen (Doppler)

3. FM-Demodulation

Eine PLL kann direkt zur Demodulation von frequenzmodulierten Signalen verwendet werden.

Dabei gilt: FrequenzĂ€nderung → PhasenĂ€nderung → Fehler im Phasendetektor.

Der Ausgang des Loop-Filters entspricht dann dem demodulierten Audiosignal.

4. Symbol- und Takt-Synchronisation (Clock Recovery)

Erweiterte PLL-Varianten (z.B. Timing Recovery Loops) werden verwendet, um:

  • den optimalen Abtastzeitpunkt zu finden
  • Symbolgrenzen korrekt zu erkennen

Das ist entscheidend fĂŒr digitale Modulationen wie BPSK, QPSK, QAM.

5. Costas Loop (Spezialform der PLL)

Eine Costas Loop ist eine spezielle PLL fĂŒr unterdrĂŒckte TrĂ€ger (z.B. BPSK, QPSK). Sie:

  • rekonstruiert den TrĂ€ger ohne direkten Referenzton
  • entfernt Phasenfehler
  • stabilisiert die I/Q-Komponenten

6. Tracking von Doppler-Verschiebungen

Bei bewegten Sendern/EmpfÀngern (z.B. Satelliten, Funkverkehr) Àndert sich die Frequenz dynamisch.

Die PLL kann diese Änderungen kontinuierlich nachfĂŒhren:

  • Satellitenkommunikation
  • Funkverbindungen mit hoher Geschwindigkeit
  • AIS / Flugfunk / Amateurfunk

7. RauschunterdrĂŒckung durch schmalbandige NachfĂŒhrung

Eine gut eingestellte PLL wirkt wie ein adaptiver Filter:

  • folgt dem Nutzsignal
  • unterdrĂŒckt breitbandiges Rauschen

Das verbessert das Signal-Rausch-VerhÀltnis im Basisband.

Kurz zusammengefasst

PLLs werden im SDR immer dann eingesetzt, wenn:

  • Frequenz und Phase nicht exakt bekannt oder instabil sind
  • ein Signal nachgefĂŒhrt, stabilisiert oder rekonstruiert werden muss