From 96aaf11a303885e8c99585560b856c880ac1b564 Mon Sep 17 00:00:00 2001 From: Manolis Surligas Date: Thu, 12 Dec 2019 21:53:39 +0200 Subject: [PATCH] Improve doppler correction and decimation This commit adds a hierarchical block that performs Doppler compensation and resampling. Rather than using directly the available Doppler correction blocks, based on the user parameters of the incoming sampling rate and the desired target sampling rate, it applies proper decimation on the signal so the frequency shift to be applied to a smaller sampling rate, reducing significantly the CPU resources required. At the previous architecture (gr-satnogs 1.x.x) we used seperate blocks for the doppler correction and the LO digital shift, operating at the device sampling rate. These two blocks, performing almost the same operation, contributed to a 30% CPU usage of the overall application. Now the LO is compensated by the Doppler correction block, taking into account at the same time the Doppler drift. After the digital LO shift, the Doppler corrected signal is passed through an Polyphase Arbitrary Resampler, to match exactly the sampling rate requested by the user. --- CMakeLists.txt | 7 +- cmake/Modules/satnogsConfig.cmake | 2 + grc/CMakeLists.txt | 1 + grc/satnogs.tree.yml | 1 + ...ogs_coarse_doppler_correction_cc.block.yml | 14 +- grc/satnogs_cw_to_symbol.block.yml | 7 +- grc/satnogs_doppler_compensation.block.yml | 47 +++++++ .../satnogs/coarse_doppler_correction_cc.h | 5 +- lib/coarse_doppler_correction_cc_impl.cc | 23 ++-- lib/coarse_doppler_correction_cc_impl.h | 6 +- python/CMakeLists.txt | 1 + python/__init__.py | 1 + python/doppler_compensation.py | 127 ++++++++++++++++++ 13 files changed, 213 insertions(+), 29 deletions(-) create mode 100644 grc/satnogs_doppler_compensation.block.yml create mode 100644 python/doppler_compensation.py diff --git a/CMakeLists.txt b/CMakeLists.txt index d3d6ee8..293ab0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,12 +136,17 @@ endif(APPLE) # Find gr-satnogs build dependencies ######################################################################## find_package(Doxygen) -find_package(SWIG) +find_package(SWIG REQUIRED) find_package(Volk REQUIRED) find_package(OggVorbis REQUIRED) find_package(PNG REQUIRED) find_package(png++ REQUIRED) find_package(JsonCpp REQUIRED) +find_package(soapy) + +if(NOT soapy_FOUND) + message(WARNING "gr-soapy not found. Flowgraphs may not be able to execute!") +endif() ######################################################################## # Include or not into the module blocks for debugging diff --git a/cmake/Modules/satnogsConfig.cmake b/cmake/Modules/satnogsConfig.cmake index 855a285..97d5427 100644 --- a/cmake/Modules/satnogsConfig.cmake +++ b/cmake/Modules/satnogsConfig.cmake @@ -24,6 +24,8 @@ FIND_LIBRARY( /usr/lib64 ) +include("${CMAKE_CURRENT_LIST_DIR}/gnuradio-satnogsTargets.cmake") + INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(SATNOGS DEFAULT_MSG SATNOGS_LIBRARIES SATNOGS_INCLUDE_DIRS) MARK_AS_ADVANCED(SATNOGS_LIBRARIES SATNOGS_INCLUDE_DIRS) diff --git a/grc/CMakeLists.txt b/grc/CMakeLists.txt index 07ede58..d51f901 100644 --- a/grc/CMakeLists.txt +++ b/grc/CMakeLists.txt @@ -31,6 +31,7 @@ list(APPEND enabled_blocks satnogs_ax25_encoder_mb.block.yml satnogs_ax100_decoder.block.yml satnogs_coarse_doppler_correction_cc.block.yml + satnogs_doppler_compensation.block.yml satnogs_doppler_correction_cc.block.yml satnogs_frame_decoder.block.yml satnogs_frame_file_sink.block.yml diff --git a/grc/satnogs.tree.yml b/grc/satnogs.tree.yml index 865c519..308d0f4 100644 --- a/grc/satnogs.tree.yml +++ b/grc/satnogs.tree.yml @@ -13,6 +13,7 @@ - satnogs_lrpt_sync - satnogs_ax25_encoder_mb - satnogs_coarse_doppler_correction_cc + - satnogs_doppler_compensation - satnogs_doppler_correction_cc - satnogs_frame_decoder - satnogs_frame_file_sink diff --git a/grc/satnogs_coarse_doppler_correction_cc.block.yml b/grc/satnogs_coarse_doppler_correction_cc.block.yml index 6b8c0d0..dac25f2 100644 --- a/grc/satnogs_coarse_doppler_correction_cc.block.yml +++ b/grc/satnogs_coarse_doppler_correction_cc.block.yml @@ -6,19 +6,25 @@ parameters: label: Target frequency dtype: real +- id: offset + label: Offset + dtype: real + default: 0.0 + - id: sampling_rate label: Sample Rate dtype: real default: samp_rate inputs: -- id: freq - domain: message - - label: in domain: stream dtype: complex +- id: freq + domain: message + optional: true + outputs: - label: out domain: stream @@ -26,7 +32,7 @@ outputs: templates: imports: import satnogs - make: satnogs.coarse_doppler_correction_cc(${target_freq}, ${sampling_rate}) + make: satnogs.coarse_doppler_correction_cc(${target_freq}, ${offset}, ${sampling_rate}) callbacks: - set_new_freq_locked(${target_freq}) diff --git a/grc/satnogs_cw_to_symbol.block.yml b/grc/satnogs_cw_to_symbol.block.yml index 375e5fb..7c1f7b0 100644 --- a/grc/satnogs_cw_to_symbol.block.yml +++ b/grc/satnogs_cw_to_symbol.block.yml @@ -27,13 +27,12 @@ parameters: default: 0 inputs: -- domain: message - id: act_threshold - optiona: true - - label: in domain: stream dtype: float + +- domain: message + id: act_threshold outputs: - domain: message diff --git a/grc/satnogs_doppler_compensation.block.yml b/grc/satnogs_doppler_compensation.block.yml new file mode 100644 index 0000000..944218f --- /dev/null +++ b/grc/satnogs_doppler_compensation.block.yml @@ -0,0 +1,47 @@ +id: satnogs_doppler_compensation +label: Doppler Compensation + +templates: + imports: import satnogs + make: satnogs.doppler_compensation(${samp_rate}, ${sat_freq}, ${lo_offset}, ${out_samp_rate}, ${compensate}) + + +parameters: +- id: samp_rate + label: Input Sampling Rate + dtype: real + +- id: sat_freq + label: Satellite Frequency + dtype: real + +- id: lo_offset + label: LO Offset + dtype: real + +- id: out_samp_rate + label: Target Sampling Rate + dtype: real + +- id: compensate + label: Compensate Doppler + dtype: int + options: [0, 1] + option_labels: ['Off', 'On'] + default: 1 + +inputs: +- label: in + domain: stream + dtype: complex + +- label: doppler + domain: message + optional: 1 + +outputs: +- label: out + domain: stream + dtype: complex + +file_format: 1 diff --git a/include/satnogs/coarse_doppler_correction_cc.h b/include/satnogs/coarse_doppler_correction_cc.h index 1df021b..b18b8a8 100644 --- a/include/satnogs/coarse_doppler_correction_cc.h +++ b/include/satnogs/coarse_doppler_correction_cc.h @@ -47,10 +47,13 @@ public: * The message input \p freq receives periodically messages containing * the predicted absolute frequency of the satellite at that specific time. * @param target_freq the absolute frequency of the satellite + * @param offset the frequency offset from the actuall target frequency. + * This is very common on SDR receivers to avoid DC spikes at the center + * frequency. This block can automatically compensate this offset * @param sampling_rate the sampling rate of the signal */ static sptr - make(double target_freq, double sampling_rate); + make(double target_freq, double offset, double sampling_rate); }; } // namespace satnogs diff --git a/lib/coarse_doppler_correction_cc_impl.cc b/lib/coarse_doppler_correction_cc_impl.cc index 7e6a19d..c9951be 100644 --- a/lib/coarse_doppler_correction_cc_impl.cc +++ b/lib/coarse_doppler_correction_cc_impl.cc @@ -32,24 +32,27 @@ namespace satnogs { coarse_doppler_correction_cc::sptr coarse_doppler_correction_cc::make(double target_freq, + double offset, double sampling_rate) { return gnuradio::get_initial_sptr( - new coarse_doppler_correction_cc_impl(target_freq, sampling_rate)); + new coarse_doppler_correction_cc_impl(target_freq, offset, + sampling_rate)); } /* * The private constructor */ coarse_doppler_correction_cc_impl::coarse_doppler_correction_cc_impl( - double target_freq, double sampling_rate) : + double target_freq, double offset, double sampling_rate) : gr::sync_block("coarse_doppler_correction_cc", gr::io_signature::make(1, 1, sizeof(gr_complex)), gr::io_signature::make(1, 1, sizeof(gr_complex))), d_target_freq(target_freq), + d_offset(offset), d_samp_rate(sampling_rate), d_buf_items(std::min((size_t)8192UL, (size_t)(d_samp_rate / 4))), - d_freq_diff(0), + d_freq_diff(d_offset), d_nco() { message_port_register_in(pmt::mp("freq")); @@ -70,7 +73,7 @@ coarse_doppler_correction_cc_impl::coarse_doppler_correction_cc_impl( pmt::mp("freq"), boost::bind(&coarse_doppler_correction_cc_impl::new_freq, this, _1)); - d_nco.set_freq(0); + d_nco.set_freq((2 * M_PI * (-d_freq_diff)) / d_samp_rate); /* Allocate aligned memory for the NCO */ d_nco_buff = (gr_complex *) volk_malloc( (d_samp_rate / 4) * sizeof(gr_complex), 32); @@ -82,10 +85,9 @@ coarse_doppler_correction_cc_impl::coarse_doppler_correction_cc_impl( void coarse_doppler_correction_cc_impl::new_freq(pmt::pmt_t msg) { - boost::mutex::scoped_lock lock(d_mutex); double new_freq; new_freq = pmt::to_double(msg); - d_freq_diff = new_freq - d_target_freq; + d_freq_diff = new_freq - (d_target_freq - d_offset); d_nco.set_freq((2 * M_PI * (-d_freq_diff)) / d_samp_rate); } @@ -113,15 +115,6 @@ coarse_doppler_correction_cc_impl::work( return noutput_items; } -void -coarse_doppler_correction_cc_impl::set_target_freq(double freq) -{ - boost::mutex::scoped_lock lock(d_mutex); - d_target_freq = freq; - d_freq_diff = 0.0; - d_nco.set_freq(0); -} - } /* namespace satnogs */ } /* namespace gr */ diff --git a/lib/coarse_doppler_correction_cc_impl.h b/lib/coarse_doppler_correction_cc_impl.h index a3d632c..e09c8ae 100644 --- a/lib/coarse_doppler_correction_cc_impl.h +++ b/lib/coarse_doppler_correction_cc_impl.h @@ -30,19 +30,20 @@ namespace satnogs { class coarse_doppler_correction_cc_impl : public coarse_doppler_correction_cc { private: double d_target_freq; + const double d_offset; const double d_samp_rate; const size_t d_buf_items; double d_freq_diff; gr::fxpt_nco d_nco; gr_complex *d_nco_buff; - boost::mutex d_mutex; void new_freq(pmt::pmt_t msg); public: coarse_doppler_correction_cc_impl(double target_freq, + double offset, double sampling_rate); ~coarse_doppler_correction_cc_impl(); @@ -50,9 +51,6 @@ public: int work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items); - - void - set_target_freq(double freq); }; } // namespace satnogs diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 8148c44..a8af9d4 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -38,6 +38,7 @@ GR_PYTHON_INSTALL( hw_settings.py utils.py ${CMAKE_BINARY_DIR}/satnogs_info.py + doppler_compensation.py DESTINATION ${GR_PYTHON_DIR}/satnogs ) diff --git a/python/__init__.py b/python/__init__.py index c67bd4e..00c050c 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -30,6 +30,7 @@ try: # this might fail if the module is python-only from .satnogs_swig import * from .hw_settings import * + from .doppler_compensation import doppler_compensation from .utils import * except ImportError as err: sys.stderr.write("Failed to import SatNOGS ({})\n".format(err)) diff --git a/python/doppler_compensation.py b/python/doppler_compensation.py new file mode 100644 index 0000000..89e9ec1 --- /dev/null +++ b/python/doppler_compensation.py @@ -0,0 +1,127 @@ +#! /usr/bin/python3 +# +# gr-satnogs: SatNOGS GNU Radio Out-Of-Tree Module +# +# Copyright (C) 2019 +# Libre Space Foundation +# +# This program 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 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see +# + +from gnuradio import gr +from gnuradio import filter +from gnuradio.filter import firdes +from gnuradio.filter import pfb +from . import satnogs_swig as satnogs +import weakref + + +class doppler_compensation(gr.hier_block2): + """ Doppler compensation and resampling block + + This block performs Doppler compensation and resampling. + Rather than using directly the available Doppler correction blocks, + based on the user parameters of the incoming sampling rate and the + desired target sampling rate, it applies proper decimation on the signal + so the frequency shift to be applied to a smaller sampling rate, + reducing significantly the CPU resources required. At the previous + architecture (gr-satnogs 1.x.x) we used seperate blocks for the doppler + correction and the LO digital shift, operating at the device sampling rate. + These two blocks, performing almost the same operation, contributed to a + 30% CPU usage of the overall application. Now the LO is compensated by + the Doppler correction block, taking into account at the same time the + Doppler drift. + + After the digital LO shift, the Doppler corrected signal is passed through + an Polyphase Arbitrary Resampler, to match exactly the sampling rate + requested by the user. + + Parameters + ---------- + samp_rate : double + The sampling rate of the input signal + sat_freq : double + The target frequency of the satellite. This highly depends on the + Doppler frequency messages. Some blocks (e.g rigctld) may produce + the target observed RF frequency, whereas others (e.g gr-leo) are + sending messages containing only the frequency drift. In the first + case, this field should contain the actual frequency of the satellite. + On the other hand, for the second case this field should be 0. + lo_offset : double + The LO offset from the actuall observation frequency. In most cases, + we use a LO offset to avoid the DC spikes. This offset should be positive + if the hardware RF frequency is less than the target frequency, negative + otherwise. + compensate : bool + This parameter instructs the Doppler correction block to apply doppler + compensation. The LO offset compensation is still applied regardless of + the value of this field. + """ + + def __init__(self, samp_rate, sat_freq, lo_offset, out_samp_rate, + compensate=True): + gr.hier_block2.__init__(self, + "doppler_compensation", + gr.io_signature(1, 1, gr.sizeof_gr_complex), + gr.io_signature(1, 1, gr.sizeof_gr_complex)) + + self.message_port_register_hier_in('doppler') + + # Decimate the incoming signal using the rational resampler first. + # Then perform the doppler correctio and a fractional resampler + # to a lower rate to save some CPU cycles + if(out_samp_rate > samp_rate): + gr.log.info("satnogs.doppler_compensation: Output sampling rate sould be " + "less or equal the device sampling rate") + raise AttributeError + + if(lo_offset > samp_rate // 4): + gr.log.info("satnogs.doppler_compensation: The LO offset frequency " + "should be > samp_rate / 4") + raise AttributeError + + self.decimation = 1 + min_s = max(abs(2 * lo_offset) + 40e3, out_samp_rate + 2 * abs(lo_offset)) + while(samp_rate / (self.decimation + 1) > min_s): + self.decimation = self.decimation + 1 + + print(self.decimation) + if(self.decimation > 1): + self.dec = filter.rational_resampler_ccc(interpolation=1, + decimation=self.decimation, + taps=None, + fractional_bw=None) + + # Even with no doppler compensation enabled we need this + # block to correct the LO offset + self.doppler = satnogs.coarse_doppler_correction_cc(sat_freq, + lo_offset, + samp_rate / self.decimation) + + self.pfb_rs = pfb.arb_resampler_ccf( + out_samp_rate / (samp_rate / self.decimation), + taps=None, + flt_size=32) + self.pfb_rs.declare_sample_delay(0) + + if(self.decimation > 1): + self.connect((self, 0), (self.dec, 0), + (self.doppler, 0), (self.pfb_rs, 0), (self, 0)) + else: + self.connect((self, 0), (self.doppler, 0), + (self.pfb_rs, 0), (self, 0)) + + if(compensate): + self.msg_connect(weakref.proxy(self), "doppler", + self.doppler, "freq")