utils: Import the gmr_multi_rx tool to listen to RF

Written-by: Dimitri Stolnikov <horiz0n@gmx.net>
Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
This commit is contained in:
Dimitri Stolnikov 2011-10-16 12:42:11 +02:00 committed by Sylvain Munaut
parent 06aee3401b
commit 213492b7f5
5 changed files with 748 additions and 0 deletions

View File

@ -1 +1,2 @@
Sylvain Munaut <tnt@246tNt.com>
Dimitri Stolnikov <horiz0n@gmx.net>

1
utils/gmr_multi_rx/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
gmr_multi_rx

View File

@ -0,0 +1,138 @@
/* Conversion of Python routines from Sylvain Munaut <tnt@246tNt.com>
* (C) 2011 by Dimitri Stolnikov <horiz0n@gmx.net>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FILTER_HELPERS_HPP
#define FILTER_HELPERS_HPP
#include <boost/math/common_factor.hpp> // gcd
#include <cmath> // ceil, floor
int gcd( int n, int d )
{
return boost::math::gcd( n, d );
}
void reduce_fract( int & n_out, int & d_out, int n_in, int d_in )
{
int g = gcd( n_in, d_in );
n_out = n_in / g;
d_out = d_in / g;
}
bool find_decim( int & hw_decim, int & sw_decim, int & ai, int d, int max_decim )
{
int x = max_decim;
while ( x >= 4 )
{
if ( d % x == 0 )
{
// If it's odd, the usrp can't do it. But maybe we can decimate twice
// as much and then interpolate more in sw
if ( x % 2 == 1 )
{
if ( x * 2 > max_decim )
{
x--;
continue;
}
hw_decim = 2 * x;
sw_decim = d / x;
ai = 2;
return true;
}
else
{
hw_decim = x;
sw_decim = d / x;
ai = 1;
return true;
}
}
x--;
}
hw_decim = 0;
sw_decim = 0;
ai = 1;
return false;
}
void find_split( int & first_decim, int & second_decim, int decim, int inter)
{
int mx = decim / (inter * 2);
first_decim = 1;
for ( int x = 2; x < mx + 1; x++ )
{
if (decim % x == 0)
first_decim = x;
}
second_decim = decim / first_decim;
}
void compute_filter_params( int & hw_decim,
int & s1_decim,
int & s2_decim,
int & s2_inter,
int req_bw,
int fpga_freq,
int symbol_rate,
int osr )
{
// Required samplerate
int req_symrate = int(std::ceil(req_bw / symbol_rate)) * symbol_rate;
if ( req_symrate < osr * symbol_rate )
req_symrate = osr * symbol_rate;
// Find the total decim / inter ratio for 1 channel
int tot_inter, tot_decim;
reduce_fract( tot_inter, tot_decim, osr * symbol_rate, fpga_freq);
// Need to split the decimation between USRP and SW
// (but we need _at_ least req_symrate out of the USRP)
int max_decim = int(std::floor(fpga_freq / req_symrate));
if ( max_decim > 256 )
max_decim = 256;
int sw_decim, ai;
find_decim( hw_decim, sw_decim, ai, tot_decim, max_decim );
if ( hw_decim == 0 )
{
// No good solution found ... so settle for a bad one
hw_decim = max_decim;
reduce_fract( tot_inter, sw_decim, tot_inter * hw_decim, tot_decim );
}
else
{
tot_inter *= ai;
}
s2_inter = tot_inter;
// Split the sw decim in 2 stage
find_split( s1_decim, s2_decim, sw_decim, tot_inter );
}
#endif // FILTER_HELPERS_HPP

View File

@ -0,0 +1,68 @@
/* (C) 2011 by Dimitri Stolnikov <horiz0n@gmx.net>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GMR_CHANNELS_HPP
#define GMR_CHANNELS_HPP
////////////////////////////////////////////////////////////////////////////////
class channel_base
{
public:
channel_base( const std::string & name, unsigned int number ) :
_name(name), _number(number) {}
virtual ~channel_base() {}
virtual double frequency() /* as per GMR-1 05.005, p4.2 */
{ return _base_freq + _bandwidth * _number; }
std::string _name;
double _symbol_rate;
double _bandwidth;
double _base_freq;
unsigned int _max_channels;
unsigned int _number;
};
////////////////////////////////////////////////////////////////////////////////
class gmr1_dl_channel : public channel_base
{
public:
gmr1_dl_channel( unsigned int number ) :
channel_base( "gmr1-dl", number )
{
_symbol_rate = 23400; /* from GMR-1 05.004, p4.2 */
_bandwidth = 31250;
_base_freq = 1525e6;
_max_channels = 1087;
}
};
////////////////////////////////////////////////////////////////////////////////
class gmr1_ul_channel : public channel_base
{
public:
gmr1_ul_channel( unsigned int number ) :
channel_base( "gmr1-ul", number )
{
_symbol_rate = 23400; /* from GMR-1 05.004, p4.2 */
_bandwidth = 31250;
_base_freq = 1525e6 + 101.5e6;
_max_channels = 1087;
}
};
#endif // GMR_CHANNELS_HPP

View File

@ -0,0 +1,540 @@
/* (C) 2011 by Dimitri Stolnikov <horiz0n@gmx.net>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
build:
"make" for USRP, requires libgnuradio-usrp
"make TARGET=fcd" for FCD, requires libgnuradio-fcd
"make TARGET=uhd" for UHD, requires libgnuradio-uhd
usage:
for FunCube Dongle call:
./gmr_multi_rx --gain 30 --gmr1-dl 941 942 943
- when receiving 3 consecutive channels, the center channel will be
distorted by the center peak caused by dc offset / iq imbalance,
- when receiving 2 channels, each channel will have a small contribution
of the center peak on the right or left side.
- best results may be achieved when receiving only one channel.
for default USRP clock, call:
- only 75% of the master output rate will be used on the usrp, because of
insufficient attenuation of the fpga channelizer at filter edges.
./gmr_multi_rx --gain 45 --gmr1-dl 941 942 943
for modified clocks, use --mcr to tell custom fpga frequency in Hz
./gmr_multi_rx --gain 45 --gmr1-dl 941 942 943 --mcr 52e6
mcr of 59.904e6 Hz is gmr1-friendly, thus alowing to save on interpolation
./gmr_multi_rx --gain 45 --gmr1-dl 941 942 943 --mcr 59.904e6
*/
#include <cstring>
#include <csignal>
#include <map>
#include <iostream>
#include <algorithm>
#include <iterator>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/program_options.hpp>
#include <boost/format.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/foreach.hpp>
#include <gr_top_block.h>
#ifdef HAVE_FCD
#include <fcd/fcd_source_c.h>
#elif defined(HAVE_UHD)
#include <gr_uhd_usrp_source.h>
#else
#include <usrp_source_c.h>
#endif
#include <gr_firdes.h>
#include <gr_freq_xlating_fir_filter_ccf.h>
#include <gr_rational_resampler_base_ccf.h>
#include <gr_file_sink.h>
#include <gr_udp_sink.h>
#include "gmr_channels.hpp"
#include "filter_helpers.hpp"
namespace po = boost::program_options;
////////////////////////////////////////////////////////////////////////////////
int main(int argc, char** argv)
{
/* variables to be set by po */
std::string prefix, udp, ant, side;
#ifdef HAVE_FCD
std::string device;
double correct;
#else
#ifdef HAVE_UHD
std::string addr, subdev;
#endif
#endif
double gain, mcr;
unsigned int time, osr;
/* setup the program options */
po::options_description opt_desc("Allowed options");
opt_desc.add_options()
("help,h", "print the command line help")
("prefix,P", po::value<std::string>(&prefix)->default_value("/tmp/"), "prefix of file to write channel samples to")
("time,T", po::value<unsigned int>(&time)->default_value(0), "recording time in seconds, 0 means infinite")
("udp,u", po::value<std::string>(&udp), "UDP destination (host:port) to send radio samples to")
("gain,g", po::value<double>(&gain)->default_value(10), "gain for the RF chain")
("osr,s", po::value<unsigned int>(&osr)->default_value(4), "oversampling rate, samples per symbol")
#ifdef HAVE_FCD
("device", po::value<std::string>(&device)->default_value("hw:1"), "FCD audio device name")
("correct", po::value<double>(&correct)->default_value(-21), "FCD frequency correction (ppm)")
#else
#ifdef HAVE_UHD
("addr", po::value<std::string>(&addr)->default_value("type=usrp1"), "UHD device address")
("subdev,S", po::value<std::string>(&subdev), "UHD sub-device spec, A:0 or B:0")
#else
("side,S", po::value<std::string>(&side), "USRP daughterboard side, A or B")
#endif
("ant,a", po::value<std::string>(&ant), "daughterboard antenna selection")
("mcr", po::value<double>(&mcr), "USRP fpga master clock rate in Hz")
#endif
;
po::options_description chan_desc("Supported channels");
chan_desc.add_options()
("gmr1-dl", po::value< std::vector<unsigned int> >()->multitoken(), "GMR-1 L-band downlink channel [1...1087]")
("gmr1-ul", po::value< std::vector<unsigned int> >()->multitoken(), "GMR-1 L-band uplink channel [1...1087]")
;
po::options_description visible("visible");
visible.add(opt_desc).add(chan_desc);
po::variables_map vm;
po::store(po::command_line_parser(argc, argv).options(visible).run(), vm);
po::notify(vm);
/* print the help message */
if (vm.count("help")) {
std::cout << boost::format("\nGMR-1 Acquisition tool\n") << std::endl;
std::cout << boost::format("%s") % opt_desc << std::endl;
std::cout << boost::format("%s") % chan_desc << std::endl;
return ~0;
}
if (not vm.count("gmr1-dl") and not vm.count("gmr1-ul")) {
std::cerr << boost::format("No channel number(s) given.") << std::endl;
return ~0;
}
std::string type = "";
std::vector< unsigned int > arfcns;
if (vm.count("gmr1-dl")) {
type = "gmr1-dl";
arfcns = vm[type].as< std::vector<unsigned int> >();
} else if (vm.count("gmr1-ul")) {
type = "gmr1-ul";
arfcns = vm[type].as< std::vector<unsigned int> >();
}
/* remove duplicate ARFCNs */
sort( arfcns.begin(), arfcns.begin() + arfcns.size() );
arfcns.resize( std::unique( arfcns.begin(), arfcns.end() ) - arfcns.begin() );
std::vector< channel_base > channels;
std::cout << "Given " << type << " ARFCNs:" << std::endl;
BOOST_FOREACH(unsigned int arfcn, arfcns)
{
channel_base channel("", 0);
if ( "gmr1-dl" == type )
channel = gmr1_dl_channel( arfcn );
else if ( "gmr1-ul" == type )
channel = gmr1_ul_channel( arfcn );
if ( arfcn < 1 || arfcn > channel._max_channels ) {
std::cerr << boost::format("Please specify the channel number in range [1...%d]")
% channel._max_channels << std::endl;
return ~0;
}
std::cout << boost::format(" %i = %f MHz")
% arfcn
% (channel.frequency()/1e6)
<< std::endl;
channels.push_back( channel );
}
double required_bandwidth = \
channels.back().frequency() \
- channels.front().frequency() \
+ channels.back()._bandwidth / 2.0 \
+ channels.front()._bandwidth / 2.0;
std::cout << boost::format("Required bandwidth is: %f kHz")
% (required_bandwidth / 1e3)
<< std::endl;
double sample_rate = 0;
#ifndef HAVE_FCD
/* use only 75% of BW due to USRP filter shape */
const double useful_bw_factor = 0.75;
#else
const double useful_bw_factor = 1.0;
#endif
channel_base last_channel = channels.back();
int channel_rate = last_channel._symbol_rate * osr;
int hw_decim, first_decim, second_decim, interpolation;
std::cout << std::endl;
#ifdef HAVE_FCD
std::cout << boost::format("Creating the FCD device...") << std::endl;
fcd_source_c_sptr radio = fcd_make_source_c( device );
radio->set_freq_corr( correct );
sample_rate = 96000; /* fixed by design */
hw_decim = 1;
first_decim = 1;
int gcd = boost::math::gcd( int(sample_rate), channel_rate );
second_decim = sample_rate / gcd;
interpolation = channel_rate / gcd;
#elif defined(HAVE_UHD)
std::cout << boost::format("Creating the UHD device...") << std::endl;
boost::shared_ptr<uhd_usrp_source> radio = \
uhd_make_usrp_source( addr,
uhd::io_type_t::COMPLEX_FLOAT32,
1 );
if (vm.count("mcr")) radio->set_clock_rate(mcr);
mcr = radio->get_clock_rate();
if (vm.count("subdev")) radio->set_subdev_spec(subdev, 0);
std::cout << boost::format("Using %s")
% radio->get_device().get()->get_pp_string()
<< std::endl;
compute_filter_params( hw_decim,
first_decim,
second_decim,
interpolation,
int(required_bandwidth / useful_bw_factor),
int(mcr),
last_channel._symbol_rate,
osr );
radio->set_samp_rate( mcr / double(hw_decim) );
sample_rate = radio->get_samp_rate();
#else
/* create a usrp device */
std::cout << boost::format("Creating the USRP device...") << std::endl;
usrp_source_c_sptr radio = usrp_make_source_c( 0 );
if (vm.count("mcr")) radio->set_fpga_master_clock_freq(long(mcr));
mcr = double(radio->fpga_master_clock_freq());
usrp_subdev_spec spec(0, 0); /* use A-side daughterboard by default */
if (vm.count("side")) spec.side = (side == "B" ? 1 : 0);
radio->set_mux( radio->determine_rx_mux_value(spec) );
db_base_sptr dboard = radio->selected_subdev(spec);
std::cout << boost::format("Using side %s (%g - %g MHz)")
% dboard->side_and_name()
% (dboard->freq_min() / 1e6)
% (dboard->freq_max() / 1e6)
<< std::endl;
compute_filter_params( hw_decim,
first_decim,
second_decim,
interpolation,
int(required_bandwidth / useful_bw_factor),
int(mcr),
last_channel._symbol_rate,
osr );
radio->set_decim_rate( hw_decim );
sample_rate = mcr / radio->decim_rate();
#endif
#ifndef HAVE_FCD
std::cout << boost::format("Master Clock Rate is: %f MHz")
#ifdef HAVE_UHD
% (radio->get_clock_rate()/1e6)
#else
% (radio->fpga_master_clock_freq()/1e6)
#endif
<< std::endl;
#endif
std::cout << boost::format("Output sample rate is: %f kHz")
% (sample_rate/1e3)
<< std::endl;
double available_bandwidth = sample_rate * useful_bw_factor;
std::cout << boost::format("Available bandwidth is: %f kHz")
% (available_bandwidth / 1e3)
<< std::endl;
if ( available_bandwidth < required_bandwidth ) {
std::cerr << "Please specify less channels or increase acquisition bandwidth." << std::endl;
return ~0;
}
/* calculate the frequency of the last channel of the group */
double center_freq = last_channel.frequency();
/* tune away from center peak caused by dc offset / phase imbalance */
center_freq -= last_channel._bandwidth / 2.0;
/* tune even more from the center if the acquisition bandwidth allows it */
double offset = available_bandwidth / 2.0;
while ( offset > last_channel._bandwidth )
offset -= last_channel._bandwidth;
offset = available_bandwidth / 2.0 - offset;
center_freq -= offset;
double tune_error = 0;
std::cout << boost::format("Setting RX Freq: %f MHz...") % (center_freq/1e6) << std::endl;
#ifdef HAVE_FCD
double fitting_channels = required_bandwidth / last_channel._bandwidth;
if ( 1 == fitting_channels )
center_freq += offset - (sample_rate / 16.0);
else if ( 2 == fitting_channels )
center_freq += offset;
else if ( 3 == fitting_channels )
center_freq += offset - last_channel._bandwidth / 2.0;
radio->set_freq( float(center_freq) );
#elif defined(HAVE_UHD)
uhd::tune_result_t tres = radio->set_center_freq( center_freq );
/*tune_error = tres.target_rf_freq - tres.actual_rf_freq - tres.actual_dsp_freq;*/
#else
freq_result_t fres = dboard->set_freq( center_freq );
std::cout << boost::format("Actual RX Freq: %f MHz...") % (fres.baseband_freq/1e6) << std::endl;
tune_error = center_freq - fres.baseband_freq;
#endif
if ( tune_error != 0 ) /* we have to compensate this later in each ddc tuner */
std::cout << boost::format("Tuning error is: %f kHz...") % (tune_error/1e3) << std::endl;
/* set the rf gain */
if (vm.count("gain")){
std::cout << boost::format("Setting RX Gain: %f dB...") % gain << std::endl;
#ifdef HAVE_FCD
radio->set_lna_gain( gain );
#else
#ifdef HAVE_UHD
radio->set_gain( gain );
#else
if ( dboard->set_gain( gain ) )
std::cout << boost::format("Actual RX Gain: %f dB...") % gain << std::endl;
else
std::cerr << "Failed to apply RX Gain" << std::endl;
#endif
#endif
}
#ifndef HAVE_FCD
/* set the antenna */
if (vm.count("ant"))
#ifdef HAVE_UHD
radio->set_antenna( ant );
#else
dboard->select_rx_antenna( ant );
#endif
#endif
#if 1
std::cout << boost::format("hw decimation stage: %i") % hw_decim << std::endl;
std::cout << boost::format("1st sw decimation stage: %i") % first_decim << std::endl;
std::cout << boost::format("2nd sw decimation stage: %i") % second_decim << std::endl;
std::cout << boost::format("interpolation stage: %i") % interpolation << std::endl;
#endif
double ddc_cutoff = last_channel._bandwidth / 2.0;
std::vector<float> ddc_taps = \
gr_firdes::low_pass( 1, /* gain */
sample_rate,
ddc_cutoff,
ddc_cutoff * 0.6 ); /* transition width */
double resampler_gain = interpolation;
double resampler_sample_rate = sample_rate * interpolation;
double resampler_cutoff = (interpolation * sample_rate) / (second_decim * 2.0);
if ( interpolation > second_decim )
resampler_cutoff = sample_rate / 2.0;
std::vector<float> channel_taps = \
gr_firdes::low_pass( resampler_gain,
resampler_sample_rate,
resampler_cutoff,
resampler_cutoff * 0.2 ); /* transition width */
std::cout << std::endl;
gr_top_block_sptr fg = gr_make_top_block( "flowgraph" );
if ( udp.length() )
{
std::vector< std::string > tokens;
split( tokens, udp, boost::is_any_of(":") );
if ( tokens.size() == 2 )
{
unsigned short port = \
boost::lexical_cast< unsigned short >( tokens[1] );
gr_udp_sink_sptr udp_sink = gr_make_udp_sink( sizeof(gr_complex),
tokens[0].c_str(),
port );
fg->connect(radio, 0, udp_sink, 0);
std::cout << boost::format("Sending samples to %s:%u ...")
% tokens[0]
% port
<< std::endl;
}
}
for ( unsigned int i = 0; i < channels.size(); i++ )
{
channel_base channel = channels[ i ];
/* calculate ddc offset from center frequency */
double tune_freq = channel.frequency() - center_freq;
/* we use the ddc to compensate the tuning error (if any) */
tune_freq += tune_error;
#if 0
std::cout << boost::format("DDC%d tune_freq is %f")
% i
% tune_freq
<< std::endl;
#endif
gr_freq_xlating_fir_filter_ccf_sptr tuner = \
gr_make_freq_xlating_fir_filter_ccf( first_decim,
ddc_taps,
-tune_freq,
sample_rate );
gr_rational_resampler_base_ccf_sptr resampler = \
gr_make_rational_resampler_base_ccf( interpolation,
second_decim,
channel_taps );
std::string file_name = \
str(boost::format("%s%s-%d-sps%d.cfile")
% prefix
% channel._name
% channel._number
% channel_rate);
gr_file_sink_sptr file_sink = \
gr_make_file_sink( sizeof(gr_complex),
file_name.c_str() );
fg->connect(radio, 0, tuner, 0);
fg->connect(tuner, 0, resampler, 0);
fg->connect(resampler, 0, file_sink, 0);
std::cout << boost::format("Writing samples for ARFCN %i to %s ...")
% channel._number
% file_name
<< std::endl;
}
/* Block all signals for background thread. */
sigset_t new_mask;
sigfillset(&new_mask);
sigset_t old_mask;
pthread_sigmask(SIG_BLOCK, &new_mask, &old_mask);
/* Launch the flowgraph in a separate thread. */
boost::thread fg_thread( boost::bind( &gr_top_block::run, fg.get() ) );
/* Restore previous signals. */
pthread_sigmask(SIG_SETMASK, &old_mask, 0);
/* Wait for signal indicating time to shut down. */
sigset_t wait_mask;
sigemptyset(&wait_mask);
sigaddset(&wait_mask, SIGINT);
sigaddset(&wait_mask, SIGQUIT);
sigaddset(&wait_mask, SIGTERM);
pthread_sigmask(SIG_BLOCK, &wait_mask, 0);
int sig = 0;
if ( time )
{
std::cout << std::endl << "Recording for " << time << " seconds..."
<< std::endl;
sleep( time );
std::cout << std::endl << "Finished recording, ";
}
else
{
std::cout << std::endl << "Press Ctrl + C to stop the receiver..."
<< std::endl;
sigwait(&wait_mask, &sig);
std::cout << std::endl << strsignal(sig) << ", ";
}
std::cout << "stopping flowgraph... " << std::flush;
fg->stop();
fg_thread.join();
std::cout << "done" << std::endl << std::endl;
return 0;
}