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:
- 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 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.

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