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:
- Phasendetektor (PD): Vergleicht die Phase des Eingangssignals mit der des lokalen Oszillators.
- Loop Filter (LF): Glättet das Fehlersignal (entscheidend für die Stabilität).
- 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 10kHz 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)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_freqPI 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 wir die PLL das verrauschte Ausgangssignal verändert.

Typische Einsatzgebiete von PLL’s 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