Imported Upstream version 3.2.2
[debian/gnuradio] / gnuradio-core / src / python / gnuradio / blks2impl / ofdm_sync_ml.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2007,2008 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 import math
24 from gnuradio import gr
25
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.
32         '''
33
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
37
38         self.input = gr.add_const_cc(0)
39
40         SNR = 10.0**(snr/10.0)
41         rho = SNR / (SNR + 1.0)
42         symbol_length = fft_length + cp_length
43
44         # ML Sync
45
46         # Energy Detection from ML Sync
47
48         self.connect(self, self.input)
49
50         # Create a delay line
51         self.delay = gr.delay(gr.sizeof_gr_complex, fft_length)
52         self.connect(self.input, self.delay)
53
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()
58
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)
61         
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)
67         
68
69         # Correlation from ML Sync
70         self.conjg = gr.conjugate_cc();
71         self.mixer = gr.multiply_cc();
72
73         movingsum2_taps = [1.0 for i in range(cp_length)]
74         self.movingsum2 = gr.fir_filter_ccf(1,movingsum2_taps)
75         
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)
83
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))
88
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()
93
94         # use the sync loop values to set the sampler and the NCO
95         #     self.diff = theta
96         #     self.angle = epsilon
97                           
98         self.connect(self.diff, self.pk_detect)
99
100         # The DPLL corrects for timing differences between CP correlations
101         use_dpll = 0
102         if use_dpll:
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))
106         else:
107             self.connect(self.pk_detect, (self.sample_and_hold,1))
108             
109         self.connect(self.angle, (self.sample_and_hold,0))
110
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]
117         kstime.reverse()
118         self.kscorr = gr.fir_filter_ccc(1, kstime)
119         self.corrmag = gr.complex_to_mag_squared()
120         self.div = gr.divide_ff()
121
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()
132         
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))
138         
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)
142         
143         # Set output signals
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))
148
149
150         if logging:
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"))
160             if use_dpll:
161                 self.connect(self.dpll, gr.file_sink(gr.sizeof_char, "ofdm_sync_ml-dpll_b.dat"))
162
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"))
165