Skip to content

Commit

Permalink
Implement DDC using a frequency xlating FIR filter
Browse files Browse the repository at this point in the history
  • Loading branch information
argilo authored and csete committed Apr 19, 2020
1 parent 2910cb6 commit e6b0312
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 27 deletions.
2 changes: 2 additions & 0 deletions gqrx.pro
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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 \
Expand Down
47 changes: 24 additions & 23 deletions src/applications/gqrx/receiver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
#endif

#define DEFAULT_AUDIO_GAIN -6.0
#define TARGET_QUAD_RATE 1e6

/**
* @brief Public contructor.
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand All @@ -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)
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
9 changes: 5 additions & 4 deletions src/applications/gqrx/receiver.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@

#include <gnuradio/blocks/file_sink.h>
#include <gnuradio/blocks/null_sink.h>
#include <gnuradio/blocks/rotator_cc.h>
#include <gnuradio/blocks/wavfile_sink.h>
#include <gnuradio/blocks/wavfile_source.h>
#include <gnuradio/top_block.h>
#include <osmosdr/source.h>
#include <string>

#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"
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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. */
Expand Down
2 changes: 2 additions & 0 deletions src/dsp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
106 changes: 106 additions & 0 deletions src/dsp/downconverter.cpp
Original file line number Diff line number Diff line change
@@ -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 <math.h>
#include <gnuradio/filter/firdes.h>
#include <gnuradio/io_signature.h>

#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);
}
61 changes: 61 additions & 0 deletions src/dsp/downconverter.h
Original file line number Diff line number Diff line change
@@ -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 <gnuradio/filter/freq_xlating_fir_filter_ccf.h>
#else
#include <gnuradio/filter/freq_xlating_fir_filter.h>
#endif

#include <gnuradio/blocks/rotator_cc.h>
#include <gnuradio/hier_block2.h>

class downconverter_cc;

typedef boost::shared_ptr<downconverter_cc> 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<float> 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;
};

0 comments on commit e6b0312

Please sign in to comment.