Design Article Detecting CTCSS Tones With Goertzels Algorithm
Design Article Detecting CTCSS Tones With Goertzels Algorithm
Continuous Tone Coded Squelch System (CTCSS) is a generic term used by the mobile radio industry; the
technology itself accepts desired signals and rejects undesired ones on a voice radio channel. It's also
known under the brand names PL (Private Line) to Motorola users or Channel Guard to GE/Ericsson users.
This mechanism is used by both mobile handheld radios and base stations/repeaters to maximize the use
of a single shared frequency among many users. The system involves the use of a sub-audible tone
injected into the voice-modulation line of the transmitter and a matching tone detector in the receiver
after the FM discriminator. When activated, the presence of a CTCSS tone on a carrier opens the audio-
squelch circuit of the receiver, allowing the signal modulation to pass through to the transmitter and be
heard by the user. Typical implementations use a set of 32 to 50 frequencies in the 67- to 254-Hz range to
signal receivers as to whether or not the received signal should be passed to the user or squelched.
Figure 1 shows a block diagram of a simple software-defined radio (SDR) implementation of a voice radio
receiver supporting this feature. This article is only concerned with the CTCSS detector and filter block
outlined in this diagram. These components take as input the voice signal, the (possibly encoded) CTCSS
signal, and a user-selected CTCSS threshold level and output the filtered voice signal as well as an
indication of which tone, if any, is present in the signal. External squelch logic takes the user-selected tone
and the actual tone present and passes the voice signal if the selected tone and present tone match.
I'll describe a method to detect CTCSS signals in this SDR environment using the modified Goertzel
discrete Fourier transform (DFT) algorithm. We'll examine the algorithm itself and how it has been used by
many others to detect dual-tone multifrequency (DTMF) signaling. I'll show you how the algorithm has
been adapted to this particular application, and I'll present a simple C++ implementation of the algorithm
along with how well it typically performs and how it can be improved.
Frequencies of interest
Various CTCSS standards define from 32 to 50 "sub-audible" frequencies that can be selected to signal
squelch. I've selected the standard Electronic Industries Alliance (EIA) tone set shown in Table 1. 2
Extending the algorithm to an arbitrary tone set is straightforward, and the code samples will support
application defined tone sets.
Sampling rate
The sampling rate of voice signals in SDR applications is usually going to be either 8kHz or 16kHz. The
algorithms presented here provide better detection characteristics at a high sample rate with the obvious
expense of added computational cost. We'll use an 8kHz input signal.
Block size
The block size is the number (N) of samples evaluated during the per-sample processing phase of the
Goertzel algorithm. The value of N and the selected or given sample rate determines the frequency
resolution of the algorithm. Selecting large N gives better frequency discrimination at the expense of more
time to detect a frequency of interest. Our analysis will trade off these two parameters to find the
minimum time required to reliably detect our frequencies of interest given a reference input signal.
Step 1
The optimized Goertzel algorithm has three steps. First, a set of precomputed constants are evaluated for
use during the signal-analysis phase. These constants, one set per frequency of interest fi, are summarized
as follows:
(1)
where:
Contrary to Banks and according to a paper by Jimmy Beard and coauthors, ki is not limited to integers,
which should allow for better frequency discrimination or faster tone detection (or both). 1,3 At this point I
noticed something interesting. Since we're going to allow the DFT coeffcient ki to take on real values, it's
clear from Equation 1 that the cosine coeffcient for each frequency of interest is independent of the block
size N. The simplified equation then becomes:
This has a practical advantage in that we can change N dynamically during any stage of this algorithm
without affecting previously processed samples or recalculating our DFT coeffcients. Later we'll see how
we can use this property to our advantage.
Step 2
Next, for each of the N input samples x (n) processed during the per-sample processing phase, the values
Q0i, Q1i, and Q2i are evaluated as:
Step 3
Finally, for the optimized or modified Goertzel, the relative power at each of the frequencies of interest is
given by:
(4)
To determine if any of the frequencies of interest are present in the input signal, the value of the relative
power (magnitude2) at each frequency can be compared to a threshold, to each other, or a combination of
these. Since we're not interested in the actual power at each frequency but rather the relative power
across all of the frequencies of interest, we won't worry about taking the square root of the mag 2.
protected:
// Override these to change behavior of the detector
virtual void InitializePower();
virtual void AccumulatePower( int tone, int &maxTone);
virtual void EvaluatePower( int maxPowerTone);
The Setcoeffcients() method sets up the basic parameters and coeffcients of the Goertzel algorithm for
the values given for N, the block size, and the sample rate of the voice signal. Details of this method are
shown in Listing 2. This method saves block size and signal sample rate in the class instance and
determines the filter coeffcients for each of the frequencies of interest. Once the coeffcients have been set
the ShowTones() method can be used to display details of the tone set and calculated k and coeffcient
for each CTCSS tone.
Listing 2 SetCoefficient() method
void CTCSSDetector::SetCoefficient (int _N, int _samplerate ) {
N = _N; // save the basic parameters for use during analysis
SampleRate = _samplerate;
// for each of the frequencies (tones) of interest calculate
// k and the associated filter coefficient as per the Goertzel
// algorithm. Note: we are using a real value (as apposed to
// an integer as described in some references. k is retained
// for later display. The tone set is specified in the
// constructor. Notice that the resulting coefficients are
// independent of N.
for ( int j = 0; j < nTones; ++j ) {
k[j] = ((double)N * toneSet[j]) / (double)SampleRate;
Listing 3 shows the heart of the CTCSS tone-detection algorithm, the Analyze() method. As we've said,
this method uses the modified Goertzel algorithm to detect specific frequencies (the CTCSS tones) in a
given input signal. This method can optionally remove the CTCSS tones using a standard high-pass filter.
samplesProcessed += samples_to_process;
return ready;
}
The Analyze() method first determines how many samples from the input are required to be processed to
complete the in-process block of N samples. This method can be called with 1 to N input samples but, as
designed, more than N samples in one invocation is an error. Each sample is normalized to the -1.0 to 1.0
range and then the feedback stage of the algorithm is applied. After each N samples, the feed-forward
stage is applied, which is where the relative power magnitudes are calculated for each CTCSS tone. If after
each N samples more samples remain in the input signal, they're processed by the feedback stage at this
point to start the analysis of the next block of N After the input signal has been processed, a high-pass
filter can be applied to remove the CTCSS tones if any are present. I have found this to be necessary
because, contrary to the description in CTCSS literature, these tones are definitely not sub-audible. The
value returned from this method is an indication whether enough signal has been processed to provide a
tone-detection indication. The actual tone detected, if any, can be determined using the
GetDetectedTone() method, shown in Listing 1.
The Feedback() method, as shown in Listing 4, is a very small piece of code that, as expected, does the
per-sample feedback stage of the Goertzel algorithm.
AccumulatePower( j, MaxPowerIndex
Listing 6 shows the class definition for the tone generator used for testing the performance of the detector.
This class has methods to specify the frequency, sample rate, and amplitude parameters and a method to
add to or replace a given input signal with the desired frequency. This tone generator can produce any
frequency in the frequency range of 30Hz up to half the specified sampling rate, and from 0% to 100% of
full scale. We'll be using this tone generator to test the CTCSS detector algorithm by injecting frequencies
at the specific CTCSS tone frequencies and across the frequency of interest spectrum at small increments
to test the response and effectiveness of the detector.
protected:
int sampleRate;
double scalingConst; // amplitude invariant
double cosignConst; // sample rate and frequency invariant
The first thing I wanted to examine was the relative power across a number of frequencies of interest to
determine how well the algorithm could discriminate between frequencies that are very close (0.1Hz)
together. I chose to look at the frequencies in the low range of CTCSS tones (67 to 100Hz) because these
are the closest together, with a minimum tone spacing of 2.5Hz between tones 2 (71.9kHz) and 3
(74.4kHz), and are therefore likely the hardest to discriminate. The code shown in Listing 7 performs this
analysis. This code segment assumes that a voice signal (voicedata) has either been read in from some
source or initialized to 0. The output of the ShowPower() method is comma-delimited text of the relative
power at each of the defined frequencies of interest. The resulting output file can then be loaded into a
spreadsheet to generate plots, as shown in Figures 3 through 6, or used to perform other analysis.
Listing 7 Tone generator for testing detector
// insert a tone from 66 to 205 Hz in 0.1 Hz increments
// and then output the detected power at each freq of
// interest.
for ( int i = 0; i <= (205 - 66) * 10; ++i ) {
memcpy ( data, voicedata, blockSize * 2);
Performing this same analysis with different block sizes, input-signal amplitude, and in the presence of
voice clearly showed the relationship between the algorithm block size, the input CTCSS tone amplitude,
and the effect of input voice on the ability of the algorithm to detect the correct tone. For instance, when
the amplitude of the input CTCSS tone is 5% of full scale and the block size is kept at 8,000, the relative
power at that frequency is reduced to 200, and a 2% signal results in a max relative power of 80 as shown
in Figure 5. But, when the block size is shortened the power accumulation is spread out significantly,
making it much harder to detect a specific frequency. This spreading effect can be seen in Figure 6.
Figure 6 shows a frequency response for a block size of 4,000 and an input signal of 2%. In this case, the
max relative power at each tone frequency is half that of the 8,000 block size, and it's spread across a
much wider frequency range.
This effect clearly illustrates the tradeoff when choosing a block size. Making the block size large gives us a
much better frequency resolution but at the expense of time (to accumulate the necessary samples to
complete the algorithm). Likewise, adding more signal strength to the inserted CTCSS tone makes it easier
to detect the tone but that's generally outside of the detector's control. My goal here was to reliably detect
tones at less than 5% of full-scale signal strength. This brings us to our second test application, which
verifies the operation of the algorithm with real-time and stored voice input.
PTRACE (2,
" Tone is " << ctcssDetector.GetDetectedTone() <<
" with power: " << ctcssDetector.GetMaxPower() <<
", Total power: " << ctcssDetector.GetTotalPower() );
}
}
The second phase takes the WAV file generated in the first phase and runs it through the detector to verify
the results of the first phase and to enable the user to vary algorithm parameters to see their effect on
detection performance. Also, the voice output of the detector is played back through the sound card. The
code segment implementing these functions is shown in Listing 9. During this phase the user can change
the block size, detection threshold to affect algorithm performance, and also enable or disable the filtering
of the embedded tones to judge the performance of the high-pass filter.
t
Enter Tone Index[-1, 0-31]: 16
Tone is 118.8hz
0:07.163 ctcss Tone is 16 with power: 200, Total power: 200
Command ? a
Enter Tone Amplitude[0.0, 100.0]: 3
Tone is 3% full scale
Command ? a
Enter Tone Amplitude[0.0, 100.0]: 2
Tone is 2% full scale
0:28.699 ctcss Tone is 15 with power: 77.7, Total power: 108
0:29.723 ctcss Tone is 16 with power: 78.6, Total power: 117
0:46.873 ctcss Tone is 27 with power: 166, Total power: 207
0:47.897 ctcss Tone is 16 with power: 81.3, Total power: 148
Command ? a
Enter Tone Amplitude[0.0, 100.0]: 3
Tone is 3% full scale
1:00.677 ctcss Tone is -1 with power: 99.6, Total power: 226
1:01.702 ctcss Tone is 16 with power: 122, Total power: 147
1:07.589 ctcss Tone is 14 with power: 112, Total power: 193
1:08.613 ctcss Tone is 16 with power: 104, Total power: 114
Command ? a
Enter Tone Amplitude[0.0, 100.0]: 4
Tone is 4% full scale
Command ? t
Enter Tone Index[-1, 0-31]: 31
Tone is 203.5hz
1:34.201 ctcss Tone is 31 with power: 160, Total power: 160
x
Exiting.
1:51.135 ctcss Stopped Reading...
1:53.556 ctcss Tone is 16 with power: 200, Total power: 200
2:11.555 ctcss Tone is 15 with power: 77.7, Total power: 108
2:12.555 ctcss Tone is 16 with power: 78.6, Total power: 117
Command ? f
Filtering is OFF
2:29.552 ctcss Tone is 27 with power: 166, Total power: 207
2:30.554 ctcss Tone is 16 with power: 81.3, Total power: 148
2:40.553 ctcss Tone is -1 with power: 99.6, Total power: 226
2:41.552 ctcss Tone is 16 with power: 122, Total power: 147
2:47.553 ctcss Tone is 14 with power: 112, Total power: 193
2:48.555 ctcss Tone is 16 with power: 104, Total power: 114
3:09.553 ctcss Tone is 31 with power: 160, Total power: 160
3:27.552 ctcss Completed reading
Improvements
My implementation of the CTCSSDetector class provided a very simple default power evaluation method
that you could easily extend to provide much better detection results. As it was, my simple threshold-
based evaluator could reliably detect CTCSS tones within my 1-second goal, even when presented with a
very noisy, over-driven input signal. An actual real-world implementation would likely override the power-
related methods to accumulate and evaluate the power at each frequency specific to the intended
application. This tailored processing could use heuristics, pattern matching, or various other techniques to
evaluate the accumulated relative powers. It's also possible these methods could modify parameters of the
detector, such as the block size N, to adapt to unexpected or changing conditions.
Gene Small is currently a system/software engineer with Harris Corporation in Melbourne, FL. Gene has a
BS in computer science from North Carolina State University and an MS in computer engineering from
Florida Institute of Technology. He has spent the past 10 years working on various embedded military
radio and related system applications. He can be reached at [email protected].
Endnotes:
1. Banks, Kevin. "The Goertzel Algorithm." Embedded Systems Programming, September 2002, p.34.
2. EIA CTCSS Tone Set: www.directcon.net/pacres/ctcss.htm
3. Beard, Jimmy, Steven Given, and Brian Young. "A Discrete Fourier Transformation Based Digital
DTMF Detection Algorithm," Mississippi State DSP Conference, Fall 1995.
4. Fisher, Terry. "Interactive Filter Design": http://www-users.cs.york.ac.uk/~fisher/mkfilter/