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
[email protected]
http://lists.gnu.org/mailman/listinfo/discuss-gnuradio