From e6b031257f221ee0ca530eb71f79abe636f805fd Mon Sep 17 00:00:00 2001 From: Clayton Smith Date: Mon, 10 Feb 2020 07:37:39 -0500 Subject: [PATCH] Implement DDC using a frequency xlating FIR filter --- gqrx.pro | 2 + src/applications/gqrx/receiver.cpp | 47 ++++++------- src/applications/gqrx/receiver.h | 9 +-- src/dsp/CMakeLists.txt | 2 + src/dsp/downconverter.cpp | 106 +++++++++++++++++++++++++++++ src/dsp/downconverter.h | 61 +++++++++++++++++ 6 files changed, 200 insertions(+), 27 deletions(-) create mode 100644 src/dsp/downconverter.cpp create mode 100644 src/dsp/downconverter.h diff --git a/gqrx.pro b/gqrx.pro index 41cdbe449..cae9bb6ea 100644 --- a/gqrx.pro +++ b/gqrx.pro @@ -103,6 +103,7 @@ SOURCES += \ src/dsp/agc_impl.cpp \ src/dsp/correct_iq_cc.cpp \ src/dsp/filter/fir_decim.cpp \ + src/dsp/downconverter.cpp \ src/dsp/fm_deemph.cpp \ src/dsp/lpf.cpp \ src/dsp/rds/decoder_impl.cc \ @@ -157,6 +158,7 @@ HEADERS += \ src/dsp/correct_iq_cc.h \ src/dsp/filter/fir_decim.h \ src/dsp/filter/fir_decim_coef.h \ + src/dsp/downconverter.h \ src/dsp/fm_deemph.h \ src/dsp/lpf.h \ src/dsp/rds/api.h \ diff --git a/src/applications/gqrx/receiver.cpp b/src/applications/gqrx/receiver.cpp index 4114b08cd..9406cba6f 100644 --- a/src/applications/gqrx/receiver.cpp +++ b/src/applications/gqrx/receiver.cpp @@ -50,6 +50,7 @@ #endif #define DEFAULT_AUDIO_GAIN -6.0 +#define TARGET_QUAD_RATE 1e6 /** * @brief Public contructor. @@ -103,19 +104,21 @@ receiver::receiver(const std::string input_device, d_decim = 1; } - d_quad_rate = d_input_rate / (double)d_decim; + d_decim_rate = d_input_rate / (double)d_decim; } else { - d_quad_rate = d_input_rate; + d_decim_rate = d_input_rate; } + d_ddc_decim = std::max(1, (int)(d_decim_rate / TARGET_QUAD_RATE)); + d_quad_rate = d_decim_rate / d_ddc_decim; + ddc = make_downconverter_cc(d_ddc_decim, 0.0, d_decim_rate); rx = make_nbrx(d_quad_rate, d_audio_rate); - rot = gr::blocks::rotator_cc::make(0.0); iq_swap = make_iq_swap_cc(false); - dc_corr = make_dc_corr_cc(d_quad_rate, 1.0); - iq_fft = make_rx_fft_c(8192u, d_quad_rate, gr::filter::firdes::WIN_HANN); + dc_corr = make_dc_corr_cc(d_decim_rate, 1.0); + iq_fft = make_rx_fft_c(8192u, d_decim_rate, gr::filter::firdes::WIN_HANN); audio_fft = make_rx_fft_f(8192u, gr::filter::firdes::WIN_HANN); audio_gain0 = gr::blocks::multiply_const_ff::make(0); @@ -351,11 +354,13 @@ double receiver::set_input_rate(double rate) d_input_rate = rate; } - d_quad_rate = d_input_rate / (double)d_decim; - dc_corr->set_sample_rate(d_quad_rate); + d_decim_rate = d_input_rate / (double)d_decim; + d_ddc_decim = std::max(1, (int)(d_decim_rate / TARGET_QUAD_RATE)); + d_quad_rate = d_decim_rate / d_ddc_decim; + dc_corr->set_sample_rate(d_decim_rate); + ddc->set_decim_and_samp_rate(d_ddc_decim, d_decim_rate); rx->set_quad_rate(d_quad_rate); iq_fft->set_quad_rate(d_quad_rate); - update_ddc(); tb->unlock(); return d_input_rate; @@ -399,18 +404,20 @@ unsigned int receiver::set_input_decim(unsigned int decim) d_decim = 1; } - d_quad_rate = d_input_rate / (double)d_decim; + d_decim_rate = d_input_rate / (double)d_decim; } else { - d_quad_rate = d_input_rate; + d_decim_rate = d_input_rate; } // update quadrature rate - dc_corr->set_sample_rate(d_quad_rate); + d_ddc_decim = std::max(1, (int)(d_decim_rate / TARGET_QUAD_RATE)); + d_quad_rate = d_decim_rate / d_ddc_decim; + dc_corr->set_sample_rate(d_decim_rate); + ddc->set_decim_and_samp_rate(d_ddc_decim, d_decim_rate); rx->set_quad_rate(d_quad_rate); iq_fft->set_quad_rate(d_quad_rate); - update_ddc(); if (d_decim >= 2) { @@ -424,7 +431,7 @@ unsigned int receiver::set_input_decim(unsigned int decim) #ifdef CUSTOM_AIRSPY_KERNELS if (input_devstr.find("airspy") != std::string::npos) - src->set_bandwidth(d_quad_rate); + src->set_bandwidth(d_decim_rate); #endif if (d_running) @@ -648,7 +655,7 @@ receiver::status receiver::set_auto_gain(bool automatic) receiver::status receiver::set_filter_offset(double offset_hz) { d_filter_offset = offset_hz; - update_ddc(); + ddc->set_center_freq(d_filter_offset - d_cw_offset); return STATUS_OK; } @@ -667,7 +674,7 @@ double receiver::get_filter_offset(void) const receiver::status receiver::set_cw_offset(double offset_hz) { d_cw_offset = offset_hz; - update_ddc(); + ddc->set_center_freq(d_filter_offset - d_cw_offset); rx->set_cw_offset(d_cw_offset); return STATUS_OK; @@ -1329,8 +1336,8 @@ void receiver::connect_all(rx_chain type) // Audio path (if there is a receiver) if (type != RX_CHAIN_NONE) { - tb->connect(b, 0, rot, 0); - tb->connect(rot, 0, rx, 0); + tb->connect(b, 0, ddc, 0); + tb->connect(ddc, 0, rx, 0); tb->connect(rx, 0, audio_fft, 0); tb->connect(rx, 0, audio_udp_sink, 0); tb->connect(rx, 1, audio_udp_sink, 1); @@ -1354,12 +1361,6 @@ void receiver::connect_all(rx_chain type) } } -/** Convenience function to update all DDC related components. */ -void receiver::update_ddc() -{ - rot->set_phase_inc(2.0 * M_PI * (-d_filter_offset + d_cw_offset) / d_quad_rate); -} - void receiver::get_rds_data(std::string &outbuff, int &num) { rx->get_rds_data(outbuff, num); diff --git a/src/applications/gqrx/receiver.h b/src/applications/gqrx/receiver.h index 109a80dc1..b577ae9b7 100644 --- a/src/applications/gqrx/receiver.h +++ b/src/applications/gqrx/receiver.h @@ -31,7 +31,6 @@ #include #include -#include #include #include #include @@ -39,6 +38,7 @@ #include #include "dsp/correct_iq_cc.h" +#include "dsp/downconverter.h" #include "dsp/filter/fir_decim.h" #include "dsp/rx_noise_blanker_cc.h" #include "dsp/rx_filter.h" @@ -226,14 +226,15 @@ class receiver private: void connect_all(rx_chain type); - void update_ddc(); private: bool d_running; /*!< Whether receiver is running or not. */ double d_input_rate; /*!< Input sample rate. */ - double d_quad_rate; /*!< Quadrature rate (input_rate / decim) */ + double d_decim_rate; /*!< Rate after decimation (input_rate / decim) */ + double d_quad_rate; /*!< Quadrature rate (after down-conversion) */ double d_audio_rate; /*!< Audio output rate. */ unsigned int d_decim; /*!< input decimation. */ + unsigned int d_ddc_decim; /*!< Down-conversion decimation. */ double d_rf_freq; /*!< Current RF frequency. */ double d_filter_offset; /*!< Current filter offset */ double d_cw_offset; /*!< CW offset */ @@ -261,7 +262,7 @@ class receiver rx_fft_c_sptr iq_fft; /*!< Baseband FFT block. */ rx_fft_f_sptr audio_fft; /*!< Audio FFT block. */ - gr::blocks::rotator_cc::sptr rot; /*!< Rotator used when only shifting frequency */ + downconverter_cc_sptr ddc; /*!< Digital down-converter for demod chain. */ gr::blocks::multiply_const_ff::sptr audio_gain0; /*!< Audio gain block. */ gr::blocks::multiply_const_ff::sptr audio_gain1; /*!< Audio gain block. */ diff --git a/src/dsp/CMakeLists.txt b/src/dsp/CMakeLists.txt index f160c0cbf..dd0174f30 100644 --- a/src/dsp/CMakeLists.txt +++ b/src/dsp/CMakeLists.txt @@ -22,6 +22,8 @@ add_source_files(SRCS_LIST agc_impl.h correct_iq_cc.cpp correct_iq_cc.h + downconverter.cpp + downconverter.h fm_deemph.cpp fm_deemph.h lpf.cpp diff --git a/src/dsp/downconverter.cpp b/src/dsp/downconverter.cpp new file mode 100644 index 000000000..dde591e1c --- /dev/null +++ b/src/dsp/downconverter.cpp @@ -0,0 +1,106 @@ +/* -*- c++ -*- */ +/* + * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt + * http://gqrx.dk/ + * + * Copyright 2020 Clayton Smith VE3IRR. + * + * Gqrx 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. + * + * Gqrx 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 Gqrx; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ +#include +#include +#include + +#include "downconverter.h" + +#define LPF_CUTOFF 120e3 + +downconverter_cc_sptr make_downconverter_cc(unsigned int decim, double center_freq, double samp_rate) +{ + return gnuradio::get_initial_sptr(new downconverter_cc(decim, center_freq, samp_rate)); +} + +downconverter_cc::downconverter_cc(unsigned int decim, double center_freq, double samp_rate) + : gr::hier_block2("downconverter_cc", + gr::io_signature::make(1, 1, sizeof(gr_complex)), + gr::io_signature::make(1, 1, sizeof(gr_complex))), + d_decim(decim), + d_center_freq(center_freq), + d_samp_rate(samp_rate) +{ + connect_all(); + update_proto_taps(); + update_phase_inc(); +} + +downconverter_cc::~downconverter_cc() +{ + +} + +void downconverter_cc::set_decim_and_samp_rate(unsigned int decim, double samp_rate) +{ + d_samp_rate = samp_rate; + if (decim != d_decim) + { + d_decim = decim; + lock(); + disconnect_all(); + connect_all(); + unlock(); + } + update_proto_taps(); + update_phase_inc(); +} + +void downconverter_cc::set_center_freq(double center_freq) +{ + d_center_freq = center_freq; + update_phase_inc(); +} + +void downconverter_cc::connect_all() +{ + if (d_decim > 1) + { + filt = gr::filter::freq_xlating_fir_filter_ccf::make(d_decim, {1}, 0.0, d_samp_rate); + connect(self(), 0, filt, 0); + connect(filt, 0, self(), 0); + } + else + { + rot = gr::blocks::rotator_cc::make(0.0); + connect(self(), 0, rot, 0); + connect(rot, 0, self(), 0); + } +} + +void downconverter_cc::update_proto_taps() +{ + if (d_decim > 1) + { + double out_rate = d_samp_rate / d_decim; + filt->set_taps(gr::filter::firdes::low_pass(1.0, d_samp_rate, LPF_CUTOFF, out_rate - 2*LPF_CUTOFF)); + } +} + +void downconverter_cc::update_phase_inc() +{ + if (d_decim > 1) + filt->set_center_freq(d_center_freq); + else + rot->set_phase_inc(-2.0 * M_PI * d_center_freq / d_samp_rate); +} diff --git a/src/dsp/downconverter.h b/src/dsp/downconverter.h new file mode 100644 index 000000000..9c3b55ad8 --- /dev/null +++ b/src/dsp/downconverter.h @@ -0,0 +1,61 @@ +/* -*- c++ -*- */ +/* + * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt + * http://gqrx.dk/ + * + * Copyright 2020 Clayton Smith VE3IRR. + * + * Gqrx 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. + * + * Gqrx 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 Gqrx; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ +#pragma once + +#if GNURADIO_VERSION < 0x030800 +#include +#else +#include +#endif + +#include +#include + +class downconverter_cc; + +typedef boost::shared_ptr downconverter_cc_sptr; +downconverter_cc_sptr make_downconverter_cc(unsigned int decim, double center_freq, double samp_rate); + +class downconverter_cc : public gr::hier_block2 +{ + friend downconverter_cc_sptr make_downconverter_cc(unsigned int decim, double center_freq, double samp_rate); + +public: + downconverter_cc(unsigned int decim, double center_freq, double samp_rate); + ~downconverter_cc(); + void set_decim_and_samp_rate(unsigned int decim, double samp_rate); + void set_center_freq(double center_freq); + +private: + unsigned int d_decim; + double d_center_freq; + double d_samp_rate; + std::vector d_proto_taps; + + void connect_all(); + void update_proto_taps(); + void update_phase_inc(); + + gr::filter::freq_xlating_fir_filter_ccf::sptr filt; + gr::blocks::rotator_cc::sptr rot; +};