From 58bd4ceaee4884652a682297a49957137cafa56d Mon Sep 17 00:00:00 2001 From: n4hy Date: Wed, 15 Apr 2009 02:21:52 +0000 Subject: [PATCH] new fm detector added based on FIR derivative detector. Stereo sep better than pll based one and more efficient. Tweaking probably needed, usrp_wfm_rcv_fmdet added to demo git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@10847 221aa14e-8319-0410-a670-987f0aec2ac5 --- gnuradio-core/src/lib/filter/Makefile.am | 4 + gnuradio-core/src/lib/general/Makefile.am | 3 + gnuradio-core/src/lib/general/general.i | 2 + gnuradio-core/src/lib/general/gr_fmdet_cf.cc | 93 +++++ gnuradio-core/src/lib/general/gr_fmdet_cf.h | 58 +++ gnuradio-core/src/lib/general/gr_fmdet_cf.i | 31 ++ .../src/python/gnuradio/blks2impl/Makefile.am | 1 + .../gnuradio/blks2impl/wfm_rcv_fmdet.py | 191 ++++++++++ gnuradio-examples/python/usrp/Makefile.am | 1 + .../python/usrp/usrp_wfm_rcv_fmdet.py | 351 ++++++++++++++++++ 10 files changed, 735 insertions(+) create mode 100644 gnuradio-core/src/lib/general/gr_fmdet_cf.cc create mode 100644 gnuradio-core/src/lib/general/gr_fmdet_cf.h create mode 100644 gnuradio-core/src/lib/general/gr_fmdet_cf.i create mode 100755 gnuradio-core/src/python/gnuradio/blks2impl/wfm_rcv_fmdet.py create mode 100755 gnuradio-examples/python/usrp/usrp_wfm_rcv_fmdet.py diff --git a/gnuradio-core/src/lib/filter/Makefile.am b/gnuradio-core/src/lib/filter/Makefile.am index b7fd0f58..c5897210 100644 --- a/gnuradio-core/src/lib/filter/Makefile.am +++ b/gnuradio-core/src/lib/filter/Makefile.am @@ -182,6 +182,7 @@ libfilter_la_common_SOURCES = \ gr_hilbert_fc.cc \ gr_iir_filter_ffd.cc \ gr_sincos.c \ + gr_secondorder_section_iir_filter_ff.cc \ gr_single_pole_iir_filter_ff.cc \ gr_single_pole_iir_filter_cc.cc \ gri_goertzel.cc \ @@ -249,7 +250,9 @@ grinclude_HEADERS = \ gr_iir_filter_ffd.h \ gr_rotator.h \ gr_sincos.h \ + gr_secondorder_section_iir.h \ gr_single_pole_iir.h \ + gr_secondorder_section_iir_filter_ff.h \ gr_single_pole_iir_filter_ff.h \ gr_single_pole_iir_filter_cc.h \ gr_vec_types.h \ @@ -307,6 +310,7 @@ swiginclude_HEADERS = \ gr_goertzel_fc.i \ gr_hilbert_fc.i \ gr_iir_filter_ffd.i \ + gr_secondorder_section_iir_filter_ff.i \ gr_single_pole_iir_filter_ff.i \ gr_single_pole_iir_filter_cc.i \ $(GENERATED_I) diff --git a/gnuradio-core/src/lib/general/Makefile.am b/gnuradio-core/src/lib/general/Makefile.am index fa3ccd5a..4ceb7248 100644 --- a/gnuradio-core/src/lib/general/Makefile.am +++ b/gnuradio-core/src/lib/general/Makefile.am @@ -79,6 +79,7 @@ libgeneral_la_SOURCES = \ gr_float_to_complex.cc \ gr_float_to_short.cc \ gr_float_to_uchar.cc \ + gr_fmdet_cf.cc \ gr_frequency_modulator_fc.cc \ gr_fxpt.cc \ gr_framer_sink_1.cc \ @@ -230,6 +231,7 @@ grinclude_HEADERS = \ gr_float_to_complex.h \ gr_float_to_short.h \ gr_float_to_uchar.h \ + gr_fmdet_cf.h \ gr_framer_sink_1.h \ gr_frequency_modulator_fc.h \ gr_fxpt.h \ @@ -394,6 +396,7 @@ swiginclude_HEADERS = \ gr_float_to_complex.i \ gr_float_to_short.i \ gr_float_to_uchar.i \ + gr_fmdet_cf.i \ gr_frequency_modulator_fc.i \ gr_framer_sink_1.i \ gr_glfsr_source_b.i \ diff --git a/gnuradio-core/src/lib/general/general.i b/gnuradio-core/src/lib/general/general.i index e7d9e978..0cb54870 100644 --- a/gnuradio-core/src/lib/general/general.i +++ b/gnuradio-core/src/lib/general/general.i @@ -85,6 +85,7 @@ #include #include #include +#include #include #include #include @@ -204,6 +205,7 @@ %include "gr_lms_dfe_cc.i" %include "gr_lms_dfe_ff.i" %include "gr_dpll_bb.i" +%include "gr_fmdet_cf.i" %include "gr_pll_freqdet_cf.i" %include "gr_pll_refout_cc.i" %include "gr_pll_carriertracking_cc.i" diff --git a/gnuradio-core/src/lib/general/gr_fmdet_cf.cc b/gnuradio-core/src/lib/general/gr_fmdet_cf.cc new file mode 100644 index 00000000..c5166003 --- /dev/null +++ b/gnuradio-core/src/lib/general/gr_fmdet_cf.cc @@ -0,0 +1,93 @@ +/* -*- c++ -*- */ +/* + * Copyright 2008 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * GNU Radio is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#define M_TWOPI (2*M_PI) + +gr_fmdet_cf_sptr +gr_make_fmdet_cf (float samplerate, float freq_low, float freq_high, float scl) +{ + return gr_fmdet_cf_sptr (new gr_fmdet_cf (samplerate, freq_low, freq_high, scl)); +} + +gr_fmdet_cf::gr_fmdet_cf (float samplerate, float freq_low, float freq_high, float scl) + : gr_sync_block ("fmdet_cf", + gr_make_io_signature (1, 1, sizeof (gr_complex)), + gr_make_io_signature (1, 1, sizeof (float))), + d_S1(0.1),d_S2(0.1), + d_S3(0.1),d_S4(0.1) +{ + #include + float delta; + d_freqhi = freq_high; + d_freqlo = freq_low; + delta = (d_freqhi - d_freqlo); + d_scl = scl; + d_bias = 0.5*scl*(d_freqhi+d_freqlo)/delta; + fprintf(stderr,"delta = %f d_scl=%f d_bias=%f\n",delta,d_scl,d_bias),fflush(stderr); +} + +int +gr_fmdet_cf::work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ + const gr_complex *iptr = (gr_complex *) input_items[0]; + float *optr = (float *) output_items[0]; + + int size = noutput_items; + + gr_complex Sdot,S0,S1=d_S1,S2=d_S2,S3=d_S3,S4=d_S4; + + while (size-- > 0) { + S0=*iptr++; + + Sdot = gr_complex(d_scl* + (-S0.real() + 8.0*S1.real() - 8.0*S3.real() + S4.real()), + d_scl* + (-S0.imag() + 8.0*S1.imag() - 8.0*S3.imag() + S4.imag())); + d_freq = (S2.real()*Sdot.imag()-S2.imag()*Sdot.real())/ + (S2.real()*S2.real()+S2.imag()*S2.imag()); + + S4=S3; + S3=S2; + S2=S1; + S1=S0; + + + *optr++ = d_freq-d_bias; + } + d_S1=S1; + d_S2=S2; + d_S3=S3; + d_S4=S4; + return noutput_items; +} diff --git a/gnuradio-core/src/lib/general/gr_fmdet_cf.h b/gnuradio-core/src/lib/general/gr_fmdet_cf.h new file mode 100644 index 00000000..7e8be31b --- /dev/null +++ b/gnuradio-core/src/lib/general/gr_fmdet_cf.h @@ -0,0 +1,58 @@ +/* -*- c++ -*- */ +/* + * Copyright 2008 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * GNU Radio is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef INCLUDED_GR_FMDET_CF_H +#define INCLUDED_GR_FMDET_CF_H + +#include + +class gr_fmdet_cf; +typedef boost::shared_ptr gr_fmdet_cf_sptr; + +gr_fmdet_cf_sptr gr_make_fmdet_cf (float samplerate, float freq_low, float freq_high, float scl); + +/*! + * \brief Implements an IQ slope detector + * + * + * input: stream of complex; output: stream of floats + * + * This implements a limiting slope detector. The limiter is in the + * normalization by the magnitude of the sample + */ + +class gr_fmdet_cf : public gr_sync_block +{ + friend gr_fmdet_cf_sptr gr_make_fmdet_cf (float samplerate, float freq_low, + float freq_high, float scl); + + gr_complex d_S1,d_S2,d_S3,d_S4; + float d_freq,d_freqlo,d_freqhi,d_scl,d_bias; + gr_fmdet_cf (float samplerate, float freq_low, float freq_high, float scl); + + int work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + +}; + +#endif diff --git a/gnuradio-core/src/lib/general/gr_fmdet_cf.i b/gnuradio-core/src/lib/general/gr_fmdet_cf.i new file mode 100644 index 00000000..e1da717c --- /dev/null +++ b/gnuradio-core/src/lib/general/gr_fmdet_cf.i @@ -0,0 +1,31 @@ +/* -*- c++ -*- */ +/* + * Copyright 2008 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * GNU Radio is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +GR_SWIG_BLOCK_MAGIC(gr,fmdet_cf) + +gr_fmdet_cf_sptr gr_make_fmdet_cf (float samplerate, float freq_low, float freq_high, float scl); + +class gr_fmdet_cf : public gr_sync_block +{ + private: + gr_fmdet_cf (float samplerate, float freq_low, float freq_high, float scl); +}; diff --git a/gnuradio-core/src/python/gnuradio/blks2impl/Makefile.am b/gnuradio-core/src/python/gnuradio/blks2impl/Makefile.am index 09cd92fc..f07abd4c 100644 --- a/gnuradio-core/src/python/gnuradio/blks2impl/Makefile.am +++ b/gnuradio-core/src/python/gnuradio/blks2impl/Makefile.am @@ -58,5 +58,6 @@ grblkspython_PYTHON = \ standard_squelch.py \ stream_to_vector_decimator.py \ wfm_rcv.py \ + wfm_rcv_fmdet.py \ wfm_rcv_pll.py \ wfm_tx.py diff --git a/gnuradio-core/src/python/gnuradio/blks2impl/wfm_rcv_fmdet.py b/gnuradio-core/src/python/gnuradio/blks2impl/wfm_rcv_fmdet.py new file mode 100755 index 00000000..858b9cde --- /dev/null +++ b/gnuradio-core/src/python/gnuradio/blks2impl/wfm_rcv_fmdet.py @@ -0,0 +1,191 @@ +# +# 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 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import gr +from gnuradio.blks2impl.fm_emph import fm_deemph +import math + +class wfm_rcv_fmdet(gr.hier_block2): + def __init__ (self, demod_rate, audio_decimation): + """ + Hierarchical block for demodulating a broadcast FM signal. + + The input is the downconverted complex baseband signal (gr_complex). + The output is two streams of the demodulated audio (float) 0=Left, 1=Right. + + @param demod_rate: input sample rate of complex baseband input. + @type demod_rate: float + @param audio_decimation: how much to decimate demod_rate to get to audio. + @type audio_decimation: integer + """ + gr.hier_block2.__init__(self, "wfm_rcv_fmdet", + gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature + gr.io_signature(2, 2, gr.sizeof_float)) # Output signature + lowfreq = -125e3 + highfreq = 125e3 + audio_rate = demod_rate / audio_decimation + + + # We assign to self so that outsiders can grab the demodulator + # if they need to. E.g., to plot its output. + # + # input: complex; output: float + + self.fm_demod = gr.fmdet_cf (demod_rate, lowfreq, highfreq, 0.05) + + # input: float; output: float + self.deemph_Left = fm_deemph (audio_rate) + self.deemph_Right = fm_deemph (audio_rate) + + # compute FIR filter taps for audio filter + width_of_transition_band = audio_rate / 32 + audio_coeffs = gr.firdes.low_pass (1.0 , # gain + demod_rate, # sampling rate + 15000 , + width_of_transition_band, + gr.firdes.WIN_HAMMING) + # input: float; output: float + self.audio_filter = gr.fir_filter_fff (audio_decimation, audio_coeffs) + if 1: + # Pick off the stereo carrier/2 with this filter. It attenuated 10 dB so apply 10 dB gain + # We pick off the negative frequency half because we want to base band by it! + ## NOTE THIS WAS HACKED TO OFFSET INSERTION LOSS DUE TO DEEMPHASIS + + stereo_carrier_filter_coeffs = gr.firdes.complex_band_pass(10.0, + demod_rate, + -19020, + -18980, + width_of_transition_band, + gr.firdes.WIN_HAMMING) + + #print "len stereo carrier filter = ",len(stereo_carrier_filter_coeffs) + #print "stereo carrier filter ", stereo_carrier_filter_coeffs + #print "width of transition band = ",width_of_transition_band, " audio rate = ", audio_rate + + # Pick off the double side band suppressed carrier Left-Right audio. It is attenuated 10 dB so apply 10 dB gain + + stereo_dsbsc_filter_coeffs = gr.firdes.complex_band_pass(20.0, + demod_rate, + 38000-15000/2, + 38000+15000/2, + width_of_transition_band, + gr.firdes.WIN_HAMMING) + #print "len stereo dsbsc filter = ",len(stereo_dsbsc_filter_coeffs) + #print "stereo dsbsc filter ", stereo_dsbsc_filter_coeffs + # construct overlap add filter system from coefficients for stereo carrier + + self.stereo_carrier_filter = gr.fir_filter_fcc(audio_decimation, stereo_carrier_filter_coeffs) + + # carrier is twice the picked off carrier so arrange to do a commplex multiply + + self.stereo_carrier_generator = gr.multiply_cc(); + + # Pick off the rds signal + + stereo_rds_filter_coeffs = gr.firdes.complex_band_pass(30.0, + demod_rate, + 57000 - 1500, + 57000 + 1500, + width_of_transition_band, + gr.firdes.WIN_HAMMING) + #print "len stereo dsbsc filter = ",len(stereo_dsbsc_filter_coeffs) + #print "stereo dsbsc filter ", stereo_dsbsc_filter_coeffs + # construct overlap add filter system from coefficients for stereo carrier + + self.rds_signal_filter = gr.fir_filter_fcc(audio_decimation, stereo_rds_filter_coeffs) + + + + + + + self.rds_carrier_generator = gr.multiply_cc(); + self.rds_signal_generator = gr.multiply_cc(); + self_rds_signal_processor = gr.null_sink(gr.sizeof_gr_complex); + + + + alpha = 5 * 0.25 * math.pi / (audio_rate) + beta = alpha * alpha / 4.0 + max_freq = -2.0*math.pi*18990/audio_rate; + min_freq = -2.0*math.pi*19010/audio_rate; + + self.stereo_carrier_pll_recovery = gr.pll_refout_cc(alpha,beta,max_freq,min_freq); + #self.stereo_carrier_pll_recovery.squelch_enable(False) #pll_refout does not have squelch yet, so disabled for now + + + # set up mixer (multiplier) to get the L-R signal at baseband + + self.stereo_basebander = gr.multiply_cc(); + + # pick off the real component of the basebanded L-R signal. The imaginary SHOULD be zero + + self.LmR_real = gr.complex_to_real(); + self.Make_Left = gr.add_ff(); + self.Make_Right = gr.sub_ff(); + + self.stereo_dsbsc_filter = gr.fir_filter_fcc(audio_decimation, stereo_dsbsc_filter_coeffs) + + + if 1: + + # send the real signal to complex filter to pick off the carrier and then to one side of a multiplier + self.connect (self, self.fm_demod,self.stereo_carrier_filter,self.stereo_carrier_pll_recovery, (self.stereo_carrier_generator,0)) + # send the already filtered carrier to the otherside of the carrier + self.connect (self.stereo_carrier_pll_recovery, (self.stereo_carrier_generator,1)) + # the resulting signal from this multiplier is the carrier with correct phase but at -38000 Hz. + + # send the new carrier to one side of the mixer (multiplier) + self.connect (self.stereo_carrier_generator, (self.stereo_basebander,0)) + # send the demphasized audio to the DSBSC pick off filter, the complex + # DSBSC signal at +38000 Hz is sent to the other side of the mixer/multiplier + self.connect (self.fm_demod,self.stereo_dsbsc_filter, (self.stereo_basebander,1)) + # the result is BASEBANDED DSBSC with phase zero! + + # Pick off the real part since the imaginary is theoretically zero and then to one side of a summer + self.connect (self.stereo_basebander, self.LmR_real, (self.Make_Left,0)) + #take the same real part of the DSBSC baseband signal and send it to negative side of a subtracter + self.connect (self.LmR_real,(self.Make_Right,1)) + + # Make rds carrier by taking the squared pilot tone and multiplying by pilot tone + self.connect (self.stereo_basebander,(self.rds_carrier_generator,0)) + self.connect (self.stereo_carrier_pll_recovery,(self.rds_carrier_generator,1)) + # take signal, filter off rds, send into mixer 0 channel + self.connect (self.fm_demod,self.rds_signal_filter,(self.rds_signal_generator,0)) + # take rds_carrier_generator output and send into mixer 1 channel + self.connect (self.rds_carrier_generator,(self.rds_signal_generator,1)) + # send basebanded rds signal and send into "processor" which for now is a null sink + self.connect (self.rds_signal_generator,self_rds_signal_processor) + + + if 1: + # pick off the audio, L+R that is what we used to have and send it to the summer + self.connect(self.fm_demod, self.audio_filter, (self.Make_Left, 1)) + # take the picked off L+R audio and send it to the PLUS side of the subtractor + self.connect(self.audio_filter,(self.Make_Right, 0)) + # The result of Make_Left gets (L+R) + (L-R) and results in 2*L + # The result of Make_Right gets (L+R) - (L-R) and results in 2*R + self.connect(self.Make_Left , self.deemph_Left, (self, 0)) + self.connect(self.Make_Right, self.deemph_Right, (self, 1)) + # NOTE: mono support will require variable number of outputs in hier_block2s + # See ticket:174 in Trac database + #else: + # self.connect (self.fm_demod, self.audio_filter, self) diff --git a/gnuradio-examples/python/usrp/Makefile.am b/gnuradio-examples/python/usrp/Makefile.am index 635da3b6..0ede005a 100644 --- a/gnuradio-examples/python/usrp/Makefile.am +++ b/gnuradio-examples/python/usrp/Makefile.am @@ -38,6 +38,7 @@ dist_ourdata_SCRIPTS = \ usrp_tv_rcv.py \ usrp_wfm_rcv.py \ usrp_wfm_rcv_nogui.py \ + usrp_wfm_rcv_fmdet.py \ usrp_wfm_rcv_pll.py \ usrp_wfm_rcv_sca.py \ usrp_wfm_rcv2_nogui.py \ diff --git a/gnuradio-examples/python/usrp/usrp_wfm_rcv_fmdet.py b/gnuradio-examples/python/usrp/usrp_wfm_rcv_fmdet.py new file mode 100755 index 00000000..82c521d5 --- /dev/null +++ b/gnuradio-examples/python/usrp/usrp_wfm_rcv_fmdet.py @@ -0,0 +1,351 @@ +#!/usr/bin/env python +# +# Copyright 2005,2006,2007 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import gr, gru, eng_notation, optfir +from gnuradio import audio +from gnuradio import usrp +from gnuradio import blks2 +from gnuradio.eng_option import eng_option +from gnuradio.wxgui import slider, powermate +from gnuradio.wxgui import stdgui2, fftsink2, form, scopesink2 +from optparse import OptionParser +from usrpm import usrp_dbid +import sys +import math +import wx + +def pick_subdevice(u): + """ + The user didn't specify a subdevice on the command line. + Try for one of these, in order: TV_RX, BASIC_RX, whatever is on side A. + + @return a subdev_spec + """ + return usrp.pick_subdev(u, (usrp_dbid.TV_RX, + usrp_dbid.TV_RX_REV_2, + usrp_dbid.TV_RX_REV_3, + usrp_dbid.BASIC_RX)) + +class wfm_rx_block (stdgui2.std_top_block): + def __init__(self,frame,panel,vbox,argv): + stdgui2.std_top_block.__init__ (self,frame,panel,vbox,argv) + + parser=OptionParser(option_class=eng_option) + parser.add_option("-R", "--rx-subdev-spec", type="subdev", default=None, + help="select USRP Rx side A or B (default=A)") + parser.add_option("-f", "--freq", type="eng_float", default=100.1e6, + help="set frequency to FREQ", metavar="FREQ") + parser.add_option("-g", "--gain", type="eng_float", default=65, + help="set gain in dB (default is midpoint)") + parser.add_option("-s", "--squelch", type="eng_float", default=0, + help="set squelch level (default is 0)") + parser.add_option("-V", "--volume", type="eng_float", default=None, + help="set volume (default is midpoint)") + parser.add_option("-O", "--audio-output", type="string", default="", + help="pcm device name. E.g., hw:0,0 or surround51 or /dev/dsp") + + + (options, args) = parser.parse_args() + if len(args) != 0: + parser.print_help() + sys.exit(1) + + self.frame = frame + self.panel = panel + + self.vol = 0 + self.state = "FREQ" + self.freq = 0 + + # build graph + + self.u = usrp.source_c() # usrp is data source + + adc_rate = self.u.adc_rate() # 64 MS/s + usrp_decim = 200 + self.u.set_decim_rate(usrp_decim) + usrp_rate = adc_rate / usrp_decim # 320 kS/s + chanfilt_decim = 1 + demod_rate = usrp_rate / chanfilt_decim + audio_decimation = 10 + audio_rate = 3*demod_rate / audio_decimation/2 # 48 kHz + + if options.rx_subdev_spec is None: + options.rx_subdev_spec = pick_subdevice(self.u) + + self.u.set_mux(usrp.determine_rx_mux_value(self.u, options.rx_subdev_spec)) + self.subdev = usrp.selected_subdev(self.u, options.rx_subdev_spec) + + + chan_filt_coeffs = gr.firdes.low_pass_2 (1, # gain + usrp_rate, # sampling rate + 90e3, # passband cutoff + 30e3, # transition bandwidth + 70, # stopband attenuation + gr.firdes.WIN_BLACKMAN) + print len(chan_filt_coeffs) + chan_filt = gr.fir_filter_ccf (chanfilt_decim, chan_filt_coeffs) + + self.rchan_sample = blks2.rational_resampler_fff(3,2) + self.lchan_sample = blks2.rational_resampler_fff(3,2) + + + #self.guts = blks2.wfm_rcv (demod_rate, audio_decimation) + self.guts = blks2.wfm_rcv_fmdet (demod_rate, audio_decimation) + + # FIXME rework {add,multiply}_const_* to handle multiple streams + self.volume_control_l = gr.multiply_const_ff(self.vol) + self.volume_control_r = gr.multiply_const_ff(self.vol) + + # sound card as final sink + audio_sink = audio.sink (int (audio_rate), + options.audio_output, + False) # ok_to_block + + # now wire it all together + self.connect (self.u, chan_filt, self.guts) + self.connect((self.guts, 0), self.lchan_sample,self.volume_control_l,(audio_sink,0)) + self.connect((self.guts, 1), self.rchan_sample,self.volume_control_r,(audio_sink,1)) + + try: + self.guts.stereo_carrier_pll_recovery.squelch_enable(True) + except: + print "FYI: This implementation of the stereo_carrier_pll_recovery has no squelch implementation yet" + + + self._build_gui(vbox, usrp_rate, demod_rate, audio_rate) + + if options.gain is None: + # if no gain was specified, use the mid-point in dB + g = self.subdev.gain_range() + options.gain = float(g[0]+g[1])/2 + + if options.volume is None: + g = self.volume_range() + options.volume = float(g[0]+g[1])/2 + + if abs(options.freq) < 1e6: + options.freq *= 1e6 + + # set initial values + + self.set_gain(options.gain) + self.set_vol(options.volume) + try: + self.guts.stereo_carrier_pll_recovery.set_lock_threshold(options.squelch) + except: + print "FYI: This implementation of the stereo_carrier_pll_recovery has no squelch implementation yet" + + if not(self.set_freq(options.freq)): + self._set_status_msg("Failed to set initial frequency") + + + def _set_status_msg(self, msg, which=0): + self.frame.GetStatusBar().SetStatusText(msg, which) + + + def _build_gui(self, vbox, usrp_rate, demod_rate, audio_rate): + + def _form_set_freq(kv): + return self.set_freq(kv['freq']) + + + if 1: + self.src_fft = fftsink2.fft_sink_c(self.panel, title="Data from USRP", + fft_size=512, sample_rate=usrp_rate, + ref_scale=32768.0, ref_level=0, y_divs=12) + self.connect (self.u, self.src_fft) + vbox.Add (self.src_fft.win, 4, wx.EXPAND) + + if 1: + post_fm_demod_fft = fftsink2.fft_sink_f(self.panel, title="Post FM Demod", + fft_size=512, sample_rate=demod_rate, + y_per_div=10, ref_level=0) + self.connect (self.guts.fm_demod, post_fm_demod_fft) + vbox.Add (post_fm_demod_fft.win, 4, wx.EXPAND) + + if 0: + post_stereo_carrier_generator_fft = fftsink2.fft_sink_c (self.panel, title="Post Stereo_carrier_generator", + fft_size=512, sample_rate=audio_rate, + y_per_div=10, ref_level=0) + self.connect (self.guts.stereo_carrier_generator, post_stereo_carrier_generator_fft) + vbox.Add (post_stereo_carrier_generator_fft.win, 4, wx.EXPAND) + + if 0: + post_deemphasis_left = fftsink2.fft_sink_f (self.panel, title="Post_Deemphasis_Left", + fft_size=512, sample_rate=audio_rate, + y_per_div=10, ref_level=0) + self.connect (self.guts.deemph_Left, post_deemphasis_left) + vbox.Add (post_deemphasis_left.win, 4, wx.EXPAND) + + if 0: + post_deemphasis_right = fftsink2.fft_sink_f(self.panel, title="Post_Deemphasis_Right", + fft_size=512, sample_rate=audio_rate, + y_per_div=10, ref_level=-20) + self.connect (self.guts.deemph_Left, post_deemphasis_right) + vbox.Add (post_deemphasis_right.win, 4, wx.EXPAND) + + + if 0: + LmR_fft = fftsink2.fft_sink_f(self.panel, title="LmR", + fft_size=512, sample_rate=audio_rate, + y_per_div=10, ref_level=-20) + self.connect (self.guts.LmR_real,LmR_fft) + vbox.Add (LmR_fft.win, 4, wx.EXPAND) + + if 0: + self.scope = scopesink2.scope_sink_f(self.panel, sample_rate=demod_rate) + self.connect (self.guts.fm_demod,self.scope) + vbox.Add (self.scope.win,4,wx.EXPAND) + + # control area form at bottom + self.myform = myform = form.form() + + hbox = wx.BoxSizer(wx.HORIZONTAL) + hbox.Add((5,0), 0) + myform['freq'] = form.float_field( + parent=self.panel, sizer=hbox, label="Freq", weight=1, + callback=myform.check_input_and_call(_form_set_freq, self._set_status_msg)) + + hbox.Add((5,0), 0) + myform['freq_slider'] = \ + form.quantized_slider_field(parent=self.panel, sizer=hbox, weight=3, + range=(87.9e6, 108.1e6, 0.1e6), + callback=self.set_freq) + hbox.Add((5,0), 0) + vbox.Add(hbox, 0, wx.EXPAND) + + hbox = wx.BoxSizer(wx.HORIZONTAL) + hbox.Add((5,0), 0) + + myform['volume'] = \ + form.quantized_slider_field(parent=self.panel, sizer=hbox, label="Volume", + weight=3, range=self.volume_range(), + callback=self.set_vol) + hbox.Add((5,0), 1) + + myform['gain'] = \ + form.quantized_slider_field(parent=self.panel, sizer=hbox, label="Gain", + weight=3, range=self.subdev.gain_range(), + callback=self.set_gain) + hbox.Add((5,0), 0) + + myform['sqlch_thrsh'] = \ + form.quantized_slider_field(parent=self.panel, sizer=hbox, label="Stereo Squelch Threshold", + weight=3, range=(0.0,1.0,0.01), + callback=self.set_squelch) + hbox.Add((5,0), 0) + vbox.Add(hbox, 0, wx.EXPAND) + + try: + self.knob = powermate.powermate(self.frame) + self.rot = 0 + powermate.EVT_POWERMATE_ROTATE (self.frame, self.on_rotate) + powermate.EVT_POWERMATE_BUTTON (self.frame, self.on_button) + except: + print "FYI: No Powermate or Contour Knob found" + + + def on_rotate (self, event): + self.rot += event.delta + if (self.state == "FREQ"): + if self.rot >= 3: + self.set_freq(self.freq + .1e6) + self.rot -= 3 + elif self.rot <=-3: + self.set_freq(self.freq - .1e6) + self.rot += 3 + else: + step = self.volume_range()[2] + if self.rot >= 3: + self.set_vol(self.vol + step) + self.rot -= 3 + elif self.rot <=-3: + self.set_vol(self.vol - step) + self.rot += 3 + + def on_button (self, event): + if event.value == 0: # button up + return + self.rot = 0 + if self.state == "FREQ": + self.state = "VOL" + else: + self.state = "FREQ" + self.update_status_bar () + + + def set_vol (self, vol): + g = self.volume_range() + self.vol = max(g[0], min(g[1], vol)) + self.volume_control_l.set_k(10**(self.vol/10)) + self.volume_control_r.set_k(10**(self.vol/10)) + self.myform['volume'].set_value(self.vol) + self.update_status_bar () + + def set_squelch(self,squelch_threshold): + try: + self.guts.stereo_carrier_pll_recovery.set_lock_threshold(squelch_threshold); + except: + print "FYI: This implementation of the stereo_carrier_pll_recovery has no squelch implementation yet" + + 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 down converter. + """ + r = usrp.tune(self.u, 0, self.subdev, target_freq) + + if r: + self.freq = target_freq + self.myform['freq'].set_value(target_freq) # update displayed value + self.myform['freq_slider'].set_value(target_freq) # update displayed value + self.update_status_bar() + self._set_status_msg("OK", 0) + return True + + self._set_status_msg("Failed", 0) + return False + + def set_gain(self, gain): + self.myform['gain'].set_value(gain) # update displayed value + self.subdev.set_gain(gain) + + def update_status_bar (self): + msg = "Volume:%r Setting:%s" % (self.vol, self.state) + self._set_status_msg(msg, 1) + self.src_fft.set_baseband_freq(self.freq) + + def volume_range(self): + return (-20.0, 0.0, 0.5) + + +if __name__ == '__main__': + app = stdgui2.stdapp (wfm_rx_block, "USRP WFM RX") + app.MainLoop () -- 2.30.2