summaryrefslogtreecommitdiff
path: root/gr-digital
diff options
context:
space:
mode:
Diffstat (limited to 'gr-digital')
-rw-r--r--gr-digital/lib/Makefile.am6
-rw-r--r--gr-digital/lib/digital_mpsk_receiver_cc.cc322
-rw-r--r--gr-digital/lib/digital_mpsk_receiver_cc.h316
-rw-r--r--gr-digital/python/Makefile.am3
-rw-r--r--gr-digital/python/qa_mpsk_receiver.py123
-rw-r--r--gr-digital/swig/Makefile.am3
-rw-r--r--gr-digital/swig/digital_mpsk_receiver_cc.i60
-rw-r--r--gr-digital/swig/digital_swig.i2
8 files changed, 831 insertions, 4 deletions
diff --git a/gr-digital/lib/Makefile.am b/gr-digital/lib/Makefile.am
index e3cbbe7ea..86b98726b 100644
--- a/gr-digital/lib/Makefile.am
+++ b/gr-digital/lib/Makefile.am
@@ -37,7 +37,8 @@ grinclude_HEADERS = \
digital_crc32.h \
digital_lms_dd_equalizer_cc.h \
digital_kurtotic_equalizer_cc.h \
- digital_metric_type.h
+ digital_metric_type.h \
+ digital_mpsk_receiver_cc.h
lib_LTLIBRARIES = libgnuradio-digital.la
@@ -53,7 +54,8 @@ libgnuradio_digital_la_SOURCES = \
digital_cma_equalizer_cc.cc \
digital_crc32.cc \
digital_lms_dd_equalizer_cc.cc \
- digital_kurtotic_equalizer_cc.cc
+ digital_kurtotic_equalizer_cc.cc \
+ digital_mpsk_receiver_cc.cc
libgnuradio_digital_la_LIBADD = \
$(GNURADIO_CORE_LA)
diff --git a/gr-digital/lib/digital_mpsk_receiver_cc.cc b/gr-digital/lib/digital_mpsk_receiver_cc.cc
new file mode 100644
index 000000000..3b2ea9840
--- /dev/null
+++ b/gr-digital/lib/digital_mpsk_receiver_cc.cc
@@ -0,0 +1,322 @@
+/* -*- c++ -*- */
+/*
+ * Copyright 2005,2006,2007,2010,2011 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gr_io_signature.h>
+#include <gr_prefs.h>
+#include <digital_mpsk_receiver_cc.h>
+#include <stdexcept>
+#include <gr_math.h>
+#include <gr_expj.h>
+#include <gri_mmse_fir_interpolator_cc.h>
+
+
+#define M_TWOPI (2*M_PI)
+#define VERBOSE_MM 0 // Used for debugging symbol timing loop
+#define VERBOSE_COSTAS 0 // Used for debugging phase and frequency tracking
+
+// Public constructor
+
+digital_mpsk_receiver_cc_sptr
+digital_make_mpsk_receiver_cc(unsigned int M, float theta,
+ float alpha, float beta,
+ float fmin, float fmax,
+ float mu, float gain_mu,
+ float omega, float gain_omega, float omega_rel)
+{
+ return gnuradio::get_initial_sptr(new digital_mpsk_receiver_cc (M, theta,
+ alpha, beta,
+ fmin, fmax,
+ mu, gain_mu,
+ omega, gain_omega, omega_rel));
+}
+
+digital_mpsk_receiver_cc::digital_mpsk_receiver_cc (unsigned int M, float theta,
+ float alpha, float beta,
+ float fmin, float fmax,
+ float mu, float gain_mu,
+ float omega, float gain_omega, float omega_rel)
+ : gr_block ("mpsk_receiver_cc",
+ gr_make_io_signature (1, 1, sizeof (gr_complex)),
+ gr_make_io_signature (1, 1, sizeof (gr_complex))),
+ d_M(M), d_theta(theta),
+ d_alpha(alpha), d_beta(beta), d_freq(0), d_max_freq(fmax), d_min_freq(fmin), d_phase(0),
+ d_current_const_point(0),
+ d_mu(mu), d_gain_mu(gain_mu), d_gain_omega(gain_omega),
+ d_omega_rel(omega_rel), d_max_omega(0), d_min_omega(0),
+ d_p_2T(0), d_p_1T(0), d_p_0T(0), d_c_2T(0), d_c_1T(0), d_c_0T(0)
+{
+ d_interp = new gri_mmse_fir_interpolator_cc();
+ d_dl_idx = 0;
+
+ set_omega(omega);
+
+ if (omega <= 0.0)
+ throw std::out_of_range ("clock rate must be > 0");
+ if (gain_mu < 0 || gain_omega < 0)
+ throw std::out_of_range ("Gains must be non-negative");
+
+ assert(d_interp->ntaps() <= DLLEN);
+
+ // zero double length delay line.
+ for (unsigned int i = 0; i < 2 * DLLEN; i++)
+ d_dl[i] = gr_complex(0.0,0.0);
+
+ // build the constellation vector from M
+ make_constellation();
+
+ // Select a phase detector and a decision maker for the modulation order
+ switch(d_M) {
+ case 2: // optimized algorithms for BPSK
+ d_phase_error_detector = &digital_mpsk_receiver_cc::phase_error_detector_bpsk; //bpsk;
+ d_decision = &digital_mpsk_receiver_cc::decision_bpsk;
+ break;
+
+ case 4: // optimized algorithms for QPSK
+ d_phase_error_detector = &digital_mpsk_receiver_cc::phase_error_detector_qpsk; //qpsk;
+ d_decision = &digital_mpsk_receiver_cc::decision_qpsk;
+ break;
+
+ default: // generic algorithms for any M (power of 2?) but not pretty
+ d_phase_error_detector = &digital_mpsk_receiver_cc::phase_error_detector_generic;
+ d_decision = &digital_mpsk_receiver_cc::decision_generic;
+ break;
+ }
+}
+
+digital_mpsk_receiver_cc::~digital_mpsk_receiver_cc ()
+{
+ delete d_interp;
+}
+
+void
+digital_mpsk_receiver_cc::forecast(int noutput_items, gr_vector_int &ninput_items_required)
+{
+ unsigned ninputs = ninput_items_required.size();
+ for (unsigned i=0; i < ninputs; i++)
+ ninput_items_required[i] = (int) ceil((noutput_items * d_omega) + d_interp->ntaps());
+}
+
+// FIXME add these back in an test difference in performance
+float
+digital_mpsk_receiver_cc::phase_error_detector_qpsk(gr_complex sample) const
+{
+ float phase_error = 0;
+ if(fabsf(sample.real()) > fabsf(sample.imag())) {
+ if(sample.real() > 0)
+ phase_error = -sample.imag();
+ else
+ phase_error = sample.imag();
+ }
+ else {
+ if(sample.imag() > 0)
+ phase_error = sample.real();
+ else
+ phase_error = -sample.real();
+ }
+
+ return phase_error;
+}
+
+float
+digital_mpsk_receiver_cc::phase_error_detector_bpsk(gr_complex sample) const
+{
+ return -(sample.real()*sample.imag());
+}
+
+float digital_mpsk_receiver_cc::phase_error_detector_generic(gr_complex sample) const
+{
+ //return gr_fast_atan2f(sample*conj(d_constellation[d_current_const_point]));
+ return -arg(sample*conj(d_constellation[d_current_const_point]));
+}
+
+unsigned int
+digital_mpsk_receiver_cc::decision_bpsk(gr_complex sample) const
+{
+ return (gr_branchless_binary_slicer(sample.real()) ^ 1);
+ //return gr_binary_slicer(sample.real()) ^ 1;
+}
+
+unsigned int
+digital_mpsk_receiver_cc::decision_qpsk(gr_complex sample) const
+{
+ unsigned int index;
+
+ //index = gr_branchless_quad_0deg_slicer(sample);
+ index = gr_quad_0deg_slicer(sample);
+ return index;
+}
+
+unsigned int
+digital_mpsk_receiver_cc::decision_generic(gr_complex sample) const
+{
+ unsigned int min_m = 0;
+ float min_s = 65535;
+
+ // Develop all possible constellation points and find the one that minimizes
+ // the Euclidean distance (error) with the sample
+ for(unsigned int m=0; m < d_M; m++) {
+ gr_complex diff = norm(d_constellation[m] - sample);
+
+ if(fabs(diff.real()) < min_s) {
+ min_s = fabs(diff.real());
+ min_m = m;
+ }
+ }
+ // Return the index of the constellation point that minimizes the error
+ return min_m;
+}
+
+
+void
+digital_mpsk_receiver_cc::make_constellation()
+{
+ for(unsigned int m=0; m < d_M; m++) {
+ d_constellation.push_back(gr_expj((M_TWOPI/d_M)*m));
+ }
+}
+
+void
+digital_mpsk_receiver_cc::mm_sampler(const gr_complex symbol)
+{
+ gr_complex sample, nco;
+
+ d_mu--; // skip a number of symbols between sampling
+ d_phase += d_freq; // increment the phase based on the frequency of the rotation
+
+ // Keep phase clamped and not walk to infinity
+ while(d_phase > M_TWOPI)
+ d_phase -= M_TWOPI;
+ while(d_phase < -M_TWOPI)
+ d_phase += M_TWOPI;
+
+ nco = gr_expj(d_phase+d_theta); // get the NCO value for derotating the current sample
+ sample = nco*symbol; // get the downconverted symbol
+
+ // Fill up the delay line for the interpolator
+ d_dl[d_dl_idx] = sample;
+ d_dl[(d_dl_idx + DLLEN)] = sample; // put this in the second half of the buffer for overflows
+ d_dl_idx = (d_dl_idx+1) % DLLEN; // Keep the delay line index in bounds
+}
+
+void
+digital_mpsk_receiver_cc::mm_error_tracking(gr_complex sample)
+{
+ gr_complex u, x, y;
+ float mm_error = 0;
+
+ // Make sample timing corrections
+
+ // set the delayed samples
+ d_p_2T = d_p_1T;
+ d_p_1T = d_p_0T;
+ d_p_0T = sample;
+ d_c_2T = d_c_1T;
+ d_c_1T = d_c_0T;
+
+ d_current_const_point = (*this.*d_decision)(d_p_0T); // make a decision on the sample value
+ d_c_0T = d_constellation[d_current_const_point];
+
+ x = (d_c_0T - d_c_2T) * conj(d_p_1T);
+ y = (d_p_0T - d_p_2T) * conj(d_c_1T);
+ u = y - x;
+ mm_error = u.real(); // the error signal is in the real part
+ mm_error = gr_branchless_clip(mm_error, 1.0); // limit mm_val
+
+ d_omega = d_omega + d_gain_omega * mm_error; // update omega based on loop error
+ d_omega = d_omega_mid + gr_branchless_clip(d_omega-d_omega_mid, d_omega_rel); // make sure we don't walk away
+
+ d_mu += d_omega + d_gain_mu * mm_error; // update mu based on loop error
+
+#if VERBOSE_MM
+ printf("mm: mu: %f omega: %f mm_error: %f sample: %f+j%f constellation: %f+j%f\n",
+ d_mu, d_omega, mm_error, sample.real(), sample.imag(),
+ d_constellation[d_current_const_point].real(), d_constellation[d_current_const_point].imag());
+#endif
+}
+
+
+void
+digital_mpsk_receiver_cc::phase_error_tracking(gr_complex sample)
+{
+ float phase_error = 0;
+
+ // Make phase and frequency corrections based on sampled value
+ phase_error = (*this.*d_phase_error_detector)(sample);
+
+ d_freq += d_beta*phase_error; // adjust frequency based on error
+ d_phase += d_freq + d_alpha*phase_error; // adjust phase based on error
+
+ // Make sure we stay within +-2pi
+ while(d_phase > M_TWOPI)
+ d_phase -= M_TWOPI;
+ while(d_phase < -M_TWOPI)
+ d_phase += M_TWOPI;
+
+ // Limit the frequency range
+ d_freq = gr_branchless_clip(d_freq, d_max_freq);
+
+#if VERBOSE_COSTAS
+ printf("cl: phase_error: %f phase: %f freq: %f sample: %f+j%f constellation: %f+j%f\n",
+ phase_error, d_phase, d_freq, sample.real(), sample.imag(),
+ d_constellation[d_current_const_point].real(), d_constellation[d_current_const_point].imag());
+#endif
+}
+
+int
+digital_mpsk_receiver_cc::general_work (int noutput_items,
+ gr_vector_int &ninput_items,
+ gr_vector_const_void_star &input_items,
+ gr_vector_void_star &output_items)
+{
+ const gr_complex *in = (const gr_complex *) input_items[0];
+ gr_complex *out = (gr_complex *) output_items[0];
+
+ int i=0, o=0;
+
+ while((o < noutput_items) && (i < ninput_items[0])) {
+ while((d_mu > 1) && (i < ninput_items[0])) {
+ mm_sampler(in[i]); // puts symbols into a buffer and adjusts d_mu
+ i++;
+ }
+
+ if(i < ninput_items[0]) {
+ gr_complex interp_sample = d_interp->interpolate(&d_dl[d_dl_idx], d_mu);
+
+ mm_error_tracking(interp_sample); // corrects M&M sample time
+ phase_error_tracking(interp_sample); // corrects phase and frequency offsets
+
+ out[o++] = interp_sample;
+ }
+ }
+
+ #if 0
+ printf("ninput_items: %d noutput_items: %d consuming: %d returning: %d\n",
+ ninput_items[0], noutput_items, i, o);
+ #endif
+
+ consume_each(i);
+ return o;
+}
diff --git a/gr-digital/lib/digital_mpsk_receiver_cc.h b/gr-digital/lib/digital_mpsk_receiver_cc.h
new file mode 100644
index 000000000..bcb06e17c
--- /dev/null
+++ b/gr-digital/lib/digital_mpsk_receiver_cc.h
@@ -0,0 +1,316 @@
+/* -*- c++ -*- */
+/*
+ * Copyright 2004,2007,2011 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.
+ */
+
+#ifndef INCLUDED_DIGITAL_MPSK_RECEIVER_CC_H
+#define INCLUDED_DIGITAL_MPSK_RECEIVER_CC_H
+
+#include <gruel/attributes.h>
+#include <gr_block.h>
+#include <gr_complex.h>
+#include <fstream>
+
+class gri_mmse_fir_interpolator_cc;
+
+class digital_mpsk_receiver_cc;
+typedef boost::shared_ptr<digital_mpsk_receiver_cc> digital_mpsk_receiver_cc_sptr;
+
+// public constructor
+digital_mpsk_receiver_cc_sptr
+digital_make_mpsk_receiver_cc (unsigned int M, float theta,
+ float alpha, float beta,
+ float fmin, float fmax,
+ float mu, float gain_mu,
+ float omega, float gain_omega, float omega_rel);
+
+/*!
+ * \brief This block takes care of receiving M-PSK modulated signals through phase, frequency, and symbol
+ * synchronization.
+ * \ingroup sync_blk
+ * \ingroup demod_blk
+ *
+ * This block takes care of receiving M-PSK modulated signals through phase, frequency, and symbol
+ * synchronization. It performs carrier frequency and phase locking as well as symbol timing recovery.
+ * It works with (D)BPSK, (D)QPSK, and (D)8PSK as tested currently. It should also work for OQPSK and
+ * PI/4 DQPSK.
+ *
+ * The phase and frequency synchronization are based on a Costas loop that finds the error of the incoming
+ * signal point compared to its nearest constellation point. The frequency and phase of the NCO are
+ * updated according to this error. There are optimized phase error detectors for BPSK and QPSK, but 8PSK
+ * is done using a brute-force computation of the constellation points to find the minimum.
+ *
+ * The symbol synchronization is done using a modified Mueller and Muller circuit from the paper:
+ *
+ * G. R. Danesfahani, T.G. Jeans, "Optimisation of modified Mueller and Muller
+ * algorithm," Electronics Letters, Vol. 31, no. 13, 22 June 1995, pp. 1032 - 1033.
+ *
+ * This circuit interpolates the downconverted sample (using the NCO developed by the Costas loop)
+ * every mu samples, then it finds the sampling error based on this and the past symbols and the decision
+ * made on the samples. Like the phase error detector, there are optimized decision algorithms for BPSK
+ * and QPKS, but 8PSK uses another brute force computation against all possible symbols. The modifications
+ * to the M&M used here reduce self-noise.
+ *
+ */
+
+class digital_mpsk_receiver_cc : public gr_block
+{
+ public:
+ ~digital_mpsk_receiver_cc ();
+ void forecast(int noutput_items, gr_vector_int &ninput_items_required);
+ int general_work (int noutput_items,
+ gr_vector_int &ninput_items,
+ gr_vector_const_void_star &input_items,
+ gr_vector_void_star &output_items);
+
+
+ // Member functions related to the symbol tracking portion of the receiver
+ //! (M&M) Returns current value of mu
+ float mu() const { return d_mu;}
+
+ //! (M&M) Returns current value of omega
+ float omega() const { return d_omega;}
+
+ //! (M&M) Returns mu gain factor
+ float gain_mu() const { return d_gain_mu;}
+
+ //! (M&M) Returns omega gain factor
+ float gain_omega() const { return d_gain_omega;}
+
+ //! (M&M) Sets value of mu
+ void set_mu (float mu) { d_mu = mu; }
+
+ //! (M&M) Sets value of omega and its min and max values
+ void set_omega (float omega) {
+ d_omega = omega;
+ d_min_omega = omega*(1.0 - d_omega_rel);
+ d_max_omega = omega*(1.0 + d_omega_rel);
+ d_omega_mid = 0.5*(d_min_omega+d_max_omega);
+ }
+
+ //! (M&M) Sets value for mu gain factor
+ void set_gain_mu (float gain_mu) { d_gain_mu = gain_mu; }
+
+ //! (M&M) Sets value for omega gain factor
+ void set_gain_omega (float gain_omega) { d_gain_omega = gain_omega; }
+
+
+
+ // Member function related to the phase/frequency tracking portion of the receiver
+ //! (CL) Returns the value for alpha (the phase gain term)
+ float alpha() const { return d_alpha; }
+
+ //! (CL) Returns the value of beta (the frequency gain term)
+ float beta() const { return d_beta; }
+
+ //! (CL) Returns the current value of the frequency of the NCO in the Costas loop
+ float freq() const { return d_freq; }
+
+ //! (CL) Returns the current value of the phase of the NCO in the Costal loop
+ float phase() const { return d_phase; }
+
+ //! (CL) Sets the value for alpha (the phase gain term)
+ void set_alpha(float alpha) { d_alpha = alpha; }
+
+ //! (CL) Setss the value of beta (the frequency gain term)
+ void set_beta(float beta) { d_beta = beta; }
+
+ //! (CL) Sets the current value of the frequency of the NCO in the Costas loop
+ void set_freq(float freq) { d_freq = freq; }
+
+ //! (CL) Setss the current value of the phase of the NCO in the Costal loop
+ void set_phase(float phase) { d_phase = phase; }
+
+
+protected:
+
+ /*!
+ * \brief Constructor to synchronize incoming M-PSK symbols
+ *
+ * \param M modulation order of the M-PSK modulation
+ * \param theta any constant phase rotation from the real axis of the constellation
+ * \param alpha gain parameter to adjust the phase in the Costas loop (~0.01)
+ * \param beta gain parameter to adjust the frequency in the Costas loop (~alpha^2/4)
+ * \param fmin minimum normalized frequency value the loop can achieve
+ * \param fmax maximum normalized frequency value the loop can achieve
+ * \param mu initial parameter for the interpolator [0,1]
+ * \param gain_mu gain parameter of the M&M error signal to adjust mu (~0.05)
+ * \param omega initial value for the number of symbols between samples (~number of samples/symbol)
+ * \param gain_omega gain parameter to adjust omega based on the error (~omega^2/4)
+ * \param omega_rel sets the maximum (omega*(1+omega_rel)) and minimum (omega*(1+omega_rel)) omega (~0.005)
+ *
+ * The constructor also chooses which phase detector and decision maker to use in the work loop based on the
+ * value of M.
+ */
+ digital_mpsk_receiver_cc (unsigned int M, float theta,
+ float alpha, float beta,
+ float fmin, float fmax,
+ float mu, float gain_mu,
+ float omega, float gain_omega, float omega_rel);
+
+ void make_constellation();
+ void mm_sampler(const gr_complex symbol);
+ void mm_error_tracking(gr_complex sample);
+ void phase_error_tracking(gr_complex sample);
+
+
+/*!
+ * \brief Phase error detector for MPSK modulations.
+ *
+ * \param sample the I&Q sample from which to determine the phase error
+ *
+ * This function determines the phase error for any MPSK signal by creating a set of PSK constellation points
+ * and doing a brute-force search to see which point minimizes the Euclidean distance. This point is then used
+ * to derotate the sample to the real-axis and a atan (using the fast approximation function) to determine the
+ * phase difference between the incoming sample and the real constellation point
+ *
+ * This should be cleaned up and made more efficient.
+ *
+ * \returns the approximated phase error.
+ */
+ float phase_error_detector_generic(gr_complex sample) const; // generic for M but more costly
+
+ /*!
+ * \brief Phase error detector for BPSK modulation.
+ *
+ * \param sample the I&Q sample from which to determine the phase error
+ *
+ * This function determines the phase error using a simple BPSK phase error detector by multiplying the real
+ * and imaginary (the error signal) components together. As the imaginary part goes to 0, so does this error.
+ *
+ * \returns the approximated phase error.
+ */
+ float phase_error_detector_bpsk(gr_complex sample) const; // optimized for BPSK
+
+ /*!
+ * \brief Phase error detector for QPSK modulation.
+ *
+ * \param sample the I&Q sample from which to determine the phase error
+ *
+ * This function determines the phase error using the limiter approach in a standard 4th order Costas loop
+ *
+ * \returns the approximated phase error.
+ */
+ float phase_error_detector_qpsk(gr_complex sample) const;
+
+
+
+ /*!
+ * \brief Decision maker for a generic MPSK constellation.
+ *
+ * \param sample the baseband I&Q sample from which to make the decision
+ *
+ * This decision maker is a generic implementation that does a brute-force search
+ * for the constellation point that minimizes the error between it and the incoming signal.
+ *
+ * \returns the index to d_constellation that minimizes the error/
+ */
+ unsigned int decision_generic(gr_complex sample) const;
+
+
+ /*!
+ * \brief Decision maker for BPSK constellation.
+ *
+ * \param sample the baseband I&Q sample from which to make the decision
+ *
+ * This decision maker is a simple slicer function that makes a decision on the symbol based on its
+ * placement on the real axis of greater than 0 or less than 0; the quadrature component is always 0.
+ *
+ * \returns the index to d_constellation that minimizes the error/
+ */
+ unsigned int decision_bpsk(gr_complex sample) const;
+
+
+ /*!
+ * \brief Decision maker for QPSK constellation.
+ *
+ * \param sample the baseband I&Q sample from which to make the decision
+ *
+ * This decision maker is a simple slicer function that makes a decision on the symbol based on its
+ * placement versus both axes and returns which quadrant the symbol is in.
+ *
+ * \returns the index to d_constellation that minimizes the error/
+ */
+ unsigned int decision_qpsk(gr_complex sample) const;
+
+ private:
+ unsigned int d_M;
+ float d_theta;
+
+ // Members related to carrier and phase tracking
+ float d_alpha;
+ float d_beta;
+ float d_freq, d_max_freq, d_min_freq;
+ float d_phase;
+
+/*!
+ * \brief Decision maker function pointer
+ *
+ * \param sample the baseband I&Q sample from which to make the decision
+ *
+ * This is a function pointer that is set in the constructor to point to the proper decision function
+ * for the specified constellation order.
+ *
+ * \return index into d_constellation point that is the closest to the recieved sample
+ */
+ unsigned int (digital_mpsk_receiver_cc::*d_decision)(gr_complex sample) const; // pointer to decision function
+
+
+ std::vector<gr_complex> d_constellation;
+ unsigned int d_current_const_point;
+
+ // Members related to symbol timing
+ float d_mu, d_gain_mu;
+ float d_omega, d_gain_omega, d_omega_rel, d_max_omega, d_min_omega, d_omega_mid;
+ gr_complex d_p_2T, d_p_1T, d_p_0T;
+ gr_complex d_c_2T, d_c_1T, d_c_0T;
+
+ /*!
+ * \brief Phase error detector function pointer
+ *
+ * \param sample the I&Q sample from which to determine the phase error
+ *
+ * This is a function pointer that is set in the constructor to point to the proper phase error detector
+ * function for the specified constellation order.
+ */
+ float (digital_mpsk_receiver_cc::*d_phase_error_detector)(gr_complex sample) const;
+
+
+ //! get interpolated value
+ gri_mmse_fir_interpolator_cc *d_interp;
+
+ //! delay line length.
+ static const unsigned int DLLEN = 8;
+
+ //! delay line plus some length for overflow protection
+ __GR_ATTR_ALIGNED(8) gr_complex d_dl[2*DLLEN];
+
+ //! index to delay line
+ unsigned int d_dl_idx;
+
+ friend digital_mpsk_receiver_cc_sptr
+ digital_make_mpsk_receiver_cc (unsigned int M, float theta,
+ float alpha, float beta,
+ float fmin, float fmax,
+ float mu, float gain_mu,
+ float omega, float gain_omega, float omega_rel);
+};
+
+#endif
diff --git a/gr-digital/python/Makefile.am b/gr-digital/python/Makefile.am
index 09be67f3a..eea4dba95 100644
--- a/gr-digital/python/Makefile.am
+++ b/gr-digital/python/Makefile.am
@@ -41,7 +41,8 @@ noinst_PYTHON = \
qa_correlate_access_code.py \
qa_costas_loop_cc.py \
qa_crc32.py \
- qa_lms_equalizer.py
+ qa_lms_equalizer.py \
+ qa_mpsk_receiver.py
digital_PYTHON = \
__init__.py \
diff --git a/gr-digital/python/qa_mpsk_receiver.py b/gr-digital/python/qa_mpsk_receiver.py
new file mode 100644
index 000000000..7e9a76e1f
--- /dev/null
+++ b/gr-digital/python/qa_mpsk_receiver.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python
+#
+# Copyright 2011 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.
+#
+
+from gnuradio import gr, gr_unittest
+import digital_swig, psk2
+import random, cmath
+
+class test_mpsk_receiver(gr_unittest.TestCase):
+
+ def setUp (self):
+ self.tb = gr.top_block ()
+
+ def tearDown (self):
+ self.tb = None
+
+ def test01 (self):
+ # Test BPSK sync
+ M = 2
+ theta = 0
+ alpha = 0.1
+ beta = 0.25*alpha*alpha
+ fmin = -0.5
+ fmax = 0.5
+ mu = 0.25
+ gain_mu = 0.01
+ omega = 2
+ gain_omega = 0.001
+ omega_rel = 0.001
+
+ self.test = digital_swig.mpsk_receiver_cc(M, theta, alpha, beta,
+ fmin, fmax, mu, gain_mu,
+ omega, gain_omega,
+ omega_rel)
+
+ data = 1000*[complex(1,0), complex(1,0), complex(-1,0), complex(-1,0)]
+ self.src = gr.vector_source_c(data, False)
+ self.snk = gr.vector_sink_c()
+
+ self.tb.connect(self.src, self.test, self.snk)
+ self.tb.run()
+
+ expected_result = 1000*[complex(-0.5,0), complex(0.5,0)]
+ dst_data = self.snk.data()
+
+ # Only compare last Ncmp samples
+ Ncmp = 100
+ len_e = len(expected_result)
+ len_d = len(dst_data)
+ expected_result = expected_result[len_e - Ncmp:]
+ dst_data = dst_data[len_d - Ncmp:]
+
+ #print expected_result
+ #print dst_data
+
+ self.assertComplexTuplesAlmostEqual (expected_result, dst_data, 1)
+
+
+ def test02 (self):
+ # Test QPSK sync
+ M = 4
+ theta = 0
+ alpha = 0.1
+ beta = 0.25*alpha*alpha
+ fmin = -0.5
+ fmax = 0.5
+ mu = 0.25
+ gain_mu = 0.01
+ omega = 2
+ gain_omega = 0.001
+ omega_rel = 0.001
+
+ self.test = digital_swig.mpsk_receiver_cc(M, theta, alpha, beta,
+ fmin, fmax, mu, gain_mu,
+ omega, gain_omega,
+ omega_rel)
+
+ data = 1000*[complex( 0.707, 0.707), complex( 0.707, 0.707),
+ complex(-0.707, 0.707), complex(-0.707, 0.707),
+ complex(-0.707, -0.707), complex(-0.707, -0.707),
+ complex( 0.707, -0.707), complex( 0.707, -0.707)]
+ self.src = gr.vector_source_c(data, False)
+ self.snk = gr.vector_sink_c()
+
+ self.tb.connect(self.src, self.test, self.snk)
+ self.tb.run()
+
+ expected_result = 1000*[complex(1.2, 0), complex(0, 1.2),
+ complex(-1.2, 0), complex(0, -1.2)]
+ dst_data = self.snk.data()
+
+ # Only compare last Ncmp samples
+ Ncmp = 100
+ len_e = len(expected_result)
+ len_d = len(dst_data)
+ expected_result = expected_result[len_e - Ncmp:]
+ dst_data = dst_data[len_d - Ncmp:]
+
+ #print expected_result
+ #print dst_data
+
+ self.assertComplexTuplesAlmostEqual (expected_result, dst_data, 1)
+
+if __name__ == '__main__':
+ gr_unittest.run(test_mpsk_receiver, "test_mpsk_receiver.xml")
diff --git a/gr-digital/swig/Makefile.am b/gr-digital/swig/Makefile.am
index 0a4176fc1..08fb140ac 100644
--- a/gr-digital/swig/Makefile.am
+++ b/gr-digital/swig/Makefile.am
@@ -69,7 +69,8 @@ digital_swig_swiginclude_headers = \
digital_cma_equalizer_cc.i \
digital_crc32.i \
digital_lms_dd_equalizer_cc.i \
- digital_kurtotic_equalizer_cc.i
+ digital_kurtotic_equalizer_cc.i \
+ digital_mpsk_receiver_cc.i
digital_swig_swig_args = \
-I$(abs_top_srcdir)/gr-digital/lib \
diff --git a/gr-digital/swig/digital_mpsk_receiver_cc.i b/gr-digital/swig/digital_mpsk_receiver_cc.i
new file mode 100644
index 000000000..cdc9f661b
--- /dev/null
+++ b/gr-digital/swig/digital_mpsk_receiver_cc.i
@@ -0,0 +1,60 @@
+/* -*- c++ -*- */
+/*
+ * Copyright 2004,2011 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.
+ */
+
+GR_SWIG_BLOCK_MAGIC(digital,mpsk_receiver_cc);
+
+digital_mpsk_receiver_cc_sptr digital_make_mpsk_receiver_cc (unsigned int M, float theta,
+ float alpha, float beta,
+ float fmin, float fmax,
+ float mu, float gain_mu,
+ float omega, float gain_omega,
+ float omega_rel);
+class digital_mpsk_receiver_cc : public gr_block
+{
+ private:
+ digital_mpsk_receiver_cc (unsigned int M,float theta,
+ float alpha, float beta,
+ float fmin, float fmax,
+ float mu, float gain_mu,
+ float omega, float gain_omega, float omega_rel);
+public:
+ float mu() const { return d_mu;}
+ float omega() const { return d_omega;}
+ float gain_mu() const { return d_gain_mu;}
+ float gain_omega() const { return d_gain_omega;}
+ void set_mu (float mu) { d_mu = mu; }
+ void set_omega (float omega) {
+ d_omega = omega;
+ d_min_omega = omega*(1.0 - d_omega_rel);
+ d_max_omega = omega*(1.0 + d_omega_rel);
+ }
+ void set_gain_mu (float gain_mu) { d_gain_mu = gain_mu; }
+ void set_gain_omega (float gain_omega) { d_gain_omega = gain_omega; }
+ float alpha() const { return d_alpha; }
+ float beta() const { return d_beta; }
+ float freq() const { return d_freq; }
+ float phase() const { return d_phase; }
+ void set_alpha(float alpha) { d_alpha = alpha; }
+ void set_beta(float beta) { d_beta = beta; }
+ void set_freq(float freq) { d_freq = freq; }
+ void set_phase(float phase) { d_phase = phase; }
+};
diff --git a/gr-digital/swig/digital_swig.i b/gr-digital/swig/digital_swig.i
index bc7c9b0d9..cfaadcaea 100644
--- a/gr-digital/swig/digital_swig.i
+++ b/gr-digital/swig/digital_swig.i
@@ -34,6 +34,7 @@
#include "digital_crc32.h"
#include "digital_kurtotic_equalizer_cc.h"
#include "digital_lms_dd_equalizer_cc.h"
+#include "digital_mpsk_receiver_cc.h"
%}
%include "digital_binary_slicer_fb.i"
@@ -48,6 +49,7 @@
%include "digital_crc32.i"
%include "digital_kurtotic_equalizer_cc.i"
%include "digital_lms_dd_equalizer_cc.i"
+%include "digital_mpsk_receiver_cc.i"
#if SWIGGUILE
%scheme %{