GNU Radio 3.5.3.2 C++ API
gr_pfb_clock_sync_fff.h
Go to the documentation of this file.
1 /* -*- c++ -*- */
2 /*
3  * Copyright 2009,2010 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_GR_PFB_CLOCK_SYNC_FFF_H
25 #define INCLUDED_GR_PFB_CLOCK_SYNC_FFF_H
26 
27 #include <gr_core_api.h>
28 #include <gr_block.h>
29 
33  const std::vector<float> &taps,
34  unsigned int filter_size=32,
35  float init_phase=0,
36  float max_rate_deviation=1.5);
37 
38 class gr_fir_fff;
39 
40 /*!
41  * \class gr_pfb_clock_sync_fff
42  *
43  * \brief Timing synchronizer using polyphase filterbanks
44  *
45  * \ingroup filter_blk
46  * \ingroup pfb_blk
47  *
48  * This block performs timing synchronization for PAM signals by
49  * minimizing the derivative of the filtered signal, which in turn
50  * maximizes the SNR and minimizes ISI.
51  *
52  * This approach works by setting up two filterbanks; one filterbank
53  * contains the signal's pulse shaping matched filter (such as a root
54  * raised cosine filter), where each branch of the filterbank contains
55  * a different phase of the filter. The second filterbank contains
56  * the derivatives of the filters in the first filterbank. Thinking of
57  * this in the time domain, the first filterbank contains filters that
58  * have a sinc shape to them. We want to align the output signal to be
59  * sampled at exactly the peak of the sinc shape. The derivative of
60  * the sinc contains a zero at the maximum point of the sinc (sinc(0)
61  * = 1, sinc(0)' = 0). Furthermore, the region around the zero point
62  * is relatively linear. We make use of this fact to generate the
63  * error signal.
64  *
65  * If the signal out of the derivative filters is d_i[n] for the ith
66  * filter, and the output of the matched filter is x_i[n], we
67  * calculate the error as: e[n] = (Re{x_i[n]} * Re{d_i[n]} +
68  * Im{x_i[n]} * Im{d_i[n]}) / 2.0 This equation averages the error in
69  * the real and imaginary parts. There are two reasons we multiply by
70  * the signal itself. First, if the symbol could be positive or
71  * negative going, but we want the error term to always tell us to go
72  * in the same direction depending on which side of the zero point we
73  * are on. The sign of x_i[n] adjusts the error term to do
74  * this. Second, the magnitude of x_i[n] scales the error term
75  * depending on the symbol's amplitude, so larger signals give us a
76  * stronger error term because we have more confidence in that
77  * symbol's value. Using the magnitude of x_i[n] instead of just the
78  * sign is especially good for signals with low SNR.
79  *
80  * The error signal, e[n], gives us a value proportional to how far
81  * away from the zero point we are in the derivative signal. We want
82  * to drive this value to zero, so we set up a second order loop. We
83  * have two variables for this loop; d_k is the filter number in the
84  * filterbank we are on and d_rate is the rate which we travel through
85  * the filters in the steady state. That is, due to the natural clock
86  * differences between the transmitter and receiver, d_rate represents
87  * that difference and would traverse the filter phase paths to keep
88  * the receiver locked. Thinking of this as a second-order PLL, the
89  * d_rate is the frequency and d_k is the phase. So we update d_rate
90  * and d_k using the standard loop equations based on two error
91  * signals, d_alpha and d_beta. We have these two values set based on
92  * each other for a critically damped system, so in the block
93  * constructor, we just ask for "gain," which is d_alpha while d_beta
94  * is equal to (gain^2)/4.
95  *
96  * The block's parameters are:
97  *
98  * \li \p sps: The clock sync block needs to know the number of samples per
99  * symbol, because it defaults to return a single point representing
100  * the symbol. The sps can be any positive real number and does not
101  * need to be an integer.
102  *
103  * \li \p loop_bw: The loop bandwidth is used to set the gain of the
104  * inner control loop (see:
105  * http://gnuradio.squarespace.com/blog/2011/8/13/control-loop-gain-values.html).
106  * This should be set small (a value of around 2pi/100 is suggested in
107  * that blog post as the step size for the number of radians around
108  * the unit circle to move relative to the error).
109  *
110  * \li \p taps: One of the most important parameters for this block is
111  * the taps of the filter. One of the benefits of this algorithm is
112  * that you can put the matched filter in here as the taps, so you get
113  * both the matched filter and sample timing correction in one go. So
114  * create your normal matched filter. For a typical digital
115  * modulation, this is a root raised cosine filter. The number of taps
116  * of this filter is based on how long you expect the channel to be;
117  * that is, how many symbols do you want to combine to get the current
118  * symbols energy back (there's probably a better way of stating
119  * that). It's usually 5 to 10 or so. That gives you your filter, but
120  * now we need to think about it as a filter with different phase
121  * profiles in each filter. So take this number of taps and multiply
122  * it by the number of filters. This is the number you would use to
123  * create your prototype filter. When you use this in the PFB
124  * filerbank, it segments these taps into the filterbanks in such a
125  * way that each bank now represents the filter at different phases,
126  * equally spaced at 2pi/N, where N is the number of filters.
127  *
128  * \li \p filter_size (default=32): The number of filters can also be
129  * set and defaults to 32. With 32 filters, you get a good enough
130  * resolution in the phase to produce very small, almost unnoticeable,
131  * ISI. Going to 64 filters can reduce this more, but after that
132  * there is very little gained for the extra complexity.
133  *
134  * \li \p init_phase (default=0): The initial phase is another
135  * settable parameter and refers to the filter path the algorithm
136  * initially looks at (i.e., d_k starts at init_phase). This value
137  * defaults to zero, but it might be useful to start at a different
138  * phase offset, such as the mid-point of the filters.
139  *
140  * \li \p max_rate_deviation (default=1.5): The next parameter is the
141  * max_rate_devitation, which defaults to 1.5. This is how far we
142  * allow d_rate to swing, positive or negative, from 0. Constraining
143  * the rate can help keep the algorithm from walking too far away to
144  * lock during times when there is no signal.
145  *
146  * \li \p osps: note that unlike the ccf version of this algorithm,
147  * this block does \a not have a setting for the number of output
148  * samples per symbol. This is mostly because it should not be
149  * necessary as the reason for having multiple output sps is to
150  * perform equalization and the equalizers will take in complex
151  * numbers in order to do magnitude and phase correction.
152  */
153 
155 {
156  private:
157  /*!
158  * Build the polyphase filterbank timing synchronizer.
159  * \param sps (double) The number of samples per second in the incoming signal
160  * \param gain (float) The alpha gain of the control loop; beta = (gain^2)/4 by default.
161  * \param taps (vector<int>) The filter taps.
162  * \param filter_size (uint) The number of filters in the filterbank (default = 32).
163  * \param init_phase (float) The initial phase to look at, or which filter to start
164  * with (default = 0).
165  * \param max_rate_deviation (float) Distance from 0 d_rate can get (default = 1.5).
166  *
167  */
169  const std::vector<float> &taps,
170  unsigned int filter_size,
171  float init_phase,
172  float max_rate_deviation);
173 
174  bool d_updated;
175  double d_sps;
176  double d_sample_num;
177  float d_alpha;
178  float d_beta;
179  int d_nfilters;
180  std::vector<gr_fir_fff*> d_filters;
181  std::vector<gr_fir_fff*> d_diff_filters;
182  std::vector< std::vector<float> > d_taps;
183  std::vector< std::vector<float> > d_dtaps;
184  float d_k;
185  float d_rate;
186  float d_rate_i;
187  float d_rate_f;
188  float d_max_dev;
189  int d_filtnum;
190  int d_taps_per_filter;
191 
192  /*!
193  * Build the polyphase filterbank timing synchronizer.
194  */
195  gr_pfb_clock_sync_fff (double sps, float gain,
196  const std::vector<float> &taps,
197  unsigned int filter_size,
198  float init_phase,
199  float max_rate_deviation);
200 
201  void create_diff_taps(const std::vector<float> &newtaps,
202  std::vector<float> &difftaps);
203 
204 public:
206 
207  /*!
208  * Resets the filterbank's filter taps with the new prototype filter
209  */
210  void set_taps (const std::vector<float> &taps,
211  std::vector< std::vector<float> > &ourtaps,
212  std::vector<gr_fir_fff*> &ourfilter);
213 
214  /*!
215  * Returns the taps of the matched filter
216  */
217  std::vector<float> channel_taps(int channel);
218 
219  /*!
220  * Returns the taps in the derivative filter
221  */
222  std::vector<float> diff_channel_taps(int channel);
223 
224  /*!
225  * Print all of the filterbank taps to screen.
226  */
227  void print_taps();
228 
229  /*!
230  * Print all of the filterbank taps of the derivative filter to screen.
231  */
232  void print_diff_taps();
233 
234  /*!
235  * Set the gain value alpha for the control loop
236  */
237  void set_alpha(float alpha)
238  {
239  d_alpha = alpha;
240  }
241 
242  /*!
243  * Set the gain value beta for the control loop
244  */
245  void set_beta(float beta)
246  {
247  d_beta = beta;
248  }
249 
250  /*!
251  * Set the maximum deviation from 0 d_rate can have
252  */
253  void set_max_rate_deviation(float m)
254  {
255  d_max_dev = m;
256  }
257 
258  bool check_topology(int ninputs, int noutputs);
259 
260  int general_work (int noutput_items,
261  gr_vector_int &ninput_items,
262  gr_vector_const_void_star &input_items,
263  gr_vector_void_star &output_items);
264 };
265 
266 #endif