1# 2# GMSK modulation and demodulation. 3# 4# 5# Copyright 2005-2007,2012 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 3, 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 25from __future__ import print_function 26from __future__ import absolute_import 27from __future__ import division 28from __future__ import unicode_literals 29 30# See gnuradio-examples/python/digital for examples 31 32from math import pi 33from pprint import pprint 34import inspect 35 36import numpy 37 38from gnuradio import gr, blocks, analog, filter 39from . import modulation_utils 40from . import digital_swig as digital 41 42# default values (used in __init__ and add_options) 43_def_samples_per_symbol = 2 44_def_bt = 0.35 45_def_verbose = False 46_def_log = False 47 48_def_gain_mu = None 49_def_mu = 0.5 50_def_freq_error = 0.0 51_def_omega_relative_limit = 0.005 52 53 54# FIXME: Figure out how to make GMSK work with pfb_arb_resampler_fff for both 55# transmit and receive so we don't require integer samples per symbol. 56 57 58# ///////////////////////////////////////////////////////////////////////////// 59# GMSK modulator 60# ///////////////////////////////////////////////////////////////////////////// 61 62class gmsk_mod(gr.hier_block2): 63 """ 64 Hierarchical block for Gaussian Minimum Shift Key (GMSK) 65 modulation. 66 67 The input is a byte stream (unsigned char with packed bits) 68 and the output is the complex modulated signal at baseband. 69 70 Args: 71 samples_per_symbol: samples per baud >= 2 (integer) 72 bt: Gaussian filter bandwidth * symbol time (float) 73 verbose: Print information about modulator? (boolean) 74 log: Print modulation data to files? (boolean) 75 """ 76 77 def __init__(self, 78 samples_per_symbol=_def_samples_per_symbol, 79 bt=_def_bt, 80 verbose=_def_verbose, 81 log=_def_log): 82 83 gr.hier_block2.__init__(self, "gmsk_mod", 84 gr.io_signature(1, 1, gr.sizeof_char), # Input signature 85 gr.io_signature(1, 1, gr.sizeof_gr_complex)) # Output signature 86 87 samples_per_symbol = int(samples_per_symbol) 88 self._samples_per_symbol = samples_per_symbol 89 self._bt = bt 90 self._differential = False 91 92 if not isinstance(samples_per_symbol, int) or samples_per_symbol < 2: 93 raise TypeError("samples_per_symbol must be an integer >= 2, is %r" % (samples_per_symbol,)) 94 95 ntaps = 4 * samples_per_symbol # up to 3 bits in filter at once 96 sensitivity = (pi / 2) / samples_per_symbol # phase change per bit = pi / 2 97 98 # Turn it into NRZ data. 99 #self.nrz = digital.bytes_to_syms() 100 self.unpack = blocks.packed_to_unpacked_bb(1, gr.GR_MSB_FIRST) 101 self.nrz = digital.chunks_to_symbols_bf([-1, 1], 1) 102 103 # Form Gaussian filter 104 # Generate Gaussian response (Needs to be convolved with window below). 105 self.gaussian_taps = filter.firdes.gaussian( 106 1, # gain 107 samples_per_symbol, # symbol_rate 108 bt, # bandwidth * symbol time 109 ntaps # number of taps 110 ) 111 112 self.sqwave = (1,) * samples_per_symbol # rectangular window 113 self.taps = numpy.convolve(numpy.array(self.gaussian_taps),numpy.array(self.sqwave)) 114 self.gaussian_filter = filter.interp_fir_filter_fff(samples_per_symbol, self.taps) 115 116 # FM modulation 117 self.fmmod = analog.frequency_modulator_fc(sensitivity) 118 119 if verbose: 120 self._print_verbage() 121 122 if log: 123 self._setup_logging() 124 125 # Connect & Initialize base class 126 self.connect(self, self.unpack, self.nrz, self.gaussian_filter, self.fmmod, self) 127 128 def samples_per_symbol(self): 129 return self._samples_per_symbol 130 131 @staticmethod 132 def bits_per_symbol(self=None): # staticmethod that's also callable on an instance 133 return 1 134 135 def _print_verbage(self): 136 print("bits per symbol = %d" % self.bits_per_symbol()) 137 print("Gaussian filter bt = %.2f" % self._bt) 138 139 140 def _setup_logging(self): 141 print("Modulation logging turned on.") 142 self.connect(self.nrz, 143 blocks.file_sink(gr.sizeof_float, "nrz.dat")) 144 self.connect(self.gaussian_filter, 145 blocks.file_sink(gr.sizeof_float, "gaussian_filter.dat")) 146 self.connect(self.fmmod, 147 blocks.file_sink(gr.sizeof_gr_complex, "fmmod.dat")) 148 149 @staticmethod 150 def add_options(parser): 151 """ 152 Adds GMSK modulation-specific options to the standard parser 153 """ 154 parser.add_option("", "--bt", type="float", default=_def_bt, 155 help="set bandwidth-time product [default=%default] (GMSK)") 156 157 @staticmethod 158 def extract_kwargs_from_options(options): 159 """ 160 Given command line options, create dictionary suitable for passing to __init__ 161 """ 162 return modulation_utils.extract_kwargs_from_options(gmsk_mod.__init__, 163 ('self',), options) 164 165 166# ///////////////////////////////////////////////////////////////////////////// 167# GMSK demodulator 168# ///////////////////////////////////////////////////////////////////////////// 169 170class gmsk_demod(gr.hier_block2): 171 """ 172 Hierarchical block for Gaussian Minimum Shift Key (GMSK) 173 demodulation. 174 175 The input is the complex modulated signal at baseband. 176 The output is a stream of bits packed 1 bit per byte (the LSB) 177 178 Args: 179 samples_per_symbol: samples per baud (integer) 180 gain_mu: controls rate of mu adjustment (float) 181 mu: fractional delay [0.0, 1.0] (float) 182 omega_relative_limit: sets max variation in omega (float) 183 freq_error: bit rate error as a fraction (float) 184 verbose: Print information about modulator? (boolean) 185 log: Print modualtion data to files? (boolean) 186 """ 187 188 def __init__(self, 189 samples_per_symbol=_def_samples_per_symbol, 190 gain_mu=_def_gain_mu, 191 mu=_def_mu, 192 omega_relative_limit=_def_omega_relative_limit, 193 freq_error=_def_freq_error, 194 verbose=_def_verbose, 195 log=_def_log): 196 197 gr.hier_block2.__init__(self, "gmsk_demod", 198 gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature 199 gr.io_signature(1, 1, gr.sizeof_char)) # Output signature 200 201 self._samples_per_symbol = samples_per_symbol 202 self._gain_mu = gain_mu 203 self._mu = mu 204 self._omega_relative_limit = omega_relative_limit 205 self._freq_error = freq_error 206 self._differential = False 207 208 if samples_per_symbol < 2: 209 raise TypeError("samples_per_symbol >= 2, is %f" % samples_per_symbol) 210 211 self._omega = samples_per_symbol*(1+self._freq_error) 212 213 if not self._gain_mu: 214 self._gain_mu = 0.175 215 216 self._gain_omega = .25 * self._gain_mu * self._gain_mu # critically damped 217 218 # Demodulate FM 219 sensitivity = (pi / 2) / samples_per_symbol 220 self.fmdemod = analog.quadrature_demod_cf(1.0 / sensitivity) 221 222 # the clock recovery block tracks the symbol clock and resamples as needed. 223 # the output of the block is a stream of soft symbols (float) 224 self.clock_recovery = digital.clock_recovery_mm_ff(self._omega, self._gain_omega, 225 self._mu, self._gain_mu, 226 self._omega_relative_limit) 227 228 # slice the floats at 0, outputting 1 bit (the LSB of the output byte) per sample 229 self.slicer = digital.binary_slicer_fb() 230 231 if verbose: 232 self._print_verbage() 233 234 if log: 235 self._setup_logging() 236 237 # Connect & Initialize base class 238 self.connect(self, self.fmdemod, self.clock_recovery, self.slicer, self) 239 240 def samples_per_symbol(self): 241 return self._samples_per_symbol 242 243 @staticmethod 244 def bits_per_symbol(self=None): # staticmethod that's also callable on an instance 245 return 1 246 247 def _print_verbage(self): 248 print("bits per symbol = %d" % self.bits_per_symbol()) 249 print("M&M clock recovery omega = %f" % self._omega) 250 print("M&M clock recovery gain mu = %f" % self._gain_mu) 251 print("M&M clock recovery mu = %f" % self._mu) 252 print("M&M clock recovery omega rel. limit = %f" % self._omega_relative_limit) 253 print("frequency error = %f" % self._freq_error) 254 255 256 def _setup_logging(self): 257 print("Demodulation logging turned on.") 258 self.connect(self.fmdemod, 259 blocks.file_sink(gr.sizeof_float, "fmdemod.dat")) 260 self.connect(self.clock_recovery, 261 blocks.file_sink(gr.sizeof_float, "clock_recovery.dat")) 262 self.connect(self.slicer, 263 blocks.file_sink(gr.sizeof_char, "slicer.dat")) 264 265 @staticmethod 266 def add_options(parser): 267 """ 268 Adds GMSK demodulation-specific options to the standard parser 269 """ 270 parser.add_option("", "--gain-mu", type="float", default=_def_gain_mu, 271 help="M&M clock recovery gain mu [default=%default] (GMSK/PSK)") 272 parser.add_option("", "--mu", type="float", default=_def_mu, 273 help="M&M clock recovery mu [default=%default] (GMSK/PSK)") 274 parser.add_option("", "--omega-relative-limit", type="float", default=_def_omega_relative_limit, 275 help="M&M clock recovery omega relative limit [default=%default] (GMSK/PSK)") 276 parser.add_option("", "--freq-error", type="float", default=_def_freq_error, 277 help="M&M clock recovery frequency error [default=%default] (GMSK)") 278 279 @staticmethod 280 def extract_kwargs_from_options(options): 281 """ 282 Given command line options, create dictionary suitable for passing to __init__ 283 """ 284 return modulation_utils.extract_kwargs_from_options(gmsk_demod.__init__, 285 ('self',), options) 286 287 288# 289# Add these to the mod/demod registry 290# 291modulation_utils.add_type_1_mod('gmsk', gmsk_mod) 292modulation_utils.add_type_1_demod('gmsk', gmsk_demod) 293