root / gnuradio-core / src / python / gnuradio / blksimpl2 / gmsk.py @ 28e08614
History | View | Annotate | Download (11.5 kB)
| 1 | #
|
|---|---|
| 2 | # GMSK modulation and demodulation.
|
| 3 | #
|
| 4 | #
|
| 5 | # Copyright 2005,2006 Free Software Foundation, Inc.
|
| 6 | #
|
| 7 | # This file is part of GNU Radio
|
| 8 | #
|
| 9 | # GNU Radio is free software; you can redistribute it and/or modify
|
| 10 | # it under the terms of the GNU General Public License as published by
|
| 11 | # the Free Software Foundation; either version 2, or (at your option)
|
| 12 | # any later version.
|
| 13 | #
|
| 14 | # GNU Radio is distributed in the hope that it will be useful,
|
| 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 17 | # GNU General Public License for more details.
|
| 18 | #
|
| 19 | # You should have received a copy of the GNU General Public License
|
| 20 | # along with GNU Radio; see the file COPYING. If not, write to
|
| 21 | # the Free Software Foundation, Inc., 51 Franklin Street,
|
| 22 | # Boston, MA 02110-1301, USA.
|
| 23 | #
|
| 24 | |
| 25 | # See gnuradio-examples/python/digital for examples
|
| 26 | |
| 27 | from gnuradio import gr |
| 28 | from gnuradio import modulation_utils |
| 29 | from math import pi |
| 30 | import Numeric |
| 31 | from pprint import pprint |
| 32 | import inspect |
| 33 | |
| 34 | # default values (used in __init__ and add_options)
|
| 35 | _def_samples_per_symbol = 2
|
| 36 | _def_bt = 0.35
|
| 37 | _def_verbose = False
|
| 38 | _def_log = False
|
| 39 | |
| 40 | _def_gain_mu = 0.05
|
| 41 | _def_mu = 0.5
|
| 42 | _def_freq_error = 0.0
|
| 43 | _def_omega_relative_limit = 0.005
|
| 44 | |
| 45 | |
| 46 | # /////////////////////////////////////////////////////////////////////////////
|
| 47 | # GMSK modulator
|
| 48 | # /////////////////////////////////////////////////////////////////////////////
|
| 49 | |
| 50 | class gmsk_mod(gr.hier_block2): |
| 51 | |
| 52 | def __init__(self, |
| 53 | samples_per_symbol=_def_samples_per_symbol, |
| 54 | bt=_def_bt, |
| 55 | verbose=_def_verbose, |
| 56 | log=_def_log): |
| 57 | """
|
| 58 | Hierarchical block for Gaussian Minimum Shift Key (GMSK)
|
| 59 | modulation.
|
| 60 | |
| 61 | The input is a byte stream (unsigned char) and the
|
| 62 | output is the complex modulated signal at baseband.
|
| 63 | |
| 64 | @param samples_per_symbol: samples per baud >= 2
|
| 65 | @type samples_per_symbol: integer
|
| 66 | @param bt: Gaussian filter bandwidth * symbol time
|
| 67 | @type bt: float
|
| 68 | @param verbose: Print information about modulator?
|
| 69 | @type verbose: bool
|
| 70 | @param debug: Print modualtion data to files?
|
| 71 | @type debug: bool
|
| 72 | """ |
| 73 | |
| 74 | gr.hier_block2.__init__(self, "gmsk_mod", |
| 75 | gr.io_signature(1,1,gr.sizeof_char), # Input signature |
| 76 | gr.io_signature(1,1,gr.sizeof_gr_complex)) # Output signature |
| 77 | |
| 78 | self._samples_per_symbol = samples_per_symbol
|
| 79 | self._bt = bt
|
| 80 | |
| 81 | if not isinstance(samples_per_symbol, int) or samples_per_symbol < 2: |
| 82 | raise TypeError, ("samples_per_symbol must be an integer >= 2, is %r" % (samples_per_symbol,)) |
| 83 | |
| 84 | ntaps = 4 * samples_per_symbol # up to 3 bits in filter at once |
| 85 | sensitivity = (pi / 2) / samples_per_symbol # phase change per bit = pi / 2 |
| 86 | |
| 87 | # Turn it into NRZ data.
|
| 88 | self.nrz = gr.bytes_to_syms()
|
| 89 | |
| 90 | # Form Gaussian filter
|
| 91 | # Generate Gaussian response (Needs to be convolved with window below).
|
| 92 | self.gaussian_taps = gr.firdes.gaussian(
|
| 93 | 1, # gain |
| 94 | samples_per_symbol, # symbol_rate
|
| 95 | bt, # bandwidth * symbol time
|
| 96 | ntaps # number of taps
|
| 97 | ) |
| 98 | |
| 99 | self.sqwave = (1,) * samples_per_symbol # rectangular window |
| 100 | self.taps = Numeric.convolve(Numeric.array(self.gaussian_taps),Numeric.array(self.sqwave)) |
| 101 | self.gaussian_filter = gr.interp_fir_filter_fff(samples_per_symbol, self.taps) |
| 102 | |
| 103 | # FM modulation
|
| 104 | self.fmmod = gr.frequency_modulator_fc(sensitivity)
|
| 105 | |
| 106 | # Define components from objects
|
| 107 | self.define_component("nrz", self.nrz) |
| 108 | self.define_component("gaussian_filter", self.gaussian_filter) |
| 109 | self.define_component("fmmod", self.fmmod) |
| 110 | |
| 111 | # Connect components
|
| 112 | self.connect("self", 0, "nrz", 0) |
| 113 | self.connect("nrz", 0, "gaussian_filter", 0) |
| 114 | self.connect("gaussian_filter", 0, "fmmod", 0) |
| 115 | self.connect("fmmod", 0, "self", 0) |
| 116 | |
| 117 | if verbose:
|
| 118 | self._print_verbage()
|
| 119 | |
| 120 | if log:
|
| 121 | self._setup_logging()
|
| 122 | |
| 123 | def samples_per_symbol(self): |
| 124 | return self._samples_per_symbol |
| 125 | |
| 126 | def bits_per_symbol(self=None): # staticmethod that's also callable on an instance |
| 127 | return 1 |
| 128 | bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. |
| 129 | |
| 130 | |
| 131 | def _print_verbage(self): |
| 132 | print "bits per symbol = %d" % self.bits_per_symbol() |
| 133 | print "Gaussian filter bt = %.2f" % self._bt |
| 134 | |
| 135 | |
| 136 | def _setup_logging(self): |
| 137 | print "Modulation logging turned on." |
| 138 | self.define_component("nrz_dat", gr.file_sink(gr.sizeof_float, "tx_nrz.dat")) |
| 139 | self.define_component("gaussian_filter_dat", gr.file_sink(gr.sizeof_float, "tx_gaussian_filter.dat")) |
| 140 | self.define_component("fmmod_dat", gr.file_sink(gr.sizeof_gr_complex, "tx_fmmod.dat")) |
| 141 | |
| 142 | self.connect("nrz", 0, "nrz_dat", 0) |
| 143 | self.connect("gaussian_filter", 0, "gaussian_filter_dat", 0) |
| 144 | self.connect("fmmod", 0, "fmmod_dat", 0) |
| 145 | |
| 146 | def add_options(parser): |
| 147 | """
|
| 148 | Adds GMSK modulation-specific options to the standard parser
|
| 149 | """ |
| 150 | parser.add_option("", "--bt", type="float", default=_def_bt, |
| 151 | help="set bandwidth-time product [default=%default] (GMSK)")
|
| 152 | add_options=staticmethod(add_options)
|
| 153 | |
| 154 | |
| 155 | def extract_kwargs_from_options(options): |
| 156 | """
|
| 157 | Given command line options, create dictionary suitable for passing to __init__
|
| 158 | """ |
| 159 | return modulation_utils.extract_kwargs_from_options(gmsk_mod.__init__,
|
| 160 | ('self', 'fg'), options) |
| 161 | extract_kwargs_from_options=staticmethod(extract_kwargs_from_options)
|
| 162 | |
| 163 | |
| 164 | |
| 165 | # /////////////////////////////////////////////////////////////////////////////
|
| 166 | # GMSK demodulator
|
| 167 | # /////////////////////////////////////////////////////////////////////////////
|
| 168 | |
| 169 | class gmsk_demod(gr.hier_block2): |
| 170 | |
| 171 | def __init__(self, |
| 172 | samples_per_symbol=_def_samples_per_symbol, |
| 173 | gain_mu=_def_gain_mu, |
| 174 | mu=_def_mu, |
| 175 | omega_relative_limit=_def_omega_relative_limit, |
| 176 | freq_error=_def_freq_error, |
| 177 | verbose=_def_verbose, |
| 178 | log=_def_log): |
| 179 | """
|
| 180 | Hierarchical block for Gaussian Minimum Shift Key (GMSK)
|
| 181 | demodulation.
|
| 182 | |
| 183 | The input is the complex modulated signal at baseband.
|
| 184 | The output is a stream of bits packed 1 bit per byte (the LSB)
|
| 185 | |
| 186 | @param samples_per_symbol: samples per baud
|
| 187 | @type samples_per_symbol: integer
|
| 188 | @param verbose: Print information about modulator?
|
| 189 | @type verbose: bool
|
| 190 | @param log: Print modualtion data to files?
|
| 191 | @type log: bool
|
| 192 | |
| 193 | Clock recovery parameters. These all have reasonble defaults.
|
| 194 |
|
| 195 | @param gain_mu: controls rate of mu adjustment
|
| 196 | @type gain_mu: float
|
| 197 | @param mu: fractional delay [0.0, 1.0]
|
| 198 | @type mu: float
|
| 199 | @param omega_relative_limit: sets max variation in omega
|
| 200 | @type omega_relative_limit: float, typically 0.000200 (200 ppm)
|
| 201 | @param freq_error: bit rate error as a fraction
|
| 202 | @param float
|
| 203 | """ |
| 204 | |
| 205 | gr.hier_block2.__init__(self, "gmsk_demod", |
| 206 | gr.io_signature(1,1,gr.sizeof_gr_complex), # Input signature |
| 207 | gr.io_signature(1,1,gr.sizeof_char)) # Output signature |
| 208 | |
| 209 | self._samples_per_symbol = samples_per_symbol
|
| 210 | self._gain_mu = gain_mu
|
| 211 | self._mu = mu
|
| 212 | self._omega_relative_limit = omega_relative_limit
|
| 213 | self._freq_error = freq_error
|
| 214 | |
| 215 | if samples_per_symbol < 2: |
| 216 | raise TypeError, "samples_per_symbol >= 2, is %f" % samples_per_symbol |
| 217 | |
| 218 | self._omega = samples_per_symbol*(1+self._freq_error) |
| 219 | |
| 220 | self._gain_omega = .25 * self._gain_mu * self._gain_mu # critically damped |
| 221 | |
| 222 | # Demodulate FM
|
| 223 | sensitivity = (pi / 2) / samples_per_symbol
|
| 224 | self.fmdemod = gr.quadrature_demod_cf(1.0 / sensitivity) |
| 225 | |
| 226 | # the clock recovery block tracks the symbol clock and resamples as needed.
|
| 227 | # the output of the block is a stream of soft symbols (float)
|
| 228 | self.clock_recovery = gr.clock_recovery_mm_ff(self._omega, self._gain_omega, |
| 229 | self._mu, self._gain_mu, |
| 230 | self._omega_relative_limit)
|
| 231 | |
| 232 | # slice the floats at 0, outputting 1 bit (the LSB of the output byte) per sample
|
| 233 | self.slicer = gr.binary_slicer_fb()
|
| 234 | |
| 235 | # Define components from objects
|
| 236 | self.define_component("fmdemod", self.fmdemod) |
| 237 | self.define_component("clock_recovery", self.clock_recovery) |
| 238 | self.define_component("slicer", self.slicer) |
| 239 | |
| 240 | # Connect components
|
| 241 | self.connect("self", 0, "fmdemod", 0) |
| 242 | self.connect("fmdemod", 0, "clock_recovery", 0) |
| 243 | self.connect("clock_recovery", 0, "slicer", 0) |
| 244 | self.connect("slicer", 0, "self", 0) |
| 245 | |
| 246 | if verbose:
|
| 247 | self._print_verbage()
|
| 248 | |
| 249 | if log:
|
| 250 | self._setup_logging()
|
| 251 | |
| 252 | def samples_per_symbol(self): |
| 253 | return self._samples_per_symbol |
| 254 | |
| 255 | def bits_per_symbol(self=None): # staticmethod that's also callable on an instance |
| 256 | return 1 |
| 257 | bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. |
| 258 | |
| 259 | def _print_verbage(self): |
| 260 | print "bits per symbol = %d" % self.bits_per_symbol() |
| 261 | print "M&M clock recovery omega = %f" % self._omega |
| 262 | print "M&M clock recovery gain mu = %f" % self._gain_mu |
| 263 | print "M&M clock recovery mu = %f" % self._mu |
| 264 | print "M&M clock recovery omega rel. limit = %f" % self._omega_relative_limit |
| 265 | print "frequency error = %f" % self._freq_error |
| 266 | |
| 267 | |
| 268 | def _setup_logging(self): |
| 269 | print "Demodulation logging turned on." |
| 270 | self.define_component("fmdemod_dat", gr.file_sink(gr.sizeof_float, "rx_fmdemod.dat")) |
| 271 | self.define_component("clock_recovery_dat", gr.file_sink(gr.sizeof_float, "rx_clock_recovery.dat")) |
| 272 | self.define_component("slicer_dat", gr.file_sink(gr.sizeof_char, "rx_slicer.dat")) |
| 273 | |
| 274 | self.connect("fmdemod", 0, "fmdemod_dat", 0) |
| 275 | self.connect("clock_recovery", 0, "clock_recovery_dat", 0) |
| 276 | self.connect("slicer", 0, "slicer_dat", 0) |
| 277 | |
| 278 | def add_options(parser): |
| 279 | """
|
| 280 | Adds GMSK demodulation-specific options to the standard parser
|
| 281 | """ |
| 282 | parser.add_option("", "--gain-mu", type="float", default=_def_gain_mu, |
| 283 | help="M&M clock recovery gain mu [default=%default] (GMSK/PSK)")
|
| 284 | parser.add_option("", "--mu", type="float", default=_def_mu, |
| 285 | help="M&M clock recovery mu [default=%default] (GMSK/PSK)")
|
| 286 | parser.add_option("", "--omega-relative-limit", type="float", default=_def_omega_relative_limit, |
| 287 | help="M&M clock recovery omega relative limit [default=%default] (GMSK/PSK)")
|
| 288 | parser.add_option("", "--freq-error", type="float", default=_def_freq_error, |
| 289 | help="M&M clock recovery frequency error [default=%default] (GMSK)")
|
| 290 | add_options=staticmethod(add_options)
|
| 291 | |
| 292 | def extract_kwargs_from_options(options): |
| 293 | """
|
| 294 | Given command line options, create dictionary suitable for passing to __init__
|
| 295 | """ |
| 296 | return modulation_utils.extract_kwargs_from_options(gmsk_demod.__init__,
|
| 297 | ('self', 'fg'), options) |
| 298 | extract_kwargs_from_options=staticmethod(extract_kwargs_from_options)
|
| 299 | |
| 300 | |
| 301 | #
|
| 302 | # Add these to the mod/demod registry
|
| 303 | #
|
| 304 | modulation_utils.add_type_1_mod('gmsk', gmsk_mod)
|
| 305 | modulation_utils.add_type_1_demod('gmsk', gmsk_demod)
|