3 # Copyright 2007,2008 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 from gnuradio import gr
26 class ofdm_sync_ml(gr.hier_block2):
27 def __init__(self, fft_length, cp_length, snr, kstime, logging):
28 ''' Maximum Likelihood OFDM synchronizer:
29 J. van de Beek, M. Sandell, and P. O. Borjesson, "ML Estimation
30 of Time and Frequency Offset in OFDM Systems," IEEE Trans.
31 Signal Processing, vol. 45, no. 7, pp. 1800-1805, 1997.
34 gr.hier_block2.__init__(self, "ofdm_sync_ml",
35 gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature
36 gr.io_signature2(2, 2, gr.sizeof_float, gr.sizeof_char)) # Output signature
38 self.input = gr.add_const_cc(0)
40 SNR = 10.0**(snr/10.0)
41 rho = SNR / (SNR + 1.0)
42 symbol_length = fft_length + cp_length
46 # Energy Detection from ML Sync
48 self.connect(self, self.input)
51 self.delay = gr.delay(gr.sizeof_gr_complex, fft_length)
52 self.connect(self.input, self.delay)
54 # magnitude squared blocks
55 self.magsqrd1 = gr.complex_to_mag_squared()
56 self.magsqrd2 = gr.complex_to_mag_squared()
57 self.adder = gr.add_ff()
59 moving_sum_taps = [rho/2 for i in range(cp_length)]
60 self.moving_sum_filter = gr.fir_filter_fff(1,moving_sum_taps)
62 self.connect(self.input,self.magsqrd1)
63 self.connect(self.delay,self.magsqrd2)
64 self.connect(self.magsqrd1,(self.adder,0))
65 self.connect(self.magsqrd2,(self.adder,1))
66 self.connect(self.adder,self.moving_sum_filter)
69 # Correlation from ML Sync
70 self.conjg = gr.conjugate_cc();
71 self.mixer = gr.multiply_cc();
73 movingsum2_taps = [1.0 for i in range(cp_length)]
74 self.movingsum2 = gr.fir_filter_ccf(1,movingsum2_taps)
76 # Correlator data handler
77 self.c2mag = gr.complex_to_mag()
78 self.angle = gr.complex_to_arg()
79 self.connect(self.input,(self.mixer,1))
80 self.connect(self.delay,self.conjg,(self.mixer,0))
81 self.connect(self.mixer,self.movingsum2,self.c2mag)
82 self.connect(self.movingsum2,self.angle)
84 # ML Sync output arg, need to find maximum point of this
85 self.diff = gr.sub_ff()
86 self.connect(self.c2mag,(self.diff,0))
87 self.connect(self.moving_sum_filter,(self.diff,1))
89 #ML measurements input to sampler block and detect
90 self.f2c = gr.float_to_complex()
91 self.pk_detect = gr.peak_detector_fb(0.2, 0.25, 30, 0.0005)
92 self.sample_and_hold = gr.sample_and_hold_ff()
94 # use the sync loop values to set the sampler and the NCO
96 # self.angle = epsilon
98 self.connect(self.diff, self.pk_detect)
100 # The DPLL corrects for timing differences between CP correlations
103 self.dpll = gr.dpll_bb(float(symbol_length),0.01)
104 self.connect(self.pk_detect, self.dpll)
105 self.connect(self.dpll, (self.sample_and_hold,1))
107 self.connect(self.pk_detect, (self.sample_and_hold,1))
109 self.connect(self.angle, (self.sample_and_hold,0))
111 ################################
112 # correlate against known symbol
113 # This gives us the same timing signal as the PN sync block only on the preamble
114 # we don't use the signal generated from the CP correlation because we don't want
115 # to readjust the timing in the middle of the packet or we ruin the equalizer settings.
116 kstime = [k.conjugate() for k in kstime]
118 self.kscorr = gr.fir_filter_ccc(1, kstime)
119 self.corrmag = gr.complex_to_mag_squared()
120 self.div = gr.divide_ff()
122 # The output signature of the correlation has a few spikes because the rest of the
123 # system uses the repeated preamble symbol. It needs to work that generically if
124 # anyone wants to use this against a WiMAX-like signal since it, too, repeats.
125 # The output theta of the correlator above is multiplied with this correlation to
126 # identify the proper peak and remove other products in this cross-correlation
127 self.threshold_factor = 0.1
128 self.slice = gr.threshold_ff(self.threshold_factor, self.threshold_factor, 0)
129 self.f2b = gr.float_to_char()
130 self.b2f = gr.char_to_float()
131 self.mul = gr.multiply_ff()
133 # Normalize the power of the corr output by the energy. This is not really needed
134 # and could be removed for performance, but it makes for a cleaner signal.
135 # if this is removed, the threshold value needs adjustment.
136 self.connect(self.input, self.kscorr, self.corrmag, (self.div,0))
137 self.connect(self.moving_sum_filter, (self.div,1))
139 self.connect(self.div, (self.mul,0))
140 self.connect(self.pk_detect, self.b2f, (self.mul,1))
141 self.connect(self.mul, self.slice)
144 # Output 0: fine frequency correction value
145 # Output 1: timing signal
146 self.connect(self.sample_and_hold, (self,0))
147 self.connect(self.slice, self.f2b, (self,1))
151 self.connect(self.moving_sum_filter, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-energy_f.dat"))
152 self.connect(self.diff, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-theta_f.dat"))
153 self.connect(self.angle, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-epsilon_f.dat"))
154 self.connect(self.corrmag, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-corrmag_f.dat"))
155 self.connect(self.kscorr, gr.file_sink(gr.sizeof_gr_complex, "ofdm_sync_ml-kscorr_c.dat"))
156 self.connect(self.div, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-div_f.dat"))
157 self.connect(self.mul, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-mul_f.dat"))
158 self.connect(self.slice, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-slice_f.dat"))
159 self.connect(self.pk_detect, gr.file_sink(gr.sizeof_char, "ofdm_sync_ml-peaks_b.dat"))
161 self.connect(self.dpll, gr.file_sink(gr.sizeof_char, "ofdm_sync_ml-dpll_b.dat"))
163 self.connect(self.sample_and_hold, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-sample_and_hold_f.dat"))
164 self.connect(self.input, gr.file_sink(gr.sizeof_gr_complex, "ofdm_sync_ml-input_c.dat"))