diff options
author | Tom Rondeau | 2011-10-12 17:01:44 -0400 |
---|---|---|
committer | Tom Rondeau | 2011-10-12 17:01:44 -0400 |
commit | 5f0bc5a2096012d6a94f72e20190ab8b3e0b4f88 (patch) | |
tree | 465294556e9209c9a0f864e171d6823e6da11053 /gr-digital | |
parent | d1e3108c8705289d194300ca9fad1c22f579be9b (diff) | |
download | gnuradio-5f0bc5a2096012d6a94f72e20190ab8b3e0b4f88.tar.gz gnuradio-5f0bc5a2096012d6a94f72e20190ab8b3e0b4f88.tar.bz2 gnuradio-5f0bc5a2096012d6a94f72e20190ab8b3e0b4f88.zip |
digital: wip: moved all OFDM examples and blks2impl to gr-digital.
Diffstat (limited to 'gr-digital')
-rw-r--r-- | gr-digital/python/ofdm.py | 82 | ||||
-rw-r--r-- | gr-digital/python/ofdm_receiver.py | 133 | ||||
-rw-r--r-- | gr-digital/python/ofdm_sync_fixed.py | 50 | ||||
-rw-r--r-- | gr-digital/python/ofdm_sync_ml.py | 165 | ||||
-rw-r--r-- | gr-digital/python/ofdm_sync_pn.py | 123 | ||||
-rw-r--r-- | gr-digital/python/ofdm_sync_pnac.py | 125 |
6 files changed, 639 insertions, 39 deletions
diff --git a/gr-digital/python/ofdm.py b/gr-digital/python/ofdm.py index e05f074f4..2663f7cf8 100644 --- a/gr-digital/python/ofdm.py +++ b/gr-digital/python/ofdm.py @@ -21,31 +21,12 @@ # import math -from gnuradio import gr, ofdm_packet_utils, modulation_utils2 +from gnuradio import gr, ofdm_packet_utils import gnuradio.gr.gr_threading as _threading import psk, qam from gnuradio.blks2impl.ofdm_receiver import ofdm_receiver -def _add_common_options(normal, expert): - """ - Adds OFDM-specific options to the Options Parser that are common - both to the modulator and demodulator. - """ - mods_list = ", ".join(modulation_utils2.type_1_constellations().keys()) - print dir(modulation_utils2) - print "MODS LIST: ", mods_list - print modulation_utils2.type_1_mods() - normal.add_option("-m", "--modulation", type="string", default="psk", - help="set modulation type (" + mods_list + ") [default=%default]") - normal.add_option("-c", "--constellation-points", type="int", default=2, - help="set number of constellation points [default=%default]") - expert.add_option("", "--fft-length", type="intx", default=512, - help="set the number of FFT bins [default=%default]") - expert.add_option("", "--occupied-tones", type="intx", default=200, - help="set the number of occupied FFT bins [default=%default]") - expert.add_option("", "--cp-length", type="intx", default=128, - help="set the number of bits in the cyclic prefix [default=%default]") # ///////////////////////////////////////////////////////////////////////////// # mod/demod with packets as i/o @@ -81,9 +62,6 @@ class ofdm_mod(gr.hier_block2): self._occupied_tones = options.occupied_tones self._cp_length = options.cp_length - print (options) - arity = options.constellation_points - win = [] #[1 for i in range(self._fft_length)] # Use freq domain to get doubled-up known symbol for correlation in time domain @@ -104,10 +82,19 @@ class ofdm_mod(gr.hier_block2): symbol_length = options.fft_length + options.cp_length - print modulation_utils2.type_1_constellations - const = modulation_utils2.type_1_constellations()[self._modulation](arity).points() - - self._pkt_input = gr.ofdm_mapper_bcv(const, msgq_limit, + mods = {"bpsk": 2, "qpsk": 4, "8psk": 8, "qam8": 8, "qam16": 16, "qam64": 64, "qam256": 256} + arity = mods[self._modulation] + + rot = 1 + if self._modulation == "qpsk": + rot = (0.707+0.707j) + + if(self._modulation.find("psk") >= 0): + rotated_const = map(lambda pt: pt * rot, psk.gray_constellation[arity]) + elif(self._modulation.find("qam") >= 0): + rotated_const = map(lambda pt: pt * rot, qam.constellation[arity]) + #print rotated_const + self._pkt_input = gr.ofdm_mapper_bcv(rotated_const, msgq_limit, options.occupied_tones, options.fft_length) self.preambles = gr.ofdm_insert_preamble(self._fft_length, padded_preambles) @@ -153,10 +140,14 @@ class ofdm_mod(gr.hier_block2): """ Adds OFDM-specific options to the Options Parser """ - _add_common_options(normal, expert) - for mod in modulation_utils2.type_1_mods().values(): - mod.add_options(expert) - + normal.add_option("-m", "--modulation", type="string", default="bpsk", + help="set modulation type (bpsk, qpsk, 8psk, qam{16,64}) [default=%default]") + expert.add_option("", "--fft-length", type="intx", default=512, + help="set the number of FFT bins [default=%default]") + expert.add_option("", "--occupied-tones", type="intx", default=200, + help="set the number of occupied FFT bins [default=%default]") + expert.add_option("", "--cp-length", type="intx", default=128, + help="set the number of bits in the cyclic prefix [default=%default]") # Make a static method to call before instantiation add_options = staticmethod(add_options) @@ -205,9 +196,6 @@ class ofdm_demod(gr.hier_block2): self._cp_length = options.cp_length self._snr = options.snr - arity = options.constellation_points - print("con points is %s" % options.constellation_points) - # Use freq domain to get doubled-up known symbol for correlation in time domain zeros_on_left = int(math.ceil((self._fft_length - self._occupied_tones)/2.0)) ksfreq = known_symbols_4512_3[0:self._occupied_tones] @@ -223,11 +211,22 @@ class ofdm_demod(gr.hier_block2): self._occupied_tones, self._snr, preambles, options.log) - constell = modulation_utils2.type_1_constellations()[self._modulation](arity) + mods = {"bpsk": 2, "qpsk": 4, "8psk": 8, "qam8": 8, "qam16": 16, "qam64": 64, "qam256": 256} + arity = mods[self._modulation] + + rot = 1 + if self._modulation == "qpsk": + rot = (0.707+0.707j) + + if(self._modulation.find("psk") >= 0): + rotated_const = map(lambda pt: pt * rot, psk.gray_constellation[arity]) + elif(self._modulation.find("qam") >= 0): + rotated_const = map(lambda pt: pt * rot, qam.constellation[arity]) + #print rotated_const phgain = 0.25 frgain = phgain*phgain / 4.0 - self.ofdm_demod = gr.ofdm_frame_sink2(constell.base(), + self.ofdm_demod = gr.ofdm_frame_sink(rotated_const, range(arity), self._rcvd_pktq, self._occupied_tones, phgain, frgain) @@ -254,9 +253,14 @@ class ofdm_demod(gr.hier_block2): """ Adds OFDM-specific options to the Options Parser """ - _add_common_options(normal, expert) - for mod in modulation_utils2.type_1_mods().values(): - mod.add_options(expert) + normal.add_option("-m", "--modulation", type="string", default="bpsk", + help="set modulation type (bpsk or qpsk) [default=%default]") + expert.add_option("", "--fft-length", type="intx", default=512, + help="set the number of FFT bins [default=%default]") + expert.add_option("", "--occupied-tones", type="intx", default=200, + help="set the number of occupied FFT bins [default=%default]") + expert.add_option("", "--cp-length", type="intx", default=128, + help="set the number of bits in the cyclic prefix [default=%default]") # Make a static method to call before instantiation add_options = staticmethod(add_options) diff --git a/gr-digital/python/ofdm_receiver.py b/gr-digital/python/ofdm_receiver.py new file mode 100644 index 000000000..56ae0c0f0 --- /dev/null +++ b/gr-digital/python/ofdm_receiver.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python +# +# Copyright 2006, 2007, 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import math +from numpy import fft +from gnuradio import gr +from gnuradio.blks2impl.ofdm_sync_ml import ofdm_sync_ml +from gnuradio.blks2impl.ofdm_sync_pn import ofdm_sync_pn +from gnuradio.blks2impl.ofdm_sync_pnac import ofdm_sync_pnac +from gnuradio.blks2impl.ofdm_sync_fixed import ofdm_sync_fixed + +class ofdm_receiver(gr.hier_block2): + """ + Performs receiver synchronization on OFDM symbols. + + The receiver performs channel filtering as well as symbol, frequency, and phase synchronization. + The synchronization routines are available in three flavors: preamble correlator (Schmidl and Cox), + modifid preamble correlator with autocorrelation (not yet working), and cyclic prefix correlator + (Van de Beeks). + """ + + def __init__(self, fft_length, cp_length, occupied_tones, snr, ks, logging=False): + """ + Hierarchical block for receiving OFDM symbols. + + The input is the complex modulated signal at baseband. + Synchronized packets are sent back to the demodulator. + + @param fft_length: total number of subcarriers + @type fft_length: int + @param cp_length: length of cyclic prefix as specified in subcarriers (<= fft_length) + @type cp_length: int + @param occupied_tones: number of subcarriers used for data + @type occupied_tones: int + @param snr: estimated signal to noise ratio used to guide cyclic prefix synchronizer + @type snr: float + @param ks: known symbols used as preambles to each packet + @type ks: list of lists + @param logging: turn file logging on or off + @type logging: bool + """ + + gr.hier_block2.__init__(self, "ofdm_receiver", + gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature + gr.io_signature2(2, 2, gr.sizeof_gr_complex*occupied_tones, gr.sizeof_char)) # Output signature + + bw = (float(occupied_tones) / float(fft_length)) / 2.0 + tb = bw*0.08 + chan_coeffs = gr.firdes.low_pass (1.0, # gain + 1.0, # sampling rate + bw+tb, # midpoint of trans. band + tb, # width of trans. band + gr.firdes.WIN_HAMMING) # filter type + self.chan_filt = gr.fft_filter_ccc(1, chan_coeffs) + + win = [1 for i in range(fft_length)] + + zeros_on_left = int(math.ceil((fft_length - occupied_tones)/2.0)) + ks0 = fft_length*[0,] + ks0[zeros_on_left : zeros_on_left + occupied_tones] = ks[0] + + ks0 = fft.ifftshift(ks0) + ks0time = fft.ifft(ks0) + # ADD SCALING FACTOR + ks0time = ks0time.tolist() + + SYNC = "pn" + if SYNC == "ml": + nco_sensitivity = -1.0/fft_length # correct for fine frequency + self.ofdm_sync = ofdm_sync_ml(fft_length, cp_length, snr, ks0time, logging) + elif SYNC == "pn": + nco_sensitivity = -2.0/fft_length # correct for fine frequency + self.ofdm_sync = ofdm_sync_pn(fft_length, cp_length, logging) + elif SYNC == "pnac": + nco_sensitivity = -2.0/fft_length # correct for fine frequency + self.ofdm_sync = ofdm_sync_pnac(fft_length, cp_length, ks0time, logging) + elif SYNC == "fixed": # for testing only; do not user over the air + self.chan_filt = gr.multiply_const_cc(1.0) # remove filter and filter delay for this + nsymbols = 18 # enter the number of symbols per packet + freq_offset = 0.0 # if you use a frequency offset, enter it here + nco_sensitivity = -2.0/fft_length # correct for fine frequency + self.ofdm_sync = ofdm_sync_fixed(fft_length, cp_length, nsymbols, freq_offset, logging) + + # Set up blocks + + self.nco = gr.frequency_modulator_fc(nco_sensitivity) # generate a signal proportional to frequency error of sync block + self.sigmix = gr.multiply_cc() + self.sampler = gr.ofdm_sampler(fft_length, fft_length+cp_length) + self.fft_demod = gr.fft_vcc(fft_length, True, win, True) + self.ofdm_frame_acq = gr.ofdm_frame_acquisition(occupied_tones, fft_length, + cp_length, ks[0]) + + self.connect(self, self.chan_filt) # filter the input channel + self.connect(self.chan_filt, self.ofdm_sync) # into the synchronization alg. + self.connect((self.ofdm_sync,0), self.nco, (self.sigmix,1)) # use sync freq. offset output to derotate input signal + self.connect(self.chan_filt, (self.sigmix,0)) # signal to be derotated + self.connect(self.sigmix, (self.sampler,0)) # sample off timing signal detected in sync alg + self.connect((self.ofdm_sync,1), (self.sampler,1)) # timing signal to sample at + + self.connect((self.sampler,0), self.fft_demod) # send derotated sampled signal to FFT + self.connect(self.fft_demod, (self.ofdm_frame_acq,0)) # find frame start and equalize signal + self.connect((self.sampler,1), (self.ofdm_frame_acq,1)) # send timing signal to signal frame start + self.connect((self.ofdm_frame_acq,0), (self,0)) # finished with fine/coarse freq correction, + self.connect((self.ofdm_frame_acq,1), (self,1)) # frame and symbol timing, and equalization + + if logging: + self.connect(self.chan_filt, gr.file_sink(gr.sizeof_gr_complex, "ofdm_receiver-chan_filt_c.dat")) + self.connect(self.fft_demod, gr.file_sink(gr.sizeof_gr_complex*fft_length, "ofdm_receiver-fft_out_c.dat")) + self.connect(self.ofdm_frame_acq, + gr.file_sink(gr.sizeof_gr_complex*occupied_tones, "ofdm_receiver-frame_acq_c.dat")) + self.connect((self.ofdm_frame_acq,1), gr.file_sink(1, "ofdm_receiver-found_corr_b.dat")) + self.connect(self.sampler, gr.file_sink(gr.sizeof_gr_complex*fft_length, "ofdm_receiver-sampler_c.dat")) + self.connect(self.sigmix, gr.file_sink(gr.sizeof_gr_complex, "ofdm_receiver-sigmix_c.dat")) + self.connect(self.nco, gr.file_sink(gr.sizeof_gr_complex, "ofdm_receiver-nco_c.dat")) diff --git a/gr-digital/python/ofdm_sync_fixed.py b/gr-digital/python/ofdm_sync_fixed.py new file mode 100644 index 000000000..9bac789bf --- /dev/null +++ b/gr-digital/python/ofdm_sync_fixed.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# +# Copyright 2007 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import math +from gnuradio import gr + +class ofdm_sync_fixed(gr.hier_block2): + def __init__(self, fft_length, cp_length, nsymbols, freq_offset, logging=False): + + gr.hier_block2.__init__(self, "ofdm_sync_fixed", + gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature + gr.io_signature2(2, 2, gr.sizeof_float, gr.sizeof_char)) # Output signature + + # Use a fixed trigger point instead of sync block + symbol_length = fft_length + cp_length + pkt_length = nsymbols*symbol_length + data = (pkt_length)*[0,] + data[(symbol_length)-1] = 1 + self.peak_trigger = gr.vector_source_b(data, True) + + # Use a pre-defined frequency offset + foffset = (pkt_length)*[math.pi*freq_offset,] + self.frequency_offset = gr.vector_source_f(foffset, True) + + self.connect(self, gr.null_sink(gr.sizeof_gr_complex)) + self.connect(self.frequency_offset, (self,0)) + self.connect(self.peak_trigger, (self,1)) + + if logging: + self.connect(self.peak_trigger, gr.file_sink(gr.sizeof_char, "ofdm_sync_fixed-peaks_b.dat")) + diff --git a/gr-digital/python/ofdm_sync_ml.py b/gr-digital/python/ofdm_sync_ml.py new file mode 100644 index 000000000..7c75d7f1d --- /dev/null +++ b/gr-digital/python/ofdm_sync_ml.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python +# +# Copyright 2007,2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import math +from gnuradio import gr + +class ofdm_sync_ml(gr.hier_block2): + def __init__(self, fft_length, cp_length, snr, kstime, logging): + ''' Maximum Likelihood OFDM synchronizer: + J. van de Beek, M. Sandell, and P. O. Borjesson, "ML Estimation + of Time and Frequency Offset in OFDM Systems," IEEE Trans. + Signal Processing, vol. 45, no. 7, pp. 1800-1805, 1997. + ''' + + gr.hier_block2.__init__(self, "ofdm_sync_ml", + gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature + gr.io_signature2(2, 2, gr.sizeof_float, gr.sizeof_char)) # Output signature + + self.input = gr.add_const_cc(0) + + SNR = 10.0**(snr/10.0) + rho = SNR / (SNR + 1.0) + symbol_length = fft_length + cp_length + + # ML Sync + + # Energy Detection from ML Sync + + self.connect(self, self.input) + + # Create a delay line + self.delay = gr.delay(gr.sizeof_gr_complex, fft_length) + self.connect(self.input, self.delay) + + # magnitude squared blocks + self.magsqrd1 = gr.complex_to_mag_squared() + self.magsqrd2 = gr.complex_to_mag_squared() + self.adder = gr.add_ff() + + moving_sum_taps = [rho/2 for i in range(cp_length)] + self.moving_sum_filter = gr.fir_filter_fff(1,moving_sum_taps) + + self.connect(self.input,self.magsqrd1) + self.connect(self.delay,self.magsqrd2) + self.connect(self.magsqrd1,(self.adder,0)) + self.connect(self.magsqrd2,(self.adder,1)) + self.connect(self.adder,self.moving_sum_filter) + + + # Correlation from ML Sync + self.conjg = gr.conjugate_cc(); + self.mixer = gr.multiply_cc(); + + movingsum2_taps = [1.0 for i in range(cp_length)] + self.movingsum2 = gr.fir_filter_ccf(1,movingsum2_taps) + + # Correlator data handler + self.c2mag = gr.complex_to_mag() + self.angle = gr.complex_to_arg() + self.connect(self.input,(self.mixer,1)) + self.connect(self.delay,self.conjg,(self.mixer,0)) + self.connect(self.mixer,self.movingsum2,self.c2mag) + self.connect(self.movingsum2,self.angle) + + # ML Sync output arg, need to find maximum point of this + self.diff = gr.sub_ff() + self.connect(self.c2mag,(self.diff,0)) + self.connect(self.moving_sum_filter,(self.diff,1)) + + #ML measurements input to sampler block and detect + self.f2c = gr.float_to_complex() + self.pk_detect = gr.peak_detector_fb(0.2, 0.25, 30, 0.0005) + self.sample_and_hold = gr.sample_and_hold_ff() + + # use the sync loop values to set the sampler and the NCO + # self.diff = theta + # self.angle = epsilon + + self.connect(self.diff, self.pk_detect) + + # The DPLL corrects for timing differences between CP correlations + use_dpll = 0 + if use_dpll: + self.dpll = gr.dpll_bb(float(symbol_length),0.01) + self.connect(self.pk_detect, self.dpll) + self.connect(self.dpll, (self.sample_and_hold,1)) + else: + self.connect(self.pk_detect, (self.sample_and_hold,1)) + + self.connect(self.angle, (self.sample_and_hold,0)) + + ################################ + # correlate against known symbol + # This gives us the same timing signal as the PN sync block only on the preamble + # we don't use the signal generated from the CP correlation because we don't want + # to readjust the timing in the middle of the packet or we ruin the equalizer settings. + kstime = [k.conjugate() for k in kstime] + kstime.reverse() + self.kscorr = gr.fir_filter_ccc(1, kstime) + self.corrmag = gr.complex_to_mag_squared() + self.div = gr.divide_ff() + + # The output signature of the correlation has a few spikes because the rest of the + # system uses the repeated preamble symbol. It needs to work that generically if + # anyone wants to use this against a WiMAX-like signal since it, too, repeats. + # The output theta of the correlator above is multiplied with this correlation to + # identify the proper peak and remove other products in this cross-correlation + self.threshold_factor = 0.1 + self.slice = gr.threshold_ff(self.threshold_factor, self.threshold_factor, 0) + self.f2b = gr.float_to_char() + self.b2f = gr.char_to_float() + self.mul = gr.multiply_ff() + + # Normalize the power of the corr output by the energy. This is not really needed + # and could be removed for performance, but it makes for a cleaner signal. + # if this is removed, the threshold value needs adjustment. + self.connect(self.input, self.kscorr, self.corrmag, (self.div,0)) + self.connect(self.moving_sum_filter, (self.div,1)) + + self.connect(self.div, (self.mul,0)) + self.connect(self.pk_detect, self.b2f, (self.mul,1)) + self.connect(self.mul, self.slice) + + # Set output signals + # Output 0: fine frequency correction value + # Output 1: timing signal + self.connect(self.sample_and_hold, (self,0)) + self.connect(self.slice, self.f2b, (self,1)) + + + if logging: + self.connect(self.moving_sum_filter, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-energy_f.dat")) + self.connect(self.diff, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-theta_f.dat")) + self.connect(self.angle, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-epsilon_f.dat")) + self.connect(self.corrmag, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-corrmag_f.dat")) + self.connect(self.kscorr, gr.file_sink(gr.sizeof_gr_complex, "ofdm_sync_ml-kscorr_c.dat")) + self.connect(self.div, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-div_f.dat")) + self.connect(self.mul, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-mul_f.dat")) + self.connect(self.slice, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-slice_f.dat")) + self.connect(self.pk_detect, gr.file_sink(gr.sizeof_char, "ofdm_sync_ml-peaks_b.dat")) + if use_dpll: + self.connect(self.dpll, gr.file_sink(gr.sizeof_char, "ofdm_sync_ml-dpll_b.dat")) + + self.connect(self.sample_and_hold, gr.file_sink(gr.sizeof_float, "ofdm_sync_ml-sample_and_hold_f.dat")) + self.connect(self.input, gr.file_sink(gr.sizeof_gr_complex, "ofdm_sync_ml-input_c.dat")) + diff --git a/gr-digital/python/ofdm_sync_pn.py b/gr-digital/python/ofdm_sync_pn.py new file mode 100644 index 000000000..05b1de2e1 --- /dev/null +++ b/gr-digital/python/ofdm_sync_pn.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python +# +# Copyright 2007,2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import math +from numpy import fft +from gnuradio import gr + +class ofdm_sync_pn(gr.hier_block2): + def __init__(self, fft_length, cp_length, logging=False): + """ + OFDM synchronization using PN Correlation: + T. M. Schmidl and D. C. Cox, "Robust Frequency and Timing + Synchonization for OFDM," IEEE Trans. Communications, vol. 45, + no. 12, 1997. + """ + + gr.hier_block2.__init__(self, "ofdm_sync_pn", + gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature + gr.io_signature2(2, 2, gr.sizeof_float, gr.sizeof_char)) # Output signature + + self.input = gr.add_const_cc(0) + + # PN Sync + + # Create a delay line + self.delay = gr.delay(gr.sizeof_gr_complex, fft_length/2) + + # Correlation from ML Sync + self.conjg = gr.conjugate_cc(); + self.corr = gr.multiply_cc(); + + # Create a moving sum filter for the corr output + if 1: + moving_sum_taps = [1.0 for i in range(fft_length//2)] + self.moving_sum_filter = gr.fir_filter_ccf(1,moving_sum_taps) + else: + moving_sum_taps = [complex(1.0,0.0) for i in range(fft_length//2)] + self.moving_sum_filter = gr.fft_filter_ccc(1,moving_sum_taps) + + # Create a moving sum filter for the input + self.inputmag2 = gr.complex_to_mag_squared() + movingsum2_taps = [1.0 for i in range(fft_length//2)] + + if 1: + self.inputmovingsum = gr.fir_filter_fff(1,movingsum2_taps) + else: + self.inputmovingsum = gr.fft_filter_fff(1,movingsum2_taps) + + self.square = gr.multiply_ff() + self.normalize = gr.divide_ff() + + # Get magnitude (peaks) and angle (phase/freq error) + self.c2mag = gr.complex_to_mag_squared() + self.angle = gr.complex_to_arg() + + self.sample_and_hold = gr.sample_and_hold_ff() + + #ML measurements input to sampler block and detect + self.sub1 = gr.add_const_ff(-1) + self.pk_detect = gr.peak_detector_fb(0.20, 0.20, 30, 0.001) + #self.pk_detect = gr.peak_detector2_fb(9) + + self.connect(self, self.input) + + # Calculate the frequency offset from the correlation of the preamble + self.connect(self.input, self.delay) + self.connect(self.input, (self.corr,0)) + self.connect(self.delay, self.conjg) + self.connect(self.conjg, (self.corr,1)) + self.connect(self.corr, self.moving_sum_filter) + self.connect(self.moving_sum_filter, self.c2mag) + self.connect(self.moving_sum_filter, self.angle) + self.connect(self.angle, (self.sample_and_hold,0)) + + # Get the power of the input signal to normalize the output of the correlation + self.connect(self.input, self.inputmag2, self.inputmovingsum) + self.connect(self.inputmovingsum, (self.square,0)) + self.connect(self.inputmovingsum, (self.square,1)) + self.connect(self.square, (self.normalize,1)) + self.connect(self.c2mag, (self.normalize,0)) + + # Create a moving sum filter for the corr output + matched_filter_taps = [1.0/cp_length for i in range(cp_length)] + self.matched_filter = gr.fir_filter_fff(1,matched_filter_taps) + self.connect(self.normalize, self.matched_filter) + + self.connect(self.matched_filter, self.sub1, self.pk_detect) + #self.connect(self.matched_filter, self.pk_detect) + self.connect(self.pk_detect, (self.sample_and_hold,1)) + + # Set output signals + # Output 0: fine frequency correction value + # Output 1: timing signal + self.connect(self.sample_and_hold, (self,0)) + self.connect(self.pk_detect, (self,1)) + + if logging: + self.connect(self.matched_filter, gr.file_sink(gr.sizeof_float, "ofdm_sync_pn-mf_f.dat")) + self.connect(self.normalize, gr.file_sink(gr.sizeof_float, "ofdm_sync_pn-theta_f.dat")) + self.connect(self.angle, gr.file_sink(gr.sizeof_float, "ofdm_sync_pn-epsilon_f.dat")) + self.connect(self.pk_detect, gr.file_sink(gr.sizeof_char, "ofdm_sync_pn-peaks_b.dat")) + self.connect(self.sample_and_hold, gr.file_sink(gr.sizeof_float, "ofdm_sync_pn-sample_and_hold_f.dat")) + self.connect(self.input, gr.file_sink(gr.sizeof_gr_complex, "ofdm_sync_pn-input_c.dat")) + diff --git a/gr-digital/python/ofdm_sync_pnac.py b/gr-digital/python/ofdm_sync_pnac.py new file mode 100644 index 000000000..10a125964 --- /dev/null +++ b/gr-digital/python/ofdm_sync_pnac.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python +# +# Copyright 2007 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import math +from numpy import fft +from gnuradio import gr + +class ofdm_sync_pnac(gr.hier_block2): + def __init__(self, fft_length, cp_length, kstime, logging=False): + """ + OFDM synchronization using PN Correlation and initial cross-correlation: + F. Tufvesson, O. Edfors, and M. Faulkner, "Time and Frequency Synchronization for OFDM using + PN-Sequency Preambles," IEEE Proc. VTC, 1999, pp. 2203-2207. + + This implementation is meant to be a more robust version of the Schmidl and Cox receiver design. + By correlating against the preamble and using that as the input to the time-delayed correlation, + this circuit produces a very clean timing signal at the end of the preamble. The timing is + more accurate and does not have the problem associated with determining the timing from the + plateau structure in the Schmidl and Cox. + + This implementation appears to require that the signal is received with a normalized power or signal + scalling factor to reduce ambiguities intorduced from partial correlation of the cyclic prefix and + the peak detection. A better peak detection block might fix this. + + Also, the cross-correlation falls apart as the frequency offset gets larger and completely fails + when an integer offset is introduced. Another thing to look at. + """ + + gr.hier_block2.__init__(self, "ofdm_sync_pnac", + gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature + gr.io_signature2(2, 2, gr.sizeof_float, gr.sizeof_char)) # Output signature + + + self.input = gr.add_const_cc(0) + + symbol_length = fft_length + cp_length + + # PN Sync with cross-correlation input + + # cross-correlate with the known symbol + kstime = [k.conjugate() for k in kstime[0:fft_length//2]] + kstime.reverse() + self.crosscorr_filter = gr.fir_filter_ccc(1, kstime) + + # Create a delay line + self.delay = gr.delay(gr.sizeof_gr_complex, fft_length/2) + + # Correlation from ML Sync + self.conjg = gr.conjugate_cc(); + self.corr = gr.multiply_cc(); + + # Create a moving sum filter for the input + self.mag = gr.complex_to_mag_squared() + movingsum_taps = (fft_length//1)*[1.0,] + self.power = gr.fir_filter_fff(1,movingsum_taps) + + # Get magnitude (peaks) and angle (phase/freq error) + self.c2mag = gr.complex_to_mag_squared() + self.angle = gr.complex_to_arg() + self.compare = gr.sub_ff() + + self.sample_and_hold = gr.sample_and_hold_ff() + + #ML measurements input to sampler block and detect + self.threshold = gr.threshold_ff(0,0,0) # threshold detection might need to be tweaked + self.peaks = gr.float_to_char() + + self.connect(self, self.input) + + # Cross-correlate input signal with known preamble + self.connect(self.input, self.crosscorr_filter) + + # use the output of the cross-correlation as input time-shifted correlation + self.connect(self.crosscorr_filter, self.delay) + self.connect(self.crosscorr_filter, (self.corr,0)) + self.connect(self.delay, self.conjg) + self.connect(self.conjg, (self.corr,1)) + self.connect(self.corr, self.c2mag) + self.connect(self.corr, self.angle) + self.connect(self.angle, (self.sample_and_hold,0)) + + # Get the power of the input signal to compare against the correlation + self.connect(self.crosscorr_filter, self.mag, self.power) + + # Compare the power to the correlator output to determine timing peak + # When the peak occurs, it peaks above zero, so the thresholder detects this + self.connect(self.c2mag, (self.compare,0)) + self.connect(self.power, (self.compare,1)) + self.connect(self.compare, self.threshold) + self.connect(self.threshold, self.peaks, (self.sample_and_hold,1)) + + # Set output signals + # Output 0: fine frequency correction value + # Output 1: timing signal + self.connect(self.sample_and_hold, (self,0)) + self.connect(self.peaks, (self,1)) + + if logging: + self.connect(self.compare, gr.file_sink(gr.sizeof_float, "ofdm_sync_pnac-compare_f.dat")) + self.connect(self.c2mag, gr.file_sink(gr.sizeof_float, "ofdm_sync_pnac-theta_f.dat")) + self.connect(self.power, gr.file_sink(gr.sizeof_float, "ofdm_sync_pnac-inputpower_f.dat")) + self.connect(self.angle, gr.file_sink(gr.sizeof_float, "ofdm_sync_pnac-epsilon_f.dat")) + self.connect(self.threshold, gr.file_sink(gr.sizeof_float, "ofdm_sync_pnac-threshold_f.dat")) + self.connect(self.peaks, gr.file_sink(gr.sizeof_char, "ofdm_sync_pnac-peaks_b.dat")) + self.connect(self.sample_and_hold, gr.file_sink(gr.sizeof_float, "ofdm_sync_pnac-sample_and_hold_f.dat")) + self.connect(self.input, gr.file_sink(gr.sizeof_gr_complex, "ofdm_sync_pnac-input_c.dat")) |