This is my first ever experience with GNU Radio. I am trying to implement a simple FM receiver shown below with its C++ API.

I seem to have pedantically deconstructed this receiver into its smallest components and implemented them with GNU Radio's C++ API but does not work.

It runs but I hear no sound which makes me suspect I have misconfigured some component, missed to set some sort of gain, etc.

Any feedback is appreciated. Thank you.

FM Receiver in GNU Radio

#include <gnuradio/analog/quadrature_demod_cf.h>
#include <gnuradio/audio/sink.h>
#include <gnuradio/blocks/null_sink.h>
#include <gnuradio/filter/fir_filter_blk.h>
#include <gnuradio/filter/firdes.h>
#include <gnuradio/filter/freq_xlating_fir_filter.h>
#include <gnuradio/filter/iir_filter_ffd.h>
#include <gnuradio/filter/pfb_arb_resampler_ccf.h>
#include <gnuradio/top_block.h>
#include <osmosdr/source.h>
#include <gnuradio/analog/simple_squelch_cc.h>

void calculate_iir_taps(double tau, float d_quad_rate);

std::vector<double> d_fftaps;  /*! Feed forward taps. */
std::vector<double> d_fbtaps;  /*! Feed back taps. */

using namespace gr;

int main(int argc, char** argv)
    int sample_rate = 2.88e6; // audio card sample rate
    int audio_decimation = 5;

    // Build a top block that ill contain flowgraph blocks
    top_block_sptr top_block = make_top_block("fm_radio");

    osmosdr::source::sptr osmocom = osmosdr::source::make("rtl=0,repeat=true,throttle=true");
    osmocom->set_gain(20, 0);
    osmocom->set_if_gain(20, 0);
    osmocom->set_bb_gain(20, 0);

    // Low pass filter
    unsigned int flt_size = 1024;
    std::vector<float> low_pass_filter = filter::firdes::low_pass(1, sample_rate, 100e3, 10e3);
    filter::pfb_arb_resampler_ccf::sptr d_filter = filter::pfb_arb_resampler_ccf::make(288, low_pass_filter, flt_size);

    // WBFM Receive
    int max_dev = 75e3;
    int quad_rate = 288e3;
    float fm_demod_gain = quad_rate / (2 * M_PI * max_dev);
    analog::quadrature_demod_cf::sptr d_quad = analog::quadrature_demod_cf::make(fm_demod_gain);


    calculate_iir_taps(50.0e-6, quad_rate);

    filter::iir_filter_ffd::sptr d_deemph = \
        filter::iir_filter_ffd::make(d_fftaps, d_fbtaps, false);

    int audio_rate = quad_rate / audio_decimation;
    int width_of_transition_band = audio_rate / 32;
    std::vector<float> d_pll_taps = \
        filter::firdes::low_pass(1, quad_rate, audio_rate / 2 - width_of_transition_band, width_of_transition_band);
    filter::fir_filter_fff::sptr low_pass_filter2 = \
        filter::fir_filter_fff::make(1, d_pll_taps);

    // Rational Resamplifier - might not be needed hence not used atm
    filter::freq_xlating_fir_filter_ccf::sptr filt = \
    gr::filter::freq_xlating_fir_filter_ccf::make(288, {1}, 0.0, sample_rate);

    audio::sink::sptr sink = audio::sink::make(48000);

    top_block->connect(osmocom, 0, d_filter, 0);
    top_block->connect(d_filter, 0, d_quad, 0);
    top_block->connect(d_quad, 0, d_deemph, 0);
    top_block->connect(d_deemph, 0, low_pass_filter2, 0);
    top_block->connect(low_pass_filter2, 0, sink, 0);


    return 0;

void calculate_iir_taps(double tau, float d_quad_rate)
    if (tau > 1.0e-9)
        // copied from fm_emph.py in gr-analog
        double  w_c;    // Digital corner frequency
        double  w_ca;   // Prewarped analog corner frequency
        double  k, z1, p1, b0;
        double  fs = d_quad_rate;

        w_c = 1.0 / tau;
        w_ca = 2.0 * fs * tan(w_c / (2.0 * fs));

        // Resulting digital pole, zero, and gain term from the bilinear
        // transformation of H(s) = w_ca / (s + w_ca) to
        // H(z) = b0 (1 - z1 z^-1)/(1 - p1 z^-1)
        k = -w_ca / (2.0 * fs);
        z1 = -1.0;
        p1 = (1.0 + k) / (1.0 - k);
        b0 = -k / (1.0 - k);

        d_fftaps[0] = b0;
        d_fftaps[1] = -z1 * b0;
        d_fbtaps[0] = 1.0;
        d_fbtaps[1] = -p1;
        d_fftaps[0] = 1.0;
        d_fftaps[1] = 0.0;
        d_fbtaps[0] = 0.0;
        d_fbtaps[1] = 0.0;

CMakeLists.txt for those wanting to try it out:

cmake_minimum_required(VERSION 3.8)



add_executable(fm_radio fm_radio.cpp)
target_link_libraries(fm_radio ${GR_LIBRARIES})
  • $\begingroup$ Hello Oleksandr, and welcome to ham.stackexchange.com! $\endgroup$ – rclocher3 Jul 13 at 14:01
  • 1
    $\begingroup$ Cheers, @rclocher3! $\endgroup$ – Oleksandr Kravchuk Jul 13 at 16:39

There is at least one error in your code that would result in silence:

std::vector<double> d_fftaps;  /*! Feed forward taps. */
std::vector<double> d_fbtaps;  /*! Feed back taps. */

filter::iir_filter_ffd::sptr d_deemph = \
    filter::iir_filter_ffd::make(d_fftaps, d_fbtaps, false);

The taps of this IIR filter are all zero (because you resized the vectors but did not write any values into them), so the output signal will be always zero.

As a quick test to see if this is the sole problem, you can remove the deemphasis filter block entirely; the audio frequency response will be wrong but you'll still be able to tell the difference.

You could also remove the squelch block temporarily, as one fewer thing to go wrong. The absolute minimum you need for a receiver that will pass some coherent signal is a decimating low-pass filter to select the desired signal and adapt the sample rate, and the quadrature demodulator; everything else is refinement.

| improve this answer | |
  • $\begingroup$ Thanks for your assistance. You were right, I have completely forgotten to set up the taps. I have borrowed the code for calculating iir taps from Gqrx and can now hear some noise. The main suspect is my implementation of the WBFM Receive block which unfortunately has no C++ implementation, so I had to copy it from the Python blocks it is comprised of. I have updated the code. Is there an example of a simple FM receiver without usage of WBFM Recive block, so I could study it? Thank you again. $\endgroup$ – Oleksandr Kravchuk Jul 13 at 1:55
  • $\begingroup$ @OleksandrKravchuk Gqrx is the one example I'd know of to point you at. I recommend focusing on understanding your signal processing stages and testing them individually to ensure they behave as you expect (and you could run them with file input and output blocks and compare the results to GRC/Python versions), rather than looking for examples to copy. $\endgroup$ – Kevin Reid AG6YO Jul 13 at 2:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.