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.
This commit is contained in:
Manolis Surligas 2019-12-12 21:53:39 +02:00
parent fe3bd06dc1
commit 96aaf11a30
13 changed files with 213 additions and 29 deletions

View File

@ -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

View File

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

View File

@ -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

View File

@ -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

View File

@ -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})

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 */

View File

@ -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

View File

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

View File

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

View File

@ -0,0 +1,127 @@
#! /usr/bin/python3
#
# gr-satnogs: SatNOGS GNU Radio Out-Of-Tree Module
#
# Copyright (C) 2019
# Libre Space Foundation <http://libre.space>
#
# 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 <http://www.gnu.org/licenses/>
#
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")