3 * Copyright 2009,2010 Free Software Foundation, Inc.
5 * This file is part of GNU Radio
7 * GNU Radio is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3, or (at your option)
12 * GNU Radio is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with GNU Radio; see the file COPYING. If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street,
20 * Boston, MA 02110-1301, USA.
24 #ifndef INCLUDED_GR_PFB_CLOCK_SYNC_CCF_H
25 #define INCLUDED_GR_PFB_CLOCK_SYNC_CCF_H
29 class gr_pfb_clock_sync_ccf;
30 typedef boost::shared_ptr<gr_pfb_clock_sync_ccf> gr_pfb_clock_sync_ccf_sptr;
31 gr_pfb_clock_sync_ccf_sptr gr_make_pfb_clock_sync_ccf (double sps, float gain,
32 const std::vector<float> &taps,
33 unsigned int filter_size=32,
35 float max_rate_deviation=1.5);
40 * \class gr_pfb_clock_sync_ccf
42 * \brief Timing synchronizer using polyphase filterbanks
46 * This block performs timing synchronization for PAM signals by minimizing the
47 * derivative of the filtered signal, which in turn maximizes the SNR and
50 * This approach works by setting up two filterbanks; one filterbanke contains the
51 * signal's pulse shaping matched filter (such as a root raised cosine filter),
52 * where each branch of the filterbank contains a different phase of the filter.
53 * The second filterbank contains the derivatives of the filters in the first
54 * filterbank. Thinking of this in the time domain, the first filterbank contains
55 * filters that have a sinc shape to them. We want to align the output signal to
56 * be sampled at exactly the peak of the sinc shape. The derivative of the sinc
57 * contains a zero at the maximum point of the sinc (sinc(0) = 1, sinc(0)' = 0).
58 * Furthermore, the region around the zero point is relatively linear. We make
59 * use of this fact to generate the error signal.
61 * If the signal out of the derivative filters is d_i[n] for the ith filter, and
62 * the output of the matched filter is x_i[n], we calculate the error as:
63 * e[n] = (Re{x_i[n]} * Re{d_i[n]} + Im{x_i[n]} * Im{d_i[n]}) / 2.0
64 * This equation averages the error in the real and imaginary parts. There are two
65 * reasons we multiply by the signal itself. First, if the symbol could be positive
66 * or negative going, but we want the error term to always tell us to go in the
67 * same direction depending on which side of the zero point we are on. The sign of
68 * x_i[n] adjusts the error term to do this. Second, the magnitude of x_i[n] scales
69 * the error term depending on the symbol's amplitude, so larger signals give us
70 * a stronger error term because we have more confidence in that symbol's value.
71 * Using the magnitude of x_i[n] instead of just the sign is especially good for
72 * signals with low SNR.
74 * The error signal, e[n], gives us a value proportional to how far away from the zero
75 * point we are in the derivative signal. We want to drive this value to zero, so we
76 * set up a second order loop. We have two variables for this loop; d_k is the filter
77 * number in the filterbank we are on and d_rate is the rate which we travel through
78 * the filters in the steady state. That is, due to the natural clock differences between
79 * the transmitter and receiver, d_rate represents that difference and would traverse
80 * the filter phase paths to keep the receiver locked. Thinking of this as a second-order
81 * PLL, the d_rate is the frequency and d_k is the phase. So we update d_rate and d_k
82 * using the standard loop equations based on two error signals, d_alpha and d_beta.
83 * We have these two values set based on each other for a critically damped system, so in
84 * the block constructor, we just ask for "gain," which is d_alpha while d_beta is
85 * equal to (gain^2)/4.
87 * The clock sync block needs to know the number of samples per second (sps), because it
88 * only returns a single point representing the sample. The sps can be any positive real
89 * number and does not need to be an integer. The filter taps must also be specified. The
90 * taps are generated by first conceiving of the prototype filter that would be the signal's
91 * matched filter. Then interpolate this by the number of filters in the filterbank. These
92 * are then distributed among all of the filters. So if the prototype filter was to have
93 * 45 taps in it, then each path of the filterbank will also have 45 taps. This is easily
94 * done by building the filter with the sample rate multiplied by the number of filters
97 * The number of filters can also be set and defaults to 32. With 32 filters, you get a
98 * good enough resolution in the phase to produce very small, almost unnoticeable, ISI.
99 * Going to 64 filters can reduce this more, but after that there is very little gained
100 * for the extra complexity.
102 * The initial phase is another settable parameter and refers to the filter path the
103 * algorithm initially looks at (i.e., d_k starts at init_phase). This value defaults
104 * to zero, but it might be useful to start at a different phase offset, such as the mid-
105 * point of the filters.
107 * The final parameter is the max_rate_devitation, which defaults to 1.5. This is how far
108 * we allow d_rate to swing, positive or negative, from 0. Constraining the rate can help
109 * keep the algorithm from walking too far away to lock during times when there is no signal.
113 class gr_pfb_clock_sync_ccf : public gr_block
117 * Build the polyphase filterbank timing synchronizer.
118 * \param sps (double) The number of samples per second in the incoming signal
119 * \param gain (float) The alpha gain of the control loop; beta = (gain^2)/4 by default.
120 * \param taps (vector<int>) The filter taps.
121 * \param filter_size (uint) The number of filters in the filterbank (default = 32).
122 * \param init_phase (float) The initial phase to look at, or which filter to start
123 * with (default = 0).
124 * \param max_rate_deviation (float) Distance from 0 d_rate can get (default = 1.5).
127 friend gr_pfb_clock_sync_ccf_sptr gr_make_pfb_clock_sync_ccf (double sps, float gain,
128 const std::vector<float> &taps,
129 unsigned int filter_size,
131 float max_rate_deviation);
139 std::vector<gr_fir_ccf*> d_filters;
140 std::vector<gr_fir_ccf*> d_diff_filters;
141 std::vector< std::vector<float> > d_taps;
142 std::vector< std::vector<float> > d_dtaps;
149 int d_taps_per_filter;
152 * Build the polyphase filterbank timing synchronizer.
154 gr_pfb_clock_sync_ccf (double sps, float gain,
155 const std::vector<float> &taps,
156 unsigned int filter_size,
158 float max_rate_deviation);
160 void create_diff_taps(const std::vector<float> &newtaps,
161 std::vector<float> &difftaps);
164 ~gr_pfb_clock_sync_ccf ();
167 * Resets the filterbank's filter taps with the new prototype filter
169 void set_taps (const std::vector<float> &taps,
170 std::vector< std::vector<float> > &ourtaps,
171 std::vector<gr_fir_ccf*> &ourfilter);
174 * Returns the taps of the matched filter
176 std::vector<float> channel_taps(int channel);
179 * Returns the taps in the derivative filter
181 std::vector<float> diff_channel_taps(int channel);
184 * Print all of the filterbank taps to screen.
189 * Print all of the filterbank taps of the derivative filter to screen.
191 void print_diff_taps();
194 * Set the gain value alpha for the control loop
196 void set_alpha(float alpha)
202 * Set the gain value beta for the control loop
204 void set_beta(float beta)
210 * Set the maximum deviation from 0 d_rate can have
212 void set_max_rate_deviation(float m)
217 bool check_topology(int ninputs, int noutputs);
219 int general_work (int noutput_items,
220 gr_vector_int &ninput_items,
221 gr_vector_const_void_star &input_items,
222 gr_vector_void_star &output_items);