Friday, April 1, 2016

Re: [Discuss-gnuradio] costas ambiguity and correlate-and-sync block in qpsk

#!/usr/bin/env python2
##################################################
# GNU Radio Python Flow Graph
# Title: Rx Syncd
# Generated: Fri Apr 1 08:56:52 2016
##################################################

if __name__ == '__main__':
import ctypes
import sys
if sys.platform.startswith('linux'):
try:
x11 = ctypes.cdll.LoadLibrary('libX11.so')
x11.XInitThreads()
except:
print "Warning: failed to XInitThreads()"

from PyQt4 import Qt
from gnuradio import analog
from gnuradio import blocks
from gnuradio import digital
from gnuradio import eng_notation
from gnuradio import gr
from gnuradio import qtgui
from gnuradio.eng_option import eng_option
from gnuradio.filter import firdes
from gnuradio.qtgui import Range, RangeWidget
from optparse import OptionParser
import cmath
import math
import numpy as np
import pmt
import sip
import sys


class Rx_syncd(gr.top_block, Qt.QWidget):

def __init__(self):
gr.top_block.__init__(self, "Rx Syncd")
Qt.QWidget.__init__(self)
self.setWindowTitle("Rx Syncd")
try:
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))
except:
pass
self.top_scroll_layout = Qt.QVBoxLayout()
self.setLayout(self.top_scroll_layout)
self.top_scroll = Qt.QScrollArea()
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)
self.top_scroll_layout.addWidget(self.top_scroll)
self.top_scroll.setWidgetResizable(True)
self.top_widget = Qt.QWidget()
self.top_scroll.setWidget(self.top_widget)
self.top_layout = Qt.QVBoxLayout(self.top_widget)
self.top_grid_layout = Qt.QGridLayout()
self.top_layout.addLayout(self.top_grid_layout)

self.settings = Qt.QSettings("GNU Radio", "Rx_syncd")
self.restoreGeometry(self.settings.value("geometry").toByteArray())

##################################################
# Variables
##################################################
self.tx_sps = tx_sps = 10
self.preamble_bytes = preamble_bytes = [0xfc, 0x0c, 0x30, 0x00]
self.eb = eb = 0.35
self.constel_rx = constel_rx = digital.constellation_rect((digital.psk_4()[0]), (digital.psk_4()[1]), 4 , 2, 2, 1, 1).base()
self.constel_rx.gen_soft_dec_lut(8)
self.sps = sps = 2
self.samp_rate = samp_rate = 320000
self.modulated_preamble_untrimmed = modulated_preamble_untrimmed = digital.modulate_vector_bc(digital.generic_mod(constellation=constel_rx,differential=False,samples_per_symbol=tx_sps,pre_diff_code=True,excess_bw=eb,verbose=False,log=False) .to_basic_block(), (preamble_bytes*6), ([1]))
self.tx_samp_rate = tx_samp_rate = samp_rate*tx_sps/sps
self.taps = taps = firdes.root_raised_cosine(32, 32,1.0, eb, 11*sps*32)
self.phase_offset = phase_offset = 0
self.modulated_preamble = modulated_preamble = [modulated_preamble_untrimmed[i] for i in range(709,860)]
self.const_order = const_order = 4

##################################################
# Blocks
##################################################
self._phase_offset_range = Range(-math.pi, math.pi, math.pi/200.0, 0, 200)
self._phase_offset_win = RangeWidget(self._phase_offset_range, self.set_phase_offset, "Phase Offset", "counter_slider", float)
self.top_layout.addWidget(self._phase_offset_win)
self.qtgui_time_sink_x_2_0_0 = qtgui.time_sink_f(
1024*4, #size
tx_samp_rate, #samp_rate
"Correlation Outputs", #name
2 #number of inputs
)
self.qtgui_time_sink_x_2_0_0.set_update_time(0.10)
self.qtgui_time_sink_x_2_0_0.set_y_axis(-1.5, 1.5)

self.qtgui_time_sink_x_2_0_0.set_y_label("Amplitude", "")

self.qtgui_time_sink_x_2_0_0.enable_tags(-1, True)
self.qtgui_time_sink_x_2_0_0.set_trigger_mode(qtgui.TRIG_MODE_TAG, qtgui.TRIG_SLOPE_POS, 0.5, 0.0005, 0, "corr_start")
self.qtgui_time_sink_x_2_0_0.enable_autoscale(False)
self.qtgui_time_sink_x_2_0_0.enable_grid(True)
self.qtgui_time_sink_x_2_0_0.enable_control_panel(False)

if not True:
self.qtgui_time_sink_x_2_0_0.disable_legend()

labels = ["Rx Phase", "|Correlation|^2", "", "", "",
"", "", "", "", ""]
widths = [1, 1, 1, 1, 1,
1, 1, 1, 1, 1]
colors = ["blue", "red", "green", "black", "cyan",
"magenta", "yellow", "dark red", "dark green", "blue"]
styles = [1, 1, 1, 1, 1,
1, 1, 1, 1, 1]
markers = [-1, -1, -1, -1, -1,
-1, -1, -1, -1, -1]
alphas = [1.0, 1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 1.0, 1.0]

for i in xrange(2):
if len(labels[i]) == 0:
self.qtgui_time_sink_x_2_0_0.set_line_label(i, "Data {0}".format(i))
else:
self.qtgui_time_sink_x_2_0_0.set_line_label(i, labels[i])
self.qtgui_time_sink_x_2_0_0.set_line_width(i, widths[i])
self.qtgui_time_sink_x_2_0_0.set_line_color(i, colors[i])
self.qtgui_time_sink_x_2_0_0.set_line_style(i, styles[i])
self.qtgui_time_sink_x_2_0_0.set_line_marker(i, markers[i])
self.qtgui_time_sink_x_2_0_0.set_line_alpha(i, alphas[i])

self._qtgui_time_sink_x_2_0_0_win = sip.wrapinstance(self.qtgui_time_sink_x_2_0_0.pyqwidget(), Qt.QWidget)
self.top_layout.addWidget(self._qtgui_time_sink_x_2_0_0_win)
self.qtgui_time_sink_x_1 = qtgui.time_sink_f(
1024, #size
samp_rate, #samp_rate
"Bits", #name
2 #number of inputs
)
self.qtgui_time_sink_x_1.set_update_time(0.10)
self.qtgui_time_sink_x_1.set_y_axis(-2, 2)

self.qtgui_time_sink_x_1.set_y_label("Amplitude", "")

self.qtgui_time_sink_x_1.enable_tags(-1, True)
self.qtgui_time_sink_x_1.set_trigger_mode(qtgui.TRIG_MODE_TAG, qtgui.TRIG_SLOPE_POS, 0, 0.0001, 0, "corr_start")
self.qtgui_time_sink_x_1.enable_autoscale(False)
self.qtgui_time_sink_x_1.enable_grid(True)
self.qtgui_time_sink_x_1.enable_control_panel(False)

if not True:
self.qtgui_time_sink_x_1.disable_legend()

labels = ["Soft Bits", "Hard Bits", "", "", "",
"", "", "", "", ""]
widths = [1, 1, 1, 1, 1,
1, 1, 1, 1, 1]
colors = ["blue", "red", "green", "black", "cyan",
"magenta", "yellow", "dark red", "dark green", "blue"]
styles = [1, 1, 1, 1, 1,
1, 1, 1, 1, 1]
markers = [-1, -1, -1, -1, -1,
-1, -1, -1, -1, -1]
alphas = [1.0, 1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 1.0, 1.0]

for i in xrange(2):
if len(labels[i]) == 0:
self.qtgui_time_sink_x_1.set_line_label(i, "Data {0}".format(i))
else:
self.qtgui_time_sink_x_1.set_line_label(i, labels[i])
self.qtgui_time_sink_x_1.set_line_width(i, widths[i])
self.qtgui_time_sink_x_1.set_line_color(i, colors[i])
self.qtgui_time_sink_x_1.set_line_style(i, styles[i])
self.qtgui_time_sink_x_1.set_line_marker(i, markers[i])
self.qtgui_time_sink_x_1.set_line_alpha(i, alphas[i])

self._qtgui_time_sink_x_1_win = sip.wrapinstance(self.qtgui_time_sink_x_1.pyqwidget(), Qt.QWidget)
self.top_layout.addWidget(self._qtgui_time_sink_x_1_win)
self.qtgui_const_sink_x_0_1 = qtgui.const_sink_c(
1024, #size
"pf clock / mm sync out", #name
1 #number of inputs
)
self.qtgui_const_sink_x_0_1.set_update_time(0.10)
self.qtgui_const_sink_x_0_1.set_y_axis(-2, 2)
self.qtgui_const_sink_x_0_1.set_x_axis(-2, 2)
self.qtgui_const_sink_x_0_1.set_trigger_mode(qtgui.TRIG_MODE_TAG, qtgui.TRIG_SLOPE_POS, 0.0, 0, "phase_est")
self.qtgui_const_sink_x_0_1.enable_autoscale(False)
self.qtgui_const_sink_x_0_1.enable_grid(False)

if not True:
self.qtgui_const_sink_x_0_1.disable_legend()

labels = ["", "", "", "", "",
"", "", "", "", ""]
widths = [1, 1, 1, 1, 1,
1, 1, 1, 1, 1]
colors = ["blue", "red", "red", "red", "red",
"red", "red", "red", "red", "red"]
styles = [1, 0, 0, 0, 0,
0, 0, 0, 0, 0]
markers = [0, 0, 0, 0, 0,
0, 0, 0, 0, 0]
alphas = [1.0, 1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 1.0, 1.0]
for i in xrange(1):
if len(labels[i]) == 0:
self.qtgui_const_sink_x_0_1.set_line_label(i, "Data {0}".format(i))
else:
self.qtgui_const_sink_x_0_1.set_line_label(i, labels[i])
self.qtgui_const_sink_x_0_1.set_line_width(i, widths[i])
self.qtgui_const_sink_x_0_1.set_line_color(i, colors[i])
self.qtgui_const_sink_x_0_1.set_line_style(i, styles[i])
self.qtgui_const_sink_x_0_1.set_line_marker(i, markers[i])
self.qtgui_const_sink_x_0_1.set_line_alpha(i, alphas[i])

self._qtgui_const_sink_x_0_1_win = sip.wrapinstance(self.qtgui_const_sink_x_0_1.pyqwidget(), Qt.QWidget)
self.top_layout.addWidget(self._qtgui_const_sink_x_0_1_win)
self.digital_pfb_clock_sync_xxx_0 = digital.pfb_clock_sync_ccf(tx_sps, 2.0*math.pi/100.0, (firdes.root_raised_cosine(32, 32*tx_sps, 1.0, eb, int(5*sps*32))), 32, 33*0, 1.5, sps)
self.digital_costas_loop_cc_1 = digital.costas_loop_cc(2.0*math.pi/100.0, const_order, False)
self.digital_corr_est_cc_0 = digital.corr_est_cc((modulated_preamble), tx_sps, len(modulated_preamble)-1, 0.81)
self.digital_constellation_soft_decoder_cf_0 = digital.constellation_soft_decoder_cf(constel_rx)
self.digital_constellation_modulator_0 = digital.generic_mod(
constellation=constel_rx,
differential=False,
samples_per_symbol=tx_sps,
pre_diff_code=True,
excess_bw=eb,
verbose=False,
log=False,
)
self.digital_cma_equalizer_cc_0 = digital.cma_equalizer_cc(23, 1, 1e-3, sps)
self.digital_binary_slicer_fb_0 = digital.binary_slicer_fb()
self.blocks_vector_source_x_0 = blocks.vector_source_b([0x00]*2 + [0x4b]*34 + preamble_bytes + [0x20, 0x20, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x20, 0x20] + [0x4b]*3 +[0xaa]*4, True, 1, 0*[gr.tag_utils.python_to_tag([0, pmt.intern("start"), pmt.PMT_T, pmt.intern("vector_source")])])
self.blocks_throttle_0_0 = blocks.throttle(gr.sizeof_gr_complex*1, tx_samp_rate,True)
self.blocks_multiply_const_vxx_2 = blocks.multiply_const_vcc((cmath.exp(1.0j*phase_offset), ))
self.blocks_multiply_const_vxx_1_0_0 = blocks.multiply_const_vff((1.0/20e3, ))
self.blocks_multiply_const_vxx_1_0 = blocks.multiply_const_vff((1.0/math.pi, ))
self.blocks_complex_to_mag_squared_0 = blocks.complex_to_mag_squared(1)
self.blocks_complex_to_arg_1 = blocks.complex_to_arg(1)
self.blocks_char_to_float_0 = blocks.char_to_float(1, 0.5)
self.blocks_add_const_vxx_0 = blocks.add_const_vff((-1.0, ))
self.analog_feedforward_agc_cc_0 = analog.feedforward_agc_cc(2048, 1.5)

##################################################
# Connections
##################################################
self.connect((self.analog_feedforward_agc_cc_0, 0), (self.digital_corr_est_cc_0, 0))
self.connect((self.blocks_add_const_vxx_0, 0), (self.qtgui_time_sink_x_1, 1))
self.connect((self.blocks_char_to_float_0, 0), (self.blocks_add_const_vxx_0, 0))
self.connect((self.blocks_complex_to_arg_1, 0), (self.blocks_multiply_const_vxx_1_0, 0))
self.connect((self.blocks_complex_to_mag_squared_0, 0), (self.blocks_multiply_const_vxx_1_0_0, 0))
self.connect((self.blocks_multiply_const_vxx_1_0, 0), (self.qtgui_time_sink_x_2_0_0, 0))
self.connect((self.blocks_multiply_const_vxx_1_0_0, 0), (self.qtgui_time_sink_x_2_0_0, 1))
self.connect((self.blocks_multiply_const_vxx_2, 0), (self.analog_feedforward_agc_cc_0, 0))
self.connect((self.blocks_throttle_0_0, 0), (self.blocks_multiply_const_vxx_2, 0))
self.connect((self.blocks_vector_source_x_0, 0), (self.digital_constellation_modulator_0, 0))
self.connect((self.digital_binary_slicer_fb_0, 0), (self.blocks_char_to_float_0, 0))
self.connect((self.digital_cma_equalizer_cc_0, 0), (self.digital_costas_loop_cc_1, 0))
self.connect((self.digital_constellation_modulator_0, 0), (self.blocks_throttle_0_0, 0))
self.connect((self.digital_constellation_soft_decoder_cf_0, 0), (self.digital_binary_slicer_fb_0, 0))
self.connect((self.digital_constellation_soft_decoder_cf_0, 0), (self.qtgui_time_sink_x_1, 0))
self.connect((self.digital_corr_est_cc_0, 0), (self.blocks_complex_to_arg_1, 0))
self.connect((self.digital_corr_est_cc_0, 1), (self.blocks_complex_to_mag_squared_0, 0))
self.connect((self.digital_corr_est_cc_0, 0), (self.digital_pfb_clock_sync_xxx_0, 0))
self.connect((self.digital_costas_loop_cc_1, 0), (self.digital_constellation_soft_decoder_cf_0, 0))
self.connect((self.digital_pfb_clock_sync_xxx_0, 0), (self.digital_cma_equalizer_cc_0, 0))
self.connect((self.digital_pfb_clock_sync_xxx_0, 0), (self.qtgui_const_sink_x_0_1, 0))

def closeEvent(self, event):
self.settings = Qt.QSettings("GNU Radio", "Rx_syncd")
self.settings.setValue("geometry", self.saveGeometry())
event.accept()

def get_tx_sps(self):
return self.tx_sps

def set_tx_sps(self, tx_sps):
self.tx_sps = tx_sps
self.set_tx_samp_rate(self.samp_rate*self.tx_sps/self.sps)
self.digital_pfb_clock_sync_xxx_0.set_taps((firdes.root_raised_cosine(32, 32*self.tx_sps, 1.0, self.eb, int(5*self.sps*32))))

def get_preamble_bytes(self):
return self.preamble_bytes

def set_preamble_bytes(self, preamble_bytes):
self.preamble_bytes = preamble_bytes
self.blocks_vector_source_x_0.set_data([0x00]*2 + [0x4b]*34 + self.preamble_bytes + [0x20, 0x20, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x20, 0x20] + [0x4b]*3 +[0xaa]*4, 0*[gr.tag_utils.python_to_tag([0, pmt.intern("start"), pmt.PMT_T, pmt.intern("vector_source")])])

def get_eb(self):
return self.eb

def set_eb(self, eb):
self.eb = eb
self.set_taps(firdes.root_raised_cosine(32, 32,1.0, self.eb, 11*self.sps*32))
self.digital_pfb_clock_sync_xxx_0.set_taps((firdes.root_raised_cosine(32, 32*self.tx_sps, 1.0, self.eb, int(5*self.sps*32))))

def get_constel_rx(self):
return self.constel_rx

def set_constel_rx(self, constel_rx):
self.constel_rx = constel_rx

def get_sps(self):
return self.sps

def set_sps(self, sps):
self.sps = sps
self.set_taps(firdes.root_raised_cosine(32, 32,1.0, self.eb, 11*self.sps*32))
self.set_tx_samp_rate(self.samp_rate*self.tx_sps/self.sps)
self.digital_pfb_clock_sync_xxx_0.set_taps((firdes.root_raised_cosine(32, 32*self.tx_sps, 1.0, self.eb, int(5*self.sps*32))))

def get_samp_rate(self):
return self.samp_rate

def set_samp_rate(self, samp_rate):
self.samp_rate = samp_rate
self.set_tx_samp_rate(self.samp_rate*self.tx_sps/self.sps)
self.qtgui_time_sink_x_1.set_samp_rate(self.samp_rate)

def get_modulated_preamble_untrimmed(self):
return self.modulated_preamble_untrimmed

def set_modulated_preamble_untrimmed(self, modulated_preamble_untrimmed):
self.modulated_preamble_untrimmed = modulated_preamble_untrimmed
self.set_modulated_preamble([self.modulated_preamble_untrimmed[i] for i in range(709,860)])

def get_tx_samp_rate(self):
return self.tx_samp_rate

def set_tx_samp_rate(self, tx_samp_rate):
self.tx_samp_rate = tx_samp_rate
self.blocks_throttle_0_0.set_sample_rate(self.tx_samp_rate)
self.qtgui_time_sink_x_2_0_0.set_samp_rate(self.tx_samp_rate)

def get_taps(self):
return self.taps

def set_taps(self, taps):
self.taps = taps

def get_phase_offset(self):
return self.phase_offset

def set_phase_offset(self, phase_offset):
self.phase_offset = phase_offset
self.blocks_multiply_const_vxx_2.set_k((cmath.exp(1.0j*self.phase_offset), ))

def get_modulated_preamble(self):
return self.modulated_preamble

def set_modulated_preamble(self, modulated_preamble):
self.modulated_preamble = modulated_preamble
self.digital_corr_est_cc_0.set_mark_delay(len(self.modulated_preamble)-1)

def get_const_order(self):
return self.const_order

def set_const_order(self, const_order):
self.const_order = const_order


if __name__ == '__main__':
parser = OptionParser(option_class=eng_option, usage="%prog: [options]")
(options, args) = parser.parse_args()
from distutils.version import StrictVersion
if StrictVersion(Qt.qVersion()) >= StrictVersion("4.5.0"):
Qt.QApplication.setGraphicsSystem(gr.prefs().get_string('qtgui','style','raster'))
qapp = Qt.QApplication(sys.argv)
tb = Rx_syncd()
tb.start()
tb.show()

print tb.get_modulated_preamble_untrimmed()

def quitting():
tb.stop()
tb.wait()
qapp.connect(qapp, Qt.SIGNAL("aboutToQuit()"), quitting)
qapp.exec_()
tb = None # to clean up Qt widgets
Hi Arik,

I have attached a slightly improved flowgraph and a hand edited .py
file.

The only modifications to the flowgraph from before, is that I set the
reference samples for the corr_est block to start at the _middle_ of the
first symbol of the preamble and stop at the _middle_ of the last symbol
in the preamble; I also ensured the phase_est and time_est tags stayed
marked at the center of the last symbol of the preamble. This has two
benefits:

1. The phase_est value will not be thrown off by the end of symbol
transition to a different unknown symbol after the preamble.
2. The correlation won't be sensitive to the unknown symbols before and
after the preamble.

In other words, it was a mistake to have the correlation reference
sequence start and end at the symbol transition times. :P

In the generated *.py script I added one line near the end:

print tb.get_modulated_preamble_untrimmed()

So you can so this:

$ source <path_to_target>/setup_env.sh
$ ./Rx_syncd.py | grep -v volk | sed -e 's/(//g' -e 's/)//g' > foo.txt
$ octave
octave:1> x = csvread('foo.txt');
octave:2> t = [1:length(x)];
octave:3> plot(t,abs(x))
octave:4> plot(t,arg(x)/pi)

and see why I picked the values I did to trim the modulated preamble to
the correct set of samples.

The modulator that generates the reference sequence has a pretty long
delay, and the first copy of the preamble it outputs is a little
distorted at the first symbol. That's why I snipped out the second copy
of the preamble.

Regards,
Andy


On Thu, 2016-03-31 at 22:04 -0400, Andy Walls wrote:
> Hi Arik,
>
> Try the attached flowgraph. I didn't get to put everything back in, but
> it demonstrates the principal.
>
> 1. Observe the corr_est block correlates to your preamble bytes.
> 2. Slide the phase offset around:
> a. observe the phase changing/wrapping in the time plot of the output of
> the correlator
> b. observe the phase_est tag value approximately matches the value of
> the phase offset slider.
> c. Observe the constellation rotate as you change the phase offset.
> d. Observe your decoded bits stay the same.
>
> Regards,
> Andy
>
>
> On Wed, 2016-03-30 at 16:56 -0400, Andy Walls wrote:
> > Hi Arik,
> >
> > I just took a quick look at your flowgraph.
> >
> > Some things jump out at me:
> >
> > 1. You have the modulator set at 2 samples/symbol, but the reality is,
> > most hardware is not going to transmit at 320 ksps. The X310 USRP for
> > example requires a minimum of 500 ksps for Tx, so you might as well just
> > upsample and get more samples/symbol anyway.
> >
> > 2. You have complex channel models in your flowgraph, but there's not
> > much point in using them until the basic system with a perfect channel
> > works properly. Disable them and save the headaches until you're ready.
> >
> > 3. Power Squelch followed by AGC is going to be a disaster. AGC will be
> > at *maximum* gain during most of the squelch periods. When your signal
> > shows up and breaks squelch, the AGC going to take time to come down
> > from that enormous gain. Get rid of the squelch. Squelch is for
> > humans, not modems.
> >
> > I'll look at it more later tonight.
> >
> > -Andy
> >
> > On Tue, 2016-03-29 at 23:48 +0000, Landsman, Arik wrote:
> > > Hi Andy,
> > >
> > > I like the "GMSK 9600" approach you mentioned - certainly will be looking into it. in the meanwhile I attached my flow graph. The debug folder has the input and output files.
> > >
> > > 0xFC0C3 is the sync word as can be seen in "stream_helloWorld". I use Bless to generate the binaries but any hex editor would work I imagine.
> > >
> > > changing sps from =2 to =8 changes corr (as expected), so generating corr symbols with a "matched" modulator seems like the way to go.
> > >
> > > changing threshold from 0.3 to say 0.5 forces a tag only on that first ultra high spike (30k or so). but 0.3 has the same spike, just more tags follow (which is encouraging).
> > >
> > > Tried bypassing the channel sim, power squelch, AGC, FLL directly into the corr_est block in case the blocks change the symbols in an unpredictable way - nothing seems to be off.
> > >
> > > As the ultimate check I usually loop the input file ("packet" if you will) and compare it at the Rx. "Hello World" pops here and there but not often enough to start considering BER testing for now.. costas output flips by 90* sporadically.
> > >
> > > finally, the gui plots are not in the order of the signal chain but are labeled properly. Hopefully easy to follow.
> > >
> > > And thanks again!
> > >
> > > Arik
> > >
> > >
> > > ________________________________________
> > > From: Andy Walls [andy@silverblocksystems.net]
> > > Sent: Friday, March 25, 2016 6:45 PM
> > > To: Landsman, Arik
> > > Cc: discuss-gnuradio@gnu.org
> > > Subject: Re: [Discuss-gnuradio] costas ambiguity and correlate-and-sync block in qpsk
> > >
> > > On Fri, 2016-03-25 at 22:14 +0000, Landsman, Arik wrote:
> > > > Hi Andy,
> > > >
> > > > I gave it a few more touches since we last spoke, but to no avail...
> > > > Costas still flips sporadically on a simulated channel.
> > > >
> > > > Generally the corr_est block would show a large spike in |corr|^2 on
> > > > the very first packet I send (>1600), but on all subsequent loops
> > > > (same packet) the value diminishes to ~100 with multiple peaks.
> > >
> > > That doesn't seem right.
> > >
> > >
> > > > I tried various preamble formats and sizes, including an 8 byte long
> > > > version to avoid any possible corr with payload. corr^2 certainly
> > > > changes but results in either multiple peaks at low levels (say 100
> > > > and 140), or with a huge spike at start as mentioned.
> > > >
> > > > I can generate tags by reducing the threshold to extremely low values
> > > > (say 0.1, 0.3), which I suppose makes sense with the delta between the
> > > > first and subsequent spike levels.
> > > >
> > > > In either case there are more than one tag generated per packet as the
> > > > packet loops. Only one preamble per packet, non repeating.
> > > >
> > > >
> > > > matched filter taps - can the corr_est block accept taps in any way?
> > >
> > > No.
> > >
> > > The idea with corr_est is to give it exactly the reference samples that
> > > you are looking to match against.
> > >
> > > That usually means building them with one of the gnuradio modulator
> > > blocks *and* discarding the the initial samples which are a transient
> > > emitted during the initial delay of the gnuradio particular modulator.
> > >
> > > Look at the GRC file I just posted to the list regarding "GMSK 9600
> > > baud". I had a modulator build the preamble samples. I had to add in a
> > > few flush/fill bits at the end of the preamble, which never get emitted
> > > by the modulator. I had to discard the initial transient emitted by the
> > > modulator.
> > >
> > > > I'd like to hold sps=8 at least for the time being, and without a
> > > > filter there is little correlation between the stream and the
> > > > reference preamble. sps=2 was too low for timing recovery without a
> > > > preamble. what do you generally use? (btw all of the debug above was
> > > > with sps=2).
> > >
> > > I use sps=10 with low data rate stuff.
> > >
> > > > I am told I've been loosing sleep over this.. in any case I'll keep
> > > > trying but in the meanwhile if you had any comments I would deeply
> > > > appreciate it.
> > >
> > > If you have a flowgraph that you could share that simulates the input,
> > > I'd would probably be easier for me to see what's wrong about the
> > > corr_est process.
> > >
> > > Regards,
> > > Andy
> > >
> > > > Many Thanks,
> > > > Arik
> > > >
> > > >
> > > >
> > > >
> > > >
> > > >
> > > >
> > > >
> > > >
> > > > From: Landsman, Arik
> > > > Sent: Monday, March 14, 2016 11:59 AM
> > > > To: Andy Walls; discuss-gnuradio@gnu.org
> > > > Subject: RE: [Discuss-gnuradio] costas ambiguity and correlate-and-sync block in qpsk
> > > >
> > > > Hi Andy,
> > > >
> > > > Thank you for the detailed answer, I am certainly going to try what you suggest in the next couple of days. will post with updates as progressing along.
> > > >
> > > > As a side, I tried to hack the corr_and_sync block using a preamble which in BPSK looks the same as both I and Q of the QPSK data (e.g. [1,-1] in bpsk is [1+j,-1-j] in qpsk). This does not quite work in practice, I was getting sinusoids with very weak correlation (amplitude in 30's 40's) from the corr port. Strange, since the block should ignore the imaginary portion of the stream anyways since designed for BPSK.
> > > >
> > > > in terms of a clock pattern for preamble, looks like Barker codes is the way to go, at least statistically speaking for low correlation with any random "preamble-like" bit combinations in the payload. So avoiding clock patterns for sure.
> > > >
> > > > Will look to tag the end of preamble, from your comments it appears to be the more universal approach. In my case freq lock should be fairly good though, channel is largely stationary, and I am using a FLL (although it does require longer bit combinations that are to result in a balanced spectrum to detect band edges). point well noted though.
> > > >
> > > > You mentioned M&M for timing recovery, have you tried the Polyphase block? without passing tags, I had better results with the latter for whatever reason. MM is more efficient though as I read.
> > > >
> > > > Just to clarify, were you capturing the time_est tags, or just relying on a combination of phase_est for costas and MM converging fast enough ahead of payload?
> > > >
> > > > finally, would you know of any good references for passing tags in gnuradio? I will certainly search around, but in case anything comes to mind (?)
> > > >
> > > > Best Regards,
> > > > Arik
> > > >
> > > >
> > > >
> > > > P.S. - In case it helps future causes - see "Phase-ambiguity resolution for QPSK modulation systems" by Nguyen, Tien Manh, NASA, 1989. Part1 outlines the basic two approaches for correcting phase, which are diff encoding or preamble correlation. The preamble algorithm there is very much similar to the one Andy describes, although likely implemented as an IC (aka ASSP). In this case the author suggests diff encoding for burst transmissions, since bits are wasted on a preamble (which theoretically has 2x BER compared to diff encoding).
> > > >
> > > > That said, if phase rotation occurs in mid-payload the full packet is lost, unless some form of post-processing is used to recover - so at higher SNR diff encoding is possibly better. But I am yet to find a publication that compares real data on the topic.
> > > >
> > > > http://ntrs.nasa.gov/search.jsp?N=0&Ntk=All&Ntt=Phase-ambiguity%20resolution%20for%20QPSK%20modulation%20systems&Ntx=mode%20matchallpartial
> > > >
> > > >
> > > >
> > > > ________________________________________
> > > > From: Andy Walls [andy@silverblocksystems.net]
> > > > Sent: Sunday, March 13, 2016 8:51 AM
> > > > To: discuss-gnuradio@gnu.org
> > > > Cc: Landsman, Arik
> > > > Subject: Re: [Discuss-gnuradio] costas ambiguity and correlate-and-sync block in qpsk
> > > >
> > > > On Sun, 2016-03-13 at 01:35 -0500, discuss-gnuradio-request@gnu.org
> > > > wrote:
> > > > > Message: 1
> > > > > Date: Sat, 12 Mar 2016 21:34:03 +0000
> > > > > From: "Landsman, Arik"
> > > >
> > > > >
> > > > > Hello folks,
> > > > >
> > > > > I am trying to resolve the 90* ambiguity of costas for a QPSK
> > > > > receiver, and was hoping folks could weigh-in in case anyone had
> > > > > success with this in the past.
> > > > >
> > > > > yes, diff encoding works.. :) trying to make it work without though.
> > > > >
> > > > > Also, I had seen Tom R's example of his cor-and-sync block
> > > > > implementation in BPSK (but not qpsk). maybe the block could be
> > > > > "hacked" to support qpsk, such as by passing the preamble as bpsk but,
> > > > > say, upsampling the block to make the generated complex reference
> > > > > align with the incoming qpsk stream. going to try this when I get home
> > > > > tonight.
> > > > >
> > > > > Since Trx will be bursty and will use a preamble anyways, another
> > > > > thought was to correlate the stream with the 4 possible versions of
> > > > > the preamble (i.e. constellation rotations), and pass the best
> > > > > candidate downstream to select the proper constell object for demod
> > > > > (as opposed to adjusting costas, as in cor-and-sync block), or use the
> > > > > result for post-processing the incorrectly demodulated data. But both
> > > > > seem a bit indirect or wastefull..
> > > > >
> > > > > Any thoughts?
> > > >
> > > > Using a preamble makes sense to me.
> > > >
> > > > Do not use the correlate_and_sync block; it is unreliable and provides
> > > > and errant "phase_est" value.
> > > >
> > > > Use the corr_est block; it does correctly and more generically what the
> > > > correlate_and_sync block aimed to do.
> > > >
> > > > To use the corr_est block:
> > > >
> > > > 1. Generate a vector of samples which represents your preamble. A
> > > > test_corr_est.grc file can be found here:
> > > >
> > > > https://github.com/gnuradio/gnuradio/tree/master/gr-digital/examples/demod
> > > >
> > > > which should give an example of how to use gnuradio modulator blocks to
> > > > build the preamble samples vector for you. You could also use, pyhton,
> > > > Matlab, octave, or whatever.
> > > >
> > > >
> > > > 2. Tell the corr_est block where on the preamble you want it to place
> > > > the tags. You must give the tag delay in units of samples. Common
> > > > choices are:
> > > > a. start of preamble
> > > > b. middle of first symbol after start of preamble
> > > > c. middle of last symbol before end of preamble
> > > > d. end of preamble
> > > >
> > > > 3. The corr_est block outputs the following tags:
> > > >
> > > > corr_start: that start of your preamble
> > > >
> > > > corr_est: the absolute value of the correlation squared
> > > >
> > > > phase_est: the phase offset between the reference preamble and the
> > > > received preamble at the sample corresponding to the correlation peak
> > > > (which happens at the end of the preamble).
> > > >
> > > > time_est: the fractional sample delay from the sample that is at the
> > > > correlation peak to where the actual coorelation peak (which happens in
> > > > between samples)
> > > >
> > > >
> > > > 4. If you are well synchronized in frequency, you can just apply the
> > > > phase_est correction to the whole burst with a phase rotation by
> > > > multiplying the burst by exp(1.0j*-phase_est_value), IIRC.
> > > >
> > > > If you are not well synchronized in frequency, the phase_est value is
> > > > only sensible to apply at the samples at the end of the preamble.
> > > > But that's enough to resync a tracking loop.
> > > >
> > > >
> > > > 5. Modify your downstream blocks to look for the above tags, and reset
> > > > their state as appropriate, to acquire and maintain synchronization.
> > > >
> > > > 6. BTW, avoid a preamble that has a clock pattern (1010101010..). It
> > > > causes duplicate correlation peaks (which you then have to inspect the
> > > > corr_est tag for the highest value), and the M&M clock recovery block
> > > > really drifts off such preambles badly for some reason.
> > > >
> > > > > Thank you in advance,
> > > > > Arik
> > > >
> > > >
> > > > Regards,
> > > > Andy
> > > >
> > >
> > >
> >
>

No comments:

Post a Comment