Hi all-
please look at this sequence of eye diagrams:
http://picasaweb.google.com/steven.p.clark/GMSKFmdemodGlitches

These are from a gmsk mod/demod pair, showing the output of the TX's
gaussian filter (blue) overlaid with the output of the RX's fmdemod (red).
BT = 0.35.

At 8 samples per symbol, everything looks ok. Red is pretty much right on
top of blue, as we'd expect.
As I increase samples per symbol, however, something strange happens, shown
in plot sequence. The RX's fmdemod output gets successively more "glitchy".

If I (in python) do the same thing that gr_quadrature_demod_cf is doing in
C, ie:

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

the glitches do not exist. This tells me that TX fmmod output is fine.
Something is going wonky as gr_quadrature_demod_cf does its work. Any ideas?
Is this a problem with the scheduler?

Code attached (incidentally, I tried using the old style flowgraph, without
hier_block2 / top_block, glitches were still present).
#!/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:])


_______________________________________________
Discuss-gnuradio mailing list
Discuss-gnuradio@gnu.org
http://lists.gnu.org/mailman/listinfo/discuss-gnuradio

Reply via email to