From 28e086141aead2e43f958f0ae14d58cac557fa2d Mon Sep 17 00:00:00 2001 From: trondeau Date: Wed, 7 Mar 2007 04:31:19 +0000 Subject: [PATCH] merged trondeau/digital-wip2 r4193:4730 into trunk - improves digital receiver and fixes ticket:72 git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@4731 221aa14e-8319-0410-a670-987f0aec2ac5 --- config/grc_gnuradio_core.m4 | 2 + config/grc_gnuradio_examples.m4 | 1 + gnuradio-core/src/lib/general/Makefile.am | 3 + gnuradio-core/src/lib/general/general.i | 2 + .../lib/general/gr_clock_recovery_mm_cc.cc | 121 ++++-- .../src/lib/general/gr_clock_recovery_mm_cc.h | 9 +- .../general/gr_correlate_access_code_bb.cc | 9 +- .../src/lib/general/gr_mpsk_receiver_cc.cc | 345 +++++++++++++++ .../src/lib/general/gr_mpsk_receiver_cc.h | 313 ++++++++++++++ .../src/lib/general/gr_mpsk_receiver_cc.i | 59 +++ gnuradio-core/src/python/gnuradio/Makefile.am | 2 +- .../src/python/gnuradio/blks2/Makefile.am | 35 ++ .../src/python/gnuradio/blks2/__init__.py | 37 ++ .../src/python/gnuradio/blksimpl/Makefile.am | 6 + .../src/python/gnuradio/blksimpl/d8psk.py | 364 ++++++++++++++++ .../src/python/gnuradio/blksimpl/dbpsk.py | 148 +++---- .../src/python/gnuradio/blksimpl/dqpsk.py | 128 +++--- .../src/python/gnuradio/blksimpl/gmsk.py | 5 +- .../src/python/gnuradio/blksimpl/psk.py | 57 ++- .../src/python/gnuradio/blksimpl/qam.py | 113 +++++ .../src/python/gnuradio/blksimpl/qam16.py | 206 +++++++++ .../src/python/gnuradio/blksimpl/qam256.py | 206 +++++++++ .../src/python/gnuradio/blksimpl/qam64.py | 206 +++++++++ .../src/python/gnuradio/blksimpl/qam8.py | 206 +++++++++ .../src/python/gnuradio/blksimpl2/Makefile.am | 40 ++ .../src/python/gnuradio/blksimpl2/__init__.py | 1 + .../src/python/gnuradio/blksimpl2/d8psk.py | 407 ++++++++++++++++++ .../src/python/gnuradio/blksimpl2/dbpsk.py | 404 +++++++++++++++++ .../src/python/gnuradio/blksimpl2/dqpsk.py | 397 +++++++++++++++++ .../src/python/gnuradio/blksimpl2/gmsk.py | 305 +++++++++++++ .../src/python/gnuradio/blksimpl2/pkt.py | 165 +++++++ .../src/python/gnuradio/blksimpl2/psk.py | 88 ++++ .../src/python/gnuradio/packet_utils.py | 22 +- .../python/digital/benchmark_loopback.py | 185 ++++++++ .../python/digital/receive_path.py | 34 +- .../python/digital/receive_path_lb.py | 136 ++++++ .../python/digital/transmit_path_lb.py | 117 +++++ gnuradio-examples/python/hier/Makefile.am | 1 + .../python/hier/digital/Makefile.am | 35 ++ gnuradio-examples/python/hier/digital/README | 77 ++++ .../python/hier/digital/benchmark_loopback.py | 206 +++++++++ .../python/hier/digital/benchmark_rx.py | 119 +++++ .../python/hier/digital/benchmark_tx.py | 126 ++++++ .../python/hier/digital/fusb_options.py | 31 ++ .../python/hier/digital/pick_bitrate.py | 143 ++++++ .../python/hier/digital/receive_path.py | 264 ++++++++++++ .../python/hier/digital/receive_path_lb.py | 140 ++++++ .../python/hier/digital/rx_voice.py | 136 ++++++ .../python/hier/digital/transmit_path.py | 239 ++++++++++ .../python/hier/digital/transmit_path_lb.py | 123 ++++++ .../python/hier/digital/tunnel.py | 290 +++++++++++++ .../python/hier/digital/tx_voice.py | 149 +++++++ .../python/hier/usrp/Makefile.am | 3 +- .../python/hier/usrp/usrp_siggen.py | 145 +++++++ 54 files changed, 6871 insertions(+), 240 deletions(-) create mode 100644 gnuradio-core/src/lib/general/gr_mpsk_receiver_cc.cc create mode 100644 gnuradio-core/src/lib/general/gr_mpsk_receiver_cc.h create mode 100644 gnuradio-core/src/lib/general/gr_mpsk_receiver_cc.i create mode 100644 gnuradio-core/src/python/gnuradio/blks2/Makefile.am create mode 100644 gnuradio-core/src/python/gnuradio/blks2/__init__.py create mode 100644 gnuradio-core/src/python/gnuradio/blksimpl/d8psk.py create mode 100644 gnuradio-core/src/python/gnuradio/blksimpl/qam.py create mode 100644 gnuradio-core/src/python/gnuradio/blksimpl/qam16.py create mode 100644 gnuradio-core/src/python/gnuradio/blksimpl/qam256.py create mode 100644 gnuradio-core/src/python/gnuradio/blksimpl/qam64.py create mode 100644 gnuradio-core/src/python/gnuradio/blksimpl/qam8.py create mode 100644 gnuradio-core/src/python/gnuradio/blksimpl2/Makefile.am create mode 100644 gnuradio-core/src/python/gnuradio/blksimpl2/__init__.py create mode 100644 gnuradio-core/src/python/gnuradio/blksimpl2/d8psk.py create mode 100644 gnuradio-core/src/python/gnuradio/blksimpl2/dbpsk.py create mode 100644 gnuradio-core/src/python/gnuradio/blksimpl2/dqpsk.py create mode 100644 gnuradio-core/src/python/gnuradio/blksimpl2/gmsk.py create mode 100644 gnuradio-core/src/python/gnuradio/blksimpl2/pkt.py create mode 100644 gnuradio-core/src/python/gnuradio/blksimpl2/psk.py create mode 100755 gnuradio-examples/python/digital/benchmark_loopback.py create mode 100644 gnuradio-examples/python/digital/receive_path_lb.py create mode 100644 gnuradio-examples/python/digital/transmit_path_lb.py create mode 100644 gnuradio-examples/python/hier/digital/Makefile.am create mode 100644 gnuradio-examples/python/hier/digital/README create mode 100755 gnuradio-examples/python/hier/digital/benchmark_loopback.py create mode 100755 gnuradio-examples/python/hier/digital/benchmark_rx.py create mode 100755 gnuradio-examples/python/hier/digital/benchmark_tx.py create mode 100644 gnuradio-examples/python/hier/digital/fusb_options.py create mode 100644 gnuradio-examples/python/hier/digital/pick_bitrate.py create mode 100644 gnuradio-examples/python/hier/digital/receive_path.py create mode 100644 gnuradio-examples/python/hier/digital/receive_path_lb.py create mode 100755 gnuradio-examples/python/hier/digital/rx_voice.py create mode 100644 gnuradio-examples/python/hier/digital/transmit_path.py create mode 100644 gnuradio-examples/python/hier/digital/transmit_path_lb.py create mode 100755 gnuradio-examples/python/hier/digital/tunnel.py create mode 100755 gnuradio-examples/python/hier/digital/tx_voice.py create mode 100755 gnuradio-examples/python/hier/usrp/usrp_siggen.py diff --git a/config/grc_gnuradio_core.m4 b/config/grc_gnuradio_core.m4 index 6b9c8542..ebf0738d 100644 --- a/config/grc_gnuradio_core.m4 +++ b/config/grc_gnuradio_core.m4 @@ -45,6 +45,8 @@ AC_DEFUN([GRC_GNURADIO_CORE],[ gnuradio-core/src/python/gnuradio/Makefile \ gnuradio-core/src/python/gnuradio/blks/Makefile \ gnuradio-core/src/python/gnuradio/blksimpl/Makefile \ + gnuradio-core/src/python/gnuradio/blks2/Makefile \ + gnuradio-core/src/python/gnuradio/blksimpl2/Makefile \ gnuradio-core/src/python/gnuradio/gr/Makefile \ gnuradio-core/src/python/gnuradio/gr/run_tests \ gnuradio-core/src/python/gnuradio/gru/Makefile \ diff --git a/config/grc_gnuradio_examples.m4 b/config/grc_gnuradio_examples.m4 index 82b0f882..bf9730a0 100644 --- a/config/grc_gnuradio_examples.m4 +++ b/config/grc_gnuradio_examples.m4 @@ -34,6 +34,7 @@ AC_DEFUN([GRC_GNURADIO_EXAMPLES],[ gnuradio-examples/python/hier/Makefile \ gnuradio-examples/python/hier/audio/Makefile \ gnuradio-examples/python/hier/networking/Makefile \ + gnuradio-examples/python/hier/digital/Makefile \ gnuradio-examples/python/hier/ofdm/Makefile \ gnuradio-examples/python/hier/sounder/Makefile \ gnuradio-examples/python/hier/usrp/Makefile \ diff --git a/gnuradio-core/src/lib/general/Makefile.am b/gnuradio-core/src/lib/general/Makefile.am index d29259b4..3856a319 100644 --- a/gnuradio-core/src/lib/general/Makefile.am +++ b/gnuradio-core/src/lib/general/Makefile.am @@ -88,6 +88,7 @@ libgeneral_la_SOURCES = \ gr_map_bb.cc \ gr_math.cc \ gr_misc.cc \ + gr_mpsk_receiver_cc.cc \ gr_nlog10_ff.cc \ gr_nop.cc \ gr_null_sink.cc \ @@ -211,6 +212,7 @@ grinclude_HEADERS = \ gr_map_bb.h \ gr_math.h \ gr_misc.h \ + gr_mpsk_receiver_cc.h \ gr_nco.h \ gr_nlog10_ff.h \ gr_nop.h \ @@ -337,6 +339,7 @@ swiginclude_HEADERS = \ gr_lms_dfe_cc.i \ gr_lms_dfe_ff.i \ gr_map_bb.i \ + gr_mpsk_receiver_cc.i \ gr_nlog10_ff.i \ gr_nop.i \ gr_null_sink.i \ diff --git a/gnuradio-core/src/lib/general/general.i b/gnuradio-core/src/lib/general/general.i index b659beb5..87368f81 100644 --- a/gnuradio-core/src/lib/general/general.i +++ b/gnuradio-core/src/lib/general/general.i @@ -67,6 +67,7 @@ #include #include #include +#include #include #include #include @@ -157,6 +158,7 @@ %include "gr_nlog10_ff.i" %include "gr_fake_channel_coder_pp.i" %include "gr_throttle.i" +%include "gr_mpsk_receiver_cc.i" %include "gr_stream_to_streams.i" %include "gr_streams_to_stream.i" %include "gr_streams_to_vector.i" diff --git a/gnuradio-core/src/lib/general/gr_clock_recovery_mm_cc.cc b/gnuradio-core/src/lib/general/gr_clock_recovery_mm_cc.cc index 88cd1407..6166e25e 100644 --- a/gnuradio-core/src/lib/general/gr_clock_recovery_mm_cc.cc +++ b/gnuradio-core/src/lib/general/gr_clock_recovery_mm_cc.cc @@ -133,47 +133,90 @@ gr_clock_recovery_mm_cc::general_work (int noutput_items, float mm_val=0; gr_complex u, x, y; - while(oo < noutput_items && ii < ni) { - d_p_2T = d_p_1T; - d_p_1T = d_p_0T; - d_p_0T = d_interp->interpolate (&in[ii], d_mu); - - d_c_2T = d_c_1T; - d_c_1T = d_c_0T; - d_c_0T = slicer_0deg(d_p_0T); - - 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_val = u.real(); - out[oo++] = d_p_0T; - - // limit mm_val - if (mm_val > 1.0) - mm_val = 1.0; - else if (mm_val < -1.0) - mm_val = -1.0; - - d_omega = d_omega + d_gain_omega * mm_val; - if (d_omega > d_max_omega) - d_omega = d_max_omega; - else if (d_omega < d_min_omega) - d_omega = d_min_omega; - - d_mu = d_mu + d_omega + d_gain_mu * mm_val; - ii += (int)floor(d_mu); - d_mu -= floor(d_mu); - - if(d_verbose) { + // This loop writes the error to the second output, if it exists + if (write_foptr) { + while(oo < noutput_items && ii < ni) { + d_p_2T = d_p_1T; + d_p_1T = d_p_0T; + d_p_0T = d_interp->interpolate (&in[ii], d_mu); + + d_c_2T = d_c_1T; + d_c_1T = d_c_0T; + d_c_0T = slicer_0deg(d_p_0T); + + 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_val = u.real(); + out[oo++] = d_p_0T; + + // limit mm_val + if (mm_val > 1.0) + mm_val = 1.0; + else if (mm_val < -1.0) + mm_val = -1.0; + + d_omega = d_omega + d_gain_omega * mm_val; + if (d_omega > d_max_omega) + d_omega = d_max_omega; + else if (d_omega < d_min_omega) + d_omega = d_min_omega; + + d_mu = d_mu + d_omega + d_gain_mu * mm_val; + ii += (int)floor(d_mu); + d_mu -= floor(d_mu); + + #if 0 printf("%f\t%f\n", d_omega, d_mu); - } - - // write the error signal to the second output - if (write_foptr) + #endif + + // write the error signal to the second output foptr[oo-1] = gr_complex(d_mu,0); - - if (ii < 0) // clamp it. This should only happen with bogus input - ii = 0; + + if (ii < 0) // clamp it. This should only happen with bogus input + ii = 0; + } + } + // This loop does not write to the second output (ugly, but faster) + else { + while(oo < noutput_items && ii < ni) { + d_p_2T = d_p_1T; + d_p_1T = d_p_0T; + d_p_0T = d_interp->interpolate (&in[ii], d_mu); + + d_c_2T = d_c_1T; + d_c_1T = d_c_0T; + d_c_0T = slicer_0deg(d_p_0T); + + 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_val = u.real(); + out[oo++] = d_p_0T; + + // limit mm_val + if (mm_val > 1.0) + mm_val = 1.0; + else if (mm_val < -1.0) + mm_val = -1.0; + + d_omega = d_omega + d_gain_omega * mm_val; + if (d_omega > d_max_omega) + d_omega = d_max_omega; + else if (d_omega < d_min_omega) + d_omega = d_min_omega; + + d_mu = d_mu + d_omega + d_gain_mu * mm_val; + ii += (int)floor(d_mu); + d_mu -= floor(d_mu); + + if(d_verbose) { + printf("%f\t%f\n", d_omega, d_mu); + } + + if (ii < 0) // clamp it. This should only happen with bogus input + ii = 0; + } } if (ii > 0){ diff --git a/gnuradio-core/src/lib/general/gr_clock_recovery_mm_cc.h b/gnuradio-core/src/lib/general/gr_clock_recovery_mm_cc.h index 0a2d73a9..c9a50358 100644 --- a/gnuradio-core/src/lib/general/gr_clock_recovery_mm_cc.h +++ b/gnuradio-core/src/lib/general/gr_clock_recovery_mm_cc.h @@ -41,10 +41,11 @@ gr_make_clock_recovery_mm_cc (float omega, float gain_omega, float mu, float gai * \ingroup block * * This implements the Mueller and Müller (M&M) discrete-time error-tracking synchronizer. - * - * See "Digital Communication Receivers: Synchronization, Channel - * Estimation and Signal Processing" by Heinrich Meyr, Marc Moeneclaey, & Stefan Fechtel. - * ISBN 0-471-50275-8. + * The complex version here is based on: + * Modified Mueller and Muller clock recovery circuit + * Based: + * 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. */ class gr_clock_recovery_mm_cc : public gr_block { diff --git a/gnuradio-core/src/lib/general/gr_correlate_access_code_bb.cc b/gnuradio-core/src/lib/general/gr_correlate_access_code_bb.cc index 18253e96..078ff502 100644 --- a/gnuradio-core/src/lib/general/gr_correlate_access_code_bb.cc +++ b/gnuradio-core/src/lib/general/gr_correlate_access_code_bb.cc @@ -88,7 +88,7 @@ gr_correlate_access_code_bb::work (int noutput_items, { const unsigned char *in = (const unsigned char *) input_items[0]; unsigned char *out = (unsigned char *) output_items[0]; - + for (int i = 0; i < noutput_items; i++){ // compute output value @@ -109,9 +109,12 @@ gr_correlate_access_code_bb::work (int noutput_items, // test for access code with up to threshold errors new_flag = (nwrong <= d_threshold); -#if 0 +#if VERBOSE if(new_flag) { - printf("%llx ==> %llx : d_flip=%u\n", d_access_code, d_data_reg, d_flip); + fprintf(stderr, "access code found: %llx\n", d_access_code); + } + else { + fprintf(stderr, "%llx ==> %llx\n", d_access_code, d_data_reg); } #endif diff --git a/gnuradio-core/src/lib/general/gr_mpsk_receiver_cc.cc b/gnuradio-core/src/lib/general/gr_mpsk_receiver_cc.cc new file mode 100644 index 00000000..87b2f541 --- /dev/null +++ b/gnuradio-core/src/lib/general/gr_mpsk_receiver_cc.cc @@ -0,0 +1,345 @@ +/* -*- c++ -*- */ +/* + * 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 2, 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 +#include +#include +#include +#include +#include +#include + + +#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 + +gr_mpsk_receiver_cc_sptr +gr_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 gr_mpsk_receiver_cc_sptr (new gr_mpsk_receiver_cc (M, theta, + alpha, beta, + fmin, fmax, + mu, gain_mu, + omega, gain_omega, omega_rel)); +} + +gr_mpsk_receiver_cc::gr_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 = &gr_mpsk_receiver_cc::phase_error_detector_generic; //bpsk; + d_decision = &gr_mpsk_receiver_cc::decision_generic; //bpsk; + break; + + case 4: // optimized algorithms for QPSK + d_phase_error_detector = &gr_mpsk_receiver_cc::phase_error_detector_generic; //qpsk; + d_decision = &gr_mpsk_receiver_cc::decision_generic; //qpsk; + break; + + default: // generic algorithms for any M (power of 2?) but not pretty + d_phase_error_detector = &gr_mpsk_receiver_cc::phase_error_detector_generic; + d_decision = &gr_mpsk_receiver_cc::decision_generic; + break; + } + + set_history(3); // ensure 2 extra input sample is available +} + +gr_mpsk_receiver_cc::~gr_mpsk_receiver_cc () +{ + delete d_interp; +} + +void +gr_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()); + //ninput_items_required[i] = (int)(d_omega); + +} + +// FIXME add these back in an test difference in performance +float +gr_mpsk_receiver_cc::phase_error_detector_qpsk(gr_complex sample) const +{ + float phase_error = ((sample.real()>0 ? 1.0 : -1.0) * sample.imag() - + (sample.imag()>0 ? 1.0 : -1.0) * sample.real()); + return -phase_error; +} + +// FIXME add these back in an test difference in performance +float +gr_mpsk_receiver_cc::phase_error_detector_bpsk(gr_complex sample) const +{ + return (sample.real()*sample.imag()); +} + +float gr_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])); +} + +// FIXME add these back in an test difference in performance +unsigned int +gr_mpsk_receiver_cc::decision_bpsk(gr_complex sample) const +{ + unsigned int index = 0; + + // Implements a 1-demensional slicer + if(sample.real() > 0) + index = 1; + return index; +} + +// FIXME add these back in an test difference in performance +unsigned int +gr_mpsk_receiver_cc::decision_qpsk(gr_complex sample) const +{ + unsigned int index = 0; + + // Implements a simple slicer function + if((sample.real() < 0) && (sample.imag() > 0)) + index = 1; + else if((sample.real() < 0) && (sample.imag() < 0)) + index = 2; + else + index = 3; + return index; +} + +unsigned int +gr_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 +gr_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 +gr_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 +gr_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 + + // limit mm_val + if (mm_error > 1.0) + mm_error = 1.0; + else if (mm_error < -1.0) + mm_error = -1.0; + + d_omega = d_omega + d_gain_omega * mm_error; // update omega based on loop error + + // make sure we don't walk away + if (d_omega > d_max_omega) + d_omega = d_max_omega; + else if (d_omega < d_min_omega) + d_omega = d_min_omega; + + 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 +gr_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); + + if (phase_error > 1) + phase_error = 1; + else if (phase_error < -1) + phase_error = -1; + + 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 + if (d_freq > d_max_freq) + d_freq = d_max_freq; + else if (d_freq < d_min_freq) + d_freq = d_min_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 +gr_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(i < ninput_items[0]) { + while(o < noutput_items) { + 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/gnuradio-core/src/lib/general/gr_mpsk_receiver_cc.h b/gnuradio-core/src/lib/general/gr_mpsk_receiver_cc.h new file mode 100644 index 00000000..f096af48 --- /dev/null +++ b/gnuradio-core/src/lib/general/gr_mpsk_receiver_cc.h @@ -0,0 +1,313 @@ +/* -*- c++ -*- */ +/* + * Copyright 2004 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 2, 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_GR_MPSK_RECEIVER_CC_H +#define INCLUDED_GR_MPSK_RECEIVER_CC_H + +#include +#include +#include + +class gri_mmse_fir_interpolator_cc; + +class gr_mpsk_receiver_cc; +typedef boost::shared_ptr gr_mpsk_receiver_cc_sptr; + +// public constructor +gr_mpsk_receiver_cc_sptr +gr_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 block + * + * 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 gr_mpsk_receiver_cc : public gr_block +{ + public: + ~gr_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); + } + + //! (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. + */ + gr_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 (gr_mpsk_receiver_cc::*d_decision)(gr_complex sample) const; // pointer to decision function + + + std::vector 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; + 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 (gr_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_complex d_dl[2*DLLEN]; + + //! index to delay line + unsigned int d_dl_idx; + + friend gr_mpsk_receiver_cc_sptr + gr_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/gnuradio-core/src/lib/general/gr_mpsk_receiver_cc.i b/gnuradio-core/src/lib/general/gr_mpsk_receiver_cc.i new file mode 100644 index 00000000..661b4d04 --- /dev/null +++ b/gnuradio-core/src/lib/general/gr_mpsk_receiver_cc.i @@ -0,0 +1,59 @@ +/* -*- c++ -*- */ +/* + * Copyright 2004 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 2, 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(gr,mpsk_receiver_cc); + +gr_mpsk_receiver_cc_sptr gr_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 gr_mpsk_receiver_cc : public gr_block +{ + private: + gr_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/gnuradio-core/src/python/gnuradio/Makefile.am b/gnuradio-core/src/python/gnuradio/Makefile.am index 4f8972f0..0d53a11c 100644 --- a/gnuradio-core/src/python/gnuradio/Makefile.am +++ b/gnuradio-core/src/python/gnuradio/Makefile.am @@ -21,7 +21,7 @@ include $(top_srcdir)/Makefile.common -SUBDIRS = gr gru gruimpl blks blksimpl +SUBDIRS = gr gru gruimpl blks blksimpl blks2 blksimpl2 grpython_PYTHON = \ __init__.py \ diff --git a/gnuradio-core/src/python/gnuradio/blks2/Makefile.am b/gnuradio-core/src/python/gnuradio/blks2/Makefile.am new file mode 100644 index 00000000..ec0547b6 --- /dev/null +++ b/gnuradio-core/src/python/gnuradio/blks2/Makefile.am @@ -0,0 +1,35 @@ +# +# Copyright 2005 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 2, 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. +# + +include $(top_srcdir)/Makefile.common + +# EXTRA_DIST = run_tests.in +# TESTS = run_tests + +grblks2pythondir = $(grpythondir)/blks2 + +grblks2python_PYTHON = \ + __init__.py + + +noinst_PYTHON = + +CLEANFILES = *.pyc *.pyo diff --git a/gnuradio-core/src/python/gnuradio/blks2/__init__.py b/gnuradio-core/src/python/gnuradio/blks2/__init__.py new file mode 100644 index 00000000..da262dfc --- /dev/null +++ b/gnuradio-core/src/python/gnuradio/blks2/__init__.py @@ -0,0 +1,37 @@ +# +# Copyright 2005 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 2, 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. +# + +import glob +import os.path + +# Semi-hideous kludge to import everything in the blksimpl2 directory +# into the gnuradio.blks2 namespace. This keeps us from having to remember +# to manually update this file. + +for p in __path__: + filenames = glob.glob (os.path.join (p, "..", "blksimpl2", "*.py")) + for f in filenames: + f = os.path.basename(f).lower() + f = f[:-3] + if f == '__init__': + continue + # print f + exec "from gnuradio.blksimpl2.%s import *" % (f,) diff --git a/gnuradio-core/src/python/gnuradio/blksimpl/Makefile.am b/gnuradio-core/src/python/gnuradio/blksimpl/Makefile.am index 8122bfa6..6bfbfa90 100644 --- a/gnuradio-core/src/python/gnuradio/blksimpl/Makefile.am +++ b/gnuradio-core/src/python/gnuradio/blksimpl/Makefile.am @@ -31,6 +31,7 @@ grblkspython_PYTHON = \ am_demod.py \ dbpsk.py \ dqpsk.py \ + d8psk.py \ filterbank.py \ fm_demod.py \ fm_emph.py \ @@ -39,6 +40,11 @@ grblkspython_PYTHON = \ nbfm_tx.py \ pkt.py \ psk.py \ + qam.py \ + qam8.py \ + qam16.py \ + qam64.py \ + qam256.py \ rational_resampler.py \ standard_squelch.py \ wfm_rcv.py \ diff --git a/gnuradio-core/src/python/gnuradio/blksimpl/d8psk.py b/gnuradio-core/src/python/gnuradio/blksimpl/d8psk.py new file mode 100644 index 00000000..42839a0c --- /dev/null +++ b/gnuradio-core/src/python/gnuradio/blksimpl/d8psk.py @@ -0,0 +1,364 @@ +# +# Copyright 2005,2006 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 2, 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 + +""" +differential 8PSK modulation and demodulation. +""" + +from gnuradio import gr, gru, modulation_utils +from math import pi, sqrt +import psk +import cmath +import Numeric +from pprint import pprint + +# default values (used in __init__ and add_options) +_def_samples_per_symbol = 3 +_def_excess_bw = 0.35 +_def_gray_code = True +_def_verbose = False +_def_log = False + +_def_costas_alpha = 0.175 +_def_gain_mu = 0.175 +_def_mu = 0.5 +_def_omega_relative_limit = 0.005 + + +# ///////////////////////////////////////////////////////////////////////////// +# D8PSK modulator +# ///////////////////////////////////////////////////////////////////////////// + +class d8psk_mod(gr.hier_block): + + def __init__(self, fg, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log): + """ + Hierarchical block for RRC-filtered QPSK modulation. + + The input is a byte stream (unsigned char) and the + output is the complex modulated signal at baseband. + + @param fg: flow graph + @type fg: flow graph + @param samples_per_symbol: samples per symbol >= 2 + @type samples_per_symbol: integer + @param excess_bw: Root-raised cosine filter excess bandwidth + @type excess_bw: float + @param gray_code: Tell modulator to Gray code the bits + @type gray_code: bool + @param verbose: Print information about modulator? + @type verbose: bool + @param debug: Print modualtion data to files? + @type debug: bool + """ + + self._fg = fg + self._samples_per_symbol = samples_per_symbol + self._excess_bw = excess_bw + self._gray_code = gray_code + + if not isinstance(samples_per_symbol, int) or samples_per_symbol < 2: + raise TypeError, ("sbp must be an integer >= 2, is %d" % samples_per_symbol) + + ntaps = 11 * samples_per_symbol + + arity = pow(2,self.bits_per_symbol()) + + # turn bytes into k-bit vectors + self.bytes2chunks = \ + gr.packed_to_unpacked_bb(self.bits_per_symbol(), gr.GR_MSB_FIRST) + + if self._gray_code: + self.symbol_mapper = gr.map_bb(psk.binary_to_gray[arity]) + else: + self.symbol_mapper = gr.map_bb(psk.binary_to_ungray[arity]) + + self.diffenc = gr.diff_encoder_bb(arity) + + rot = 1 + rotated_const = map(lambda pt: pt * rot, psk.constellation[arity]) + self.chunks2symbols = gr.chunks_to_symbols_bc(rotated_const) + + # pulse shaping filter + self.rrc_taps = gr.firdes.root_raised_cosine( + self._samples_per_symbol, # gain (sps since we're interpolating by sps) + self._samples_per_symbol, # sampling rate + 1.0, # symbol rate + self._excess_bw, # excess bandwidth (roll-off factor) + ntaps) + + self.rrc_filter = gr.interp_fir_filter_ccf(self._samples_per_symbol, self.rrc_taps) + + if verbose: + self._print_verbage() + + if log: + self._setup_logging() + + # Connect & Initialize base class + self._fg.connect(self.bytes2chunks, self.symbol_mapper, self.diffenc, + self.chunks2symbols, self.rrc_filter) + gr.hier_block.__init__(self, self._fg, self.bytes2chunks, self.rrc_filter) + + 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 3 + bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM + + def _print_verbage(self): + print "bits per symbol = %d" % self.bits_per_symbol() + print "Gray code = %s" % self._gray_code + print "RS roll-off factor = %f" % self._excess_bw + + def _setup_logging(self): + print "Modulation logging turned on." + self._fg.connect(self.bytes2chunks, + gr.file_sink(gr.sizeof_char, "tx_bytes2chunks.dat")) + self._fg.connect(self.symbol_mapper, + gr.file_sink(gr.sizeof_char, "tx_graycoder.dat")) + self._fg.connect(self.diffenc, + gr.file_sink(gr.sizeof_char, "tx_diffenc.dat")) + self._fg.connect(self.chunks2symbols, + gr.file_sink(gr.sizeof_gr_complex, "tx_chunks2symbols.dat")) + self._fg.connect(self.rrc_filter, + gr.file_sink(gr.sizeof_gr_complex, "tx_rrc_filter.dat")) + + def add_options(parser): + """ + Adds 8PSK modulation-specific options to the standard parser + """ + parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw, + help="set RRC excess bandwith factor [default=%default] (PSK)") + parser.add_option("", "--no-gray-code", dest="gray_code", + action="store_false", default=_def_gray_code, + help="disable gray coding on modulated bits (PSK)") + add_options=staticmethod(add_options) + + + def extract_kwargs_from_options(options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return modulation_utils.extract_kwargs_from_options(d8psk_mod.__init__, + ('self', 'fg'), options) + extract_kwargs_from_options=staticmethod(extract_kwargs_from_options) + + +# ///////////////////////////////////////////////////////////////////////////// +# D8PSK demodulator +# +# Differentially coherent detection of differentially encoded 8psk +# ///////////////////////////////////////////////////////////////////////////// + +class d8psk_demod(gr.hier_block): + + def __init__(self, fg, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + costas_alpha=_def_costas_alpha, + gain_mu=_def_gain_mu, + mu=_def_mu, + omega_relative_limit=_def_omega_relative_limit, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log): + """ + Hierarchical block for RRC-filtered DQPSK demodulation + + The input is the complex modulated signal at baseband. + The output is a stream of bits packed 1 bit per byte (LSB) + + @param fg: flow graph + @type fg: flow graph + @param samples_per_symbol: samples per symbol >= 2 + @type samples_per_symbol: float + @param excess_bw: Root-raised cosine filter excess bandwidth + @type excess_bw: float + @param costas_alpha: loop filter gain + @type costas_alphas: float + @param gain_mu: for M&M block + @type gain_mu: float + @param mu: for M&M block + @type mu: float + @param omega_relative_limit: for M&M block + @type omega_relative_limit: float + @param gray_code: Tell modulator to Gray code the bits + @type gray_code: bool + @param verbose: Print information about modulator? + @type verbose: bool + @param debug: Print modualtion data to files? + @type debug: bool + """ + + self._fg = fg + self._samples_per_symbol = samples_per_symbol + self._excess_bw = excess_bw + self._costas_alpha = costas_alpha + self._mm_gain_mu = gain_mu + self._mm_mu = mu + self._mm_omega_relative_limit = omega_relative_limit + self._gray_code = gray_code + + if samples_per_symbol < 2: + raise TypeError, "sbp must be >= 2, is %d" % samples_per_symbol + + arity = pow(2,self.bits_per_symbol()) + + # Automatic gain control + scale = (1.0/16384.0) + self.pre_scaler = gr.multiply_const_cc(scale) # scale the signal from full-range to +-1 + #self.agc = gr.agc_cc(1e-2, 1, 1, 100) + self.agc = gr.agc2_cc(1e-1, 1e-2, 1, 1, 100) + #self.agc = gr.feedforward_agc_cc(16, 1.0) + + # RRC data filter + ntaps = 11 * samples_per_symbol + self.rrc_taps = gr.firdes.root_raised_cosine( + 1.0, # gain + self._samples_per_symbol, # sampling rate + 1.0, # symbol rate + self._excess_bw, # excess bandwidth (roll-off factor) + ntaps) + self.rrc_filter=gr.interp_fir_filter_ccf(1, self.rrc_taps) + + # symbol clock recovery + self._mm_omega = self._samples_per_symbol + self._mm_gain_omega = .25 * self._mm_gain_mu * self._mm_gain_mu + self._costas_beta = 0.25 * self._costas_alpha * self._costas_alpha + fmin = -0.025 + fmax = 0.025 + + self.receiver=gr.mpsk_receiver_cc(arity, 0, + self._costas_alpha, self._costas_beta, + fmin, fmax, + self._mm_mu, self._mm_gain_mu, + self._mm_omega, self._mm_gain_omega, + self._mm_omega_relative_limit) + + # Perform Differential decoding on the constellation + self.diffdec = gr.diff_phasor_cc() + + # find closest constellation point + rot = 1 + rotated_const = map(lambda pt: pt * rot, psk.constellation[arity]) + self.slicer = gr.constellation_decoder_cb(rotated_const, range(arity)) + + if self._gray_code: + self.symbol_mapper = gr.map_bb(psk.gray_to_binary[arity]) + else: + self.symbol_mapper = gr.map_bb(psk.ungray_to_binary[arity]) + + + # unpack the k bit vector into a stream of bits + self.unpack = gr.unpack_k_bits_bb(self.bits_per_symbol()) + + if verbose: + self._print_verbage() + + if log: + self._setup_logging() + + # Connect & Initialize base class + self._fg.connect(self.pre_scaler, self.agc, self.rrc_filter, self.receiver, + self.diffdec, self.slicer, self.symbol_mapper, self.unpack) + gr.hier_block.__init__(self, self._fg, self.pre_scaler, self.unpack) + + 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 3 + bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM + + def _print_verbage(self): + print "\nDemodulator:" + print "bits per symbol: %d" % self.bits_per_symbol() + print "Gray code: %s" % self._gray_code + print "RRC roll-off factor: %.2f" % self._excess_bw + print "Costas Loop alpha: %.2e" % self._costas_alpha + print "Costas Loop beta: %.2e" % self._costas_beta + print "M&M mu: %.2f" % self._mm_mu + print "M&M mu gain: %.2e" % self._mm_gain_mu + print "M&M omega: %.2f" % self._mm_omega + print "M&M omega gain: %.2e" % self._mm_gain_omega + print "M&M omega limit: %.2f" % self._mm_omega_relative_limit + + + def _setup_logging(self): + print "Modulation logging turned on." + self._fg.connect(self.pre_scaler, + gr.file_sink(gr.sizeof_gr_complex, "rx_prescaler.dat")) + self._fg.connect(self.agc, + gr.file_sink(gr.sizeof_gr_complex, "rx_agc.dat")) + self._fg.connect(self.rrc_filter, + gr.file_sink(gr.sizeof_gr_complex, "rx_rrc_filter.dat")) + self._fg.connect(self.receiver, + gr.file_sink(gr.sizeof_gr_complex, "rx_receiver.dat")) + self._fg.connect(self.diffdec, + gr.file_sink(gr.sizeof_gr_complex, "rx_diffdec.dat")) + self._fg.connect(self.slicer, + gr.file_sink(gr.sizeof_char, "rx_slicer.dat")) + self._fg.connect(self.symbol_mapper, + gr.file_sink(gr.sizeof_char, "rx_gray_decoder.dat")) + self._fg.connect(self.unpack, + gr.file_sink(gr.sizeof_char, "rx_unpack.dat")) + + def add_options(parser): + """ + Adds modulation-specific options to the standard parser + """ + parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw, + help="set RRC excess bandwith factor [default=%default] (PSK)") + parser.add_option("", "--no-gray-code", dest="gray_code", + action="store_false", default=_def_gray_code, + help="disable gray coding on modulated bits (PSK)") + parser.add_option("", "--costas-alpha", type="float", default=_def_costas_alpha, + help="set Costas loop alpha value [default=%default] (PSK)") + parser.add_option("", "--gain-mu", type="float", default=_def_gain_mu, + help="set M&M symbol sync loop gain mu value [default=%default] (PSK)") + parser.add_option("", "--mu", type="float", default=_def_mu, + help="set M&M symbol sync loop mu value [default=%default] (PSK)") + add_options=staticmethod(add_options) + + def extract_kwargs_from_options(options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return modulation_utils.extract_kwargs_from_options( + d8psk_demod.__init__, ('self', 'fg'), options) + extract_kwargs_from_options=staticmethod(extract_kwargs_from_options) + + +# +# Add these to the mod/demod registry +# +# NOT READY TO BE USED YET -- ENABLE AT YOUR OWN RISK +#modulation_utils.add_type_1_mod('d8psk', d8psk_mod) +#modulation_utils.add_type_1_demod('d8psk', d8psk_demod) diff --git a/gnuradio-core/src/python/gnuradio/blksimpl/dbpsk.py b/gnuradio-core/src/python/gnuradio/blksimpl/dbpsk.py index 470ab8e2..28fb4266 100644 --- a/gnuradio-core/src/python/gnuradio/blksimpl/dbpsk.py +++ b/gnuradio-core/src/python/gnuradio/blksimpl/dbpsk.py @@ -39,9 +39,9 @@ _def_gray_code = True _def_verbose = False _def_log = False -_def_costas_alpha = None -_def_gain_mu = 0.03 -_def_mu = 0.05 +_def_costas_alpha = 0.1 +_def_gain_mu = None +_def_mu = 0.5 _def_omega_relative_limit = 0.005 @@ -88,7 +88,7 @@ class dbpsk_mod(gr.hier_block): ntaps = 11 * self._samples_per_symbol arity = pow(2,self.bits_per_symbol()) - + # turn bytes into k-bit vectors self.bytes2chunks = \ gr.packed_to_unpacked_bb(self.bits_per_symbol(), gr.GR_MSB_FIRST) @@ -99,18 +99,17 @@ class dbpsk_mod(gr.hier_block): self.symbol_mapper = gr.map_bb(psk.binary_to_ungray[arity]) self.diffenc = gr.diff_encoder_bb(arity) - + self.chunks2symbols = gr.chunks_to_symbols_bc(psk.constellation[arity]) # pulse shaping filter self.rrc_taps = gr.firdes.root_raised_cosine( - self._samples_per_symbol, # gain (samples_per_symbol since we're - # interpolating by samples_per_symbol) - self._samples_per_symbol, # sampling rate - 1.0, # symbol rate - self._excess_bw, # excess bandwidth (roll-off factor) + self._samples_per_symbol, # gain (samples_per_symbol since we're + # interpolating by samples_per_symbol) + self._samples_per_symbol, # sampling rate + 1.0, # symbol rate + self._excess_bw, # excess bandwidth (roll-off factor) ntaps) - self.rrc_filter = gr.interp_fir_filter_ccf(self._samples_per_symbol, self.rrc_taps) @@ -155,22 +154,23 @@ class dbpsk_mod(gr.hier_block): def _print_verbage(self): - print "bits per symbol = %d" % self.bits_per_symbol() - print "Gray code = %s" % self._gray_code - print "RRC roll-off factor = %.2f" % self._excess_bw + print "\nModulator:" + print "bits per symbol: %d" % self.bits_per_symbol() + print "Gray code: %s" % self._gray_code + print "RRC roll-off factor: %.2f" % self._excess_bw def _setup_logging(self): print "Modulation logging turned on." self._fg.connect(self.bytes2chunks, - gr.file_sink(gr.sizeof_char, "bytes2chunks.dat")) + gr.file_sink(gr.sizeof_char, "tx_bytes2chunks.dat")) self._fg.connect(self.symbol_mapper, - gr.file_sink(gr.sizeof_char, "graycoder.dat")) + gr.file_sink(gr.sizeof_char, "tx_graycoder.dat")) self._fg.connect(self.diffenc, - gr.file_sink(gr.sizeof_char, "diffenc.dat")) + gr.file_sink(gr.sizeof_char, "tx_diffenc.dat")) self._fg.connect(self.chunks2symbols, - gr.file_sink(gr.sizeof_gr_complex, "chunks2symbols.dat")) + gr.file_sink(gr.sizeof_gr_complex, "tx_chunks2symbols.dat")) self._fg.connect(self.rrc_filter, - gr.file_sink(gr.sizeof_gr_complex, "rrc_filter.dat")) + gr.file_sink(gr.sizeof_gr_complex, "tx_rrc_filter.dat")) # ///////////////////////////////////////////////////////////////////////////// @@ -223,9 +223,9 @@ class dbpsk_demod(gr.hier_block): self._samples_per_symbol = samples_per_symbol self._excess_bw = excess_bw self._costas_alpha = costas_alpha - self._gain_mu = gain_mu - self._mu = mu - self._omega_relative_limit = omega_relative_limit + self._mm_gain_mu = gain_mu + self._mm_mu = mu + self._mm_omega_relative_limit = omega_relative_limit self._gray_code = gray_code if samples_per_symbol < 2: @@ -237,43 +237,41 @@ class dbpsk_demod(gr.hier_block): scale = (1.0/16384.0) self.pre_scaler = gr.multiply_const_cc(scale) # scale the signal from full-range to +-1 #self.agc = gr.agc2_cc(0.6e-1, 1e-3, 1, 1, 100) - self.agc = gr.feedforward_agc_cc(16, 1.0) - - - # Costas loop (carrier tracking) - # The Costas loop is not needed for BPSK, though it can help. Turn the Costas loop on - # by setting an alpha value not None. - if self._costas_alpha is not None: - costas_order = 2 - beta = .25 * self._costas_alpha * self._costas_alpha - self.costas_loop = gr.costas_loop_cc(self._costas_alpha, beta, 0.002, -0.002, costas_order) + self.agc = gr.feedforward_agc_cc(16, 2.0) # RRC data filter - ntaps = 11 * self._samples_per_symbol + ntaps = 11 * samples_per_symbol self.rrc_taps = gr.firdes.root_raised_cosine( - 1.0, # gain + 1.0, # gain self._samples_per_symbol, # sampling rate 1.0, # symbol rate self._excess_bw, # excess bandwidth (roll-off factor) ntaps) - - self.rrc_filter=gr.fir_filter_ccf(1, self.rrc_taps) + self.rrc_filter=gr.interp_fir_filter_ccf(1, self.rrc_taps) # symbol clock recovery - omega = self._samples_per_symbol - gain_omega = .25 * self._gain_mu * self._gain_mu - self.clock_recovery=gr.clock_recovery_mm_cc(omega, gain_omega, - self._mu, self._gain_mu, - self._omega_relative_limit) + if not self._mm_gain_mu: + self._mm_gain_mu = 0.1 + + self._mm_omega = self._samples_per_symbol + self._mm_gain_omega = .25 * self._mm_gain_mu * self._mm_gain_mu + self._costas_beta = 0.25 * self._costas_alpha * self._costas_alpha + fmin = -0.025 + fmax = 0.025 + + self.receiver=gr.mpsk_receiver_cc(arity, 0, + self._costas_alpha, self._costas_beta, + fmin, fmax, + self._mm_mu, self._mm_gain_mu, + self._mm_omega, self._mm_gain_omega, + self._mm_omega_relative_limit) + + # Do differential decoding based on phase change of symbols + self.diffdec = gr.diff_phasor_cc() # find closest constellation point rot = 1 rotated_const = map(lambda pt: pt * rot, psk.constellation[arity]) - #print "rotated_const =", rotated_const - - self.diffdec = gr.diff_phasor_cc() - #self.diffdec = gr.diff_decoder_bb(arity) - self.slicer = gr.constellation_decoder_cb(rotated_const, range(arity)) if self._gray_code: @@ -291,15 +289,8 @@ class dbpsk_demod(gr.hier_block): self._setup_logging() # Connect and Initialize base class - if self._costas_alpha is not None: # With Costas Loop - self._fg.connect(self.pre_scaler, self.agc, self.costas_loop, - self.rrc_filter, self.clock_recovery, self.diffdec, - self.slicer, self.symbol_mapper, self.unpack) - else: # Without Costas Loop - self._fg.connect(self.pre_scaler, self.agc, - self.rrc_filter, self.clock_recovery, self.diffdec, - self.slicer, self.symbol_mapper, self.unpack) - + self._fg.connect(self.pre_scaler, self.agc, self.rrc_filter, self.receiver, + self.diffdec, self.slicer, self.symbol_mapper, self.unpack) gr.hier_block.__init__(self, self._fg, self.pre_scaler, self.unpack) def samples_per_symbol(self): @@ -310,42 +301,36 @@ class dbpsk_demod(gr.hier_block): bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM def _print_verbage(self): - print "bits per symbol = %d" % self.bits_per_symbol() - print "Gray code = %s" % self._gray_code - print "RRC roll-off factor = %.2f" % self._excess_bw - if self._costas_alpha is not None: - print "Costas Loop alpha = %.5f" % self._costas_alpha - else: - print "Costas Loop is turned off" - print "M&M symbol sync gain = %.5f" % self._gain_mu - print "M&M symbol sync mu = %.5f" % self._mu - print "M&M omega relative limit = %.5f" % self._omega_relative_limit + print "\nDemodulator:" + print "bits per symbol: %d" % self.bits_per_symbol() + print "Gray code: %s" % self._gray_code + print "RRC roll-off factor: %.2f" % self._excess_bw + print "Costas Loop alpha: %.2e" % self._costas_alpha + print "Costas Loop beta: %.2e" % self._costas_beta + print "M&M mu: %.2f" % self._mm_mu + print "M&M mu gain: %.2e" % self._mm_gain_mu + print "M&M omega: %.2f" % self._mm_omega + print "M&M omega gain: %.2e" % self._mm_gain_omega + print "M&M omega limit: %.2f" % self._mm_omega_relative_limit def _setup_logging(self): print "Modulation logging turned on." self._fg.connect(self.pre_scaler, - gr.file_sink(gr.sizeof_gr_complex, "prescaler.dat")) + gr.file_sink(gr.sizeof_gr_complex, "rx_prescaler.dat")) self._fg.connect(self.agc, - gr.file_sink(gr.sizeof_gr_complex, "agc.dat")) - if self._costas_alpha is not None: - self._fg.connect(self.costas_loop, - gr.file_sink(gr.sizeof_gr_complex, "costas_loop.dat")) - self._fg.connect((self.costas_loop,1), - gr.file_sink(gr.sizeof_gr_complex, "costas_error.dat")) + gr.file_sink(gr.sizeof_gr_complex, "rx_agc.dat")) self._fg.connect(self.rrc_filter, - gr.file_sink(gr.sizeof_gr_complex, "rrc_filter.dat")) - self._fg.connect(self.clock_recovery, - gr.file_sink(gr.sizeof_gr_complex, "clock_recovery.dat")) - self._fg.connect((self.clock_recovery,1), - gr.file_sink(gr.sizeof_gr_complex, "clock_recovery_error.dat")) + gr.file_sink(gr.sizeof_gr_complex, "rx_rrc_filter.dat")) + self._fg.connect(self.receiver, + gr.file_sink(gr.sizeof_gr_complex, "rx_receiver.dat")) self._fg.connect(self.diffdec, - gr.file_sink(gr.sizeof_gr_complex, "diffdec.dat")) + gr.file_sink(gr.sizeof_gr_complex, "rx_diffdec.dat")) self._fg.connect(self.slicer, - gr.file_sink(gr.sizeof_char, "slicer.dat")) + gr.file_sink(gr.sizeof_char, "rx_slicer.dat")) self._fg.connect(self.symbol_mapper, - gr.file_sink(gr.sizeof_char, "symbol_mapper.dat")) + gr.file_sink(gr.sizeof_char, "rx_symbol_mapper.dat")) self._fg.connect(self.unpack, - gr.file_sink(gr.sizeof_char, "unpack.dat")) + gr.file_sink(gr.sizeof_char, "rx_unpack.dat")) def add_options(parser): """ @@ -373,7 +358,6 @@ class dbpsk_demod(gr.hier_block): return modulation_utils.extract_kwargs_from_options( dbpsk_demod.__init__, ('self', 'fg'), options) extract_kwargs_from_options=staticmethod(extract_kwargs_from_options) - # # Add these to the mod/demod registry # diff --git a/gnuradio-core/src/python/gnuradio/blksimpl/dqpsk.py b/gnuradio-core/src/python/gnuradio/blksimpl/dqpsk.py index 4332767b..b6701ef9 100644 --- a/gnuradio-core/src/python/gnuradio/blksimpl/dqpsk.py +++ b/gnuradio-core/src/python/gnuradio/blksimpl/dqpsk.py @@ -39,9 +39,9 @@ _def_gray_code = True _def_verbose = False _def_log = False -_def_costas_alpha = None -_def_gain_mu = 0.03 -_def_mu = 0.05 +_def_costas_alpha = 0.15 +_def_gain_mu = None +_def_mu = 0.5 _def_omega_relative_limit = 0.005 @@ -133,22 +133,23 @@ class dqpsk_mod(gr.hier_block): bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM def _print_verbage(self): - print "bits per symbol = %d" % self.bits_per_symbol() - print "Gray code = %s" % self._gray_code - print "RRS roll-off factor = %f" % self._excess_bw + print "\nModulator:" + print "bits per symbol: %d" % self.bits_per_symbol() + print "Gray code: %s" % self._gray_code + print "RRS roll-off factor: %f" % self._excess_bw def _setup_logging(self): print "Modulation logging turned on." self._fg.connect(self.bytes2chunks, - gr.file_sink(gr.sizeof_char, "bytes2chunks.dat")) + gr.file_sink(gr.sizeof_char, "tx_bytes2chunks.dat")) self._fg.connect(self.symbol_mapper, - gr.file_sink(gr.sizeof_char, "graycoder.dat")) + gr.file_sink(gr.sizeof_char, "tx_graycoder.dat")) self._fg.connect(self.diffenc, - gr.file_sink(gr.sizeof_char, "diffenc.dat")) + gr.file_sink(gr.sizeof_char, "tx_diffenc.dat")) self._fg.connect(self.chunks2symbols, - gr.file_sink(gr.sizeof_gr_complex, "chunks2symbols.dat")) + gr.file_sink(gr.sizeof_gr_complex, "tx_chunks2symbols.dat")) self._fg.connect(self.rrc_filter, - gr.file_sink(gr.sizeof_gr_complex, "rrc_filter.dat")) + gr.file_sink(gr.sizeof_gr_complex, "tx_rrc_filter.dat")) def add_options(parser): """ @@ -221,9 +222,9 @@ class dqpsk_demod(gr.hier_block): self._samples_per_symbol = samples_per_symbol self._excess_bw = excess_bw self._costas_alpha = costas_alpha - self._gain_mu = gain_mu - self._mu = mu - self._omega_relative_limit = omega_relative_limit + self._mm_gain_mu = gain_mu + self._mm_mu = mu + self._mm_omega_relative_limit = omega_relative_limit self._gray_code = gray_code if samples_per_symbol < 2: @@ -235,53 +236,47 @@ class dqpsk_demod(gr.hier_block): scale = (1.0/16384.0) self.pre_scaler = gr.multiply_const_cc(scale) # scale the signal from full-range to +-1 #self.agc = gr.agc2_cc(0.6e-1, 1e-3, 1, 1, 100) - self.agc = gr.feedforward_agc_cc(16, 1.0) + self.agc = gr.feedforward_agc_cc(16, 2.0) - # Costas loop (carrier tracking) - if self._costas_alpha is None: # If no alpha value was specified by the user - alpha_dir = {2:0.075, 3:0.09, 4:0.09, 5:0.095, 6:0.10, 7:0.105} - self._costas_alpha = alpha_dir[self._samples_per_symbol] - - costas_order = 4 - # The value of beta is now set to be underdamped; this value can have a huge impact on the - # performance of QPSK. Set to 0.25 for critically damped or higher for underdamped responses. - beta = .35 * self._costas_alpha * self._costas_alpha - self.costas_loop = gr.costas_loop_cc(self._costas_alpha, beta, 0.02, -0.02, costas_order) - # RRC data filter ntaps = 11 * samples_per_symbol self.rrc_taps = gr.firdes.root_raised_cosine( - self._samples_per_symbol, # gain + 1.0, # gain self._samples_per_symbol, # sampling rate 1.0, # symbol rate self._excess_bw, # excess bandwidth (roll-off factor) ntaps) + self.rrc_filter=gr.interp_fir_filter_ccf(1, self.rrc_taps) - self.rrc_filter=gr.fir_filter_ccf(1, self.rrc_taps) - - # symbol clock recovery - omega = self._samples_per_symbol - gain_omega = .25 * self._gain_mu * self._gain_mu - self.clock_recovery=gr.clock_recovery_mm_cc(omega, gain_omega, - self._mu, self._gain_mu, - self._omega_relative_limit) + if not self._mm_gain_mu: + sbs_to_mm = {2: 0.050, 3: 0.075, 4: 0.11, 5: 0.125, 6: 0.15, 7: 0.15} + self._mm_gain_mu = sbs_to_mm[samples_per_symbol] + self._mm_omega = self._samples_per_symbol + self._mm_gain_omega = .25 * self._mm_gain_mu * self._mm_gain_mu + self._costas_beta = 0.25 * self._costas_alpha * self._costas_alpha + fmin = -0.025 + fmax = 0.025 + + self.receiver=gr.mpsk_receiver_cc(arity, pi/4.0, + self._costas_alpha, self._costas_beta, + fmin, fmax, + self._mm_mu, self._mm_gain_mu, + self._mm_omega, self._mm_gain_omega, + self._mm_omega_relative_limit) + + # Perform Differential decoding on the constellation self.diffdec = gr.diff_phasor_cc() - #self.diffdec = gr.diff_decoder_bb(arity) - + # find closest constellation point rot = 1 - #rot = .707 + .707j rotated_const = map(lambda pt: pt * rot, psk.constellation[arity]) - #print "rotated_const = %s" % rotated_const - self.slicer = gr.constellation_decoder_cb(rotated_const, range(arity)) if self._gray_code: self.symbol_mapper = gr.map_bb(psk.gray_to_binary[arity]) else: self.symbol_mapper = gr.map_bb(psk.ungray_to_binary[arity]) - # unpack the k bit vector into a stream of bits self.unpack = gr.unpack_k_bits_bb(self.bits_per_symbol()) @@ -293,10 +288,8 @@ class dqpsk_demod(gr.hier_block): self._setup_logging() # Connect & Initialize base class - self._fg.connect(self.pre_scaler, self.agc, self.costas_loop, - self.rrc_filter, self.clock_recovery, - self.diffdec, self.slicer, self.symbol_mapper, - self.unpack) + self._fg.connect(self.pre_scaler, self.agc, self.rrc_filter, self.receiver, + self.diffdec, self.slicer, self.symbol_mapper, self.unpack) gr.hier_block.__init__(self, self._fg, self.pre_scaler, self.unpack) def samples_per_symbol(self): @@ -307,39 +300,36 @@ class dqpsk_demod(gr.hier_block): bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM def _print_verbage(self): - print "bits per symbol = %d" % self.bits_per_symbol() - print "Gray code = %s" % self._gray_code - print "RRC roll-off factor = %.2f" % self._excess_bw - print "Costas Loop alpha = %.5f" % self._costas_alpha - print "M&M symbol sync gain = %.5f" % self._gain_mu - print "M&M symbol sync mu = %.5f" % self._mu - print "M&M omega relative limit = %.5f" % self._omega_relative_limit - + print "\nDemodulator:" + print "bits per symbol: %d" % self.bits_per_symbol() + print "Gray code: %s" % self._gray_code + print "RRC roll-off factor: %.2f" % self._excess_bw + print "Costas Loop alpha: %.2e" % self._costas_alpha + print "Costas Loop beta: %.2e" % self._costas_beta + print "M&M mu: %.2f" % self._mm_mu + print "M&M mu gain: %.2e" % self._mm_gain_mu + print "M&M omega: %.2f" % self._mm_omega + print "M&M omega gain: %.2e" % self._mm_gain_omega + print "M&M omega limit: %.2f" % self._mm_omega_relative_limit def _setup_logging(self): print "Modulation logging turned on." self._fg.connect(self.pre_scaler, - gr.file_sink(gr.sizeof_gr_complex, "prescaler.dat")) + gr.file_sink(gr.sizeof_gr_complex, "rx_prescaler.dat")) self._fg.connect(self.agc, - gr.file_sink(gr.sizeof_gr_complex, "agc.dat")) - self._fg.connect(self.costas_loop, - gr.file_sink(gr.sizeof_gr_complex, "costas_loop.dat")) - self._fg.connect((self.costas_loop,1), - gr.file_sink(gr.sizeof_gr_complex, "costas_error.dat")) + gr.file_sink(gr.sizeof_gr_complex, "rx_agc.dat")) self._fg.connect(self.rrc_filter, - gr.file_sink(gr.sizeof_gr_complex, "rrc_filter.dat")) - self._fg.connect(self.clock_recovery, - gr.file_sink(gr.sizeof_gr_complex, "clock_recovery.dat")) - self._fg.connect((self.clock_recovery,1), - gr.file_sink(gr.sizeof_gr_complex, "clock_recovery_error.dat")) + gr.file_sink(gr.sizeof_gr_complex, "rx_rrc_filter.dat")) + self._fg.connect(self.receiver, + gr.file_sink(gr.sizeof_gr_complex, "rx_receiver.dat")) self._fg.connect(self.diffdec, - gr.file_sink(gr.sizeof_gr_complex, "diffdec.dat")) + gr.file_sink(gr.sizeof_gr_complex, "rx_diffdec.dat")) self._fg.connect(self.slicer, - gr.file_sink(gr.sizeof_char, "slicer.dat")) + gr.file_sink(gr.sizeof_char, "rx_slicer.dat")) self._fg.connect(self.symbol_mapper, - gr.file_sink(gr.sizeof_char, "gray_decoder.dat")) + gr.file_sink(gr.sizeof_char, "rx_gray_decoder.dat")) self._fg.connect(self.unpack, - gr.file_sink(gr.sizeof_char, "unpack.dat")) + gr.file_sink(gr.sizeof_char, "rx_unpack.dat")) def add_options(parser): """ @@ -350,7 +340,7 @@ class dqpsk_demod(gr.hier_block): parser.add_option("", "--no-gray-code", dest="gray_code", action="store_false", default=_def_gray_code, help="disable gray coding on modulated bits (PSK)") - parser.add_option("", "--costas-alpha", type="float", default=None, + parser.add_option("", "--costas-alpha", type="float", default=_def_costas_alpha, help="set Costas loop alpha value [default=%default] (PSK)") parser.add_option("", "--gain-mu", type="float", default=_def_gain_mu, help="set M&M symbol sync loop gain mu value [default=%default] (PSK)") diff --git a/gnuradio-core/src/python/gnuradio/blksimpl/gmsk.py b/gnuradio-core/src/python/gnuradio/blksimpl/gmsk.py index 7a77a84e..29bf8e14 100644 --- a/gnuradio-core/src/python/gnuradio/blksimpl/gmsk.py +++ b/gnuradio-core/src/python/gnuradio/blksimpl/gmsk.py @@ -37,7 +37,7 @@ _def_bt = 0.35 _def_verbose = False _def_log = False -_def_gain_mu = 0.05 +_def_gain_mu = None _def_mu = 0.5 _def_freq_error = 0.0 _def_omega_relative_limit = 0.005 @@ -208,6 +208,9 @@ class gmsk_demod(gr.hier_block): self._omega = samples_per_symbol*(1+self._freq_error) + if not self._gain_mu: + self._gain_mu = 0.175 + self._gain_omega = .25 * self._gain_mu * self._gain_mu # critically damped # Demodulate FM diff --git a/gnuradio-core/src/python/gnuradio/blksimpl/psk.py b/gnuradio-core/src/python/gnuradio/blksimpl/psk.py index fdb6c9e6..58677b29 100644 --- a/gnuradio-core/src/python/gnuradio/blksimpl/psk.py +++ b/gnuradio-core/src/python/gnuradio/blksimpl/psk.py @@ -19,12 +19,33 @@ # Boston, MA 02110-1301, USA. # -from math import pi, sqrt -import cmath +from math import pi, sqrt, log10 +import math, cmath +# The following algorithm generates Gray coded constellations for M-PSK for M=[2,4,8] +def make_gray_constellation(m): + # number of bits/symbol (log2(M)) + k = int(log10(m) / log10(2.0)) + + coeff = 1 + const_map = [] + bits = [0]*3 + for i in range(m): + # get a vector of the k bits to use in this mapping + bits[3-k:3] = [((i&(0x01 << k-j-1)) >> k-j-1) for j in range(k)] + + theta = -(2*bits[0]-1)*(2*pi/m)*(bits[0]+abs(bits[1]-bits[2])+2*bits[1]) + re = math.cos(theta) + im = math.sin(theta) + const_map.append(complex(re, im)) # plug it into the constellation + + # return the constellation; by default, it is normalized + return const_map + +# This makes a constellation that increments around the unit circle def make_constellation(m): return [cmath.exp(i * 2 * pi / m * 1j) for i in range(m)] - + # Common definition of constellations for Tx and Rx constellation = { 2 : make_constellation(2), # BPSK @@ -35,18 +56,18 @@ constellation = { # ----------------------- # Do Gray code # ----------------------- -# binary to gray coding +# binary to gray coding -- constellation does Gray coding binary_to_gray = { - 2 : (0, 1), - 4 : (0, 1, 3, 2), - 8 : (0, 1, 3, 2, 7, 6, 4, 5) + 2 : range(2), + 4 : [0,1,3,2], + 8 : [0, 1, 3, 2, 7, 6, 4, 5] } - + # gray to binary gray_to_binary = { - 2 : (0, 1), - 4 : (0, 1, 3, 2), - 8 : (0, 1, 3, 2, 6, 7, 5, 4) + 2 : range(2), + 4 : [0,1,3,2], + 8 : [0, 1, 3, 2, 6, 7, 5, 4] } # ----------------------- @@ -54,14 +75,14 @@ gray_to_binary = { # ----------------------- # identity mapping binary_to_ungray = { - 2 : (0, 1), - 4 : (0, 1, 2, 3), - 8 : (0, 1, 2, 3, 4, 5, 6, 7) + 2 : range(2), + 4 : range(4), + 8 : range(8) } - + # identity mapping ungray_to_binary = { - 2 : (0, 1), - 4 : (0, 1, 2, 3), - 8 : (0, 1, 2, 3, 4, 5, 6, 7) + 2 : range(2), + 4 : range(4), + 8 : range(8) } diff --git a/gnuradio-core/src/python/gnuradio/blksimpl/qam.py b/gnuradio-core/src/python/gnuradio/blksimpl/qam.py new file mode 100644 index 00000000..1bf9ad72 --- /dev/null +++ b/gnuradio-core/src/python/gnuradio/blksimpl/qam.py @@ -0,0 +1,113 @@ +# +# Copyright 2005,2006 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 2, 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 math import pi, sqrt +import math + +# These constellations are generated for Gray coding when symbos [1, ..., m] are used +# Mapping to Gray coding is therefore unnecessary + +def make_constellation(m): + # number of bits/symbol (log2(M)) + k = int(math.log10(m) / math.log10(2.0)) + + coeff = 1 + const_map = [] + for i in range(m): + a = (i&(0x01 << k-1)) >> k-1 + b = (i&(0x01 << k-2)) >> k-2 + bits_i = [((i&(0x01 << k-j-1)) >> k-j-1) for j in range(2, k, 2)] + bits_q = [((i&(0x01 << k-j-1)) >> k-j-1) for j in range(3, k, 2)] + + ss = 0 + ll = len(bits_i) + for ii in range(ll): + rr = 0 + for jj in range(ll-ii): + rr = abs(bits_i[jj] - rr) + ss += rr*pow(2.0, ii+1) + re = (2*a-1)*(ss+1) + + ss = 0 + ll = len(bits_q) + for ii in range(ll): + rr = 0 + for jj in range(ll-ii): + rr = abs(bits_q[jj] - rr) + ss += rr*pow(2.0, ii+1) + im = (2*b-1)*(ss+1) + + a = max(re, im) + if a > coeff: + coeff = a + const_map.append(complex(re, im)) + + norm_map = [complex(i.real/coeff, i.imag/coeff) for i in const_map] + return norm_map + +# Common definition of constellations for Tx and Rx +constellation = { + 4 : make_constellation(4), # QAM4 (QPSK) + 8 : make_constellation(8), # QAM8 + 16: make_constellation(16), # QAM16 + 64: make_constellation(64), # QAM64 + 256: make_constellation(256) # QAM256 + } + +# ----------------------- +# Do Gray code +# ----------------------- +# binary to gray coding +binary_to_gray = { + 4 : range(4), + 8 : range(8), + 16: range(16), + 64: range(64), + 256: range(256) + } + +# gray to binary +gray_to_binary = { + 4 : range(4), + 8 : range(8), + 16: range(16), + 64: range(64), + 256: range(256) + } + +# ----------------------- +# Don't Gray code +# ----------------------- +# identity mapping +binary_to_ungray = { + 4 : range(4), + 8 : range(8), + 16: range(16), + 64: range(64) + } + +# identity mapping +ungray_to_binary = { + 4 : range(4), + 8 : range(8), + 16: range(16), + 64: range(64) + } diff --git a/gnuradio-core/src/python/gnuradio/blksimpl/qam16.py b/gnuradio-core/src/python/gnuradio/blksimpl/qam16.py new file mode 100644 index 00000000..c04a2874 --- /dev/null +++ b/gnuradio-core/src/python/gnuradio/blksimpl/qam16.py @@ -0,0 +1,206 @@ +# +# Copyright 2005,2006 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 2, 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 + +""" +QAM16 modulation and demodulation. +""" + +from gnuradio import gr, gru, modulation_utils +from math import pi, sqrt +import qam +import cmath +import Numeric +from pprint import pprint + +# default values (used in __init__ and add_options) +_def_samples_per_symbol = 2 +_def_excess_bw = 0.35 +_def_gray_code = True +_def_verbose = False +_def_log = False + +_def_costas_alpha = None +_def_gain_mu = 0.03 +_def_mu = 0.05 +_def_omega_relative_limit = 0.005 + + +# ///////////////////////////////////////////////////////////////////////////// +# QAM16 modulator +# ///////////////////////////////////////////////////////////////////////////// + +class qam16_mod(gr.hier_block): + + def __init__(self, fg, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log): + + """ + Hierarchical block for RRC-filtered QPSK modulation. + + The input is a byte stream (unsigned char) and the + output is the complex modulated signal at baseband. + + @param fg: flow graph + @type fg: flow graph + @param samples_per_symbol: samples per symbol >= 2 + @type samples_per_symbol: integer + @param excess_bw: Root-raised cosine filter excess bandwidth + @type excess_bw: float + @param gray_code: Tell modulator to Gray code the bits + @type gray_code: bool + @param verbose: Print information about modulator? + @type verbose: bool + @param debug: Print modualtion data to files? + @type debug: bool + """ + + self._fg = fg + self._samples_per_symbol = samples_per_symbol + self._excess_bw = excess_bw + self._gray_code = gray_code + + if not isinstance(samples_per_symbol, int) or samples_per_symbol < 2: + raise TypeError, ("sbp must be an integer >= 2, is %d" % samples_per_symbol) + + ntaps = 11 * samples_per_symbol + + arity = pow(2, self.bits_per_symbol()) + + # turn bytes into k-bit vectors + self.bytes2chunks = \ + gr.packed_to_unpacked_bb(self.bits_per_symbol(), gr.GR_MSB_FIRST) + + if self._gray_code: + self.symbol_mapper = gr.map_bb(qam.binary_to_gray[arity]) + else: + self.symbol_mapper = gr.map_bb(qam.binary_to_ungray[arity]) + + self.diffenc = gr.diff_encoder_bb(arity) + + rot = 1.0 + print "constellation with %d arity" % arity + rotated_const = map(lambda pt: pt * rot, qam.constellation[arity]) + self.chunks2symbols = gr.chunks_to_symbols_bc(rotated_const) + + # pulse shaping filter + self.rrc_taps = gr.firdes.root_raised_cosine( + self._samples_per_symbol, # gain (sps since we're interpolating by sps) + self._samples_per_symbol, # sampling rate + 1.0, # symbol rate + self._excess_bw, # excess bandwidth (roll-off factor) + ntaps) + + self.rrc_filter = gr.interp_fir_filter_ccf(self._samples_per_symbol, self.rrc_taps) + + if verbose: + self._print_verbage() + + if log: + self._setup_logging() + + # Connect & Initialize base class + self._fg.connect(self.bytes2chunks, self.symbol_mapper, self.diffenc, + self.chunks2symbols, self.rrc_filter) + gr.hier_block.__init__(self, self._fg, self.bytes2chunks, self.rrc_filter) + + 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 4 + bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM + + def _print_verbage(self): + print "bits per symbol = %d" % self.bits_per_symbol() + print "Gray code = %s" % self._gray_code + print "RRS roll-off factor = %f" % self._excess_bw + + def _setup_logging(self): + print "Modulation logging turned on." + self._fg.connect(self.bytes2chunks, + gr.file_sink(gr.sizeof_char, "bytes2chunks.dat")) + self._fg.connect(self.symbol_mapper, + gr.file_sink(gr.sizeof_char, "graycoder.dat")) + self._fg.connect(self.diffenc, + gr.file_sink(gr.sizeof_char, "diffenc.dat")) + self._fg.connect(self.chunks2symbols, + gr.file_sink(gr.sizeof_gr_complex, "chunks2symbols.dat")) + self._fg.connect(self.rrc_filter, + gr.file_sink(gr.sizeof_gr_complex, "rrc_filter.dat")) + + def add_options(parser): + """ + Adds QAM modulation-specific options to the standard parser + """ + parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw, + help="set RRC excess bandwith factor [default=%default] (PSK)") + parser.add_option("", "--no-gray-code", dest="gray_code", + action="store_false", default=_def_gray_code, + help="disable gray coding on modulated bits (PSK)") + add_options=staticmethod(add_options) + + + def extract_kwargs_from_options(options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return modulation_utils.extract_kwargs_from_options(qam16_mod.__init__, + ('self', 'fg'), options) + extract_kwargs_from_options=staticmethod(extract_kwargs_from_options) + + +# ///////////////////////////////////////////////////////////////////////////// +# QAM16 demodulator +# +# ///////////////////////////////////////////////////////////////////////////// + +class qam16_demod(gr.hier_block): + + def __init__(self, fg, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + costas_alpha=_def_costas_alpha, + gain_mu=_def_gain_mu, + mu=_def_mu, + omega_relative_limit=_def_omega_relative_limit, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log): + + # do this + pass + + def bits_per_symbol(self=None): # staticmethod that's also callable on an instance + return 4 + bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM + +# +# Add these to the mod/demod registry +# +# NOT READY TO BE USED YET -- ENABLE AT YOUR OWN RISK +#modulation_utils.add_type_1_mod('qam16', qam16_mod) +#modulation_utils.add_type_1_demod('qam16', qam16_demod) diff --git a/gnuradio-core/src/python/gnuradio/blksimpl/qam256.py b/gnuradio-core/src/python/gnuradio/blksimpl/qam256.py new file mode 100644 index 00000000..66d1158a --- /dev/null +++ b/gnuradio-core/src/python/gnuradio/blksimpl/qam256.py @@ -0,0 +1,206 @@ +# +# Copyright 2005,2006 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 2, 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 + +""" +QAM256 modulation and demodulation. +""" + +from gnuradio import gr, gru, modulation_utils +from math import pi, sqrt +import qam +import cmath +import Numeric +from pprint import pprint + +# default values (used in __init__ and add_options) +_def_samples_per_symbol = 2 +_def_excess_bw = 0.35 +_def_gray_code = True +_def_verbose = False +_def_log = False + +_def_costas_alpha = None +_def_gain_mu = 0.03 +_def_mu = 0.05 +_def_omega_relative_limit = 0.005 + + +# ///////////////////////////////////////////////////////////////////////////// +# QAM256 modulator +# ///////////////////////////////////////////////////////////////////////////// + +class qam256_mod(gr.hier_block): + + def __init__(self, fg, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log): + + """ + Hierarchical block for RRC-filtered QPSK modulation. + + The input is a byte stream (unsigned char) and the + output is the complex modulated signal at baseband. + + @param fg: flow graph + @type fg: flow graph + @param samples_per_symbol: samples per symbol >= 2 + @type samples_per_symbol: integer + @param excess_bw: Root-raised cosine filter excess bandwidth + @type excess_bw: float + @param gray_code: Tell modulator to Gray code the bits + @type gray_code: bool + @param verbose: Print information about modulator? + @type verbose: bool + @param debug: Print modualtion data to files? + @type debug: bool + """ + + self._fg = fg + self._samples_per_symbol = samples_per_symbol + self._excess_bw = excess_bw + self._gray_code = gray_code + + if not isinstance(samples_per_symbol, int) or samples_per_symbol < 2: + raise TypeError, ("sbp must be an integer >= 2, is %d" % samples_per_symbol) + + ntaps = 11 * samples_per_symbol + + arity = pow(2, self.bits_per_symbol()) + + # turn bytes into k-bit vectors + self.bytes2chunks = \ + gr.packed_to_unpacked_bb(self.bits_per_symbol(), gr.GR_MSB_FIRST) + + if self._gray_code: + self.symbol_mapper = gr.map_bb(qam.binary_to_gray[arity]) + else: + self.symbol_mapper = gr.map_bb(qam.binary_to_ungray[arity]) + + self.diffenc = gr.diff_encoder_bb(arity) + + rot = 1.0 + print "constellation with %d arity" % arity + rotated_const = map(lambda pt: pt * rot, qam.constellation[arity]) + self.chunks2symbols = gr.chunks_to_symbols_bc(rotated_const) + + # pulse shaping filter + self.rrc_taps = gr.firdes.root_raised_cosine( + self._samples_per_symbol, # gain (sps since we're interpolating by sps) + self._samples_per_symbol, # sampling rate + 1.0, # symbol rate + self._excess_bw, # excess bandwidth (roll-off factor) + ntaps) + + self.rrc_filter = gr.interp_fir_filter_ccf(self._samples_per_symbol, self.rrc_taps) + + if verbose: + self._print_verbage() + + if log: + self._setup_logging() + + # Connect & Initialize base class + self._fg.connect(self.bytes2chunks, self.symbol_mapper, self.diffenc, + self.chunks2symbols, self.rrc_filter) + gr.hier_block.__init__(self, self._fg, self.bytes2chunks, self.rrc_filter) + + 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 8 + bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM + + def _print_verbage(self): + print "bits per symbol = %d" % self.bits_per_symbol() + print "Gray code = %s" % self._gray_code + print "RRS roll-off factor = %f" % self._excess_bw + + def _setup_logging(self): + print "Modulation logging turned on." + self._fg.connect(self.bytes2chunks, + gr.file_sink(gr.sizeof_char, "bytes2chunks.dat")) + self._fg.connect(self.symbol_mapper, + gr.file_sink(gr.sizeof_char, "graycoder.dat")) + self._fg.connect(self.diffenc, + gr.file_sink(gr.sizeof_char, "diffenc.dat")) + self._fg.connect(self.chunks2symbols, + gr.file_sink(gr.sizeof_gr_complex, "chunks2symbols.dat")) + self._fg.connect(self.rrc_filter, + gr.file_sink(gr.sizeof_gr_complex, "rrc_filter.dat")) + + def add_options(parser): + """ + Adds QAM modulation-specific options to the standard parser + """ + parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw, + help="set RRC excess bandwith factor [default=%default] (PSK)") + parser.add_option("", "--no-gray-code", dest="gray_code", + action="store_false", default=_def_gray_code, + help="disable gray coding on modulated bits (PSK)") + add_options=staticmethod(add_options) + + + def extract_kwargs_from_options(options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return modulation_utils.extract_kwargs_from_options(qam256_mod.__init__, + ('self', 'fg'), options) + extract_kwargs_from_options=staticmethod(extract_kwargs_from_options) + + +# ///////////////////////////////////////////////////////////////////////////// +# QAM256 demodulator +# +# ///////////////////////////////////////////////////////////////////////////// + +class qam256_demod(gr.hier_block): + + def __init__(self, fg, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + costas_alpha=_def_costas_alpha, + gain_mu=_def_gain_mu, + mu=_def_mu, + omega_relative_limit=_def_omega_relative_limit, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log): + + # do this + pass + + def bits_per_symbol(self=None): # staticmethod that's also callable on an instance + return 8 + bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM + +# +# Add these to the mod/demod registry +# +# NOT READY TO BE USED YET -- ENABLE AT YOUR OWN RISK +#modulation_utils.add_type_1_mod('qam256', qam256_mod) +#modulation_utils.add_type_1_demod('qam256', qam256_demod) diff --git a/gnuradio-core/src/python/gnuradio/blksimpl/qam64.py b/gnuradio-core/src/python/gnuradio/blksimpl/qam64.py new file mode 100644 index 00000000..cadded6d --- /dev/null +++ b/gnuradio-core/src/python/gnuradio/blksimpl/qam64.py @@ -0,0 +1,206 @@ +# +# Copyright 2005,2006 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 2, 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 + +""" +differential QPSK modulation and demodulation. +""" + +from gnuradio import gr, gru, modulation_utils +from math import pi, sqrt +import qam +import cmath +import Numeric +from pprint import pprint + +# default values (used in __init__ and add_options) +_def_samples_per_symbol = 2 +_def_excess_bw = 0.35 +_def_gray_code = True +_def_verbose = False +_def_log = False + +_def_costas_alpha = None +_def_gain_mu = 0.03 +_def_mu = 0.05 +_def_omega_relative_limit = 0.005 + + +# ///////////////////////////////////////////////////////////////////////////// +# QAM64 modulator +# ///////////////////////////////////////////////////////////////////////////// + +class qam64_mod(gr.hier_block): + + def __init__(self, fg, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log): + + """ + Hierarchical block for RRC-filtered QPSK modulation. + + The input is a byte stream (unsigned char) and the + output is the complex modulated signal at baseband. + + @param fg: flow graph + @type fg: flow graph + @param samples_per_symbol: samples per symbol >= 2 + @type samples_per_symbol: integer + @param excess_bw: Root-raised cosine filter excess bandwidth + @type excess_bw: float + @param gray_code: Tell modulator to Gray code the bits + @type gray_code: bool + @param verbose: Print information about modulator? + @type verbose: bool + @param debug: Print modualtion data to files? + @type debug: bool + """ + + self._fg = fg + self._samples_per_symbol = samples_per_symbol + self._excess_bw = excess_bw + self._gray_code = gray_code + + if not isinstance(samples_per_symbol, int) or samples_per_symbol < 2: + raise TypeError, ("sbp must be an integer >= 2, is %d" % samples_per_symbol) + + ntaps = 11 * samples_per_symbol + + arity = pow(2, self.bits_per_symbol()) + + # turn bytes into k-bit vectors + self.bytes2chunks = \ + gr.packed_to_unpacked_bb(self.bits_per_symbol(), gr.GR_MSB_FIRST) + + if self._gray_code: + self.symbol_mapper = gr.map_bb(qam.binary_to_gray[arity]) + else: + self.symbol_mapper = gr.map_bb(qam.binary_to_ungray[arity]) + + self.diffenc = gr.diff_encoder_bb(arity) + + rot = 1.0 + print "constellation with %d arity" % arity + rotated_const = map(lambda pt: pt * rot, qam.constellation[arity]) + self.chunks2symbols = gr.chunks_to_symbols_bc(rotated_const) + + # pulse shaping filter + self.rrc_taps = gr.firdes.root_raised_cosine( + self._samples_per_symbol, # gain (sps since we're interpolating by sps) + self._samples_per_symbol, # sampling rate + 1.0, # symbol rate + self._excess_bw, # excess bandwidth (roll-off factor) + ntaps) + + self.rrc_filter = gr.interp_fir_filter_ccf(self._samples_per_symbol, self.rrc_taps) + + if verbose: + self._print_verbage() + + if log: + self._setup_logging() + + # Connect & Initialize base class + self._fg.connect(self.bytes2chunks, self.symbol_mapper, self.diffenc, + self.chunks2symbols, self.rrc_filter) + gr.hier_block.__init__(self, self._fg, self.bytes2chunks, self.rrc_filter) + + 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 6 + bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM + + def _print_verbage(self): + print "bits per symbol = %d" % self.bits_per_symbol() + print "Gray code = %s" % self._gray_code + print "RRS roll-off factor = %f" % self._excess_bw + + def _setup_logging(self): + print "Modulation logging turned on." + self._fg.connect(self.bytes2chunks, + gr.file_sink(gr.sizeof_char, "bytes2chunks.dat")) + self._fg.connect(self.symbol_mapper, + gr.file_sink(gr.sizeof_char, "graycoder.dat")) + self._fg.connect(self.diffenc, + gr.file_sink(gr.sizeof_char, "diffenc.dat")) + self._fg.connect(self.chunks2symbols, + gr.file_sink(gr.sizeof_gr_complex, "chunks2symbols.dat")) + self._fg.connect(self.rrc_filter, + gr.file_sink(gr.sizeof_gr_complex, "rrc_filter.dat")) + + def add_options(parser): + """ + Adds QAM modulation-specific options to the standard parser + """ + parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw, + help="set RRC excess bandwith factor [default=%default] (PSK)") + parser.add_option("", "--no-gray-code", dest="gray_code", + action="store_false", default=_def_gray_code, + help="disable gray coding on modulated bits (PSK)") + add_options=staticmethod(add_options) + + + def extract_kwargs_from_options(options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return modulation_utils.extract_kwargs_from_options(qam64_mod.__init__, + ('self', 'fg'), options) + extract_kwargs_from_options=staticmethod(extract_kwargs_from_options) + + +# ///////////////////////////////////////////////////////////////////////////// +# QAM16 demodulator +# +# ///////////////////////////////////////////////////////////////////////////// + +class qam64_demod(gr.hier_block): + + def __init__(self, fg, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + costas_alpha=_def_costas_alpha, + gain_mu=_def_gain_mu, + mu=_def_mu, + omega_relative_limit=_def_omega_relative_limit, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log): + + # do this + pass + + def bits_per_symbol(self=None): # staticmethod that's also callable on an instance + return 6 + bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM + +# +# Add these to the mod/demod registry +# +# NOT READY TO BE USED YET -- ENABLE AT YOUR OWN RISK +#modulation_utils.add_type_1_mod('qam64', qam64_mod) +#modulation_utils.add_type_1_demod('qam16', qam16_demod) diff --git a/gnuradio-core/src/python/gnuradio/blksimpl/qam8.py b/gnuradio-core/src/python/gnuradio/blksimpl/qam8.py new file mode 100644 index 00000000..e1895a4b --- /dev/null +++ b/gnuradio-core/src/python/gnuradio/blksimpl/qam8.py @@ -0,0 +1,206 @@ +# +# Copyright 2005,2006 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 2, 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 + +""" +QAM8 modulation and demodulation. +""" + +from gnuradio import gr, gru, modulation_utils +from math import pi, sqrt +import qam +import cmath +import Numeric +from pprint import pprint + +# default values (used in __init__ and add_options) +_def_samples_per_symbol = 2 +_def_excess_bw = 0.35 +_def_gray_code = True +_def_verbose = False +_def_log = False + +_def_costas_alpha = None +_def_gain_mu = 0.03 +_def_mu = 0.05 +_def_omega_relative_limit = 0.005 + + +# ///////////////////////////////////////////////////////////////////////////// +# QAM8 modulator +# ///////////////////////////////////////////////////////////////////////////// + +class qam8_mod(gr.hier_block): + + def __init__(self, fg, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log): + + """ + Hierarchical block for RRC-filtered QPSK modulation. + + The input is a byte stream (unsigned char) and the + output is the complex modulated signal at baseband. + + @param fg: flow graph + @type fg: flow graph + @param samples_per_symbol: samples per symbol >= 2 + @type samples_per_symbol: integer + @param excess_bw: Root-raised cosine filter excess bandwidth + @type excess_bw: float + @param gray_code: Tell modulator to Gray code the bits + @type gray_code: bool + @param verbose: Print information about modulator? + @type verbose: bool + @param debug: Print modualtion data to files? + @type debug: bool + """ + + self._fg = fg + self._samples_per_symbol = samples_per_symbol + self._excess_bw = excess_bw + self._gray_code = gray_code + + if not isinstance(samples_per_symbol, int) or samples_per_symbol < 2: + raise TypeError, ("sbp must be an integer >= 2, is %d" % samples_per_symbol) + + ntaps = 11 * samples_per_symbol + + arity = pow(2, self.bits_per_symbol()) + + # turn bytes into k-bit vectors + self.bytes2chunks = \ + gr.packed_to_unpacked_bb(self.bits_per_symbol(), gr.GR_MSB_FIRST) + + if self._gray_code: + self.symbol_mapper = gr.map_bb(qam.binary_to_gray[arity]) + else: + self.symbol_mapper = gr.map_bb(qam.binary_to_ungray[arity]) + + self.diffenc = gr.diff_encoder_bb(arity) + + rot = 1.0 + print "constellation with %d arity" % arity + rotated_const = map(lambda pt: pt * rot, qam.constellation[arity]) + self.chunks2symbols = gr.chunks_to_symbols_bc(rotated_const) + + # pulse shaping filter + self.rrc_taps = gr.firdes.root_raised_cosine( + self._samples_per_symbol, # gain (sps since we're interpolating by sps) + self._samples_per_symbol, # sampling rate + 1.0, # symbol rate + self._excess_bw, # excess bandwidth (roll-off factor) + ntaps) + + self.rrc_filter = gr.interp_fir_filter_ccf(self._samples_per_symbol, self.rrc_taps) + + if verbose: + self._print_verbage() + + if log: + self._setup_logging() + + # Connect & Initialize base class + self._fg.connect(self.bytes2chunks, self.symbol_mapper, self.diffenc, + self.chunks2symbols, self.rrc_filter) + gr.hier_block.__init__(self, self._fg, self.bytes2chunks, self.rrc_filter) + + 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 3 + bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM + + def _print_verbage(self): + print "bits per symbol = %d" % self.bits_per_symbol() + print "Gray code = %s" % self._gray_code + print "RRS roll-off factor = %f" % self._excess_bw + + def _setup_logging(self): + print "Modulation logging turned on." + self._fg.connect(self.bytes2chunks, + gr.file_sink(gr.sizeof_char, "bytes2chunks.dat")) + self._fg.connect(self.symbol_mapper, + gr.file_sink(gr.sizeof_char, "graycoder.dat")) + self._fg.connect(self.diffenc, + gr.file_sink(gr.sizeof_char, "diffenc.dat")) + self._fg.connect(self.chunks2symbols, + gr.file_sink(gr.sizeof_gr_complex, "chunks2symbols.dat")) + self._fg.connect(self.rrc_filter, + gr.file_sink(gr.sizeof_gr_complex, "rrc_filter.dat")) + + def add_options(parser): + """ + Adds QAM modulation-specific options to the standard parser + """ + parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw, + help="set RRC excess bandwith factor [default=%default] (PSK)") + parser.add_option("", "--no-gray-code", dest="gray_code", + action="store_false", default=_def_gray_code, + help="disable gray coding on modulated bits (PSK)") + add_options=staticmethod(add_options) + + + def extract_kwargs_from_options(options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return modulation_utils.extract_kwargs_from_options(qam8_mod.__init__, + ('self', 'fg'), options) + extract_kwargs_from_options=staticmethod(extract_kwargs_from_options) + + +# ///////////////////////////////////////////////////////////////////////////// +# QAM8 demodulator +# +# ///////////////////////////////////////////////////////////////////////////// + +class qam8_demod(gr.hier_block): + + def __init__(self, fg, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + costas_alpha=_def_costas_alpha, + gain_mu=_def_gain_mu, + mu=_def_mu, + omega_relative_limit=_def_omega_relative_limit, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log): + + # do this + pass + + def bits_per_symbol(self=None): # staticmethod that's also callable on an instance + return 3 + bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM + +# +# Add these to the mod/demod registry +# +# NOT READY TO BE USED YET -- ENABLE AT YOUR OWN RISK +#modulation_utils.add_type_1_mod('qam8', qam8_mod) +#modulation_utils.add_type_1_demod('qam8', qam8_demod) diff --git a/gnuradio-core/src/python/gnuradio/blksimpl2/Makefile.am b/gnuradio-core/src/python/gnuradio/blksimpl2/Makefile.am new file mode 100644 index 00000000..4852ff3f --- /dev/null +++ b/gnuradio-core/src/python/gnuradio/blksimpl2/Makefile.am @@ -0,0 +1,40 @@ +# +# Copyright 2005 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 2, 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. +# + +include $(top_srcdir)/Makefile.common + +# EXTRA_DIST = run_tests.in +# TESTS = run_tests + +grblkspythondir = $(grpythondir)/blksimpl2 + +grblkspython_PYTHON = \ + __init__.py \ + dbpsk.py \ + dqpsk.py \ + d8psk.py \ + gmsk.py \ + pkt.py \ + psk.py + +noinst_PYTHON = + +CLEANFILES = *.pyc *.pyo diff --git a/gnuradio-core/src/python/gnuradio/blksimpl2/__init__.py b/gnuradio-core/src/python/gnuradio/blksimpl2/__init__.py new file mode 100644 index 00000000..a4917cf6 --- /dev/null +++ b/gnuradio-core/src/python/gnuradio/blksimpl2/__init__.py @@ -0,0 +1 @@ +# make this a package diff --git a/gnuradio-core/src/python/gnuradio/blksimpl2/d8psk.py b/gnuradio-core/src/python/gnuradio/blksimpl2/d8psk.py new file mode 100644 index 00000000..b499b8fa --- /dev/null +++ b/gnuradio-core/src/python/gnuradio/blksimpl2/d8psk.py @@ -0,0 +1,407 @@ +# +# Copyright 2005,2006 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 2, 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 + +""" +differential 8PSK modulation and demodulation. +""" + +from gnuradio import gr, gru, modulation_utils +from math import pi, sqrt +import psk +import cmath +import Numeric +from pprint import pprint + +# default values (used in __init__ and add_options) +_def_samples_per_symbol = 3 +_def_excess_bw = 0.35 +_def_gray_code = True +_def_verbose = False +_def_log = False + +_def_costas_alpha = 0.01 +_def_gain_mu = 0.05 +_def_mu = 0.5 +_def_omega_relative_limit = 0.005 + + +# ///////////////////////////////////////////////////////////////////////////// +# DQPSK modulator +# ///////////////////////////////////////////////////////////////////////////// + +class d8psk_mod(gr.hier_block2): + + def __init__(self, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log): + """ + Hierarchical block for RRC-filtered QPSK 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 symbol >= 2 + @type samples_per_symbol: integer + @param excess_bw: Root-raised cosine filter excess bandwidth + @type excess_bw: float + @param gray_code: Tell modulator to Gray code the bits + @type gray_code: bool + @param verbose: Print information about modulator? + @type verbose: bool + @param debug: Print modualtion data to files? + @type debug: bool + """ + + gr.hier_block2.__init__(self, "d8psk_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._excess_bw = excess_bw + self._gray_code = gray_code + + if not isinstance(samples_per_symbol, int) or samples_per_symbol < 2: + raise TypeError, ("sbp must be an integer >= 2, is %d" % samples_per_symbol) + + ntaps = 11 * samples_per_symbol + + arity = pow(2,self.bits_per_symbol()) + + # turn bytes into k-bit vectors + self.bytes2chunks = \ + gr.packed_to_unpacked_bb(self.bits_per_symbol(), gr.GR_MSB_FIRST) + + if self._gray_code: + self.symbol_mapper = gr.map_bb(psk.binary_to_gray[arity]) + else: + self.symbol_mapper = gr.map_bb(psk.binary_to_ungray[arity]) + + self.diffenc = gr.diff_encoder_bb(arity) + + rot = 1 + rotated_const = map(lambda pt: pt * rot, psk.constellation[arity]) + self.chunks2symbols = gr.chunks_to_symbols_bc(rotated_const) + + # pulse shaping filter + self.rrc_taps = gr.firdes.root_raised_cosine( + self._samples_per_symbol, # gain (sps since we're interpolating by sps) + self._samples_per_symbol, # sampling rate + 1.0, # symbol rate + self._excess_bw, # excess bandwidth (roll-off factor) + ntaps) + + self.rrc_filter = gr.interp_fir_filter_ccf(self._samples_per_symbol, self.rrc_taps) + + # Define components from objects + self.define_component("bytes2chunks", self.bytes2chunks) + self.define_component("symbol_mapper", self.symbol_mapper) +# self.define_component("diffenc", self.diffenc) + self.define_component("chunks2symbols", self.chunks2symbols) + self.define_component("rrc_filter", self.rrc_filter) + + # Connect components + self.connect("self", 0, "bytes2chunks", 0) + self.connect("bytes2chunks", 0, "symbol_mapper", 0) +# self.connect("symbol_mapper", 0, "diffenc", 0) +# self.connect("diffenc", 0, "chunks2symbols", 0) + self.connect("symbol_mapper", 0, "chunks2symbols", 0) + self.connect("chunks2symbols", 0, "rrc_filter", 0) + self.connect("rrc_filter", 0, "self", 0) + + 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 3 + bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM + + def _print_verbage(self): + print "\nModulator:" + print "bits per symbol = %d" % self.bits_per_symbol() + print "Gray code = %s" % self._gray_code + print "RS roll-off factor = %f" % self._excess_bw + + def _setup_logging(self): + print "Modulation logging turned on." + self.define_component("bytes2chunks_dat", gr.file_sink(gr.sizeof_char, "tx_bytes2chunks.dat")) + self.define_component("symbol_mapper_dat", gr.file_sink(gr.sizeof_char, "tx_symbol_mapper.dat")) +# self.define_component("diffenc_dat", gr.file_sink(gr.sizeof_char, "tx_diffenc.dat")) + self.define_component("chunks2symbols_dat", gr.file_sink(gr.sizeof_gr_complex, "tx_chunks2symbols.dat")) + self.define_component("rrc_filter_dat", gr.file_sink(gr.sizeof_gr_complex, "tx_rrc_filter.dat")) + + self.connect("bytes2chunks", 0, "bytes2chunks_dat", 0) + self.connect("symbol_mapper", 0, "symbol_mapper_dat", 0) +# self.connect("diffenc", 0, "diffenc_dat", 0) + self.connect("chunks2symbols", 0, "chunks2symbols_dat", 0) + self.connect("rrc_filter", 0, "rrc_filter_dat", 0) + + def add_options(parser): + """ + Adds 8PSK modulation-specific options to the standard parser + """ + parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw, + help="set RRC excess bandwith factor [default=%default] (PSK)") + parser.add_option("", "--no-gray-code", dest="gray_code", + action="store_false", default=_def_gray_code, + help="disable gray coding on modulated bits (PSK)") + add_options=staticmethod(add_options) + + + def extract_kwargs_from_options(options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return modulation_utils.extract_kwargs_from_options(d8psk_mod.__init__, + ('self', 'fg'), options) + extract_kwargs_from_options=staticmethod(extract_kwargs_from_options) + + +# ///////////////////////////////////////////////////////////////////////////// +# D8PSK demodulator +# +# Differentially coherent detection of differentially encoded 8psk +# ///////////////////////////////////////////////////////////////////////////// + +WITH_SYNC = False +class d8psk_demod(gr.hier_block2): + + def __init__(self, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + costas_alpha=_def_costas_alpha, + gain_mu=_def_gain_mu, + mu=_def_mu, + omega_relative_limit=_def_omega_relative_limit, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log): + """ + Hierarchical block for RRC-filtered DQPSK demodulation + + The input is the complex modulated signal at baseband. + The output is a stream of bits packed 1 bit per byte (LSB) + + @param samples_per_symbol: samples per symbol >= 2 + @type samples_per_symbol: float + @param excess_bw: Root-raised cosine filter excess bandwidth + @type excess_bw: float + @param costas_alpha: loop filter gain + @type costas_alphas: float + @param gain_mu: for M&M block + @type gain_mu: float + @param mu: for M&M block + @type mu: float + @param omega_relative_limit: for M&M block + @type omega_relative_limit: float + @param gray_code: Tell modulator to Gray code the bits + @type gray_code: bool + @param verbose: Print information about modulator? + @type verbose: bool + @param debug: Print modualtion data to files? + @type debug: bool + """ + + gr.hier_block2.__init__(self, "d8psk_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._excess_bw = excess_bw + self._costas_alpha = costas_alpha + self._mm_gain_mu = gain_mu + self._mm_mu = mu + self._mm_omega_relative_limit = omega_relative_limit + self._gray_code = gray_code + + if samples_per_symbol < 2: + raise TypeError, "sbp must be >= 2, is %d" % samples_per_symbol + + arity = pow(2,self.bits_per_symbol()) + + # Automatic gain control + scale = (1.0/16384.0) + self.pre_scaler = gr.multiply_const_cc(scale) # scale the signal from full-range to +-1 + #self.agc = gr.agc2_cc(0.6e-1, 1e-3, 1, 1, 100) + self.agc = gr.feedforward_agc_cc(16, 1.0) + + # RRC data filter + ntaps = 11 * samples_per_symbol + self.rrc_taps = gr.firdes.root_raised_cosine( + 1.0, # gain + self._samples_per_symbol, # sampling rate + 1.0, # symbol rate + self._excess_bw, # excess bandwidth (roll-off factor) + ntaps) + self.rrc_filter=gr.interp_fir_filter_ccf(1, self.rrc_taps) + + # symbol clock recovery + self._mm_omega = self._samples_per_symbol + self._mm_gain_omega = .25 * self._mm_gain_mu * self._mm_gain_mu + self._costas_beta = 0.25 * self._costas_alpha * self._costas_alpha + fmin = -0.05 + fmax = 0.05 + + self.receiver=gr.mpsk_receiver_cc(arity, 0, + self._costas_alpha, self._costas_beta, + fmin, fmax, + self._mm_mu, self._mm_gain_mu, + self._mm_omega, self._mm_gain_omega, + self._mm_omega_relative_limit) + + #self.diffdec = gr.diff_decoder_bb(arity) + + # find closest constellation point + rot = 1 + rotated_const = map(lambda pt: pt * rot, psk.constellation[arity]) + self.slicer = gr.constellation_decoder_cb(rotated_const, range(arity)) + + if self._gray_code: + self.symbol_mapper = gr.map_bb(psk.gray_to_binary[arity]) + else: + self.symbol_mapper = gr.map_bb(psk.ungray_to_binary[arity]) + + + # unpack the k bit vector into a stream of bits + self.unpack = gr.unpack_k_bits_bb(self.bits_per_symbol()) + + + # Define components + self.define_component("pre_scaler", self.pre_scaler) + self.define_component("agc", self.agc) + self.define_component("rrc_filter", self.rrc_filter) + self.define_component("receiver", self.receiver) + self.define_component("slicer", self.slicer) + #self.define_component("diffdec", self.diffdec) + self.define_component("symbol_mapper", self.symbol_mapper) + self.define_component("unpack", self.unpack) + + # Connect and Initialize base class + self.connect("self", 0, "pre_scaler", 0) + self.connect("pre_scaler", 0, "agc", 0) + self.connect("agc", 0, "rrc_filter", 0) + self.connect("rrc_filter", 0, "receiver", 0) + self.connect("receiver", 0, "slicer", 0) + #self.connect("slicer", 0, "diffdec", 0) + #self.connect("diffdec", 0, "symbol_mapper", 0) + self.connect("slicer", 0, "symbol_mapper", 0) + self.connect("symbol_mapper", 0, "unpack", 0) + self.connect("unpack", 0, "self", 0) + + 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 3 + bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM + + def _print_verbage(self): + print "\nDemodulator:" + print "bits per symbol: %d" % self.bits_per_symbol() + print "Gray code: %s" % self._gray_code + print "RRC roll-off factor: %.2f" % self._excess_bw + print "Costas Loop alpha: %.2e" % self._costas_alpha + print "Costas Loop beta: %.2e" % self._costas_beta + print "M&M mu: %.2f" % self._mm_mu + print "M&M mu gain: %.2e" % self._mm_gain_mu + print "M&M omega: %.2f" % self._mm_omega + print "M&M omega gain: %.2e" % self._mm_gain_omega + print "M&M omega limit: %.2f" % self._mm_omega_relative_limit + + + def _setup_logging(self): + print "Demodulation logging turned on." + self.define_component("prescaler_dat", + gr.file_sink(gr.sizeof_gr_complex, "rx_prescaler.dat")) + self.define_component("agc_dat", + gr.file_sink(gr.sizeof_gr_complex, "rx_agc.dat")) + self.define_component("rrc_filter_dat", + gr.file_sink(gr.sizeof_gr_complex, "rx_rrc_filter.dat")) + self.define_component("receiver_dat", + gr.file_sink(gr.sizeof_gr_complex, "rx_receiver.dat")) + self.define_component("slicer_dat", + gr.file_sink(gr.sizeof_char, "rx_slicer.dat")) +# self.define_component("diffdec_dat", +# gr.file_sink(gr.sizeof_char, "rx_diffdec.dat")) + self.define_component("symbol_mapper_dat", + gr.file_sink(gr.sizeof_char, "rx_symbol_mapper.dat")) + self.define_component("unpack_dat", + gr.file_sink(gr.sizeof_char, "rx_unpack.dat")) + + self.connect("pre_scaler", 0, "prescaler_dat", 0) + self.connect("agc", 0, "agc_dat", 0) + self.connect("rrc_filter", 0, "rrc_filter_dat", 0) + self.connect("receiver", 0, "receiver_dat", 0) + self.connect("slicer", 0, "slicer_dat", 0) +# self.connect("diffdec", 0, "diffdec_dat", 0) + self.connect("symbol_mapper", 0, "symbol_mapper_dat", 0) + self.connect("unpack", 0, "unpack_dat", 0) + + def add_options(parser): + """ + Adds modulation-specific options to the standard parser + """ + parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw, + help="set RRC excess bandwith factor [default=%default] (PSK)") + parser.add_option("", "--no-gray-code", dest="gray_code", + action="store_false", default=_def_gray_code, + help="disable gray coding on modulated bits (PSK)") + parser.add_option("", "--costas-alpha", type="float", default=None, + help="set Costas loop alpha value [default=%default] (PSK)") + parser.add_option("", "--gain-mu", type="float", default=_def_gain_mu, + help="set M&M symbol sync loop gain mu value [default=%default] (PSK)") + parser.add_option("", "--mu", type="float", default=_def_mu, + help="set M&M symbol sync loop mu value [default=%default] (PSK)") + add_options=staticmethod(add_options) + + def extract_kwargs_from_options(options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return modulation_utils.extract_kwargs_from_options( + d8psk_demod.__init__, ('self', 'fg'), options) + extract_kwargs_from_options=staticmethod(extract_kwargs_from_options) + + +# +# Add these to the mod/demod registry +# +# NOT READY TO BE USED YET -- ENABLE AT YOUR OWN RISK +#modulation_utils.add_type_1_mod('d8psk', d8psk_mod) +#modulation_utils.add_type_1_demod('d8psk', d8psk_demod) diff --git a/gnuradio-core/src/python/gnuradio/blksimpl2/dbpsk.py b/gnuradio-core/src/python/gnuradio/blksimpl2/dbpsk.py new file mode 100644 index 00000000..36a2ea8a --- /dev/null +++ b/gnuradio-core/src/python/gnuradio/blksimpl2/dbpsk.py @@ -0,0 +1,404 @@ +# +# Copyright 2005,2006 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 2, 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 + +""" +differential BPSK modulation and demodulation. +""" + +from gnuradio import gr, gru, modulation_utils +from math import pi, sqrt +import psk +import cmath +import Numeric +from pprint import pprint + +# default values (used in __init__ and add_options) +_def_samples_per_symbol = 2 +_def_excess_bw = 0.35 +_def_gray_code = True +_def_verbose = False +_def_log = False + +_def_costas_alpha = 0.15 +_def_gain_mu = 0.1 +_def_mu = 0.5 +_def_omega_relative_limit = 0.005 + + +# ///////////////////////////////////////////////////////////////////////////// +# DBPSK modulator +# ///////////////////////////////////////////////////////////////////////////// + +class dbpsk_mod(gr.hier_block2): + def __init__(self, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log): + """ + Hierarchical block for RRC-filtered differential BPSK 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 excess_bw: Root-raised cosine filter excess bandwidth + @type excess_bw: float + @param gray_code: Tell modulator to Gray code the bits + @type gray_code: bool + @param verbose: Print information about modulator? + @type verbose: bool + @param log: Log modulation data to files? + @type log: bool + """ + + gr.hier_block2.__init__(self, "dbpsk_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._excess_bw = excess_bw + self._gray_code = gray_code + + if not isinstance(self._samples_per_symbol, int) or self._samples_per_symbol < 2: + raise TypeError, ("sbp must be an integer >= 2, is %d" % self._samples_per_symbol) + + ntaps = 11 * self._samples_per_symbol + + arity = pow(2,self.bits_per_symbol()) + + # turn bytes into k-bit vectors + self.bytes2chunks = \ + gr.packed_to_unpacked_bb(self.bits_per_symbol(), gr.GR_MSB_FIRST) + + if self._gray_code: + self.symbol_mapper = gr.map_bb(psk.binary_to_gray[arity]) + else: + self.symbol_mapper = gr.map_bb(psk.binary_to_ungray[arity]) + + self.diffenc = gr.diff_encoder_bb(arity) + + self.chunks2symbols = gr.chunks_to_symbols_bc(psk.constellation[arity]) + + # pulse shaping filter + self.rrc_taps = gr.firdes.root_raised_cosine( + self._samples_per_symbol, # gain (samples_per_symbol since we're + # interpolating by samples_per_symbol) + self._samples_per_symbol, # sampling rate + 1.0, # symbol rate + self._excess_bw, # excess bandwidth (roll-off factor) + ntaps) + self.rrc_filter = gr.interp_fir_filter_ccf(self._samples_per_symbol, + self.rrc_taps) + + self.define_component("bytes2chunks", self.bytes2chunks) + self.define_component("symbol_mapper", self.symbol_mapper) + self.define_component("diffenc", self.diffenc) + self.define_component("chunks2symbols", self.chunks2symbols) + self.define_component("rrc_filter", self.rrc_filter) + + # Connect components + self.connect("self", 0, "bytes2chunks", 0) + self.connect("bytes2chunks", 0, "symbol_mapper", 0) + self.connect("symbol_mapper", 0, "diffenc", 0) + self.connect("diffenc", 0, "chunks2symbols", 0) + self.connect("chunks2symbols", 0, "rrc_filter", 0) + self.connect("rrc_filter", 0, "self", 0) + + 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): # static method that's also callable on an instance + return 1 + bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM + + def add_options(parser): + """ + Adds DBPSK modulation-specific options to the standard parser + """ + parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw, + help="set RRC excess bandwith factor [default=%default]") + parser.add_option("", "--no-gray-code", dest="gray_code", + action="store_false", default=True, + help="disable gray coding on modulated bits (PSK)") + add_options=staticmethod(add_options) + + def extract_kwargs_from_options(options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return modulation_utils.extract_kwargs_from_options(dbpsk_mod.__init__, + ('self', 'fg'), options) + extract_kwargs_from_options=staticmethod(extract_kwargs_from_options) + + + def _print_verbage(self): + print "\nModulator:" + print "bits per symbol = %d" % self.bits_per_symbol() + print "Gray code = %s" % self._gray_code + print "RRC roll-off factor = %.2f" % self._excess_bw + + def _setup_logging(self): + print "Modulation logging turned on." + self.define_component("bytes2chunks_dat", + gr.file_sink(gr.sizeof_char, "tx_bytes2chunks.dat")) + self.define_component("symbol_mapper_dat", + gr.file_sink(gr.sizeof_char, "tx_symbol_mapper.dat")) + self.define_component("diffenc_dat", + gr.file_sink(gr.sizeof_char, "tx_diffenc.dat")) + self.define_component("chunks2symbols_dat", + gr.file_sink(gr.sizeof_gr_complex, "tx_chunks2symbols.dat")) + self.define_component("rrc_filter_dat", + gr.file_sink(gr.sizeof_gr_complex, "tx_rrc_filter.dat")) + + self.connect("bytes2chunks", 0, "bytes2chunks_dat", 0) + self.connect("symbol_mapper", 0, "symbol_mapper_dat", 0) + self.connect("diffenc", 0, "diffenc_dat", 0) + self.connect("chunks2symbols", 0, "chunks2symbols_dat", 0) + self.connect("rrc_filter", 0, "rrc_filter_dat", 0) + + +# ///////////////////////////////////////////////////////////////////////////// +# DBPSK demodulator +# +# Differentially coherent detection of differentially encoded BPSK +# ///////////////////////////////////////////////////////////////////////////// + +class dbpsk_demod(gr.hier_block2): + + def __init__(self, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + costas_alpha=_def_costas_alpha, + gain_mu=_def_gain_mu, + mu=_def_mu, + omega_relative_limit=_def_omega_relative_limit, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log): + """ + Hierarchical block for RRC-filtered differential BPSK demodulation + + The input is the complex modulated signal at baseband. + The output is a stream of bits packed 1 bit per byte (LSB) + + @param samples_per_symbol: samples per symbol >= 2 + @type samples_per_symbol: float + @param excess_bw: Root-raised cosine filter excess bandwidth + @type excess_bw: float + @param costas_alpha: loop filter gain + @type costas_alphas: float + @param gain_mu: for M&M block + @type gain_mu: float + @param mu: for M&M block + @type mu: float + @param omega_relative_limit: for M&M block + @type omega_relative_limit: float + @param gray_code: Tell modulator to Gray code the bits + @type gray_code: bool + @param verbose: Print information about modulator? + @type verbose: bool + @param debug: Print modualtion data to files? + @type debug: bool + """ + + gr.hier_block2.__init__(self, "dbpsk_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._excess_bw = excess_bw + self._costas_alpha = costas_alpha + self._mm_gain_mu = gain_mu + self._mm_mu = mu + self._mm_omega_relative_limit = omega_relative_limit + self._gray_code = gray_code + + if samples_per_symbol < 2: + raise TypeError, "samples_per_symbol must be >= 2, is %r" % (samples_per_symbol,) + + arity = pow(2,self.bits_per_symbol()) + + # Automatic gain control + scale = (1.0/16384.0) + self.pre_scaler = gr.multiply_const_cc(scale) # scale the signal from full-range to +-1 + #self.agc = gr.agc2_cc(0.6e-1, 1e-3, 1, 1, 100) + self.agc = gr.feedforward_agc_cc(16, 1.0) + + + # RRC data filter + ntaps = 11 * samples_per_symbol + self.rrc_taps = gr.firdes.root_raised_cosine( + 1.0, # gain + self._samples_per_symbol, # sampling rate + 1.0, # symbol rate + self._excess_bw, # excess bandwidth (roll-off factor) + ntaps) + self.rrc_filter=gr.interp_fir_filter_ccf(1, self.rrc_taps) + + # symbol clock recovery + self._mm_omega = self._samples_per_symbol + self._mm_gain_omega = .25 * self._mm_gain_mu * self._mm_gain_mu + self._costas_beta = 0.25 * self._costas_alpha * self._costas_alpha + fmin = -0.02 + fmax = 0.02 + + self.receiver=gr.mpsk_receiver_cc(arity, 0, + self._costas_alpha, self._costas_beta, + fmin, fmax, + self._mm_mu, self._mm_gain_mu, + self._mm_omega, self._mm_gain_omega, + self._mm_omega_relative_limit) + + # Using differential decoding + self.diffdec = gr.diff_phasor_cc() + + # find closest constellation point + rot = 1 + rotated_const = map(lambda pt: pt * rot, psk.constellation[arity]) + self.slicer = gr.constellation_decoder_cb(rotated_const, range(arity)) + + if self._gray_code: + self.symbol_mapper = gr.map_bb(psk.gray_to_binary[arity]) + else: + self.symbol_mapper = gr.map_bb(psk.ungray_to_binary[arity]) + + # unpack the k bit vector into a stream of bits + self.unpack = gr.unpack_k_bits_bb(self.bits_per_symbol()) + + # Define components + self.define_component("pre_scaler", self.pre_scaler) + self.define_component("agc", self.agc) + self.define_component("rrc_filter", self.rrc_filter) + self.define_component("receiver", self.receiver) + self.define_component("slicer", self.slicer) + self.define_component("diffdec", self.diffdec) + self.define_component("symbol_mapper", self.symbol_mapper) + self.define_component("unpack", self.unpack) + + # Connect and Initialize base class + self.connect("self", 0, "pre_scaler", 0) + self.connect("pre_scaler", 0, "agc", 0) + self.connect("agc", 0, "rrc_filter", 0) + self.connect("rrc_filter", 0, "receiver", 0) + self.connect("receiver", 0, "diffdec", 0) + self.connect("diffdec", 0, "slicer", 0) + self.connect("slicer", 0, "symbol_mapper", 0) + self.connect("symbol_mapper", 0, "unpack", 0) + self.connect("unpack", 0, "self", 0) + + 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. RTFM + + def _print_verbage(self): + print "\nDemodulator:" + print "bits per symbol: %d" % self.bits_per_symbol() + print "Gray code: %s" % self._gray_code + print "RRC roll-off factor: %.2f" % self._excess_bw + print "Costas Loop alpha: %.2e" % self._costas_alpha + print "Costas Loop beta: %.2e" % self._costas_beta + print "M&M mu: %.2f" % self._mm_mu + print "M&M mu gain: %.2e" % self._mm_gain_mu + print "M&M omega: %.2f" % self._mm_omega + print "M&M omega gain: %.2e" % self._mm_gain_omega + print "M&M omega limit: %.2f" % self._mm_omega_relative_limit + + def _setup_logging(self): + print "Demodulation logging turned on." + self.define_component("prescaler_dat", + gr.file_sink(gr.sizeof_gr_complex, "rx_prescaler.dat")) + self.define_component("agc_dat", + gr.file_sink(gr.sizeof_gr_complex, "rx_agc.dat")) + self.define_component("rrc_filter_dat", + gr.file_sink(gr.sizeof_gr_complex, "rx_rrc_filter.dat")) + self.define_component("receiver_dat", + gr.file_sink(gr.sizeof_gr_complex, "rx_receiver.dat")) + self.define_component("diffdec_dat", + gr.file_sink(gr.sizeof_gr_complex, "rx_diffdec.dat")) + self.define_component("slicer_dat", + gr.file_sink(gr.sizeof_char, "rx_slicer.dat")) + self.define_component("symbol_mapper_dat", + gr.file_sink(gr.sizeof_char, "rx_symbol_mapper.dat")) + self.define_component("unpack_dat", + gr.file_sink(gr.sizeof_char, "rx_unpack.dat")) + + self.connect("pre_scaler", 0, "prescaler_dat", 0) + self.connect("agc", 0, "agc_dat", 0) + self.connect("rrc_filter", 0, "rrc_filter_dat", 0) + self.connect("receiver", 0, "receiver_dat", 0) + self.connect("diffdec", 0, "diffdec_dat", 0) + self.connect("slicer", 0, "slicer_dat", 0) + self.connect("symbol_mapper", 0, "symbol_mapper_dat", 0) + self.connect("unpack", 0, "unpack_dat", 0) + + + def add_options(parser): + """ + Adds DBPSK demodulation-specific options to the standard parser + """ + parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw, + help="set RRC excess bandwith factor [default=%default] (PSK)") + parser.add_option("", "--no-gray-code", dest="gray_code", + action="store_false", default=_def_gray_code, + help="disable gray coding on modulated bits (PSK)") + parser.add_option("", "--costas-alpha", type="float", default=None, + help="set Costas loop alpha value [default=%default] (PSK)") + parser.add_option("", "--gain-mu", type="float", default=_def_gain_mu, + help="set M&M symbol sync loop gain mu value [default=%default] (GMSK/PSK)") + parser.add_option("", "--mu", type="float", default=_def_mu, + help="set M&M symbol sync loop mu value [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)") + add_options=staticmethod(add_options) + + def extract_kwargs_from_options(options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return modulation_utils.extract_kwargs_from_options( + dbpsk_demod.__init__, ('self'), options) + extract_kwargs_from_options=staticmethod(extract_kwargs_from_options) +# +# Add these to the mod/demod registry +# +modulation_utils.add_type_1_mod('dbpsk', dbpsk_mod) +modulation_utils.add_type_1_demod('dbpsk', dbpsk_demod) diff --git a/gnuradio-core/src/python/gnuradio/blksimpl2/dqpsk.py b/gnuradio-core/src/python/gnuradio/blksimpl2/dqpsk.py new file mode 100644 index 00000000..04623c93 --- /dev/null +++ b/gnuradio-core/src/python/gnuradio/blksimpl2/dqpsk.py @@ -0,0 +1,397 @@ +# +# Copyright 2005,2006 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 2, 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 + +""" +differential QPSK modulation and demodulation. +""" + +from gnuradio import gr, gru, modulation_utils +from math import pi, sqrt +import psk +import cmath +import Numeric +from pprint import pprint + +# default values (used in __init__ and add_options) +_def_samples_per_symbol = 2 +_def_excess_bw = 0.35 +_def_gray_code = True +_def_verbose = False +_def_log = False + +_def_costas_alpha = 0.15 +_def_gain_mu = 0.1 +_def_mu = 0.5 +_def_omega_relative_limit = 0.005 + + +# ///////////////////////////////////////////////////////////////////////////// +# DQPSK modulator +# ///////////////////////////////////////////////////////////////////////////// + +class dqpsk_mod(gr.hier_block2): + def __init__(self, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log): + """ + Hierarchical block for RRC-filtered QPSK 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 symbol >= 2 + @type samples_per_symbol: integer + @param excess_bw: Root-raised cosine filter excess bandwidth + @type excess_bw: float + @param gray_code: Tell modulator to Gray code the bits + @type gray_code: bool + @param verbose: Print information about modulator? + @type verbose: bool + @param debug: Print modualtion data to files? + @type debug: bool + """ + + gr.hier_block2.__init__(self, "dqpsk_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._excess_bw = excess_bw + self._gray_code = gray_code + + if not isinstance(samples_per_symbol, int) or samples_per_symbol < 2: + raise TypeError, ("sbp must be an integer >= 2, is %d" % samples_per_symbol) + + ntaps = 11 * samples_per_symbol + + arity = pow(2,self.bits_per_symbol()) + + # turn bytes into k-bit vectors + self.bytes2chunks = \ + gr.packed_to_unpacked_bb(self.bits_per_symbol(), gr.GR_MSB_FIRST) + + if self._gray_code: + self.symbol_mapper = gr.map_bb(psk.binary_to_gray[arity]) + else: + self.symbol_mapper = gr.map_bb(psk.binary_to_ungray[arity]) + + self.diffenc = gr.diff_encoder_bb(arity) + + rot = .707 + .707j + rotated_const = map(lambda pt: pt * rot, psk.constellation[arity]) + self.chunks2symbols = gr.chunks_to_symbols_bc(rotated_const) + + # pulse shaping filter + self.rrc_taps = gr.firdes.root_raised_cosine( + self._samples_per_symbol, # gain (sps since we're interpolating by sps) + self._samples_per_symbol, # sampling rate + 1.0, # symbol rate + self._excess_bw, # excess bandwidth (roll-off factor) + ntaps) + + self.rrc_filter = gr.interp_fir_filter_ccf(self._samples_per_symbol, self.rrc_taps) + + # Define components from objects + self.define_component("bytes2chunks", self.bytes2chunks) + self.define_component("symbol_mapper", self.symbol_mapper) + self.define_component("diffenc", self.diffenc) + self.define_component("chunks2symbols", self.chunks2symbols) + self.define_component("rrc_filter", self.rrc_filter) + + # Connect components + self.connect("self", 0, "bytes2chunks", 0) + self.connect("bytes2chunks", 0, "symbol_mapper", 0) + self.connect("symbol_mapper", 0, "diffenc", 0) + self.connect("diffenc", 0, "chunks2symbols", 0) + self.connect("chunks2symbols", 0, "rrc_filter", 0) + self.connect("rrc_filter", 0, "self", 0) + + 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 2 + bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM + + def _print_verbage(self): + print "\nModulator:" + print "bits per symbol = %d" % self.bits_per_symbol() + print "Gray code = %s" % self._gray_code + print "RRS roll-off factor = %f" % self._excess_bw + + def _setup_logging(self): + print "Modulation logging turned on." + self.define_component("bytes2chunks_dat", gr.file_sink(gr.sizeof_char, "tx_bytes2chunks.dat")) + self.define_component("symbol_mapper_dat", gr.file_sink(gr.sizeof_char, "tx_symbol_mapper.dat")) + self.define_component("diffenc_dat", gr.file_sink(gr.sizeof_char, "tx_diffenc.dat")) + self.define_component("chunks2symbols_dat", gr.file_sink(gr.sizeof_gr_complex, "tx_chunks2symbols.dat")) + self.define_component("rrc_filter_dat", gr.file_sink(gr.sizeof_gr_complex, "tx_rrc_filter.dat")) + + self.connect("bytes2chunks", 0, "bytes2chunks_dat", 0) + self.connect("symbol_mapper", 0, "symbol_mapper_dat", 0) + self.connect("diffenc", 0, "diffenc_dat", 0) + self.connect("chunks2symbols", 0, "chunks2symbols_dat", 0) + self.connect("rrc_filter", 0, "rrc_filter_dat", 0) + + def add_options(parser): + """ + Adds QPSK modulation-specific options to the standard parser + """ + parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw, + help="set RRC excess bandwith factor [default=%default] (PSK)") + parser.add_option("", "--no-gray-code", dest="gray_code", + action="store_false", default=_def_gray_code, + help="disable gray coding on modulated bits (PSK)") + add_options=staticmethod(add_options) + + + def extract_kwargs_from_options(options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return modulation_utils.extract_kwargs_from_options(dqpsk_mod.__init__, + ('self', 'fg'), options) + extract_kwargs_from_options=staticmethod(extract_kwargs_from_options) + + +# ///////////////////////////////////////////////////////////////////////////// +# DQPSK demodulator +# +# Differentially coherent detection of differentially encoded qpsk +# ///////////////////////////////////////////////////////////////////////////// + +class dqpsk_demod(gr.hier_block2): + + def __init__(self, + samples_per_symbol=_def_samples_per_symbol, + excess_bw=_def_excess_bw, + costas_alpha=_def_costas_alpha, + gain_mu=_def_gain_mu, + mu=_def_mu, + omega_relative_limit=_def_omega_relative_limit, + gray_code=_def_gray_code, + verbose=_def_verbose, + log=_def_log): + """ + Hierarchical block for RRC-filtered DQPSK demodulation + + The input is the complex modulated signal at baseband. + The output is a stream of bits packed 1 bit per byte (LSB) + + @param samples_per_symbol: samples per symbol >= 2 + @type samples_per_symbol: float + @param excess_bw: Root-raised cosine filter excess bandwidth + @type excess_bw: float + @param costas_alpha: loop filter gain + @type costas_alphas: float + @param gain_mu: for M&M block + @type gain_mu: float + @param mu: for M&M block + @type mu: float + @param omega_relative_limit: for M&M block + @type omega_relative_limit: float + @param gray_code: Tell modulator to Gray code the bits + @type gray_code: bool + @param verbose: Print information about modulator? + @type verbose: bool + @param debug: Print modualtion data to files? + @type debug: bool + """ + + gr.hier_block2.__init__(self, "dqpsk_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._excess_bw = excess_bw + self._costas_alpha = costas_alpha + self._mm_gain_mu = gain_mu + self._mm_mu = mu + self._mm_omega_relative_limit = omega_relative_limit + self._gray_code = gray_code + + if samples_per_symbol < 2: + raise TypeError, "sbp must be >= 2, is %d" % samples_per_symbol + + arity = pow(2,self.bits_per_symbol()) + + # Automatic gain control + scale = (1.0/16384.0) + self.pre_scaler = gr.multiply_const_cc(scale) # scale the signal from full-range to +-1 + self.agc = gr.feedforward_agc_cc(16, 2.0) + + # RRC data filter + ntaps = 11 * samples_per_symbol + self.rrc_taps = gr.firdes.root_raised_cosine( + 1.0, # gain + self._samples_per_symbol, # sampling rate + 1.0, # symbol rate + self._excess_bw, # excess bandwidth (roll-off factor) + ntaps) + self.rrc_filter=gr.interp_fir_filter_ccf(1, self.rrc_taps) + + # symbol clock recovery + self._mm_omega = self._samples_per_symbol + self._mm_gain_omega = .25 * self._mm_gain_mu * self._mm_gain_mu + self._costas_beta = 0.25 * self._costas_alpha * self._costas_alpha + fmin = -0.01 + fmax = 0.01 + + self.receiver=gr.mpsk_receiver_cc(arity, pi/4.0, + self._costas_alpha, self._costas_beta, + fmin, fmax, + self._mm_mu, self._mm_gain_mu, + self._mm_omega, self._mm_gain_omega, + self._mm_omega_relative_limit) + + # Perform Differential decoding on the constellation + self.diffdec = gr.diff_phasor_cc() + + # find closest constellation point + rot = 1 + rotated_const = map(lambda pt: pt * rot, psk.constellation[arity]) + self.slicer = gr.constellation_decoder_cb(rotated_const, range(arity)) + + if self._gray_code: + self.symbol_mapper = gr.map_bb(psk.gray_to_binary[arity]) + else: + self.symbol_mapper = gr.map_bb(psk.ungray_to_binary[arity]) + + # unpack the k bit vector into a stream of bits + self.unpack = gr.unpack_k_bits_bb(self.bits_per_symbol()) + + # Define components + self.define_component("pre_scaler", self.pre_scaler) + self.define_component("agc", self.agc) + self.define_component("rrc_filter", self.rrc_filter) + self.define_component("receiver", self.receiver) + self.define_component("diffdec", self.diffdec) + self.define_component("slicer", self.slicer) + self.define_component("symbol_mapper", self.symbol_mapper) + self.define_component("unpack", self.unpack) + + # Connect and Initialize base class + self.connect("self", 0, "pre_scaler", 0) + self.connect("pre_scaler", 0, "agc", 0) + self.connect("agc", 0, "rrc_filter", 0) + self.connect("rrc_filter", 0, "receiver", 0) + self.connect("receiver", 0, "diffdec", 0) + self.connect("diffdec", 0, "slicer", 0) + self.connect("slicer", 0, "symbol_mapper", 0) + self.connect("symbol_mapper", 0, "unpack", 0) + self.connect("unpack", 0, "self", 0) + + 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 2 + bits_per_symbol = staticmethod(bits_per_symbol) # make it a static method. RTFM + + def _print_verbage(self): + print "\nDemodulator:" + print "bits per symbol: %d" % self.bits_per_symbol() + print "Gray code: %s" % self._gray_code + print "RRC roll-off factor: %.2f" % self._excess_bw + print "Costas Loop alpha: %.2e" % self._costas_alpha + print "Costas Loop beta: %.2e" % self._costas_beta + print "M&M mu: %.2f" % self._mm_mu + print "M&M mu gain: %.2e" % self._mm_gain_mu + print "M&M omega: %.2f" % self._mm_omega + print "M&M omega gain: %.2e" % self._mm_gain_omega + print "M&M omega limit: %.2f" % self._mm_omega_relative_limit + + def _setup_logging(self): + print "Demodulation logging turned on." + self.define_component("prescaler_dat", + gr.file_sink(gr.sizeof_gr_complex, "rx_prescaler.dat")) + self.define_component("agc_dat", + gr.file_sink(gr.sizeof_gr_complex, "rx_agc.dat")) + self.define_component("rrc_filter_dat", + gr.file_sink(gr.sizeof_gr_complex, "rx_rrc_filter.dat")) + self.define_component("receiver_dat", + gr.file_sink(gr.sizeof_gr_complex, "rx_receiver.dat")) + self.define_component("diffdec_dat", + gr.file_sink(gr.sizeof_gr_complex, "rx_diffdec.dat")) + self.define_component("slicer_dat", + gr.file_sink(gr.sizeof_char, "rx_slicer.dat")) + self.define_component("symbol_mapper_dat", + gr.file_sink(gr.sizeof_char, "rx_symbol_mapper.dat")) + self.define_component("unpack_dat", + gr.file_sink(gr.sizeof_char, "rx_unpack.dat")) + + self.connect("pre_scaler", 0, "prescaler_dat", 0) + self.connect("agc", 0, "agc_dat", 0) + self.connect("rrc_filter", 0, "rrc_filter_dat", 0) + self.connect("receiver", 0, "receiver_dat", 0) + self.connect("diffdec", 0, "diffdec_dat", 0) + self.connect("slicer", 0, "slicer_dat", 0) + self.connect("symbol_mapper", 0, "symbol_mapper_dat", 0) + self.connect("unpack", 0, "unpack_dat", 0) + + def add_options(parser): + """ + Adds modulation-specific options to the standard parser + """ + parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw, + help="set RRC excess bandwith factor [default=%default] (PSK)") + parser.add_option("", "--no-gray-code", dest="gray_code", + action="store_false", default=_def_gray_code, + help="disable gray coding on modulated bits (PSK)") + parser.add_option("", "--costas-alpha", type="float", default=None, + help="set Costas loop alpha value [default=%default] (PSK)") + parser.add_option("", "--gain-mu", type="float", default=_def_gain_mu, + help="set M&M symbol sync loop gain mu value [default=%default] (PSK)") + parser.add_option("", "--mu", type="float", default=_def_mu, + help="set M&M symbol sync loop mu value [default=%default] (PSK)") + add_options=staticmethod(add_options) + + def extract_kwargs_from_options(options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return modulation_utils.extract_kwargs_from_options( + dqpsk_demod.__init__, ('self', 'fg'), options) + extract_kwargs_from_options=staticmethod(extract_kwargs_from_options) + + +# +# Add these to the mod/demod registry +# +modulation_utils.add_type_1_mod('dqpsk', dqpsk_mod) +modulation_utils.add_type_1_demod('dqpsk', dqpsk_demod) diff --git a/gnuradio-core/src/python/gnuradio/blksimpl2/gmsk.py b/gnuradio-core/src/python/gnuradio/blksimpl2/gmsk.py new file mode 100644 index 00000000..e852b3c1 --- /dev/null +++ b/gnuradio-core/src/python/gnuradio/blksimpl2/gmsk.py @@ -0,0 +1,305 @@ +# +# GMSK modulation and demodulation. +# +# +# Copyright 2005,2006 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 2, 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 Numeric +from pprint import pprint +import inspect + +# default values (used in __init__ and add_options) +_def_samples_per_symbol = 2 +_def_bt = 0.35 +_def_verbose = False +_def_log = False + +_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 = Numeric.convolve(Numeric.array(self.gaussian_taps),Numeric.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) + + # Define components from objects + self.define_component("nrz", self.nrz) + self.define_component("gaussian_filter", self.gaussian_filter) + self.define_component("fmmod", self.fmmod) + + # Connect components + self.connect("self", 0, "nrz", 0) + self.connect("nrz", 0, "gaussian_filter", 0) + self.connect("gaussian_filter", 0, "fmmod", 0) + self.connect("fmmod", 0, "self", 0) + + 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.define_component("nrz_dat", gr.file_sink(gr.sizeof_float, "tx_nrz.dat")) + self.define_component("gaussian_filter_dat", gr.file_sink(gr.sizeof_float, "tx_gaussian_filter.dat")) + self.define_component("fmmod_dat", gr.file_sink(gr.sizeof_gr_complex, "tx_fmmod.dat")) + + self.connect("nrz", 0, "nrz_dat", 0) + self.connect("gaussian_filter", 0, "gaussian_filter_dat", 0) + self.connect("fmmod", 0, "fmmod_dat", 0) + + 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) + + + def extract_kwargs_from_options(options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return modulation_utils.extract_kwargs_from_options(gmsk_mod.__init__, + ('self', 'fg'), options) + extract_kwargs_from_options=staticmethod(extract_kwargs_from_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() + + # Define components from objects + self.define_component("fmdemod", self.fmdemod) + self.define_component("clock_recovery", self.clock_recovery) + self.define_component("slicer", self.slicer) + + # Connect components + self.connect("self", 0, "fmdemod", 0) + self.connect("fmdemod", 0, "clock_recovery", 0) + self.connect("clock_recovery", 0, "slicer", 0) + self.connect("slicer", 0, "self", 0) + + 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.define_component("fmdemod_dat", gr.file_sink(gr.sizeof_float, "rx_fmdemod.dat")) + self.define_component("clock_recovery_dat", gr.file_sink(gr.sizeof_float, "rx_clock_recovery.dat")) + self.define_component("slicer_dat", gr.file_sink(gr.sizeof_char, "rx_slicer.dat")) + + self.connect("fmdemod", 0, "fmdemod_dat", 0) + self.connect("clock_recovery", 0, "clock_recovery_dat", 0) + self.connect("slicer", 0, "slicer_dat", 0) + + 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) + + def extract_kwargs_from_options(options): + """ + Given command line options, create dictionary suitable for passing to __init__ + """ + return modulation_utils.extract_kwargs_from_options(gmsk_demod.__init__, + ('self', 'fg'), options) + extract_kwargs_from_options=staticmethod(extract_kwargs_from_options) + + +# +# Add these to the mod/demod registry +# +modulation_utils.add_type_1_mod('gmsk', gmsk_mod) +modulation_utils.add_type_1_demod('gmsk', gmsk_demod) diff --git a/gnuradio-core/src/python/gnuradio/blksimpl2/pkt.py b/gnuradio-core/src/python/gnuradio/blksimpl2/pkt.py new file mode 100644 index 00000000..1e1dae17 --- /dev/null +++ b/gnuradio-core/src/python/gnuradio/blksimpl2/pkt.py @@ -0,0 +1,165 @@ +# +# Copyright 2005,2006 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 2, 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 math import pi +import Numeric + +from gnuradio import gr, packet_utils +import gnuradio.gr.gr_threading as _threading + + +# ///////////////////////////////////////////////////////////////////////////// +# mod/demod with packets as i/o +# ///////////////////////////////////////////////////////////////////////////// + +class mod_pkts(gr.hier_block2): + """ + Wrap an arbitrary digital modulator in our packet handling framework. + + Send packets by calling send_pkt + """ + def __init__(self, modulator, access_code=None, msgq_limit=2, pad_for_usrp=True): + """ + Hierarchical block for sending packets + + Packets to be sent are enqueued by calling send_pkt. + The output is the complex modulated signal at baseband. + + @param modulator: instance of modulator class (gr_block or hier_block) + @type modulator: complex baseband out + @param access_code: AKA sync vector + @type access_code: string of 1's and 0's between 1 and 64 long + @param msgq_limit: maximum number of messages in message queue + @type msgq_limit: int + @param pad_for_usrp: If true, packets are padded such that they end up a multiple of 128 samples + """ + + gr.hier_block2.__init__(self, "mod_pkts", + gr.io_signature(0,0,0), # Input signature + gr.io_signature(1,1,gr.sizeof_gr_complex)) # Output signature + + self._modulator = modulator + self._pad_for_usrp = pad_for_usrp + + if access_code is None: + access_code = packet_utils.default_access_code + if not packet_utils.is_1_0_string(access_code): + raise ValueError, "Invalid access_code %r. Must be string of 1's and 0's" % (access_code,) + self._access_code = access_code + + # accepts messages from the outside world + self._pkt_input = gr.message_source(gr.sizeof_char, msgq_limit) + self.define_component("packet_source", self._pkt_input) + self.define_component("modulator", self._modulator) + + self.connect("packet_source", 0, "modulator", 0) + self.connect("modulator", 0, "self", 0) + + def send_pkt(self, payload='', eof=False): + """ + Send the payload. + + @param payload: data to send + @type payload: string + """ + if eof: + msg = gr.message(1) # tell self._pkt_input we're not sending any more packets + else: + # print "original_payload =", string_to_hex_list(payload) + pkt = packet_utils.make_packet(payload, + self._modulator.samples_per_symbol(), + self._modulator.bits_per_symbol(), + self._access_code, + self._pad_for_usrp) + #print "pkt =", string_to_hex_list(pkt) + msg = gr.message_from_string(pkt) + self._pkt_input.msgq().insert_tail(msg) + + + +class demod_pkts(gr.hier_block2): + """ + Wrap an arbitrary digital demodulator in our packet handling framework. + + The input is complex baseband. When packets are demodulated, they are passed to the + app via the callback. + """ + + def __init__(self, demodulator, access_code=None, callback=None, threshold=-1): + """ + Hierarchical block for demodulating and deframing packets. + + The input is the complex modulated signal at baseband. + Demodulated packets are sent to the handler. + + @param demodulator: instance of demodulator class (gr_block or hier_block) + @type demodulator: complex baseband in + @param access_code: AKA sync vector + @type access_code: string of 1's and 0's + @param callback: function of two args: ok, payload + @type callback: ok: bool; payload: string + @param threshold: detect access_code with up to threshold bits wrong (-1 -> use default) + @type threshold: int + """ + + gr.hier_block2.__init__(self, "demod_pkts", + gr.io_signature(1,1,gr.sizeof_gr_complex), # Input signature + gr.io_signature(0,0,0)) # Output signature + + self._demodulator = demodulator + if access_code is None: + access_code = packet_utils.default_access_code + if not packet_utils.is_1_0_string(access_code): + raise ValueError, "Invalid access_code %r. Must be string of 1's and 0's" % (access_code,) + self._access_code = access_code + + if threshold == -1: + threshold = 12 # FIXME raise exception + + self._rcvd_pktq = gr.msg_queue() # holds packets from the PHY + + self.define_component("demodulator", self._demodulator) + self.define_component("correlator", gr.correlate_access_code_bb(access_code, threshold)) + self.define_component("framer_sink", gr.framer_sink_1(self._rcvd_pktq)) + + self.connect("self", 0, "demodulator",0) + self.connect("demodulator", 0, "correlator", 0) + self.connect("correlator", 0, "framer_sink", 0) + + self._watcher = _queue_watcher_thread(self._rcvd_pktq, callback) + + +class _queue_watcher_thread(_threading.Thread): + def __init__(self, rcvd_pktq, callback): + _threading.Thread.__init__(self) + self.setDaemon(1) + self.rcvd_pktq = rcvd_pktq + self.callback = callback + self.keep_running = True + self.start() + + + def run(self): + while self.keep_running: + msg = self.rcvd_pktq.delete_head() + ok, payload = packet_utils.unmake_packet(msg.to_string()) + if self.callback: + self.callback(ok, payload) diff --git a/gnuradio-core/src/python/gnuradio/blksimpl2/psk.py b/gnuradio-core/src/python/gnuradio/blksimpl2/psk.py new file mode 100644 index 00000000..2a882f88 --- /dev/null +++ b/gnuradio-core/src/python/gnuradio/blksimpl2/psk.py @@ -0,0 +1,88 @@ +# +# Copyright 2005,2006 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 2, 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 math import pi, sqrt, log10 +import math, cmath + +# The following algorithm generates Gray coded constellations for M-PSK for M=[2,4,8] +def make_gray_constellation(m): + # number of bits/symbol (log2(M)) + k = int(log10(m) / log10(2.0)) + + coeff = 1 + const_map = [] + bits = [0]*3 + for i in range(m): + # get a vector of the k bits to use in this mapping + bits[3-k:3] = [((i&(0x01 << k-j-1)) >> k-j-1) for j in range(k)] + + theta = -(2*bits[0]-1)*(2*pi/m)*(bits[0]+abs(bits[1]-bits[2])+2*bits[1]) + re = math.cos(theta) + im = math.sin(theta) + const_map.append(complex(re, im)) # plug it into the constellation + + # return the constellation; by default, it is normalized + return const_map + +# This makes a constellation that increments around the unit circle +def make_constellation(m): + return [cmath.exp(i * 2 * pi / m * 1j) for i in range(m)] + +# Common definition of constellations for Tx and Rx +constellation = { + 2 : make_constellation(2), # BPSK + 4 : make_constellation(4), # QPSK + 8 : make_constellation(8) # 8PSK + } + +# ----------------------- +# Do Gray code +# ----------------------- +# binary to gray coding -- constellation does Gray coding +binary_to_gray = { + 2 : range(2), + 4 : [0,1,3,2], + 8 : [0, 1, 3, 2, 7, 6, 4, 5] + } + +# gray to binary +gray_to_binary = { + 2 : range(2), + 4 : [0,1,3,2], + 8 : [0, 1, 3, 2, 6, 7, 5, 4] + } + +# ----------------------- +# Don't Gray code +# ----------------------- +# identity mapping +binary_to_ungray = { + 2 : range(2), + 4 : range(4), + 8 : range(8) + } + +# identity mapping +ungray_to_binary = { + 2 : range(2), + 4 : range(4), + 8 : range(8) + } diff --git a/gnuradio-core/src/python/gnuradio/packet_utils.py b/gnuradio-core/src/python/gnuradio/packet_utils.py index 182c80cd..f3552582 100644 --- a/gnuradio-core/src/python/gnuradio/packet_utils.py +++ b/gnuradio-core/src/python/gnuradio/packet_utils.py @@ -72,7 +72,7 @@ def conv_1_0_string_to_packed_binary_string(s): default_access_code = \ conv_packed_binary_string_to_1_0_string('\xAC\xDD\xA4\xE2\xF2\x8C\x20\xFC') preamble = \ - conv_packed_binary_string_to_1_0_string('\x6C\xC6\x6C\xC6\x6C\xC6\x6C\xC6') + conv_packed_binary_string_to_1_0_string('\xA4\xF2') def is_1_0_string(s): if not isinstance(s, str): @@ -103,7 +103,7 @@ def make_header(payload_len, whitener_offset=0): def make_packet(payload, samples_per_symbol, bits_per_symbol, access_code=default_access_code, pad_for_usrp=True, - whitener_offset=0): + whitener_offset=0, whitening=True): """ Build a packet, given access code, payload, and whitener offset @@ -135,8 +135,13 @@ def make_packet(payload, samples_per_symbol, bits_per_symbol, if L > MAXLEN: raise ValueError, "len(payload) must be in [0, %d]" % (MAXLEN,) - pkt = ''.join((packed_preamble, packed_access_code, make_header(L, whitener_offset), - whiten(payload_with_crc, whitener_offset), '\x55')) + if whitening: + pkt = ''.join((packed_preamble, packed_access_code, make_header(L, whitener_offset), + whiten(payload_with_crc, whitener_offset), '\x55')) + else: + pkt = ''.join((packed_preamble, packed_access_code, make_header(L, whitener_offset), + (payload_with_crc), '\x55')) + if pad_for_usrp: pkt = pkt + (_npadding_bytes(len(pkt), samples_per_symbol, bits_per_symbol) * '\x55') @@ -165,13 +170,18 @@ def _npadding_bytes(pkt_byte_len, samples_per_symbol, bits_per_symbol): return byte_modulus - r -def unmake_packet(whitened_payload_with_crc, whitener_offset=0): +def unmake_packet(whitened_payload_with_crc, whitener_offset=0, dewhitening=True): """ Return (ok, payload) @param whitened_payload_with_crc: string """ - payload_with_crc = dewhiten(whitened_payload_with_crc, whitener_offset) + + if dewhitening: + payload_with_crc = dewhiten(whitened_payload_with_crc, whitener_offset) + else: + payload_with_crc = (whitened_payload_with_crc) + ok, payload = gru.check_crc32(payload_with_crc) if 0: diff --git a/gnuradio-examples/python/digital/benchmark_loopback.py b/gnuradio-examples/python/digital/benchmark_loopback.py new file mode 100755 index 00000000..d036f63a --- /dev/null +++ b/gnuradio-examples/python/digital/benchmark_loopback.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python +#!/usr/bin/env python +# +# Copyright 2005, 2006 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 2, 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, gru, modulation_utils +from gnuradio import eng_notation +from gnuradio.eng_option import eng_option +from optparse import OptionParser + +import random, time, struct, sys, math + +# from current dir +from transmit_path_lb import transmit_path +from receive_path_lb import receive_path +import fusb_options + +class awgn_channel(gr.hier_block): + def __init__(self, fg, sample_rate, noise_voltage, frequency_offset, seed=False): + self.input = gr.add_const_cc(0) # dummy input device + + # Create the Gaussian noise source + if not seed: + self.noise = gr.noise_source_c(gr.GR_GAUSSIAN, noise_voltage) + else: + rseed = int(time.time()) + self.noise = gr.noise_source_c(gr.GR_GAUSSIAN, noise_voltage, rseed) + + self.adder = gr.add_cc() + + # Create the frequency offset + self.offset = gr.sig_source_c(1, gr.GR_SIN_WAVE, + frequency_offset, 1.0, 0.0) + self.mixer = gr.multiply_cc() + + # Connect the components + fg.connect(self.input, (self.mixer, 0)) + fg.connect(self.offset, (self.mixer, 1)) + fg.connect(self.mixer, (self.adder, 0)) + fg.connect(self.noise, (self.adder, 1)) + + gr.hier_block.__init__(self, fg, self.input, self.adder) + +class my_graph(gr.flow_graph): + def __init__(self, mod_class, demod_class, rx_callback, options): + gr.flow_graph.__init__(self) + + channelon = True; + + SNR = 10.0**(options.snr/10.0) + frequency_offset = options.frequency_offset + + power_in_signal = abs(options.tx_amplitude)**2 + noise_power = power_in_signal/SNR + noise_voltage = math.sqrt(noise_power) + + self.txpath = transmit_path(self, mod_class, options) + self.throttle = gr.throttle(gr.sizeof_gr_complex, options.sample_rate) + self.rxpath = receive_path(self, demod_class, rx_callback, options) + + if channelon: + self.channel = awgn_channel(self, options.sample_rate, noise_voltage, frequency_offset, options.seed) + + # Connect components + self.connect(self.txpath, self.throttle, self.channel, self.rxpath) + else: + # Connect components + self.connect(self.txpath, self.throttle, self.rxpath) + + +# ///////////////////////////////////////////////////////////////////////////// +# main +# ///////////////////////////////////////////////////////////////////////////// + +def main(): + + global n_rcvd, n_right + + n_rcvd = 0 + n_right = 0 + + def rx_callback(ok, payload): + global n_rcvd, n_right + (pktno,) = struct.unpack('!H', payload[0:2]) + n_rcvd += 1 + if ok: + n_right += 1 + + print "ok = %5s pktno = %4d n_rcvd = %4d n_right = %4d" % ( + ok, pktno, n_rcvd, n_right) + + def send_pkt(payload='', eof=False): + return fg.txpath.send_pkt(payload, eof) + + + mods = modulation_utils.type_1_mods() + demods = modulation_utils.type_1_demods() + + parser = OptionParser(option_class=eng_option, conflict_handler="resolve") + expert_grp = parser.add_option_group("Expert") + channel_grp = parser.add_option_group("Channel") + + parser.add_option("-m", "--modulation", type="choice", choices=mods.keys(), + default='dbpsk', + help="Select modulation from: %s [default=%%default]" + % (', '.join(mods.keys()),)) + + parser.add_option("-s", "--size", type="eng_float", default=1500, + help="set packet size [default=%default]") + parser.add_option("-M", "--megabytes", type="eng_float", default=1.0, + help="set megabytes to transmit [default=%default]") + parser.add_option("","--discontinuous", action="store_true", default=False, + help="enable discontinous transmission (bursts of 5 packets)") + + channel_grp.add_option("", "--sample-rate", type="eng_float", default=1e5, + help="set speed of channel/simulation rate to RATE [default=%default]") + channel_grp.add_option("", "--snr", type="eng_float", default=30, + help="set the SNR of the channel in dB [default=%default]") + channel_grp.add_option("", "--frequency-offset", type="eng_float", default=0, + help="set frequency offset introduced by channel [default=%default]") + channel_grp.add_option("", "--seed", action="store_true", default=False, + help="use a random seed for AWGN noise [default=%default]") + + transmit_path.add_options(parser, expert_grp) + receive_path.add_options(parser, expert_grp) + + for mod in mods.values(): + mod.add_options(expert_grp) + for demod in demods.values(): + demod.add_options(expert_grp) + + (options, args) = parser.parse_args () + + if len(args) != 0: + parser.print_help() + sys.exit(1) + + r = gr.enable_realtime_scheduling() + if r != gr.RT_OK: + print "Warning: failed to enable realtime scheduling" + + # Create an instance of a hierarchical block + fg = my_graph(mods[options.modulation], demods[options.modulation], rx_callback, options) + fg.start() + + # generate and send packets + nbytes = int(1e6 * options.megabytes) + n = 0 + pktno = 0 + pkt_size = int(options.size) + + while n < nbytes: + send_pkt(struct.pack('!H', pktno) + (pkt_size - 2) * chr(pktno & 0xff)) + n += pkt_size + if options.discontinuous and pktno % 5 == 4: + time.sleep(1) + pktno += 1 + + send_pkt(eof=True) + + fg.wait() + +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + pass diff --git a/gnuradio-examples/python/digital/receive_path.py b/gnuradio-examples/python/digital/receive_path.py index 9d55c88d..03e623f9 100644 --- a/gnuradio-examples/python/digital/receive_path.py +++ b/gnuradio-examples/python/digital/receive_path.py @@ -57,7 +57,22 @@ class receive_path(gr.hier_block): # Set up USRP source; also adjusts decim, samples_per_symbol, and bitrate self._setup_usrp_source() - + + g = self.subdev.gain_range() + if options.show_rx_gain_range: + print "Rx Gain Range: minimum = %g, maximum = %g, step size = %g" \ + % (g[0], g[1], g[2]) + + self.set_gain(options.rx_gain) + + self.set_auto_tr(True) # enable Auto Transmit/Receive switching + + # Set RF frequency + ok = self.set_freq(self._rx_freq) + if not ok: + print "Failed to set Rx frequency to %s" % (eng_notation.num_to_str(self._rx_freq)) + raise ValueError, eng_notation.num_to_str(self._rx_freq) + # copy the final answers back into options for use by demodulator options.samples_per_symbol = self._samples_per_symbol options.bitrate = self._bitrate @@ -71,7 +86,7 @@ class receive_path(gr.hier_block): chan_coeffs = gr.firdes.low_pass (1.0, # gain sw_decim * self._samples_per_symbol, # sampling rate 1.0, # midpoint of trans. band - 0.1, # width of trans. band + 0.5, # width of trans. band gr.firdes.WIN_HANN) # filter type # Decimating channel filter @@ -86,21 +101,7 @@ class receive_path(gr.hier_block): access_code=None, callback=self._rx_callback, threshold=-1) - - ok = self.set_freq(self._rx_freq) - if not ok: - print "Failed to set Rx frequency to %s" % (eng_notation.num_to_str(self._rx_freq)) - raise ValueError, eng_notation.num_to_str(self._rx_freq) - g = self.subdev.gain_range() - if options.show_rx_gain_range: - print "Rx Gain Range: minimum = %g, maximum = %g, step size = %g" \ - % (g[0], g[1], g[2]) - - self.set_gain(options.rx_gain) - - self.set_auto_tr(True) # enable Auto Transmit/Receive switching - # Carrier Sensing Blocks alpha = 0.001 thresh = 30 # in dB, will have to adjust @@ -228,6 +229,7 @@ class receive_path(gr.hier_block): """ Prints information about the receive path """ + print "\nReceive Path:" print "Using RX d'board %s" % (self.subdev.side_and_name(),) print "Rx gain: %g" % (self.gain,) print "modulation: %s" % (self._demod_class.__name__) diff --git a/gnuradio-examples/python/digital/receive_path_lb.py b/gnuradio-examples/python/digital/receive_path_lb.py new file mode 100644 index 00000000..2bfdd72d --- /dev/null +++ b/gnuradio-examples/python/digital/receive_path_lb.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# +# Copyright 2005,2006 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 2, 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, gru, blks +from gnuradio import eng_notation +import copy +import sys + +# ///////////////////////////////////////////////////////////////////////////// +# receive path +# ///////////////////////////////////////////////////////////////////////////// + +class receive_path(gr.hier_block): + def __init__(self, fg, demod_class, rx_callback, options): + + options = copy.copy(options) # make a copy so we can destructively modify + + self._verbose = options.verbose + self._bitrate = options.bitrate # desired bit rate + self._samples_per_symbol = options.samples_per_symbol # desired samples/symbol + + self._rx_callback = rx_callback # this callback is fired when there's a packet available + self._demod_class = demod_class # the demodulator_class we're using + + # Get demod_kwargs + demod_kwargs = self._demod_class.extract_kwargs_from_options(options) + + # Design filter to get actual channel we want + sw_decim = 1 + chan_coeffs = gr.firdes.low_pass (1.0, # gain + sw_decim * self._samples_per_symbol, # sampling rate + 1.0, # midpoint of trans. band + 0.5, # width of trans. band + gr.firdes.WIN_HANN) # filter type + self.channel_filter = gr.fft_filter_ccc(sw_decim, chan_coeffs) + + # receiver + self.packet_receiver = \ + blks.demod_pkts(fg, + self._demod_class(fg, **demod_kwargs), + access_code=None, + callback=self._rx_callback, + threshold=-1) + + # Carrier Sensing Blocks + alpha = 0.001 + thresh = 30 # in dB, will have to adjust + self.probe = gr.probe_avg_mag_sqrd_c(thresh,alpha) + + # Display some information about the setup + if self._verbose: + self._print_verbage() + + # connect the channel input filter to the carrier power detector + fg.connect(self.channel_filter, self.probe) + + # connect channel filter to the packet receiver + fg.connect(self.channel_filter, self.packet_receiver) + + gr.hier_block.__init__(self, fg, self.channel_filter, None) + + def bitrate(self): + return self._bitrate + + def samples_per_symbol(self): + return self._samples_per_symbol + + def carrier_sensed(self): + """ + Return True if we think carrier is present. + """ + #return self.probe.level() > X + return self.probe.unmuted() + + def carrier_threshold(self): + """ + Return current setting in dB. + """ + return self.probe.threshold() + + def set_carrier_threshold(self, threshold_in_db): + """ + Set carrier threshold. + + @param threshold_in_db: set detection threshold + @type threshold_in_db: float (dB) + """ + self.probe.set_threshold(threshold_in_db) + + + def add_options(normal, expert): + """ + Adds receiver-specific options to the Options Parser + """ + if not normal.has_option("--bitrate"): + normal.add_option("-r", "--bitrate", type="eng_float", default=100e3, + help="specify bitrate [default=%default].") + normal.add_option("", "--show-rx-gain-range", action="store_true", default=False, + help="print min and max Rx gain available on selected daughterboard") + normal.add_option("-v", "--verbose", action="store_true", default=False) + expert.add_option("-S", "--samples-per-symbol", type="int", default=2, + help="set samples/symbol [default=%default]") + expert.add_option("", "--log", action="store_true", default=False, + help="Log all parts of flow graph to files (CAUTION: lots of data)") + + # Make a static method to call before instantiation + add_options = staticmethod(add_options) + + + def _print_verbage(self): + """ + Prints information about the receive path + """ + print "\nReceive Path:" + print "modulation: %s" % (self._demod_class.__name__) + print "bitrate: %sb/s" % (eng_notation.num_to_str(self._bitrate)) + print "samples/symbol: %3d" % (self._samples_per_symbol) diff --git a/gnuradio-examples/python/digital/transmit_path_lb.py b/gnuradio-examples/python/digital/transmit_path_lb.py new file mode 100644 index 00000000..cd2c871d --- /dev/null +++ b/gnuradio-examples/python/digital/transmit_path_lb.py @@ -0,0 +1,117 @@ +# +# Copyright 2005,2006 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 2, 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, gru, blks +from gnuradio import eng_notation + +import copy +import sys + +# ///////////////////////////////////////////////////////////////////////////// +# transmit path +# ///////////////////////////////////////////////////////////////////////////// + +class transmit_path(gr.hier_block): + def __init__(self, fg, modulator_class, options): + ''' + See below for what options should hold + ''' + + options = copy.copy(options) # make a copy so we can destructively modify + + self._verbose = options.verbose + self._tx_amplitude = options.tx_amplitude # digital amplitude sent to USRP + self._bitrate = options.bitrate # desired bit rate + self._samples_per_symbol = options.samples_per_symbol # desired samples/baud + + self._modulator_class = modulator_class # the modulator_class we are using + + # Get mod_kwargs + mod_kwargs = self._modulator_class.extract_kwargs_from_options(options) + + # transmitter + self.packet_transmitter = \ + blks.mod_pkts(fg, + self._modulator_class(fg, **mod_kwargs), + access_code=None, + msgq_limit=4, + pad_for_usrp=True) + + self.amp = gr.multiply_const_cc(1) + self.set_tx_amplitude(self._tx_amplitude) + + # Display some information about the setup + if self._verbose: + self._print_verbage() + + # Connect components in the flowgraph + fg.connect(self.packet_transmitter, self.amp) + + gr.hier_block.__init__(self, fg, None, self.amp) + + def set_tx_amplitude(self, ampl): + """ + Sets the transmit amplitude sent to the USRP + @param: ampl 0 <= ampl < 32768. Try 8000 + """ + self._tx_amplitude = max(0.0, min(ampl, 32767.0)) + self.amp.set_k(self._tx_amplitude) + + def send_pkt(self, payload='', eof=False): + """ + Calls the transmitter method to send a packet + """ + return self.packet_transmitter.send_pkt(payload, eof) + + def bitrate(self): + return self._bitrate + + def samples_per_symbol(self): + return self._samples_per_symbol + + def add_options(normal, expert): + """ + Adds transmitter-specific options to the Options Parser + """ + if not normal.has_option('--bitrate'): + normal.add_option("-r", "--bitrate", type="eng_float", default=100e3, + help="specify bitrate [default=%default].") + normal.add_option("", "--tx-amplitude", type="eng_float", default=12000, metavar="AMPL", + help="set transmitter digital amplitude: 0 <= AMPL < 32768 [default=%default]") + normal.add_option("-v", "--verbose", action="store_true", default=False) + + expert.add_option("-S", "--samples-per-symbol", type="int", default=2, + help="set samples/symbol [default=%default]") + expert.add_option("", "--log", action="store_true", default=False, + help="Log all parts of flow graph to file (CAUTION: lots of data)") + + # Make a static method to call before instantiation + add_options = staticmethod(add_options) + + def _print_verbage(self): + """ + Prints information about the transmit path + """ + print "Tx amplitude %s" % (self._tx_amplitude) + print "modulation: %s" % (self._modulator_class.__name__) + print "bitrate: %sb/s" % (eng_notation.num_to_str(self._bitrate)) + print "samples/symbol: %3d" % (self._samples_per_symbol) + diff --git a/gnuradio-examples/python/hier/Makefile.am b/gnuradio-examples/python/hier/Makefile.am index e58b3612..f9931b3e 100644 --- a/gnuradio-examples/python/hier/Makefile.am +++ b/gnuradio-examples/python/hier/Makefile.am @@ -21,6 +21,7 @@ SUBDIRS = \ audio \ + digital \ ofdm \ networking \ sounder \ diff --git a/gnuradio-examples/python/hier/digital/Makefile.am b/gnuradio-examples/python/hier/digital/Makefile.am new file mode 100644 index 00000000..9cdbc712 --- /dev/null +++ b/gnuradio-examples/python/hier/digital/Makefile.am @@ -0,0 +1,35 @@ +# +# Copyright 2004 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 2, 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. +# + +EXTRA_DIST = \ + README \ + benchmark_loopback.py \ + benchmark_rx.py \ + benchmark_tx.py \ + fusb_options.py \ + pick_bitrate.py \ + receive_path.py \ + receive_path_lb.py \ + rx_voice.py \ + transmit_path.py \ + transmit_path_lb.py \ + tunnel.py \ + tx_voice.py diff --git a/gnuradio-examples/python/hier/digital/README b/gnuradio-examples/python/hier/digital/README new file mode 100644 index 00000000..9d8a4049 --- /dev/null +++ b/gnuradio-examples/python/hier/digital/README @@ -0,0 +1,77 @@ +Quick overview of what's here: + +* benchmark_tx.py: generates packets of the size you +specify and sends them across the air using the USRP. Known to work +well using the USRP with the RFX transceiver daughterboards. +You can specify the bitrate to use with the -r command line +parameter. The default is 500k. Some machines will do 1M or more. +You can select the modulation to use with the -m command +line argument. The legal values for are gmsk, dbpsk and dqpsk. + +* benchmark_rx.py: the receiver half of benchmark_tx.py. +Command line arguments are pretty much the same as rx. Works well +with a USRP and RFX transceiver daughterboards. Will also work +with TVRX daugherboard, but you'll need to fiddle with the gain. See +below. Prints a summary of each packet received and keeps a running +total of packets received, and how many of them were error free. +There are two levels of error reporting going on. If the access code +(PN code) and header of a packet were properly detected, then you'll +get an output line. If the CRC32 of the payload was correct you get +"ok = True", else "ok = False". The "pktno" is extracted from the +received packet. If there are skipped numbers, you're missing some +packets. Be sure you've got a suitable antenna connected to the TX/RX +port on each board. For the RFX-400, "70 cm" / 420 MHz antennas for ham +handi-talkies work great. These are available at ham radio supplies, +etc. The boards need to be at least 3m apart. You can also try +experimenting with the rx gain (-g command line option). + +Generally speaking, I start the rx first on one machine, and then fire +up the tx on the other machine. The tx also supports a discontinous +transmission mode where it sends bursts of 5 packets and then waits 1 +second. This is useful for ensuring that all the receiver control +loops lock up fast enough. + +* tunnel.py: This program provides a framework for building your own +MACs. It creates a "TAP" interface in the kernel, typically gr0, +and sends and receives ethernet frames through it. See +/usr/src/linux/Documentation/networking/tuntap.txt and/or Google for +"universal tun tap". The Linux 2.6 kernel includes the tun module, you +don't have to build it. You may have to "modprobe tun" if it's not +loaded by default. If /dev/net/tun doesn't exist, try "modprobe tun". + +To run this program you'll need to be root or running with the +appropriate capability to open the tun interface. You'll need to fire +up two copies on different machines. Once each is running you'll need +to ifconfig the gr0 interface to set the IP address. + +This will allow two machines to talk, but anything beyond the two +machines depends on your networking setup. Left as an exercise... + +On machine A: + + $ su + # ./tunnel.py --freq 423.0M --bitrate 500k + # # in another window on A, also as root... + # ifconfig gr0 192.168.200.1 + + +On machine B: + + $ su + # ./tunnel.py --freq 423.0M --bitrate 500k + # # in another window on B, also as root... + # ifconfig gr0 192.168.200.2 + +Now, on machine A you shold be able to ping machine B: + + $ ping 192.168.200.2 + +and you should see some output for each packet in the +tunnel.py window if you used the -v option. + +Likewise, on machine B: + + $ ping 192.168.200.1 + +This now uses a carrier sense MAC, so you should be able to ssh +between the machines, web browse, etc. diff --git a/gnuradio-examples/python/hier/digital/benchmark_loopback.py b/gnuradio-examples/python/hier/digital/benchmark_loopback.py new file mode 100755 index 00000000..92a7bb07 --- /dev/null +++ b/gnuradio-examples/python/hier/digital/benchmark_loopback.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python +#!/usr/bin/env python +# +# Copyright 2005, 2006 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 2, 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, gru, modulation_utils +from gnuradio import eng_notation +from gnuradio.eng_option import eng_option +from optparse import OptionParser + +import random, time, struct, sys, math + +# from current dir +from transmit_path_lb import transmit_path +from receive_path_lb import receive_path +import fusb_options + +class awgn_channel(gr.hier_block2): + def __init__(self, sample_rate, noise_voltage, frequency_offset, seed=False): + gr.hier_block2.__init__(self, "awgn_channel", + gr.io_signature(1,1,gr.sizeof_gr_complex), # Input signature + gr.io_signature(1,1,gr.sizeof_gr_complex)) # Output signature + + # Create the Gaussian noise source + if not seed: + self.noise = gr.noise_source_c(gr.GR_GAUSSIAN, noise_voltage) + else: + rseed = int(time.time()) + self.noise = gr.noise_source_c(gr.GR_GAUSSIAN, noise_voltage, rseed) + self.define_component("noise", self.noise) + self.define_component("adder", gr.add_cc()) + + # Create the frequency offset + self.define_component("offset", gr.sig_source_c((sample_rate*1.0), gr.GR_SIN_WAVE, + frequency_offset, 1.0, 0.0)) + self.define_component("mixer", gr.multiply_cc()) + + # Connect the components + self.connect("self", 0, "mixer", 0) + self.connect("offset", 0, "mixer", 1) + self.connect("mixer", 0, "adder", 0) + self.connect("noise", 0, "adder", 1) + self.connect("adder", 0, "self", 0) + + +class my_graph(gr.hier_block2): + def __init__(self, mod_class, demod_class, rx_callback, options): + gr.hier_block2.__init__(self, "my_graph", + gr.io_signature(0,0,0), # Input signature + gr.io_signature(0,0,0)) # Output signature + + channelon = True; + + SNR = 10.0**(options.snr/10.0) + frequency_offset = options.frequency_offset + + power_in_signal = abs(options.tx_amplitude)**2 + noise_power = power_in_signal/SNR + noise_voltage = math.sqrt(noise_power) + + self.txpath = transmit_path(mod_class, options) + self.throttle = gr.throttle(gr.sizeof_gr_complex, options.sample_rate) + self.rxpath = receive_path(demod_class, rx_callback, options) + + if channelon: + self.channel = awgn_channel(options.sample_rate, noise_voltage, frequency_offset, options.seed) + + # Define the components + self.define_component("txpath", self.txpath) + self.define_component("throttle", self.throttle) + self.define_component("channel", self.channel) + self.define_component("rxpath", self.rxpath) + + # Connect components + self.connect("txpath", 0, "throttle", 0) + self.connect("throttle", 0, "channel", 0) + self.connect("channel", 0, "rxpath", 0) + else: + # Define the components + self.define_component("txpath", self.txpath) + self.define_component("throttle", self.throttle) + self.define_component("rxpath", self.rxpath) + + # Connect components + self.connect("txpath", 0, "throttle", 0) + self.connect("throttle", 0, "rxpath", 0) + + +# ///////////////////////////////////////////////////////////////////////////// +# main +# ///////////////////////////////////////////////////////////////////////////// + +def main(): + + global n_rcvd, n_right + + n_rcvd = 0 + n_right = 0 + + def rx_callback(ok, payload): + global n_rcvd, n_right + (pktno,) = struct.unpack('!H', payload[0:2]) + n_rcvd += 1 + if ok: + n_right += 1 + + print "ok = %5s pktno = %4d n_rcvd = %4d n_right = %4d" % ( + ok, pktno, n_rcvd, n_right) + + def send_pkt(payload='', eof=False): + return top_block.txpath.send_pkt(payload, eof) + + + mods = modulation_utils.type_1_mods() + demods = modulation_utils.type_1_demods() + + parser = OptionParser(option_class=eng_option, conflict_handler="resolve") + expert_grp = parser.add_option_group("Expert") + channel_grp = parser.add_option_group("Channel") + + parser.add_option("-m", "--modulation", type="choice", choices=mods.keys(), + default='dbpsk', + help="Select modulation from: %s [default=%%default]" + % (', '.join(mods.keys()),)) + + parser.add_option("-s", "--size", type="eng_float", default=1500, + help="set packet size [default=%default]") + parser.add_option("-M", "--megabytes", type="eng_float", default=1.0, + help="set megabytes to transmit [default=%default]") + parser.add_option("","--discontinuous", action="store_true", default=False, + help="enable discontinous transmission (bursts of 5 packets)") + + channel_grp.add_option("", "--sample-rate", type="eng_float", default=1e5, + help="set speed of channel/simulation rate to RATE [default=%default]") + channel_grp.add_option("", "--snr", type="eng_float", default=30, + help="set the SNR of the channel in dB [default=%default]") + channel_grp.add_option("", "--frequency-offset", type="eng_float", default=0, + help="set frequency offset introduced by channel [default=%default]") + channel_grp.add_option("", "--seed", action="store_true", default=False, + help="use a random seed for AWGN noise [default=%default]") + + transmit_path.add_options(parser, expert_grp) + receive_path.add_options(parser, expert_grp) + + for mod in mods.values(): + mod.add_options(expert_grp) + for demod in demods.values(): + demod.add_options(expert_grp) + + (options, args) = parser.parse_args () + + if len(args) != 0: + parser.print_help() + sys.exit(1) + + r = gr.enable_realtime_scheduling() + if r != gr.RT_OK: + print "Warning: failed to enable realtime scheduling" + + # Create an instance of a hierarchical block + top_block = my_graph(mods[options.modulation], demods[options.modulation], rx_callback, options) + + # Create an instance of a runtime, passing it the top block + runtime = gr.runtime(top_block) + runtime.start() + + # generate and send packets + nbytes = int(1e6 * options.megabytes) + n = 0 + pktno = 0 + pkt_size = int(options.size) + + while n < nbytes: + send_pkt(struct.pack('!H', pktno) + (pkt_size - 2) * chr(pktno & 0xff)) + n += pkt_size + if options.discontinuous and pktno % 5 == 4: + time.sleep(1) + pktno += 1 + + send_pkt(eof=True) + + runtime.wait() + +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + pass diff --git a/gnuradio-examples/python/hier/digital/benchmark_rx.py b/gnuradio-examples/python/hier/digital/benchmark_rx.py new file mode 100755 index 00000000..f65a634a --- /dev/null +++ b/gnuradio-examples/python/hier/digital/benchmark_rx.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +# +# Copyright 2005,2006 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 2, 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, gru, modulation_utils +from gnuradio import usrp +from gnuradio import eng_notation +from gnuradio.eng_option import eng_option +from optparse import OptionParser + +import random +import struct +import sys + +# from current dir +from receive_path import receive_path +import fusb_options + +#import os +#print os.getpid() +#raw_input('Attach and press enter: ') + + +class my_graph(gr.hier_block2): + def __init__(self, demod_class, rx_callback, options): + gr.hier_block2.__init__(self, "my_graph", + gr.io_signature(0,0,0), # Input signature + gr.io_signature(0,0,0)) # Output signature + self.rxpath = receive_path(demod_class, rx_callback, options) + self.define_component("rxpath", self.rxpath) + + +# ///////////////////////////////////////////////////////////////////////////// +# main +# ///////////////////////////////////////////////////////////////////////////// + +global n_rcvd, n_right + +def main(): + global n_rcvd, n_right + + n_rcvd = 0 + n_right = 0 + + def rx_callback(ok, payload): + global n_rcvd, n_right + (pktno,) = struct.unpack('!H', payload[0:2]) + n_rcvd += 1 + if ok: + n_right += 1 + + print "ok = %5s pktno = %4d n_rcvd = %4d n_right = %4d" % ( + ok, pktno, n_rcvd, n_right) + + + demods = modulation_utils.type_1_demods() + + # Create Options Parser: + parser = OptionParser (option_class=eng_option, conflict_handler="resolve") + expert_grp = parser.add_option_group("Expert") + + parser.add_option("-m", "--modulation", type="choice", choices=demods.keys(), + default='gmsk', + help="Select modulation from: %s [default=%%default]" + % (', '.join(demods.keys()),)) + + receive_path.add_options(parser, expert_grp) + + for mod in demods.values(): + mod.add_options(expert_grp) + + fusb_options.add_options(expert_grp) + (options, args) = parser.parse_args () + + if len(args) != 0: + parser.print_help(sys.stderr) + sys.exit(1) + + if options.rx_freq is None: + sys.stderr.write("You must specify -f FREQ or --freq FREQ\n") + parser.print_help(sys.stderr) + sys.exit(1) + + r = gr.enable_realtime_scheduling() + if r != gr.RT_OK: + print "Warning: Failed to enable realtime scheduling." + + # Create an instance of a hierarchical block + top_block = my_graph(demods[options.modulation], rx_callback, options) + + # Create an instance of a runtime, passing it the top block + runtime = gr.runtime(top_block) + runtime.start() + + runtime.wait() # wait for it to finish + +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + pass diff --git a/gnuradio-examples/python/hier/digital/benchmark_tx.py b/gnuradio-examples/python/hier/digital/benchmark_tx.py new file mode 100755 index 00000000..627c92b3 --- /dev/null +++ b/gnuradio-examples/python/hier/digital/benchmark_tx.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python +# +# Copyright 2005, 2006 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 2, 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, gru, modulation_utils +from gnuradio import usrp +from gnuradio import eng_notation +from gnuradio.eng_option import eng_option +from optparse import OptionParser + +import random, time, struct, sys + +# from current dir +from transmit_path import transmit_path +import fusb_options + +#import os +#print os.getpid() +#raw_input('Attach and press enter') + +class my_graph(gr.hier_block2): + def __init__(self, mod_class, options): + gr.hier_block2.__init__(self, "my_graph", + gr.io_signature(0,0,0), # Input signature + gr.io_signature(0,0,0)) # Output signature + self.txpath = transmit_path(mod_class, options) + self.define_component("txpath", self.txpath) + + +# ///////////////////////////////////////////////////////////////////////////// +# main +# ///////////////////////////////////////////////////////////////////////////// + +def main(): + + def send_pkt(payload='', eof=False): + return top_block.txpath.send_pkt(payload, eof) + + def rx_callback(ok, payload): + print "ok = %r, payload = '%s'" % (ok, payload) + + mods = modulation_utils.type_1_mods() + + parser = OptionParser(option_class=eng_option, conflict_handler="resolve") + expert_grp = parser.add_option_group("Expert") + + parser.add_option("-m", "--modulation", type="choice", choices=mods.keys(), + default='gmsk', + help="Select modulation from: %s [default=%%default]" + % (', '.join(mods.keys()),)) + + parser.add_option("-s", "--size", type="eng_float", default=1500, + help="set packet size [default=%default]") + parser.add_option("-M", "--megabytes", type="eng_float", default=1.0, + help="set megabytes to transmit [default=%default]") + parser.add_option("","--discontinuous", action="store_true", default=False, + help="enable discontinous transmission (bursts of 5 packets)") + + transmit_path.add_options(parser, expert_grp) + + for mod in mods.values(): + mod.add_options(expert_grp) + + fusb_options.add_options(expert_grp) + (options, args) = parser.parse_args () + + if len(args) != 0: + parser.print_help() + sys.exit(1) + + if options.tx_freq is None: + sys.stderr.write("You must specify -f FREQ or --freq FREQ\n") + parser.print_help(sys.stderr) + sys.exit(1) + + r = gr.enable_realtime_scheduling() + if r != gr.RT_OK: + print "Warning: failed to enable realtime scheduling" + + # Create an instance of a hierarchical block + top_block = my_graph(mods[options.modulation], options) + + # Create an instance of a runtime, passing it the top block + runtime = gr.runtime(top_block) + runtime.start() + + # generate and send packets + nbytes = int(1e6 * options.megabytes) + n = 0 + pktno = 0 + pkt_size = int(options.size) + + while n < nbytes: + send_pkt(struct.pack('!H', pktno) + (pkt_size - 2) * chr(pktno & 0xff)) + n += pkt_size + sys.stderr.write('.') + if options.discontinuous and pktno % 5 == 4: + time.sleep(1) + pktno += 1 + + send_pkt(eof=True) + runtime.wait() + +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + pass diff --git a/gnuradio-examples/python/hier/digital/fusb_options.py b/gnuradio-examples/python/hier/digital/fusb_options.py new file mode 100644 index 00000000..cdb15d72 --- /dev/null +++ b/gnuradio-examples/python/hier/digital/fusb_options.py @@ -0,0 +1,31 @@ +# +# Copyright 2006 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 2, 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. +# + +def add_options(parser): + """ + Add Fast USB specifc options to command line parser. + + @param parser: instance of OptionParser + """ + parser.add_option("-B", "--fusb-block-size", type="int", default=0, + help="specify fast usb block size [default=%default]") + parser.add_option("-N", "--fusb-nblocks", type="int", default=0, + help="specify number of fast usb blocks [default=%default]") diff --git a/gnuradio-examples/python/hier/digital/pick_bitrate.py b/gnuradio-examples/python/hier/digital/pick_bitrate.py new file mode 100644 index 00000000..42aefa94 --- /dev/null +++ b/gnuradio-examples/python/hier/digital/pick_bitrate.py @@ -0,0 +1,143 @@ +# +# Copyright 2005,2006 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 2, 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. +# + +_default_bitrate = 500e3 + +_valid_samples_per_symbol = (2,3,4,5,6,7) + +def _gen_tx_info(converter_rate): + results = [] + for samples_per_symbol in _valid_samples_per_symbol: + for interp in range(16, 512 + 1, 4): + bitrate = converter_rate / interp / samples_per_symbol + results.append((bitrate, samples_per_symbol, interp)) + results.sort() + return results + +def _gen_rx_info(converter_rate): + results = [] + for samples_per_symbol in _valid_samples_per_symbol: + for decim in range(8, 256 + 1, 2): + bitrate = converter_rate / decim / samples_per_symbol + results.append((bitrate, samples_per_symbol, decim)) + results.sort() + return results + +def _filter_info(info, samples_per_symbol, xrate): + if samples_per_symbol is not None: + info = [x for x in info if x[1] == samples_per_symbol] + if xrate is not None: + info = [x for x in info if x[2] == xrate] + return info + +def _pick_best(target_bitrate, bits_per_symbol, info): + """ + @returns tuple (bitrate, samples_per_symbol, interp_rate_or_decim_rate) + """ + if len(info) == 0: + raise RuntimeError, "info is zero length!" + + if target_bitrate is None: # return the fastest one + return info[-1] + + # convert bit rate to symbol rate + target_symbolrate = target_bitrate / bits_per_symbol + + # Find the closest matching symbol rate. + # In the event of a tie, the one with the lowest samples_per_symbol wins. + # (We already sorted them, so the first one is the one we take) + + best = info[0] + best_delta = abs(target_symbolrate - best[0]) + for x in info[1:]: + delta = abs(target_symbolrate - x[0]) + if delta < best_delta: + best_delta = delta + best = x + + # convert symbol rate back to bit rate + return ((best[0] * bits_per_symbol),) + best[1:] + +def _pick_bitrate(bitrate, bits_per_symbol, samples_per_symbol, + xrate, converter_rate, gen_info): + """ + @returns tuple (bitrate, samples_per_symbol, interp_rate_or_decim_rate) + """ + if not isinstance(bits_per_symbol, int) or bits_per_symbol < 1: + raise ValueError, "bits_per_symbol must be an int >= 1" + + if samples_per_symbol is not None and xrate is not None: # completely determined + return (float(converter_rate) / xrate / samples_per_symbol, + samples_per_symbol, xrate) + + if bitrate is None and samples_per_symbol is None and xrate is None: + bitrate = _default_bitrate + + # now we have a target bitrate and possibly an xrate or + # samples_per_symbol constraint, but not both of them. + + return _pick_best(bitrate, bits_per_symbol, + _filter_info(gen_info(converter_rate), samples_per_symbol, xrate)) + +# --------------------------------------------------------------------------------------- + +def pick_tx_bitrate(bitrate, bits_per_symbol, samples_per_symbol, + interp_rate, converter_rate=128e6): + """ + Given the 4 input parameters, return at configuration that matches + + @param bitrate: desired bitrate or None + @type bitrate: number or None + @param bits_per_symbol: E.g., BPSK -> 1, QPSK -> 2, 8-PSK -> 3 + @type bits_per_symbol: integer >= 1 + @param samples_per_symbol: samples/baud (aka samples/symbol) + @type samples_per_symbol: number or None + @param interp_rate: USRP interpolation factor + @type interp_rate: integer or None + @param converter_rate: converter sample rate in Hz + @type converter_rate: number + + @returns tuple (bitrate, samples_per_symbol, interp_rate) + """ + return _pick_bitrate(bitrate, bits_per_symbol, samples_per_symbol, + interp_rate, converter_rate, _gen_tx_info) + + +def pick_rx_bitrate(bitrate, bits_per_symbol, samples_per_symbol, + decim_rate, converter_rate=64e6): + """ + Given the 4 input parameters, return at configuration that matches + + @param bitrate: desired bitrate or None + @type bitrate: number or None + @param bits_per_symbol: E.g., BPSK -> 1, QPSK -> 2, 8-PSK -> 3 + @type bits_per_symbol: integer >= 1 + @param samples_per_symbol: samples/baud (aka samples/symbol) + @type samples_per_symbol: number or None + @param decim_rate: USRP decimation factor + @type decim_rate: integer or None + @param converter_rate: converter sample rate in Hz + @type converter_rate: number + + @returns tuple (bitrate, samples_per_symbol, decim_rate) + """ + return _pick_bitrate(bitrate, bits_per_symbol, samples_per_symbol, + decim_rate, converter_rate, _gen_rx_info) diff --git a/gnuradio-examples/python/hier/digital/receive_path.py b/gnuradio-examples/python/hier/digital/receive_path.py new file mode 100644 index 00000000..64a547dc --- /dev/null +++ b/gnuradio-examples/python/hier/digital/receive_path.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python +# +# Copyright 2005,2006 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 2, 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, gru, blks2 +from gnuradio import usrp +from gnuradio import eng_notation +import copy +import sys + +# from current dir +from pick_bitrate import pick_rx_bitrate + +# ///////////////////////////////////////////////////////////////////////////// +# receive path +# ///////////////////////////////////////////////////////////////////////////// + +class receive_path(gr.hier_block2): + def __init__(self, demod_class, rx_callback, options): + gr.hier_block2.__init__(self, "receive_path", + gr.io_signature(0,0,0), # Input signature + gr.io_signature(0,0,0)) # Output signature + + options = copy.copy(options) # make a copy so we can destructively modify + + self._verbose = options.verbose + self._rx_freq = options.rx_freq # receiver's center frequency + self._rx_gain = options.rx_gain # receiver's gain + self._rx_subdev_spec = options.rx_subdev_spec # daughterboard to use + self._bitrate = options.bitrate # desired bit rate + self._decim = options.decim # Decimating rate for the USRP (prelim) + self._samples_per_symbol = options.samples_per_symbol # desired samples/symbol + self._fusb_block_size = options.fusb_block_size # usb info for USRP + self._fusb_nblocks = options.fusb_nblocks # usb info for USRP + + self._rx_callback = rx_callback # this callback is fired when there's a packet available + self._demod_class = demod_class # the demodulator_class we're using + + if self._rx_freq is None: + sys.stderr.write("-f FREQ or --freq FREQ or --rx-freq FREQ must be specified\n") + raise SystemExit + + # Set up USRP source; also adjusts decim, samples_per_symbol, and bitrate + self._setup_usrp_source() + + g = self.subdev.gain_range() + if options.show_rx_gain_range: + print "Rx Gain Range: minimum = %g, maximum = %g, step size = %g" \ + % (g[0], g[1], g[2]) + + self.set_gain(options.rx_gain) + + self.set_auto_tr(True) # enable Auto Transmit/Receive switching + + # Set RF frequency + ok = self.set_freq(self._rx_freq) + if not ok: + print "Failed to set Rx frequency to %s" % (eng_notation.num_to_str(self._rx_freq)) + raise ValueError, eng_notation.num_to_str(self._rx_freq) + + # copy the final answers back into options for use by demodulator + options.samples_per_symbol = self._samples_per_symbol + options.bitrate = self._bitrate + options.decim = self._decim + + # Get demod_kwargs + demod_kwargs = self._demod_class.extract_kwargs_from_options(options) + + # Design filter to get actual channel we want + sw_decim = 1 + chan_coeffs = gr.firdes.low_pass (1.0, # gain + sw_decim * self._samples_per_symbol, # sampling rate + 1.0, # midpoint of trans. band + 0.5, # width of trans. band + gr.firdes.WIN_HANN) # filter type + + # Decimating channel filter + # complex in and out, float taps + self.chan_filt = gr.fft_filter_ccc(sw_decim, chan_coeffs) + #self.chan_filt = gr.fir_filter_ccf(sw_decim, chan_coeffs) + # receiver + self.packet_receiver = \ + blks2.demod_pkts(self._demod_class(**demod_kwargs), + access_code=None, + callback=self._rx_callback, + threshold=-1) + + # Carrier Sensing Blocks + alpha = 0.001 + thresh = 30 # in dB, will have to adjust + self.probe = gr.probe_avg_mag_sqrd_c(thresh,alpha) + + # Display some information about the setup + if self._verbose: + self._print_verbage() + + # Define the components + self.define_component("usrp", self.u) + self.define_component("channel_filter", gr.fft_filter_ccc(sw_decim, chan_coeffs)) + self.define_component("channel_probe", self.probe) + self.define_component("packet_receiver", self.packet_receiver) + + # connect the channel input filter to the carrier power detector + self.connect("usrp", 0, "channel_filter", 0) + self.connect("channel_filter", 0, "channel_probe", 0) + + # connect channel filter to the packet receiver + self.connect("channel_filter", 0, "packet_receiver", 0) + + + def _setup_usrp_source(self): + self.u = usrp.source_c (fusb_block_size=self._fusb_block_size, + fusb_nblocks=self._fusb_nblocks) + adc_rate = self.u.adc_rate() + + # derive values of bitrate, samples_per_symbol, and decim from desired info + (self._bitrate, self._samples_per_symbol, self._decim) = \ + pick_rx_bitrate(self._bitrate, self._demod_class.bits_per_symbol(), \ + self._samples_per_symbol, self._decim, adc_rate) + + self.u.set_decim_rate(self._decim) + + # determine the daughterboard subdevice we're using + if self._rx_subdev_spec is None: + self._rx_subdev_spec = usrp.pick_rx_subdevice(self.u) + self.subdev = usrp.selected_subdev(self.u, self._rx_subdev_spec) + + self.u.set_mux(usrp.determine_rx_mux_value(self.u, self._rx_subdev_spec)) + + def set_freq(self, target_freq): + """ + Set the center frequency we're interested in. + + @param target_freq: frequency in Hz + @rypte: bool + + Tuning is a two step process. First we ask the front-end to + tune as close to the desired frequency as it can. Then we use + the result of that operation and our target_frequency to + determine the value for the digital up converter. + """ + r = self.u.tune(0, self.subdev, target_freq) + if r: + return True + + return False + + def set_gain(self, gain): + """ + Sets the analog gain in the USRP + """ + if gain is None: + r = self.subdev.gain_range() + gain = (r[0] + r[1])/2 # set gain to midpoint + self.gain = gain + return self.subdev.set_gain(gain) + + def set_auto_tr(self, enable): + return self.subdev.set_auto_tr(enable) + + def bitrate(self): + return self._bitrate + + def samples_per_symbol(self): + return self._samples_per_symbol + + def decim(self): + return self._decim + + def carrier_sensed(self): + """ + Return True if we think carrier is present. + """ + #return self.probe.level() > X + return self.probe.unmuted() + + def carrier_threshold(self): + """ + Return current setting in dB. + """ + return self.probe.threshold() + + def set_carrier_threshold(self, threshold_in_db): + """ + Set carrier threshold. + + @param threshold_in_db: set detection threshold + @type threshold_in_db: float (dB) + """ + self.probe.set_threshold(threshold_in_db) + + + def add_options(normal, expert): + """ + Adds receiver-specific options to the Options Parser + """ + add_freq_option(normal) + if not normal.has_option("--bitrate"): + normal.add_option("-r", "--bitrate", type="eng_float", default=None, + help="specify bitrate. samples-per-symbol and interp/decim will be derived.") + normal.add_option("-R", "--rx-subdev-spec", type="subdev", default=None, + help="select USRP Rx side A or B") + normal.add_option("", "--rx-gain", type="eng_float", default=None, metavar="GAIN", + help="set receiver gain in dB [default=midpoint]. See also --show-rx-gain-range") + normal.add_option("", "--show-rx-gain-range", action="store_true", default=False, + help="print min and max Rx gain available on selected daughterboard") + normal.add_option("-v", "--verbose", action="store_true", default=False) + expert.add_option("-S", "--samples-per-symbol", type="int", default=None, + help="set samples/symbol [default=%default]") + expert.add_option("", "--rx-freq", type="eng_float", default=None, + help="set Rx frequency to FREQ [default=%default]", metavar="FREQ") + expert.add_option("-d", "--decim", type="intx", default=None, + help="set fpga decimation rate to DECIM [default=%default]") + expert.add_option("", "--log", action="store_true", default=False, + help="Log all parts of flow graph to files (CAUTION: lots of data)") + + # Make a static method to call before instantiation + add_options = staticmethod(add_options) + + + def _print_verbage(self): + """ + Prints information about the receive path + """ + print "\nReceive Path:" + print "Using RX d'board %s" % (self.subdev.side_and_name(),) + print "Rx gain: %g" % (self.gain,) + print "modulation: %s" % (self._demod_class.__name__) + print "bitrate: %sb/s" % (eng_notation.num_to_str(self._bitrate)) + print "samples/symbol: %3d" % (self._samples_per_symbol) + print "decim: %3d" % (self._decim) + print "Rx Frequency: %s" % (eng_notation.num_to_str(self._rx_freq)) + +def add_freq_option(parser): + """ + Hackery that has the -f / --freq option set both tx_freq and rx_freq + """ + def freq_callback(option, opt_str, value, parser): + parser.values.rx_freq = value + parser.values.tx_freq = value + + if not parser.has_option('--freq'): + parser.add_option('-f', '--freq', type="eng_float", + action="callback", callback=freq_callback, + help="set Tx and/or Rx frequency to FREQ [default=%default]", + metavar="FREQ") diff --git a/gnuradio-examples/python/hier/digital/receive_path_lb.py b/gnuradio-examples/python/hier/digital/receive_path_lb.py new file mode 100644 index 00000000..fada441f --- /dev/null +++ b/gnuradio-examples/python/hier/digital/receive_path_lb.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python +# +# Copyright 2005,2006 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 2, 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, gru, blks2 +from gnuradio import eng_notation +import copy +import sys + +# ///////////////////////////////////////////////////////////////////////////// +# receive path +# ///////////////////////////////////////////////////////////////////////////// + +class receive_path(gr.hier_block2): + def __init__(self, demod_class, rx_callback, options): + gr.hier_block2.__init__(self, "receive_path", + gr.io_signature(1,1,gr.sizeof_gr_complex), # Input signature + gr.io_signature(0,0,0)) # Output signature + + options = copy.copy(options) # make a copy so we can destructively modify + + self._verbose = options.verbose + self._bitrate = options.bitrate # desired bit rate + self._samples_per_symbol = options.samples_per_symbol # desired samples/symbol + + self._rx_callback = rx_callback # this callback is fired when there's a packet available + self._demod_class = demod_class # the demodulator_class we're using + + # Get demod_kwargs + demod_kwargs = self._demod_class.extract_kwargs_from_options(options) + + # Design filter to get actual channel we want + sw_decim = 1 + chan_coeffs = gr.firdes.low_pass (1.0, # gain + sw_decim * self._samples_per_symbol, # sampling rate + 1.0, # midpoint of trans. band + 0.5, # width of trans. band + gr.firdes.WIN_HANN) # filter type + + # receiver + self.packet_receiver = \ + blks2.demod_pkts(self._demod_class(**demod_kwargs), + access_code=None, + callback=self._rx_callback, + threshold=-1) + + # Carrier Sensing Blocks + alpha = 0.001 + thresh = 30 # in dB, will have to adjust + self.probe = gr.probe_avg_mag_sqrd_c(thresh,alpha) + + # Display some information about the setup + if self._verbose: + self._print_verbage() + + # Define the components + self.define_component("channel_filter", gr.fft_filter_ccc(sw_decim, chan_coeffs)) + self.define_component("channel_probe", self.probe) + self.define_component("packet_receiver", self.packet_receiver) + + # connect the channel input filter to the carrier power detector + self.connect("self", 0, "channel_filter", 0) + self.connect("channel_filter", 0, "channel_probe", 0) + + # connect channel filter to the packet receiver + self.connect("channel_filter", 0, "packet_receiver", 0) + + def bitrate(self): + return self._bitrate + + def samples_per_symbol(self): + return self._samples_per_symbol + + def carrier_sensed(self): + """ + Return True if we think carrier is present. + """ + #return self.probe.level() > X + return self.probe.unmuted() + + def carrier_threshold(self): + """ + Return current setting in dB. + """ + return self.probe.threshold() + + def set_carrier_threshold(self, threshold_in_db): + """ + Set carrier threshold. + + @param threshold_in_db: set detection threshold + @type threshold_in_db: float (dB) + """ + self.probe.set_threshold(threshold_in_db) + + + def add_options(normal, expert): + """ + Adds receiver-specific options to the Options Parser + """ + if not normal.has_option("--bitrate"): + normal.add_option("-r", "--bitrate", type="eng_float", default=100e3, + help="specify bitrate [default=%default].") + normal.add_option("", "--show-rx-gain-range", action="store_true", default=False, + help="print min and max Rx gain available on selected daughterboard") + normal.add_option("-v", "--verbose", action="store_true", default=False) + expert.add_option("-S", "--samples-per-symbol", type="int", default=2, + help="set samples/symbol [default=%default]") + expert.add_option("", "--log", action="store_true", default=False, + help="Log all parts of flow graph to files (CAUTION: lots of data)") + + # Make a static method to call before instantiation + add_options = staticmethod(add_options) + + + def _print_verbage(self): + """ + Prints information about the receive path + """ + print "modulation: %s" % (self._demod_class.__name__) + print "bitrate: %sb/s" % (eng_notation.num_to_str(self._bitrate)) + print "samples/symbol: %3d" % (self._samples_per_symbol) diff --git a/gnuradio-examples/python/hier/digital/rx_voice.py b/gnuradio-examples/python/hier/digital/rx_voice.py new file mode 100755 index 00000000..13048624 --- /dev/null +++ b/gnuradio-examples/python/hier/digital/rx_voice.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# +# Copyright 2005,2006 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 2, 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, gru, modulation_utils +from gnuradio import usrp +from gnuradio import audio +from gnuradio import eng_notation +from gnuradio.eng_option import eng_option +from optparse import OptionParser + +from gnuradio.vocoder import gsm_full_rate + +import random +import struct + +# from current dir +from receive_path import receive_path +import fusb_options + +#import os +#print os.getpid() +#raw_input('Attach and press enter') + + +class audio_tx(gr.hier_block): + def __init__(self, fg, audio_output_dev): + self.packet_src = gr.message_source(33) + voice_decoder = gsm_full_rate.decode_ps() + s2f = gr.short_to_float () + sink_scale = gr.multiply_const_ff(1.0/32767.) + audio_sink = audio.sink(8000, audio_output_dev) + fg.connect(self.packet_src, voice_decoder, s2f, sink_scale, audio_sink) + gr.hier_block.__init__(self, fg, self.packet_src, audio_sink) + + def msgq(self): + return self.packet_src.msgq() + + +class my_graph(gr.flow_graph): + + def __init__(self, demod_class, rx_callback, options): + gr.flow_graph.__init__(self) + self.rxpath = receive_path(self, demod_class, rx_callback, options) + self.audio_tx = audio_tx(self, options.audio_output) + + +# ///////////////////////////////////////////////////////////////////////////// +# main +# ///////////////////////////////////////////////////////////////////////////// + +global n_rcvd, n_right + +def main(): + global n_rcvd, n_right + + n_rcvd = 0 + n_right = 0 + + def rx_callback(ok, payload): + global n_rcvd, n_right + n_rcvd += 1 + if ok: + n_right += 1 + + fg.audio_tx.msgq().insert_tail(gr.message_from_string(payload)) + + print "ok = %r n_rcvd = %4d n_right = %4d" % ( + ok, n_rcvd, n_right) + + demods = modulation_utils.type_1_demods() + + # Create Options Parser: + parser = OptionParser (option_class=eng_option, conflict_handler="resolve") + expert_grp = parser.add_option_group("Expert") + + parser.add_option("-m", "--modulation", type="choice", choices=demods.keys(), + default='gmsk', + help="Select modulation from: %s [default=%%default]" + % (', '.join(demods.keys()),)) + parser.add_option("-O", "--audio-output", type="string", default="", + help="pcm output device name. E.g., hw:0,0 or /dev/dsp") + + receive_path.add_options(parser, expert_grp) + + for mod in demods.values(): + mod.add_options(expert_grp) + + fusb_options.add_options(expert_grp) + + parser.set_defaults(bitrate=50e3) # override default bitrate default + (options, args) = parser.parse_args () + + if len(args) != 0: + parser.print_help(sys.stderr) + sys.exit(1) + + if options.rx_freq is None: + sys.stderr.write("You must specify -f FREQ or --freq FREQ\n") + parser.print_help(sys.stderr) + sys.exit(1) + + + # build the graph + fg = my_graph(demods[options.modulation], rx_callback, options) + + r = gr.enable_realtime_scheduling() + if r != gr.RT_OK: + print "Warning: Failed to enable realtime scheduling." + + fg.start() # start flow graph + fg.wait() # wait for it to finish + +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + pass diff --git a/gnuradio-examples/python/hier/digital/transmit_path.py b/gnuradio-examples/python/hier/digital/transmit_path.py new file mode 100644 index 00000000..c517210a --- /dev/null +++ b/gnuradio-examples/python/hier/digital/transmit_path.py @@ -0,0 +1,239 @@ +# +# Copyright 2005,2006 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 2, 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, gru, blks2 +from gnuradio import usrp +from gnuradio import eng_notation + +import copy +import sys + +# from current dir +from pick_bitrate import pick_tx_bitrate + +# ///////////////////////////////////////////////////////////////////////////// +# transmit path +# ///////////////////////////////////////////////////////////////////////////// + +class transmit_path(gr.hier_block2): + def __init__(self, modulator_class, options): + ''' + See below for what options should hold + ''' + + gr.hier_block2.__init__(self, "transmit_path", + gr.io_signature(0,0,0), # Input signature + gr.io_signature(0,0,0)) # Output signature + + options = copy.copy(options) # make a copy so we can destructively modify + + self._verbose = options.verbose + self._tx_freq = options.tx_freq # tranmitter's center frequency + self._tx_amplitude = options.tx_amplitude # digital amplitude sent to USRP + self._tx_subdev_spec = options.tx_subdev_spec # daughterboard to use + self._bitrate = options.bitrate # desired bit rate + self._interp = options.interp # interpolating rate for the USRP (prelim) + self._samples_per_symbol = options.samples_per_symbol # desired samples/baud + self._fusb_block_size = options.fusb_block_size # usb info for USRP + self._fusb_nblocks = options.fusb_nblocks # usb info for USRP + + self._modulator_class = modulator_class # the modulator_class we are using + + if self._tx_freq is None: + sys.stderr.write("-f FREQ or --freq FREQ or --tx-freq FREQ must be specified\n") + raise SystemExit + + # Set up USRP sink; also adjusts interp, samples_per_symbol, and bitrate + self._setup_usrp_sink() + + # copy the final answers back into options for use by modulator + options.samples_per_symbol = self._samples_per_symbol + options.bitrate = self._bitrate + options.interp = self._interp + + # Get mod_kwargs + mod_kwargs = self._modulator_class.extract_kwargs_from_options(options) + + # Set center frequency of USRP + ok = self.set_freq(self._tx_freq) + if not ok: + print "Failed to set Tx frequency to %s" % (eng_notation.num_to_str(self._tx_freq),) + raise ValueError + + # transmitter + self.packet_transmitter = \ + blks2.mod_pkts(self._modulator_class(**mod_kwargs), + access_code=None, + msgq_limit=4, + pad_for_usrp=True) + + # Set the USRP for maximum transmit gain + # (Note that on the RFX cards this is a nop.) + self.set_gain(self.subdev.gain_range()[0]) + + self.amp = gr.multiply_const_cc(1) + self.set_tx_amplitude(self._tx_amplitude) + + # enable Auto Transmit/Receive switching + self.set_auto_tr(True) + + # Display some information about the setup + if self._verbose: + self._print_verbage() + + # Define the components + self.define_component("packet_transmitter", self.packet_transmitter) + self.define_component("amp", self.amp) + self.define_component("usrp", self.u) + + # Connect components in the flowgraph; set amp component to the output of this block + self.connect("packet_transmitter", 0, "amp", 0) + self.connect("amp", 0, "usrp", 0) + + def _setup_usrp_sink(self): + """ + Creates a USRP sink, determines the settings for best bitrate, + and attaches to the transmitter's subdevice. + """ + self.u = usrp.sink_c(fusb_block_size=self._fusb_block_size, + fusb_nblocks=self._fusb_nblocks) + dac_rate = self.u.dac_rate(); + + # derive values of bitrate, samples_per_symbol, and interp from desired info + (self._bitrate, self._samples_per_symbol, self._interp) = \ + pick_tx_bitrate(self._bitrate, self._modulator_class.bits_per_symbol(), + self._samples_per_symbol, self._interp, dac_rate) + + self.u.set_interp_rate(self._interp) + + # determine the daughterboard subdevice we're using + if self._tx_subdev_spec is None: + self._tx_subdev_spec = usrp.pick_tx_subdevice(self.u) + self.u.set_mux(usrp.determine_tx_mux_value(self.u, self._tx_subdev_spec)) + self.subdev = usrp.selected_subdev(self.u, self._tx_subdev_spec) + + + def set_freq(self, target_freq): + """ + Set the center frequency we're interested in. + + @param target_freq: frequency in Hz + @rypte: bool + + Tuning is a two step process. First we ask the front-end to + tune as close to the desired frequency as it can. Then we use + the result of that operation and our target_frequency to + determine the value for the digital up converter. + """ + r = self.u.tune(self.subdev._which, self.subdev, target_freq) + if r: + return True + + return False + + def set_gain(self, gain): + """ + Sets the analog gain in the USRP + """ + self.gain = gain + self.subdev.set_gain(gain) + + def set_tx_amplitude(self, ampl): + """ + Sets the transmit amplitude sent to the USRP + @param: ampl 0 <= ampl < 32768. Try 8000 + """ + self._tx_amplitude = max(0.0, min(ampl, 32767.0)) + self.amp.set_k(self._tx_amplitude) + + def set_auto_tr(self, enable): + """ + Turns on auto transmit/receive of USRP daughterboard (if exits; else ignored) + """ + return self.subdev.set_auto_tr(enable) + + def send_pkt(self, payload='', eof=False): + """ + Calls the transmitter method to send a packet + """ + return self.packet_transmitter.send_pkt(payload, eof) + + def bitrate(self): + return self._bitrate + + def samples_per_symbol(self): + return self._samples_per_symbol + + def interp(self): + return self._interp + + def add_options(normal, expert): + """ + Adds transmitter-specific options to the Options Parser + """ + add_freq_option(normal) + if not normal.has_option('--bitrate'): + normal.add_option("-r", "--bitrate", type="eng_float", default=None, + help="specify bitrate. samples-per-symbol and interp/decim will be derived.") + normal.add_option("-T", "--tx-subdev-spec", type="subdev", default=None, + help="select USRP Tx side A or B") + normal.add_option("", "--tx-amplitude", type="eng_float", default=12000, metavar="AMPL", + help="set transmitter digital amplitude: 0 <= AMPL < 32768 [default=%default]") + normal.add_option("-v", "--verbose", action="store_true", default=False) + + expert.add_option("-S", "--samples-per-symbol", type="int", default=None, + help="set samples/symbol [default=%default]") + expert.add_option("", "--tx-freq", type="eng_float", default=None, + help="set transmit frequency to FREQ [default=%default]", metavar="FREQ") + expert.add_option("-i", "--interp", type="intx", default=None, + help="set fpga interpolation rate to INTERP [default=%default]") + expert.add_option("", "--log", action="store_true", default=False, + help="Log all parts of flow graph to file (CAUTION: lots of data)") + + # Make a static method to call before instantiation + add_options = staticmethod(add_options) + + def _print_verbage(self): + """ + Prints information about the transmit path + """ + print "Using TX d'board %s" % (self.subdev.side_and_name(),) + print "Tx amplitude %s" % (self._tx_amplitude) + print "modulation: %s" % (self._modulator_class.__name__) + print "bitrate: %sb/s" % (eng_notation.num_to_str(self._bitrate)) + print "samples/symbol: %3d" % (self._samples_per_symbol) + print "interp: %3d" % (self._interp) + print "Tx Frequency: %s" % (eng_notation.num_to_str(self._tx_freq)) + + +def add_freq_option(parser): + """ + Hackery that has the -f / --freq option set both tx_freq and rx_freq + """ + def freq_callback(option, opt_str, value, parser): + parser.values.rx_freq = value + parser.values.tx_freq = value + + if not parser.has_option('--freq'): + parser.add_option('-f', '--freq', type="eng_float", + action="callback", callback=freq_callback, + help="set Tx and/or Rx frequency to FREQ [default=%default]", + metavar="FREQ") diff --git a/gnuradio-examples/python/hier/digital/transmit_path_lb.py b/gnuradio-examples/python/hier/digital/transmit_path_lb.py new file mode 100644 index 00000000..7bf4b605 --- /dev/null +++ b/gnuradio-examples/python/hier/digital/transmit_path_lb.py @@ -0,0 +1,123 @@ +# +# Copyright 2005,2006 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 2, 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, gru, blks2 +from gnuradio import eng_notation + +import copy +import sys + +# ///////////////////////////////////////////////////////////////////////////// +# transmit path +# ///////////////////////////////////////////////////////////////////////////// + +class transmit_path(gr.hier_block2): + def __init__(self, modulator_class, options): + ''' + See below for what options should hold + ''' + + gr.hier_block2.__init__(self, "transmit_path", + gr.io_signature(0,0,0), # Input signature + gr.io_signature(1,1,gr.sizeof_gr_complex)) # Output signature + + options = copy.copy(options) # make a copy so we can destructively modify + + self._verbose = options.verbose + self._tx_amplitude = options.tx_amplitude # digital amplitude sent to USRP + self._bitrate = options.bitrate # desired bit rate + self._samples_per_symbol = options.samples_per_symbol # desired samples/baud + + self._modulator_class = modulator_class # the modulator_class we are using + + # Get mod_kwargs + mod_kwargs = self._modulator_class.extract_kwargs_from_options(options) + + # transmitter + self.packet_transmitter = \ + blks2.mod_pkts(self._modulator_class(**mod_kwargs), + access_code=None, + msgq_limit=4, + pad_for_usrp=True) + + self.amp = gr.multiply_const_cc(1) + self.set_tx_amplitude(self._tx_amplitude) + + # Display some information about the setup + if self._verbose: + self._print_verbage() + + # Define the components + self.define_component("packet_transmitter", self.packet_transmitter) + self.define_component("amp", self.amp) + + # Connect components in the flowgraph; set amp component to the output of this block + self.connect("packet_transmitter", 0, "amp", 0) + self.connect("amp", 0, "self", 0) + + def set_tx_amplitude(self, ampl): + """ + Sets the transmit amplitude sent to the USRP + @param: ampl 0 <= ampl < 32768. Try 8000 + """ + self._tx_amplitude = max(0.0, min(ampl, 32767.0)) + self.amp.set_k(self._tx_amplitude) + + def send_pkt(self, payload='', eof=False): + """ + Calls the transmitter method to send a packet + """ + return self.packet_transmitter.send_pkt(payload, eof) + + def bitrate(self): + return self._bitrate + + def samples_per_symbol(self): + return self._samples_per_symbol + + def add_options(normal, expert): + """ + Adds transmitter-specific options to the Options Parser + """ + if not normal.has_option('--bitrate'): + normal.add_option("-r", "--bitrate", type="eng_float", default=100e3, + help="specify bitrate [default=%default].") + normal.add_option("", "--tx-amplitude", type="eng_float", default=12000, metavar="AMPL", + help="set transmitter digital amplitude: 0 <= AMPL < 32768 [default=%default]") + normal.add_option("-v", "--verbose", action="store_true", default=False) + + expert.add_option("-S", "--samples-per-symbol", type="int", default=2, + help="set samples/symbol [default=%default]") + expert.add_option("", "--log", action="store_true", default=False, + help="Log all parts of flow graph to file (CAUTION: lots of data)") + + # Make a static method to call before instantiation + add_options = staticmethod(add_options) + + def _print_verbage(self): + """ + Prints information about the transmit path + """ + print "Tx amplitude %s" % (self._tx_amplitude) + print "modulation: %s" % (self._modulator_class.__name__) + print "bitrate: %sb/s" % (eng_notation.num_to_str(self._bitrate)) + print "samples/symbol: %3d" % (self._samples_per_symbol) + diff --git a/gnuradio-examples/python/hier/digital/tunnel.py b/gnuradio-examples/python/hier/digital/tunnel.py new file mode 100755 index 00000000..56229f53 --- /dev/null +++ b/gnuradio-examples/python/hier/digital/tunnel.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python +# +# Copyright 2005,2006 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 2, 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 code sets up up a virtual ethernet interface (typically gr0), +# and relays packets between the interface and the GNU Radio PHY+MAC +# +# What this means in plain language, is that if you've got a couple +# of USRPs on different machines, and if you run this code on those +# machines, you can talk between them using normal TCP/IP networking. +# +# ///////////////////////////////////////////////////////////////////////////// + + +from gnuradio import gr, gru, modulation_utils +from gnuradio import usrp +from gnuradio import eng_notation +from gnuradio.eng_option import eng_option +from optparse import OptionParser + +import random +import time +import struct +import sys +import os + +# from current dir +from transmit_path import transmit_path +from receive_path import receive_path +import fusb_options + +#print os.getpid() +#raw_input('Attach and press enter') + + +# ///////////////////////////////////////////////////////////////////////////// +# +# Use the Universal TUN/TAP device driver to move packets to/from kernel +# +# See /usr/src/linux/Documentation/networking/tuntap.txt +# +# ///////////////////////////////////////////////////////////////////////////// + +# Linux specific... +# TUNSETIFF ifr flags from + +IFF_TUN = 0x0001 # tunnel IP packets +IFF_TAP = 0x0002 # tunnel ethernet frames +IFF_NO_PI = 0x1000 # don't pass extra packet info +IFF_ONE_QUEUE = 0x2000 # beats me ;) + +def open_tun_interface(tun_device_filename): + from fcntl import ioctl + + mode = IFF_TAP | IFF_NO_PI + TUNSETIFF = 0x400454ca + + tun = os.open(tun_device_filename, os.O_RDWR) + ifs = ioctl(tun, TUNSETIFF, struct.pack("16sH", "gr%d", mode)) + ifname = ifs[:16].strip("\x00") + return (tun, ifname) + + +# ///////////////////////////////////////////////////////////////////////////// +# the flow graph +# ///////////////////////////////////////////////////////////////////////////// + +class my_graph(gr.flow_graph): + + def __init__(self, mod_class, demod_class, + rx_callback, options): + + gr.flow_graph.__init__(self) + self.txpath = transmit_path(self, mod_class, options) + self.rxpath = receive_path(self, demod_class, rx_callback, options) + + def send_pkt(self, payload='', eof=False): + return self.txpath.send_pkt(payload, eof) + + def carrier_sensed(self): + """ + Return True if the receive path thinks there's carrier + """ + return self.rxpath.carrier_sensed() + + +# ///////////////////////////////////////////////////////////////////////////// +# Carrier Sense MAC +# ///////////////////////////////////////////////////////////////////////////// + +class cs_mac(object): + """ + Prototype carrier sense MAC + + Reads packets from the TUN/TAP interface, and sends them to the PHY. + Receives packets from the PHY via phy_rx_callback, and sends them + into the TUN/TAP interface. + + Of course, we're not restricted to getting packets via TUN/TAP, this + is just an example. + """ + def __init__(self, tun_fd, verbose=False): + self.tun_fd = tun_fd # file descriptor for TUN/TAP interface + self.verbose = verbose + self.fg = None # flow graph (access to PHY) + + def set_flow_graph(self, fg): + self.fg = fg + + def phy_rx_callback(self, ok, payload): + """ + Invoked by thread associated with PHY to pass received packet up. + + @param ok: bool indicating whether payload CRC was OK + @param payload: contents of the packet (string) + """ + if self.verbose: + print "Rx: ok = %r len(payload) = %4d" % (ok, len(payload)) + if ok: + os.write(self.tun_fd, payload) + + def main_loop(self): + """ + Main loop for MAC. + Only returns if we get an error reading from TUN. + + FIXME: may want to check for EINTR and EAGAIN and reissue read + """ + min_delay = 0.001 # seconds + + while 1: + payload = os.read(self.tun_fd, 10*1024) + if not payload: + self.fg.send_pkt(eof=True) + break + + if self.verbose: + print "Tx: len(payload) = %4d" % (len(payload),) + + delay = min_delay + while self.fg.carrier_sensed(): + sys.stderr.write('B') + time.sleep(delay) + if delay < 0.050: + delay = delay * 2 # exponential back-off + + self.fg.send_pkt(payload) + + +# ///////////////////////////////////////////////////////////////////////////// +# main +# ///////////////////////////////////////////////////////////////////////////// + +def main(): + + mods = modulation_utils.type_1_mods() + demods = modulation_utils.type_1_demods() + + parser = OptionParser (option_class=eng_option, conflict_handler="resolve") + expert_grp = parser.add_option_group("Expert") + + parser.add_option("-m", "--modulation", type="choice", choices=mods.keys(), + default='gmsk', + help="Select modulation from: %s [default=%%default]" + % (', '.join(mods.keys()),)) + + parser.add_option("-v","--verbose", action="store_true", default=False) + expert_grp.add_option("-c", "--carrier-threshold", type="eng_float", default=30, + help="set carrier detect threshold (dB) [default=%default]") + expert_grp.add_option("","--tun-device-filename", default="/dev/net/tun", + help="path to tun device file [default=%default]") + + transmit_path.add_options(parser, expert_grp) + receive_path.add_options(parser, expert_grp) + + for mod in mods.values(): + mod.add_options(expert_grp) + + for demod in demods.values(): + demod.add_options(expert_grp) + + fusb_options.add_options(expert_grp) + + (options, args) = parser.parse_args () + if len(args) != 0: + parser.print_help(sys.stderr) + sys.exit(1) + + if options.rx_freq is None or options.tx_freq is None: + sys.stderr.write("You must specify -f FREQ or --freq FREQ\n") + parser.print_help(sys.stderr) + sys.exit(1) + + # open the TUN/TAP interface + (tun_fd, tun_ifname) = open_tun_interface(options.tun_device_filename) + + # Attempt to enable realtime scheduling + r = gr.enable_realtime_scheduling() + if r == gr.RT_OK: + realtime = True + else: + realtime = False + print "Note: failed to enable realtime scheduling" + + + # If the user hasn't set the fusb_* parameters on the command line, + # pick some values that will reduce latency. + + if options.fusb_block_size == 0 and options.fusb_nblocks == 0: + if realtime: # be more aggressive + options.fusb_block_size = gr.prefs().get_long('fusb', 'rt_block_size', 1024) + options.fusb_nblocks = gr.prefs().get_long('fusb', 'rt_nblocks', 16) + else: + options.fusb_block_size = gr.prefs().get_long('fusb', 'block_size', 4096) + options.fusb_nblocks = gr.prefs().get_long('fusb', 'nblocks', 16) + + #print "fusb_block_size =", options.fusb_block_size + #print "fusb_nblocks =", options.fusb_nblocks + + # instantiate the MAC + mac = cs_mac(tun_fd, verbose=True) + + + # build the graph (PHY) + fg = my_graph(mods[options.modulation], + demods[options.modulation], + mac.phy_rx_callback, + options) + + mac.set_flow_graph(fg) # give the MAC a handle for the PHY + + if fg.txpath.bitrate() != fg.rxpath.bitrate(): + print "WARNING: Transmit bitrate = %sb/sec, Receive bitrate = %sb/sec" % ( + eng_notation.num_to_str(fg.txpath.bitrate()), + eng_notation.num_to_str(fg.rxpath.bitrate())) + + print "modulation: %s" % (options.modulation,) + print "freq: %s" % (eng_notation.num_to_str(options.tx_freq)) + print "bitrate: %sb/sec" % (eng_notation.num_to_str(fg.txpath.bitrate()),) + print "samples/symbol: %3d" % (fg.txpath.samples_per_symbol(),) + #print "interp: %3d" % (fg.txpath.interp(),) + #print "decim: %3d" % (fg.rxpath.decim(),) + + fg.rxpath.set_carrier_threshold(options.carrier_threshold) + print "Carrier sense threshold:", options.carrier_threshold, "dB" + + print + print "Allocated virtual ethernet interface: %s" % (tun_ifname,) + print "You must now use ifconfig to set its IP address. E.g.," + print + print " $ sudo ifconfig %s 192.168.200.1" % (tun_ifname,) + print + print "Be sure to use a different address in the same subnet for each machine." + print + + + fg.start() # Start executing the flow graph (runs in separate threads) + + mac.main_loop() # don't expect this to return... + + fg.stop() # but if it does, tell flow graph to stop. + fg.wait() # wait for it to finish + + +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + pass diff --git a/gnuradio-examples/python/hier/digital/tx_voice.py b/gnuradio-examples/python/hier/digital/tx_voice.py new file mode 100755 index 00000000..eba0c3c7 --- /dev/null +++ b/gnuradio-examples/python/hier/digital/tx_voice.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python +# +# Copyright 2005,2006 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 2, 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, gru, modulation_utils +from gnuradio import usrp +from gnuradio import audio +from gnuradio import eng_notation +from gnuradio.eng_option import eng_option +from optparse import OptionParser + +from gnuradio.vocoder import gsm_full_rate + +import random +import time +import struct +import sys + +# from current dir +from transmit_path import transmit_path +import fusb_options + +#import os +#print os.getpid() +#raw_input('Attach and press enter') + + +class audio_rx(gr.hier_block): + def __init__(self, fg, audio_input_dev): + sample_rate = 8000 + src = audio.source(sample_rate, audio_input_dev) + src_scale = gr.multiply_const_ff(32767) + f2s = gr.float_to_short() + voice_coder = gsm_full_rate.encode_sp() + self.packets_from_encoder = gr.msg_queue() + packet_sink = gr.message_sink(33, self.packets_from_encoder, False) + fg.connect(src, src_scale, f2s, voice_coder, packet_sink) + gr.hier_block.__init__(self, fg, src, packet_sink) + + def get_encoded_voice_packet(self): + return self.packets_from_encoder.delete_head() + + +class my_graph(gr.flow_graph): + + def __init__(self, modulator_class, options): + gr.flow_graph.__init__(self) + self.txpath = transmit_path(self, modulator_class, options) + self.audio_rx = audio_rx(self, options.audio_input) + + + +# ///////////////////////////////////////////////////////////////////////////// +# main +# ///////////////////////////////////////////////////////////////////////////// + +def main(): + + def send_pkt(payload='', eof=False): + return fg.txpath.send_pkt(payload, eof) + + def rx_callback(ok, payload): + print "ok = %r, payload = '%s'" % (ok, payload) + + mods = modulation_utils.type_1_mods() + + parser = OptionParser(option_class=eng_option, conflict_handler="resolve") + expert_grp = parser.add_option_group("Expert") + + parser.add_option("-m", "--modulation", type="choice", choices=mods.keys(), + default='gmsk', + help="Select modulation from: %s [default=%%default]" + % (', '.join(mods.keys()),)) + parser.add_option("-M", "--megabytes", type="eng_float", default=0, + help="set megabytes to transmit [default=inf]") + parser.add_option("-I", "--audio-input", type="string", default="", + help="pcm input device name. E.g., hw:0,0 or /dev/dsp") + + transmit_path.add_options(parser, expert_grp) + + for mod in mods.values(): + mod.add_options(expert_grp) + + fusb_options.add_options(expert_grp) + + parser.set_defaults(bitrate=50e3) # override default bitrate default + (options, args) = parser.parse_args () + + if len(args) != 0: + parser.print_help() + sys.exit(1) + + if options.tx_freq is None: + sys.stderr.write("You must specify -f FREQ or --freq FREQ\n") + parser.print_help(sys.stderr) + sys.exit(1) + + + # build the graph + fg = my_graph(mods[options.modulation], options) + + r = gr.enable_realtime_scheduling() + if r != gr.RT_OK: + print "Warning: failed to enable realtime scheduling" + + + fg.start() # start flow graph + + # generate and send packets + nbytes = int(1e6 * options.megabytes) + n = 0 + pktno = 0 + + while nbytes == 0 or n < nbytes: + packet = fg.audio_rx.get_encoded_voice_packet() + s = packet.to_string() + send_pkt(s) + n += len(s) + sys.stderr.write('.') + pktno += 1 + + send_pkt(eof=True) + fg.wait() # wait for it to finish + fg.txpath.set_auto_tr(False) + + +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + pass diff --git a/gnuradio-examples/python/hier/usrp/Makefile.am b/gnuradio-examples/python/hier/usrp/Makefile.am index 20a21cf6..cff19ae1 100644 --- a/gnuradio-examples/python/hier/usrp/Makefile.am +++ b/gnuradio-examples/python/hier/usrp/Makefile.am @@ -20,6 +20,7 @@ # EXTRA_DIST = \ - usrp_fft.py + usrp_fft.py \ + usrp_siggen.py MOSTLYCLEANFILES = *.pyc *~ diff --git a/gnuradio-examples/python/hier/usrp/usrp_siggen.py b/gnuradio-examples/python/hier/usrp/usrp_siggen.py new file mode 100755 index 00000000..28f80a82 --- /dev/null +++ b/gnuradio-examples/python/hier/usrp/usrp_siggen.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python + +from gnuradio import gr, gru +from gnuradio import usrp +from gnuradio.eng_option import eng_option +from gnuradio import eng_notation +from optparse import OptionParser +import sys + + +class my_graph(gr.hier_block2): + def __init__ (self, type, ampl, wfreq, offset, subdev_spec, interp, rf_freq): + # Call hierarchical block constructor + # Top-level blocks have no inputs or outputs + gr.hier_block2.__init__(self, + "usrp_siggen", # Block type + gr.io_signature(0,0,0), # Input signature + gr.io_signature(0,0,0)) # Output signature + + # controllable values + self.interp = interp + self.waveform_type = type + self.waveform_ampl = ampl + self.waveform_freq = wfreq + self.waveform_offset = offset + + self.u = usrp.sink_c (0, self.interp) + + # determine the daughterboard subdevice we're using + if subdev_spec is None: + ubdev_spec = usrp.pick_tx_subdevice(self.u) + m = usrp.determine_tx_mux_value(self.u, subdev_spec) + self.u.set_mux(m) + self.subdev = usrp.selected_subdev(self.u, subdev_spec) + self.subdev.set_gain(self.subdev.gain_range()[1]) # set max Tx gain + self.subdev.set_enable(True) # enable transmitter + print "Using TX d'board %s" % (self.subdev.side_and_name(),) + + if not self.set_freq(rf_freq): + sys.stderr.write('Failed to set RF frequency\n') + raise SystemExit + + self.define_component("usrp", self.u) + + if type == gr.GR_SIN_WAVE or type == gr.GR_CONST_WAVE: + self.src = gr.sig_source_c (self.usb_freq (), + gr.GR_SIN_WAVE, + self.waveform_freq, + self.waveform_ampl, + self.waveform_offset) + self.define_component("src", self.src) + + elif type == gr.GR_UNIFORM or type == gr.GR_GAUSSIAN: + self.src = gr.noise_source_c (gr.GR_UNIFORM, + self.waveform_ampl) + self.define_component("src", self.src) + + else: + raise ValueError, type + + # self.file_sink = gr.file_sink (gr.sizeof_gr_complex, "siggen.dat") + # self.define_component("file_sink", self.file_sink) + + self.connect ("src", 0, "usrp", 0) + + + def usb_freq (self): + return self.u.dac_freq() / self.interp + + def usb_throughput (self): + return self.usb_freq () * 4 + + def set_freq(self, target_freq): + """ + Set the center frequency we're interested in. + + @param target_freq: frequency in Hz + @rypte: bool + + Tuning is a two step process. First we ask the front-end to + tune as close to the desired frequency as it can. Then we use + the result of that operation and our target_frequency to + determine the value for the digital up converter. + """ + r = self.u.tune(self.subdev._which, self.subdev, target_freq) + if r: + #print "r.baseband_freq =", eng_notation.num_to_str(r.baseband_freq) + #print "r.dxc_freq =", eng_notation.num_to_str(r.dxc_freq) + #print "r.residual_freq =", eng_notation.num_to_str(r.residual_freq) + #print "r.inverted =", r.inverted + return True + + return False + + + +def main (): + parser = OptionParser (option_class=eng_option) + parser.add_option ("-T", "--tx-subdev-spec", type="subdev", default=(0, 0), + help="select USRP Tx side A or B") + parser.add_option ("-f", "--rf-freq", type="eng_float", default=None, + help="set RF center frequency to FREQ") + parser.add_option ("-i", "--interp", type="int", default=64, + help="set fgpa interpolation rate to INTERP [default=%default]") + + parser.add_option ("--sine", dest="type", action="store_const", const=gr.GR_SIN_WAVE, + help="generate a complex sinusoid [default]", default=gr.GR_SIN_WAVE) + parser.add_option ("--const", dest="type", action="store_const", const=gr.GR_CONST_WAVE, + help="generate a constant output") + parser.add_option ("--gaussian", dest="type", action="store_const", const=gr.GR_GAUSSIAN, + help="generate Gaussian random output") + parser.add_option ("--uniform", dest="type", action="store_const", const=gr.GR_UNIFORM, + help="generate Uniform random output") + + parser.add_option ("-w", "--waveform-freq", type="eng_float", default=100e3, + help="set waveform frequency to FREQ [default=%default]") + parser.add_option ("-a", "--amplitude", type="eng_float", default=16e3, + help="set waveform amplitude to AMPLITUDE [default=%default]", metavar="AMPL") + parser.add_option ("-o", "--offset", type="eng_float", default=0, + help="set waveform offset to OFFSET [default=%default]") + (options, args) = parser.parse_args () + + if len(args) != 0: + parser.print_help() + raise SystemExit + + if options.rf_freq is None: + sys.stderr.write("usrp_siggen: must specify RF center frequency with -f RF_FREQ\n") + parser.print_help() + raise SystemExit + + top_block = my_graph(options.type, options.amplitude, options.waveform_freq, options.offset, + options.tx_subdev_spec, options.interp, options.rf_freq) + + runtime = gr.runtime(top_block) + + try: + # Run forever + runtime.run() + except KeyboardInterrupt: + # Ctrl-C exits + pass + +if __name__ == '__main__': + main () -- 2.30.2