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")