diff options
Diffstat (limited to 'gr-digital/python')
26 files changed, 3638 insertions, 0 deletions
diff --git a/gr-digital/python/.gitignore b/gr-digital/python/.gitignore new file mode 100644 index 000000000..604b402c5 --- /dev/null +++ b/gr-digital/python/.gitignore @@ -0,0 +1,3 @@ +/Makefile +/Makefile.in +/run_tests diff --git a/gr-digital/python/Makefile.am b/gr-digital/python/Makefile.am new file mode 100644 index 000000000..392ce45c1 --- /dev/null +++ b/gr-digital/python/Makefile.am @@ -0,0 +1,54 @@ +# +# Copyright 2011 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. +# + +include $(top_srcdir)/Makefile.common + +TESTS = +EXTRA_DIST += run_tests.in + +if PYTHON +SUBDIRS = utils +TESTS += run_tests + +digitaldir = $(grpythondir)/digital + +noinst_PYTHON = \ + qa_digital.py \ + qa_constellation.py \ + qa_constellation_receiver.py \ + qa_costas_loop_cc.py + +digital_PYTHON = \ + __init__.py \ + psk.py \ + dbpsk.py \ + dqpsk.py \ + d8psk.py \ + psk2.py \ + generic_mod_demod.py \ + qam.py \ + bpsk.py \ + qpsk.py \ + ofdm.py \ + pkt.py \ + modulation_utils2.py + +endif diff --git a/gr-digital/python/__init__.py b/gr-digital/python/__init__.py new file mode 100644 index 000000000..3d0be3865 --- /dev/null +++ b/gr-digital/python/__init__.py @@ -0,0 +1,32 @@ +# +# Copyright 2011 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. +# + +# The presence of this file turns this directory into a Python package + +from digital_swig import * +from dbpsk import * +from dqpsk import * +from d8psk import * +from psk2 import * +from qam import * +from ofdm import * +from pkt import * +from modulation_utils2 import * diff --git a/gr-digital/python/bpsk.py b/gr-digital/python/bpsk.py new file mode 100644 index 000000000..6d2eb5d6d --- /dev/null +++ b/gr-digital/python/bpsk.py @@ -0,0 +1,98 @@ +# +# Copyright 2005,2006,2011 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. +# + +""" +BPSK modulation and demodulation. +""" + +from math import pi, log +from cmath import exp + +from gnuradio import gr, modulation_utils2 +from gnuradio.digital.generic_mod_demod import generic_mod, generic_demod + +# Default number of points in constellation. +_def_constellation_points = 2 +# Whether differential coding is used. +_def_differential = True + +# ///////////////////////////////////////////////////////////////////////////// +# BPSK constellation +# ///////////////////////////////////////////////////////////////////////////// + +def bpsk_constellation(m=_def_constellation_points): + if m != _def_constellation_points: + raise ValueError("BPSK can only have 2 constellation points.") + return gr.constellation_bpsk() + +# ///////////////////////////////////////////////////////////////////////////// +# BPSK modulator +# ///////////////////////////////////////////////////////////////////////////// + +class bpsk_mod(generic_mod): + + def __init__(self, constellation_points=_def_constellation_points, + *args, **kwargs): + + """ + Hierarchical block for RRC-filtered BPSK modulation. + + The input is a byte stream (unsigned char) and the + output is the complex modulated signal at baseband. + + See generic_mod block for list of parameters. + """ + + constellation = gr.constellation_bpsk() + if constellation_points != 2: + raise ValueError('Number of constellation points must be 2 for BPSK.') + super(bpsk_mod, self).__init__(constellation, *args, **kwargs) + +# ///////////////////////////////////////////////////////////////////////////// +# BPSK demodulator +# +# ///////////////////////////////////////////////////////////////////////////// + +class bpsk_demod(generic_demod): + + def __init__(self, constellation_points=_def_constellation_points, + *args, **kwargs): + + """ + Hierarchical block for RRC-filtered BPSK modulation. + + The input is a byte stream (unsigned char) and the + output is the complex modulated signal at baseband. + + See generic_demod block for list of parameters. + """ + + constellation = gr.constellation_bpsk() + if constellation_points != 2: + raise ValueError('Number of constellation points must be 2 for BPSK.') + super(bpsk_demod, self).__init__(constellation, *args, **kwargs) + +# +# Add these to the mod/demod registry +# +modulation_utils2.add_type_1_mod('bpsk', bpsk_mod) +modulation_utils2.add_type_1_demod('bpsk', bpsk_demod) +modulation_utils2.add_type_1_constellation('bpsk', bpsk_constellation) diff --git a/gr-digital/python/d8psk.py b/gr-digital/python/d8psk.py new file mode 100644 index 000000000..8bed395a7 --- /dev/null +++ b/gr-digital/python/d8psk.py @@ -0,0 +1,369 @@ +# +# Copyright 2005,2006,2007,2011 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. +# + +# See gnuradio-examples/python/digital for examples + +""" +differential 8PSK modulation and demodulation. +""" + +from gnuradio import gr, modulation_utils2 +from math import pi, sqrt +import digital_swig, psk +import cmath +from pprint import pprint + +# default values (used in __init__ and add_options) +_def_samples_per_symbol = 3 +_def_excess_bw = 0.35 +_def_gray_code = True +_def_verbose = False +_def_log = False + +_def_freq_alpha = 0.010 +_def_phase_damping = 0.4 +_def_phase_natfreq = 0.25 +_def_timing_alpha = 0.100 +_def_timing_beta = 0.010 +_def_timing_max_dev = 1.5 + +# ///////////////////////////////////////////////////////////////////////////// +# D8PSK modulator +# ///////////////////////////////////////////////////////////////////////////// + +class d8psk_mod(gr.hier_block2): + + def __init__(self, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log): + """ + Hierarchical block for RRC-filtered differential 8PSK modulation. + + The input is a byte stream (unsigned char) and the + output is the complex modulated signal at baseband. + + @param samples_per_symbol: samples per symbol >= 2 + @type samples_per_symbol: integer + @param excess_bw: Root-raised cosine filter excess bandwidth + @type excess_bw: float + @param gray_code: Tell modulator to Gray code the bits + @type gray_code: bool + @param verbose: Print information about modulator? + @type verbose: bool + @param log: Log modulation data to files? + @type log: bool + """ + + gr.hier_block2.__init__(self, "d8psk_mod", + gr.io_signature(1, 1, gr.sizeof_char), # Input signature + gr.io_signature(1, 1, gr.sizeof_gr_complex)) # Output signature + + self._samples_per_symbol = samples_per_symbol + self._excess_bw = excess_bw + self._gray_code = gray_code + + if not isinstance(samples_per_symbol, int) or samples_per_symbol < 2: + raise TypeError, ("sbp must be an integer >= 2, is %d" % samples_per_symbol) + + arity = pow(2,self.bits_per_symbol()) + + # turn bytes into k-bit vectors + self.bytes2chunks = \ + gr.packed_to_unpacked_bb(self.bits_per_symbol(), gr.GR_MSB_FIRST) + + if self._gray_code: + self.symbol_mapper = gr.map_bb(psk.binary_to_gray[arity]) + else: + self.symbol_mapper = gr.map_bb(psk.binary_to_ungray[arity]) + + self.diffenc = gr.diff_encoder_bb(arity) + + rot = 1 + rotated_const = map(lambda pt: pt * rot, psk.constellation[arity]) + self.chunks2symbols = gr.chunks_to_symbols_bc(rotated_const) + + # pulse shaping filter + nfilts = 32 + ntaps = 11 * int(nfilts * self._samples_per_symbol) # make nfilts filters of ntaps each + self.rrc_taps = gr.firdes.root_raised_cosine( + nfilts, # gain + nfilts, # sampling rate based on 32 filters in resampler + 1.0, # symbol rate + self._excess_bw, # excess bandwidth (roll-off factor) + ntaps) + self.rrc_filter = gr.pfb_arb_resampler_ccf(self._samples_per_symbol, self.rrc_taps) + + if verbose: + self._print_verbage() + + if log: + self._setup_logging() + + # Connect & Initialize base class + self.connect(self, self.bytes2chunks, self.symbol_mapper, self.diffenc, + self.chunks2symbols, self.rrc_filter, self) + + def samples_per_symbol(self): + return self._samples_per_symbol + + def bits_per_symbol(self=None): # staticmethod that's also callable on an instance + return 3 + bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM + + def _print_verbage(self): + print "\nModulator:" + print "bits per symbol: %d" % self.bits_per_symbol() + print "Gray code: %s" % self._gray_code + print "RRC roll-off factor: %f" % self._excess_bw + + def _setup_logging(self): + print "Modulation logging turned on." + self.connect(self.bytes2chunks, + gr.file_sink(gr.sizeof_char, "tx_bytes2chunks.dat")) + self.connect(self.symbol_mapper, + gr.file_sink(gr.sizeof_char, "tx_graycoder.dat")) + self.connect(self.diffenc, + gr.file_sink(gr.sizeof_char, "tx_diffenc.dat")) + self.connect(self.chunks2symbols, + gr.file_sink(gr.sizeof_gr_complex, "tx_chunks2symbols.dat")) + self.connect(self.rrc_filter, + gr.file_sink(gr.sizeof_gr_complex, "tx_rrc_filter.dat")) + + def add_options(parser): + """ + Adds 8PSK modulation-specific options to the standard parser + """ + parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw, + help="set RRC excess bandwith factor [default=%default] (PSK)") + parser.add_option("", "--no-gray-code", dest="gray_code", + action="store_false", default=_def_gray_code, + help="disable gray coding on modulated bits (PSK)") + add_options=staticmethod(add_options) + + + def extract_kwargs_from_options(options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return modulation_utils2.extract_kwargs_from_options(d8psk_mod.__init__, + ('self',), options) + extract_kwargs_from_options=staticmethod(extract_kwargs_from_options) + + +# ///////////////////////////////////////////////////////////////////////////// +# D8PSK demodulator +# +# Differentially coherent detection of differentially encoded 8psk +# ///////////////////////////////////////////////////////////////////////////// + +class d8psk_demod(gr.hier_block2): + + def __init__(self, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + freq_alpha=_def_freq_alpha, + phase_damping=_def_phase_damping, + phase_natfreq=_def_phase_natfreq, + timing_alpha=_def_timing_alpha, + timing_max_dev=_def_timing_max_dev, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log, + sync_out=False): + """ + Hierarchical block for RRC-filtered DQPSK demodulation + + The input is the complex modulated signal at baseband. + The output is a stream of bits packed 1 bit per byte (LSB) + + @param samples_per_symbol: samples per symbol >= 2 + @type samples_per_symbol: float + @param excess_bw: Root-raised cosine filter excess bandwidth + @type excess_bw: float + @param freq_alpha: loop filter gain for frequency recovery + @type freq_alpha: float + @param phase_damping: loop filter damping factor for phase/fine frequency recovery + @type phase_damping: float + @param phase_natfreq: loop filter natural frequency for phase/fine frequency recovery + @type phase_natfreq: float + @param timing_alpha: timing loop alpha gain + @type timing_alpha: float + @param timing_max: timing loop maximum rate deviations + @type timing_max: float + @param gray_code: Tell modulator to Gray code the bits + @type gray_code: bool + @param verbose: Print information about modulator? + @type verbose: bool + @param debug: Print modualtion data to files? + @type debug: bool + """ + if sync_out: io_sig_out = gr.io_signaturev(2, 2, (gr.sizeof_char, gr.sizeof_gr_complex)) + else: io_sig_out = gr.io_signature(1, 1, gr.sizeof_char) + + gr.hier_block2.__init__(self, "d8psk_demod", + gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature + io_sig_out) # Output signature + + self._samples_per_symbol = samples_per_symbol + self._excess_bw = excess_bw + self._freq_alpha = freq_alpha + self._freq_beta = 0.25*self._freq_alpha**2 + self._phase_damping = phase_damping + self._phase_natfreq = phase_natfreq + self._timing_alpha = timing_alpha + self._timing_beta = _def_timing_beta + self._timing_max_dev=timing_max_dev + self._gray_code = gray_code + + if samples_per_symbol < 2: + raise TypeError, "sbp must be >= 2, is %d" % samples_per_symbol + + arity = pow(2,self.bits_per_symbol()) + + # Automatic gain control + self.agc = gr.agc2_cc(0.6e-1, 1e-3, 1, 1, 100) + #self.agc = gr.feedforward_agc_cc(16, 2.0) + + # Frequency correction + self.freq_recov = gr.fll_band_edge_cc(self._samples_per_symbol, self._excess_bw, + 11*int(self._samples_per_symbol), + self._freq_alpha, self._freq_beta) + + # symbol timing recovery with RRC data filter + nfilts = 32 + ntaps = 11 * int(samples_per_symbol*nfilts) + taps = gr.firdes.root_raised_cosine(1.41*nfilts, nfilts, + 1.0/float(self._samples_per_symbol), + self._excess_bw, ntaps) + self.time_recov = gr.pfb_clock_sync_ccf(self._samples_per_symbol, + self._timing_alpha, + taps, nfilts, nfilts/2, self._timing_max_dev) + self.time_recov.set_beta(self._timing_beta) + + # Perform phase / fine frequency correction + self.phase_recov = digital_swig.costas_loop_cc(self._phase_damping, + self._phase_natfreq, + arity) + + # Perform Differential decoding on the constellation + self.diffdec = gr.diff_phasor_cc() + + # find closest constellation point + rot = cmath.exp(1j*cmath.pi/8.0) + rotated_const = map(lambda pt: pt * rot, psk.constellation[arity]) + self.slicer = gr.constellation_decoder_cb(rotated_const, range(arity)) + + if self._gray_code: + self.symbol_mapper = gr.map_bb(psk.gray_to_binary[arity]) + else: + self.symbol_mapper = gr.map_bb(psk.ungray_to_binary[arity]) + + # unpack the k bit vector into a stream of bits + self.unpack = gr.unpack_k_bits_bb(self.bits_per_symbol()) + + if verbose: + self._print_verbage() + + if log: + self._setup_logging() + + # Connect + self.connect(self, self.agc, + self.freq_recov, self.time_recov, self.phase_recov, + self.diffdec, self.slicer, self.symbol_mapper, self.unpack, self) + if sync_out: self.connect(self.phase_recov, (self, 1)) + + def samples_per_symbol(self): + return self._samples_per_symbol + + def bits_per_symbol(self=None): # staticmethod that's also callable on an instance + return 3 + bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM + + def _print_verbage(self): + print "\nDemodulator:" + print "bits per symbol: %d" % self.bits_per_symbol() + print "Gray code: %s" % self._gray_code + print "RRC roll-off factor: %.2f" % self._excess_bw + print "FLL gain: %.2f" % self._freq_alpha + print "Timing alpha gain: %.2f" % self._timing_alpha + print "Timing beta gain: %.2f" % self._timing_beta + print "Timing max dev: %.2f" % self._timing_max_dev + print "Phase track alpha: %.2e" % self._phase_alpha + print "Phase track beta: %.2e" % self._phase_beta + + def _setup_logging(self): + print "Modulation logging turned on." + self.connect(self.agc, + gr.file_sink(gr.sizeof_gr_complex, "rx_agc.dat")) + self.connect(self.freq_recov, + gr.file_sink(gr.sizeof_gr_complex, "rx_freq_recov.dat")) + self.connect(self.time_recov, + gr.file_sink(gr.sizeof_gr_complex, "rx_time_recov.dat")) + self.connect(self.phase_recov, + gr.file_sink(gr.sizeof_gr_complex, "rx_phase_recov.dat")) + self.connect(self.diffdec, + gr.file_sink(gr.sizeof_gr_complex, "rx_diffdec.dat")) + self.connect(self.slicer, + gr.file_sink(gr.sizeof_char, "rx_slicer.dat")) + self.connect(self.symbol_mapper, + gr.file_sink(gr.sizeof_char, "rx_gray_decoder.dat")) + self.connect(self.unpack, + gr.file_sink(gr.sizeof_char, "rx_unpack.dat")) + + def add_options(parser): + """ + Adds D8PSK demodulation-specific options to the standard parser + """ + parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw, + help="set RRC excess bandwith factor [default=%default] (PSK)") + parser.add_option("", "--no-gray-code", dest="gray_code", + action="store_false", default=_def_gray_code, + help="disable gray coding on modulated bits (PSK)") + parser.add_option("", "--freq-alpha", type="float", default=_def_freq_alpha, + help="set frequency lock loop alpha gain value [default=%default] (PSK)") + parser.add_option("", "--phase-alpha", type="float", default=_def_phase_alpha, + help="set phase tracking loop alpha value [default=%default] (PSK)") + parser.add_option("", "--timing-alpha", type="float", default=_def_timing_alpha, + help="set timing symbol sync loop gain alpha value [default=%default] (GMSK/PSK)") + parser.add_option("", "--timing-beta", type="float", default=_def_timing_beta, + help="set timing symbol sync loop gain beta value [default=%default] (GMSK/PSK)") + parser.add_option("", "--timing-max-dev", type="float", default=_def_timing_max_dev, + help="set timing symbol sync loop maximum deviation [default=%default] (GMSK/PSK)") + add_options=staticmethod(add_options) + + def extract_kwargs_from_options(options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return modulation_utils2.extract_kwargs_from_options( + d8psk_demod.__init__, ('self',), options) + extract_kwargs_from_options=staticmethod(extract_kwargs_from_options) + + +# +# Add these to the mod/demod registry +# +modulation_utils2.add_type_1_mod('d8psk', d8psk_mod) +modulation_utils2.add_type_1_demod('d8psk', d8psk_demod) diff --git a/gr-digital/python/dbpsk.py b/gr-digital/python/dbpsk.py new file mode 100644 index 000000000..2e9b756e6 --- /dev/null +++ b/gr-digital/python/dbpsk.py @@ -0,0 +1,370 @@ +# +# Copyright 2009,2010,2011 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. +# + +# See gnuradio-examples/python/digital for examples + +""" +differential BPSK modulation and demodulation. +""" + +from gnuradio import gr, modulation_utils2 +from math import pi, sqrt, ceil +import digital_swig, psk +import cmath +from pprint import pprint + +# default values (used in __init__ and add_options) +_def_samples_per_symbol = 2 +_def_excess_bw = 0.35 +_def_gray_code = True +_def_verbose = False +_def_log = False + +_def_freq_alpha = 0.010 +_def_phase_damping = 0.4 +_def_phase_natfreq = 0.25 +_def_timing_alpha = 0.100 +_def_timing_beta = 0.010 +_def_timing_max_dev = 1.5 + + +# ///////////////////////////////////////////////////////////////////////////// +# DBPSK modulator +# ///////////////////////////////////////////////////////////////////////////// + +class dbpsk_mod(gr.hier_block2): + + def __init__(self, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log): + """ + Hierarchical block for RRC-filtered differential BPSK modulation. + + The input is a byte stream (unsigned char) and the + output is the complex modulated signal at baseband. + + @param samples_per_symbol: samples per symbol >= 2 + @type samples_per_symbol: integer + @param excess_bw: Root-raised cosine filter excess bandwidth + @type excess_bw: float + @param gray_code: Tell modulator to Gray code the bits + @type gray_code: bool + @param verbose: Print information about modulator? + @type verbose: bool + @param log: Log modulation data to files? + @type log: bool + """ + + gr.hier_block2.__init__(self, "dbpsk_mod", + gr.io_signature(1, 1, gr.sizeof_char), # Input signature + gr.io_signature(1, 1, gr.sizeof_gr_complex)) # Output signature + + self._samples_per_symbol = samples_per_symbol + self._excess_bw = excess_bw + self._gray_code = gray_code + + if self._samples_per_symbol < 2: + raise TypeError, ("sbp must be an integer >= 2, is %d" % self._samples_per_symbol) + + arity = pow(2,self.bits_per_symbol()) + + # turn bytes into k-bit vectors + self.bytes2chunks = \ + gr.packed_to_unpacked_bb(self.bits_per_symbol(), gr.GR_MSB_FIRST) + + if self._gray_code: + self.symbol_mapper = gr.map_bb(psk.binary_to_gray[arity]) + else: + self.symbol_mapper = gr.map_bb(psk.binary_to_ungray[arity]) + + self.diffenc = gr.diff_encoder_bb(arity) + + self.chunks2symbols = gr.chunks_to_symbols_bc(psk.constellation[arity]) + + # pulse shaping filter + nfilts = 32 + ntaps = nfilts * 11 * int(self._samples_per_symbol) # make nfilts filters of ntaps each + self.rrc_taps = gr.firdes.root_raised_cosine( + nfilts, # gain + nfilts, # sampling rate based on 32 filters in resampler + 1.0, # symbol rate + self._excess_bw, # excess bandwidth (roll-off factor) + ntaps) + self.rrc_filter = gr.pfb_arb_resampler_ccf(self._samples_per_symbol, self.rrc_taps) + + # Connect + self.connect(self, self.bytes2chunks, self.symbol_mapper, self.diffenc, + self.chunks2symbols, self.rrc_filter, self) + + if verbose: + self._print_verbage() + + if log: + self._setup_logging() + + + def samples_per_symbol(self): + return self._samples_per_symbol + + def bits_per_symbol(self=None): # static method that's also callable on an instance + return 1 + bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM + + def add_options(parser): + """ + Adds DBPSK modulation-specific options to the standard parser + """ + parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw, + help="set RRC excess bandwith factor [default=%default]") + parser.add_option("", "--no-gray-code", dest="gray_code", + action="store_false", default=True, + help="disable gray coding on modulated bits (PSK)") + add_options=staticmethod(add_options) + + def extract_kwargs_from_options(options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return modulation_utils2.extract_kwargs_from_options(dbpsk_mod.__init__, + ('self',), options) + extract_kwargs_from_options=staticmethod(extract_kwargs_from_options) + + + def _print_verbage(self): + print "\nModulator:" + print "bits per symbol: %d" % self.bits_per_symbol() + print "Gray code: %s" % self._gray_code + print "RRC roll-off factor: %.2f" % self._excess_bw + + def _setup_logging(self): + print "Modulation logging turned on." + self.connect(self.bytes2chunks, + gr.file_sink(gr.sizeof_char, "tx_bytes2chunks.dat")) + self.connect(self.symbol_mapper, + gr.file_sink(gr.sizeof_char, "tx_graycoder.dat")) + self.connect(self.diffenc, + gr.file_sink(gr.sizeof_char, "tx_diffenc.dat")) + self.connect(self.chunks2symbols, + gr.file_sink(gr.sizeof_gr_complex, "tx_chunks2symbols.dat")) + self.connect(self.rrc_filter, + gr.file_sink(gr.sizeof_gr_complex, "tx_rrc_filter.dat")) + + +# ///////////////////////////////////////////////////////////////////////////// +# DBPSK demodulator +# +# Differentially coherent detection of differentially encoded BPSK +# ///////////////////////////////////////////////////////////////////////////// + +class dbpsk_demod(gr.hier_block2): + + def __init__(self, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + freq_alpha=_def_freq_alpha, + phase_damping=_def_phase_damping, + phase_natfreq=_def_phase_natfreq, + timing_alpha=_def_timing_alpha, + timing_max_dev=_def_timing_max_dev, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log, + sync_out=False): + """ + Hierarchical block for RRC-filtered differential BPSK demodulation + + The input is the complex modulated signal at baseband. + The output is a stream of bits packed 1 bit per byte (LSB) + + @param samples_per_symbol: samples per symbol >= 2 + @type samples_per_symbol: float + @param excess_bw: Root-raised cosine filter excess bandwidth + @type excess_bw: float + @param freq_alpha: loop filter gain for frequency recovery + @type freq_alpha: float + @param phase_damping: loop filter damping factor for phase/fine frequency recovery + @type phase_damping: float + @param phase_natfreq: loop filter natural frequency for phase/fine frequency recovery + @type phase_natfreq: float + @param timing_alpha: loop alpha gain for timing recovery + @type timing_alpha: float + @param timing_max: timing loop maximum rate deviations + @type timing_max: float + @param gray_code: Tell modulator to Gray code the bits + @type gray_code: bool + @param verbose: Print information about modulator? + @type verbose: bool + @param log: Print modualtion data to files? + @type log: bool + @param sync_out: Output a sync signal on :1? + @type sync_out: bool + """ + if sync_out: io_sig_out = gr.io_signaturev(2, 2, (gr.sizeof_char, gr.sizeof_gr_complex)) + else: io_sig_out = gr.io_signature(1, 1, gr.sizeof_char) + + gr.hier_block2.__init__(self, "dbpsk_demod", + gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature + io_sig_out) # Output signature + + self._samples_per_symbol = samples_per_symbol + self._excess_bw = excess_bw + self._freq_alpha = freq_alpha + self._freq_beta = 0.10*self._freq_alpha + self._phase_damping = phase_damping + self._phase_natfreq = phase_natfreq + self._timing_alpha = timing_alpha + self._timing_beta = _def_timing_beta + self._timing_max_dev=timing_max_dev + self._gray_code = gray_code + + if samples_per_symbol < 2: + raise TypeError, "samples_per_symbol must be >= 2, is %r" % (samples_per_symbol,) + + arity = pow(2,self.bits_per_symbol()) + + # Automatic gain control + self.agc = gr.agc2_cc(0.6e-1, 1e-3, 1, 1, 100) + #self.agc = gr.feedforward_agc_cc(16, 1.0) + + # Frequency correction + self.freq_recov = gr.fll_band_edge_cc(self._samples_per_symbol, self._excess_bw, + 11*int(self._samples_per_symbol), + self._freq_alpha, self._freq_beta) + + # symbol timing recovery with RRC data filter + nfilts = 32 + ntaps = 11 * int(self._samples_per_symbol*nfilts) + taps = gr.firdes.root_raised_cosine(nfilts, nfilts, + 1.0/float(self._samples_per_symbol), + self._excess_bw, ntaps) + self.time_recov = gr.pfb_clock_sync_ccf(self._samples_per_symbol, + self._timing_alpha, + taps, nfilts, nfilts/2, self._timing_max_dev) + self.time_recov.set_beta(self._timing_beta) + + # Perform phase / fine frequency correction + self.phase_recov = digital_swig.costas_loop_cc(self._phase_damping, + self._phase_natfreq, + arity) + + # Do differential decoding based on phase change of symbols + self.diffdec = gr.diff_phasor_cc() + + # find closest constellation point + rot = 1 + rotated_const = map(lambda pt: pt * rot, psk.constellation[arity]) + self.slicer = gr.constellation_decoder_cb(rotated_const, range(arity)) + + if self._gray_code: + self.symbol_mapper = gr.map_bb(psk.gray_to_binary[arity]) + else: + self.symbol_mapper = gr.map_bb(psk.ungray_to_binary[arity]) + + # unpack the k bit vector into a stream of bits + self.unpack = gr.unpack_k_bits_bb(self.bits_per_symbol()) + + if verbose: + self._print_verbage() + + if log: + self._setup_logging() + + # Connect + self.connect(self, self.agc, + self.freq_recov, self.time_recov, self.phase_recov, + self.diffdec, self.slicer, self.symbol_mapper, self.unpack, self) + if sync_out: self.connect(self.phase_recov, (self, 1)) + + def samples_per_symbol(self): + return self._samples_per_symbol + + def bits_per_symbol(self=None): # staticmethod that's also callable on an instance + return 1 + bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM + + def _print_verbage(self): + print "\nDemodulator:" + print "bits per symbol: %d" % self.bits_per_symbol() + print "Gray code: %s" % self._gray_code + print "RRC roll-off factor: %.2f" % self._excess_bw + print "FLL gain: %.2e" % self._freq_alpha + print "Timing alpha gain: %.2e" % self._timing_alpha + print "Timing beta gain: %.2e" % self._timing_beta + print "Timing max dev: %.2f" % self._timing_max_dev + print "Phase track alpha: %.2e" % self._phase_alpha + print "Phase track beta: %.2e" % self._phase_beta + + def _setup_logging(self): + print "Modulation logging turned on." + self.connect(self.agc, + gr.file_sink(gr.sizeof_gr_complex, "rx_agc.dat")) + self.connect(self.freq_recov, + gr.file_sink(gr.sizeof_gr_complex, "rx_freq_recov.dat")) + self.connect(self.time_recov, + gr.file_sink(gr.sizeof_gr_complex, "rx_time_recov.dat")) + self.connect(self.phase_recov, + gr.file_sink(gr.sizeof_gr_complex, "rx_phase_recov.dat")) + self.connect(self.diffdec, + gr.file_sink(gr.sizeof_gr_complex, "rx_diffdec.dat")) + self.connect(self.slicer, + gr.file_sink(gr.sizeof_char, "rx_slicer.dat")) + self.connect(self.symbol_mapper, + gr.file_sink(gr.sizeof_char, "rx_symbol_mapper.dat")) + self.connect(self.unpack, + gr.file_sink(gr.sizeof_char, "rx_unpack.dat")) + + def add_options(parser): + """ + Adds DBPSK demodulation-specific options to the standard parser + """ + parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw, + help="set RRC excess bandwith factor [default=%default] (PSK)") + parser.add_option("", "--no-gray-code", dest="gray_code", + action="store_false", default=_def_gray_code, + help="disable gray coding on modulated bits (PSK)") + parser.add_option("", "--freq-alpha", type="float", default=_def_freq_alpha, + help="set frequency lock loop alpha gain value [default=%default] (PSK)") + parser.add_option("", "--phase-alpha", type="float", default=_def_phase_alpha, + help="set phase tracking loop alpha value [default=%default] (PSK)") + parser.add_option("", "--timing-alpha", type="float", default=_def_timing_alpha, + help="set timing symbol sync loop gain alpha value [default=%default] (GMSK/PSK)") + parser.add_option("", "--timing-beta", type="float", default=_def_timing_beta, + help="set timing symbol sync loop gain beta value [default=%default] (GMSK/PSK)") + parser.add_option("", "--timing-max-dev", type="float", default=_def_timing_max_dev, + help="set timing symbol sync loop maximum deviation [default=%default] (GMSK/PSK)") + add_options=staticmethod(add_options) + + def extract_kwargs_from_options(options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return modulation_utils2.extract_kwargs_from_options( + dbpsk_demod.__init__, ('self',), options) + extract_kwargs_from_options=staticmethod(extract_kwargs_from_options) + +# +# Add these to the mod/demod registry +# +modulation_utils2.add_type_1_mod('dbpsk3', dbpsk_mod) +modulation_utils2.add_type_1_demod('dbpsk3', dbpsk_demod) diff --git a/gr-digital/python/dqpsk.py b/gr-digital/python/dqpsk.py new file mode 100644 index 000000000..29afd5530 --- /dev/null +++ b/gr-digital/python/dqpsk.py @@ -0,0 +1,374 @@ +# +# Copyright 2009,2010,2011 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. +# + +# See gnuradio-examples/python/digital for examples + +""" +differential QPSK modulation and demodulation. +""" + +from gnuradio import gr, modulation_utils2 +from math import pi, sqrt +import digital_swig, psk +import cmath +from pprint import pprint + +# default values (used in __init__ and add_options) +_def_samples_per_symbol = 2 +_def_excess_bw = 0.35 +_def_gray_code = True +_def_verbose = False +_def_log = False + +_def_freq_alpha = 0.010 +_def_phase_damping = 0.4 +_def_phase_natfreq = 0.25 +_def_timing_alpha = 0.100 +_def_timing_beta = 0.010 +_def_timing_max_dev = 1.5 + + +# ///////////////////////////////////////////////////////////////////////////// +# DQPSK modulator +# ///////////////////////////////////////////////////////////////////////////// + +class dqpsk_mod(gr.hier_block2): + + def __init__(self, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log): + """ + Hierarchical block for RRC-filtered QPSK modulation. + + The input is a byte stream (unsigned char) and the + output is the complex modulated signal at baseband. + + @param samples_per_symbol: samples per symbol >= 2 + @type samples_per_symbol: integer + @param excess_bw: Root-raised cosine filter excess bandwidth + @type excess_bw: float + @param gray_code: Tell modulator to Gray code the bits + @type gray_code: bool + @param verbose: Print information about modulator? + @type verbose: bool + @param debug: Print modualtion data to files? + @type debug: bool + """ + + gr.hier_block2.__init__(self, "dqpsk_mod", + gr.io_signature(1, 1, gr.sizeof_char), # Input signature + gr.io_signature(1, 1, gr.sizeof_gr_complex)) # Output signature + + self._samples_per_symbol = samples_per_symbol + self._excess_bw = excess_bw + self._gray_code = gray_code + + if samples_per_symbol < 2: + raise TypeError, ("sbp must be >= 2, is %f" % samples_per_symbol) + + arity = pow(2,self.bits_per_symbol()) + + # turn bytes into k-bit vectors + self.bytes2chunks = \ + gr.packed_to_unpacked_bb(self.bits_per_symbol(), gr.GR_MSB_FIRST) + + if self._gray_code: + self.symbol_mapper = gr.map_bb(psk.binary_to_gray[arity]) + else: + self.symbol_mapper = gr.map_bb(psk.binary_to_ungray[arity]) + + self.diffenc = gr.diff_encoder_bb(arity) + + rot = .707 + .707j + rotated_const = map(lambda pt: pt * rot, psk.constellation[arity]) + self.chunks2symbols = gr.chunks_to_symbols_bc(rotated_const) + + # pulse shaping filter + nfilts = 32 + ntaps = 11 * int(nfilts * self._samples_per_symbol) # make nfilts filters of ntaps each + self.rrc_taps = gr.firdes.root_raised_cosine( + nfilts, # gain + nfilts, # sampling rate based on 32 filters in resampler + 1.0, # symbol rate + self._excess_bw, # excess bandwidth (roll-off factor) + ntaps) + self.rrc_filter = gr.pfb_arb_resampler_ccf(self._samples_per_symbol, self.rrc_taps) + + if verbose: + self._print_verbage() + + if log: + self._setup_logging() + + # Connect & Initialize base class + self.connect(self, self.bytes2chunks, self.symbol_mapper, self.diffenc, + self.chunks2symbols, self.rrc_filter, self) + + def samples_per_symbol(self): + return self._samples_per_symbol + + def bits_per_symbol(self=None): # staticmethod that's also callable on an instance + return 2 + bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM + + def _print_verbage(self): + print "\nModulator:" + print "bits per symbol: %d" % self.bits_per_symbol() + print "Gray code: %s" % self._gray_code + print "RRC roll-off factor: %f" % self._excess_bw + + def _setup_logging(self): + print "Modulation logging turned on." + self.connect(self.bytes2chunks, + gr.file_sink(gr.sizeof_char, "tx_bytes2chunks.dat")) + self.connect(self.symbol_mapper, + gr.file_sink(gr.sizeof_char, "tx_graycoder.dat")) + self.connect(self.diffenc, + gr.file_sink(gr.sizeof_char, "tx_diffenc.dat")) + self.connect(self.chunks2symbols, + gr.file_sink(gr.sizeof_gr_complex, "tx_chunks2symbols.dat")) + self.connect(self.rrc_filter, + gr.file_sink(gr.sizeof_gr_complex, "tx_rrc_filter.dat")) + + def add_options(parser): + """ + Adds QPSK modulation-specific options to the standard parser + """ + parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw, + help="set RRC excess bandwith factor [default=%default] (PSK)") + parser.add_option("", "--no-gray-code", dest="gray_code", + action="store_false", default=_def_gray_code, + help="disable gray coding on modulated bits (PSK)") + add_options=staticmethod(add_options) + + + def extract_kwargs_from_options(options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return modulation_utils2.extract_kwargs_from_options(dqpsk_mod.__init__, + ('self',), options) + extract_kwargs_from_options=staticmethod(extract_kwargs_from_options) + + +# ///////////////////////////////////////////////////////////////////////////// +# DQPSK demodulator +# +# Differentially coherent detection of differentially encoded qpsk +# ///////////////////////////////////////////////////////////////////////////// + +class dqpsk_demod(gr.hier_block2): + + def __init__(self, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + freq_alpha=_def_freq_alpha, + phase_damping=_def_phase_damping, + phase_natfreq=_def_phase_natfreq, + timing_alpha=_def_timing_alpha, + timing_max_dev=_def_timing_max_dev, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log, + sync_out=False): + """ + Hierarchical block for RRC-filtered DQPSK demodulation + + The input is the complex modulated signal at baseband. + The output is a stream of bits packed 1 bit per byte (LSB) + + @param samples_per_symbol: samples per symbol >= 2 + @type samples_per_symbol: float + @param excess_bw: Root-raised cosine filter excess bandwidth + @type excess_bw: float + @param freq_alpha: loop filter gain for frequency recovery + @type freq_alpha: float + @param phase_damping: loop filter damping factor for phase/fine frequency recovery + @type phase_damping: float + @param phase_natfreq: loop filter natural frequency for phase/fine frequency recovery + @type phase_natfreq: float + @param timing_alpha: timing loop alpha gain + @type timing_alpha: float + @param timing_max: timing loop maximum rate deviations + @type timing_max: float + @param gray_code: Tell modulator to Gray code the bits + @type gray_code: bool + @param verbose: Print information about modulator? + @type verbose: bool + @param log: Print modualtion data to files? + @type log: bool + @param sync_out: Output a sync signal on :1? + @type sync_out: bool + """ + if sync_out: io_sig_out = gr.io_signaturev(2, 2, (gr.sizeof_char, gr.sizeof_gr_complex)) + else: io_sig_out = gr.io_signature(1, 1, gr.sizeof_char) + + gr.hier_block2.__init__(self, "dqpsk_demod", + gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature + io_sig_out) # Output signature + + self._samples_per_symbol = samples_per_symbol + self._excess_bw = excess_bw + self._freq_alpha = freq_alpha + self._freq_beta = 0.25*self._freq_alpha**2 + self._phase_damping = phase_damping + self._phase_natfreq = phase_natfreq + self._timing_alpha = timing_alpha + self._timing_beta = _def_timing_beta + self._timing_max_dev=timing_max_dev + self._gray_code = gray_code + + if samples_per_symbol < 2: + raise TypeError, "sbp must be >= 2, is %d" % samples_per_symbol + + arity = pow(2,self.bits_per_symbol()) + + # Automatic gain control + self.agc = gr.agc2_cc(0.6e-1, 1e-3, 1, 1, 100) + #self.agc = gr.feedforward_agc_cc(16, 2.0) + + # Frequency correction + self.freq_recov = gr.fll_band_edge_cc(self._samples_per_symbol, self._excess_bw, + 11*int(self._samples_per_symbol), + self._freq_alpha, self._freq_beta) + + + # symbol timing recovery with RRC data filter + nfilts = 32 + ntaps = 11 * int(samples_per_symbol*nfilts) + taps = gr.firdes.root_raised_cosine(nfilts, nfilts, + 1.0/float(self._samples_per_symbol), + self._excess_bw, ntaps) + self.time_recov = gr.pfb_clock_sync_ccf(self._samples_per_symbol, + self._timing_alpha, + taps, nfilts, nfilts/2, self._timing_max_dev) + self.time_recov.set_beta(self._timing_beta) + + + # Perform phase / fine frequency correction + self.phase_recov = digital_swig.costas_loop_cc(self._phase_damping, + self._phase_natfreq, + arity) + + # Perform Differential decoding on the constellation + self.diffdec = gr.diff_phasor_cc() + + # find closest constellation point + rot = 1 + rotated_const = map(lambda pt: pt * rot, psk.constellation[arity]) + self.slicer = gr.constellation_decoder_cb(rotated_const, range(arity)) + + if self._gray_code: + self.symbol_mapper = gr.map_bb(psk.gray_to_binary[arity]) + else: + self.symbol_mapper = gr.map_bb(psk.ungray_to_binary[arity]) + + # unpack the k bit vector into a stream of bits + self.unpack = gr.unpack_k_bits_bb(self.bits_per_symbol()) + + if verbose: + self._print_verbage() + + if log: + self._setup_logging() + + # Connect + self.connect(self, self.agc, + self.freq_recov, self.time_recov, self.phase_recov, + self.diffdec, self.slicer, self.symbol_mapper, self.unpack, self) + if sync_out: self.connect(self.phase_recov, (self, 1)) + + def samples_per_symbol(self): + return self._samples_per_symbol + + def bits_per_symbol(self=None): # staticmethod that's also callable on an instance + return 2 + bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM + + def _print_verbage(self): + print "\nDemodulator:" + print "bits per symbol: %d" % self.bits_per_symbol() + print "Gray code: %s" % self._gray_code + print "RRC roll-off factor: %.2f" % self._excess_bw + print "FLL gain: %.2f" % self._freq_alpha + print "Timing alpha gain: %.2f" % self._timing_alpha + print "Timing beta gain: %.2f" % self._timing_beta + print "Timing max dev: %.2f" % self._timing_max_dev + print "Phase track alpha: %.2e" % self._phase_alpha + print "Phase track beta: %.2e" % self._phase_beta + + def _setup_logging(self): + print "Modulation logging turned on." + self.connect(self.agc, + gr.file_sink(gr.sizeof_gr_complex, "rx_agc.dat")) + self.connect(self.freq_recov, + gr.file_sink(gr.sizeof_gr_complex, "rx_freq_recov.dat")) + self.connect(self.time_recov, + gr.file_sink(gr.sizeof_gr_complex, "rx_time_recov.dat")) + self.connect(self.phase_recov, + gr.file_sink(gr.sizeof_gr_complex, "rx_phase_recov.dat")) + self.connect(self.diffdec, + gr.file_sink(gr.sizeof_gr_complex, "rx_diffdec.dat")) + self.connect(self.slicer, + gr.file_sink(gr.sizeof_char, "rx_slicer.dat")) + self.connect(self.symbol_mapper, + gr.file_sink(gr.sizeof_char, "rx_gray_decoder.dat")) + self.connect(self.unpack, + gr.file_sink(gr.sizeof_char, "rx_unpack.dat")) + + def add_options(parser): + """ + Adds DQPSK demodulation-specific options to the standard parser + """ + parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw, + help="set RRC excess bandwith factor [default=%default] (PSK)") + parser.add_option("", "--no-gray-code", dest="gray_code", + action="store_false", default=_def_gray_code, + help="disable gray coding on modulated bits (PSK)") + parser.add_option("", "--freq-alpha", type="float", default=_def_freq_alpha, + help="set frequency lock loop alpha gain value [default=%default] (PSK)") + parser.add_option("", "--phase-alpha", type="float", default=_def_phase_alpha, + help="set phase tracking loop alpha value [default=%default] (PSK)") + parser.add_option("", "--timing-alpha", type="float", default=_def_timing_alpha, + help="set timing symbol sync loop gain alpha value [default=%default] (GMSK/PSK)") + parser.add_option("", "--timing-beta", type="float", default=_def_timing_beta, + help="set timing symbol sync loop gain beta value [default=%default] (GMSK/PSK)") + parser.add_option("", "--timing-max-dev", type="float", default=_def_timing_max_dev, + help="set timing symbol sync loop maximum deviation [default=%default] (GMSK/PSK)") + add_options=staticmethod(add_options) + + def extract_kwargs_from_options(options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return modulation_utils2.extract_kwargs_from_options( + dqpsk_demod.__init__, ('self',), options) + extract_kwargs_from_options=staticmethod(extract_kwargs_from_options) + + +# +# Add these to the mod/demod registry +# +modulation_utils2.add_type_1_mod('dqpsk', dqpsk_mod) +modulation_utils2.add_type_1_demod('dqpsk', dqpsk_demod) diff --git a/gr-digital/python/generic_mod_demod.py b/gr-digital/python/generic_mod_demod.py new file mode 100644 index 000000000..f8051db0a --- /dev/null +++ b/gr-digital/python/generic_mod_demod.py @@ -0,0 +1,397 @@ +# +# Copyright 2005,2006,2007,2009,2011 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. +# + +# See gnuradio-examples/python/digital for examples + +""" +Generic modulation and demodulation. +""" + +from gnuradio import gr +from modulation_utils2 import extract_kwargs_from_options_for_class +#from gnuradio.digital.utils import mod_codes +from utils import mod_codes +import digital_swig + +# default values (used in __init__ and add_options) +_def_samples_per_symbol = 2 +_def_excess_bw = 0.35 +_def_verbose = False +_def_log = False + +# Frequency correction +_def_freq_alpha = 0.010 +# Symbol timing recovery +_def_timing_alpha = 0.100 +_def_timing_beta = 0.010 +_def_timing_max_dev = 1.5 +# Fine frequency / Phase correction +_def_phase_alpha = 0.1 +# Number of points in constellation +_def_constellation_points = 16 +# Whether differential coding is used. +_def_differential = True + +def add_common_options(parser): + """ + Sets options common to both modulator and demodulator. + """ + parser.add_option("-p", "--constellation-points", type="int", default=_def_constellation_points, + help="set the number of constellation points (must be a power of 2 (power of 4 for QAM) [default=%default]") + parser.add_option("", "--differential", action="store_true", dest="differential", default=True, + help="use differential encoding [default=%default]") + parser.add_option("", "--not-differential", action="store_false", dest="differential", + help="do not use differential encoding [default=%default]") + parser.add_option("", "--mod-code", type="choice", choices=mod_codes.codes, + default=mod_codes.NO_CODE, + help="Select modulation code from: %s [default=%%default]" + % (', '.join(mod_codes.codes),)) + parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw, + help="set RRC excess bandwith factor [default=%default]") + + +# ///////////////////////////////////////////////////////////////////////////// +# Generic modulator +# ///////////////////////////////////////////////////////////////////////////// + +class generic_mod(gr.hier_block2): + + def __init__(self, constellation, + differential=_def_differential, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + verbose=_def_verbose, + log=_def_log): + """ + Hierarchical block for RRC-filtered differential generic modulation. + + The input is a byte stream (unsigned char) and the + output is the complex modulated signal at baseband. + + @param constellation: determines the modulation type + @type constellation: gnuradio.digital.gr_constellation + @param samples_per_symbol: samples per baud >= 2 + @type samples_per_symbol: integer + @param excess_bw: Root-raised cosine filter excess bandwidth + @type excess_bw: float + @param verbose: Print information about modulator? + @type verbose: bool + @param log: Log modulation data to files? + @type log: bool + """ + + gr.hier_block2.__init__(self, "generic_mod", + gr.io_signature(1, 1, gr.sizeof_char), # Input signature + gr.io_signature(1, 1, gr.sizeof_gr_complex)) # Output signature + + self._constellation = constellation.base() + self._samples_per_symbol = samples_per_symbol + self._excess_bw = excess_bw + self._differential = differential + + if not isinstance(self._samples_per_symbol, int) or self._samples_per_symbol < 2: + raise TypeError, ("sbp must be an integer >= 2, is %d" % self._samples_per_symbol) + + ntaps = 11 * self._samples_per_symbol + + arity = pow(2,self.bits_per_symbol()) + + # turn bytes into k-bit vectors + self.bytes2chunks = \ + gr.packed_to_unpacked_bb(self.bits_per_symbol(), gr.GR_MSB_FIRST) + + if self._constellation.apply_pre_diff_code(): + self.symbol_mapper = gr.map_bb(self._constellation.pre_diff_code()) + + if differential: + self.diffenc = gr.diff_encoder_bb(arity) + + self.chunks2symbols = gr.chunks_to_symbols_bc(self._constellation.points()) + + # pulse shaping filter + self.rrc_taps = gr.firdes.root_raised_cosine( + self._samples_per_symbol, # gain (samples_per_symbol since we're + # interpolating by samples_per_symbol) + self._samples_per_symbol, # sampling rate + 1.0, # symbol rate + self._excess_bw, # excess bandwidth (roll-off factor) + ntaps) + self.rrc_filter = gr.interp_fir_filter_ccf(self._samples_per_symbol, + self.rrc_taps) + + # Connect + blocks = [self, self.bytes2chunks] + if self._constellation.apply_pre_diff_code(): + blocks.append(self.symbol_mapper) + if differential: + blocks.append(self.diffenc) + blocks += [self.chunks2symbols, self.rrc_filter, self] + self.connect(*blocks) + + if verbose: + self._print_verbage() + + if log: + self._setup_logging() + + + def samples_per_symbol(self): + return self._samples_per_symbol + + def bits_per_symbol(self): # static method that's also callable on an instance + return self._constellation.bits_per_symbol() + + def add_options(parser): + """ + Adds generic modulation options to the standard parser + """ + add_common_options(parser) + add_options=staticmethod(add_options) + + def extract_kwargs_from_options(cls, options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return extract_kwargs_from_options_for_class(cls, options) + extract_kwargs_from_options=classmethod(extract_kwargs_from_options) + + + def _print_verbage(self): + print "\nModulator:" + print "bits per symbol: %d" % self.bits_per_symbol() + print "RRC roll-off factor: %.2f" % self._excess_bw + + def _setup_logging(self): + print "Modulation logging turned on." + self.connect(self.bytes2chunks, + gr.file_sink(gr.sizeof_char, "tx_bytes2chunks.dat")) + if self._constellation.apply_pre_diff_code(): + self.connect(self.symbol_mapper, + gr.file_sink(gr.sizeof_char, "tx_symbol_mapper.dat")) + if self._differential: + self.connect(self.diffenc, + gr.file_sink(gr.sizeof_char, "tx_diffenc.dat")) + self.connect(self.chunks2symbols, + gr.file_sink(gr.sizeof_gr_complex, "tx_chunks2symbols.dat")) + self.connect(self.rrc_filter, + gr.file_sink(gr.sizeof_gr_complex, "tx_rrc_filter.dat")) + + +# ///////////////////////////////////////////////////////////////////////////// +# Generic demodulator +# +# Differentially coherent detection of differentially encoded generically +# modulated signal. +# ///////////////////////////////////////////////////////////////////////////// + +class generic_demod(gr.hier_block2): + + def __init__(self, constellation, + samples_per_symbol=_def_samples_per_symbol, + differential=_def_differential, + excess_bw=_def_excess_bw, + freq_alpha=_def_freq_alpha, + timing_alpha=_def_timing_alpha, + timing_max_dev=_def_timing_max_dev, + phase_alpha=_def_phase_alpha, + verbose=_def_verbose, + log=_def_log): + """ + Hierarchical block for RRC-filtered differential generic demodulation. + + The input is the complex modulated signal at baseband. + The output is a stream of bits packed 1 bit per byte (LSB) + + @param constellation: determines the modulation type + @type constellation: gnuradio.digital.gr_constellation + @param samples_per_symbol: samples per symbol >= 2 + @type samples_per_symbol: float + @param excess_bw: Root-raised cosine filter excess bandwidth + @type excess_bw: float + @param freq_alpha: loop filter gain for frequency recovery + @type freq_alpha: float + @param timing_alpha: loop alpha gain for timing recovery + @type timing_alpha: float + @param timing_max_dev: timing loop maximum rate deviations + @type timing_max_dev: float + @param phase_alpha: loop filter gain in phase loop + @type phase_alphas: float + @param verbose: Print information about modulator? + @type verbose: bool + @param debug: Print modualtion data to files? + @type debug: bool + """ + + gr.hier_block2.__init__(self, "generic_demod", + gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature + gr.io_signature(1, 1, gr.sizeof_char)) # Output signature + + self._constellation = constellation.base() + self._samples_per_symbol = samples_per_symbol + self._excess_bw = excess_bw + self._phase_alpha = phase_alpha + self._freq_alpha = freq_alpha + self._freq_beta = 0.10*self._freq_alpha + self._timing_alpha = timing_alpha + self._timing_beta = _def_timing_beta + self._timing_max_dev=timing_max_dev + self._differential = differential + + if not isinstance(self._samples_per_symbol, int) or self._samples_per_symbol < 2: + raise TypeError, ("sbp must be an integer >= 2, is %d" % self._samples_per_symbol) + + arity = pow(2,self.bits_per_symbol()) + + # Automatic gain control + self.agc = gr.agc2_cc(0.6e-1, 1e-3, 1, 1, 100) + + # Frequency correction + self.freq_recov = gr.fll_band_edge_cc(self._samples_per_symbol, self._excess_bw, + 11*int(self._samples_per_symbol), + self._freq_alpha, self._freq_beta) + + # symbol timing recovery with RRC data filter + nfilts = 32 + ntaps = 11 * int(self._samples_per_symbol*nfilts) + taps = gr.firdes.root_raised_cosine(nfilts, nfilts, + 1.0/float(self._samples_per_symbol), + self._excess_bw, ntaps) + self.time_recov = gr.pfb_clock_sync_ccf(self._samples_per_symbol, + self._timing_alpha, + taps, nfilts, nfilts/2, self._timing_max_dev) + self.time_recov.set_beta(self._timing_beta) + + #self._phase_beta = 0.25 * self._phase_alpha * self._phase_alpha + self._phase_beta = 0.25 * self._phase_alpha * self._phase_alpha + fmin = -0.25 + fmax = 0.25 + + self.receiver = digital_swig.constellation_receiver_cb( + self._constellation, + self._phase_alpha, self._phase_beta, + fmin, fmax) + + # Do differential decoding based on phase change of symbols + if differential: + self.diffdec = gr.diff_decoder_bb(arity) + + if self._constellation.apply_pre_diff_code(): + self.symbol_mapper = gr.map_bb( + mod_codes.invert_code(self._constellation.pre_diff_code())) + + # unpack the k bit vector into a stream of bits + self.unpack = gr.unpack_k_bits_bb(self.bits_per_symbol()) + + if verbose: + self._print_verbage() + + if log: + self._setup_logging() + + # Connect and Initialize base class + blocks = [self, self.agc, self.freq_recov, self.time_recov, self.receiver] + if differential: + blocks.append(self.diffdec) + if self._constellation.apply_pre_diff_code(): + blocks.append(self.symbol_mapper) + blocks += [self.unpack, self] + self.connect(*blocks) + + def samples_per_symbol(self): + return self._samples_per_symbol + + def bits_per_symbol(self): # staticmethod that's also callable on an instance + return self._constellation.bits_per_symbol() + + def _print_verbage(self): + print "\nDemodulator:" + print "bits per symbol: %d" % self.bits_per_symbol() + print "RRC roll-off factor: %.2f" % self._excess_bw + print "FLL gain: %.2e" % self._freq_alpha + print "Timing alpha gain: %.2e" % self._timing_alpha + print "Timing beta gain: %.2e" % self._timing_beta + print "Timing max dev: %.2f" % self._timing_max_dev + print "Phase track alpha: %.2e" % self._phase_alpha + print "Phase track beta: %.2e" % self._phase_beta + + def _setup_logging(self): + print "Modulation logging turned on." + self.connect(self.agc, + gr.file_sink(gr.sizeof_gr_complex, "rx_agc.dat")) + self.connect((self.freq_recov, 0), + gr.file_sink(gr.sizeof_gr_complex, "rx_freq_recov.dat")) + self.connect((self.freq_recov, 1), + gr.file_sink(gr.sizeof_float, "rx_freq_recov_freq.dat")) + self.connect((self.freq_recov, 2), + gr.file_sink(gr.sizeof_float, "rx_freq_recov_phase.dat")) + self.connect((self.freq_recov, 3), + gr.file_sink(gr.sizeof_gr_complex, "rx_freq_recov_error.dat")) + self.connect((self.time_recov, 0), + gr.file_sink(gr.sizeof_gr_complex, "rx_time_recov.dat")) + self.connect((self.time_recov, 1), + gr.file_sink(gr.sizeof_float, "rx_time_recov_error.dat")) + self.connect((self.time_recov, 2), + gr.file_sink(gr.sizeof_float, "rx_time_recov_rate.dat")) + self.connect((self.time_recov, 3), + gr.file_sink(gr.sizeof_float, "rx_time_recov_phase.dat")) + self.connect((self.receiver, 0), + gr.file_sink(gr.sizeof_char, "rx_receiver.dat")) + self.connect((self.receiver, 1), + gr.file_sink(gr.sizeof_float, "rx_receiver_error.dat")) + self.connect((self.receiver, 2), + gr.file_sink(gr.sizeof_float, "rx_receiver_phase.dat")) + self.connect((self.receiver, 3), + gr.file_sink(gr.sizeof_float, "rx_receiver_freq.dat")) + if self._differential: + self.connect(self.diffdec, + gr.file_sink(gr.sizeof_char, "rx_diffdec.dat")) + if self._constellation.apply_pre_diff_code(): + self.connect(self.symbol_mapper, + gr.file_sink(gr.sizeof_char, "rx_symbol_mapper.dat")) + self.connect(self.unpack, + gr.file_sink(gr.sizeof_char, "rx_unpack.dat")) + + def add_options(parser): + """ + Adds generic demodulation options to the standard parser + """ + # Add options shared with modulator. + add_common_options(parser) + # Add options specific to demodulator. + parser.add_option("", "--freq-alpha", type="float", default=_def_freq_alpha, + help="set frequency lock loop alpha gain value [default=%default]") + parser.add_option("", "--phase-alpha", type="float", default=_def_phase_alpha, + help="set phase tracking loop alpha value [default=%default]") + parser.add_option("", "--timing-alpha", type="float", default=_def_timing_alpha, + help="set timing symbol sync loop gain alpha value [default=%default]") + parser.add_option("", "--timing-beta", type="float", default=_def_timing_beta, + help="set timing symbol sync loop gain beta value [default=%default]") + parser.add_option("", "--timing-max-dev", type="float", default=_def_timing_max_dev, + help="set timing symbol sync loop maximum deviation [default=%default]") + add_options=staticmethod(add_options) + + def extract_kwargs_from_options(cls, options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return extract_kwargs_from_options_for_class(cls, options) + extract_kwargs_from_options=classmethod(extract_kwargs_from_options) + diff --git a/gr-digital/python/modulation_utils2.py b/gr-digital/python/modulation_utils2.py new file mode 100644 index 000000000..f30055f4a --- /dev/null +++ b/gr-digital/python/modulation_utils2.py @@ -0,0 +1,101 @@ +# +# Copyright 2010 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 this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Miscellaneous utilities for managing mods and demods, as well as other items +useful in dealing with generalized handling of different modulations and demods. +""" + +import inspect + + +# Type 1 modulators accept a stream of bytes on their input and produce complex baseband output +_type_1_modulators = {} + +def type_1_mods(): + return _type_1_modulators + +def add_type_1_mod(name, mod_class): + _type_1_modulators[name] = mod_class + + +# Type 1 demodulators accept complex baseband input and produce a stream of bits, packed +# 1 bit / byte as their output. Their output is completely unambiguous. There is no need +# to resolve phase or polarity ambiguities. +_type_1_demodulators = {} + +def type_1_demods(): + return _type_1_demodulators + +def add_type_1_demod(name, demod_class): + _type_1_demodulators[name] = demod_class + +# Also record the constellation making functions of the modulations +_type_1_constellations = {} + +def type_1_constellations(): + return _type_1_constellations + +def add_type_1_constellation(name, constellation): + _type_1_constellations[name] = constellation + + +def extract_kwargs_from_options(function, excluded_args, options): + """ + Given a function, a list of excluded arguments and the result of + parsing command line options, create a dictionary of key word + arguments suitable for passing to the function. The dictionary + will be populated with key/value pairs where the keys are those + that are common to the function's argument list (minus the + excluded_args) and the attributes in options. The values are the + corresponding values from options unless that value is None. + In that case, the corresponding dictionary entry is not populated. + + (This allows different modulations that have the same parameter + names, but different default values to coexist. The downside is + that --help in the option parser will list the default as None, + but in that case the default provided in the __init__ argument + list will be used since there is no kwargs entry.) + + @param function: the function whose parameter list will be examined + @param excluded_args: function arguments that are NOT to be added to the dictionary + @type excluded_args: sequence of strings + @param options: result of command argument parsing + @type options: optparse.Values + """ + # Try this in C++ ;) + args, varargs, varkw, defaults = inspect.getargspec(function) + d = {} + for kw in [a for a in args if a not in excluded_args]: + if hasattr(options, kw): + if getattr(options, kw) is not None: + d[kw] = getattr(options, kw) + return d + +def extract_kwargs_from_options_for_class(cls, options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + d = extract_kwargs_from_options( + cls.__init__, ('self',), options) + for base in cls.__bases__: + if hasattr(base, 'extract_kwargs_from_options'): + d.update(base.extract_kwargs_from_options(options)) + return d diff --git a/gr-digital/python/ofdm.py b/gr-digital/python/ofdm.py new file mode 100644 index 000000000..e05f074f4 --- /dev/null +++ b/gr-digital/python/ofdm.py @@ -0,0 +1,295 @@ +#!/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 gnuradio import gr, ofdm_packet_utils, modulation_utils2 +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 +# ///////////////////////////////////////////////////////////////////////////// + +class ofdm_mod(gr.hier_block2): + """ + Modulates an OFDM stream. Based on the options fft_length, occupied_tones, and + cp_length, this block creates OFDM symbols using a specified modulation option. + + Send packets by calling send_pkt + """ + def __init__(self, options, msgq_limit=2, pad_for_usrp=True): + """ + Hierarchical block for sending packets + + Packets to be sent are enqueued by calling send_pkt. + The output is the complex modulated signal at baseband. + + @param options: pass modulation options from higher layers (fft length, occupied tones, etc.) + @param msgq_limit: maximum number of messages in message queue + @type msgq_limit: int + @param pad_for_usrp: If true, packets are padded such that they end up a multiple of 128 samples + """ + + gr.hier_block2.__init__(self, "ofdm_mod", + gr.io_signature(0, 0, 0), # Input signature + gr.io_signature(1, 1, gr.sizeof_gr_complex)) # Output signature + + self._pad_for_usrp = pad_for_usrp + self._modulation = options.modulation + self._fft_length = options.fft_length + 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 + zeros_on_left = int(math.ceil((self._fft_length - self._occupied_tones)/2.0)) + ksfreq = known_symbols_4512_3[0:self._occupied_tones] + for i in range(len(ksfreq)): + if((zeros_on_left + i) & 1): + ksfreq[i] = 0 + + # hard-coded known symbols + preambles = (ksfreq,) + + padded_preambles = list() + for pre in preambles: + padded = self._fft_length*[0,] + padded[zeros_on_left : zeros_on_left + self._occupied_tones] = pre + padded_preambles.append(padded) + + 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, + options.occupied_tones, options.fft_length) + + self.preambles = gr.ofdm_insert_preamble(self._fft_length, padded_preambles) + self.ifft = gr.fft_vcc(self._fft_length, False, win, True) + self.cp_adder = gr.ofdm_cyclic_prefixer(self._fft_length, symbol_length) + self.scale = gr.multiply_const_cc(1.0 / math.sqrt(self._fft_length)) + + self.connect((self._pkt_input, 0), (self.preambles, 0)) + self.connect((self._pkt_input, 1), (self.preambles, 1)) + self.connect(self.preambles, self.ifft, self.cp_adder, self.scale, self) + + if options.verbose: + self._print_verbage() + + if options.log: + self.connect(self._pkt_input, gr.file_sink(gr.sizeof_gr_complex*options.fft_length, + "ofdm_mapper_c.dat")) + self.connect(self.preambles, gr.file_sink(gr.sizeof_gr_complex*options.fft_length, + "ofdm_preambles.dat")) + self.connect(self.ifft, gr.file_sink(gr.sizeof_gr_complex*options.fft_length, + "ofdm_ifft_c.dat")) + self.connect(self.cp_adder, gr.file_sink(gr.sizeof_gr_complex, + "ofdm_cp_adder_c.dat")) + + def send_pkt(self, payload='', eof=False): + """ + Send the payload. + + @param payload: data to send + @type payload: string + """ + if eof: + msg = gr.message(1) # tell self._pkt_input we're not sending any more packets + else: + # print "original_payload =", string_to_hex_list(payload) + pkt = ofdm_packet_utils.make_packet(payload, 1, 1, self._pad_for_usrp, whitening=True) + + #print "pkt =", string_to_hex_list(pkt) + msg = gr.message_from_string(pkt) + self._pkt_input.msgq().insert_tail(msg) + + def add_options(normal, expert): + """ + 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) + + # Make a static method to call before instantiation + add_options = staticmethod(add_options) + + def _print_verbage(self): + """ + Prints information about the OFDM modulator + """ + print "\nOFDM Modulator:" + print "Modulation Type: %s" % (self._modulation) + print "FFT length: %3d" % (self._fft_length) + print "Occupied Tones: %3d" % (self._occupied_tones) + print "CP length: %3d" % (self._cp_length) + + +class ofdm_demod(gr.hier_block2): + """ + Demodulates a received OFDM stream. Based on the options fft_length, occupied_tones, and + cp_length, this block performs synchronization, FFT, and demodulation of incoming OFDM + symbols and passes packets up the a higher layer. + + The input is complex baseband. When packets are demodulated, they are passed to the + app via the callback. + """ + + def __init__(self, options, callback=None): + """ + Hierarchical block for demodulating and deframing packets. + + The input is the complex modulated signal at baseband. + Demodulated packets are sent to the handler. + + @param options: pass modulation options from higher layers (fft length, occupied tones, etc.) + @param callback: function of two args: ok, payload + @type callback: ok: bool; payload: string + """ + gr.hier_block2.__init__(self, "ofdm_demod", + gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature + gr.io_signature(1, 1, gr.sizeof_gr_complex)) # Output signature + + + self._rcvd_pktq = gr.msg_queue() # holds packets from the PHY + + self._modulation = options.modulation + self._fft_length = options.fft_length + self._occupied_tones = options.occupied_tones + 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] + for i in range(len(ksfreq)): + if((zeros_on_left + i) & 1): + ksfreq[i] = 0 + + # hard-coded known symbols + preambles = (ksfreq,) + + symbol_length = self._fft_length + self._cp_length + self.ofdm_recv = ofdm_receiver(self._fft_length, self._cp_length, + self._occupied_tones, self._snr, preambles, + options.log) + + constell = modulation_utils2.type_1_constellations()[self._modulation](arity) + + phgain = 0.25 + frgain = phgain*phgain / 4.0 + self.ofdm_demod = gr.ofdm_frame_sink2(constell.base(), + self._rcvd_pktq, + self._occupied_tones, + phgain, frgain) + + self.connect(self, self.ofdm_recv) + self.connect((self.ofdm_recv, 0), (self.ofdm_demod, 0)) + self.connect((self.ofdm_recv, 1), (self.ofdm_demod, 1)) + + # added output signature to work around bug, though it might not be a bad + # thing to export, anyway + self.connect(self.ofdm_recv.chan_filt, self) + + if options.log: + self.connect(self.ofdm_demod, gr.file_sink(gr.sizeof_gr_complex*self._occupied_tones, "ofdm_frame_sink_c.dat")) + else: + self.connect(self.ofdm_demod, gr.null_sink(gr.sizeof_gr_complex*self._occupied_tones)) + + if options.verbose: + self._print_verbage() + + self._watcher = _queue_watcher_thread(self._rcvd_pktq, callback) + + def add_options(normal, expert): + """ + 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) + # Make a static method to call before instantiation + add_options = staticmethod(add_options) + + def _print_verbage(self): + """ + Prints information about the OFDM demodulator + """ + print "\nOFDM Demodulator:" + print "Modulation Type: %s" % (self._modulation) + print "FFT length: %3d" % (self._fft_length) + print "Occupied Tones: %3d" % (self._occupied_tones) + print "CP length: %3d" % (self._cp_length) + + + +class _queue_watcher_thread(_threading.Thread): + def __init__(self, rcvd_pktq, callback): + _threading.Thread.__init__(self) + self.setDaemon(1) + self.rcvd_pktq = rcvd_pktq + self.callback = callback + self.keep_running = True + self.start() + + + def run(self): + while self.keep_running: + msg = self.rcvd_pktq.delete_head() + ok, payload = ofdm_packet_utils.unmake_packet(msg.to_string()) + if self.callback: + self.callback(ok, payload) + +# Generating known symbols with: +# i = [2*random.randint(0,1)-1 for i in range(4512)] + +known_symbols_4512_3 = [-1, -1, 1, -1, 1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, -1, -1, 1, -1, -1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, 1, -1, -1, -1, -1, 1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, -1, -1, 1, 1, 1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, 1, -1, 1, 1, -1, -1, -1, -1, 1, 1, 1, 1, 1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, -1, -1, -1, 1, 1, 1, -1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, 1, 1, 1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, 1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, 1, -1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, -1, -1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1, 1, -1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, -1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, 1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, 1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1, 1, 1, 1, 1, -1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, -1, -1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, 1, 1, 1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, 1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1, -1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, -1, -1, 1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, -1, -1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, -1, 1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1, 1, 1, -1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1] diff --git a/gr-digital/python/pkt.py b/gr-digital/python/pkt.py new file mode 100644 index 000000000..aa720d1a5 --- /dev/null +++ b/gr-digital/python/pkt.py @@ -0,0 +1,184 @@ +# +# Copyright 2005, 2006, 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. +# + +from math import pi +from gnuradio import gr, packet_utils +import gnuradio.gr.gr_threading as _threading + + +# ///////////////////////////////////////////////////////////////////////////// +# mod/demod with packets as i/o +# ///////////////////////////////////////////////////////////////////////////// + +class mod_pkts(gr.hier_block2): + """ + Wrap an arbitrary digital modulator in our packet handling framework. + + Send packets by calling send_pkt + """ + def __init__(self, modulator, access_code=None, msgq_limit=2, pad_for_usrp=True, use_whitener_offset=False, + modulate=True): + """ + Hierarchical block for sending packets + + Packets to be sent are enqueued by calling send_pkt. + The output is the complex modulated signal at baseband. + + @param modulator: instance of modulator class (gr_block or hier_block2) + @type modulator: complex baseband out + @param access_code: AKA sync vector + @type access_code: string of 1's and 0's between 1 and 64 long + @param msgq_limit: maximum number of messages in message queue + @type msgq_limit: int + @param pad_for_usrp: If true, packets are padded such that they end up a multiple of 128 samples + @param use_whitener_offset: If true, start of whitener XOR string is incremented each packet + @param modulate: If false, no modulation will be performed. + + See gmsk_mod for remaining parameters + """ + if modulate: + output_size = gr.sizeof_gr_complex + else: + output_size = gr.sizeof_char + + gr.hier_block2.__init__(self, "mod_pkts", + gr.io_signature(0, 0, 0), # Input signature + gr.io_signature(1, 1, output_size)) # Output signature + + self._modulator = modulator + self._pad_for_usrp = pad_for_usrp + self._use_whitener_offset = use_whitener_offset + self._whitener_offset = 0 + + if access_code is None: + access_code = packet_utils.default_access_code + if not packet_utils.is_1_0_string(access_code): + raise ValueError, "Invalid access_code %r. Must be string of 1's and 0's" % (access_code,) + self._access_code = access_code + + # accepts messages from the outside world + self._pkt_input = gr.message_source(gr.sizeof_char, msgq_limit) + if modulate: + self.connect(self._pkt_input, self._modulator, self) + else: + self.connect(self._pkt_input, self) + + def send_pkt(self, payload='', eof=False): + """ + Send the payload. + + @param payload: data to send + @type payload: string + """ + if eof: + msg = gr.message(1) # tell self._pkt_input we're not sending any more packets + else: + # print "original_payload =", string_to_hex_list(payload) + pkt = packet_utils.make_packet(payload, + self._modulator.samples_per_symbol(), + self._modulator.bits_per_symbol(), + self._access_code, + self._pad_for_usrp, + self._whitener_offset) + #print "pkt =", string_to_hex_list(pkt) + msg = gr.message_from_string(pkt) + if self._use_whitener_offset is True: + self._whitener_offset = (self._whitener_offset + 1) % 16 + + self._pkt_input.msgq().insert_tail(msg) + + + +class demod_pkts(gr.hier_block2): + """ + Wrap an arbitrary digital demodulator in our packet handling framework. + + The input is complex baseband. When packets are demodulated, they are passed to the + app via the callback. + """ + + def __init__(self, demodulator, access_code=None, callback=None, threshold=-1, demodulate=True): + """ + Hierarchical block for demodulating and deframing packets. + + The input is the complex modulated signal at baseband. + Demodulated packets are sent to the handler. + + If demodulator is None it is assumed the input is already demodulated. + + @param demodulator: instance of demodulator class (gr_block or hier_block2) + @type demodulator: complex baseband in + @param access_code: AKA sync vector + @type access_code: string of 1's and 0's + @param callback: function of two args: ok, payload + @type callback: ok: bool; payload: string + @param threshold: detect access_code with up to threshold bits wrong (-1 -> use default) + @type threshold: int + """ + + if demodulator is not None: + input_size = gr.sizeof_gr_complex + else: + input_size = gr.sizeof_char + + gr.hier_block2.__init__(self, "demod_pkts", + gr.io_signature(1, 1, input_size), # Input signature + gr.io_signature(0, 0, 0)) # Output signature + + self._demodulator = demodulator + if access_code is None: + access_code = packet_utils.default_access_code + if not packet_utils.is_1_0_string(access_code): + raise ValueError, "Invalid access_code %r. Must be string of 1's and 0's" % (access_code,) + self._access_code = access_code + + if threshold == -1: + threshold = 12 # FIXME raise exception + + self._rcvd_pktq = gr.msg_queue() # holds packets from the PHY + self.correlator = gr.correlate_access_code_bb(access_code, threshold) + + self.framer_sink = gr.framer_sink_1(self._rcvd_pktq) + if self._demodulator is not None: + self.connect(self, self._demodulator, self.correlator, self.framer_sink) + else: + self.connect(self, self.correlator, self.framer_sink) + + if callback is not None: + self._watcher = _queue_watcher_thread(self._rcvd_pktq, callback) + + +class _queue_watcher_thread(_threading.Thread): + def __init__(self, rcvd_pktq, callback): + _threading.Thread.__init__(self) + self.setDaemon(1) + self.rcvd_pktq = rcvd_pktq + self.callback = callback + self.keep_running = True + self.start() + + + def run(self): + while self.keep_running: + msg = self.rcvd_pktq.delete_head() + ok, payload = packet_utils.unmake_packet(msg.to_string(), int(msg.arg1())) + if self.callback: + self.callback(ok, payload) diff --git a/gr-digital/python/psk.py b/gr-digital/python/psk.py new file mode 100644 index 000000000..acedf3b69 --- /dev/null +++ b/gr-digital/python/psk.py @@ -0,0 +1,94 @@ +# +# Copyright 2005,2006 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. +# + +from math import pi, sqrt, log10 +import math, cmath + +# The following algorithm generates Gray coded constellations for M-PSK for M=[2,4,8] +def make_gray_constellation(m): + # number of bits/symbol (log2(M)) + k = int(log10(m) / log10(2.0)) + + coeff = 1 + const_map = [] + bits = [0]*3 + for i in range(m): + # get a vector of the k bits to use in this mapping + bits[3-k:3] = [((i&(0x01 << k-j-1)) >> k-j-1) for j in range(k)] + + theta = -(2*bits[0]-1)*(2*pi/m)*(bits[0]+abs(bits[1]-bits[2])+2*bits[1]) + re = math.cos(theta) + im = math.sin(theta) + const_map.append(complex(re, im)) # plug it into the constellation + + # return the constellation; by default, it is normalized + return const_map + +# This makes a constellation that increments around the unit circle +def make_constellation(m): + return [cmath.exp(i * 2 * pi / m * 1j) for i in range(m)] + +# Common definition of constellations for Tx and Rx +constellation = { + 2 : make_constellation(2), # BPSK + 4 : make_constellation(4), # QPSK + 8 : make_constellation(8) # 8PSK + } + +gray_constellation = { + 2 : make_gray_constellation(2), # BPSK + 4 : make_gray_constellation(4), # QPSK + 8 : make_gray_constellation(8) # 8PSK + } + +# ----------------------- +# Do Gray code +# ----------------------- +# binary to gray coding -- constellation does Gray coding +binary_to_gray = { + 2 : range(2), + 4 : [0,1,3,2], + 8 : [0, 1, 3, 2, 7, 6, 4, 5] + } + +# gray to binary +gray_to_binary = { + 2 : range(2), + 4 : [0,1,3,2], + 8 : [0, 1, 3, 2, 6, 7, 5, 4] + } + +# ----------------------- +# Don't Gray code +# ----------------------- +# identity mapping +binary_to_ungray = { + 2 : range(2), + 4 : range(4), + 8 : range(8) + } + +# identity mapping +ungray_to_binary = { + 2 : range(2), + 4 : range(4), + 8 : range(8) + } diff --git a/gr-digital/python/psk2.py b/gr-digital/python/psk2.py new file mode 100644 index 000000000..778c1e5e5 --- /dev/null +++ b/gr-digital/python/psk2.py @@ -0,0 +1,123 @@ +# +# Copyright 2005,2006,2011 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. +# + +""" +PSK modulation and demodulation. +""" + +from math import pi, log +from cmath import exp + +from gnuradio import gr +import digital_swig +import modulation_utils2 +from utils import mod_codes, gray_code +from generic_mod_demod import generic_mod, generic_demod + +# Default number of points in constellation. +_def_constellation_points = 4 +# The default encoding (e.g. gray-code, set-partition) +_def_mod_code = mod_codes.GRAY_CODE + +def create_encodings(mod_code, arity): + post_diff_code = None + if mod_code not in mod_codes.codes: + raise ValueError('That modulation code does not exist.') + if mod_code == mod_codes.GRAY_CODE: + pre_diff_code = gray_code.gray_code(arity) + elif mod_code == mod_codes.SET_PARTITION_CODE: + pre_diff_code = set_partition_code.set_partition_code(arity) + elif mod_code == mod_codes.NO_CODE: + pre_diff_code = [] + else: + raise ValueError('That modulation code is not implemented for this constellation.') + return (pre_diff_code, post_diff_code) + +# ///////////////////////////////////////////////////////////////////////////// +# PSK constellation +# ///////////////////////////////////////////////////////////////////////////// + +def psk_constellation(m=_def_constellation_points, mod_code=_def_mod_code): + """ + Creates a PSK constellation object. + """ + k = log(m) / log(2.0) + if (k != int(k)): + raise StandardError('Number of constellation points must be a power of two.') + points = [exp(2*pi*(0+1j)*i/m) for i in range(0,m)] + pre_diff_code, post_diff_code = create_encodings(mod_code, m) + if post_diff_code is not None: + inverse_post_diff_code = mod_codes.invert_code(post_diff_code) + points = [points[x] for x in inverse_post_diff_code] + constellation = digital_swig.constellation_psk(points, pre_diff_code, m) + return constellation + +# ///////////////////////////////////////////////////////////////////////////// +# PSK modulator +# ///////////////////////////////////////////////////////////////////////////// + +class psk_mod(generic_mod): + + def __init__(self, constellation_points=_def_constellation_points, + mod_code=_def_mod_code, + *args, **kwargs): + + """ + Hierarchical block for RRC-filtered PSK modulation. + + The input is a byte stream (unsigned char) and the + output is the complex modulated signal at baseband. + + See generic_mod block for list of parameters. + """ + + constellation = psk_constellation(constellation_points, mod_code) + super(psk_mod, self).__init__(constellation, *args, **kwargs) + +# ///////////////////////////////////////////////////////////////////////////// +# PSK demodulator +# +# ///////////////////////////////////////////////////////////////////////////// + +class psk_demod(generic_demod): + + def __init__(self, constellation_points=_def_constellation_points, + mod_code=_def_mod_code, + *args, **kwargs): + + """ + Hierarchical block for RRC-filtered PSK modulation. + + The input is a byte stream (unsigned char) and the + output is the complex modulated signal at baseband. + + See generic_demod block for list of parameters. + """ + + constellation = psk_constellation(constellation_points, mod_code) + super(psk_demod, self).__init__(constellation, *args, **kwargs) + +# +# Add these to the mod/demod registry +# +modulation_utils2.add_type_1_mod('psk', psk_mod) +modulation_utils2.add_type_1_demod('psk', psk_demod) +modulation_utils2.add_type_1_constellation('psk', psk_constellation) diff --git a/gr-digital/python/qa_constellation.py b/gr-digital/python/qa_constellation.py new file mode 100755 index 000000000..02afb8d2d --- /dev/null +++ b/gr-digital/python/qa_constellation.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python +# +# Copyright 2011 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 random +from cmath import exp, pi, log + +from gnuradio import gr, gr_unittest, blks2 +from utils import mod_codes +import digital_swig + +# import from local folder +import psk2 +import qam + +tested_mod_codes = (mod_codes.NO_CODE, mod_codes.GRAY_CODE) + +# A list of the constellations to test. +# Each constellation is given by a 3-tuple. +# First item is a function to generate the constellation +# Second item is a dictionary of arguments for function with lists of +# possible values. +# Third item is whether differential encoding should be tested. +# Fourth item is the name of the argument to constructor that specifices +# whether differential encoding is used. + +def twod_constell(): + """ + + """ + points = ((1+0j), (0+1j), + (-1+0j), (0-1j)) + rot_sym = 2 + dim = 2 + return digital_swig.constellation_calcdist(points, [], rot_sym, dim) + +def threed_constell(): + oned_points = ((1+0j), (0+1j), (-1+0j), (0-1j)) + points = [] + r4 = range(0, 4) + for ia in r4: + for ib in r4: + for ic in r4: + points += [oned_points[ia], oned_points[ib], oned_points[ic]] + rot_sym = 4 + dim = 3 + return digital_swig.constellation_calcdist(points, [], rot_sym, dim) + +tested_constellation_info = ( + (psk2.psk_constellation, + {'m': (2, 4, 8, 16, 32, 64), + 'mod_code': tested_mod_codes, }, + True, None), + (digital_swig.constellation_bpsk, {}, True, None), + # No differential testing for qpsk because it is gray-coded. + # This is because soft decision making is simpler if we can assume + # gray coding. + (digital_swig.constellation_qpsk, {}, False, None), + (digital_swig.constellation_8psk, {}, False, None), + (twod_constell, {}, True, None), + (threed_constell, {}, True, None), + ) + +def tested_constellations(): + """ + Generator to produce (constellation, differential) tuples for testing purposes. + """ + for constructor, poss_args, differential, diff_argname in tested_constellation_info: + if differential: + diff_poss = (True, False) + else: + diff_poss = (False,) + poss_args = [[argname, argvalues, 0] for argname, argvalues in poss_args.items()] + for current_diff in diff_poss: + # Add an index into args to keep track of current position in argvalues + while True: + current_args = dict([(argname, argvalues[argindex]) + for argname, argvalues, argindex in poss_args]) + if diff_argname is not None: + current_args[diff_argname] = current_diff + constellation = constructor(**current_args) + yield (constellation, current_diff) + for this_poss_arg in poss_args: + argname, argvalues, argindex = this_poss_arg + if argindex < len(argvalues) - 1: + this_poss_arg[2] += 1 + break + else: + this_poss_arg[2] = 0 + if sum([argindex for argname, argvalues, argindex in poss_args]) == 0: + break + + +class test_constellation (gr_unittest.TestCase): + + src_length = 256 + + def setUp(self): + # Generate a list of random bits. + self.src_data = tuple([random.randint(0,1) for i in range(0, self.src_length)]) + + def tearDown(self): + pass + + def test_hard_decision(self): + for constellation, differential in tested_constellations(): + if differential: + rs = constellation.rotational_symmetry() + rotations = [exp(i*2*pi*(0+1j)/rs) for i in range(0, rs)] + else: + rotations = [None] + for rotation in rotations: + src = gr.vector_source_b(self.src_data) + content = mod_demod(constellation, differential, rotation) + dst = gr.vector_sink_b() + self.tb = gr.top_block() + self.tb.connect(src, content, dst) + self.tb.run() + data = dst.data() + # Don't worry about cut off data for now. + first = constellation.bits_per_symbol() + self.assertEqual (self.src_data[first:len(data)], data[first:]) + + +class mod_demod(gr.hier_block2): + def __init__(self, constellation, differential, rotation): + if constellation.arity() > 256: + # If this becomes limiting some of the blocks should be generalised so that they can work + # with shorts and ints as well as chars. + raise ValueError("Constellation cannot contain more than 256 points.") + + gr.hier_block2.__init__(self, "mod_demod", + gr.io_signature(1, 1, gr.sizeof_char), # Input signature + gr.io_signature(1, 1, gr.sizeof_char)) # Output signature + + arity = constellation.arity() + + # TX + self.constellation = constellation + self.differential = differential + self.blocks = [self] + # We expect a stream of unpacked bits. + # First step is to pack them. + self.blocks.append( + gr.unpacked_to_packed_bb(1, gr.GR_MSB_FIRST)) + # Second step we unpack them such that we have k bits in each byte where + # each constellation symbol hold k bits. + self.blocks.append( + gr.packed_to_unpacked_bb(self.constellation.bits_per_symbol(), + gr.GR_MSB_FIRST)) + # Apply any pre-differential coding + # Gray-coding is done here if we're also using differential coding. + if self.constellation.apply_pre_diff_code(): + self.blocks.append(gr.map_bb(self.constellation.pre_diff_code())) + # Differential encoding. + if self.differential: + self.blocks.append(gr.diff_encoder_bb(arity)) + # Convert to constellation symbols. + self.blocks.append(gr.chunks_to_symbols_bc(self.constellation.points(), self.constellation.dimensionality())) + # CHANNEL + # Channel just consists of a rotation to check differential coding. + if rotation is not None: + self.blocks.append(gr.multiply_const_cc(rotation)) + + # RX + # Convert the constellation symbols back to binary values. + self.blocks.append(digital_swig.constellation_decoder_cb(self.constellation.base())) + # Differential decoding. + if self.differential: + self.blocks.append(gr.diff_decoder_bb(arity)) + # Decode any pre-differential coding. + if self.constellation.apply_pre_diff_code(): + self.blocks.append(gr.map_bb( + mod_codes.invert_code(self.constellation.pre_diff_code()))) + # unpack the k bit vector into a stream of bits + self.blocks.append(gr.unpack_k_bits_bb( + self.constellation.bits_per_symbol())) + # connect to block output + check_index = len(self.blocks) + self.blocks = self.blocks[:check_index] + self.blocks.append(self) + + self.connect(*self.blocks) + + +if __name__ == '__main__': + gr_unittest.run(test_constellation, "test_constellation.xml") diff --git a/gr-digital/python/qa_constellation_receiver.py b/gr-digital/python/qa_constellation_receiver.py new file mode 100755 index 000000000..70b62c7aa --- /dev/null +++ b/gr-digital/python/qa_constellation_receiver.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# +# Copyright 2011 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 random + +from gnuradio import gr, blks2, packet_utils, gr_unittest +from utils import mod_codes, alignment +import digital_swig +from generic_mod_demod import generic_mod, generic_demod + +from qa_constellation import tested_constellations, twod_constell + + +# Set a seed so that if errors turn up they are reproducible. +# 1234 fails +random.seed(1239) + +# TESTING PARAMETERS +# The number of symbols to test with. +# We need this many to let the frequency recovery block converge. +DATA_LENGTH = 200000 +# Test fails if fraction of output that is correct is less than this. +REQ_CORRECT = 0.8 + +# CHANNEL PARAMETERS +NOISE_VOLTAGE = 0.01 +FREQUENCY_OFFSET = 0.01 +TIMING_OFFSET = 1.0 + +# RECEIVER PARAMETERS +# Increased from normal default of 0.01 to speed things up. +FREQ_ALPHA = 0.02 +# Decreased from normal default of 0.1 is required for the constellations +# with smaller point separations. +PHASE_ALPHA = 0.02 + + +class test_constellation_receiver (gr_unittest.TestCase): + + # We ignore the first half of the output data since often it takes + # a while for the receiver to lock on. + ignore_fraction = 0.8 + seed = 1234 + max_data_length = DATA_LENGTH * 6 + max_num_samples = 1000 + + def test_basic(self): + """ + Tests a bunch of different constellations by using generic + modulation, a channel, and generic demodulation. The generic + demodulation uses constellation_receiver which is what + we're really trying to test. + """ + + # Assumes not more than 64 points in a constellation + # Generates some random input data to use. + self.src_data = tuple( + [random.randint(0,1) for i in range(0, self.max_data_length)]) + # Generates some random indices to use for comparing input and + # output data (a full comparison is too slow in python). + self.indices = alignment.random_sample( + self.max_data_length, self.max_num_samples, self.seed) + + for constellation, differential in tested_constellations(): + # The constellation_receiver doesn't work for constellations + # of multple dimensions (i.e. multiple complex numbers to a + # single symbol). + # That is not implemented since the receiver has no way of + # knowing where the beginning of a symbol is. + # It also doesn't work for non-differential modulation. + if constellation.dimensionality() != 1 or not differential: + continue + data_length = DATA_LENGTH * constellation.bits_per_symbol() + tb = rec_test_tb(constellation, differential, + src_data=self.src_data[:data_length]) + tb.run() + data = tb.dst.data() + d1 = tb.src_data[:int(len(tb.src_data)*self.ignore_fraction)] + d2 = data[:int(len(data)*self.ignore_fraction)] + correct, overlap, offset, indices = alignment.align_sequences( + d1, d2, indices=self.indices) + self.assertTrue(correct > REQ_CORRECT) + + +class rec_test_tb (gr.top_block): + """ + Takes a constellation an runs a generic modulation, channel, + and generic demodulation. + """ + def __init__(self, constellation, differential, + data_length=None, src_data=None): + """ + constellation -- a constellation object + differential -- whether differential encoding is used + data_length -- the number of bits of data to use + src_data -- a list of the bits to use + """ + super(rec_test_tb, self).__init__() + # Transmission Blocks + if src_data is None: + self.src_data = tuple([random.randint(0,1) for i in range(0, data_length)]) + else: + self.src_data = src_data + packer = gr.unpacked_to_packed_bb(1, gr.GR_MSB_FIRST) + src = gr.vector_source_b(self.src_data) + mod = generic_mod(constellation, differential=differential) + # Channel + channel = gr.channel_model(NOISE_VOLTAGE, FREQUENCY_OFFSET, TIMING_OFFSET) + # Receiver Blocks + demod = generic_demod(constellation, differential=differential, + freq_alpha=FREQ_ALPHA, + phase_alpha=PHASE_ALPHA) + self.dst = gr.vector_sink_b() + self.connect(src, packer, mod, channel, demod, self.dst) + +if __name__ == '__main__': + gr_unittest.run(test_constellation_receiver, "test_constellation_receiver.xml") diff --git a/gr-digital/python/qa_costas_loop_cc.py b/gr-digital/python/qa_costas_loop_cc.py new file mode 100755 index 000000000..368704093 --- /dev/null +++ b/gr-digital/python/qa_costas_loop_cc.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python +# +# Copyright 2011 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. +# + +from gnuradio import gr, blks2, gr_unittest +import digital_swig +import random, cmath + +class test_digital(gr_unittest.TestCase): + + def setUp (self): + self.tb = gr.top_block () + + def tearDown (self): + self.tb = None + + def test01 (self): + # test basic functionality by setting all gains to 0 + damp = 0.4 + natfreq = 0.25 + order = 2 + self.test = digital_swig.costas_loop_cc(damp, natfreq, order) + + data = 100*[complex(1,0),] + self.src = gr.vector_source_c(data, False) + self.snk = gr.vector_sink_c() + + self.tb.connect(self.src, self.test, self.snk) + self.tb.run() + + expected_result = data + dst_data = self.snk.data() + self.assertComplexTuplesAlmostEqual (expected_result, dst_data, 5) + + def test02 (self): + # Make sure it doesn't diverge given perfect data + damp = 0.4 + natfreq = 0.25 + order = 2 + self.test = digital_swig.costas_loop_cc(damp, natfreq, order) + + data = [complex(2*random.randint(0,1)-1, 0) for i in xrange(100)] + self.src = gr.vector_source_c(data, False) + self.snk = gr.vector_sink_c() + + self.tb.connect(self.src, self.test, self.snk) + self.tb.run() + + expected_result = data + dst_data = self.snk.data() + + self.assertComplexTuplesAlmostEqual (expected_result, dst_data, 5) + + def test03 (self): + # BPSK Convergence test with static rotation + damp = 0.4 + natfreq = 0.25 + order = 2 + self.test = digital_swig.costas_loop_cc(damp, natfreq, order) + + rot = cmath.exp(0.2j) # some small rotation + data = [complex(2*random.randint(0,1)-1, 0) for i in xrange(100)] + + N = 40 # settling time + expected_result = data[N:] + data = [rot*d for d in data] + + self.src = gr.vector_source_c(data, False) + self.snk = gr.vector_sink_c() + + self.tb.connect(self.src, self.test, self.snk) + self.tb.run() + + dst_data = self.snk.data()[N:] + + # generously compare results; the loop will converge near to, but + # not exactly on, the target data + self.assertComplexTuplesAlmostEqual (expected_result, dst_data, 2) + + def test04 (self): + # QPSK Convergence test with static rotation + damp = 0.4 + natfreq = 0.25 + order = 4 + self.test = digital_swig.costas_loop_cc(damp, natfreq, order) + + rot = cmath.exp(0.2j) # some small rotation + data = [complex(2*random.randint(0,1)-1, 2*random.randint(0,1)-1) + for i in xrange(100)] + + N = 40 # settling time + expected_result = data[N:] + data = [rot*d for d in data] + + self.src = gr.vector_source_c(data, False) + self.snk = gr.vector_sink_c() + + self.tb.connect(self.src, self.test, self.snk) + self.tb.run() + + dst_data = self.snk.data()[N:] + + # generously compare results; the loop will converge near to, but + # not exactly on, the target data + self.assertComplexTuplesAlmostEqual (expected_result, dst_data, 2) + + def test05 (self): + # 8PSK Convergence test with static rotation + damp = 0.5 + natfreq = 0.5 + order = 8 + self.test = digital_swig.costas_loop_cc(damp, natfreq, order) + + rot = cmath.exp(-cmath.pi/8.0j) # rotate to match Costas rotation + const = blks2.psk.make_constellation(order) + data = [random.randint(0,7) for i in xrange(100)] + data = [2*rot*const[d] for d in data] + + N = 40 # settling time + expected_result = data[N:] + + rot = cmath.exp(0.1j) # some small rotation + data = [rot*d for d in data] + + self.src = gr.vector_source_c(data, False) + self.snk = gr.vector_sink_c() + + self.tb.connect(self.src, self.test, self.snk) + self.tb.run() + + dst_data = self.snk.data()[N:] + + # generously compare results; the loop will converge near to, but + # not exactly on, the target data + self.assertComplexTuplesAlmostEqual (expected_result, dst_data, 2) + +if __name__ == '__main__': + gr_unittest.run(test_digital, "test_digital.xml") diff --git a/gr-digital/python/qa_digital.py b/gr-digital/python/qa_digital.py new file mode 100755 index 000000000..97e35da56 --- /dev/null +++ b/gr-digital/python/qa_digital.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# +# Copyright 2011 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. +# + +from gnuradio import gr, gr_unittest +import digital_swig + +class test_digital(gr_unittest.TestCase): + + def setUp (self): + self.tb = gr.top_block () + + def tearDown (self): + self.tb = None + +if __name__ == '__main__': + gr_unittest.run(test_digital, "test_digital.xml") diff --git a/gr-digital/python/qam.py b/gr-digital/python/qam.py new file mode 100644 index 000000000..ee2b1e416 --- /dev/null +++ b/gr-digital/python/qam.py @@ -0,0 +1,227 @@ +# +# Copyright 2005,2006,2011 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. +# + +""" +QAM modulation and demodulation. +""" + +from math import pi, sqrt, log + +from gnuradio import gr +import modulation_utils2 +from generic_mod_demod import generic_mod, generic_demod +from utils.gray_code import gray_code +from utils import mod_codes + +# Default number of points in constellation. +_def_constellation_points = 16 +# Whether the quadrant bits are coded differentially. +_def_differential = True +# Whether gray coding is used. If differential is True then gray +# coding is used within but not between each quadrant. +_def_mod_code = mod_codes.NO_CODE + +def is_power_of_four(x): + v = log(x)/log(4) + return int(v) == v + +def get_bit(x, n): + """ Get the n'th bit of integer x (from little end).""" + return (x&(0x01 << n)) >> n + +def get_bits(x, n, k): + """ Get the k bits of integer x starting at bit n(from little end).""" + # Remove the n smallest bits + v = x >> n + # Remove all bits bigger than n+k-1 + return v % pow(2, k) + +def make_differential_constellation(m, gray_coded): + """ + Create a constellation with m possible symbols where m must be + a power of 4. + + Points are laid out in a square grid. + + Bits referring to the quadrant are differentilly encoded, + remaining bits are gray coded. + + """ + sqrtm = pow(m, 0.5) + if (not isinstance(m, int) or m < 4 or not is_power_of_four(m)): + raise ValueError("m must be a power of 4 integer.") + # Each symbol holds k bits. + k = int(log(m) / log(2.0)) + # First create a constellation for one quadrant containing m/4 points. + # The quadrant has 'side' points along each side of a quadrant. + side = int(sqrtm/2) + if gray_coded: + # Number rows and columns using gray codes. + gcs = gray_code(side) + # Get inverse gray codes. + i_gcs = dict([(v, key) for key, v in enumerate(gcs)]) + else: + i_gcs = dict([(i, i) for i in range(0, side)]) + # The distance between points is found. + step = 1/(side-0.5) + + gc_to_x = [(i_gcs[gc]+0.5)*step for gc in range(0, side)] + + # Takes the (x, y) location of the point with the quadrant along + # with the quadrant number. (x, y) are integers referring to which + # point within the quadrant it is. + # A complex number representing this location of this point is returned. + def get_c(gc_x, gc_y, quad): + if quad == 0: + return complex(gc_to_x[gc_x], gc_to_x[gc_y]) + if quad == 1: + return complex(-gc_to_x[gc_y], gc_to_x[gc_x]) + if quad == 2: + return complex(-gc_to_x[gc_x], -gc_to_x[gc_y]) + if quad == 3: + return complex(gc_to_x[gc_y], -gc_to_x[gc_x]) + raise StandardError("Impossible!") + + # First two bits determine quadrant. + # Next (k-2)/2 bits determine x position. + # Following (k-2)/2 bits determine y position. + # How x and y relate to real and imag depends on quadrant (see get_c function). + const_map = [] + for i in range(m): + y = get_bits(i, 0, (k-2)/2) + x = get_bits(i, (k-2)/2, (k-2)/2) + quad = get_bits(i, k-2, 2) + const_map.append(get_c(x, y, quad)) + + return const_map + +def make_not_differential_constellation(m, gray_coded): + side = int(pow(m, 0.5)) + if (not isinstance(m, int) or m < 4 or not is_power_of_four(m)): + raise ValueError("m must be a power of 4 integer.") + # Each symbol holds k bits. + k = int(log(m) / log(2.0)) + if gray_coded: + # Number rows and columns using gray codes. + gcs = gray_code(side) + # Get inverse gray codes. + i_gcs = mod_codes.invert_code(gcs) + else: + i_gcs = range(0, side) + # The distance between points is found. + step = 2.0/(side-1) + + gc_to_x = [-1 + i_gcs[gc]*step for gc in range(0, side)] + # First k/2 bits determine x position. + # Following k/2 bits determine y position. + const_map = [] + for i in range(m): + y = gc_to_x[get_bits(i, 0, k/2)] + x = gc_to_x[get_bits(i, k/2, k/2)] + const_map.append(complex(x,y)) + return const_map + +# ///////////////////////////////////////////////////////////////////////////// +# QAM constellation +# ///////////////////////////////////////////////////////////////////////////// + +def qam_constellation(constellation_points=_def_constellation_points, + differential=_def_differential, + mod_code=_def_mod_code): + """ + Creates a QAM constellation object. + """ + if mod_code == mod_codes.GRAY_CODE: + gray_coded = True + elif mod_code == mod_codes.NO_CODE: + gray_coded = False + else: + raise ValueError("Mod code is not implemented for QAM") + if differential: + points = make_differential_constellation(constellation_points, gray_coded) + else: + points = make_not_differential_constellation(constellation_points, gray_coded) + side = int(sqrt(constellation_points)) + width = 2.0/(side-1) + # No pre-diff code + # Should add one so that we can gray-code the quadrant bits too. + pre_diff_code = [] + constellation = gr.constellation_rect(points, pre_diff_code, 4, side, side, width, width) + return constellation + +# ///////////////////////////////////////////////////////////////////////////// +# QAM modulator +# ///////////////////////////////////////////////////////////////////////////// + +class qam_mod(generic_mod): + + def __init__(self, constellation_points=_def_constellation_points, + differential=_def_differential, + mod_code=_def_mod_code, + *args, **kwargs): + + """ + Hierarchical block for RRC-filtered QAM modulation. + + The input is a byte stream (unsigned char) and the + output is the complex modulated signal at baseband. + + See generic_mod block for list of parameters. + """ + + constellation = qam_constellation(constellation_points, differential, mod_code) + # We take care of the gray coding in the constellation generation so it doesn't + # need to be done in the block. + super(qam_mod, self).__init__(constellation, differential=differential, + *args, **kwargs) + +# ///////////////////////////////////////////////////////////////////////////// +# QAM demodulator +# +# ///////////////////////////////////////////////////////////////////////////// + +class qam_demod(generic_demod): + + def __init__(self, constellation_points=_def_constellation_points, + differential=_def_differential, + mod_code=_def_mod_code, + *args, **kwargs): + + """ + Hierarchical block for RRC-filtered QAM modulation. + + The input is a byte stream (unsigned char) and the + output is the complex modulated signal at baseband. + + See generic_demod block for list of parameters. + """ + constellation = qam_constellation(constellation_points, differential, mod_code) + # We take care of the gray coding in the constellation generation so it doesn't + # need to be done in the block. + super(qam_demod, self).__init__(constellation, differential=differential, + *args, **kwargs) + +# +# Add these to the mod/demod registry +# +modulation_utils2.add_type_1_mod('qam', qam_mod) +modulation_utils2.add_type_1_demod('qam', qam_demod) +modulation_utils2.add_type_1_constellation('qam', qam_constellation) diff --git a/gr-digital/python/qpsk.py b/gr-digital/python/qpsk.py new file mode 100644 index 000000000..ea1724424 --- /dev/null +++ b/gr-digital/python/qpsk.py @@ -0,0 +1,79 @@ +# +# Copyright 2005,2006,2011 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. +# + +""" +QPSK modulation. + +Demodulation is not included since the generic_mod_demod +doesn't work for non-differential encodings. +""" + +from gnuradio import gr, modulation_utils2 +from gnuradio.digital.generic_mod_demod import generic_mod + + +# Default number of points in constellation. +_def_constellation_points = 4 +# Whether differential coding is used. +_def_differential = False +_def_gray_coded = True + +# ///////////////////////////////////////////////////////////////////////////// +# QPSK constellation +# ///////////////////////////////////////////////////////////////////////////// + +def qpsk_constellation(m=_def_constellation_points): + if m != _def_constellation_points: + raise ValueError("QPSK can only have 4 constellation points.") + return gr.constellation_qpsk() + +# ///////////////////////////////////////////////////////////////////////////// +# QPSK modulator +# ///////////////////////////////////////////////////////////////////////////// + +class qpsk_mod(generic_mod): + + def __init__(self, constellation_points=_def_constellation_points, + differential=_def_differential, + gray_coded=_def_gray_coded, + *args, **kwargs): + + """ + Hierarchical block for RRC-filtered QPSK modulation. + + The input is a byte stream (unsigned char) and the + output is the complex modulated signal at baseband. + + See generic_mod block for list of parameters. + """ + + constellation = gr.constellation_qpsk() + if constellation_points != 4: + raise ValueError("QPSK can only have 4 constellation points.") + if differential or not gray_coded: + raise ValueError("This QPSK mod/demod works only for gray-coded, non-differential.") + super(qpsk_mod, self).__init__(constellation, differential, gray_coded, *args, **kwargs) + +# +# Add these to the mod/demod registry +# +modulation_utils2.add_type_1_mod('qpsk', qpsk_mod) +modulation_utils2.add_type_1_constellation('qpsk', qpsk_constellation) diff --git a/gr-digital/python/run_tests.in b/gr-digital/python/run_tests.in new file mode 100644 index 000000000..b39e7e847 --- /dev/null +++ b/gr-digital/python/run_tests.in @@ -0,0 +1,10 @@ +#!/bin/sh + +# 1st parameter is absolute path to component source directory +# 2nd parameter is absolute path to component build directory +# 3rd parameter is path to Python QA directory + +@top_builddir@/run_tests.sh \ + @abs_top_srcdir@/gr-digital \ + @abs_top_builddir@/gr-digital \ + @srcdir@ diff --git a/gr-digital/python/utils/.gitignore b/gr-digital/python/utils/.gitignore new file mode 100644 index 000000000..60c81fdce --- /dev/null +++ b/gr-digital/python/utils/.gitignore @@ -0,0 +1,3 @@ +run_tests +Makefile +Makefile.in diff --git a/gr-digital/python/utils/Makefile.am b/gr-digital/python/utils/Makefile.am new file mode 100644 index 000000000..6da4d61dd --- /dev/null +++ b/gr-digital/python/utils/Makefile.am @@ -0,0 +1,34 @@ +# +# Copyright 2011 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. +# + +include $(top_srcdir)/Makefile.common + +if PYTHON +utilspythondir = $(grpythondir)/digital/utils + +TESTS = + +utilspython_PYTHON = \ + __init__.py \ + gray_code.py \ + mod_codes.py \ + alignment.py +endif
\ No newline at end of file diff --git a/gr-digital/python/utils/__init__.py b/gr-digital/python/utils/__init__.py new file mode 100644 index 000000000..b3e997f9f --- /dev/null +++ b/gr-digital/python/utils/__init__.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# +# Copyright 2011 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. +# diff --git a/gr-digital/python/utils/alignment.py b/gr-digital/python/utils/alignment.py new file mode 100644 index 000000000..d32365866 --- /dev/null +++ b/gr-digital/python/utils/alignment.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python +# +# Copyright 2011 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. +# +""" +This module contains functions for aligning sequences. + +>>> import random +>>> random.seed(1234) +>>> ran_seq = [random.randint(0,1) for i in range(0, 100)] +>>> offset_seq = [0] * 20 + ran_seq +>>> correct, overlap, offset = align_sequences(ran_seq, offset_seq) +>>> print(correct, overlap, offset) +(1.0, 100, -20) +>>> offset_err_seq = [] +>>> for bit in offset_seq: +... if random.randint(0,4) == 4: +... offset_err_seq.append(random.randint(0,1)) +... else: +... offset_err_seq.append(bit) +>>> correct, overlap, offset = align_sequences(ran_seq, offset_err_seq) +>>> print(overlap, offset) +(100, -20) + +""" + +import random + +# DEFAULT PARAMETERS +# If the fraction of matching bits between two sequences is greater than +# this the sequences are assumed to be aligned. +def_correct_cutoff = 0.9 +# The maximum offset to test during sequence alignment. +def_max_offset = 500 +# The maximum number of samples to take from two sequences to check alignment. +def_num_samples = 1000 + +def compare_sequences(d1, d2, offset, sample_indices=None): + """ + Takes two binary sequences and an offset and returns the number of + matching entries and the number of compared entries. + d1 & d2 -- sequences + offset -- offset of d2 relative to d1 + sample_indices -- a list of indices to use for the comparison + """ + max_index = min(len(d1), len(d2)+offset) + if sample_indices is None: + sample_indices = range(0, max_index) + correct = 0 + total = 0 + for i in sample_indices: + if i >= max_index: + break + if d1[i] == d2[i-offset]: + correct += 1 + total += 1 + return (correct, total) + +def random_sample(size, num_samples=def_num_samples, seed=None): + """ + Returns a set of random integers between 0 and (size-1). + The set contains no more than num_samples integers. + """ + random.seed(seed) + if num_samples > size: + indices = set(range(0, size)) + else: + if num_samples > size/2: + num_samples = num_samples/2 + indices = set([]) + while len(indices) < num_samples: + index = random.randint(0, size-1) + indices.add(index) + indices = list(indices) + indices.sort() + return indices + +def align_sequences(d1, d2, + num_samples=def_num_samples, + max_offset=def_max_offset, + correct_cutoff=def_correct_cutoff, + seed=None, + indices=None): + """ + Takes two sequences and finds the offset and which the two sequences best + match. It returns the fraction correct, the number of entries compared, + the offset. + d1 & d2 -- sequences to compare + num_samples -- the maximum number of entries to compare + max_offset -- the maximum offset between the sequences that is checked + correct_cutoff -- If the fraction of bits correct is greater than this then + the offset is assumed to optimum. + seed -- a random number seed + indices -- an explicit list of the indices used to compare the two sequences + """ + max_overlap = max(len(d1), len(d2)) + if indices is None: + indices = random_sample(max_overlap, num_samples, seed) + max_frac_correct = 0 + best_offset = None + best_compared = None + best_correct = None + pos_range = range(0, min(len(d1), max_offset)) + neg_range = range(-1, -min(len(d2), max_offset), -1) + # Interleave the positive and negative offsets. + int_range = [item for items in zip(pos_range, neg_range) for item in items] + for offset in int_range: + correct, compared = compare_sequences(d1, d2, offset, indices) + frac_correct = 1.0*correct/compared + if frac_correct > max_frac_correct: + max_frac_correct = frac_correct + best_offset = offset + best_compared = compared + best_correct = correct + if frac_correct > correct_cutoff: + break + return max_frac_correct, best_compared, best_offset, indices + +if __name__ == "__main__": + import doctest + doctest.testmod() + diff --git a/gr-digital/python/utils/gray_code.py b/gr-digital/python/utils/gray_code.py new file mode 100644 index 000000000..926a1ded1 --- /dev/null +++ b/gr-digital/python/utils/gray_code.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# +# Copyright 2011 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. +# + +class GrayCodeGenerator(object): + """ + Generates and caches gray codes. + """ + + def __init__(self): + self.gcs = [0, 1] + # The last power of two passed through. + self.lp2 = 2 + # The next power of two that will be passed through. + self.np2 = 4 + # Curent index + self.i = 2 + + def get_gray_code(self, length): + """ + Returns a list of gray code of given length. + """ + if len(self.gcs) < length: + self.generate_new_gray_code(length) + return self.gcs[:length] + + def generate_new_gray_code(self, length): + """ + Generates new gray code and places into cache. + """ + while len(self.gcs) < length: + if self.i == self.lp2: + # if i is a power of two then gray number is of form 1100000... + result = self.i + self.i/2 + else: + # if not we take advantage of the symmetry of all but the last bit + # around a power of two. + result = self.gcs[2*self.lp2-1-self.i] + self.lp2 + self.gcs.append(result) + self.i += 1 + if self.i == self.np2: + self.lp2 = self.i + self.np2 = self.i*2 + +_gray_code_generator = GrayCodeGenerator() + +gray_code = _gray_code_generator.get_gray_code + diff --git a/gr-digital/python/utils/mod_codes.py b/gr-digital/python/utils/mod_codes.py new file mode 100644 index 000000000..caacda5cc --- /dev/null +++ b/gr-digital/python/utils/mod_codes.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +# +# Copyright 2011 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. +# + +GRAY_CODE = 'gray' +SET_PARTITION_CODE = 'set-partition' +NO_CODE = 'none' + +codes = (GRAY_CODE, SET_PARTITION_CODE, NO_CODE) + +def invert_code(code): + c = enumerate(code) + ic = [(b, a) for (a, b) in c] + ic.sort() + return [a for (b, a) in ic] |