Squelch-Blöcke im Vergleich

Power Squelch, Simple Squelch, CTCSS Squelch und Standard Squelch schalten alle bei fehlendem Nutzsignal stumm — messen dafür aber vier grundlegend verschiedene Dinge.

„Squelch" (Rauschsperre) blendet die Ausgabe stumm, solange kein auswertbares Nutzsignal anliegt. GRC 3.10 bietet dafür vier Blöcke, die auf den ersten Blick ähnlich aussehen, aber unterschiedliche Messgrößen zur Entscheidung heranziehen — die Wahl des richtigen Blocks hängt davon ab, was überhaupt als „Signal vorhanden" gelten soll.

1. Überblick: vier verschiedene Messprinzipien

Block Misst Datentyp Typische Einsatzstelle
Power Squelch Signalleistung in dB gegen eine feste Schwelle Complex oder Float vor der Demodulation, auf dem HF/IQ-Signal
Simple Squelch dasselbe wie Power Squelch, ohne Ramp/Gate-Optionen Complex vor der Demodulation, auf dem HF/IQ-Signal
CTCSS Squelch Vorhandensein eines bestimmten Subtons (67–254 Hz) Float nach der Demodulation, auf dem NF-Signal
Standard Squelch Verhältnis von Rauschenergie in einem hohen zu einem tiefen Frequenzband Float nach der FM-Demodulation, auf dem NF-Signal

Power Squelch und Simple Squelch reagieren rein auf Pegel — sie unterscheiden nicht, ob eine hohe Leistung von echtem Nutzsignal oder von einem starken Störer stammt. CTCSS Squelch und Standard Squelch werten dagegen ein Merkmal des demodulierten Audios aus und funktionieren deshalb unabhängig vom absoluten Pegel (der durch eine vorgeschaltete AGC ohnehin meist schon normiert ist).

2. Power Squelch

Der Power Squelch-Block (analog.pwr_squelch_cc/_ff) berechnet die mit einem Einpol-IIR-Filter geglättete Signalleistung und vergleicht sie in dB gegen eine feste Schwelle. Er ist die allgemeinste der vier Varianten: wahlweise für komplexe oder reelle Signale, mit einstellbarer Glättungszeit sowie optionalem sanftem Ein-/Ausblenden statt eines harten Schnitts.

Power Squelch Block

Parameter Typ/Einheit Bedeutung Dimensionierungshinweis
type Enum Complex oder Float muss zum Datentyp des Eingangssignals passen
threshold (Threshold) dB Schaltschwelle der Signalleistung siehe verifizierte Messung unten — Schwelle direkt in 10·log10(mittlere Leistung)
alpha (Alpha) Glättungsfaktor des Einpol-IIR-Leistungsschätzers größer = schnellere Reaktion, aber empfindlicher gegen kurze Ausreißer; Default 1e-4 reagiert sehr träge
ramp (Ramp) Samples Länge der sinusförmigen Ein-/Ausblendrampe 0 = harter Schnitt; größere Werte vermeiden hörbare Knackser beim Umschalten
gate (Gate) Bool Ja = blockierte Samples werden nicht ausgegeben (Stream wird kürzer); Nein = blockierte Samples werden als Nullen ausgegeben für Echtzeit-Ausgabe (Audio-Sink, feste Rate) immer „Nein" — siehe verifiziertes Beispiel
Verifiziert (GNU Radio 3.10.12.0, tatsächlich ausgeführt): Drei oft missverstandene Verhaltensweisen wurden real gemessen statt nur aus der Doku übernommen.
from gnuradio import gr, blocks, analog
import numpy as np

n = 1000
sig = np.concatenate([0.01*np.ones(n), 1.0*np.ones(n)]).astype(np.complex64)

def run(threshold, alpha, ramp, gate):
    tb = gr.top_block()
    src = blocks.vector_source_c(sig.tolist(), False)
    sq = analog.pwr_squelch_cc(threshold, alpha, ramp, gate)
    snk = blocks.vector_sink_c()
    tb.connect(src, sq, snk)
    tb.run()
    return np.array(snk.data())

# 1) Schwelle: amplitude=0.1 entspricht exakt 20*log10(0.1) = -20 dB
out = run(-20, 0.5, 0, False)
print("amp=0.01 (unter Schwelle) -> Ausgabe am Ende von Segment 1:", round(abs(out[n-1]), 4))
print("amp=1.00 (über Schwelle)  -> Ausgabe am Ende von Segment 2:", round(abs(out[-1]), 4))

# 2) Gate=False vs. Gate=True: Streamlaenge
out_f = run(-20, 0.5, 0, False)
out_t = run(-20, 0.5, 0, True)
print(f"Gate=Nein: Eingang {len(sig)} Samples -> Ausgang {len(out_f)} Samples")
print(f"Gate=Ja:   Eingang {len(sig)} Samples -> Ausgang {len(out_t)} Samples")

# 3) Ramp: Uebergang bei Gate=False, Ramp=0 vs. Ramp=200
for ramp in (0, 200):
    out_r = np.abs(run(-20, 0.5, ramp, False))
    window = out_r[n-5:n+250]
    voll = np.nonzero(np.isclose(window, 1.0, atol=0.01))[0][0]
    print(f"Ramp={ramp}: voller Pegel erreicht nach {voll} Samples ab Uebergang")
from gnuradio import gr, blocks, analog
import numpy as np

n = 1000
sig = np.concatenate([0.01*np.ones(n), 1.0*np.ones(n)]).astype(np.complex64)

def run(threshold, alpha, ramp, gate):
    tb = gr.top_block()
    src = blocks.vector_source_c(sig.tolist(), False)
    sq = analog.pwr_squelch_cc(threshold, alpha, ramp, gate)
    snk = blocks.vector_sink_c()
    tb.connect(src, sq, snk)
    tb.run()
    return np.array(snk.data())

# 1) Schwelle: amplitude=0.1 entspricht exakt 20*log10(0.1) = -20 dB
out = run(-20, 0.5, 0, False)
print("amp=0.01 (unter Schwelle) -> Ausgabe am Ende von Segment 1:", round(abs(out[n-1]), 4))
print("amp=1.00 (über Schwelle)  -> Ausgabe am Ende von Segment 2:", round(abs(out[-1]), 4))

# 2) Gate=False vs. Gate=True: Streamlaenge
out_f = run(-20, 0.5, 0, False)
out_t = run(-20, 0.5, 0, True)
print(f"Gate=Nein: Eingang {len(sig)} Samples -> Ausgang {len(out_f)} Samples")
print(f"Gate=Ja:   Eingang {len(sig)} Samples -> Ausgang {len(out_t)} Samples")

# 3) Ramp: Uebergang bei Gate=False, Ramp=0 vs. Ramp=200
for ramp in (0, 200):
    out_r = np.abs(run(-20, 0.5, ramp, False))
    window = out_r[n-5:n+250]
    voll = np.nonzero(np.isclose(window, 1.0, atol=0.01))[0][0]
    print(f"Ramp={ramp}: voller Pegel erreicht nach {voll} Samples ab Uebergang")

Ausgabe:

amp=0.01 (unter Schwelle) -> Ausgabe am Ende von Segment 1: 0.0
amp=1.00 (über Schwelle)  -> Ausgabe am Ende von Segment 2: 1.0
Gate=Nein: Eingang 2000 Samples -> Ausgang 2000 Samples
Gate=Ja:   Eingang 2000 Samples -> Ausgang 1000 Samples
Ramp=0: voller Pegel erreicht nach 5 Samples ab Uebergang
Ramp=200: voller Pegel erreicht nach 193 Samples ab Uebergang
amp=0.01 (unter Schwelle) -> Ausgabe am Ende von Segment 1: 0.0
amp=1.00 (über Schwelle)  -> Ausgabe am Ende von Segment 2: 1.0
Gate=Nein: Eingang 2000 Samples -> Ausgang 2000 Samples
Gate=Ja:   Eingang 2000 Samples -> Ausgang 1000 Samples
Ramp=0: voller Pegel erreicht nach 5 Samples ab Uebergang
Ramp=200: voller Pegel erreicht nach 193 Samples ab Uebergang

Warum diese Werte: Die Schwelle greift exakt bei der konfigurierten dB-Zahl — 0,1 Amplitude entspricht rechnerisch genau −20 dB, und genau dort kippt das Verhalten von blockiert zu durchgelassen. Bei Gate=Nein bleibt die Ausgangslänge mit der Eingangslänge identisch (blockierte Abschnitte werden zu Nullen), bei Gate=Ja verschwinden die blockierten Samples komplett — der Stream wird kürzer. Das ist entscheidend für alles, was eine feste Samplerate erwartet (Audio-Sink, WAV-Sink): Dort muss Gate=Nein stehen, sonst gerät die Wiedergabe aus dem Takt. Mit Ramp=0 ist der Übergang instantan (0 Samples), mit Ramp=200 zieht er sich über rund 190 Samples hin — nah an den konfigurierten 200, die kleine Differenz kommt von der Einschwingzeit des Leistungsschätzers selbst.

3. Simple Squelch

Der Simple Squelch-Block (analog.simple_squelch_cc) arbeitet nach demselben Prinzip wie Power Squelch — Leistungsschätzung per Einpol-IIR-Filter, Vergleich gegen eine dB-Schwelle —, bietet aber nur die beiden Kernparameter und keine Ramp-/Gate-Optionen. Er ist ausschließlich für komplexe Signale verfügbar.

Simple Squelch Block

Parameter Typ/Einheit Bedeutung
threshold (Threshold) dB Schaltschwelle der Signalleistung
alpha (Alpha) Glättungsfaktor des Leistungsschätzers
Verifiziert (GNU Radio 3.10.12.0, tatsächlich ausgeführt): Simple Squelch ist keine eigenständige Implementierung, sondern verhält sich bei gleichen Parametern bitidentisch zu Power Squelch mit `Gate=Nein, Ramp=0`:
from gnuradio import gr, blocks, analog
import numpy as np

n = 1000
sig = np.concatenate([0.01*np.ones(n), 1.0*np.ones(n)]).astype(np.complex64)

def run(block):
    tb = gr.top_block()
    src = blocks.vector_source_c(sig.tolist(), False)
    snk = blocks.vector_sink_c()
    tb.connect(src, block, snk)
    tb.run()
    return np.array(snk.data())

out_simple = run(analog.simple_squelch_cc(-20, 0.5))
out_power  = run(analog.pwr_squelch_cc(-20, 0.5, 0, False))

print("identisch:", (out_simple == out_power).all())
print("maximale Abweichung:", float(np.max(np.abs(out_simple - out_power))))
from gnuradio import gr, blocks, analog
import numpy as np

n = 1000
sig = np.concatenate([0.01*np.ones(n), 1.0*np.ones(n)]).astype(np.complex64)

def run(block):
    tb = gr.top_block()
    src = blocks.vector_source_c(sig.tolist(), False)
    snk = blocks.vector_sink_c()
    tb.connect(src, block, snk)
    tb.run()
    return np.array(snk.data())

out_simple = run(analog.simple_squelch_cc(-20, 0.5))
out_power  = run(analog.pwr_squelch_cc(-20, 0.5, 0, False))

print("identisch:", (out_simple == out_power).all())
print("maximale Abweichung:", float(np.max(np.abs(out_simple - out_power))))

Ausgabe:

identisch: True
maximale Abweichung: 0.0
identisch: True
maximale Abweichung: 0.0

Warum diese Werte: Simple Squelch ist damit im Kern eine reduzierte Bedienoberfläche für exakt denselben Leistungsdetektor — sinnvoll, wenn Ramp und Gate ohnehin nie gebraucht werden und die zwei zusätzlichen Parameter im Flowgraph nur ablenken würden.

4. CTCSS Squelch

Der CTCSS Squelch-Block (analog.ctcss_squelch_ff) prüft nicht die Gesamtleistung, sondern gezielt, ob ein bestimmter Subton (Continuous Tone-Coded Squelch System, 67–254 Hz, unterhalb der normalen Sprachwahrnehmung) im Signal vorhanden ist — das klassische Verfahren analoger FM-Betriebsfunk- und Relaisstellen, um nur Aussendungen der eigenen Gruppe durchzulassen und andere Stationen auf derselben Frequenz auszublenden. Er arbeitet auf dem bereits demodulierten NF-Signal (Float), nicht auf IQ-Daten.

CTCSS Squelch Block

Parameter Typ/Einheit Bedeutung Dimensionierungshinweis
rate (Sampling Rate) Hz Abtastrate des NF-Signals durch die vorgeschaltete Quelle vorgegeben
freq (Tone Frequency) Hz zu erkennende CTCSS-Tonfrequenz eine der genormten CTCSS-Tonfrequenzen (z. B. 67,0; 100,0; 131,8 Hz), je nach Absprache mit der Gegenstelle/Relaisstelle
level (Level) linear minimale Amplitude des Tons zum Öffnen an das erwartete NF-Pegelverhältnis Ton zu Sprache anpassen
len (Length) Samples Länge des internen Tondetektor-Fensters 0 = automatisch anhand von rate/freq bestimmt
ramp (Ramp) Samples Ein-/Ausblendrampe wie bei Power Squelch
gate (Gate) Bool Samples verwerfen statt Nullen ausgeben wie bei Power Squelch
Verifiziert (GNU Radio 3.10.12.0, tatsächlich ausgeführt): Zwei NF-Signale mit Rauschen, eines mit dem konfigurierten 100-Hz-Subton, eines mit einem abweichenden 150-Hz-Ton:
from gnuradio import gr, blocks, analog
import numpy as np

rate, tone_freq, n = 8000, 100.0, 16000
t = np.arange(n) / rate
sig_match    = (0.05*np.sin(2*np.pi*tone_freq*t) + 0.02*np.random.default_rng(1).normal(size=n)).astype(np.float32)
sig_mismatch = (0.05*np.sin(2*np.pi*150.0*t)      + 0.02*np.random.default_rng(1).normal(size=n)).astype(np.float32)

for name, sig in [("100 Hz (passender Ton)", sig_match), ("150 Hz (falscher Ton)", sig_mismatch)]:
    tb = gr.top_block()
    src = blocks.vector_source_f(sig.tolist(), False)
    sq = analog.ctcss_squelch_ff(rate, tone_freq, 0.01, 0, 0, False)
    snk = blocks.vector_sink_f()
    tb.connect(src, sq, snk)
    tb.run()
    out = np.array(snk.data())
    tail_rms = float(np.sqrt(np.mean(out[-500:]**2)))
    print(f"{name}: RMS der letzten 500 Ausgabesamples = {round(tail_rms, 4)}")
from gnuradio import gr, blocks, analog
import numpy as np

rate, tone_freq, n = 8000, 100.0, 16000
t = np.arange(n) / rate
sig_match    = (0.05*np.sin(2*np.pi*tone_freq*t) + 0.02*np.random.default_rng(1).normal(size=n)).astype(np.float32)
sig_mismatch = (0.05*np.sin(2*np.pi*150.0*t)      + 0.02*np.random.default_rng(1).normal(size=n)).astype(np.float32)

for name, sig in [("100 Hz (passender Ton)", sig_match), ("150 Hz (falscher Ton)", sig_mismatch)]:
    tb = gr.top_block()
    src = blocks.vector_source_f(sig.tolist(), False)
    sq = analog.ctcss_squelch_ff(rate, tone_freq, 0.01, 0, 0, False)
    snk = blocks.vector_sink_f()
    tb.connect(src, sq, snk)
    tb.run()
    out = np.array(snk.data())
    tail_rms = float(np.sqrt(np.mean(out[-500:]**2)))
    print(f"{name}: RMS der letzten 500 Ausgabesamples = {round(tail_rms, 4)}")

Ausgabe:

100 Hz (passender Ton): RMS der letzten 500 Ausgabesamples = 0.0419
150 Hz (falscher Ton): RMS der letzten 500 Ausgabesamples = 0.0
100 Hz (passender Ton): RMS der letzten 500 Ausgabesamples = 0.0419
150 Hz (falscher Ton): RMS der letzten 500 Ausgabesamples = 0.0

Warum diese Werte: Bei passendem Subton kommt praktisch der volle Signalpegel durch (RMS≈0,042, nahe am Eingangspegel), bei abweichender Tonfrequenz bleibt die Ausgabe exakt bei Null — unabhängig davon, dass beide Testsignale dieselbe Rauschleistung und dieselbe Tonamplitude tragen. Der Block reagiert also wirklich auf die Frequenz des Subtons, nicht auf dessen Pegel.

5. Standard Squelch

Der Standard Squelch-Block (analog.standard_squelch, ein Hierarchie-Block aus mehreren primitiven Blöcken) ist speziell für klassische FM-Sprachempfänger gebaut: Er filtert das demodulierte NF-Signal parallel durch zwei IIR-Bandpässe (einen im Sprachband, einen darüber) und bildet daraus ein Verhältnis der beiden gemessenen Energien. Bei einem echten FM-Träger ist dieses Verhältnis niedrig (die Energie konzentriert sich im Sprachband), ohne Träger dominiert das für FM-Demodulatoren typische, breitbandig ansteigende Rauschen den oberen Bereich. Der entscheidende Unterschied zu Power/Simple Squelch: Die Entscheidung hängt nicht vom absoluten Pegel ab, sondern von der Form des Spektrums — eine laute Rauschquelle wird trotzdem gesperrt, ein leises, aber sauberes Sprachsignal kommt trotzdem durch.

Standard Squelch Block

Parameter Typ/Einheit Bedeutung Dimensionierungshinweis
audio_rate (Audio Rate) Hz Abtastrate des NF-Eingangssignals bestimmt die Zeitkonstante der internen Glättung (100 ms fest einprogrammiert), nicht die Filterkoeffizienten selbst
threshold (Threshold) 0,0 … 1,0 Schaltschwelle für das Energieverhältnis, keine dB-Angabe kleinere Werte = strenger (nur sehr sauberes Sprachband öffnet), größere Werte = toleranter
Verifiziert (GNU Radio 3.10.12.0, tatsächlich ausgeführt): weißes Rauschen gegen einen sauberen 300-Hz-Ton (stellvertretend für Sprachbandinhalt), bei identischer Amplitude und identischer Schwelle:
from gnuradio import gr, blocks, analog
import numpy as np

audio_rate, n = 8000.0, 40000
rng = np.random.default_rng(2)
noise = (0.3*rng.normal(size=n)).astype(np.float32)
tone  = (0.3*np.sin(2*np.pi*300*np.arange(n)/audio_rate)).astype(np.float32)

for name, sig in [("weißes Rauschen", noise), ("300-Hz-Ton (Sprachband)", tone)]:
    tb = gr.top_block()
    src = blocks.vector_source_f(sig.tolist(), False)
    sq = analog.standard_squelch(audio_rate)
    sq.set_threshold(0.5)
    snk = blocks.vector_sink_f()
    tb.connect(src, sq, snk)
    tb.run()
    out = np.array(snk.data())
    tail_rms = float(np.sqrt(np.mean(out[-2000:]**2)))
    print(f"{name}: RMS der letzten 2000 Ausgabesamples = {round(tail_rms, 5)}  (Eingangs-RMS: {round(float(np.sqrt(np.mean(sig**2))), 5)})")
from gnuradio import gr, blocks, analog
import numpy as np

audio_rate, n = 8000.0, 40000
rng = np.random.default_rng(2)
noise = (0.3*rng.normal(size=n)).astype(np.float32)
tone  = (0.3*np.sin(2*np.pi*300*np.arange(n)/audio_rate)).astype(np.float32)

for name, sig in [("weißes Rauschen", noise), ("300-Hz-Ton (Sprachband)", tone)]:
    tb = gr.top_block()
    src = blocks.vector_source_f(sig.tolist(), False)
    sq = analog.standard_squelch(audio_rate)
    sq.set_threshold(0.5)
    snk = blocks.vector_sink_f()
    tb.connect(src, sq, snk)
    tb.run()
    out = np.array(snk.data())
    tail_rms = float(np.sqrt(np.mean(out[-2000:]**2)))
    print(f"{name}: RMS der letzten 2000 Ausgabesamples = {round(tail_rms, 5)}  (Eingangs-RMS: {round(float(np.sqrt(np.mean(sig**2))), 5)})")

Ausgabe:

weißes Rauschen: RMS der letzten 2000 Ausgabesamples = 0.04292  (Eingangs-RMS: 0.30024)
300-Hz-Ton (Sprachband): RMS der letzten 2000 Ausgabesamples = 0.21213  (Eingangs-RMS: 0.21213)
weißes Rauschen: RMS der letzten 2000 Ausgabesamples = 0.04292  (Eingangs-RMS: 0.30024)
300-Hz-Ton (Sprachband): RMS der letzten 2000 Ausgabesamples = 0.21213  (Eingangs-RMS: 0.21213)

Warum diese Werte: Der Ton kommt bei identischer Schwelle praktisch unverändert durch (Ausgangs-RMS = Eingangs-RMS), während das gleich laute Rauschen auf rund 14 % seines Eingangspegels abgeschwächt wird. Bei identischer Amplitude entscheidet damit tatsächlich die spektrale Form, nicht die Lautstärke — genau das, was ein reiner Power Squelch an dieser Stelle nicht leisten könnte.

6. Welchen Block wann einsetzen?

  • Power Squelch: Standardwahl, wenn ein einfacher Pegel-Schwellwert genügt und Ramp/Gate gebraucht werden — etwa direkt hinter einem Bandpassfilter, um bei fehlendem Träger stummzuschalten. Genau dieses Muster nutzt der USB-Sprachempfänger für 144,300 MHz: Der Power Squelch sitzt dort bewusst vor der AGC, weil die AGC sonst auch reines Rauschen auf einen konstanten Referenzpegel hochregeln und die Schwelle damit wirkungslos machen würde.
  • Simple Squelch: identisch zu Power Squelch, nur wenn Ramp/Gate von vornherein ausgeschlossen sind und die reduzierte Parameterliste den Flowgraph übersichtlicher macht.
  • CTCSS Squelch: wenn mehrere Stationen dieselbe Frequenz nutzen und nur Aussendungen mit einem bestimmten, bekannten Subton durchgelassen werden sollen (klassischer Anwendungsfall: Relaisfunkstellen, Betriebsfunk).
  • Standard Squelch: wenn — anders als bei den obigen Projekten mit SSB — ein FM-demoduliertes Signal vorliegt und die Unterscheidung „Träger vorhanden oder nicht" nicht zuverlässig über den reinen Pegel getroffen werden kann, weil AGC oder unterschiedliche Signalstärken den Pegel bereits verwischt haben.

7. Ein- und Ausgänge

Block Eingang Ausgang
Power Squelch Complex oder Float (je nach type) identisch zum Eingang
Simple Squelch Complex Complex
CTCSS Squelch Float Float
Standard Squelch Float Float

8. Download

Download: Alle vier Verifikationsskripte dieses Artikels als ZIP — squelch-bloecke.zip herunterladen, entpacken, dann:
python3 test_power_squelch.py
python3 test_simple_squelch.py
python3 test_ctcss_squelch.py
python3 test_standard_squelch.py
python3 test_power_squelch.py
python3 test_simple_squelch.py
python3 test_ctcss_squelch.py
python3 test_standard_squelch.py