#!/usr/bin/env python # # GMSK modulation and demodulation. # # # 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. # # See gnuradio-examples/python/digital for examples from gnuradio import gr from gnuradio import modulation_utils from math import pi import numpy from pprint import pprint import inspect import time import struct import pylab as p import sys from math import atan2 # default values (used in __init__ and add_options) _def_samples_per_symbol = 2 _def_bt = 0.35 _def_verbose = True _def_log = True _def_gain_mu = 0.05 _def_mu = 0.5 _def_freq_error = 0.0 _def_omega_relative_limit = 0.005 # ///////////////////////////////////////////////////////////////////////////// # GMSK modulator # ///////////////////////////////////////////////////////////////////////////// class gmsk_mod(gr.hier_block2): def __init__(self, samples_per_symbol=_def_samples_per_symbol, bt=_def_bt, verbose=_def_verbose, log=_def_log): """ Hierarchical block for Gaussian Minimum Shift Key (GMSK) 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 baud >= 2 @type samples_per_symbol: integer @param bt: Gaussian filter bandwidth * symbol time @type bt: 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, "gmsk_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._bt = bt if not isinstance(samples_per_symbol, int) or samples_per_symbol < 2: raise TypeError, ("samples_per_symbol must be an integer >= 2, is %r" % (samples_per_symbol,)) ntaps = 4 * samples_per_symbol # up to 3 bits in filter at once sensitivity = (pi / 2) / samples_per_symbol # phase change per bit = pi / 2 # Turn it into NRZ data. self.nrz = gr.bytes_to_syms() # Form Gaussian filter # Generate Gaussian response (Needs to be convolved with window below). self.gaussian_taps = gr.firdes.gaussian( 1, # gain samples_per_symbol, # symbol_rate bt, # bandwidth * symbol time ntaps # number of taps ) self.sqwave = (1,) * samples_per_symbol # rectangular window self.taps = numpy.convolve(numpy.array(self.gaussian_taps),numpy.array(self.sqwave)) self.gaussian_filter = gr.interp_fir_filter_fff(samples_per_symbol, self.taps) # FM modulation self.fmmod = gr.frequency_modulator_fc(sensitivity) # Connect components self.connect(self, self.nrz, self.gaussian_filter, self.fmmod, 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): # staticmethod that's also callable on an instance return 1 bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. def _print_verbage(self): print "bits per symbol = %d" % self.bits_per_symbol() print "Gaussian filter bt = %.2f" % self._bt def _setup_logging(self): print "Modulation logging turned on." self.connect(self.nrz, gr.file_sink(gr.sizeof_float, "tx_nrz.dat")) self.connect(self.gaussian_filter, gr.file_sink(gr.sizeof_float, "tx_gaussian_filter.dat")) self.connect(self.fmmod, gr.file_sink(gr.sizeof_gr_complex, "tx_fmmod.dat")) def add_options(parser): """ Adds GMSK modulation-specific options to the standard parser """ parser.add_option("", "--bt", type="float", default=_def_bt, help="set bandwidth-time product [default=%default] (GMSK)") add_options=staticmethod(add_options) # ///////////////////////////////////////////////////////////////////////////// # GMSK demodulator # ///////////////////////////////////////////////////////////////////////////// class gmsk_demod(gr.hier_block2): def __init__(self, samples_per_symbol=_def_samples_per_symbol, gain_mu=_def_gain_mu, mu=_def_mu, omega_relative_limit=_def_omega_relative_limit, freq_error=_def_freq_error, verbose=_def_verbose, log=_def_log): """ Hierarchical block for Gaussian Minimum Shift Key (GMSK) demodulation. The input is the complex modulated signal at baseband. The output is a stream of bits packed 1 bit per byte (the LSB) @param samples_per_symbol: samples per baud @type samples_per_symbol: integer @param verbose: Print information about modulator? @type verbose: bool @param log: Print modualtion data to files? @type log: bool Clock recovery parameters. These all have reasonble defaults. @param gain_mu: controls rate of mu adjustment @type gain_mu: float @param mu: fractional delay [0.0, 1.0] @type mu: float @param omega_relative_limit: sets max variation in omega @type omega_relative_limit: float, typically 0.000200 (200 ppm) @param freq_error: bit rate error as a fraction @param float """ gr.hier_block2.__init__(self, "gmsk_demod", gr.io_signature(1,1,gr.sizeof_gr_complex), # Input signature gr.io_signature(1,1,gr.sizeof_char)) # Output signature self._samples_per_symbol = samples_per_symbol self._gain_mu = gain_mu self._mu = mu self._omega_relative_limit = omega_relative_limit self._freq_error = freq_error if samples_per_symbol < 2: raise TypeError, "samples_per_symbol >= 2, is %f" % samples_per_symbol self._omega = samples_per_symbol*(1+self._freq_error) self._gain_omega = .25 * self._gain_mu * self._gain_mu # critically damped # Demodulate FM sensitivity = (pi / 2) / samples_per_symbol self.fmdemod = gr.quadrature_demod_cf(1.0 / sensitivity) # the clock recovery block tracks the symbol clock and resamples as needed. # the output of the block is a stream of soft symbols (float) self.clock_recovery = gr.clock_recovery_mm_ff(self._omega, self._gain_omega, self._mu, self._gain_mu, self._omega_relative_limit) # slice the floats at 0, outputting 1 bit (the LSB of the output byte) per sample self.slicer = gr.binary_slicer_fb() # Connect components self.connect(self, self.fmdemod, self.clock_recovery, self.slicer, 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): # staticmethod that's also callable on an instance return 1 bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. def _print_verbage(self): print "bits per symbol = %d" % self.bits_per_symbol() print "M&M clock recovery omega = %f" % self._omega print "M&M clock recovery gain mu = %f" % self._gain_mu print "M&M clock recovery mu = %f" % self._mu print "M&M clock recovery omega rel. limit = %f" % self._omega_relative_limit print "frequency error = %f" % self._freq_error def _setup_logging(self): print "Demodulation logging turned on." self.connect(self.fmdemod, gr.file_sink(gr.sizeof_float, "rx_fmdemod.dat")) self.connect(self.clock_recovery, gr.file_sink(gr.sizeof_float, "rx_clock_recovery.dat")) self.connect(self.slicer, gr.file_sink(gr.sizeof_char, "rx_slicer.dat")) def add_options(parser): """ Adds GMSK demodulation-specific options to the standard parser """ parser.add_option("", "--gain-mu", type="float", default=_def_gain_mu, help="M&M clock recovery gain mu [default=%default] (GMSK/PSK)") parser.add_option("", "--mu", type="float", default=_def_mu, help="M&M clock recovery mu [default=%default] (GMSK/PSK)") parser.add_option("", "--omega-relative-limit", type="float", default=_def_omega_relative_limit, help="M&M clock recovery omega relative limit [default=%default] (GMSK/PSK)") parser.add_option("", "--freq-error", type="float", default=_def_freq_error, help="M&M clock recovery frequency error [default=%default] (GMSK)") add_options=staticmethod(add_options) class test_block(gr.top_block): def __init__(self, samp_per_symbol, bt): gr.top_block.__init__(self, "test_block") degree = 11 length = 2**degree-1 src = gr.glfsr_source_b(degree) packer = gr.unpacked_to_packed_bb(1, gr.GR_MSB_FIRST) head = gr.head(gr.sizeof_char, 3*length) mod = gmsk_mod(samples_per_symbol=samp_per_symbol, bt=bt) demod = gmsk_demod(samples_per_symbol=samp_per_symbol) dst = gr.null_sink(gr.sizeof_char) #dst = gr.vector_sink_b() self.connect(src, head, packer, mod, demod, dst) def fm_quadrature_demod(re, im, gain): num_c = min(len(re),len(im)) out = [] for i in range(1,num_c): prod = complex(re[i],im[i])*(complex(re[i-1],im[i-1]).conjugate()) out.append(gain*atan2(prod.imag, prod.real)) return out def get_traces(data, num_traces, samp_per_symbol): traces = [] trace_len = samp_per_symbol*2 for i in range(num_traces): start_ind = samp_per_symbol/2+(i+3)*trace_len traces.append(data[start_ind:start_ind+trace_len+1]) return traces def main(args): if(len(args) < 1): print 'Please pass in samp_per_sym' sys.exit(0) bt=0.35 samp_per_symbol=int(args[0]) t = test_block(bt=bt, samp_per_symbol=samp_per_symbol) t.start() t.wait() num_traces = 100 if 1: f = open('tx_gaussian_filter.dat','r') d = f.read() num_f = len(d)/4 data = struct.unpack('f'*num_f, d) p.figure() p.hold(True) traces = get_traces(data, num_traces, samp_per_symbol) for tr in traces: p.plot(tr, 'b') f = open('rx_fmdemod.dat','r') d = f.read() num_f = len(d)/4 data = struct.unpack('f'*num_f, d) #p.figure() #p.hold(True) p.title('bt=%f samp_per_sym=%d b=tx, r=rx' % (bt, samp_per_symbol)) traces = get_traces(data, num_traces, samp_per_symbol) for tr in traces: p.plot(tr, 'r') p.xlim(0, samp_per_symbol*2) p.ylim(-1.2, 1.2) if 1: p.figure() p.title('rx_fmdemod') p.plot(data) if 0: f = open('tx_fmmod.dat','r') d = f.read() num_f = len(d)/4 data = struct.unpack('f'*num_f, d) re = data[0::2] im = data[1::2] p.figure() p.title('tx_fmmod') p.plot(re) p.hold(True) p.plot(im, 'r') if 1: sensitivity = (pi / 2) / samp_per_symbol out = fm_quadrature_demod(re, im, 1.0/sensitivity) p.figure() p.title('python_fmdemod') p.plot(out) p.show() if __name__ == '__main__': main(sys.argv[1:])