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.

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

| Parameter | Typ/Einheit | Bedeutung |
|---|---|---|
threshold (Threshold) |
dB | Schaltschwelle der Signalleistung |
alpha (Alpha) |
— | Glättungsfaktor des Leistungsschätzers |
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.

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

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