summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gr-digital/include/digital_constellation.h74
-rw-r--r--gr-digital/lib/digital_constellation.cc31
-rw-r--r--gr-digital/python/CMakeLists.txt1
-rwxr-xr-xgr-digital/python/qa_constellation.py51
-rwxr-xr-xgr-digital/python/qa_constellation_receiver.py50
-rw-r--r--gr-digital/python/qam.py125
-rw-r--r--gr-digital/python/qamlike.py75
-rw-r--r--gr-digital/swig/digital_constellation.i28
8 files changed, 398 insertions, 37 deletions
diff --git a/gr-digital/include/digital_constellation.h b/gr-digital/include/digital_constellation.h
index 76cd30b25f..b9c27bbc44 100644
--- a/gr-digital/include/digital_constellation.h
+++ b/gr-digital/include/digital_constellation.h
@@ -276,6 +276,80 @@ class DIGITAL_API digital_constellation_rect : public digital_constellation_sect
};
+/************************************************************/
+/* digital_constellation_expl_rect */
+/************************************************************/
+
+/*!
+ * \brief Rectangular digital constellation
+ * \ingroup digital
+ *
+ * Only implemented for 1-(complex)dimensional constellation.
+ *
+ * Constellation space is divided into rectangular sectors. Each
+ * sector is associated with the nearest constellation point.
+ *
+ * This class is different from constellation_rect in that the mapping
+ * from sector to constellation point is explicitly passed into the
+ * constructor as sector_values. Usually we do not need this, since
+ * we want each sector to be automatically mapped to the closest
+ * constellation point, however sometimes it's nice to have the
+ * flexibility.
+ */
+
+class digital_constellation_expl_rect;
+typedef boost::shared_ptr<digital_constellation_expl_rect> digital_constellation_expl_rect_sptr;
+
+// public constructor
+DIGITAL_API digital_constellation_expl_rect_sptr
+digital_make_constellation_expl_rect (
+ std::vector<gr_complex> constellation,
+ std::vector<unsigned int> pre_diff_code,
+ unsigned int rotational_symmetry,
+ unsigned int real_sectors,
+ unsigned int imag_sectors,
+ float width_real_sectors,
+ float width_imag_sectors,
+ std::vector<unsigned int> sector_values
+);
+
+class DIGITAL_API digital_constellation_expl_rect : public digital_constellation_rect
+{
+ public:
+
+ digital_constellation_expl_rect (
+ std::vector<gr_complex> constellation,
+ std::vector<unsigned int> pre_diff_code,
+ unsigned int rotational_symmetry,
+ unsigned int real_sectors,
+ unsigned int imag_sectors,
+ float width_real_sectors,
+ float width_imag_sectors,
+ std::vector<unsigned int> sector_values
+ );
+
+ protected:
+ unsigned int calc_sector_value (unsigned int sector) {
+ return d_sector_values[sector];
+ }
+
+ private:
+ std::vector<unsigned int> d_sector_values;
+
+ friend DIGITAL_API digital_constellation_expl_rect_sptr
+ digital_make_constellation_expl_rect (
+ std::vector<gr_complex> constellation,
+ std::vector<unsigned int> pre_diff_code,
+ unsigned int rotational_symmetry,
+ unsigned int real_sectors,
+ unsigned int imag_sectors,
+ float width_real_sectors,
+ float width_imag_sectors,
+ std::vector<unsigned int> sector_values
+ );
+
+};
+
/************************************************************/
/* digital_constellation_psk */
diff --git a/gr-digital/lib/digital_constellation.cc b/gr-digital/lib/digital_constellation.cc
index da79f2caa4..0d4b88b047 100644
--- a/gr-digital/lib/digital_constellation.cc
+++ b/gr-digital/lib/digital_constellation.cc
@@ -345,6 +345,37 @@ digital_constellation_rect::calc_sector_value (unsigned int sector)
return closest_point;
}
+digital_constellation_expl_rect_sptr
+digital_make_constellation_expl_rect(
+ std::vector<gr_complex> constellation,
+ std::vector<unsigned int> pre_diff_code,
+ unsigned int rotational_symmetry,
+ unsigned int real_sectors,
+ unsigned int imag_sectors,
+ float width_real_sectors,
+ float width_imag_sectors,
+ std::vector<unsigned int> sector_values
+)
+{
+ return digital_constellation_expl_rect_sptr(
+ new digital_constellation_expl_rect(
+ constellation, pre_diff_code, rotational_symmetry, real_sectors, imag_sectors,
+ width_real_sectors, width_imag_sectors, sector_values));
+}
+
+digital_constellation_expl_rect::digital_constellation_expl_rect (
+ std::vector<gr_complex> constellation,
+ std::vector<unsigned int> pre_diff_code,
+ unsigned int rotational_symmetry,
+ unsigned int real_sectors,
+ unsigned int imag_sectors,
+ float width_real_sectors,
+ float width_imag_sectors,
+ std::vector<unsigned int> sector_values
+ ) : digital_constellation_rect(
+ constellation, pre_diff_code, rotational_symmetry, real_sectors, imag_sectors,
+ width_real_sectors, width_imag_sectors),
+ d_sector_values(sector_values) {};
digital_constellation_psk_sptr
digital_make_constellation_psk(std::vector<gr_complex> constellation,
diff --git a/gr-digital/python/CMakeLists.txt b/gr-digital/python/CMakeLists.txt
index 8f2af0664d..9be5e1ae75 100644
--- a/gr-digital/python/CMakeLists.txt
+++ b/gr-digital/python/CMakeLists.txt
@@ -43,6 +43,7 @@ GR_PYTHON_INSTALL(
pkt.py
psk.py
qam.py
+ qamlike.py
qpsk.py
DESTINATION ${GR_PYTHON_DIR}/gnuradio/digital
COMPONENT "digital_python"
diff --git a/gr-digital/python/qa_constellation.py b/gr-digital/python/qa_constellation.py
index 6962ec6338..ddd8c71e64 100755
--- a/gr-digital/python/qa_constellation.py
+++ b/gr-digital/python/qa_constellation.py
@@ -30,6 +30,7 @@ import digital_swig
# import from local folder
import psk
import qam
+import qamlike
tested_mod_codes = (mod_codes.NO_CODE, mod_codes.GRAY_CODE)
@@ -64,14 +65,19 @@ def threed_constell():
dim = 3
return digital_swig.constellation_calcdist(points, [], rot_sym, dim)
-tested_constellation_info = (
+# A list of tuples for constellation testing. The contents of the
+# tuples are (constructor, poss_args, differential, diff_argname).
+
+# These constellations should lock on well.
+easy_constellation_info = (
(psk.psk_constellation,
- {'m': (2, 4, 8, 16, 32, 64),
+ {'m': (2, 4, 8, 16, ),
'mod_code': tested_mod_codes, },
True, None),
(qam.qam_constellation,
- {'constellation_points': (4, 16, 64),
- 'mod_code': tested_mod_codes, },
+ {'constellation_points': (4,),
+ 'mod_code': tested_mod_codes,
+ 'large_ampls_to_corners': [False],},
True, None),
(digital_swig.constellation_bpsk, {}, True, None),
(digital_swig.constellation_qpsk, {}, False, None),
@@ -81,11 +87,44 @@ tested_constellation_info = (
(threed_constell, {}, True, None),
)
-def tested_constellations():
+# These constellations don't work nicely.
+# We have a lower required error rate.
+medium_constellation_info = (
+ (psk.psk_constellation,
+ {'m': (32, 64),
+ 'mod_code': tested_mod_codes, },
+ True, None),
+ (qam.qam_constellation,
+ {'constellation_points': (16 ,),
+ 'mod_code': tested_mod_codes,
+ 'large_ampls_to_corners': [False, True],},
+ True, None),
+ (qamlike.qam32_holeinside_constellation,
+ {'large_ampls_to_corners': [True]},
+ True, None),
+)
+
+# These constellation are basically broken in our test
+difficult_constellation_info = (
+ (qam.qam_constellation,
+ {'constellation_points': (64,),
+ 'mod_code': tested_mod_codes,
+ 'large_ampls_to_corners': [False, True],},
+ True, None),
+)
+
+def tested_constellations(easy=True, medium=True, difficult=True):
"""
Generator to produce (constellation, differential) tuples for testing purposes.
"""
- for constructor, poss_args, differential, diff_argname in tested_constellation_info:
+ constellation_info = []
+ if easy:
+ constellation_info += easy_constellation_info
+ if medium:
+ constellation_info += medium_constellation_info
+ if difficult:
+ constellation_info += difficult_constellation_info
+ for constructor, poss_args, differential, diff_argname in constellation_info:
if differential:
diff_poss = (True, False)
else:
diff --git a/gr-digital/python/qa_constellation_receiver.py b/gr-digital/python/qa_constellation_receiver.py
index 2d25433b92..37e56b4cf7 100755
--- a/gr-digital/python/qa_constellation_receiver.py
+++ b/gr-digital/python/qa_constellation_receiver.py
@@ -39,7 +39,9 @@ SEED = 1239
# We need this many to let the frequency recovery block converge.
DATA_LENGTH = 2000
# Test fails if fraction of output that is correct is less than this.
-REQ_CORRECT = 0.7
+EASY_REQ_CORRECT = 0.9
+# For constellations that aren't expected to work so well.
+MEDIUM_REQ_CORRECT = 0.8
# CHANNEL PARAMETERS
NOISE_VOLTAGE = 0.01
@@ -78,25 +80,33 @@ class test_constellation_receiver (gr_unittest.TestCase):
self.indices = alignment.random_sample(
self.max_data_length, self.max_num_samples, SEED)
- for constellation, differential in tested_constellations():
- # The constellation_receiver doesn't work for constellations
- # of multple dimensions (i.e. multiple complex numbers to a
- # single symbol).
- # That is not implemented since the receiver has no way of
- # knowing where the beginning of a symbol is.
- # It also doesn't work for non-differential modulation.
- if constellation.dimensionality() != 1 or not differential:
- continue
- data_length = DATA_LENGTH * constellation.bits_per_symbol()
- tb = rec_test_tb(constellation, differential,
- src_data=self.src_data[:data_length])
- tb.run()
- data = tb.dst.data()
- d1 = tb.src_data[:int(len(tb.src_data)*self.ignore_fraction)]
- d2 = data[:int(len(data)*self.ignore_fraction)]
- correct, overlap, offset, indices = alignment.align_sequences(
- d1, d2, indices=self.indices)
- self.assertTrue(correct > REQ_CORRECT)
+ requirements = (
+ (EASY_REQ_CORRECT, tested_constellations(easy=True, medium=False, difficult=False)),
+ (MEDIUM_REQ_CORRECT, tested_constellations(easy=False, medium=True, difficult=False)),
+ )
+ for req_correct, tcs in requirements:
+ for constellation, differential in tcs:
+ # The constellation_receiver doesn't work for constellations
+ # of multple dimensions (i.e. multiple complex numbers to a
+ # single symbol).
+ # That is not implemented since the receiver has no way of
+ # knowing where the beginning of a symbol is.
+ # It also doesn't work for non-differential modulation.
+ if constellation.dimensionality() != 1 or not differential:
+ continue
+ data_length = DATA_LENGTH * constellation.bits_per_symbol()
+ tb = rec_test_tb(constellation, differential,
+ src_data=self.src_data[:data_length])
+ tb.run()
+ data = tb.dst.data()
+ d1 = tb.src_data[:int(len(tb.src_data)*self.ignore_fraction)]
+ d2 = data[:int(len(data)*self.ignore_fraction)]
+ correct, overlap, offset, indices = alignment.align_sequences(
+ d1, d2, indices=self.indices)
+ if correct <= req_correct:
+ print("Constellation is {0}. Differential is {1}. Required correct is {2}. Correct is {3}. FAIL.".
+ format(constellation, differential, req_correct, correct))
+ self.assertTrue(correct > req_correct)
class rec_test_tb (gr.top_block):
diff --git a/gr-digital/python/qam.py b/gr-digital/python/qam.py
index 5b1f7683b8..6834e1945a 100644
--- a/gr-digital/python/qam.py
+++ b/gr-digital/python/qam.py
@@ -146,9 +146,17 @@ def make_non_differential_constellation(m, gray_coded):
def qam_constellation(constellation_points=_def_constellation_points,
differential=_def_differential,
- mod_code=_def_mod_code):
+ mod_code=_def_mod_code,
+ large_ampls_to_corners=False):
"""
Creates a QAM constellation object.
+
+ If large_ampls_to_corners=True then sectors that are probably
+ occupied due to a phase offset, are not mapped to the closest
+ constellation point. Rather we take into account the fact that a
+ phase offset is probably the problem and map them to the closest
+ corner point. It's a bit hackish but it seems to improve
+ frequency locking.
"""
if mod_code == mod_codes.GRAY_CODE:
gray_coded = True
@@ -165,10 +173,85 @@ def qam_constellation(constellation_points=_def_constellation_points,
# No pre-diff code
# Should add one so that we can gray-code the quadrant bits too.
pre_diff_code = []
- constellation = digital_swig.constellation_rect(points, pre_diff_code, 4,
- side, side, width, width)
+ if not large_ampls_to_corners:
+ constellation = digital_swig.constellation_rect(points, pre_diff_code, 4,
+ side, side, width, width)
+ else:
+ sector_values = large_ampls_to_corners_mapping(side, points, width)
+ constellation = digital_swig.constellation_expl_rect(
+ points, pre_diff_code, 4, side, side, width, width, sector_values)
return constellation
+def find_closest_point(p, qs):
+ """
+ Return in index of the closest point in 'qs' to 'p'.
+ """
+ min_dist = None
+ min_i = None
+ for i, q in enumerate(qs):
+ dist = abs(q-p)
+ if min_dist is None or dist < min_dist:
+ min_dist = dist
+ min_i = i
+ return min_i
+
+def large_ampls_to_corners_mapping(side, points, width):
+ """
+ We have a grid that we use for decision making. One additional row/column
+ is placed on each side of the grid. Points in these additional rows/columns
+ are mapped to the corners rather than the closest constellation points.
+
+ Args:
+ side: The number of rows/columns in the grid that we use to do
+ decision making.
+ points: The list of constellation points.
+ width: The width of the rows/columns.
+
+ Returns:
+ sector_values maps the sector index to the constellation
+ point index.
+ """
+ # First find the indices of the corner points.
+ # Assume the corner points are the 4 points with the largest magnitudes.
+ corner_indices = []
+ corner_points = []
+ max_mag = 0
+ for i, p in enumerate(points):
+ if abs(p) > max_mag:
+ corner_indices = [i]
+ corner_points = [p]
+ max_mag = abs(p)
+ elif abs(p) == max_mag:
+ corner_indices.append(i)
+ corner_points.append(p)
+ if len(corner_indices) != 4:
+ raise ValueError("Found {0} corner indices. Expected 4."
+ .format(len(corner_indices)))
+ # We want an additional layer around the constellation
+ # Value in this extra layer will be mapped to the closest corner rather
+ # than the closest constellation point.
+ extra_layers = 1
+ side = side + extra_layers*2
+ # Calculate sector values
+ sector_values = []
+ for real_x in range(side):
+ for imag_x in range(side):
+ sector = real_x * side + imag_x
+ # If this sector is a normal constellation sector then
+ # use the center point.
+ c = ((real_x-side/2.0+0.5)*width +
+ (imag_x-side/2.0+0.5)*width*1j)
+ if (real_x >= extra_layers and real_x < side-extra_layers
+ and imag_x >= extra_layers and imag_x < side-extra_layers):
+ # This is not an edge row/column. Find closest point.
+ index = find_closest_point(c, points)
+ else:
+ # This is an edge. Find closest corner point.
+ index = corner_indices[find_closest_point(c, corner_points)]
+ sector_values.append(index)
+ return sector_values
+
+
# /////////////////////////////////////////////////////////////////////////////
# QAM modulator
# /////////////////////////////////////////////////////////////////////////////
@@ -186,12 +269,19 @@ class qam_mod(generic_mod):
The input is a byte stream (unsigned char) and the
output is the complex modulated signal at baseband.
- See generic_mod block for list of parameters.
+ Args:
+ constellation_points: Number of constellation points.
+ Must be a power of 4.
+ mod_code: Specifies an encoding to use (typically used to indicated
+ if we want gray coding, see digital.utils.mod_codes)
+
+ See generic_mod block for list of additional parameters.
"""
- constellation = qam_constellation(constellation_points, differential, mod_code)
- # We take care of the gray coding in the constellation generation so it doesn't
- # need to be done in the block.
+ constellation = qam_constellation(constellation_points, differential,
+ mod_code)
+ # We take care of the gray coding in the constellation
+ # generation so it doesn't need to be done in the block.
super(qam_mod, self).__init__(constellation, differential=differential,
*args, **kwargs)
@@ -205,6 +295,7 @@ class qam_demod(generic_demod):
def __init__(self, constellation_points=_def_constellation_points,
differential=_def_differential,
mod_code=_def_mod_code,
+ large_ampls_to_corner = False,
*args, **kwargs):
"""
@@ -213,11 +304,23 @@ class qam_demod(generic_demod):
The input is a byte stream (unsigned char) and the
output is the complex modulated signal at baseband.
- See generic_demod block for list of parameters.
+ Args:
+ constellation_points: Number of constellation points.
+ Must be a power of 4.
+ mod_code: Specifies an encoding to use (typically used to indicated
+ if we want gray coding, see digital.utils.mod_codes)
+ large_ampls_to_corners: If this is set to True then when the
+ constellation is making decisions, points that are far outside
+ the constellation are mapped to the closest corner rather than
+ the closet constellation point. This can help with phase
+ locking.
+
+ See generic_demod block for list of additional parameters.
"""
- constellation = qam_constellation(constellation_points, differential, mod_code)
- # We take care of the gray coding in the constellation generation so it doesn't
- # need to be done in the block.
+ constellation = qam_constellation(constellation_points, differential,
+ mod_code)
+ # We take care of the gray coding in the constellation
+ # generation so it doesn't need to be done in the block.
super(qam_demod, self).__init__(constellation, differential=differential,
*args, **kwargs)
diff --git a/gr-digital/python/qamlike.py b/gr-digital/python/qamlike.py
new file mode 100644
index 0000000000..2f8c855339
--- /dev/null
+++ b/gr-digital/python/qamlike.py
@@ -0,0 +1,75 @@
+# Copyright 2013 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.
+#
+
+"""
+This file contains constellations that are similar to QAM, but are not perfect squares.
+"""
+
+import digital_swig
+from qam import large_ampls_to_corners_mapping
+
+def qam32_holeinside_constellation(large_ampls_to_corners=False):
+ # First make constellation for one quadrant.
+ # 0 1 2
+ # 2 - 010 111 110
+ # 1 - 011 101 100
+ # 0 - 000 001
+
+ # Have put hole in the side rather than corner.
+ # Corner point is helpful for frequency locking.
+
+ # It has an attempt at some gray-coding, but not
+ # a very good one.
+
+ # Indices are (horizontal, vertical).
+ indices_and_numbers = (
+ ((0, 0), 0b000),
+ ((0, 1), 0b011),
+ ((0, 2), 0b010),
+ ((1, 0), 0b001),
+ ((1, 1), 0b101),
+ ((1, 2), 0b111),
+ ((2, 1), 0b100),
+ ((2, 2), 0b110),
+ )
+ points = [None]*32
+ for indices, number in indices_and_numbers:
+ p_in_quadrant = 0.5+indices[0] + 1j*(0.5+indices[1])
+ for quadrant in range(4):
+ index = number + 8 * quadrant
+ rotation = pow(1j, quadrant)
+ p = p_in_quadrant * rotation
+ points[index] = p
+ side = 6
+ width = 1
+ # Double number of boxes on side
+ # This is so that points in the 'hole' get assigned correctly.
+ side = 12
+ width = 0.5
+ pre_diff_code = []
+ if not large_ampls_to_corners:
+ constellation = digital_swig.constellation_rect(points, pre_diff_code, 4,
+ side, side, width, width)
+ else:
+ sector_values = large_ampls_to_corners_mapping(side, points, width)
+ constellation = digital_swig.constellation_expl_rect(
+ points, pre_diff_code, 4, side, side, width, width, sector_values)
+ return constellation
+
diff --git a/gr-digital/swig/digital_constellation.i b/gr-digital/swig/digital_constellation.i
index 248f900149..4c4f8aa01d 100644
--- a/gr-digital/swig/digital_constellation.i
+++ b/gr-digital/swig/digital_constellation.i
@@ -102,6 +102,33 @@ public:
float width_real_sectors, float width_imag_sectors);
};
+class digital_constellation_expl_rect;
+typedef boost::shared_ptr<digital_constellation_expl_rect> digital_constellation_expl_rect_sptr;
+%template(digital_constellation_expl_rect_sptr) boost::shared_ptr<digital_constellation_expl_rect>;
+%rename(constellation_expl_rect) digital_make_constellation_expl_rect;
+digital_constellation_expl_rect_sptr digital_make_constellation_expl_rect(
+ std::vector<gr_complex> constellation,
+ std::vector<unsigned int> pre_diff_code,
+ unsigned int rotational_symmetry,
+ unsigned int real_sectors, unsigned int imag_sectors,
+ float width_real_sectors, float width_imag_sectors,
+ std::vector<unsigned int> sector_values
+);
+%ignore digital_constellation_expl_rect;
+
+class digital_constellation_expl_rect : public digital_constellation_rect
+{
+public:
+ digital_constellation_expl_rect (
+ std::vector<gr_complex> constellation,
+ std::vector<unsigned int> pre_diff_code,
+ unsigned int rotational_symmetry,
+ unsigned int real_sectors, unsigned int imag_sectors,
+ float width_real_sectors, float width_imag_sectors,
+ std::vector<unsigned int> sector_values
+ );
+};
+
class digital_constellation_psk;
typedef boost::shared_ptr<digital_constellation_psk> digital_constellation_psk_sptr;
%template(digital_constellation_psk_sptr) boost::shared_ptr<digital_constellation_psk>;
@@ -198,6 +225,7 @@ public:
digital_constellation_calcdist_sptr.__repr__ = lambda self: '<constellation calcdist (m=%s)>' % str(len(self.points()))
digital_constellation_rect_sptr.__repr__ = lambda self: '<constellation rect (m=%s)>' % str(len(self.points()))
+digital_constellation_expl_rect_sptr.__repr__ = lambda self: '<constellation expl_rect (m=%s)>' % str(len(self.points()))
digital_constellation_psk_sptr.__repr__ = lambda self: '<constellation psk (m=%s)>' % str(len(self.points()))
digital_constellation_bpsk_sptr.__repr__ = lambda self: '<constellation bpsk>'
digital_constellation_qpsk_sptr.__repr__ = lambda self: '<constellation qpsk>'