GNU Radio 3.6.3 C++ API
digital_pfb_clock_sync_ccf.h
Go to the documentation of this file.
1 /* -*- c++ -*- */
2 /*
3  * Copyright 2009,2010,2012 Free Software Foundation, Inc.
4  *
5  * This file is part of GNU Radio
6  *
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)
10  * any later version.
11  *
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.
16  *
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.
21  */
22 
23 
24 #ifndef INCLUDED_DIGITAL_PFB_CLOCK_SYNC_CCF_H
25 #define INCLUDED_DIGITAL_PFB_CLOCK_SYNC_CCF_H
26 
27 #include <digital_api.h>
28 #include <gr_block.h>
29 
33 digital_make_pfb_clock_sync_ccf(double sps, float loop_bw,
34  const std::vector<float> &taps,
35  unsigned int filter_size=32,
36  float init_phase=0,
37  float max_rate_deviation=1.5,
38  int osps=1);
39 
40 class gr_fir_ccf;
41 
42 /*!
43  * \class digital_pfb_clock_sync_ccf
44  *
45  * \brief Timing synchronizer using polyphase filterbanks
46  *
47  * \ingroup filter_blk
48  * \ingroup pfb_blk
49  *
50  * This block performs timing synchronization for PAM signals by
51  * minimizing the derivative of the filtered signal, which in turn
52  * maximizes the SNR and minimizes ISI.
53  *
54  * This approach works by setting up two filterbanks; one filterbank
55  * contains the signal's pulse shaping matched filter (such as a root
56  * raised cosine filter), where each branch of the filterbank contains
57  * a different phase of the filter. The second filterbank contains
58  * the derivatives of the filters in the first filterbank. Thinking of
59  * this in the time domain, the first filterbank contains filters that
60  * have a sinc shape to them. We want to align the output signal to be
61  * sampled at exactly the peak of the sinc shape. The derivative of
62  * the sinc contains a zero at the maximum point of the sinc (sinc(0)
63  * = 1, sinc(0)' = 0). Furthermore, the region around the zero point
64  * is relatively linear. We make use of this fact to generate the
65  * error signal.
66  *
67  * If the signal out of the derivative filters is d_i[n] for the ith
68  * filter, and the output of the matched filter is x_i[n], we
69  * calculate the error as: e[n] = (Re{x_i[n]} * Re{d_i[n]} +
70  * Im{x_i[n]} * Im{d_i[n]}) / 2.0 This equation averages the error in
71  * the real and imaginary parts. There are two reasons we multiply by
72  * the signal itself. First, if the symbol could be positive or
73  * negative going, but we want the error term to always tell us to go
74  * in the same direction depending on which side of the zero point we
75  * are on. The sign of x_i[n] adjusts the error term to do
76  * this. Second, the magnitude of x_i[n] scales the error term
77  * depending on the symbol's amplitude, so larger signals give us a
78  * stronger error term because we have more confidence in that
79  * symbol's value. Using the magnitude of x_i[n] instead of just the
80  * sign is especially good for signals with low SNR.
81  *
82  * The error signal, e[n], gives us a value proportional to how far
83  * away from the zero point we are in the derivative signal. We want
84  * to drive this value to zero, so we set up a second order loop. We
85  * have two variables for this loop; d_k is the filter number in the
86  * filterbank we are on and d_rate is the rate which we travel through
87  * the filters in the steady state. That is, due to the natural clock
88  * differences between the transmitter and receiver, d_rate represents
89  * that difference and would traverse the filter phase paths to keep
90  * the receiver locked. Thinking of this as a second-order PLL, the
91  * d_rate is the frequency and d_k is the phase. So we update d_rate
92  * and d_k using the standard loop equations based on two error
93  * signals, d_alpha and d_beta. We have these two values set based on
94  * each other for a critically damped system, so in the block
95  * constructor, we just ask for "gain," which is d_alpha while d_beta
96  * is equal to (gain^2)/4.
97  *
98  * The block's parameters are:
99  *
100  * \li \p sps: The clock sync block needs to know the number of samples per
101  * symbol, because it defaults to return a single point representing
102  * the symbol. The sps can be any positive real number and does not
103  * need to be an integer.
104  *
105  * \li \p loop_bw: The loop bandwidth is used to set the gain of the
106  * inner control loop (see:
107  * http://gnuradio.squarespace.com/blog/2011/8/13/control-loop-gain-values.html).
108  * This should be set small (a value of around 2pi/100 is suggested in
109  * that blog post as the step size for the number of radians around
110  * the unit circle to move relative to the error).
111  *
112  * \li \p taps: One of the most important parameters for this block is
113  * the taps of the filter. One of the benefits of this algorithm is
114  * that you can put the matched filter in here as the taps, so you get
115  * both the matched filter and sample timing correction in one go. So
116  * create your normal matched filter. For a typical digital
117  * modulation, this is a root raised cosine filter. The number of taps
118  * of this filter is based on how long you expect the channel to be;
119  * that is, how many symbols do you want to combine to get the current
120  * symbols energy back (there's probably a better way of stating
121  * that). It's usually 5 to 10 or so. That gives you your filter, but
122  * now we need to think about it as a filter with different phase
123  * profiles in each filter. So take this number of taps and multiply
124  * it by the number of filters. This is the number you would use to
125  * create your prototype filter. When you use this in the PFB
126  * filerbank, it segments these taps into the filterbanks in such a
127  * way that each bank now represents the filter at different phases,
128  * equally spaced at 2pi/N, where N is the number of filters.
129  *
130  * \li \p filter_size (default=32): The number of filters can also be
131  * set and defaults to 32. With 32 filters, you get a good enough
132  * resolution in the phase to produce very small, almost unnoticeable,
133  * ISI. Going to 64 filters can reduce this more, but after that
134  * there is very little gained for the extra complexity.
135  *
136  * \li \p init_phase (default=0): The initial phase is another
137  * settable parameter and refers to the filter path the algorithm
138  * initially looks at (i.e., d_k starts at init_phase). This value
139  * defaults to zero, but it might be useful to start at a different
140  * phase offset, such as the mid-point of the filters.
141  *
142  * \li \p max_rate_deviation (default=1.5): The next parameter is the
143  * max_rate_devitation, which defaults to 1.5. This is how far we
144  * allow d_rate to swing, positive or negative, from 0. Constraining
145  * the rate can help keep the algorithm from walking too far away to
146  * lock during times when there is no signal.
147  *
148  * \li \p osps (default=1): The osps is the number of output samples per symbol. By default,
149  * the algorithm produces 1 sample per symbol, sampled at the exact
150  * sample value. This osps value was added to better work with
151  * equalizers, which do a better job of modeling the channel if they
152  * have 2 samps/sym.
153  */
154 
156 {
157  private:
158  /*!
159  * Build the polyphase filterbank timing synchronizer.
160  * \param sps (double) The number of samples per symbol in the incoming signal
161  * \param loop_bw (float) The bandwidth of the control loop; set's alpha and beta.
162  * \param taps (vector<int>) The filter taps.
163  * \param filter_size (uint) The number of filters in the filterbank (default = 32).
164  * \param init_phase (float) The initial phase to look at, or which filter to start
165  * with (default = 0).
166  * \param max_rate_deviation (float) Distance from 0 d_rate can get (default = 1.5).
167  * \param osps (int) The number of output samples per symbol (default=1).
168  *
169  */
171  digital_make_pfb_clock_sync_ccf(double sps, float loop_bw,
172  const std::vector<float> &taps,
173  unsigned int filter_size,
174  float init_phase,
175  float max_rate_deviation,
176  int osps);
177 
178  bool d_updated;
179  double d_sps;
180  double d_sample_num;
181  float d_loop_bw;
182  float d_damping;
183  float d_alpha;
184  float d_beta;
185 
186  int d_nfilters;
187  int d_taps_per_filter;
188  std::vector<gr_fir_ccf*> d_filters;
189  std::vector<gr_fir_ccf*> d_diff_filters;
190  std::vector< std::vector<float> > d_taps;
191  std::vector< std::vector<float> > d_dtaps;
192 
193  float d_k;
194  float d_rate;
195  float d_rate_i;
196  float d_rate_f;
197  float d_max_dev;
198  int d_filtnum;
199  int d_osps;
200  float d_error;
201  int d_out_idx;
202 
203  /*!
204  * Build the polyphase filterbank timing synchronizer.
205  */
206  digital_pfb_clock_sync_ccf(double sps, float loop_bw,
207  const std::vector<float> &taps,
208  unsigned int filter_size,
209  float init_phase,
210  float max_rate_deviation,
211  int osps);
212 
213  void create_diff_taps(const std::vector<float> &newtaps,
214  std::vector<float> &difftaps);
215 
216 public:
218 
219  /*! \brief update the system gains from omega and eta
220  *
221  * This function updates the system gains based on the loop
222  * bandwidth and damping factor of the system.
223  * These two factors can be set separately through their own
224  * set functions.
225  */
226  void update_gains();
227 
228  /*!
229  * Resets the filterbank's filter taps with the new prototype filter
230  */
231  void set_taps(const std::vector<float> &taps,
232  std::vector< std::vector<float> > &ourtaps,
233  std::vector<gr_fir_ccf*> &ourfilter);
234 
235  /*!
236  * Returns all of the taps of the matched filter
237  */
238  std::vector< std::vector<float> > get_taps();
239 
240  /*!
241  * Returns all of the taps of the derivative filter
242  */
243  std::vector< std::vector<float> > get_diff_taps();
244 
245  /*!
246  * Returns the taps of the matched filter for a particular channel
247  */
248  std::vector<float> get_channel_taps(int channel);
249 
250  /*!
251  * Returns the taps in the derivative filter for a particular channel
252  */
253  std::vector<float> get_diff_channel_taps(int channel);
254 
255  /*!
256  * Return the taps as a formatted string for printing
257  */
258  std::string get_taps_as_string();
259 
260  /*!
261  * Return the derivative filter taps as a formatted string for printing
262  */
263  std::string get_diff_taps_as_string();
264 
265 
266  /*******************************************************************
267  SET FUNCTIONS
268  *******************************************************************/
269 
270 
271  /*!
272  * \brief Set the loop bandwidth
273  *
274  * Set the loop filter's bandwidth to \p bw. This should be between
275  * 2*pi/200 and 2*pi/100 (in rads/samp). It must also be a positive
276  * number.
277  *
278  * When a new damping factor is set, the gains, alpha and beta, of the loop
279  * are recalculated by a call to update_gains().
280  *
281  * \param bw (float) new bandwidth
282  *
283  */
284  void set_loop_bandwidth(float bw);
285 
286  /*!
287  * \brief Set the loop damping factor
288  *
289  * Set the loop filter's damping factor to \p df. The damping factor
290  * should be sqrt(2)/2.0 for critically damped systems.
291  * Set it to anything else only if you know what you are doing. It must
292  * be a number between 0 and 1.
293  *
294  * When a new damping factor is set, the gains, alpha and beta, of the loop
295  * are recalculated by a call to update_gains().
296  *
297  * \param df (float) new damping factor
298  *
299  */
300  void set_damping_factor(float df);
301 
302  /*!
303  * \brief Set the loop gain alpha
304  *
305  * Set's the loop filter's alpha gain parameter.
306  *
307  * This value should really only be set by adjusting the loop bandwidth
308  * and damping factor.
309  *
310  * \param alpha (float) new alpha gain
311  *
312  */
313  void set_alpha(float alpha);
314 
315  /*!
316  * \brief Set the loop gain beta
317  *
318  * Set's the loop filter's beta gain parameter.
319  *
320  * This value should really only be set by adjusting the loop bandwidth
321  * and damping factor.
322  *
323  * \param beta (float) new beta gain
324  *
325  */
326  void set_beta(float beta);
327 
328  /*!
329  * Set the maximum deviation from 0 d_rate can have
330  */
331  void set_max_rate_deviation(float m)
332  {
333  d_max_dev = m;
334  }
335 
336  /*******************************************************************
337  GET FUNCTIONS
338  *******************************************************************/
339 
340  /*!
341  * \brief Returns the loop bandwidth
342  */
343  float get_loop_bandwidth() const;
344 
345  /*!
346  * \brief Returns the loop damping factor
347  */
348  float get_damping_factor() const;
349 
350  /*!
351  * \brief Returns the loop gain alpha
352  */
353  float get_alpha() const;
354 
355  /*!
356  * \brief Returns the loop gain beta
357  */
358  float get_beta() const;
359 
360  /*!
361  * \brief Returns the current clock rate
362  */
363  float get_clock_rate() const;
364 
365  /*******************************************************************
366  *******************************************************************/
367 
368  bool check_topology(int ninputs, int noutputs);
369 
370  int general_work(int noutput_items,
371  gr_vector_int &ninput_items,
372  gr_vector_const_void_star &input_items,
373  gr_vector_void_star &output_items);
374 };
375 
376 #endif