Costas Loop

\(\)Wenn wir Daten digital über die Luft senden, nutzen wir Verfahren wie die Phase Shift Keying (PSK). Bei BPSK (Binary PSK) wird die Information in einem Phasensprung von 180° codiert. Eine 0 schwingt in eine Richtung (\(\phi = 0^\circ \)), eine 1 schwingt in die entgegengesetzte Richtung (\(\phi = 180^\circ\)).

Die Hürde: Warum die Standard-PLL versagt

Das Kernproblem ist, dass BPSK-Signale oft mit einer trägerunterdrückten Amplitudenmodulation (DSB-SC) gesendet werden. Das bedeutet, dass im Spektrum kein reiner Träger “heraussticht”, auf den eine Standard-PLL einrasten könnte.

Wenn wir eine normale PLL auf das BPSK-Signal anwenden, wird sie zwar versuchen, der Phase zu folgen, aber sie hat ein fundamentales Problem: Sie kann nicht zwischen dem unmodulierten Träger und der Datenmodulation (den 180°-Sprüngen) unterscheiden. Der Phasenregelkreis der Standard-PLL wird ständig gestört und verliert den Lock.

Die Costas Loop wurde von John P. Costas speziell dafür entwickelt, diesen virtuellen Träger zu rekonstruieren, während die Datenmodulation ignoriert wird.

Die Costas Loop verwendet einen speziellen Phasendetektor (Phase Error Detector, PED). Sie nutzt nicht nur einen, sondern zwei Pfade (In-Phase \( I \) und Quadratur \( Q \)), um den Phasenfehler zu bestimmen, der durch die Datenmodulation unbeeinflusst bleibt.

Herleitung für BPSK

Wir gehen davon aus, dass wir ein empfangenes BPSK-Signal \( s(t) \) haben, das bereits ins Basisband verschoben wurde:

$$ s(t) = a(t) \cdot e^{j(\omega t + \phi)} $$

Hierbei ist \( a(t) \) der Datenstrom (\( \pm 1 \)) und \( \phi \) ist ein kleiner Phasenfehler, den wir korrigieren wollen.

Der Costas Loop besteht aus drei Hauptkomponenten (siehe Bild 1):

  1. Complex Mixer (Complex Multiplier): Mischt das Eingangssignal mit dem NCO-Signal.
  2. In-phase/Quadrature separation: Teilt das Signal in \( I \)- und \( Q \)-Zweige auf und filtert sie (LPF).
  3. Phase Error Detector (PED): Berechnet den Phasenfehler.

Nach der Trennung haben wir:

  • In-phase Pfad (I): \( I(t) \approx a(t) \cos(\phi) \)
  • Quadratur Pfad (Q): \( Q(t) \approx a(t) \sin(\phi) \)

Der entscheidende Schritt ist die Multiplikation von \( I \) und \( Q \) im PED:

$$ Error = I(t) \cdot Q(t) = (a(t))^2 \cos(\phi) \sin(\phi) $$

Warum das Datenmodulation eliminiert

Der Datenstrom \( a(t) \) ist entweder \( +1 \) oder \( -1 \). Wenn wir diesen Wert quadrieren, erhalten wir immer 1 :

$$ (a(t))^2 = 1 $$

Unser Phasenfehler-Signal wird somit unabhängig von den Daten:

$$ Error = 1 \cdot \cos(\phi) \sin(\phi) $$

Mit der trigonometrischen Identität \( \sin(2\phi) = 2 \cos(\phi) \sin(\phi) \) erhalten wir:

$$ Error = \frac{1}{2} \sin(2\phi) $$

Für kleine Winkel (\( \phi \approx 0 \)) gilt die Näherung \( \sin(x) \approx x \):

$$ Error \approx \phi $$

Zusammenfassung des PED:

Wir haben ein Fehlersignal erhalten, das direkt proportional zum Phasenfehler ist, aber keine Information mehr über die Datenmodulation enthält. Dieser Fehler wird nun an den Loop-Filter (PI-Regler) weitergegeben, der den NCO steuert – genau wie in der Standard-PLL.

Simulation einer Costas Loop

Zur Demonstration der Funktionsweise enthält der folgende Code ein künstliches BPSK Signal, das demoduliert werden soll.

Python
import numpy as np
import matplotlib.pyplot as plt

# --- 1. Simulation Parameter ---
fs = 10000          # Abtastrate (Hz)
N = 3000            # Anzahl der Samples
t = np.arange(N) / fs

# Signal Parameter
f_carrier = 100     # Frequenz des Trägers (Hz)
phi_offset = 0.6    # Anfänglicher Phasenfehler (in Radiant)
noise_power = 0.05  # Rauschleistung

# Loop Parameter (PI-Regler)
# Diese Werte bestimmen, wie schnell und stabil die Loop einrastet
alpha = 0.05        # Proportionaler Gewinn (Kp)
beta = 0.001        # Integraler Gewinn (Ki)

# --- 2. BPSK Signal generieren ---
# Zufällige Bits (+1, -1)
bits = np.random.choice([-1, 1], N)
# Trägersignal mit Phasenoffset erzeugen
input_signal = bits * np.exp(1j * (2 * np.pi * f_carrier * t + phi_offset))
# Rauschen hinzufügen
input_signal += (np.random.randn(N) + 1j * np.random.randn(N)) * np.sqrt(noise_power/2)

# --- 3. Costas Loop Implementierung ---
nco_phase = 0.0
integrator = 0.0
freq_estimate = 0.0

# Arrays für die Visualisierung
phase_error_log = np.zeros(N)
nco_phase_log = np.zeros(N)
synced_signal = np.zeros(N, dtype=complex)

for i in range(N):
    # 1. Mischer (Phase Correction)
    # Wir drehen das Eingangssignal mit der geschätzten Phase zurück
    sample_in = input_signal[i] * np.exp(-1j * (2 * np.pi * f_carrier * t[i] + nco_phase))
    synced_signal[i] = sample_in
    
    # 2. Phase Error Detector (PED) für BPSK
    # Mathematisch: error = real(y) * imag(y) -> entfernt die 180° Datenmodulation
    error = sample_in.real * sample_in.imag
    phase_error_log[i] = error
    
    # 3. Loop Filter (PI-Regler)
    integrator += beta * error
    adjustment = alpha * error + integrator
    
    # 4. NCO Update
    nco_phase += adjustment

# --- 4. Plotting ---
plt.figure(figsize=(12, 8))
plt.subplots_adjust(hspace=0.4)

# Plot A: Phasenfehler über die Zeit
plt.subplot(2, 2, (1, 2))
plt.plot(phase_error_log, color='red', alpha=0.7)
plt.axhline(0, color='black', linestyle='--')
plt.title("Phasenfehler (PED Output) - Einschwingverhalten")
plt.xlabel("Samples")
plt.ylabel("Error Magnitude")
plt.grid(True)

# Plot B: Konstellation VOR der Synchronisation (Erste 500 Samples)
plt.subplot(2, 2, 3)
plt.scatter(input_signal[:500].real, input_signal[:500].imag, color='gray', alpha=0.5, s=10)
plt.title("Konstellation: Unsynchronisiert\n(Rotiert durch Phasenoffset)")
plt.axis('equal')
plt.grid(True)

# Plot C: Konstellation NACH der Synchronisation (Letzte 500 Samples)
plt.subplot(2, 2, 4)
plt.scatter(synced_signal[-500:].real, synced_signal[-500:].imag, color='green', alpha=0.6, s=10)
plt.title("Konstellation: Synchronisiert (Locked)\n(BPSK Punkte bei +1 und -1)")
plt.axis('equal')
plt.grid(True)

plt.show()

Im oberen Diagramm sieht man zunächst einen großen Ausschlag, bis schließlich der PI Regler das Signal einfängt.

Im linken Diagramm ist das Eingangssignal, ist das Phasenoffset falsch. Das Signal rotiert um den Mittelpunkt. Im rechten Diagrammteil ist dann der Output nach der Costas Loop.

Je komplexer das Modulationsverfahren (QAM-16, QAM-64), desto präziser muss die Costas Loop arbeiten, da die Phasensprünge immer kleiner werden. Während bei BPSK ein “bisschen Rauschen” kaum stört, führt bei QPSK ein kleiner Phasenfehler sofort dazu, dass Symbole in den falschen Quadranten rutschen und Bitfehler entstehen. Im rechten Diagramm kann man sehen, dass sich 4 oder 8 Datenpunkte bei der aktuellen Streuung leicht überlagern können und zu Datenfehlern führen.

Ober sind wir davon ausgegangen, dass wie ein sauberes -1,1 Datensignal haben, das mathematisch durch das Quadrat ausgelöscht werden kann. Praktisch bleibt jedoch ein Restrauschen im Fehlersignal zurück. Deshalb ist die Wahl der Loop Bandwidth so entscheidend: Sie muss groß genug sein, um Frequenzdrifts zu folgen, aber klein genug, um das durch die Datenmodulation und Rauschen verursachte ‘Zittern’ wegzufiltern. In der Realität setzt man daher vor die Costas Loop immer einen AGC (Automatic Gain Control) Block, um das Signal auf eine konstante Amplitude zu normieren.
Moderne Systeme verwenden aber auch entscheidungsgesteuerte PED, die in der Lage sind das wahrscheinlichste Bit zu ermitteln und damit fest auf -1 und 1 zu transformieren.

In GNU Radio übernimmt der Costas Loop Block diese Aufgabe