From andrea.montefusco at gmail.com Fri Feb 2 18:37:35 2018 From: andrea.montefusco at gmail.com (andrea montefusco) Date: Fri, 2 Feb 2018 19:37:35 +0100 Subject: [PATCH] gr-osmosdr: Microtelecom Perseus HF receiver support Message-ID: Hi maintainer and all, Below you find a patch that implements Microtelecom Perseus support in gr-osmosdr. I have tested it in gqrx SDR software. --- CMakeLists.txt | 1 + cmake/Modules/FindLibPERSEUS.cmake | 24 ++ lib/CMakeLists.txt | 8 + lib/airspy/airspy_source_c.cc | 5 +- lib/config.h.in | 1 + lib/device.cc | 8 + lib/perseus/CMakeLists.txt | 37 +++ lib/perseus/perseus_source_c.cc | 507 ++++++++++++++++++++++++++++++ +++++++ lib/perseus/perseus_source_c.h | 120 +++++++++ lib/rtl/rtl_source_c.cc | 2 +- lib/source_impl.cc | 18 +- 11 files changed, 727 insertions(+), 4 deletions(-) create mode 100644 cmake/Modules/FindLibPERSEUS.cmake create mode 100644 lib/perseus/CMakeLists.txt create mode 100644 lib/perseus/perseus_source_c.cc create mode 100644 lib/perseus/perseus_source_c.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 296456d..94acd4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,6 +173,7 @@ find_package(Volk) find_package(LibbladeRF) find_package(SoapySDR NO_MODULE) find_package(LibFreeSRP) +find_package(LibPERSEUS) find_package(Doxygen) if(NOT GNURADIO_RUNTIME_FOUND) diff --git a/cmake/Modules/FindLibPERSEUS.cmake b/cmake/Modules/ FindLibPERSEUS.cmake new file mode 100644 index 0000000..5477327 --- /dev/null +++ b/cmake/Modules/FindLibPERSEUS.cmake @@ -0,0 +1,24 @@ +INCLUDE(FindPkgConfig) +PKG_CHECK_MODULES(PC_LIBPERSEUS libperseus-sdr) + +FIND_PATH( + LIBPERSEUS_INCLUDE_DIRS + NAMES perseus-sdr.h + HINTS $ENV{LIBPERSEUS_DIR}/include + ${PC_LIBPERSEUS_INCLUDEDIR} + PATHS /usr/local/include + /usr/include +) + +FIND_LIBRARY( + LIBPERSEUS_LIBRARIES + NAMES perseus-sdr + HINTS $ENV{LIBPERSEUS_DIR}/lib + ${PC_LIBPERSEUS_LIBDIR} + PATHS /usr/local/lib + /usr/lib +) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBPERSEUS DEFAULT_MSG LIBPERSEUS_LIBRARIES LIBPERSEUS_INCLUDE_DIRS) +MARK_AS_ADVANCED(LIBPERSEUS_LIBRARIES LIBPERSEUS_INCLUDE_DIRS) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index c05b8d9..f555816 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -249,6 +249,14 @@ GR_INCLUDE_SUBDIRECTORY(freesrp) endif(ENABLE_FREESRP) ######################################################################## +# Setup PERSEUS component +######################################################################## +GR_REGISTER_COMPONENT("Microtelecom Perseus Receiver" ENABLE_PERSEUS LIBPERSEUS_FOUND) +if(ENABLE_PERSEUS) +GR_INCLUDE_SUBDIRECTORY(perseus) +endif(ENABLE_PERSEUS) + +######################################################################## # Setup configuration file ######################################################################## ADD_DEFINITIONS(-DHAVE_CONFIG_H=1) diff --git a/lib/airspy/airspy_source_c.cc b/lib/airspy/airspy_source_c.cc index 50150e5..02fc6ab 100644 --- a/lib/airspy/airspy_source_c.cc +++ b/lib/airspy/airspy_source_c.cc @@ -350,9 +350,10 @@ osmosdr::meta_range_t airspy_source_c::get_sample_ rates() { osmosdr::meta_range_t range; - for (size_t i = 0; i < _sample_rates.size(); i++) + for (size_t i = 0; i < _sample_rates.size(); i++) { + std::cerr << "SR: [" << i << "]: " << _sample_rates[i].first << std::endl; range += osmosdr::range_t( _sample_rates[i].first ); - + } return range; } diff --git a/lib/config.h.in b/lib/config.h.in index 42e72f1..dac7e39 100644 --- a/lib/config.h.in +++ b/lib/config.h.in @@ -19,6 +19,7 @@ #cmakedefine ENABLE_SOAPY #cmakedefine ENABLE_REDPITAYA #cmakedefine ENABLE_FREESRP +#cmakedefine ENABLE_PERSEUS //provide NAN define for MSVC older than VC12 #if defined(_MSC_VER) && (_MSC_VER < 1800) diff --git a/lib/device.cc b/lib/device.cc index 025a22b..df0d734 100644 --- a/lib/device.cc +++ b/lib/device.cc @@ -90,6 +90,10 @@ #include #endif +#ifdef ENABLE_PERSEUS +#include +#endif + #include "arg_helpers.h" using namespace osmosdr; @@ -194,6 +198,10 @@ devices_t device::find(const device_t &hint) BOOST_FOREACH( std::string dev, soapy_source_c::get_devices() ) devices.push_back( device_t(dev) ); #endif +#ifdef ENABLE_PERSEUS + BOOST_FOREACH( std::string dev, perseus_source_c::get_devices() ) + devices.push_back( device_t(dev) ); +#endif /* software-only sources should be appended at the very end, * hopefully resulting in hardware sources to be shown first diff --git a/lib/perseus/CMakeLists.txt b/lib/perseus/CMakeLists.txt new file mode 100644 index 0000000..f45a10d --- /dev/null +++ b/lib/perseus/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright 2017 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio 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, or (at your option) +# any later version. +# +# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${LIBPERSEUS_INCLUDE_DIRS} +) + +set(perseus_srcs + ${CMAKE_CURRENT_SOURCE_DIR}/perseus_source_c.cc +) + +######################################################################## +# Append gnuradio-osmosdr library sources +######################################################################## +list(APPEND gr_osmosdr_srcs ${perseus_srcs}) +list(APPEND gr_osmosdr_libs ${LIBPERSEUS_LIBRARIES} ${GNURADIO_BLOCKS_LIBRARIES}) diff --git a/lib/perseus/perseus_source_c.cc b/lib/perseus/perseus_source_ c.cc new file mode 100644 index 0000000..0720da4 --- /dev/null +++ b/lib/perseus/perseus_source_c.cc @@ -0,0 +1,507 @@ +/* -*- c++ -*- */ +/* + * Copyright 2018 Andrea Montefusco IW0HDV + * + * GNU Radio 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, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +/* + * config.h is generated by configure. It contains the results + * of probing for features, options etc. It should be the first + * file included in your .cc file. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "perseus_source_c.h" +#include "arg_helpers.h" + + + +using namespace boost::assign; + +#define PERSEUS_FORMAT_ERROR(ret, msg) \ + boost::str( boost::format(msg " (%1%)") % ret ) + +#define PERSEUS_THROW_ON_ERROR(ret, msg) \ + if ( ret != PERSEUS_NOERROR ) \ + { \ + throw std::runtime_error( PERSEUS_FORMAT_ERROR(ret, msg) ); \ + } + +#define PERSEUS_FUNC_STR(func, arg) \ + boost::str(boost::format(func "(%1%)") % arg) + " has failed" + +perseus_source_c_sptr make_perseus_source_c (const std::string & args) +{ + return gnuradio::get_initial_sptr(new perseus_source_c (args)); +} + +/* + * Specify constraints on number of input and output streams. + * This info is used to construct the input and output signatures + * (2nd & 3rd args to gr::block's constructor). The input and + * output signatures are used by the runtime system to + * check that a valid number and type of inputs and outputs + * are connected to this block. In this case, we accept + * only 0 input and 1 output. + */ +static const int MIN_IN = 0; // mininum number of input streams +static const int MAX_IN = 0; // maximum number of input streams +static const int MIN_OUT = 1; // minimum number of output streams +static const int MAX_OUT = 1; // maximum number of output streams + +/* + * The private constructor + */ +perseus_source_c::perseus_source_c (const std::string &args) + : gr::sync_block ("perseus_source_c", + gr::io_signature::make(MIN_IN, MAX_IN, sizeof (gr_complex)), + gr::io_signature::make(MIN_OUT, MAX_OUT, sizeof (gr_complex))), + _dev(NULL), + _sample_rate(0), + _center_freq(0), + _freq_corr(0), + _frun(false), + _cnt(0) +{ + //int ret; + + dict_t dict = params_to_dict(args); + + _dev = NULL; + + // Set debug info dumped to stderr to the maximum verbose level + perseus_set_debug(3); + + //fprintf (stderr, "Revision: %s\n", git_revision); + //fprintf (stderr, "SAMPLE RATE: %d\n", sr); + //fprintf (stderr, "NBUF: %d BUF SIZE: %d TOTAL BUFFER LENGTH: %d\n", nb, bs, nb*bs); + + // Check how many Perseus receivers are connected to the system + int num_perseus = perseus_init(); + std::cerr << "## Microtelecom Perseus ctor: " << num_perseus << " Perseus receivers found." << std::endl; + + if (num_perseus==0) { + perseus_exit(); + PERSEUS_THROW_ON_ERROR(PERSEUS_DEVNOTFOUND, "No Perseus receivers detected"); + } + // Open the first one... + if ((_dev = perseus_open(0)) == NULL) { + PERSEUS_THROW_ON_ERROR(PERSEUS_DEVNOTFOUND, "Unable to open Perseus device"); + } + // Download the standard firmware to the unit + std::cerr << "Downloading firmware..." << std::endl; + if (int rc = perseus_firmware_download(_dev,NULL)<0) { + PERSEUS_THROW_ON_ERROR (rc, "firmware download error"); + } + + // Dump some information about the receiver (S/N and HW rev) + int flag; + perseus_is_preserie(_dev, &flag); + if (flag != 0) + fprintf(stderr, "The device is a preserie unit"); + else { + eeprom_prodid prodid; + if (perseus_get_product_id(_dev,&prodid)<0) + fprintf(stderr, "get product id error: %s", perseus_errorstr()); + else + fprintf(stderr, "Receiver S/N: %05d-%02hX%02hX-%02hX%02hX-%02hX%02hX - HW Release:%hd.%hd\n", + (uint16_t) prodid.sn, + (uint16_t) prodid.signature[5], + (uint16_t) prodid.signature[4], + (uint16_t) prodid.signature[3], + (uint16_t) prodid.signature[2], + (uint16_t) prodid.signature[1], + (uint16_t) prodid.signature[0], + (uint16_t) prodid.hwrel, + (uint16_t) prodid.hwver); + } + // Printing all sampling rates available ..... + { + int buf[BUFSIZ]; + + if (perseus_get_sampling_rates (_dev, buf, sizeof(buf)/sizeof(buf[0])) < 0) { + fprintf(stderr, "get sampling rates error: %s\n", perseus_errorstr()); + } else { + int i = 0; + while (buf[i]) { + fprintf(stderr, "#%d: sample rate: %d\n", i, buf[i]); + + _sample_rates.push_back( std::pair( buf[i], i ) ); + i++; + } + /* since they may (and will) give us an unsorted array we have to sort it here + * to play nice with the monotonic requirement of meta-range later on */ + std::sort(_sample_rates.begin(), _sample_rates.end()); + + for (size_t i = 0; i < _sample_rates.size(); i++) + std::cerr << boost::format("%gM ") % (_sample_rates[i].first / 1e6); + + std::cerr << std::endl; + } + } + // Configure the receiver for 2 MS/s operations + fprintf(stderr, "Configuring FPGA...\n"); + if (int ret = perseus_set_sampling_rate(_dev, 96000) < 0) { // specify the sampling rate value in Samples/second + std::cerr << "fpga configuration error: " << perseus_errorstr() << std::endl; + PERSEUS_THROW_ON_ERROR(ret, "Perseus fpga configuration error"); + } + + perseus_set_attenuator_in_db(_dev, PERSEUS_ATT_0DB); + // Enable ADC Dither, Disable ADC Preamp + perseus_set_adc(_dev, true, false); + // set the NCO frequency in the middle of the range allowed + set_center_freq( (get_freq_range().start() + get_freq_range().stop()) / 2.0 ); + set_sample_rate( get_sample_rates().start() ); + + _fifo = new boost::circular_buffer(5000000); + if (!_fifo) { + throw std::runtime_error( std::string(__FUNCTION__) + " " + + "Failed to allocate a sample FIFO!" ); + } +} + +/* + * Our virtual destructor. + */ +perseus_source_c::~perseus_source_c () +{ + if (_dev) { + if (_frun) stop(); + int ret = perseus_close(_dev); + if ( ret != PERSEUS_NOERROR ) { + std::cerr << PERSEUS_FORMAT_ERROR(ret, "Failed to close Perseus") << std::endl; + } else + _dev = NULL; + } + perseus_exit(); + + if (_fifo) { + delete _fifo; + _fifo = NULL; + } +} + + +typedef union { + struct { + int32_t i; + int32_t q; + } __attribute__((__packed__)) iq; + struct { + uint8_t i1; + uint8_t i2; + uint8_t i3; + uint8_t i4; + uint8_t q1; + uint8_t q2; + uint8_t q3; + uint8_t q4; + } __attribute__((__packed__)) ; +} iq_sample; + + +int perseus_source_c::perseus_rx_callback(void *buf, int buf_size) +{ + size_t i, n_avail, to_copy, num_samples; + + uint8_t *samplebuf = (uint8_t*)buf; + + num_samples = buf_size/6; + + if (buf_size % 6) + std::cerr << "FATAL !!!!!!! not on boundary" << std::endl; + + iq_sample s; + + _fifo_lock.lock(); + + n_avail = _fifo->capacity() - _fifo->size(); + to_copy = (n_avail < num_samples ? n_avail : num_samples); + + for (i = 0; i < to_copy; i++ ) { + + s.i1 = s.q1 = 0; + s.i2 = *samplebuf++; + s.i3 = *samplebuf++; + s.i4 = *samplebuf++; + s.q2 = *samplebuf++; + s.q3 = *samplebuf++; + s.q4 = *samplebuf++; + + // convert 24 bit two's complements integers to float in [-1.0 - +1.0] range + + /* Push sample to the fifo */ + _fifo->push_back( gr_complex( (float)(s.iq.i) / (INT_MAX - 256), (float)(s.iq.q) / (INT_MAX - 256) ) ); + + } + // keep a counter of processed bytes + _cnt += buf_size; + + _fifo_lock.unlock(); + + /* We have made some new samples available to the consumer in work() */ + if (to_copy) { + //std::cerr << "+" << std::flush; + _samp_avail.notify_one(); + } + + /* Indicate overrun, if necessary */ + if (to_copy < num_samples) + std::cerr << "O" << std::flush; + + return 0; // TODO: return -1 on error/stop +} + +// static method used as callback from the libperseus-sdr library +int perseus_source_c::_perseus_rx_callback(void *buf, int buf_size, void *extra) +{ + perseus_source_c *obj = (perseus_source_c *)extra; + + return obj->perseus_rx_callback((float *)buf, buf_size); + + return 0; +} + + +bool perseus_source_c::start() +{ + int ret; + + if ( ! _dev ) + return false; + + std::cerr << "***** START COUNTER: " << _cnt + << " mod 6 " << (_cnt % 6) << std::endl; + + _frun = true; + + // in order to guarantee samples stream synchronization + // re-set the sample rate each time the stream is started + set_sample_rate( get_sample_rate() ); + + ret = perseus_start_async_input(_dev, + 6 * 1024 * 2 /* buffer size */, + perseus_source_c::_perseus_rx_callback, + (void *)this); + if (ret != PERSEUS_NOERROR) { + std::cerr << "Failed to start RX streaming (" + << ret << ")" << std::endl; + return false; + } else + return true; +} + +bool perseus_source_c::stop() +{ + if ( ! _dev ) + return false; + + _frun = false; + + std::cerr << "***** STOP COUNTER: " << _cnt + << " mod 6 " << (_cnt % 6) << std::endl; + + int ret = perseus_stop_async_input(_dev); + + if ( ret != PERSEUS_NOERROR ) { + std::cerr << "Failed to stop RX streaming (" << ret << ")" << std::endl; + return false; + } else + return true; +} + +int perseus_source_c::work( int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items ) +{ + gr_complex *out = (gr_complex *)output_items[0]; + + if ( _frun == false ) return WORK_DONE; + + boost::unique_lock lock(_fifo_lock); + + /* Wait until we have the requested number of samples */ + int n_samples_avail = _fifo->size(); + + while (n_samples_avail < noutput_items) { + _samp_avail.wait(lock); + n_samples_avail = _fifo->size(); + } + + for(int i = 0; i < noutput_items; ++i) { + out[i] = _fifo->at(0); + _fifo->pop_front(); + } + + return noutput_items; +} + +std::vector perseus_source_c::get_devices() +{ + std::vector devices; + std::string label; + + std::string args = "perseus=0,label='Perseus'"; + devices.push_back( args ); + + return devices; +} + +size_t perseus_source_c::get_num_channels() +{ + return 1; +} + +osmosdr::meta_range_t perseus_source_c::get_sample_rates() +{ + osmosdr::meta_range_t range; + + for (size_t i = 0; i < _sample_rates.size(); i++) { + range += osmosdr::range_t( _sample_rates[i].first ); + std::cerr << "SR: " << i << " : (" + << _sample_rates[i].first << ")" << std::endl; + } + return range; +} + +double perseus_source_c::set_sample_rate( double rate ) +{ + int ret; + if ((ret = perseus_set_sampling_rate(_dev, rate)) < 0) { + PERSEUS_THROW_ON_ERROR(ret, "Unable to set sample rate."); + } else + _sample_rate = rate; + + return get_sample_rate(); +} + +double perseus_source_c::get_sample_rate() +{ + return _sample_rate; +} + +osmosdr::freq_range_t perseus_source_c::get_freq_range( size_t chan ) +{ + osmosdr::freq_range_t range; + + range += osmosdr::range_t( PERSEUS_DDC_FREQ_MIN, PERSEUS_DDC_FREQ_MAX ); + + return range; +} + +double perseus_source_c::set_center_freq( double freq, size_t chan ) +{ + int ret; + + if (_dev) { + ret = + perseus_set_ddc_center_freq(_dev, + (uint64_t)(freq * (1.0 + (_freq_corr * 10e-6))), + 1); + if ( ret == PERSEUS_NOERROR ) { + _center_freq = freq; + } else { + PERSEUS_THROW_ON_ERROR( ret, PERSEUS_FUNC_STR( "perseus_set_ddc_center_freq", freq ) ) + } + } + + return get_center_freq( chan ); +} + +double perseus_source_c::get_center_freq( size_t chan ) +{ + return _center_freq; +} + +double perseus_source_c::set_freq_corr( double ppm, size_t chan ) +{ + return _freq_corr = ppm; +} + +double perseus_source_c::get_freq_corr( size_t chan ) +{ + return _freq_corr; +} + +std::vector perseus_source_c::get_gain_names( size_t chan ) +{ + return {}; +} + +osmosdr::gain_range_t perseus_source_c::get_gain_range( size_t chan ) +{ + return osmosdr::gain_range_t(); +} + +osmosdr::gain_range_t perseus_source_c::get_gain_range( const std::string & name, size_t chan ) +{ + return osmosdr::gain_range_t(); +} + + +double perseus_source_c::set_gain( double gain, size_t chan ) +{ + return gain; +} + +double perseus_source_c::set_gain( double gain, const std::string & name, size_t chan) +{ + return gain; +} + +double perseus_source_c::get_gain( size_t chan ) +{ + return 0.0; +} + +double perseus_source_c::get_gain( const std::string & name, size_t chan ) +{ + return 0.0; +} + +std::vector< std::string > perseus_source_c::get_antennas( size_t chan ) +{ + std::vector< std::string > antennas; + + antennas += get_antenna( chan ); + + return antennas; +} + +std::string perseus_source_c::set_antenna( const std::string & antenna, size_t chan ) +{ + return get_antenna( chan ); +} + +std::string perseus_source_c::get_antenna( size_t chan ) +{ + return "RX"; +} diff --git a/lib/perseus/perseus_source_c.h b/lib/perseus/perseus_source_c.h new file mode 100644 index 0000000..9eda23d --- /dev/null +++ b/lib/perseus/perseus_source_c.h @@ -0,0 +1,120 @@ +/* -*- c++ -*- */ +/* + * Copyright 2013 Dimitri Stolnikov + * + * This file is part of GNU Radio + * + * GNU Radio 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, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ +#ifndef INCLUDED_PERSEUS_SOURCE_C_H +#define INCLUDED_PERSEUS_SOURCE_C_H + +#include +#include +#include + +#include + +#include "source_iface.h" +#include + +class perseus_source_c; + +typedef boost::shared_ptr perseus_source_c_sptr; + +/*! + * \brief Return a shared_ptr to a new instance of perseus_source_c. + * + * To avoid accidental use of raw pointers, perseus_source_c's + * constructor is private. make_perseus_source_c is the public + * interface for creating new instances. + */ +perseus_source_c_sptr make_perseus_source_c (const std::string & args = ""); + +/*! + * \brief Provides a stream of complex samples. + * \ingroup block + */ +class perseus_source_c : + public gr::sync_block, + public source_iface +{ +private: + // The friend declaration allows make_perseus_source_c to + // access the private constructor. + + friend perseus_source_c_sptr make_perseus_source_c (const std::string & args); + + perseus_source_c (const std::string & args); + +public: + ~perseus_source_c (); + + bool start(); + bool stop(); + + int work( int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items ); + + static std::vector< std::string > get_devices(); + + size_t get_num_channels( void ); + + osmosdr::meta_range_t get_sample_rates( void ); + double set_sample_rate( double rate ); + double get_sample_rate( void ); + + osmosdr::freq_range_t get_freq_range( size_t chan = 0 ); + double set_center_freq( double freq, size_t chan = 0 ); + double get_center_freq( size_t chan = 0 ); + double set_freq_corr( double ppm, size_t chan = 0 ); + double get_freq_corr( size_t chan = 0 ); + + std::vector get_gain_names( size_t chan = 0 ); + osmosdr::gain_range_t get_gain_range( size_t chan = 0 ); + osmosdr::gain_range_t get_gain_range( const std::string & name, size_t chan = 0 ); + double set_gain( double gain, size_t chan = 0 ); + double set_gain( double gain, const std::string & name, size_t chan = 0 ); + double get_gain( size_t chan = 0 ); + double get_gain( const std::string & name, size_t chan = 0 ); + + std::vector< std::string > get_antennas( size_t chan = 0 ); + std::string set_antenna( const std::string & antenna, size_t chan = 0 ); + std::string get_antenna( size_t chan = 0 ); + + +private: + static int _perseus_rx_callback(void *buf, int buf_size, void *extra); + + int perseus_rx_callback(void *samples, int sample_count); + + perseus_descr *_dev; + + boost::circular_buffer *_fifo; + boost::mutex _fifo_lock; + boost::condition_variable _samp_avail; + + std::vector< std::pair > _sample_rates; + double _sample_rate; + double _center_freq; + double _freq_corr; + + bool _frun; + int _cnt; +}; + +#endif /* INCLUDED_PERSEUS_SOURCE_C_H */ diff --git a/lib/rtl/rtl_source_c.cc b/lib/rtl/rtl_source_c.cc index a371464..eb7042e 100644 --- a/lib/rtl/rtl_source_c.cc +++ b/lib/rtl/rtl_source_c.cc @@ -221,7 +221,7 @@ rtl_source_c::rtl_source_c (const std::string &args) throw std::runtime_error("Failed to enable offset tuning."); } - ret = rtlsdr_set_bias_tee(_dev, bias_tee); +// ret = rtlsdr_set_bias_tee(_dev, bias_tee); if (ret < 0) throw std::runtime_error("Failed to set bias tee."); diff --git a/lib/source_impl.cc b/lib/source_impl.cc index a28f314..ead4ef7 100644 --- a/lib/source_impl.cc +++ b/lib/source_impl.cc @@ -92,6 +92,9 @@ #include #endif +#ifdef ENABLE_PERSEUS +#include +#endif #include "arg_helpers.h" #include "source_impl.h" @@ -171,6 +174,9 @@ source_impl::source_impl( const std::string &args ) #ifdef ENABLE_FREESRP dev_types.push_back("freesrp"); #endif +#ifdef ENABLE_PERSEUS + dev_types.push_back("perseus"); +#endif std::cerr << "gr-osmosdr " << GR_OSMOSDR_VERSION << " (" << GR_OSMOSDR_LIBVER << ") " << "gnuradio " << gr::version() << std::endl; @@ -252,7 +258,10 @@ source_impl::source_impl( const std::string &args ) BOOST_FOREACH( std::string dev, freesrp_source_c::get_devices() ) dev_list.push_back( dev ); #endif - +#ifdef ENABLE_PERSEUS + BOOST_FOREACH( std::string dev, perseus_source_c::get_devices() ) + dev_list.push_back( dev ); +#endif // std::cerr << std::endl; // BOOST_FOREACH( std::string dev, dev_list ) // std::cerr << "'" << dev << "'" << std::endl; @@ -383,6 +392,13 @@ source_impl::source_impl( const std::string &args ) } #endif +#ifdef ENABLE_PERSEUS + if ( dict.count("perseus") ) { + perseus_source_c_sptr src = make_perseus_source_c( arg ); + block = src; iface = src.get(); + } +#endif + if ( iface != NULL && long(block.get()) != 0 ) { _devs.push_back( iface ); -- 1.9.1 >From 1b6cf50a7a6074754717b0270757931c379561bc Mon Sep 17 00:00:00 2001 From: Andrea Montefusco IW0HDV Date: Thu, 1 Feb 2018 17:33:39 +0100 Subject: More hardware controls added (attenuator, ADC preamp and dither, preselector) --- lib/perseus/perseus_source_c.cc | 136 ++++++++++++++++++++++++++++++ ++++++---- lib/perseus/perseus_source_c.h | 19 +++++- 2 files changed, 142 insertions(+), 13 deletions(-) diff --git a/lib/perseus/perseus_source_c.cc b/lib/perseus/perseus_source_ c.cc index 0720da4..89674b1 100644 --- a/lib/perseus/perseus_source_c.cc +++ b/lib/perseus/perseus_source_c.cc @@ -89,7 +89,11 @@ perseus_source_c::perseus_source_c (const std::string &args) _center_freq(0), _freq_corr(0), _frun(false), - _cnt(0) + _cnt(0), + _att_value(0), + _adc_preamp(false), + _adc_dither(false), + _preselector(false) { //int ret; @@ -174,9 +178,9 @@ perseus_source_c::perseus_source_c (const std::string &args) PERSEUS_THROW_ON_ERROR(ret, "Perseus fpga configuration error"); } - perseus_set_attenuator_in_db(_dev, PERSEUS_ATT_0DB); + perseus_set_attenuator_in_db(_dev, _att_value); // Enable ADC Dither, Disable ADC Preamp - perseus_set_adc(_dev, true, false); + perseus_set_adc(_dev, _adc_dither == true ? 1:0, _adc_preamp == true ? 1:0); // set the NCO frequency in the middle of the range allowed set_center_freq( (get_freq_range().start() + get_freq_range().stop()) / 2.0 ); set_sample_rate( get_sample_rates().start() ); @@ -425,7 +429,7 @@ double perseus_source_c::set_center_freq( double freq, size_t chan ) ret = perseus_set_ddc_center_freq(_dev, (uint64_t)(freq * (1.0 + (_freq_corr * 10e-6))), - 1); + _preselector == true ? 1:0); if ( ret == PERSEUS_NOERROR ) { _center_freq = freq; } else { @@ -453,55 +457,165 @@ double perseus_source_c::get_freq_corr( size_t chan ) std::vector perseus_source_c::get_gain_names( size_t chan ) { - return {}; + std::vector< std::string > names; + + names += "Attenuator"; + names += "ADC preamp"; + names += "ADC dither"; + + return names; } +// +// FIXME +// osmosdr::gain_range_t perseus_source_c::get_gain_range( size_t chan ) { - return osmosdr::gain_range_t(); + return get_gain_range( "Attenuator", chan ); } osmosdr::gain_range_t perseus_source_c::get_gain_range( const std::string & name, size_t chan ) { + if ( "Attenuator" == name ) { + return osmosdr::gain_range_t( -30, 0, 10 ); + } + if ( "ADC preamp" == name ) { + return osmosdr::gain_range_t( 0, 6, 6 ); + } + if ( "ADC dither" == name ) { + return osmosdr::gain_range_t( 0, 1, 1 ); + } + return osmosdr::gain_range_t(); } -double perseus_source_c::set_gain( double gain, size_t chan ) +double perseus_source_c::set_attenuator( double gain, size_t chan ) { + int ret; + + gain = - gain; + if (_dev && _att_value != gain) { + ret = perseus_set_attenuator_in_db (_dev, gain); + + if ( PERSEUS_NOERROR == ret ) { + _att_value = gain; + } else { + std::cerr << "ERROR: perseus_set_attenuator_in_db: " << gain << std::endl; + } + } + + return _att_value; +} + +double perseus_source_c::set_adc_preamp( double gain, size_t chan ) +{ + std::cerr << "set_adc_preamp: " << gain << std::endl; + + if (_dev) { + _adc_preamp = (gain != 0. ? true : false); + int ret = + perseus_set_adc(_dev, _adc_dither == true ? 1:0, _adc_preamp == true ? 1:0); + if ( PERSEUS_NOERROR != ret ) { + std::cerr << "ERROR: perseus_set_adc" << ret << std::endl; + } + } return gain; } -double perseus_source_c::set_gain( double gain, const std::string & name, size_t chan) +double perseus_source_c::set_adc_dither( double gain, size_t chan ) { + std::cerr << "set_adc_dither: " << gain << std::endl; + + if (_dev) { + _adc_dither = (gain != 0. ? true : false); + int ret = + perseus_set_adc(_dev, _adc_dither == true ? 1:0, _adc_preamp == true ? 1:0); + if ( PERSEUS_NOERROR != ret ) { + std::cerr << "ERROR: perseus_set_adc" << ret << std::endl; + } + } return gain; } -double perseus_source_c::get_gain( size_t chan ) +double perseus_source_c::get_attenuator( size_t chan ) { - return 0.0; + return _att_value; } +double perseus_source_c::get_adc_preamp(size_t chan ) +{ + double v = _adc_preamp == true ? 6. : 0.; + std::cerr << "get_adc_preamp: " << _adc_preamp << " value" << v << std::endl; + return v; +} + +double perseus_source_c::get_adc_dither(size_t chan ) +{ + double v = _adc_dither == true ? 1. : 0.; + std::cerr << "get_adc_dither: " << _adc_dither << " value" << v << std::endl; + return v; +} + + +double perseus_source_c::set_gain( double gain, const std::string & name, size_t chan) +{ + if ( "Attenuator" == name ) { + return set_attenuator( gain, chan ); + } + if ( "ADC preamp" == name ) { + return set_adc_preamp( gain, chan ); + } + if ( "ADC dither" == name ) { + return set_adc_dither( gain, chan ); + } + return gain; +} + + double perseus_source_c::get_gain( const std::string & name, size_t chan ) { + if ( "Attenuator" == name ) { + return get_attenuator( chan ); + } + if ( "ADC preamp" == name ) { + return get_adc_preamp( chan ); + } + if ( "ADC dither" == name ) { + return get_adc_dither( chan ); + } return 0.0; } + + std::vector< std::string > perseus_source_c::get_antennas( size_t chan ) { std::vector< std::string > antennas; - antennas += get_antenna( chan ); + antennas += "RX"; + antennas += "RX+PRESELECTOR"; return antennas; } std::string perseus_source_c::set_antenna( const std::string & antenna, size_t chan ) { + if ( antenna == "RX" ) { + _preselector = false; + set_center_freq( get_center_freq() ); + return "RX"; + } + if ( antenna == "RX+PRESELECTOR" ) { + _preselector = true; + set_center_freq( get_center_freq() ); + return "RX+PRESELECTOR"; + } return get_antenna( chan ); } std::string perseus_source_c::get_antenna( size_t chan ) { + std::cerr << "perseus_source_c::get_antenna: " << chan << std::endl; return "RX"; } diff --git a/lib/perseus/perseus_source_c.h b/lib/perseus/perseus_source_c.h index 9eda23d..934ff2b 100644 --- a/lib/perseus/perseus_source_c.h +++ b/lib/perseus/perseus_source_c.h @@ -87,9 +87,20 @@ public: std::vector get_gain_names( size_t chan = 0 ); osmosdr::gain_range_t get_gain_range( size_t chan = 0 ); osmosdr::gain_range_t get_gain_range( const std::string & name, size_t chan = 0 ); - double set_gain( double gain, size_t chan = 0 ); + + double set_gain( double gain, size_t chan = 0 ) {return gain; } + double get_gain( size_t chan = 0 ) {return _att_value; } + + double set_attenuator( double gain, size_t chan = 0 ); + double get_attenuator( size_t chan = 0 ); + + double set_adc_preamp( double gain, size_t chan = 0 ); + double get_adc_preamp( size_t chan = 0 ); + + double set_adc_dither( double gain, size_t chan = 0 ); + double get_adc_dither( size_t chan = 0 ); + double set_gain( double gain, const std::string & name, size_t chan = 0 ); - double get_gain( size_t chan = 0 ); double get_gain( const std::string & name, size_t chan = 0 ); std::vector< std::string > get_antennas( size_t chan = 0 ); @@ -115,6 +126,10 @@ private: bool _frun; int _cnt; + double _att_value; + bool _adc_preamp; + bool _adc_dither; + bool _preselector; }; #endif /* INCLUDED_PERSEUS_SOURCE_C_H */ -- 1.9.1 -- ?? Andrea Montefusco IW0HDV ------------------------------------------ As my old boss, an Apollo veteran, would often remind us ?It?s good to be smart, but it?s better to be lucky.? Wayne Hale, Space Shuttle Flight Director -------------- next part -------------- An HTML attachment was scrubbed... URL: From andrea.montefusco at gmail.com Fri Feb 2 18:47:10 2018 From: andrea.montefusco at gmail.com (andrea montefusco) Date: Fri, 2 Feb 2018 19:47:10 +0100 Subject: [PATCH] gr-osmosdr: Microtelecom Perseus HF receiver support Message-ID: Hi maintainer and all, Below you find a patch that implements Microtelecom Perseus support in gr-osmosdr. I have tested it in gqrx SDR software. (second attempt as the first one got corrupted) --- CMakeLists.txt | 1 + cmake/Modules/FindLibPERSEUS.cmake | 24 ++ lib/CMakeLists.txt | 8 + lib/airspy/airspy_source_c.cc | 5 +- lib/config.h.in | 1 + lib/device.cc | 8 + lib/perseus/CMakeLists.txt | 37 +++ lib/perseus/perseus_source_c.cc | 507 +++++++++++++++++++++++++++++++++++++ lib/perseus/perseus_source_c.h | 120 +++++++++ lib/rtl/rtl_source_c.cc | 2 +- lib/source_impl.cc | 18 +- 11 files changed, 727 insertions(+), 4 deletions(-) create mode 100644 cmake/Modules/FindLibPERSEUS.cmake create mode 100644 lib/perseus/CMakeLists.txt create mode 100644 lib/perseus/perseus_source_c.cc create mode 100644 lib/perseus/perseus_source_c.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 296456d..94acd4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,6 +173,7 @@ find_package(Volk) find_package(LibbladeRF) find_package(SoapySDR NO_MODULE) find_package(LibFreeSRP) +find_package(LibPERSEUS) find_package(Doxygen) if(NOT GNURADIO_RUNTIME_FOUND) diff --git a/cmake/Modules/FindLibPERSEUS.cmake b/cmake/Modules/FindLibPERSEUS.cmake new file mode 100644 index 0000000..5477327 --- /dev/null +++ b/cmake/Modules/FindLibPERSEUS.cmake @@ -0,0 +1,24 @@ +INCLUDE(FindPkgConfig) +PKG_CHECK_MODULES(PC_LIBPERSEUS libperseus-sdr) + +FIND_PATH( + LIBPERSEUS_INCLUDE_DIRS + NAMES perseus-sdr.h + HINTS $ENV{LIBPERSEUS_DIR}/include + ${PC_LIBPERSEUS_INCLUDEDIR} + PATHS /usr/local/include + /usr/include +) + +FIND_LIBRARY( + LIBPERSEUS_LIBRARIES + NAMES perseus-sdr + HINTS $ENV{LIBPERSEUS_DIR}/lib + ${PC_LIBPERSEUS_LIBDIR} + PATHS /usr/local/lib + /usr/lib +) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBPERSEUS DEFAULT_MSG LIBPERSEUS_LIBRARIES LIBPERSEUS_INCLUDE_DIRS) +MARK_AS_ADVANCED(LIBPERSEUS_LIBRARIES LIBPERSEUS_INCLUDE_DIRS) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index c05b8d9..f555816 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -249,6 +249,14 @@ GR_INCLUDE_SUBDIRECTORY(freesrp) endif(ENABLE_FREESRP) ######################################################################## +# Setup PERSEUS component +######################################################################## +GR_REGISTER_COMPONENT("Microtelecom Perseus Receiver" ENABLE_PERSEUS LIBPERSEUS_FOUND) +if(ENABLE_PERSEUS) +GR_INCLUDE_SUBDIRECTORY(perseus) +endif(ENABLE_PERSEUS) + +######################################################################## # Setup configuration file ######################################################################## ADD_DEFINITIONS(-DHAVE_CONFIG_H=1) diff --git a/lib/airspy/airspy_source_c.cc b/lib/airspy/airspy_source_c.cc index 50150e5..02fc6ab 100644 --- a/lib/airspy/airspy_source_c.cc +++ b/lib/airspy/airspy_source_c.cc @@ -350,9 +350,10 @@ osmosdr::meta_range_t airspy_source_c::get_sample_rates() { osmosdr::meta_range_t range; - for (size_t i = 0; i < _sample_rates.size(); i++) + for (size_t i = 0; i < _sample_rates.size(); i++) { + std::cerr << "SR: [" << i << "]: " << _sample_rates[i].first << std::endl; range += osmosdr::range_t( _sample_rates[i].first ); - + } return range; } diff --git a/lib/config.h.in b/lib/config.h.in index 42e72f1..dac7e39 100644 --- a/lib/config.h.in +++ b/lib/config.h.in @@ -19,6 +19,7 @@ #cmakedefine ENABLE_SOAPY #cmakedefine ENABLE_REDPITAYA #cmakedefine ENABLE_FREESRP +#cmakedefine ENABLE_PERSEUS //provide NAN define for MSVC older than VC12 #if defined(_MSC_VER) && (_MSC_VER < 1800) diff --git a/lib/device.cc b/lib/device.cc index 025a22b..df0d734 100644 --- a/lib/device.cc +++ b/lib/device.cc @@ -90,6 +90,10 @@ #include #endif +#ifdef ENABLE_PERSEUS +#include +#endif + #include "arg_helpers.h" using namespace osmosdr; @@ -194,6 +198,10 @@ devices_t device::find(const device_t &hint) BOOST_FOREACH( std::string dev, soapy_source_c::get_devices() ) devices.push_back( device_t(dev) ); #endif +#ifdef ENABLE_PERSEUS + BOOST_FOREACH( std::string dev, perseus_source_c::get_devices() ) + devices.push_back( device_t(dev) ); +#endif /* software-only sources should be appended at the very end, * hopefully resulting in hardware sources to be shown first diff --git a/lib/perseus/CMakeLists.txt b/lib/perseus/CMakeLists.txt new file mode 100644 index 0000000..f45a10d --- /dev/null +++ b/lib/perseus/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright 2017 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio 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, or (at your option) +# any later version. +# +# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${LIBPERSEUS_INCLUDE_DIRS} +) + +set(perseus_srcs + ${CMAKE_CURRENT_SOURCE_DIR}/perseus_source_c.cc +) + +######################################################################## +# Append gnuradio-osmosdr library sources +######################################################################## +list(APPEND gr_osmosdr_srcs ${perseus_srcs}) +list(APPEND gr_osmosdr_libs ${LIBPERSEUS_LIBRARIES} ${GNURADIO_BLOCKS_LIBRARIES}) diff --git a/lib/perseus/perseus_source_c.cc b/lib/perseus/perseus_source_c.cc new file mode 100644 index 0000000..0720da4 --- /dev/null +++ b/lib/perseus/perseus_source_c.cc @@ -0,0 +1,507 @@ +/* -*- c++ -*- */ +/* + * Copyright 2018 Andrea Montefusco IW0HDV + * + * GNU Radio 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, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +/* + * config.h is generated by configure. It contains the results + * of probing for features, options etc. It should be the first + * file included in your .cc file. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "perseus_source_c.h" +#include "arg_helpers.h" + + + +using namespace boost::assign; + +#define PERSEUS_FORMAT_ERROR(ret, msg) \ + boost::str( boost::format(msg " (%1%)") % ret ) + +#define PERSEUS_THROW_ON_ERROR(ret, msg) \ + if ( ret != PERSEUS_NOERROR ) \ + { \ + throw std::runtime_error( PERSEUS_FORMAT_ERROR(ret, msg) ); \ + } + +#define PERSEUS_FUNC_STR(func, arg) \ + boost::str(boost::format(func "(%1%)") % arg) + " has failed" + +perseus_source_c_sptr make_perseus_source_c (const std::string & args) +{ + return gnuradio::get_initial_sptr(new perseus_source_c (args)); +} + +/* + * Specify constraints on number of input and output streams. + * This info is used to construct the input and output signatures + * (2nd & 3rd args to gr::block's constructor). The input and + * output signatures are used by the runtime system to + * check that a valid number and type of inputs and outputs + * are connected to this block. In this case, we accept + * only 0 input and 1 output. + */ +static const int MIN_IN = 0; // mininum number of input streams +static const int MAX_IN = 0; // maximum number of input streams +static const int MIN_OUT = 1; // minimum number of output streams +static const int MAX_OUT = 1; // maximum number of output streams + +/* + * The private constructor + */ +perseus_source_c::perseus_source_c (const std::string &args) + : gr::sync_block ("perseus_source_c", + gr::io_signature::make(MIN_IN, MAX_IN, sizeof (gr_complex)), + gr::io_signature::make(MIN_OUT, MAX_OUT, sizeof (gr_complex))), + _dev(NULL), + _sample_rate(0), + _center_freq(0), + _freq_corr(0), + _frun(false), + _cnt(0) +{ + //int ret; + + dict_t dict = params_to_dict(args); + + _dev = NULL; + + // Set debug info dumped to stderr to the maximum verbose level + perseus_set_debug(3); + + //fprintf (stderr, "Revision: %s\n", git_revision); + //fprintf (stderr, "SAMPLE RATE: %d\n", sr); + //fprintf (stderr, "NBUF: %d BUF SIZE: %d TOTAL BUFFER LENGTH: %d\n", nb, bs, nb*bs); + + // Check how many Perseus receivers are connected to the system + int num_perseus = perseus_init(); + std::cerr << "## Microtelecom Perseus ctor: " << num_perseus << " Perseus receivers found." << std::endl; + + if (num_perseus==0) { + perseus_exit(); + PERSEUS_THROW_ON_ERROR(PERSEUS_DEVNOTFOUND, "No Perseus receivers detected"); + } + // Open the first one... + if ((_dev = perseus_open(0)) == NULL) { + PERSEUS_THROW_ON_ERROR(PERSEUS_DEVNOTFOUND, "Unable to open Perseus device"); + } + // Download the standard firmware to the unit + std::cerr << "Downloading firmware..." << std::endl; + if (int rc = perseus_firmware_download(_dev,NULL)<0) { + PERSEUS_THROW_ON_ERROR (rc, "firmware download error"); + } + + // Dump some information about the receiver (S/N and HW rev) + int flag; + perseus_is_preserie(_dev, &flag); + if (flag != 0) + fprintf(stderr, "The device is a preserie unit"); + else { + eeprom_prodid prodid; + if (perseus_get_product_id(_dev,&prodid)<0) + fprintf(stderr, "get product id error: %s", perseus_errorstr()); + else + fprintf(stderr, "Receiver S/N: %05d-%02hX%02hX-%02hX%02hX-%02hX%02hX - HW Release:%hd.%hd\n", + (uint16_t) prodid.sn, + (uint16_t) prodid.signature[5], + (uint16_t) prodid.signature[4], + (uint16_t) prodid.signature[3], + (uint16_t) prodid.signature[2], + (uint16_t) prodid.signature[1], + (uint16_t) prodid.signature[0], + (uint16_t) prodid.hwrel, + (uint16_t) prodid.hwver); + } + // Printing all sampling rates available ..... + { + int buf[BUFSIZ]; + + if (perseus_get_sampling_rates (_dev, buf, sizeof(buf)/sizeof(buf[0])) < 0) { + fprintf(stderr, "get sampling rates error: %s\n", perseus_errorstr()); + } else { + int i = 0; + while (buf[i]) { + fprintf(stderr, "#%d: sample rate: %d\n", i, buf[i]); + + _sample_rates.push_back( std::pair( buf[i], i ) ); + i++; + } + /* since they may (and will) give us an unsorted array we have to sort it here + * to play nice with the monotonic requirement of meta-range later on */ + std::sort(_sample_rates.begin(), _sample_rates.end()); + + for (size_t i = 0; i < _sample_rates.size(); i++) + std::cerr << boost::format("%gM ") % (_sample_rates[i].first / 1e6); + + std::cerr << std::endl; + } + } + // Configure the receiver for 2 MS/s operations + fprintf(stderr, "Configuring FPGA...\n"); + if (int ret = perseus_set_sampling_rate(_dev, 96000) < 0) { // specify the sampling rate value in Samples/second + std::cerr << "fpga configuration error: " << perseus_errorstr() << std::endl; + PERSEUS_THROW_ON_ERROR(ret, "Perseus fpga configuration error"); + } + + perseus_set_attenuator_in_db(_dev, PERSEUS_ATT_0DB); + // Enable ADC Dither, Disable ADC Preamp + perseus_set_adc(_dev, true, false); + // set the NCO frequency in the middle of the range allowed + set_center_freq( (get_freq_range().start() + get_freq_range().stop()) / 2.0 ); + set_sample_rate( get_sample_rates().start() ); + + _fifo = new boost::circular_buffer(5000000); + if (!_fifo) { + throw std::runtime_error( std::string(__FUNCTION__) + " " + + "Failed to allocate a sample FIFO!" ); + } +} + +/* + * Our virtual destructor. + */ +perseus_source_c::~perseus_source_c () +{ + if (_dev) { + if (_frun) stop(); + int ret = perseus_close(_dev); + if ( ret != PERSEUS_NOERROR ) { + std::cerr << PERSEUS_FORMAT_ERROR(ret, "Failed to close Perseus") << std::endl; + } else + _dev = NULL; + } + perseus_exit(); + + if (_fifo) { + delete _fifo; + _fifo = NULL; + } +} + + +typedef union { + struct { + int32_t i; + int32_t q; + } __attribute__((__packed__)) iq; + struct { + uint8_t i1; + uint8_t i2; + uint8_t i3; + uint8_t i4; + uint8_t q1; + uint8_t q2; + uint8_t q3; + uint8_t q4; + } __attribute__((__packed__)) ; +} iq_sample; + + +int perseus_source_c::perseus_rx_callback(void *buf, int buf_size) +{ + size_t i, n_avail, to_copy, num_samples; + + uint8_t *samplebuf = (uint8_t*)buf; + + num_samples = buf_size/6; + + if (buf_size % 6) + std::cerr << "FATAL !!!!!!! not on boundary" << std::endl; + + iq_sample s; + + _fifo_lock.lock(); + + n_avail = _fifo->capacity() - _fifo->size(); + to_copy = (n_avail < num_samples ? n_avail : num_samples); + + for (i = 0; i < to_copy; i++ ) { + + s.i1 = s.q1 = 0; + s.i2 = *samplebuf++; + s.i3 = *samplebuf++; + s.i4 = *samplebuf++; + s.q2 = *samplebuf++; + s.q3 = *samplebuf++; + s.q4 = *samplebuf++; + + // convert 24 bit two's complements integers to float in [-1.0 - +1.0] range + + /* Push sample to the fifo */ + _fifo->push_back( gr_complex( (float)(s.iq.i) / (INT_MAX - 256), (float)(s.iq.q) / (INT_MAX - 256) ) ); + + } + // keep a counter of processed bytes + _cnt += buf_size; + + _fifo_lock.unlock(); + + /* We have made some new samples available to the consumer in work() */ + if (to_copy) { + //std::cerr << "+" << std::flush; + _samp_avail.notify_one(); + } + + /* Indicate overrun, if necessary */ + if (to_copy < num_samples) + std::cerr << "O" << std::flush; + + return 0; // TODO: return -1 on error/stop +} + +// static method used as callback from the libperseus-sdr library +int perseus_source_c::_perseus_rx_callback(void *buf, int buf_size, void *extra) +{ + perseus_source_c *obj = (perseus_source_c *)extra; + + return obj->perseus_rx_callback((float *)buf, buf_size); + + return 0; +} + + +bool perseus_source_c::start() +{ + int ret; + + if ( ! _dev ) + return false; + + std::cerr << "***** START COUNTER: " << _cnt + << " mod 6 " << (_cnt % 6) << std::endl; + + _frun = true; + + // in order to guarantee samples stream synchronization + // re-set the sample rate each time the stream is started + set_sample_rate( get_sample_rate() ); + + ret = perseus_start_async_input(_dev, + 6 * 1024 * 2 /* buffer size */, + perseus_source_c::_perseus_rx_callback, + (void *)this); + if (ret != PERSEUS_NOERROR) { + std::cerr << "Failed to start RX streaming (" + << ret << ")" << std::endl; + return false; + } else + return true; +} + +bool perseus_source_c::stop() +{ + if ( ! _dev ) + return false; + + _frun = false; + + std::cerr << "***** STOP COUNTER: " << _cnt + << " mod 6 " << (_cnt % 6) << std::endl; + + int ret = perseus_stop_async_input(_dev); + + if ( ret != PERSEUS_NOERROR ) { + std::cerr << "Failed to stop RX streaming (" << ret << ")" << std::endl; + return false; + } else + return true; +} + +int perseus_source_c::work( int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items ) +{ + gr_complex *out = (gr_complex *)output_items[0]; + + if ( _frun == false ) return WORK_DONE; + + boost::unique_lock lock(_fifo_lock); + + /* Wait until we have the requested number of samples */ + int n_samples_avail = _fifo->size(); + + while (n_samples_avail < noutput_items) { + _samp_avail.wait(lock); + n_samples_avail = _fifo->size(); + } + + for(int i = 0; i < noutput_items; ++i) { + out[i] = _fifo->at(0); + _fifo->pop_front(); + } + + return noutput_items; +} + +std::vector perseus_source_c::get_devices() +{ + std::vector devices; + std::string label; + + std::string args = "perseus=0,label='Perseus'"; + devices.push_back( args ); + + return devices; +} + +size_t perseus_source_c::get_num_channels() +{ + return 1; +} + +osmosdr::meta_range_t perseus_source_c::get_sample_rates() +{ + osmosdr::meta_range_t range; + + for (size_t i = 0; i < _sample_rates.size(); i++) { + range += osmosdr::range_t( _sample_rates[i].first ); + std::cerr << "SR: " << i << " : (" + << _sample_rates[i].first << ")" << std::endl; + } + return range; +} + +double perseus_source_c::set_sample_rate( double rate ) +{ + int ret; + if ((ret = perseus_set_sampling_rate(_dev, rate)) < 0) { + PERSEUS_THROW_ON_ERROR(ret, "Unable to set sample rate."); + } else + _sample_rate = rate; + + return get_sample_rate(); +} + +double perseus_source_c::get_sample_rate() +{ + return _sample_rate; +} + +osmosdr::freq_range_t perseus_source_c::get_freq_range( size_t chan ) +{ + osmosdr::freq_range_t range; + + range += osmosdr::range_t( PERSEUS_DDC_FREQ_MIN, PERSEUS_DDC_FREQ_MAX ); + + return range; +} + +double perseus_source_c::set_center_freq( double freq, size_t chan ) +{ + int ret; + + if (_dev) { + ret = + perseus_set_ddc_center_freq(_dev, + (uint64_t)(freq * (1.0 + (_freq_corr * 10e-6))), + 1); + if ( ret == PERSEUS_NOERROR ) { + _center_freq = freq; + } else { + PERSEUS_THROW_ON_ERROR( ret, PERSEUS_FUNC_STR( "perseus_set_ddc_center_freq", freq ) ) + } + } + + return get_center_freq( chan ); +} + +double perseus_source_c::get_center_freq( size_t chan ) +{ + return _center_freq; +} + +double perseus_source_c::set_freq_corr( double ppm, size_t chan ) +{ + return _freq_corr = ppm; +} + +double perseus_source_c::get_freq_corr( size_t chan ) +{ + return _freq_corr; +} + +std::vector perseus_source_c::get_gain_names( size_t chan ) +{ + return {}; +} + +osmosdr::gain_range_t perseus_source_c::get_gain_range( size_t chan ) +{ + return osmosdr::gain_range_t(); +} + +osmosdr::gain_range_t perseus_source_c::get_gain_range( const std::string & name, size_t chan ) +{ + return osmosdr::gain_range_t(); +} + + +double perseus_source_c::set_gain( double gain, size_t chan ) +{ + return gain; +} + +double perseus_source_c::set_gain( double gain, const std::string & name, size_t chan) +{ + return gain; +} + +double perseus_source_c::get_gain( size_t chan ) +{ + return 0.0; +} + +double perseus_source_c::get_gain( const std::string & name, size_t chan ) +{ + return 0.0; +} + +std::vector< std::string > perseus_source_c::get_antennas( size_t chan ) +{ + std::vector< std::string > antennas; + + antennas += get_antenna( chan ); + + return antennas; +} + +std::string perseus_source_c::set_antenna( const std::string & antenna, size_t chan ) +{ + return get_antenna( chan ); +} + +std::string perseus_source_c::get_antenna( size_t chan ) +{ + return "RX"; +} diff --git a/lib/perseus/perseus_source_c.h b/lib/perseus/perseus_source_c.h new file mode 100644 index 0000000..9eda23d --- /dev/null +++ b/lib/perseus/perseus_source_c.h @@ -0,0 +1,120 @@ +/* -*- c++ -*- */ +/* + * Copyright 2013 Dimitri Stolnikov + * + * This file is part of GNU Radio + * + * GNU Radio 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, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ +#ifndef INCLUDED_PERSEUS_SOURCE_C_H +#define INCLUDED_PERSEUS_SOURCE_C_H + +#include +#include +#include + +#include + +#include "source_iface.h" +#include + +class perseus_source_c; + +typedef boost::shared_ptr perseus_source_c_sptr; + +/*! + * \brief Return a shared_ptr to a new instance of perseus_source_c. + * + * To avoid accidental use of raw pointers, perseus_source_c's + * constructor is private. make_perseus_source_c is the public + * interface for creating new instances. + */ +perseus_source_c_sptr make_perseus_source_c (const std::string & args = ""); + +/*! + * \brief Provides a stream of complex samples. + * \ingroup block + */ +class perseus_source_c : + public gr::sync_block, + public source_iface +{ +private: + // The friend declaration allows make_perseus_source_c to + // access the private constructor. + + friend perseus_source_c_sptr make_perseus_source_c (const std::string & args); + + perseus_source_c (const std::string & args); + +public: + ~perseus_source_c (); + + bool start(); + bool stop(); + + int work( int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items ); + + static std::vector< std::string > get_devices(); + + size_t get_num_channels( void ); + + osmosdr::meta_range_t get_sample_rates( void ); + double set_sample_rate( double rate ); + double get_sample_rate( void ); + + osmosdr::freq_range_t get_freq_range( size_t chan = 0 ); + double set_center_freq( double freq, size_t chan = 0 ); + double get_center_freq( size_t chan = 0 ); + double set_freq_corr( double ppm, size_t chan = 0 ); + double get_freq_corr( size_t chan = 0 ); + + std::vector get_gain_names( size_t chan = 0 ); + osmosdr::gain_range_t get_gain_range( size_t chan = 0 ); + osmosdr::gain_range_t get_gain_range( const std::string & name, size_t chan = 0 ); + double set_gain( double gain, size_t chan = 0 ); + double set_gain( double gain, const std::string & name, size_t chan = 0 ); + double get_gain( size_t chan = 0 ); + double get_gain( const std::string & name, size_t chan = 0 ); + + std::vector< std::string > get_antennas( size_t chan = 0 ); + std::string set_antenna( const std::string & antenna, size_t chan = 0 ); + std::string get_antenna( size_t chan = 0 ); + + +private: + static int _perseus_rx_callback(void *buf, int buf_size, void *extra); + + int perseus_rx_callback(void *samples, int sample_count); + + perseus_descr *_dev; + + boost::circular_buffer *_fifo; + boost::mutex _fifo_lock; + boost::condition_variable _samp_avail; + + std::vector< std::pair > _sample_rates; + double _sample_rate; + double _center_freq; + double _freq_corr; + + bool _frun; + int _cnt; +}; + +#endif /* INCLUDED_PERSEUS_SOURCE_C_H */ diff --git a/lib/rtl/rtl_source_c.cc b/lib/rtl/rtl_source_c.cc index a371464..eb7042e 100644 --- a/lib/rtl/rtl_source_c.cc +++ b/lib/rtl/rtl_source_c.cc @@ -221,7 +221,7 @@ rtl_source_c::rtl_source_c (const std::string &args) throw std::runtime_error("Failed to enable offset tuning."); } - ret = rtlsdr_set_bias_tee(_dev, bias_tee); +// ret = rtlsdr_set_bias_tee(_dev, bias_tee); if (ret < 0) throw std::runtime_error("Failed to set bias tee."); diff --git a/lib/source_impl.cc b/lib/source_impl.cc index a28f314..ead4ef7 100644 --- a/lib/source_impl.cc +++ b/lib/source_impl.cc @@ -92,6 +92,9 @@ #include #endif +#ifdef ENABLE_PERSEUS +#include +#endif #include "arg_helpers.h" #include "source_impl.h" @@ -171,6 +174,9 @@ source_impl::source_impl( const std::string &args ) #ifdef ENABLE_FREESRP dev_types.push_back("freesrp"); #endif +#ifdef ENABLE_PERSEUS + dev_types.push_back("perseus"); +#endif std::cerr << "gr-osmosdr " << GR_OSMOSDR_VERSION << " (" << GR_OSMOSDR_LIBVER << ") " << "gnuradio " << gr::version() << std::endl; @@ -252,7 +258,10 @@ source_impl::source_impl( const std::string &args ) BOOST_FOREACH( std::string dev, freesrp_source_c::get_devices() ) dev_list.push_back( dev ); #endif - +#ifdef ENABLE_PERSEUS + BOOST_FOREACH( std::string dev, perseus_source_c::get_devices() ) + dev_list.push_back( dev ); +#endif // std::cerr << std::endl; // BOOST_FOREACH( std::string dev, dev_list ) // std::cerr << "'" << dev << "'" << std::endl; @@ -383,6 +392,13 @@ source_impl::source_impl( const std::string &args ) } #endif +#ifdef ENABLE_PERSEUS + if ( dict.count("perseus") ) { + perseus_source_c_sptr src = make_perseus_source_c( arg ); + block = src; iface = src.get(); + } +#endif + if ( iface != NULL && long(block.get()) != 0 ) { _devs.push_back( iface ); -- 1.9.1 >From 1b6cf50a7a6074754717b0270757931c379561bc Mon Sep 17 00:00:00 2001 From: Andrea Montefusco IW0HDV Date: Thu, 1 Feb 2018 17:33:39 +0100 Subject: More hardware controls added (attenuator, ADC preamp and dither, preselector) --- lib/perseus/perseus_source_c.cc | 136 ++++++++++++++++++++++++++++++++++++---- lib/perseus/perseus_source_c.h | 19 +++++- 2 files changed, 142 insertions(+), 13 deletions(-) diff --git a/lib/perseus/perseus_source_c.cc b/lib/perseus/perseus_source_c.cc index 0720da4..89674b1 100644 --- a/lib/perseus/perseus_source_c.cc +++ b/lib/perseus/perseus_source_c.cc @@ -89,7 +89,11 @@ perseus_source_c::perseus_source_c (const std::string &args) _center_freq(0), _freq_corr(0), _frun(false), - _cnt(0) + _cnt(0), + _att_value(0), + _adc_preamp(false), + _adc_dither(false), + _preselector(false) { //int ret; @@ -174,9 +178,9 @@ perseus_source_c::perseus_source_c (const std::string &args) PERSEUS_THROW_ON_ERROR(ret, "Perseus fpga configuration error"); } - perseus_set_attenuator_in_db(_dev, PERSEUS_ATT_0DB); + perseus_set_attenuator_in_db(_dev, _att_value); // Enable ADC Dither, Disable ADC Preamp - perseus_set_adc(_dev, true, false); + perseus_set_adc(_dev, _adc_dither == true ? 1:0, _adc_preamp == true ? 1:0); // set the NCO frequency in the middle of the range allowed set_center_freq( (get_freq_range().start() + get_freq_range().stop()) / 2.0 ); set_sample_rate( get_sample_rates().start() ); @@ -425,7 +429,7 @@ double perseus_source_c::set_center_freq( double freq, size_t chan ) ret = perseus_set_ddc_center_freq(_dev, (uint64_t)(freq * (1.0 + (_freq_corr * 10e-6))), - 1); + _preselector == true ? 1:0); if ( ret == PERSEUS_NOERROR ) { _center_freq = freq; } else { @@ -453,55 +457,165 @@ double perseus_source_c::get_freq_corr( size_t chan ) std::vector perseus_source_c::get_gain_names( size_t chan ) { - return {}; + std::vector< std::string > names; + + names += "Attenuator"; + names += "ADC preamp"; + names += "ADC dither"; + + return names; } +// +// FIXME +// osmosdr::gain_range_t perseus_source_c::get_gain_range( size_t chan ) { - return osmosdr::gain_range_t(); + return get_gain_range( "Attenuator", chan ); } osmosdr::gain_range_t perseus_source_c::get_gain_range( const std::string & name, size_t chan ) { + if ( "Attenuator" == name ) { + return osmosdr::gain_range_t( -30, 0, 10 ); + } + if ( "ADC preamp" == name ) { + return osmosdr::gain_range_t( 0, 6, 6 ); + } + if ( "ADC dither" == name ) { + return osmosdr::gain_range_t( 0, 1, 1 ); + } + return osmosdr::gain_range_t(); } -double perseus_source_c::set_gain( double gain, size_t chan ) +double perseus_source_c::set_attenuator( double gain, size_t chan ) { + int ret; + + gain = - gain; + if (_dev && _att_value != gain) { + ret = perseus_set_attenuator_in_db (_dev, gain); + + if ( PERSEUS_NOERROR == ret ) { + _att_value = gain; + } else { + std::cerr << "ERROR: perseus_set_attenuator_in_db: " << gain << std::endl; + } + } + + return _att_value; +} + +double perseus_source_c::set_adc_preamp( double gain, size_t chan ) +{ + std::cerr << "set_adc_preamp: " << gain << std::endl; + + if (_dev) { + _adc_preamp = (gain != 0. ? true : false); + int ret = + perseus_set_adc(_dev, _adc_dither == true ? 1:0, _adc_preamp == true ? 1:0); + if ( PERSEUS_NOERROR != ret ) { + std::cerr << "ERROR: perseus_set_adc" << ret << std::endl; + } + } return gain; } -double perseus_source_c::set_gain( double gain, const std::string & name, size_t chan) +double perseus_source_c::set_adc_dither( double gain, size_t chan ) { + std::cerr << "set_adc_dither: " << gain << std::endl; + + if (_dev) { + _adc_dither = (gain != 0. ? true : false); + int ret = + perseus_set_adc(_dev, _adc_dither == true ? 1:0, _adc_preamp == true ? 1:0); + if ( PERSEUS_NOERROR != ret ) { + std::cerr << "ERROR: perseus_set_adc" << ret << std::endl; + } + } return gain; } -double perseus_source_c::get_gain( size_t chan ) +double perseus_source_c::get_attenuator( size_t chan ) { - return 0.0; + return _att_value; } +double perseus_source_c::get_adc_preamp(size_t chan ) +{ + double v = _adc_preamp == true ? 6. : 0.; + std::cerr << "get_adc_preamp: " << _adc_preamp << " value" << v << std::endl; + return v; +} + +double perseus_source_c::get_adc_dither(size_t chan ) +{ + double v = _adc_dither == true ? 1. : 0.; + std::cerr << "get_adc_dither: " << _adc_dither << " value" << v << std::endl; + return v; +} + + +double perseus_source_c::set_gain( double gain, const std::string & name, size_t chan) +{ + if ( "Attenuator" == name ) { + return set_attenuator( gain, chan ); + } + if ( "ADC preamp" == name ) { + return set_adc_preamp( gain, chan ); + } + if ( "ADC dither" == name ) { + return set_adc_dither( gain, chan ); + } + return gain; +} + + double perseus_source_c::get_gain( const std::string & name, size_t chan ) { + if ( "Attenuator" == name ) { + return get_attenuator( chan ); + } + if ( "ADC preamp" == name ) { + return get_adc_preamp( chan ); + } + if ( "ADC dither" == name ) { + return get_adc_dither( chan ); + } return 0.0; } + + std::vector< std::string > perseus_source_c::get_antennas( size_t chan ) { std::vector< std::string > antennas; - antennas += get_antenna( chan ); + antennas += "RX"; + antennas += "RX+PRESELECTOR"; return antennas; } std::string perseus_source_c::set_antenna( const std::string & antenna, size_t chan ) { + if ( antenna == "RX" ) { + _preselector = false; + set_center_freq( get_center_freq() ); + return "RX"; + } + if ( antenna == "RX+PRESELECTOR" ) { + _preselector = true; + set_center_freq( get_center_freq() ); + return "RX+PRESELECTOR"; + } return get_antenna( chan ); } std::string perseus_source_c::get_antenna( size_t chan ) { + std::cerr << "perseus_source_c::get_antenna: " << chan << std::endl; return "RX"; } diff --git a/lib/perseus/perseus_source_c.h b/lib/perseus/perseus_source_c.h index 9eda23d..934ff2b 100644 --- a/lib/perseus/perseus_source_c.h +++ b/lib/perseus/perseus_source_c.h @@ -87,9 +87,20 @@ public: std::vector get_gain_names( size_t chan = 0 ); osmosdr::gain_range_t get_gain_range( size_t chan = 0 ); osmosdr::gain_range_t get_gain_range( const std::string & name, size_t chan = 0 ); - double set_gain( double gain, size_t chan = 0 ); + + double set_gain( double gain, size_t chan = 0 ) {return gain; } + double get_gain( size_t chan = 0 ) {return _att_value; } + + double set_attenuator( double gain, size_t chan = 0 ); + double get_attenuator( size_t chan = 0 ); + + double set_adc_preamp( double gain, size_t chan = 0 ); + double get_adc_preamp( size_t chan = 0 ); + + double set_adc_dither( double gain, size_t chan = 0 ); + double get_adc_dither( size_t chan = 0 ); + double set_gain( double gain, const std::string & name, size_t chan = 0 ); - double get_gain( size_t chan = 0 ); double get_gain( const std::string & name, size_t chan = 0 ); std::vector< std::string > get_antennas( size_t chan = 0 ); @@ -115,6 +126,10 @@ private: bool _frun; int _cnt; + double _att_value; + bool _adc_preamp; + bool _adc_dither; + bool _preselector; }; #endif /* INCLUDED_PERSEUS_SOURCE_C_H */ -- 1.9.1 -- Andrea Montefusco IW0HDV ------------------------------------------ As my old boss, an Apollo veteran, would often remind us ?It?s good to be smart, but it?s better to be lucky.? Wayne Hale, Space Shuttle Flight Director -------------- next part -------------- An HTML attachment was scrubbed... URL: From andrea.montefusco at gmail.com Fri Feb 2 19:30:01 2018 From: andrea.montefusco at gmail.com (andrea montefusco) Date: Fri, 2 Feb 2018 20:30:01 +0100 Subject: Fwd: [PATCH] gr-osmosdr Microtelecom Perseus HF receiver support In-Reply-To: <5a74bb0c.aerZvsWGPppEi2JP%andrea.montefusco@gmail.com> References: <5a74bb0c.aerZvsWGPppEi2JP%andrea.montefusco@gmail.com> Message-ID: ---------- Forwarded message ---------- From: Date: Fri, Feb 2, 2018 at 8:25 PM Subject: [PATCH] gr-osmosdr Microtelecom Perseus HF receiver support To: andrea.montefusco at gmail.com Hi maintainer and all, Below you find a patch that implements Microtelecom Perseus support . I have testes it using gqrx SDR software. --- CMakeLists.txt | 1 + cmake/Modules/FindLibPERSEUS.cmake | 24 ++ lib/CMakeLists.txt | 8 + lib/airspy/airspy_source_c.cc | 5 +- lib/config.h.in | 1 + lib/device.cc | 8 + lib/perseus/CMakeLists.txt | 37 +++ lib/perseus/perseus_source_c.cc | 507 ++++++++++++++++++++++++++++++ +++++++ lib/perseus/perseus_source_c.h | 120 +++++++++ lib/rtl/rtl_source_c.cc | 2 +- lib/source_impl.cc | 18 +- 11 files changed, 727 insertions(+), 4 deletions(-) create mode 100644 cmake/Modules/FindLibPERSEUS.cmake create mode 100644 lib/perseus/CMakeLists.txt create mode 100644 lib/perseus/perseus_source_c.cc create mode 100644 lib/perseus/perseus_source_c.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 296456d..94acd4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,6 +173,7 @@ find_package(Volk) find_package(LibbladeRF) find_package(SoapySDR NO_MODULE) find_package(LibFreeSRP) +find_package(LibPERSEUS) find_package(Doxygen) if(NOT GNURADIO_RUNTIME_FOUND) diff --git a/cmake/Modules/FindLibPERSEUS.cmake b/cmake/Modules/ FindLibPERSEUS.cmake new file mode 100644 index 0000000..5477327 --- /dev/null +++ b/cmake/Modules/FindLibPERSEUS.cmake @@ -0,0 +1,24 @@ +INCLUDE(FindPkgConfig) +PKG_CHECK_MODULES(PC_LIBPERSEUS libperseus-sdr) + +FIND_PATH( + LIBPERSEUS_INCLUDE_DIRS + NAMES perseus-sdr.h + HINTS $ENV{LIBPERSEUS_DIR}/include + ${PC_LIBPERSEUS_INCLUDEDIR} + PATHS /usr/local/include + /usr/include +) + +FIND_LIBRARY( + LIBPERSEUS_LIBRARIES + NAMES perseus-sdr + HINTS $ENV{LIBPERSEUS_DIR}/lib + ${PC_LIBPERSEUS_LIBDIR} + PATHS /usr/local/lib + /usr/lib +) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBPERSEUS DEFAULT_MSG LIBPERSEUS_LIBRARIES LIBPERSEUS_INCLUDE_DIRS) +MARK_AS_ADVANCED(LIBPERSEUS_LIBRARIES LIBPERSEUS_INCLUDE_DIRS) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index c05b8d9..f555816 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -249,6 +249,14 @@ GR_INCLUDE_SUBDIRECTORY(freesrp) endif(ENABLE_FREESRP) ######################################################################## +# Setup PERSEUS component +######################################################################## +GR_REGISTER_COMPONENT("Microtelecom Perseus Receiver" ENABLE_PERSEUS LIBPERSEUS_FOUND) +if(ENABLE_PERSEUS) +GR_INCLUDE_SUBDIRECTORY(perseus) +endif(ENABLE_PERSEUS) + +######################################################################## # Setup configuration file ######################################################################## ADD_DEFINITIONS(-DHAVE_CONFIG_H=1) diff --git a/lib/airspy/airspy_source_c.cc b/lib/airspy/airspy_source_c.cc index 50150e5..02fc6ab 100644 --- a/lib/airspy/airspy_source_c.cc +++ b/lib/airspy/airspy_source_c.cc @@ -350,9 +350,10 @@ osmosdr::meta_range_t airspy_source_c::get_sample_ rates() { osmosdr::meta_range_t range; - for (size_t i = 0; i < _sample_rates.size(); i++) + for (size_t i = 0; i < _sample_rates.size(); i++) { + std::cerr << "SR: [" << i << "]: " << _sample_rates[i].first << std::endl; range += osmosdr::range_t( _sample_rates[i].first ); - + } return range; } diff --git a/lib/config.h.in b/lib/config.h.in index 42e72f1..dac7e39 100644 --- a/lib/config.h.in +++ b/lib/config.h.in @@ -19,6 +19,7 @@ #cmakedefine ENABLE_SOAPY #cmakedefine ENABLE_REDPITAYA #cmakedefine ENABLE_FREESRP +#cmakedefine ENABLE_PERSEUS //provide NAN define for MSVC older than VC12 #if defined(_MSC_VER) && (_MSC_VER < 1800) diff --git a/lib/device.cc b/lib/device.cc index 025a22b..df0d734 100644 --- a/lib/device.cc +++ b/lib/device.cc @@ -90,6 +90,10 @@ #include #endif +#ifdef ENABLE_PERSEUS +#include +#endif + #include "arg_helpers.h" using namespace osmosdr; @@ -194,6 +198,10 @@ devices_t device::find(const device_t &hint) BOOST_FOREACH( std::string dev, soapy_source_c::get_devices() ) devices.push_back( device_t(dev) ); #endif +#ifdef ENABLE_PERSEUS + BOOST_FOREACH( std::string dev, perseus_source_c::get_devices() ) + devices.push_back( device_t(dev) ); +#endif /* software-only sources should be appended at the very end, * hopefully resulting in hardware sources to be shown first diff --git a/lib/perseus/CMakeLists.txt b/lib/perseus/CMakeLists.txt new file mode 100644 index 0000000..f45a10d --- /dev/null +++ b/lib/perseus/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright 2017 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio 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, or (at your option) +# any later version. +# +# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${LIBPERSEUS_INCLUDE_DIRS} +) + +set(perseus_srcs + ${CMAKE_CURRENT_SOURCE_DIR}/perseus_source_c.cc +) + +######################################################################## +# Append gnuradio-osmosdr library sources +######################################################################## +list(APPEND gr_osmosdr_srcs ${perseus_srcs}) +list(APPEND gr_osmosdr_libs ${LIBPERSEUS_LIBRARIES} ${GNURADIO_BLOCKS_LIBRARIES}) diff --git a/lib/perseus/perseus_source_c.cc b/lib/perseus/perseus_source_ c.cc new file mode 100644 index 0000000..0720da4 --- /dev/null +++ b/lib/perseus/perseus_source_c.cc @@ -0,0 +1,507 @@ +/* -*- c++ -*- */ +/* + * Copyright 2018 Andrea Montefusco IW0HDV + * + * GNU Radio 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, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +/* + * config.h is generated by configure. It contains the results + * of probing for features, options etc. It should be the first + * file included in your .cc file. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "perseus_source_c.h" +#include "arg_helpers.h" + + + +using namespace boost::assign; + +#define PERSEUS_FORMAT_ERROR(ret, msg) \ + boost::str( boost::format(msg " (%1%)") % ret ) + +#define PERSEUS_THROW_ON_ERROR(ret, msg) \ + if ( ret != PERSEUS_NOERROR ) \ + { \ + throw std::runtime_error( PERSEUS_FORMAT_ERROR(ret, msg) ); \ + } + +#define PERSEUS_FUNC_STR(func, arg) \ + boost::str(boost::format(func "(%1%)") % arg) + " has failed" + +perseus_source_c_sptr make_perseus_source_c (const std::string & args) +{ + return gnuradio::get_initial_sptr(new perseus_source_c (args)); +} + +/* + * Specify constraints on number of input and output streams. + * This info is used to construct the input and output signatures + * (2nd & 3rd args to gr::block's constructor). The input and + * output signatures are used by the runtime system to + * check that a valid number and type of inputs and outputs + * are connected to this block. In this case, we accept + * only 0 input and 1 output. + */ +static const int MIN_IN = 0; // mininum number of input streams +static const int MAX_IN = 0; // maximum number of input streams +static const int MIN_OUT = 1; // minimum number of output streams +static const int MAX_OUT = 1; // maximum number of output streams + +/* + * The private constructor + */ +perseus_source_c::perseus_source_c (const std::string &args) + : gr::sync_block ("perseus_source_c", + gr::io_signature::make(MIN_IN, MAX_IN, sizeof (gr_complex)), + gr::io_signature::make(MIN_OUT, MAX_OUT, sizeof (gr_complex))), + _dev(NULL), + _sample_rate(0), + _center_freq(0), + _freq_corr(0), + _frun(false), + _cnt(0) +{ + //int ret; + + dict_t dict = params_to_dict(args); + + _dev = NULL; + + // Set debug info dumped to stderr to the maximum verbose level + perseus_set_debug(3); + + //fprintf (stderr, "Revision: %s\n", git_revision); + //fprintf (stderr, "SAMPLE RATE: %d\n", sr); + //fprintf (stderr, "NBUF: %d BUF SIZE: %d TOTAL BUFFER LENGTH: %d\n", nb, bs, nb*bs); + + // Check how many Perseus receivers are connected to the system + int num_perseus = perseus_init(); + std::cerr << "## Microtelecom Perseus ctor: " << num_perseus << " Perseus receivers found." << std::endl; + + if (num_perseus==0) { + perseus_exit(); + PERSEUS_THROW_ON_ERROR(PERSEUS_DEVNOTFOUND, "No Perseus receivers detected"); + } + // Open the first one... + if ((_dev = perseus_open(0)) == NULL) { + PERSEUS_THROW_ON_ERROR(PERSEUS_DEVNOTFOUND, "Unable to open Perseus device"); + } + // Download the standard firmware to the unit + std::cerr << "Downloading firmware..." << std::endl; + if (int rc = perseus_firmware_download(_dev,NULL)<0) { + PERSEUS_THROW_ON_ERROR (rc, "firmware download error"); + } + + // Dump some information about the receiver (S/N and HW rev) + int flag; + perseus_is_preserie(_dev, &flag); + if (flag != 0) + fprintf(stderr, "The device is a preserie unit"); + else { + eeprom_prodid prodid; + if (perseus_get_product_id(_dev,&prodid)<0) + fprintf(stderr, "get product id error: %s", perseus_errorstr()); + else + fprintf(stderr, "Receiver S/N: %05d-%02hX%02hX-%02hX%02hX-%02hX%02hX - HW Release:%hd.%hd\n", + (uint16_t) prodid.sn, + (uint16_t) prodid.signature[5], + (uint16_t) prodid.signature[4], + (uint16_t) prodid.signature[3], + (uint16_t) prodid.signature[2], + (uint16_t) prodid.signature[1], + (uint16_t) prodid.signature[0], + (uint16_t) prodid.hwrel, + (uint16_t) prodid.hwver); + } + // Printing all sampling rates available ..... + { + int buf[BUFSIZ]; + + if (perseus_get_sampling_rates (_dev, buf, sizeof(buf)/sizeof(buf[0])) < 0) { + fprintf(stderr, "get sampling rates error: %s\n", perseus_errorstr()); + } else { + int i = 0; + while (buf[i]) { + fprintf(stderr, "#%d: sample rate: %d\n", i, buf[i]); + + _sample_rates.push_back( std::pair( buf[i], i ) ); + i++; + } + /* since they may (and will) give us an unsorted array we have to sort it here + * to play nice with the monotonic requirement of meta-range later on */ + std::sort(_sample_rates.begin(), _sample_rates.end()); + + for (size_t i = 0; i < _sample_rates.size(); i++) + std::cerr << boost::format("%gM ") % (_sample_rates[i].first / 1e6); + + std::cerr << std::endl; + } + } + // Configure the receiver for 2 MS/s operations + fprintf(stderr, "Configuring FPGA...\n"); + if (int ret = perseus_set_sampling_rate(_dev, 96000) < 0) { // specify the sampling rate value in Samples/second + std::cerr << "fpga configuration error: " << perseus_errorstr() << std::endl; + PERSEUS_THROW_ON_ERROR(ret, "Perseus fpga configuration error"); + } + + perseus_set_attenuator_in_db(_dev, PERSEUS_ATT_0DB); + // Enable ADC Dither, Disable ADC Preamp + perseus_set_adc(_dev, true, false); + // set the NCO frequency in the middle of the range allowed + set_center_freq( (get_freq_range().start() + get_freq_range().stop()) / 2.0 ); + set_sample_rate( get_sample_rates().start() ); + + _fifo = new boost::circular_buffer(5000000); + if (!_fifo) { + throw std::runtime_error( std::string(__FUNCTION__) + " " + + "Failed to allocate a sample FIFO!" ); + } +} + +/* + * Our virtual destructor. + */ +perseus_source_c::~perseus_source_c () +{ + if (_dev) { + if (_frun) stop(); + int ret = perseus_close(_dev); + if ( ret != PERSEUS_NOERROR ) { + std::cerr << PERSEUS_FORMAT_ERROR(ret, "Failed to close Perseus") << std::endl; + } else + _dev = NULL; + } + perseus_exit(); + + if (_fifo) { + delete _fifo; + _fifo = NULL; + } +} + + +typedef union { + struct { + int32_t i; + int32_t q; + } __attribute__((__packed__)) iq; + struct { + uint8_t i1; + uint8_t i2; + uint8_t i3; + uint8_t i4; + uint8_t q1; + uint8_t q2; + uint8_t q3; + uint8_t q4; + } __attribute__((__packed__)) ; +} iq_sample; + + +int perseus_source_c::perseus_rx_callback(void *buf, int buf_size) +{ + size_t i, n_avail, to_copy, num_samples; + + uint8_t *samplebuf = (uint8_t*)buf; + + num_samples = buf_size/6; + + if (buf_size % 6) + std::cerr << "FATAL !!!!!!! not on boundary" << std::endl; + + iq_sample s; + + _fifo_lock.lock(); + + n_avail = _fifo->capacity() - _fifo->size(); + to_copy = (n_avail < num_samples ? n_avail : num_samples); + + for (i = 0; i < to_copy; i++ ) { + + s.i1 = s.q1 = 0; + s.i2 = *samplebuf++; + s.i3 = *samplebuf++; + s.i4 = *samplebuf++; + s.q2 = *samplebuf++; + s.q3 = *samplebuf++; + s.q4 = *samplebuf++; + + // convert 24 bit two's complements integers to float in [-1.0 - +1.0] range + + /* Push sample to the fifo */ + _fifo->push_back( gr_complex( (float)(s.iq.i) / (INT_MAX - 256), (float)(s.iq.q) / (INT_MAX - 256) ) ); + + } + // keep a counter of processed bytes + _cnt += buf_size; + + _fifo_lock.unlock(); + + /* We have made some new samples available to the consumer in work() */ + if (to_copy) { + //std::cerr << "+" << std::flush; + _samp_avail.notify_one(); + } + + /* Indicate overrun, if necessary */ + if (to_copy < num_samples) + std::cerr << "O" << std::flush; + + return 0; // TODO: return -1 on error/stop +} + +// static method used as callback from the libperseus-sdr library +int perseus_source_c::_perseus_rx_callback(void *buf, int buf_size, void *extra) +{ + perseus_source_c *obj = (perseus_source_c *)extra; + + return obj->perseus_rx_callback((float *)buf, buf_size); + + return 0; +} + + +bool perseus_source_c::start() +{ + int ret; + + if ( ! _dev ) + return false; + + std::cerr << "***** START COUNTER: " << _cnt + << " mod 6 " << (_cnt % 6) << std::endl; + + _frun = true; + + // in order to guarantee samples stream synchronization + // re-set the sample rate each time the stream is started + set_sample_rate( get_sample_rate() ); + + ret = perseus_start_async_input(_dev, + 6 * 1024 * 2 /* buffer size */, + perseus_source_c::_perseus_rx_callback, + (void *)this); + if (ret != PERSEUS_NOERROR) { + std::cerr << "Failed to start RX streaming (" + << ret << ")" << std::endl; + return false; + } else + return true; +} + +bool perseus_source_c::stop() +{ + if ( ! _dev ) + return false; + + _frun = false; + + std::cerr << "***** STOP COUNTER: " << _cnt + << " mod 6 " << (_cnt % 6) << std::endl; + + int ret = perseus_stop_async_input(_dev); + + if ( ret != PERSEUS_NOERROR ) { + std::cerr << "Failed to stop RX streaming (" << ret << ")" << std::endl; + return false; + } else + return true; +} + +int perseus_source_c::work( int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items ) +{ + gr_complex *out = (gr_complex *)output_items[0]; + + if ( _frun == false ) return WORK_DONE; + + boost::unique_lock lock(_fifo_lock); + + /* Wait until we have the requested number of samples */ + int n_samples_avail = _fifo->size(); + + while (n_samples_avail < noutput_items) { + _samp_avail.wait(lock); + n_samples_avail = _fifo->size(); + } + + for(int i = 0; i < noutput_items; ++i) { + out[i] = _fifo->at(0); + _fifo->pop_front(); + } + + return noutput_items; +} + +std::vector perseus_source_c::get_devices() +{ + std::vector devices; + std::string label; + + std::string args = "perseus=0,label='Perseus'"; + devices.push_back( args ); + + return devices; +} + +size_t perseus_source_c::get_num_channels() +{ + return 1; +} + +osmosdr::meta_range_t perseus_source_c::get_sample_rates() +{ + osmosdr::meta_range_t range; + + for (size_t i = 0; i < _sample_rates.size(); i++) { + range += osmosdr::range_t( _sample_rates[i].first ); + std::cerr << "SR: " << i << " : (" + << _sample_rates[i].first << ")" << std::endl; + } + return range; +} + +double perseus_source_c::set_sample_rate( double rate ) +{ + int ret; + if ((ret = perseus_set_sampling_rate(_dev, rate)) < 0) { + PERSEUS_THROW_ON_ERROR(ret, "Unable to set sample rate."); + } else + _sample_rate = rate; + + return get_sample_rate(); +} + +double perseus_source_c::get_sample_rate() +{ + return _sample_rate; +} + +osmosdr::freq_range_t perseus_source_c::get_freq_range( size_t chan ) +{ + osmosdr::freq_range_t range; + + range += osmosdr::range_t( PERSEUS_DDC_FREQ_MIN, PERSEUS_DDC_FREQ_MAX ); + + return range; +} + +double perseus_source_c::set_center_freq( double freq, size_t chan ) +{ + int ret; + + if (_dev) { + ret = + perseus_set_ddc_center_freq(_dev, + (uint64_t)(freq * (1.0 + (_freq_corr * 10e-6))), + 1); + if ( ret == PERSEUS_NOERROR ) { + _center_freq = freq; + } else { + PERSEUS_THROW_ON_ERROR( ret, PERSEUS_FUNC_STR( "perseus_set_ddc_center_freq", freq ) ) + } + } + + return get_center_freq( chan ); +} + +double perseus_source_c::get_center_freq( size_t chan ) +{ + return _center_freq; +} + +double perseus_source_c::set_freq_corr( double ppm, size_t chan ) +{ + return _freq_corr = ppm; +} + +double perseus_source_c::get_freq_corr( size_t chan ) +{ + return _freq_corr; +} + +std::vector perseus_source_c::get_gain_names( size_t chan ) +{ + return {}; +} + +osmosdr::gain_range_t perseus_source_c::get_gain_range( size_t chan ) +{ + return osmosdr::gain_range_t(); +} + +osmosdr::gain_range_t perseus_source_c::get_gain_range( const std::string & name, size_t chan ) +{ + return osmosdr::gain_range_t(); +} + + +double perseus_source_c::set_gain( double gain, size_t chan ) +{ + return gain; +} + +double perseus_source_c::set_gain( double gain, const std::string & name, size_t chan) +{ + return gain; +} + +double perseus_source_c::get_gain( size_t chan ) +{ + return 0.0; +} + +double perseus_source_c::get_gain( const std::string & name, size_t chan ) +{ + return 0.0; +} + +std::vector< std::string > perseus_source_c::get_antennas( size_t chan ) +{ + std::vector< std::string > antennas; + + antennas += get_antenna( chan ); + + return antennas; +} + +std::string perseus_source_c::set_antenna( const std::string & antenna, size_t chan ) +{ + return get_antenna( chan ); +} + +std::string perseus_source_c::get_antenna( size_t chan ) +{ + return "RX"; +} diff --git a/lib/perseus/perseus_source_c.h b/lib/perseus/perseus_source_c.h new file mode 100644 index 0000000..9eda23d --- /dev/null +++ b/lib/perseus/perseus_source_c.h @@ -0,0 +1,120 @@ +/* -*- c++ -*- */ +/* + * Copyright 2013 Dimitri Stolnikov + * + * This file is part of GNU Radio + * + * GNU Radio 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, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ +#ifndef INCLUDED_PERSEUS_SOURCE_C_H +#define INCLUDED_PERSEUS_SOURCE_C_H + +#include +#include +#include + +#include + +#include "source_iface.h" +#include + +class perseus_source_c; + +typedef boost::shared_ptr perseus_source_c_sptr; + +/*! + * \brief Return a shared_ptr to a new instance of perseus_source_c. + * + * To avoid accidental use of raw pointers, perseus_source_c's + * constructor is private. make_perseus_source_c is the public + * interface for creating new instances. + */ +perseus_source_c_sptr make_perseus_source_c (const std::string & args = ""); + +/*! + * \brief Provides a stream of complex samples. + * \ingroup block + */ +class perseus_source_c : + public gr::sync_block, + public source_iface +{ +private: + // The friend declaration allows make_perseus_source_c to + // access the private constructor. + + friend perseus_source_c_sptr make_perseus_source_c (const std::string & args); + + perseus_source_c (const std::string & args); + +public: + ~perseus_source_c (); + + bool start(); + bool stop(); + + int work( int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items ); + + static std::vector< std::string > get_devices(); + + size_t get_num_channels( void ); + + osmosdr::meta_range_t get_sample_rates( void ); + double set_sample_rate( double rate ); + double get_sample_rate( void ); + + osmosdr::freq_range_t get_freq_range( size_t chan = 0 ); + double set_center_freq( double freq, size_t chan = 0 ); + double get_center_freq( size_t chan = 0 ); + double set_freq_corr( double ppm, size_t chan = 0 ); + double get_freq_corr( size_t chan = 0 ); + + std::vector get_gain_names( size_t chan = 0 ); + osmosdr::gain_range_t get_gain_range( size_t chan = 0 ); + osmosdr::gain_range_t get_gain_range( const std::string & name, size_t chan = 0 ); + double set_gain( double gain, size_t chan = 0 ); + double set_gain( double gain, const std::string & name, size_t chan = 0 ); + double get_gain( size_t chan = 0 ); + double get_gain( const std::string & name, size_t chan = 0 ); + + std::vector< std::string > get_antennas( size_t chan = 0 ); + std::string set_antenna( const std::string & antenna, size_t chan = 0 ); + std::string get_antenna( size_t chan = 0 ); + + +private: + static int _perseus_rx_callback(void *buf, int buf_size, void *extra); + + int perseus_rx_callback(void *samples, int sample_count); + + perseus_descr *_dev; + + boost::circular_buffer *_fifo; + boost::mutex _fifo_lock; + boost::condition_variable _samp_avail; + + std::vector< std::pair > _sample_rates; + double _sample_rate; + double _center_freq; + double _freq_corr; + + bool _frun; + int _cnt; +}; + +#endif /* INCLUDED_PERSEUS_SOURCE_C_H */ diff --git a/lib/rtl/rtl_source_c.cc b/lib/rtl/rtl_source_c.cc index a371464..eb7042e 100644 --- a/lib/rtl/rtl_source_c.cc +++ b/lib/rtl/rtl_source_c.cc @@ -221,7 +221,7 @@ rtl_source_c::rtl_source_c (const std::string &args) throw std::runtime_error("Failed to enable offset tuning."); } - ret = rtlsdr_set_bias_tee(_dev, bias_tee); +// ret = rtlsdr_set_bias_tee(_dev, bias_tee); if (ret < 0) throw std::runtime_error("Failed to set bias tee."); diff --git a/lib/source_impl.cc b/lib/source_impl.cc index a28f314..ead4ef7 100644 --- a/lib/source_impl.cc +++ b/lib/source_impl.cc @@ -92,6 +92,9 @@ #include #endif +#ifdef ENABLE_PERSEUS +#include +#endif #include "arg_helpers.h" #include "source_impl.h" @@ -171,6 +174,9 @@ source_impl::source_impl( const std::string &args ) #ifdef ENABLE_FREESRP dev_types.push_back("freesrp"); #endif +#ifdef ENABLE_PERSEUS + dev_types.push_back("perseus"); +#endif std::cerr << "gr-osmosdr " << GR_OSMOSDR_VERSION << " (" << GR_OSMOSDR_LIBVER << ") " << "gnuradio " << gr::version() << std::endl; @@ -252,7 +258,10 @@ source_impl::source_impl( const std::string &args ) BOOST_FOREACH( std::string dev, freesrp_source_c::get_devices() ) dev_list.push_back( dev ); #endif - +#ifdef ENABLE_PERSEUS + BOOST_FOREACH( std::string dev, perseus_source_c::get_devices() ) + dev_list.push_back( dev ); +#endif // std::cerr << std::endl; // BOOST_FOREACH( std::string dev, dev_list ) // std::cerr << "'" << dev << "'" << std::endl; @@ -383,6 +392,13 @@ source_impl::source_impl( const std::string &args ) } #endif +#ifdef ENABLE_PERSEUS + if ( dict.count("perseus") ) { + perseus_source_c_sptr src = make_perseus_source_c( arg ); + block = src; iface = src.get(); + } +#endif + if ( iface != NULL && long(block.get()) != 0 ) { _devs.push_back( iface ); -- 1.9.1 >From 1b6cf50a7a6074754717b0270757931c379561bc Mon Sep 17 00:00:00 2001 From: Andrea Montefusco IW0HDV Date: Thu, 1 Feb 2018 17:33:39 +0100 Subject: More hardware controls added (attenuator, ADC preamp and dither, preselector) --- lib/perseus/perseus_source_c.cc | 136 ++++++++++++++++++++++++++++++ ++++++---- lib/perseus/perseus_source_c.h | 19 +++++- 2 files changed, 142 insertions(+), 13 deletions(-) diff --git a/lib/perseus/perseus_source_c.cc b/lib/perseus/perseus_source_ c.cc index 0720da4..89674b1 100644 --- a/lib/perseus/perseus_source_c.cc +++ b/lib/perseus/perseus_source_c.cc @@ -89,7 +89,11 @@ perseus_source_c::perseus_source_c (const std::string &args) _center_freq(0), _freq_corr(0), _frun(false), - _cnt(0) + _cnt(0), + _att_value(0), + _adc_preamp(false), + _adc_dither(false), + _preselector(false) { //int ret; @@ -174,9 +178,9 @@ perseus_source_c::perseus_source_c (const std::string &args) PERSEUS_THROW_ON_ERROR(ret, "Perseus fpga configuration error"); } - perseus_set_attenuator_in_db(_dev, PERSEUS_ATT_0DB); + perseus_set_attenuator_in_db(_dev, _att_value); // Enable ADC Dither, Disable ADC Preamp - perseus_set_adc(_dev, true, false); + perseus_set_adc(_dev, _adc_dither == true ? 1:0, _adc_preamp == true ? 1:0); // set the NCO frequency in the middle of the range allowed set_center_freq( (get_freq_range().start() + get_freq_range().stop()) / 2.0 ); set_sample_rate( get_sample_rates().start() ); @@ -425,7 +429,7 @@ double perseus_source_c::set_center_freq( double freq, size_t chan ) ret = perseus_set_ddc_center_freq(_dev, (uint64_t)(freq * (1.0 + (_freq_corr * 10e-6))), - 1); + _preselector == true ? 1:0); if ( ret == PERSEUS_NOERROR ) { _center_freq = freq; } else { @@ -453,55 +457,165 @@ double perseus_source_c::get_freq_corr( size_t chan ) std::vector perseus_source_c::get_gain_names( size_t chan ) { - return {}; + std::vector< std::string > names; + + names += "Attenuator"; + names += "ADC preamp"; + names += "ADC dither"; + + return names; } +// +// FIXME +// osmosdr::gain_range_t perseus_source_c::get_gain_range( size_t chan ) { - return osmosdr::gain_range_t(); + return get_gain_range( "Attenuator", chan ); } osmosdr::gain_range_t perseus_source_c::get_gain_range( const std::string & name, size_t chan ) { + if ( "Attenuator" == name ) { + return osmosdr::gain_range_t( -30, 0, 10 ); + } + if ( "ADC preamp" == name ) { + return osmosdr::gain_range_t( 0, 6, 6 ); + } + if ( "ADC dither" == name ) { + return osmosdr::gain_range_t( 0, 1, 1 ); + } + return osmosdr::gain_range_t(); } -double perseus_source_c::set_gain( double gain, size_t chan ) +double perseus_source_c::set_attenuator( double gain, size_t chan ) { + int ret; + + gain = - gain; + if (_dev && _att_value != gain) { + ret = perseus_set_attenuator_in_db (_dev, gain); + + if ( PERSEUS_NOERROR == ret ) { + _att_value = gain; + } else { + std::cerr << "ERROR: perseus_set_attenuator_in_db: " << gain << std::endl; + } + } + + return _att_value; +} + +double perseus_source_c::set_adc_preamp( double gain, size_t chan ) +{ + std::cerr << "set_adc_preamp: " << gain << std::endl; + + if (_dev) { + _adc_preamp = (gain != 0. ? true : false); + int ret = + perseus_set_adc(_dev, _adc_dither == true ? 1:0, _adc_preamp == true ? 1:0); + if ( PERSEUS_NOERROR != ret ) { + std::cerr << "ERROR: perseus_set_adc" << ret << std::endl; + } + } return gain; } -double perseus_source_c::set_gain( double gain, const std::string & name, size_t chan) +double perseus_source_c::set_adc_dither( double gain, size_t chan ) { + std::cerr << "set_adc_dither: " << gain << std::endl; + + if (_dev) { + _adc_dither = (gain != 0. ? true : false); + int ret = + perseus_set_adc(_dev, _adc_dither == true ? 1:0, _adc_preamp == true ? 1:0); + if ( PERSEUS_NOERROR != ret ) { + std::cerr << "ERROR: perseus_set_adc" << ret << std::endl; + } + } return gain; } -double perseus_source_c::get_gain( size_t chan ) +double perseus_source_c::get_attenuator( size_t chan ) { - return 0.0; + return _att_value; } +double perseus_source_c::get_adc_preamp(size_t chan ) +{ + double v = _adc_preamp == true ? 6. : 0.; + std::cerr << "get_adc_preamp: " << _adc_preamp << " value" << v << std::endl; + return v; +} + +double perseus_source_c::get_adc_dither(size_t chan ) +{ + double v = _adc_dither == true ? 1. : 0.; + std::cerr << "get_adc_dither: " << _adc_dither << " value" << v << std::endl; + return v; +} + + +double perseus_source_c::set_gain( double gain, const std::string & name, size_t chan) +{ + if ( "Attenuator" == name ) { + return set_attenuator( gain, chan ); + } + if ( "ADC preamp" == name ) { + return set_adc_preamp( gain, chan ); + } + if ( "ADC dither" == name ) { + return set_adc_dither( gain, chan ); + } + return gain; +} + + double perseus_source_c::get_gain( const std::string & name, size_t chan ) { + if ( "Attenuator" == name ) { + return get_attenuator( chan ); + } + if ( "ADC preamp" == name ) { + return get_adc_preamp( chan ); + } + if ( "ADC dither" == name ) { + return get_adc_dither( chan ); + } return 0.0; } + + std::vector< std::string > perseus_source_c::get_antennas( size_t chan ) { std::vector< std::string > antennas; - antennas += get_antenna( chan ); + antennas += "RX"; + antennas += "RX+PRESELECTOR"; return antennas; } std::string perseus_source_c::set_antenna( const std::string & antenna, size_t chan ) { + if ( antenna == "RX" ) { + _preselector = false; + set_center_freq( get_center_freq() ); + return "RX"; + } + if ( antenna == "RX+PRESELECTOR" ) { + _preselector = true; + set_center_freq( get_center_freq() ); + return "RX+PRESELECTOR"; + } return get_antenna( chan ); } std::string perseus_source_c::get_antenna( size_t chan ) { + std::cerr << "perseus_source_c::get_antenna: " << chan << std::endl; return "RX"; } diff --git a/lib/perseus/perseus_source_c.h b/lib/perseus/perseus_source_c.h index 9eda23d..934ff2b 100644 --- a/lib/perseus/perseus_source_c.h +++ b/lib/perseus/perseus_source_c.h @@ -87,9 +87,20 @@ public: std::vector get_gain_names( size_t chan = 0 ); osmosdr::gain_range_t get_gain_range( size_t chan = 0 ); osmosdr::gain_range_t get_gain_range( const std::string & name, size_t chan = 0 ); - double set_gain( double gain, size_t chan = 0 ); + + double set_gain( double gain, size_t chan = 0 ) {return gain; } + double get_gain( size_t chan = 0 ) {return _att_value; } + + double set_attenuator( double gain, size_t chan = 0 ); + double get_attenuator( size_t chan = 0 ); + + double set_adc_preamp( double gain, size_t chan = 0 ); + double get_adc_preamp( size_t chan = 0 ); + + double set_adc_dither( double gain, size_t chan = 0 ); + double get_adc_dither( size_t chan = 0 ); + double set_gain( double gain, const std::string & name, size_t chan = 0 ); - double get_gain( size_t chan = 0 ); double get_gain( const std::string & name, size_t chan = 0 ); std::vector< std::string > get_antennas( size_t chan = 0 ); @@ -115,6 +126,10 @@ private: bool _frun; int _cnt; + double _att_value; + bool _adc_preamp; + bool _adc_dither; + bool _preselector; }; #endif /* INCLUDED_PERSEUS_SOURCE_C_H */ -- 1.9.1 . QUIT -- Andrea Montefusco IW0HDV ------------------------------------------ As my old boss, an Apollo veteran, would often remind us ?It?s good to be smart, but it?s better to be lucky.? Wayne Hale, Space Shuttle Flight Director -------------- next part -------------- An HTML attachment was scrubbed... URL: From andrea.montefusco at gmail.com Fri Feb 2 19:30:59 2018 From: andrea.montefusco at gmail.com (andrea.montefusco at gmail.com) Date: Fri, 02 Feb 2018 20:30:59 +0100 Subject: [PATCH] gr-osmosdr Microtelecom Perseus HF receiver support Message-ID: <5a74bc73.5T/EJyKs2UE74SBV%andrea.montefusco@gmail.com> Hi maintainer and all, Below you find a patch that implements Microtelecom Perseus support . I have testes it using gqrx SDR software. --- CMakeLists.txt | 1 + cmake/Modules/FindLibPERSEUS.cmake | 24 ++ lib/CMakeLists.txt | 8 + lib/airspy/airspy_source_c.cc | 5 +- lib/config.h.in | 1 + lib/device.cc | 8 + lib/perseus/CMakeLists.txt | 37 +++ lib/perseus/perseus_source_c.cc | 507 +++++++++++++++++++++++++++++++++++++ lib/perseus/perseus_source_c.h | 120 +++++++++ lib/rtl/rtl_source_c.cc | 2 +- lib/source_impl.cc | 18 +- 11 files changed, 727 insertions(+), 4 deletions(-) create mode 100644 cmake/Modules/FindLibPERSEUS.cmake create mode 100644 lib/perseus/CMakeLists.txt create mode 100644 lib/perseus/perseus_source_c.cc create mode 100644 lib/perseus/perseus_source_c.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 296456d..94acd4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,6 +173,7 @@ find_package(Volk) find_package(LibbladeRF) find_package(SoapySDR NO_MODULE) find_package(LibFreeSRP) +find_package(LibPERSEUS) find_package(Doxygen) if(NOT GNURADIO_RUNTIME_FOUND) diff --git a/cmake/Modules/FindLibPERSEUS.cmake b/cmake/Modules/FindLibPERSEUS.cmake new file mode 100644 index 0000000..5477327 --- /dev/null +++ b/cmake/Modules/FindLibPERSEUS.cmake @@ -0,0 +1,24 @@ +INCLUDE(FindPkgConfig) +PKG_CHECK_MODULES(PC_LIBPERSEUS libperseus-sdr) + +FIND_PATH( + LIBPERSEUS_INCLUDE_DIRS + NAMES perseus-sdr.h + HINTS $ENV{LIBPERSEUS_DIR}/include + ${PC_LIBPERSEUS_INCLUDEDIR} + PATHS /usr/local/include + /usr/include +) + +FIND_LIBRARY( + LIBPERSEUS_LIBRARIES + NAMES perseus-sdr + HINTS $ENV{LIBPERSEUS_DIR}/lib + ${PC_LIBPERSEUS_LIBDIR} + PATHS /usr/local/lib + /usr/lib +) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBPERSEUS DEFAULT_MSG LIBPERSEUS_LIBRARIES LIBPERSEUS_INCLUDE_DIRS) +MARK_AS_ADVANCED(LIBPERSEUS_LIBRARIES LIBPERSEUS_INCLUDE_DIRS) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index c05b8d9..f555816 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -249,6 +249,14 @@ GR_INCLUDE_SUBDIRECTORY(freesrp) endif(ENABLE_FREESRP) ######################################################################## +# Setup PERSEUS component +######################################################################## +GR_REGISTER_COMPONENT("Microtelecom Perseus Receiver" ENABLE_PERSEUS LIBPERSEUS_FOUND) +if(ENABLE_PERSEUS) +GR_INCLUDE_SUBDIRECTORY(perseus) +endif(ENABLE_PERSEUS) + +######################################################################## # Setup configuration file ######################################################################## ADD_DEFINITIONS(-DHAVE_CONFIG_H=1) diff --git a/lib/airspy/airspy_source_c.cc b/lib/airspy/airspy_source_c.cc index 50150e5..02fc6ab 100644 --- a/lib/airspy/airspy_source_c.cc +++ b/lib/airspy/airspy_source_c.cc @@ -350,9 +350,10 @@ osmosdr::meta_range_t airspy_source_c::get_sample_rates() { osmosdr::meta_range_t range; - for (size_t i = 0; i < _sample_rates.size(); i++) + for (size_t i = 0; i < _sample_rates.size(); i++) { + std::cerr << "SR: [" << i << "]: " << _sample_rates[i].first << std::endl; range += osmosdr::range_t( _sample_rates[i].first ); - + } return range; } diff --git a/lib/config.h.in b/lib/config.h.in index 42e72f1..dac7e39 100644 --- a/lib/config.h.in +++ b/lib/config.h.in @@ -19,6 +19,7 @@ #cmakedefine ENABLE_SOAPY #cmakedefine ENABLE_REDPITAYA #cmakedefine ENABLE_FREESRP +#cmakedefine ENABLE_PERSEUS //provide NAN define for MSVC older than VC12 #if defined(_MSC_VER) && (_MSC_VER < 1800) diff --git a/lib/device.cc b/lib/device.cc index 025a22b..df0d734 100644 --- a/lib/device.cc +++ b/lib/device.cc @@ -90,6 +90,10 @@ #include #endif +#ifdef ENABLE_PERSEUS +#include +#endif + #include "arg_helpers.h" using namespace osmosdr; @@ -194,6 +198,10 @@ devices_t device::find(const device_t &hint) BOOST_FOREACH( std::string dev, soapy_source_c::get_devices() ) devices.push_back( device_t(dev) ); #endif +#ifdef ENABLE_PERSEUS + BOOST_FOREACH( std::string dev, perseus_source_c::get_devices() ) + devices.push_back( device_t(dev) ); +#endif /* software-only sources should be appended at the very end, * hopefully resulting in hardware sources to be shown first diff --git a/lib/perseus/CMakeLists.txt b/lib/perseus/CMakeLists.txt new file mode 100644 index 0000000..f45a10d --- /dev/null +++ b/lib/perseus/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright 2017 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio 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, or (at your option) +# any later version. +# +# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${LIBPERSEUS_INCLUDE_DIRS} +) + +set(perseus_srcs + ${CMAKE_CURRENT_SOURCE_DIR}/perseus_source_c.cc +) + +######################################################################## +# Append gnuradio-osmosdr library sources +######################################################################## +list(APPEND gr_osmosdr_srcs ${perseus_srcs}) +list(APPEND gr_osmosdr_libs ${LIBPERSEUS_LIBRARIES} ${GNURADIO_BLOCKS_LIBRARIES}) diff --git a/lib/perseus/perseus_source_c.cc b/lib/perseus/perseus_source_c.cc new file mode 100644 index 0000000..0720da4 --- /dev/null +++ b/lib/perseus/perseus_source_c.cc @@ -0,0 +1,507 @@ +/* -*- c++ -*- */ +/* + * Copyright 2018 Andrea Montefusco IW0HDV + * + * GNU Radio 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, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +/* + * config.h is generated by configure. It contains the results + * of probing for features, options etc. It should be the first + * file included in your .cc file. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "perseus_source_c.h" +#include "arg_helpers.h" + + + +using namespace boost::assign; + +#define PERSEUS_FORMAT_ERROR(ret, msg) \ + boost::str( boost::format(msg " (%1%)") % ret ) + +#define PERSEUS_THROW_ON_ERROR(ret, msg) \ + if ( ret != PERSEUS_NOERROR ) \ + { \ + throw std::runtime_error( PERSEUS_FORMAT_ERROR(ret, msg) ); \ + } + +#define PERSEUS_FUNC_STR(func, arg) \ + boost::str(boost::format(func "(%1%)") % arg) + " has failed" + +perseus_source_c_sptr make_perseus_source_c (const std::string & args) +{ + return gnuradio::get_initial_sptr(new perseus_source_c (args)); +} + +/* + * Specify constraints on number of input and output streams. + * This info is used to construct the input and output signatures + * (2nd & 3rd args to gr::block's constructor). The input and + * output signatures are used by the runtime system to + * check that a valid number and type of inputs and outputs + * are connected to this block. In this case, we accept + * only 0 input and 1 output. + */ +static const int MIN_IN = 0; // mininum number of input streams +static const int MAX_IN = 0; // maximum number of input streams +static const int MIN_OUT = 1; // minimum number of output streams +static const int MAX_OUT = 1; // maximum number of output streams + +/* + * The private constructor + */ +perseus_source_c::perseus_source_c (const std::string &args) + : gr::sync_block ("perseus_source_c", + gr::io_signature::make(MIN_IN, MAX_IN, sizeof (gr_complex)), + gr::io_signature::make(MIN_OUT, MAX_OUT, sizeof (gr_complex))), + _dev(NULL), + _sample_rate(0), + _center_freq(0), + _freq_corr(0), + _frun(false), + _cnt(0) +{ + //int ret; + + dict_t dict = params_to_dict(args); + + _dev = NULL; + + // Set debug info dumped to stderr to the maximum verbose level + perseus_set_debug(3); + + //fprintf (stderr, "Revision: %s\n", git_revision); + //fprintf (stderr, "SAMPLE RATE: %d\n", sr); + //fprintf (stderr, "NBUF: %d BUF SIZE: %d TOTAL BUFFER LENGTH: %d\n", nb, bs, nb*bs); + + // Check how many Perseus receivers are connected to the system + int num_perseus = perseus_init(); + std::cerr << "## Microtelecom Perseus ctor: " << num_perseus << " Perseus receivers found." << std::endl; + + if (num_perseus==0) { + perseus_exit(); + PERSEUS_THROW_ON_ERROR(PERSEUS_DEVNOTFOUND, "No Perseus receivers detected"); + } + // Open the first one... + if ((_dev = perseus_open(0)) == NULL) { + PERSEUS_THROW_ON_ERROR(PERSEUS_DEVNOTFOUND, "Unable to open Perseus device"); + } + // Download the standard firmware to the unit + std::cerr << "Downloading firmware..." << std::endl; + if (int rc = perseus_firmware_download(_dev,NULL)<0) { + PERSEUS_THROW_ON_ERROR (rc, "firmware download error"); + } + + // Dump some information about the receiver (S/N and HW rev) + int flag; + perseus_is_preserie(_dev, &flag); + if (flag != 0) + fprintf(stderr, "The device is a preserie unit"); + else { + eeprom_prodid prodid; + if (perseus_get_product_id(_dev,&prodid)<0) + fprintf(stderr, "get product id error: %s", perseus_errorstr()); + else + fprintf(stderr, "Receiver S/N: %05d-%02hX%02hX-%02hX%02hX-%02hX%02hX - HW Release:%hd.%hd\n", + (uint16_t) prodid.sn, + (uint16_t) prodid.signature[5], + (uint16_t) prodid.signature[4], + (uint16_t) prodid.signature[3], + (uint16_t) prodid.signature[2], + (uint16_t) prodid.signature[1], + (uint16_t) prodid.signature[0], + (uint16_t) prodid.hwrel, + (uint16_t) prodid.hwver); + } + // Printing all sampling rates available ..... + { + int buf[BUFSIZ]; + + if (perseus_get_sampling_rates (_dev, buf, sizeof(buf)/sizeof(buf[0])) < 0) { + fprintf(stderr, "get sampling rates error: %s\n", perseus_errorstr()); + } else { + int i = 0; + while (buf[i]) { + fprintf(stderr, "#%d: sample rate: %d\n", i, buf[i]); + + _sample_rates.push_back( std::pair( buf[i], i ) ); + i++; + } + /* since they may (and will) give us an unsorted array we have to sort it here + * to play nice with the monotonic requirement of meta-range later on */ + std::sort(_sample_rates.begin(), _sample_rates.end()); + + for (size_t i = 0; i < _sample_rates.size(); i++) + std::cerr << boost::format("%gM ") % (_sample_rates[i].first / 1e6); + + std::cerr << std::endl; + } + } + // Configure the receiver for 2 MS/s operations + fprintf(stderr, "Configuring FPGA...\n"); + if (int ret = perseus_set_sampling_rate(_dev, 96000) < 0) { // specify the sampling rate value in Samples/second + std::cerr << "fpga configuration error: " << perseus_errorstr() << std::endl; + PERSEUS_THROW_ON_ERROR(ret, "Perseus fpga configuration error"); + } + + perseus_set_attenuator_in_db(_dev, PERSEUS_ATT_0DB); + // Enable ADC Dither, Disable ADC Preamp + perseus_set_adc(_dev, true, false); + // set the NCO frequency in the middle of the range allowed + set_center_freq( (get_freq_range().start() + get_freq_range().stop()) / 2.0 ); + set_sample_rate( get_sample_rates().start() ); + + _fifo = new boost::circular_buffer(5000000); + if (!_fifo) { + throw std::runtime_error( std::string(__FUNCTION__) + " " + + "Failed to allocate a sample FIFO!" ); + } +} + +/* + * Our virtual destructor. + */ +perseus_source_c::~perseus_source_c () +{ + if (_dev) { + if (_frun) stop(); + int ret = perseus_close(_dev); + if ( ret != PERSEUS_NOERROR ) { + std::cerr << PERSEUS_FORMAT_ERROR(ret, "Failed to close Perseus") << std::endl; + } else + _dev = NULL; + } + perseus_exit(); + + if (_fifo) { + delete _fifo; + _fifo = NULL; + } +} + + +typedef union { + struct { + int32_t i; + int32_t q; + } __attribute__((__packed__)) iq; + struct { + uint8_t i1; + uint8_t i2; + uint8_t i3; + uint8_t i4; + uint8_t q1; + uint8_t q2; + uint8_t q3; + uint8_t q4; + } __attribute__((__packed__)) ; +} iq_sample; + + +int perseus_source_c::perseus_rx_callback(void *buf, int buf_size) +{ + size_t i, n_avail, to_copy, num_samples; + + uint8_t *samplebuf = (uint8_t*)buf; + + num_samples = buf_size/6; + + if (buf_size % 6) + std::cerr << "FATAL !!!!!!! not on boundary" << std::endl; + + iq_sample s; + + _fifo_lock.lock(); + + n_avail = _fifo->capacity() - _fifo->size(); + to_copy = (n_avail < num_samples ? n_avail : num_samples); + + for (i = 0; i < to_copy; i++ ) { + + s.i1 = s.q1 = 0; + s.i2 = *samplebuf++; + s.i3 = *samplebuf++; + s.i4 = *samplebuf++; + s.q2 = *samplebuf++; + s.q3 = *samplebuf++; + s.q4 = *samplebuf++; + + // convert 24 bit two's complements integers to float in [-1.0 - +1.0] range + + /* Push sample to the fifo */ + _fifo->push_back( gr_complex( (float)(s.iq.i) / (INT_MAX - 256), (float)(s.iq.q) / (INT_MAX - 256) ) ); + + } + // keep a counter of processed bytes + _cnt += buf_size; + + _fifo_lock.unlock(); + + /* We have made some new samples available to the consumer in work() */ + if (to_copy) { + //std::cerr << "+" << std::flush; + _samp_avail.notify_one(); + } + + /* Indicate overrun, if necessary */ + if (to_copy < num_samples) + std::cerr << "O" << std::flush; + + return 0; // TODO: return -1 on error/stop +} + +// static method used as callback from the libperseus-sdr library +int perseus_source_c::_perseus_rx_callback(void *buf, int buf_size, void *extra) +{ + perseus_source_c *obj = (perseus_source_c *)extra; + + return obj->perseus_rx_callback((float *)buf, buf_size); + + return 0; +} + + +bool perseus_source_c::start() +{ + int ret; + + if ( ! _dev ) + return false; + + std::cerr << "***** START COUNTER: " << _cnt + << " mod 6 " << (_cnt % 6) << std::endl; + + _frun = true; + + // in order to guarantee samples stream synchronization + // re-set the sample rate each time the stream is started + set_sample_rate( get_sample_rate() ); + + ret = perseus_start_async_input(_dev, + 6 * 1024 * 2 /* buffer size */, + perseus_source_c::_perseus_rx_callback, + (void *)this); + if (ret != PERSEUS_NOERROR) { + std::cerr << "Failed to start RX streaming (" + << ret << ")" << std::endl; + return false; + } else + return true; +} + +bool perseus_source_c::stop() +{ + if ( ! _dev ) + return false; + + _frun = false; + + std::cerr << "***** STOP COUNTER: " << _cnt + << " mod 6 " << (_cnt % 6) << std::endl; + + int ret = perseus_stop_async_input(_dev); + + if ( ret != PERSEUS_NOERROR ) { + std::cerr << "Failed to stop RX streaming (" << ret << ")" << std::endl; + return false; + } else + return true; +} + +int perseus_source_c::work( int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items ) +{ + gr_complex *out = (gr_complex *)output_items[0]; + + if ( _frun == false ) return WORK_DONE; + + boost::unique_lock lock(_fifo_lock); + + /* Wait until we have the requested number of samples */ + int n_samples_avail = _fifo->size(); + + while (n_samples_avail < noutput_items) { + _samp_avail.wait(lock); + n_samples_avail = _fifo->size(); + } + + for(int i = 0; i < noutput_items; ++i) { + out[i] = _fifo->at(0); + _fifo->pop_front(); + } + + return noutput_items; +} + +std::vector perseus_source_c::get_devices() +{ + std::vector devices; + std::string label; + + std::string args = "perseus=0,label='Perseus'"; + devices.push_back( args ); + + return devices; +} + +size_t perseus_source_c::get_num_channels() +{ + return 1; +} + +osmosdr::meta_range_t perseus_source_c::get_sample_rates() +{ + osmosdr::meta_range_t range; + + for (size_t i = 0; i < _sample_rates.size(); i++) { + range += osmosdr::range_t( _sample_rates[i].first ); + std::cerr << "SR: " << i << " : (" + << _sample_rates[i].first << ")" << std::endl; + } + return range; +} + +double perseus_source_c::set_sample_rate( double rate ) +{ + int ret; + if ((ret = perseus_set_sampling_rate(_dev, rate)) < 0) { + PERSEUS_THROW_ON_ERROR(ret, "Unable to set sample rate."); + } else + _sample_rate = rate; + + return get_sample_rate(); +} + +double perseus_source_c::get_sample_rate() +{ + return _sample_rate; +} + +osmosdr::freq_range_t perseus_source_c::get_freq_range( size_t chan ) +{ + osmosdr::freq_range_t range; + + range += osmosdr::range_t( PERSEUS_DDC_FREQ_MIN, PERSEUS_DDC_FREQ_MAX ); + + return range; +} + +double perseus_source_c::set_center_freq( double freq, size_t chan ) +{ + int ret; + + if (_dev) { + ret = + perseus_set_ddc_center_freq(_dev, + (uint64_t)(freq * (1.0 + (_freq_corr * 10e-6))), + 1); + if ( ret == PERSEUS_NOERROR ) { + _center_freq = freq; + } else { + PERSEUS_THROW_ON_ERROR( ret, PERSEUS_FUNC_STR( "perseus_set_ddc_center_freq", freq ) ) + } + } + + return get_center_freq( chan ); +} + +double perseus_source_c::get_center_freq( size_t chan ) +{ + return _center_freq; +} + +double perseus_source_c::set_freq_corr( double ppm, size_t chan ) +{ + return _freq_corr = ppm; +} + +double perseus_source_c::get_freq_corr( size_t chan ) +{ + return _freq_corr; +} + +std::vector perseus_source_c::get_gain_names( size_t chan ) +{ + return {}; +} + +osmosdr::gain_range_t perseus_source_c::get_gain_range( size_t chan ) +{ + return osmosdr::gain_range_t(); +} + +osmosdr::gain_range_t perseus_source_c::get_gain_range( const std::string & name, size_t chan ) +{ + return osmosdr::gain_range_t(); +} + + +double perseus_source_c::set_gain( double gain, size_t chan ) +{ + return gain; +} + +double perseus_source_c::set_gain( double gain, const std::string & name, size_t chan) +{ + return gain; +} + +double perseus_source_c::get_gain( size_t chan ) +{ + return 0.0; +} + +double perseus_source_c::get_gain( const std::string & name, size_t chan ) +{ + return 0.0; +} + +std::vector< std::string > perseus_source_c::get_antennas( size_t chan ) +{ + std::vector< std::string > antennas; + + antennas += get_antenna( chan ); + + return antennas; +} + +std::string perseus_source_c::set_antenna( const std::string & antenna, size_t chan ) +{ + return get_antenna( chan ); +} + +std::string perseus_source_c::get_antenna( size_t chan ) +{ + return "RX"; +} diff --git a/lib/perseus/perseus_source_c.h b/lib/perseus/perseus_source_c.h new file mode 100644 index 0000000..9eda23d --- /dev/null +++ b/lib/perseus/perseus_source_c.h @@ -0,0 +1,120 @@ +/* -*- c++ -*- */ +/* + * Copyright 2013 Dimitri Stolnikov + * + * This file is part of GNU Radio + * + * GNU Radio 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, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ +#ifndef INCLUDED_PERSEUS_SOURCE_C_H +#define INCLUDED_PERSEUS_SOURCE_C_H + +#include +#include +#include + +#include + +#include "source_iface.h" +#include + +class perseus_source_c; + +typedef boost::shared_ptr perseus_source_c_sptr; + +/*! + * \brief Return a shared_ptr to a new instance of perseus_source_c. + * + * To avoid accidental use of raw pointers, perseus_source_c's + * constructor is private. make_perseus_source_c is the public + * interface for creating new instances. + */ +perseus_source_c_sptr make_perseus_source_c (const std::string & args = ""); + +/*! + * \brief Provides a stream of complex samples. + * \ingroup block + */ +class perseus_source_c : + public gr::sync_block, + public source_iface +{ +private: + // The friend declaration allows make_perseus_source_c to + // access the private constructor. + + friend perseus_source_c_sptr make_perseus_source_c (const std::string & args); + + perseus_source_c (const std::string & args); + +public: + ~perseus_source_c (); + + bool start(); + bool stop(); + + int work( int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items ); + + static std::vector< std::string > get_devices(); + + size_t get_num_channels( void ); + + osmosdr::meta_range_t get_sample_rates( void ); + double set_sample_rate( double rate ); + double get_sample_rate( void ); + + osmosdr::freq_range_t get_freq_range( size_t chan = 0 ); + double set_center_freq( double freq, size_t chan = 0 ); + double get_center_freq( size_t chan = 0 ); + double set_freq_corr( double ppm, size_t chan = 0 ); + double get_freq_corr( size_t chan = 0 ); + + std::vector get_gain_names( size_t chan = 0 ); + osmosdr::gain_range_t get_gain_range( size_t chan = 0 ); + osmosdr::gain_range_t get_gain_range( const std::string & name, size_t chan = 0 ); + double set_gain( double gain, size_t chan = 0 ); + double set_gain( double gain, const std::string & name, size_t chan = 0 ); + double get_gain( size_t chan = 0 ); + double get_gain( const std::string & name, size_t chan = 0 ); + + std::vector< std::string > get_antennas( size_t chan = 0 ); + std::string set_antenna( const std::string & antenna, size_t chan = 0 ); + std::string get_antenna( size_t chan = 0 ); + + +private: + static int _perseus_rx_callback(void *buf, int buf_size, void *extra); + + int perseus_rx_callback(void *samples, int sample_count); + + perseus_descr *_dev; + + boost::circular_buffer *_fifo; + boost::mutex _fifo_lock; + boost::condition_variable _samp_avail; + + std::vector< std::pair > _sample_rates; + double _sample_rate; + double _center_freq; + double _freq_corr; + + bool _frun; + int _cnt; +}; + +#endif /* INCLUDED_PERSEUS_SOURCE_C_H */ diff --git a/lib/rtl/rtl_source_c.cc b/lib/rtl/rtl_source_c.cc index a371464..eb7042e 100644 --- a/lib/rtl/rtl_source_c.cc +++ b/lib/rtl/rtl_source_c.cc @@ -221,7 +221,7 @@ rtl_source_c::rtl_source_c (const std::string &args) throw std::runtime_error("Failed to enable offset tuning."); } - ret = rtlsdr_set_bias_tee(_dev, bias_tee); +// ret = rtlsdr_set_bias_tee(_dev, bias_tee); if (ret < 0) throw std::runtime_error("Failed to set bias tee."); diff --git a/lib/source_impl.cc b/lib/source_impl.cc index a28f314..ead4ef7 100644 --- a/lib/source_impl.cc +++ b/lib/source_impl.cc @@ -92,6 +92,9 @@ #include #endif +#ifdef ENABLE_PERSEUS +#include +#endif #include "arg_helpers.h" #include "source_impl.h" @@ -171,6 +174,9 @@ source_impl::source_impl( const std::string &args ) #ifdef ENABLE_FREESRP dev_types.push_back("freesrp"); #endif +#ifdef ENABLE_PERSEUS + dev_types.push_back("perseus"); +#endif std::cerr << "gr-osmosdr " << GR_OSMOSDR_VERSION << " (" << GR_OSMOSDR_LIBVER << ") " << "gnuradio " << gr::version() << std::endl; @@ -252,7 +258,10 @@ source_impl::source_impl( const std::string &args ) BOOST_FOREACH( std::string dev, freesrp_source_c::get_devices() ) dev_list.push_back( dev ); #endif - +#ifdef ENABLE_PERSEUS + BOOST_FOREACH( std::string dev, perseus_source_c::get_devices() ) + dev_list.push_back( dev ); +#endif // std::cerr << std::endl; // BOOST_FOREACH( std::string dev, dev_list ) // std::cerr << "'" << dev << "'" << std::endl; @@ -383,6 +392,13 @@ source_impl::source_impl( const std::string &args ) } #endif +#ifdef ENABLE_PERSEUS + if ( dict.count("perseus") ) { + perseus_source_c_sptr src = make_perseus_source_c( arg ); + block = src; iface = src.get(); + } +#endif + if ( iface != NULL && long(block.get()) != 0 ) { _devs.push_back( iface ); -- 1.9.1 >From 1b6cf50a7a6074754717b0270757931c379561bc Mon Sep 17 00:00:00 2001 From: Andrea Montefusco IW0HDV Date: Thu, 1 Feb 2018 17:33:39 +0100 Subject: More hardware controls added (attenuator, ADC preamp and dither, preselector) --- lib/perseus/perseus_source_c.cc | 136 ++++++++++++++++++++++++++++++++++++---- lib/perseus/perseus_source_c.h | 19 +++++- 2 files changed, 142 insertions(+), 13 deletions(-) diff --git a/lib/perseus/perseus_source_c.cc b/lib/perseus/perseus_source_c.cc index 0720da4..89674b1 100644 --- a/lib/perseus/perseus_source_c.cc +++ b/lib/perseus/perseus_source_c.cc @@ -89,7 +89,11 @@ perseus_source_c::perseus_source_c (const std::string &args) _center_freq(0), _freq_corr(0), _frun(false), - _cnt(0) + _cnt(0), + _att_value(0), + _adc_preamp(false), + _adc_dither(false), + _preselector(false) { //int ret; @@ -174,9 +178,9 @@ perseus_source_c::perseus_source_c (const std::string &args) PERSEUS_THROW_ON_ERROR(ret, "Perseus fpga configuration error"); } - perseus_set_attenuator_in_db(_dev, PERSEUS_ATT_0DB); + perseus_set_attenuator_in_db(_dev, _att_value); // Enable ADC Dither, Disable ADC Preamp - perseus_set_adc(_dev, true, false); + perseus_set_adc(_dev, _adc_dither == true ? 1:0, _adc_preamp == true ? 1:0); // set the NCO frequency in the middle of the range allowed set_center_freq( (get_freq_range().start() + get_freq_range().stop()) / 2.0 ); set_sample_rate( get_sample_rates().start() ); @@ -425,7 +429,7 @@ double perseus_source_c::set_center_freq( double freq, size_t chan ) ret = perseus_set_ddc_center_freq(_dev, (uint64_t)(freq * (1.0 + (_freq_corr * 10e-6))), - 1); + _preselector == true ? 1:0); if ( ret == PERSEUS_NOERROR ) { _center_freq = freq; } else { @@ -453,55 +457,165 @@ double perseus_source_c::get_freq_corr( size_t chan ) std::vector perseus_source_c::get_gain_names( size_t chan ) { - return {}; + std::vector< std::string > names; + + names += "Attenuator"; + names += "ADC preamp"; + names += "ADC dither"; + + return names; } +// +// FIXME +// osmosdr::gain_range_t perseus_source_c::get_gain_range( size_t chan ) { - return osmosdr::gain_range_t(); + return get_gain_range( "Attenuator", chan ); } osmosdr::gain_range_t perseus_source_c::get_gain_range( const std::string & name, size_t chan ) { + if ( "Attenuator" == name ) { + return osmosdr::gain_range_t( -30, 0, 10 ); + } + if ( "ADC preamp" == name ) { + return osmosdr::gain_range_t( 0, 6, 6 ); + } + if ( "ADC dither" == name ) { + return osmosdr::gain_range_t( 0, 1, 1 ); + } + return osmosdr::gain_range_t(); } -double perseus_source_c::set_gain( double gain, size_t chan ) +double perseus_source_c::set_attenuator( double gain, size_t chan ) { + int ret; + + gain = - gain; + if (_dev && _att_value != gain) { + ret = perseus_set_attenuator_in_db (_dev, gain); + + if ( PERSEUS_NOERROR == ret ) { + _att_value = gain; + } else { + std::cerr << "ERROR: perseus_set_attenuator_in_db: " << gain << std::endl; + } + } + + return _att_value; +} + +double perseus_source_c::set_adc_preamp( double gain, size_t chan ) +{ + std::cerr << "set_adc_preamp: " << gain << std::endl; + + if (_dev) { + _adc_preamp = (gain != 0. ? true : false); + int ret = + perseus_set_adc(_dev, _adc_dither == true ? 1:0, _adc_preamp == true ? 1:0); + if ( PERSEUS_NOERROR != ret ) { + std::cerr << "ERROR: perseus_set_adc" << ret << std::endl; + } + } return gain; } -double perseus_source_c::set_gain( double gain, const std::string & name, size_t chan) +double perseus_source_c::set_adc_dither( double gain, size_t chan ) { + std::cerr << "set_adc_dither: " << gain << std::endl; + + if (_dev) { + _adc_dither = (gain != 0. ? true : false); + int ret = + perseus_set_adc(_dev, _adc_dither == true ? 1:0, _adc_preamp == true ? 1:0); + if ( PERSEUS_NOERROR != ret ) { + std::cerr << "ERROR: perseus_set_adc" << ret << std::endl; + } + } return gain; } -double perseus_source_c::get_gain( size_t chan ) +double perseus_source_c::get_attenuator( size_t chan ) { - return 0.0; + return _att_value; } +double perseus_source_c::get_adc_preamp(size_t chan ) +{ + double v = _adc_preamp == true ? 6. : 0.; + std::cerr << "get_adc_preamp: " << _adc_preamp << " value" << v << std::endl; + return v; +} + +double perseus_source_c::get_adc_dither(size_t chan ) +{ + double v = _adc_dither == true ? 1. : 0.; + std::cerr << "get_adc_dither: " << _adc_dither << " value" << v << std::endl; + return v; +} + + +double perseus_source_c::set_gain( double gain, const std::string & name, size_t chan) +{ + if ( "Attenuator" == name ) { + return set_attenuator( gain, chan ); + } + if ( "ADC preamp" == name ) { + return set_adc_preamp( gain, chan ); + } + if ( "ADC dither" == name ) { + return set_adc_dither( gain, chan ); + } + return gain; +} + + double perseus_source_c::get_gain( const std::string & name, size_t chan ) { + if ( "Attenuator" == name ) { + return get_attenuator( chan ); + } + if ( "ADC preamp" == name ) { + return get_adc_preamp( chan ); + } + if ( "ADC dither" == name ) { + return get_adc_dither( chan ); + } return 0.0; } + + std::vector< std::string > perseus_source_c::get_antennas( size_t chan ) { std::vector< std::string > antennas; - antennas += get_antenna( chan ); + antennas += "RX"; + antennas += "RX+PRESELECTOR"; return antennas; } std::string perseus_source_c::set_antenna( const std::string & antenna, size_t chan ) { + if ( antenna == "RX" ) { + _preselector = false; + set_center_freq( get_center_freq() ); + return "RX"; + } + if ( antenna == "RX+PRESELECTOR" ) { + _preselector = true; + set_center_freq( get_center_freq() ); + return "RX+PRESELECTOR"; + } return get_antenna( chan ); } std::string perseus_source_c::get_antenna( size_t chan ) { + std::cerr << "perseus_source_c::get_antenna: " << chan << std::endl; return "RX"; } diff --git a/lib/perseus/perseus_source_c.h b/lib/perseus/perseus_source_c.h index 9eda23d..934ff2b 100644 --- a/lib/perseus/perseus_source_c.h +++ b/lib/perseus/perseus_source_c.h @@ -87,9 +87,20 @@ public: std::vector get_gain_names( size_t chan = 0 ); osmosdr::gain_range_t get_gain_range( size_t chan = 0 ); osmosdr::gain_range_t get_gain_range( const std::string & name, size_t chan = 0 ); - double set_gain( double gain, size_t chan = 0 ); + + double set_gain( double gain, size_t chan = 0 ) {return gain; } + double get_gain( size_t chan = 0 ) {return _att_value; } + + double set_attenuator( double gain, size_t chan = 0 ); + double get_attenuator( size_t chan = 0 ); + + double set_adc_preamp( double gain, size_t chan = 0 ); + double get_adc_preamp( size_t chan = 0 ); + + double set_adc_dither( double gain, size_t chan = 0 ); + double get_adc_dither( size_t chan = 0 ); + double set_gain( double gain, const std::string & name, size_t chan = 0 ); - double get_gain( size_t chan = 0 ); double get_gain( const std::string & name, size_t chan = 0 ); std::vector< std::string > get_antennas( size_t chan = 0 ); @@ -115,6 +126,10 @@ private: bool _frun; int _cnt; + double _att_value; + bool _adc_preamp; + bool _adc_dither; + bool _preselector; }; #endif /* INCLUDED_PERSEUS_SOURCE_C_H */ -- 1.9.1 . QUIT From andrea.montefusco at gmail.com Fri Feb 2 19:25:53 2018 From: andrea.montefusco at gmail.com (andrea.montefusco at gmail.com) Date: Fri, 02 Feb 2018 20:25:53 +0100 Subject: [PATCH] gr-osmosdr Microtelecom Perseus HF receiver support Message-ID: <5a74bb41.lNO/DtxYbZSsAO/d%andrea.montefusco@gmail.com> Hi maintainer and all, Below you find a patch that implements Microtelecom Perseus support . I have testes it using gqrx SDR software. --- CMakeLists.txt | 1 + cmake/Modules/FindLibPERSEUS.cmake | 24 ++ lib/CMakeLists.txt | 8 + lib/airspy/airspy_source_c.cc | 5 +- lib/config.h.in | 1 + lib/device.cc | 8 + lib/perseus/CMakeLists.txt | 37 +++ lib/perseus/perseus_source_c.cc | 507 +++++++++++++++++++++++++++++++++++++ lib/perseus/perseus_source_c.h | 120 +++++++++ lib/rtl/rtl_source_c.cc | 2 +- lib/source_impl.cc | 18 +- 11 files changed, 727 insertions(+), 4 deletions(-) create mode 100644 cmake/Modules/FindLibPERSEUS.cmake create mode 100644 lib/perseus/CMakeLists.txt create mode 100644 lib/perseus/perseus_source_c.cc create mode 100644 lib/perseus/perseus_source_c.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 296456d..94acd4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,6 +173,7 @@ find_package(Volk) find_package(LibbladeRF) find_package(SoapySDR NO_MODULE) find_package(LibFreeSRP) +find_package(LibPERSEUS) find_package(Doxygen) if(NOT GNURADIO_RUNTIME_FOUND) diff --git a/cmake/Modules/FindLibPERSEUS.cmake b/cmake/Modules/FindLibPERSEUS.cmake new file mode 100644 index 0000000..5477327 --- /dev/null +++ b/cmake/Modules/FindLibPERSEUS.cmake @@ -0,0 +1,24 @@ +INCLUDE(FindPkgConfig) +PKG_CHECK_MODULES(PC_LIBPERSEUS libperseus-sdr) + +FIND_PATH( + LIBPERSEUS_INCLUDE_DIRS + NAMES perseus-sdr.h + HINTS $ENV{LIBPERSEUS_DIR}/include + ${PC_LIBPERSEUS_INCLUDEDIR} + PATHS /usr/local/include + /usr/include +) + +FIND_LIBRARY( + LIBPERSEUS_LIBRARIES + NAMES perseus-sdr + HINTS $ENV{LIBPERSEUS_DIR}/lib + ${PC_LIBPERSEUS_LIBDIR} + PATHS /usr/local/lib + /usr/lib +) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBPERSEUS DEFAULT_MSG LIBPERSEUS_LIBRARIES LIBPERSEUS_INCLUDE_DIRS) +MARK_AS_ADVANCED(LIBPERSEUS_LIBRARIES LIBPERSEUS_INCLUDE_DIRS) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index c05b8d9..f555816 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -249,6 +249,14 @@ GR_INCLUDE_SUBDIRECTORY(freesrp) endif(ENABLE_FREESRP) ######################################################################## +# Setup PERSEUS component +######################################################################## +GR_REGISTER_COMPONENT("Microtelecom Perseus Receiver" ENABLE_PERSEUS LIBPERSEUS_FOUND) +if(ENABLE_PERSEUS) +GR_INCLUDE_SUBDIRECTORY(perseus) +endif(ENABLE_PERSEUS) + +######################################################################## # Setup configuration file ######################################################################## ADD_DEFINITIONS(-DHAVE_CONFIG_H=1) diff --git a/lib/airspy/airspy_source_c.cc b/lib/airspy/airspy_source_c.cc index 50150e5..02fc6ab 100644 --- a/lib/airspy/airspy_source_c.cc +++ b/lib/airspy/airspy_source_c.cc @@ -350,9 +350,10 @@ osmosdr::meta_range_t airspy_source_c::get_sample_rates() { osmosdr::meta_range_t range; - for (size_t i = 0; i < _sample_rates.size(); i++) + for (size_t i = 0; i < _sample_rates.size(); i++) { + std::cerr << "SR: [" << i << "]: " << _sample_rates[i].first << std::endl; range += osmosdr::range_t( _sample_rates[i].first ); - + } return range; } diff --git a/lib/config.h.in b/lib/config.h.in index 42e72f1..dac7e39 100644 --- a/lib/config.h.in +++ b/lib/config.h.in @@ -19,6 +19,7 @@ #cmakedefine ENABLE_SOAPY #cmakedefine ENABLE_REDPITAYA #cmakedefine ENABLE_FREESRP +#cmakedefine ENABLE_PERSEUS //provide NAN define for MSVC older than VC12 #if defined(_MSC_VER) && (_MSC_VER < 1800) diff --git a/lib/device.cc b/lib/device.cc index 025a22b..df0d734 100644 --- a/lib/device.cc +++ b/lib/device.cc @@ -90,6 +90,10 @@ #include #endif +#ifdef ENABLE_PERSEUS +#include +#endif + #include "arg_helpers.h" using namespace osmosdr; @@ -194,6 +198,10 @@ devices_t device::find(const device_t &hint) BOOST_FOREACH( std::string dev, soapy_source_c::get_devices() ) devices.push_back( device_t(dev) ); #endif +#ifdef ENABLE_PERSEUS + BOOST_FOREACH( std::string dev, perseus_source_c::get_devices() ) + devices.push_back( device_t(dev) ); +#endif /* software-only sources should be appended at the very end, * hopefully resulting in hardware sources to be shown first diff --git a/lib/perseus/CMakeLists.txt b/lib/perseus/CMakeLists.txt new file mode 100644 index 0000000..f45a10d --- /dev/null +++ b/lib/perseus/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright 2017 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio 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, or (at your option) +# any later version. +# +# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${LIBPERSEUS_INCLUDE_DIRS} +) + +set(perseus_srcs + ${CMAKE_CURRENT_SOURCE_DIR}/perseus_source_c.cc +) + +######################################################################## +# Append gnuradio-osmosdr library sources +######################################################################## +list(APPEND gr_osmosdr_srcs ${perseus_srcs}) +list(APPEND gr_osmosdr_libs ${LIBPERSEUS_LIBRARIES} ${GNURADIO_BLOCKS_LIBRARIES}) diff --git a/lib/perseus/perseus_source_c.cc b/lib/perseus/perseus_source_c.cc new file mode 100644 index 0000000..0720da4 --- /dev/null +++ b/lib/perseus/perseus_source_c.cc @@ -0,0 +1,507 @@ +/* -*- c++ -*- */ +/* + * Copyright 2018 Andrea Montefusco IW0HDV + * + * GNU Radio 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, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +/* + * config.h is generated by configure. It contains the results + * of probing for features, options etc. It should be the first + * file included in your .cc file. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "perseus_source_c.h" +#include "arg_helpers.h" + + + +using namespace boost::assign; + +#define PERSEUS_FORMAT_ERROR(ret, msg) \ + boost::str( boost::format(msg " (%1%)") % ret ) + +#define PERSEUS_THROW_ON_ERROR(ret, msg) \ + if ( ret != PERSEUS_NOERROR ) \ + { \ + throw std::runtime_error( PERSEUS_FORMAT_ERROR(ret, msg) ); \ + } + +#define PERSEUS_FUNC_STR(func, arg) \ + boost::str(boost::format(func "(%1%)") % arg) + " has failed" + +perseus_source_c_sptr make_perseus_source_c (const std::string & args) +{ + return gnuradio::get_initial_sptr(new perseus_source_c (args)); +} + +/* + * Specify constraints on number of input and output streams. + * This info is used to construct the input and output signatures + * (2nd & 3rd args to gr::block's constructor). The input and + * output signatures are used by the runtime system to + * check that a valid number and type of inputs and outputs + * are connected to this block. In this case, we accept + * only 0 input and 1 output. + */ +static const int MIN_IN = 0; // mininum number of input streams +static const int MAX_IN = 0; // maximum number of input streams +static const int MIN_OUT = 1; // minimum number of output streams +static const int MAX_OUT = 1; // maximum number of output streams + +/* + * The private constructor + */ +perseus_source_c::perseus_source_c (const std::string &args) + : gr::sync_block ("perseus_source_c", + gr::io_signature::make(MIN_IN, MAX_IN, sizeof (gr_complex)), + gr::io_signature::make(MIN_OUT, MAX_OUT, sizeof (gr_complex))), + _dev(NULL), + _sample_rate(0), + _center_freq(0), + _freq_corr(0), + _frun(false), + _cnt(0) +{ + //int ret; + + dict_t dict = params_to_dict(args); + + _dev = NULL; + + // Set debug info dumped to stderr to the maximum verbose level + perseus_set_debug(3); + + //fprintf (stderr, "Revision: %s\n", git_revision); + //fprintf (stderr, "SAMPLE RATE: %d\n", sr); + //fprintf (stderr, "NBUF: %d BUF SIZE: %d TOTAL BUFFER LENGTH: %d\n", nb, bs, nb*bs); + + // Check how many Perseus receivers are connected to the system + int num_perseus = perseus_init(); + std::cerr << "## Microtelecom Perseus ctor: " << num_perseus << " Perseus receivers found." << std::endl; + + if (num_perseus==0) { + perseus_exit(); + PERSEUS_THROW_ON_ERROR(PERSEUS_DEVNOTFOUND, "No Perseus receivers detected"); + } + // Open the first one... + if ((_dev = perseus_open(0)) == NULL) { + PERSEUS_THROW_ON_ERROR(PERSEUS_DEVNOTFOUND, "Unable to open Perseus device"); + } + // Download the standard firmware to the unit + std::cerr << "Downloading firmware..." << std::endl; + if (int rc = perseus_firmware_download(_dev,NULL)<0) { + PERSEUS_THROW_ON_ERROR (rc, "firmware download error"); + } + + // Dump some information about the receiver (S/N and HW rev) + int flag; + perseus_is_preserie(_dev, &flag); + if (flag != 0) + fprintf(stderr, "The device is a preserie unit"); + else { + eeprom_prodid prodid; + if (perseus_get_product_id(_dev,&prodid)<0) + fprintf(stderr, "get product id error: %s", perseus_errorstr()); + else + fprintf(stderr, "Receiver S/N: %05d-%02hX%02hX-%02hX%02hX-%02hX%02hX - HW Release:%hd.%hd\n", + (uint16_t) prodid.sn, + (uint16_t) prodid.signature[5], + (uint16_t) prodid.signature[4], + (uint16_t) prodid.signature[3], + (uint16_t) prodid.signature[2], + (uint16_t) prodid.signature[1], + (uint16_t) prodid.signature[0], + (uint16_t) prodid.hwrel, + (uint16_t) prodid.hwver); + } + // Printing all sampling rates available ..... + { + int buf[BUFSIZ]; + + if (perseus_get_sampling_rates (_dev, buf, sizeof(buf)/sizeof(buf[0])) < 0) { + fprintf(stderr, "get sampling rates error: %s\n", perseus_errorstr()); + } else { + int i = 0; + while (buf[i]) { + fprintf(stderr, "#%d: sample rate: %d\n", i, buf[i]); + + _sample_rates.push_back( std::pair( buf[i], i ) ); + i++; + } + /* since they may (and will) give us an unsorted array we have to sort it here + * to play nice with the monotonic requirement of meta-range later on */ + std::sort(_sample_rates.begin(), _sample_rates.end()); + + for (size_t i = 0; i < _sample_rates.size(); i++) + std::cerr << boost::format("%gM ") % (_sample_rates[i].first / 1e6); + + std::cerr << std::endl; + } + } + // Configure the receiver for 2 MS/s operations + fprintf(stderr, "Configuring FPGA...\n"); + if (int ret = perseus_set_sampling_rate(_dev, 96000) < 0) { // specify the sampling rate value in Samples/second + std::cerr << "fpga configuration error: " << perseus_errorstr() << std::endl; + PERSEUS_THROW_ON_ERROR(ret, "Perseus fpga configuration error"); + } + + perseus_set_attenuator_in_db(_dev, PERSEUS_ATT_0DB); + // Enable ADC Dither, Disable ADC Preamp + perseus_set_adc(_dev, true, false); + // set the NCO frequency in the middle of the range allowed + set_center_freq( (get_freq_range().start() + get_freq_range().stop()) / 2.0 ); + set_sample_rate( get_sample_rates().start() ); + + _fifo = new boost::circular_buffer(5000000); + if (!_fifo) { + throw std::runtime_error( std::string(__FUNCTION__) + " " + + "Failed to allocate a sample FIFO!" ); + } +} + +/* + * Our virtual destructor. + */ +perseus_source_c::~perseus_source_c () +{ + if (_dev) { + if (_frun) stop(); + int ret = perseus_close(_dev); + if ( ret != PERSEUS_NOERROR ) { + std::cerr << PERSEUS_FORMAT_ERROR(ret, "Failed to close Perseus") << std::endl; + } else + _dev = NULL; + } + perseus_exit(); + + if (_fifo) { + delete _fifo; + _fifo = NULL; + } +} + + +typedef union { + struct { + int32_t i; + int32_t q; + } __attribute__((__packed__)) iq; + struct { + uint8_t i1; + uint8_t i2; + uint8_t i3; + uint8_t i4; + uint8_t q1; + uint8_t q2; + uint8_t q3; + uint8_t q4; + } __attribute__((__packed__)) ; +} iq_sample; + + +int perseus_source_c::perseus_rx_callback(void *buf, int buf_size) +{ + size_t i, n_avail, to_copy, num_samples; + + uint8_t *samplebuf = (uint8_t*)buf; + + num_samples = buf_size/6; + + if (buf_size % 6) + std::cerr << "FATAL !!!!!!! not on boundary" << std::endl; + + iq_sample s; + + _fifo_lock.lock(); + + n_avail = _fifo->capacity() - _fifo->size(); + to_copy = (n_avail < num_samples ? n_avail : num_samples); + + for (i = 0; i < to_copy; i++ ) { + + s.i1 = s.q1 = 0; + s.i2 = *samplebuf++; + s.i3 = *samplebuf++; + s.i4 = *samplebuf++; + s.q2 = *samplebuf++; + s.q3 = *samplebuf++; + s.q4 = *samplebuf++; + + // convert 24 bit two's complements integers to float in [-1.0 - +1.0] range + + /* Push sample to the fifo */ + _fifo->push_back( gr_complex( (float)(s.iq.i) / (INT_MAX - 256), (float)(s.iq.q) / (INT_MAX - 256) ) ); + + } + // keep a counter of processed bytes + _cnt += buf_size; + + _fifo_lock.unlock(); + + /* We have made some new samples available to the consumer in work() */ + if (to_copy) { + //std::cerr << "+" << std::flush; + _samp_avail.notify_one(); + } + + /* Indicate overrun, if necessary */ + if (to_copy < num_samples) + std::cerr << "O" << std::flush; + + return 0; // TODO: return -1 on error/stop +} + +// static method used as callback from the libperseus-sdr library +int perseus_source_c::_perseus_rx_callback(void *buf, int buf_size, void *extra) +{ + perseus_source_c *obj = (perseus_source_c *)extra; + + return obj->perseus_rx_callback((float *)buf, buf_size); + + return 0; +} + + +bool perseus_source_c::start() +{ + int ret; + + if ( ! _dev ) + return false; + + std::cerr << "***** START COUNTER: " << _cnt + << " mod 6 " << (_cnt % 6) << std::endl; + + _frun = true; + + // in order to guarantee samples stream synchronization + // re-set the sample rate each time the stream is started + set_sample_rate( get_sample_rate() ); + + ret = perseus_start_async_input(_dev, + 6 * 1024 * 2 /* buffer size */, + perseus_source_c::_perseus_rx_callback, + (void *)this); + if (ret != PERSEUS_NOERROR) { + std::cerr << "Failed to start RX streaming (" + << ret << ")" << std::endl; + return false; + } else + return true; +} + +bool perseus_source_c::stop() +{ + if ( ! _dev ) + return false; + + _frun = false; + + std::cerr << "***** STOP COUNTER: " << _cnt + << " mod 6 " << (_cnt % 6) << std::endl; + + int ret = perseus_stop_async_input(_dev); + + if ( ret != PERSEUS_NOERROR ) { + std::cerr << "Failed to stop RX streaming (" << ret << ")" << std::endl; + return false; + } else + return true; +} + +int perseus_source_c::work( int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items ) +{ + gr_complex *out = (gr_complex *)output_items[0]; + + if ( _frun == false ) return WORK_DONE; + + boost::unique_lock lock(_fifo_lock); + + /* Wait until we have the requested number of samples */ + int n_samples_avail = _fifo->size(); + + while (n_samples_avail < noutput_items) { + _samp_avail.wait(lock); + n_samples_avail = _fifo->size(); + } + + for(int i = 0; i < noutput_items; ++i) { + out[i] = _fifo->at(0); + _fifo->pop_front(); + } + + return noutput_items; +} + +std::vector perseus_source_c::get_devices() +{ + std::vector devices; + std::string label; + + std::string args = "perseus=0,label='Perseus'"; + devices.push_back( args ); + + return devices; +} + +size_t perseus_source_c::get_num_channels() +{ + return 1; +} + +osmosdr::meta_range_t perseus_source_c::get_sample_rates() +{ + osmosdr::meta_range_t range; + + for (size_t i = 0; i < _sample_rates.size(); i++) { + range += osmosdr::range_t( _sample_rates[i].first ); + std::cerr << "SR: " << i << " : (" + << _sample_rates[i].first << ")" << std::endl; + } + return range; +} + +double perseus_source_c::set_sample_rate( double rate ) +{ + int ret; + if ((ret = perseus_set_sampling_rate(_dev, rate)) < 0) { + PERSEUS_THROW_ON_ERROR(ret, "Unable to set sample rate."); + } else + _sample_rate = rate; + + return get_sample_rate(); +} + +double perseus_source_c::get_sample_rate() +{ + return _sample_rate; +} + +osmosdr::freq_range_t perseus_source_c::get_freq_range( size_t chan ) +{ + osmosdr::freq_range_t range; + + range += osmosdr::range_t( PERSEUS_DDC_FREQ_MIN, PERSEUS_DDC_FREQ_MAX ); + + return range; +} + +double perseus_source_c::set_center_freq( double freq, size_t chan ) +{ + int ret; + + if (_dev) { + ret = + perseus_set_ddc_center_freq(_dev, + (uint64_t)(freq * (1.0 + (_freq_corr * 10e-6))), + 1); + if ( ret == PERSEUS_NOERROR ) { + _center_freq = freq; + } else { + PERSEUS_THROW_ON_ERROR( ret, PERSEUS_FUNC_STR( "perseus_set_ddc_center_freq", freq ) ) + } + } + + return get_center_freq( chan ); +} + +double perseus_source_c::get_center_freq( size_t chan ) +{ + return _center_freq; +} + +double perseus_source_c::set_freq_corr( double ppm, size_t chan ) +{ + return _freq_corr = ppm; +} + +double perseus_source_c::get_freq_corr( size_t chan ) +{ + return _freq_corr; +} + +std::vector perseus_source_c::get_gain_names( size_t chan ) +{ + return {}; +} + +osmosdr::gain_range_t perseus_source_c::get_gain_range( size_t chan ) +{ + return osmosdr::gain_range_t(); +} + +osmosdr::gain_range_t perseus_source_c::get_gain_range( const std::string & name, size_t chan ) +{ + return osmosdr::gain_range_t(); +} + + +double perseus_source_c::set_gain( double gain, size_t chan ) +{ + return gain; +} + +double perseus_source_c::set_gain( double gain, const std::string & name, size_t chan) +{ + return gain; +} + +double perseus_source_c::get_gain( size_t chan ) +{ + return 0.0; +} + +double perseus_source_c::get_gain( const std::string & name, size_t chan ) +{ + return 0.0; +} + +std::vector< std::string > perseus_source_c::get_antennas( size_t chan ) +{ + std::vector< std::string > antennas; + + antennas += get_antenna( chan ); + + return antennas; +} + +std::string perseus_source_c::set_antenna( const std::string & antenna, size_t chan ) +{ + return get_antenna( chan ); +} + +std::string perseus_source_c::get_antenna( size_t chan ) +{ + return "RX"; +} diff --git a/lib/perseus/perseus_source_c.h b/lib/perseus/perseus_source_c.h new file mode 100644 index 0000000..9eda23d --- /dev/null +++ b/lib/perseus/perseus_source_c.h @@ -0,0 +1,120 @@ +/* -*- c++ -*- */ +/* + * Copyright 2013 Dimitri Stolnikov + * + * This file is part of GNU Radio + * + * GNU Radio 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, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ +#ifndef INCLUDED_PERSEUS_SOURCE_C_H +#define INCLUDED_PERSEUS_SOURCE_C_H + +#include +#include +#include + +#include + +#include "source_iface.h" +#include + +class perseus_source_c; + +typedef boost::shared_ptr perseus_source_c_sptr; + +/*! + * \brief Return a shared_ptr to a new instance of perseus_source_c. + * + * To avoid accidental use of raw pointers, perseus_source_c's + * constructor is private. make_perseus_source_c is the public + * interface for creating new instances. + */ +perseus_source_c_sptr make_perseus_source_c (const std::string & args = ""); + +/*! + * \brief Provides a stream of complex samples. + * \ingroup block + */ +class perseus_source_c : + public gr::sync_block, + public source_iface +{ +private: + // The friend declaration allows make_perseus_source_c to + // access the private constructor. + + friend perseus_source_c_sptr make_perseus_source_c (const std::string & args); + + perseus_source_c (const std::string & args); + +public: + ~perseus_source_c (); + + bool start(); + bool stop(); + + int work( int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items ); + + static std::vector< std::string > get_devices(); + + size_t get_num_channels( void ); + + osmosdr::meta_range_t get_sample_rates( void ); + double set_sample_rate( double rate ); + double get_sample_rate( void ); + + osmosdr::freq_range_t get_freq_range( size_t chan = 0 ); + double set_center_freq( double freq, size_t chan = 0 ); + double get_center_freq( size_t chan = 0 ); + double set_freq_corr( double ppm, size_t chan = 0 ); + double get_freq_corr( size_t chan = 0 ); + + std::vector get_gain_names( size_t chan = 0 ); + osmosdr::gain_range_t get_gain_range( size_t chan = 0 ); + osmosdr::gain_range_t get_gain_range( const std::string & name, size_t chan = 0 ); + double set_gain( double gain, size_t chan = 0 ); + double set_gain( double gain, const std::string & name, size_t chan = 0 ); + double get_gain( size_t chan = 0 ); + double get_gain( const std::string & name, size_t chan = 0 ); + + std::vector< std::string > get_antennas( size_t chan = 0 ); + std::string set_antenna( const std::string & antenna, size_t chan = 0 ); + std::string get_antenna( size_t chan = 0 ); + + +private: + static int _perseus_rx_callback(void *buf, int buf_size, void *extra); + + int perseus_rx_callback(void *samples, int sample_count); + + perseus_descr *_dev; + + boost::circular_buffer *_fifo; + boost::mutex _fifo_lock; + boost::condition_variable _samp_avail; + + std::vector< std::pair > _sample_rates; + double _sample_rate; + double _center_freq; + double _freq_corr; + + bool _frun; + int _cnt; +}; + +#endif /* INCLUDED_PERSEUS_SOURCE_C_H */ diff --git a/lib/rtl/rtl_source_c.cc b/lib/rtl/rtl_source_c.cc index a371464..eb7042e 100644 --- a/lib/rtl/rtl_source_c.cc +++ b/lib/rtl/rtl_source_c.cc @@ -221,7 +221,7 @@ rtl_source_c::rtl_source_c (const std::string &args) throw std::runtime_error("Failed to enable offset tuning."); } - ret = rtlsdr_set_bias_tee(_dev, bias_tee); +// ret = rtlsdr_set_bias_tee(_dev, bias_tee); if (ret < 0) throw std::runtime_error("Failed to set bias tee."); diff --git a/lib/source_impl.cc b/lib/source_impl.cc index a28f314..ead4ef7 100644 --- a/lib/source_impl.cc +++ b/lib/source_impl.cc @@ -92,6 +92,9 @@ #include #endif +#ifdef ENABLE_PERSEUS +#include +#endif #include "arg_helpers.h" #include "source_impl.h" @@ -171,6 +174,9 @@ source_impl::source_impl( const std::string &args ) #ifdef ENABLE_FREESRP dev_types.push_back("freesrp"); #endif +#ifdef ENABLE_PERSEUS + dev_types.push_back("perseus"); +#endif std::cerr << "gr-osmosdr " << GR_OSMOSDR_VERSION << " (" << GR_OSMOSDR_LIBVER << ") " << "gnuradio " << gr::version() << std::endl; @@ -252,7 +258,10 @@ source_impl::source_impl( const std::string &args ) BOOST_FOREACH( std::string dev, freesrp_source_c::get_devices() ) dev_list.push_back( dev ); #endif - +#ifdef ENABLE_PERSEUS + BOOST_FOREACH( std::string dev, perseus_source_c::get_devices() ) + dev_list.push_back( dev ); +#endif // std::cerr << std::endl; // BOOST_FOREACH( std::string dev, dev_list ) // std::cerr << "'" << dev << "'" << std::endl; @@ -383,6 +392,13 @@ source_impl::source_impl( const std::string &args ) } #endif +#ifdef ENABLE_PERSEUS + if ( dict.count("perseus") ) { + perseus_source_c_sptr src = make_perseus_source_c( arg ); + block = src; iface = src.get(); + } +#endif + if ( iface != NULL && long(block.get()) != 0 ) { _devs.push_back( iface ); -- 1.9.1 >From 1b6cf50a7a6074754717b0270757931c379561bc Mon Sep 17 00:00:00 2001 From: Andrea Montefusco IW0HDV Date: Thu, 1 Feb 2018 17:33:39 +0100 Subject: More hardware controls added (attenuator, ADC preamp and dither, preselector) --- lib/perseus/perseus_source_c.cc | 136 ++++++++++++++++++++++++++++++++++++---- lib/perseus/perseus_source_c.h | 19 +++++- 2 files changed, 142 insertions(+), 13 deletions(-) diff --git a/lib/perseus/perseus_source_c.cc b/lib/perseus/perseus_source_c.cc index 0720da4..89674b1 100644 --- a/lib/perseus/perseus_source_c.cc +++ b/lib/perseus/perseus_source_c.cc @@ -89,7 +89,11 @@ perseus_source_c::perseus_source_c (const std::string &args) _center_freq(0), _freq_corr(0), _frun(false), - _cnt(0) + _cnt(0), + _att_value(0), + _adc_preamp(false), + _adc_dither(false), + _preselector(false) { //int ret; @@ -174,9 +178,9 @@ perseus_source_c::perseus_source_c (const std::string &args) PERSEUS_THROW_ON_ERROR(ret, "Perseus fpga configuration error"); } - perseus_set_attenuator_in_db(_dev, PERSEUS_ATT_0DB); + perseus_set_attenuator_in_db(_dev, _att_value); // Enable ADC Dither, Disable ADC Preamp - perseus_set_adc(_dev, true, false); + perseus_set_adc(_dev, _adc_dither == true ? 1:0, _adc_preamp == true ? 1:0); // set the NCO frequency in the middle of the range allowed set_center_freq( (get_freq_range().start() + get_freq_range().stop()) / 2.0 ); set_sample_rate( get_sample_rates().start() ); @@ -425,7 +429,7 @@ double perseus_source_c::set_center_freq( double freq, size_t chan ) ret = perseus_set_ddc_center_freq(_dev, (uint64_t)(freq * (1.0 + (_freq_corr * 10e-6))), - 1); + _preselector == true ? 1:0); if ( ret == PERSEUS_NOERROR ) { _center_freq = freq; } else { @@ -453,55 +457,165 @@ double perseus_source_c::get_freq_corr( size_t chan ) std::vector perseus_source_c::get_gain_names( size_t chan ) { - return {}; + std::vector< std::string > names; + + names += "Attenuator"; + names += "ADC preamp"; + names += "ADC dither"; + + return names; } +// +// FIXME +// osmosdr::gain_range_t perseus_source_c::get_gain_range( size_t chan ) { - return osmosdr::gain_range_t(); + return get_gain_range( "Attenuator", chan ); } osmosdr::gain_range_t perseus_source_c::get_gain_range( const std::string & name, size_t chan ) { + if ( "Attenuator" == name ) { + return osmosdr::gain_range_t( -30, 0, 10 ); + } + if ( "ADC preamp" == name ) { + return osmosdr::gain_range_t( 0, 6, 6 ); + } + if ( "ADC dither" == name ) { + return osmosdr::gain_range_t( 0, 1, 1 ); + } + return osmosdr::gain_range_t(); } -double perseus_source_c::set_gain( double gain, size_t chan ) +double perseus_source_c::set_attenuator( double gain, size_t chan ) { + int ret; + + gain = - gain; + if (_dev && _att_value != gain) { + ret = perseus_set_attenuator_in_db (_dev, gain); + + if ( PERSEUS_NOERROR == ret ) { + _att_value = gain; + } else { + std::cerr << "ERROR: perseus_set_attenuator_in_db: " << gain << std::endl; + } + } + + return _att_value; +} + +double perseus_source_c::set_adc_preamp( double gain, size_t chan ) +{ + std::cerr << "set_adc_preamp: " << gain << std::endl; + + if (_dev) { + _adc_preamp = (gain != 0. ? true : false); + int ret = + perseus_set_adc(_dev, _adc_dither == true ? 1:0, _adc_preamp == true ? 1:0); + if ( PERSEUS_NOERROR != ret ) { + std::cerr << "ERROR: perseus_set_adc" << ret << std::endl; + } + } return gain; } -double perseus_source_c::set_gain( double gain, const std::string & name, size_t chan) +double perseus_source_c::set_adc_dither( double gain, size_t chan ) { + std::cerr << "set_adc_dither: " << gain << std::endl; + + if (_dev) { + _adc_dither = (gain != 0. ? true : false); + int ret = + perseus_set_adc(_dev, _adc_dither == true ? 1:0, _adc_preamp == true ? 1:0); + if ( PERSEUS_NOERROR != ret ) { + std::cerr << "ERROR: perseus_set_adc" << ret << std::endl; + } + } return gain; } -double perseus_source_c::get_gain( size_t chan ) +double perseus_source_c::get_attenuator( size_t chan ) { - return 0.0; + return _att_value; } +double perseus_source_c::get_adc_preamp(size_t chan ) +{ + double v = _adc_preamp == true ? 6. : 0.; + std::cerr << "get_adc_preamp: " << _adc_preamp << " value" << v << std::endl; + return v; +} + +double perseus_source_c::get_adc_dither(size_t chan ) +{ + double v = _adc_dither == true ? 1. : 0.; + std::cerr << "get_adc_dither: " << _adc_dither << " value" << v << std::endl; + return v; +} + + +double perseus_source_c::set_gain( double gain, const std::string & name, size_t chan) +{ + if ( "Attenuator" == name ) { + return set_attenuator( gain, chan ); + } + if ( "ADC preamp" == name ) { + return set_adc_preamp( gain, chan ); + } + if ( "ADC dither" == name ) { + return set_adc_dither( gain, chan ); + } + return gain; +} + + double perseus_source_c::get_gain( const std::string & name, size_t chan ) { + if ( "Attenuator" == name ) { + return get_attenuator( chan ); + } + if ( "ADC preamp" == name ) { + return get_adc_preamp( chan ); + } + if ( "ADC dither" == name ) { + return get_adc_dither( chan ); + } return 0.0; } + + std::vector< std::string > perseus_source_c::get_antennas( size_t chan ) { std::vector< std::string > antennas; - antennas += get_antenna( chan ); + antennas += "RX"; + antennas += "RX+PRESELECTOR"; return antennas; } std::string perseus_source_c::set_antenna( const std::string & antenna, size_t chan ) { + if ( antenna == "RX" ) { + _preselector = false; + set_center_freq( get_center_freq() ); + return "RX"; + } + if ( antenna == "RX+PRESELECTOR" ) { + _preselector = true; + set_center_freq( get_center_freq() ); + return "RX+PRESELECTOR"; + } return get_antenna( chan ); } std::string perseus_source_c::get_antenna( size_t chan ) { + std::cerr << "perseus_source_c::get_antenna: " << chan << std::endl; return "RX"; } diff --git a/lib/perseus/perseus_source_c.h b/lib/perseus/perseus_source_c.h index 9eda23d..934ff2b 100644 --- a/lib/perseus/perseus_source_c.h +++ b/lib/perseus/perseus_source_c.h @@ -87,9 +87,20 @@ public: std::vector get_gain_names( size_t chan = 0 ); osmosdr::gain_range_t get_gain_range( size_t chan = 0 ); osmosdr::gain_range_t get_gain_range( const std::string & name, size_t chan = 0 ); - double set_gain( double gain, size_t chan = 0 ); + + double set_gain( double gain, size_t chan = 0 ) {return gain; } + double get_gain( size_t chan = 0 ) {return _att_value; } + + double set_attenuator( double gain, size_t chan = 0 ); + double get_attenuator( size_t chan = 0 ); + + double set_adc_preamp( double gain, size_t chan = 0 ); + double get_adc_preamp( size_t chan = 0 ); + + double set_adc_dither( double gain, size_t chan = 0 ); + double get_adc_dither( size_t chan = 0 ); + double set_gain( double gain, const std::string & name, size_t chan = 0 ); - double get_gain( size_t chan = 0 ); double get_gain( const std::string & name, size_t chan = 0 ); std::vector< std::string > get_antennas( size_t chan = 0 ); @@ -115,6 +126,10 @@ private: bool _frun; int _cnt; + double _att_value; + bool _adc_preamp; + bool _adc_dither; + bool _preselector; }; #endif /* INCLUDED_PERSEUS_SOURCE_C_H */ -- 1.9.1 . QUIT From 246tnt at gmail.com Fri Feb 2 22:33:34 2018 From: 246tnt at gmail.com (Sylvain Munaut) Date: Fri, 2 Feb 2018 23:33:34 +0100 Subject: [PATCH] gr-osmosdr: Microtelecom Perseus HF receiver support In-Reply-To: References: Message-ID: Hi, Can't comment about the patch it self but : > lib/airspy/airspy_source_c.cc | 5 +- > lib/rtl/rtl_source_c.cc | 2 +- Those looks like mistakes that have nothing to do in this patch .... Cheers, Sylvain From andrea.montefusco at gmail.com Sat Feb 3 09:37:57 2018 From: andrea.montefusco at gmail.com (andrea montefusco) Date: Sat, 3 Feb 2018 10:37:57 +0100 Subject: [PATCH] gr-osmosdr: Microtelecom Perseus HF receiver support In-Reply-To: References: Message-ID: On Fri, Feb 2, 2018 at 11:33 PM, Sylvain Munaut <246tnt at gmail.com> wrote: > Hi, > > Can't comment about the patch it self but : > > lib/airspy/airspy_source_c.cc | 5 +- > > lib/rtl/rtl_source_c.cc | 2 +- > > Those looks like mistakes that have nothing to do in this patch .... > > ?Yes, I forgot to remove that. Going to send another email with the fixed patch (cannot send it from here as GMAIL is breaking lines at column 70 or so).? -- Andrea Montefusco IW0HDV ------------------------------------------ As my old boss, an Apollo veteran, would often remind us ?It?s good to be smart, but it?s better to be lucky.? Wayne Hale, Space Shuttle Flight Director -------------- next part -------------- An HTML attachment was scrubbed... URL: From andrea.montefusco at gmail.com Sat Feb 3 09:38:48 2018 From: andrea.montefusco at gmail.com (andrea.montefusco at gmail.com) Date: Sat, 03 Feb 2018 10:38:48 +0100 Subject: [PATCH] gr-osmosdr Microtelecom Perseus HF receiver support (II) Message-ID: <5a758328.jVmt9CwXV2YCfoTx%andrea.montefusco@gmail.com> Second attempt: I removed all the changes not related to Perseus. @Sylvain: thanks for take time to review. --- CMakeLists.txt | 1 + cmake/Modules/FindLibPERSEUS.cmake | 24 ++ lib/CMakeLists.txt | 8 + lib/config.h.in | 1 + lib/device.cc | 8 + lib/perseus/CMakeLists.txt | 37 +++ lib/perseus/perseus_source_c.cc | 613 +++++++++++++++++++++++++++++++++++++ lib/perseus/perseus_source_c.h | 135 ++++++++ lib/source_impl.cc | 18 +- 9 files changed, 844 insertions(+), 1 deletion(-) create mode 100644 cmake/Modules/FindLibPERSEUS.cmake create mode 100644 lib/perseus/CMakeLists.txt create mode 100644 lib/perseus/perseus_source_c.cc create mode 100644 lib/perseus/perseus_source_c.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 296456d..94acd4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,6 +173,7 @@ find_package(Volk) find_package(LibbladeRF) find_package(SoapySDR NO_MODULE) find_package(LibFreeSRP) +find_package(LibPERSEUS) find_package(Doxygen) if(NOT GNURADIO_RUNTIME_FOUND) diff --git a/cmake/Modules/FindLibPERSEUS.cmake b/cmake/Modules/FindLibPERSEUS.cmake new file mode 100644 index 0000000..5477327 --- /dev/null +++ b/cmake/Modules/FindLibPERSEUS.cmake @@ -0,0 +1,24 @@ +INCLUDE(FindPkgConfig) +PKG_CHECK_MODULES(PC_LIBPERSEUS libperseus-sdr) + +FIND_PATH( + LIBPERSEUS_INCLUDE_DIRS + NAMES perseus-sdr.h + HINTS $ENV{LIBPERSEUS_DIR}/include + ${PC_LIBPERSEUS_INCLUDEDIR} + PATHS /usr/local/include + /usr/include +) + +FIND_LIBRARY( + LIBPERSEUS_LIBRARIES + NAMES perseus-sdr + HINTS $ENV{LIBPERSEUS_DIR}/lib + ${PC_LIBPERSEUS_LIBDIR} + PATHS /usr/local/lib + /usr/lib +) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBPERSEUS DEFAULT_MSG LIBPERSEUS_LIBRARIES LIBPERSEUS_INCLUDE_DIRS) +MARK_AS_ADVANCED(LIBPERSEUS_LIBRARIES LIBPERSEUS_INCLUDE_DIRS) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index c05b8d9..f555816 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -249,6 +249,14 @@ GR_INCLUDE_SUBDIRECTORY(freesrp) endif(ENABLE_FREESRP) ######################################################################## +# Setup PERSEUS component +######################################################################## +GR_REGISTER_COMPONENT("Microtelecom Perseus Receiver" ENABLE_PERSEUS LIBPERSEUS_FOUND) +if(ENABLE_PERSEUS) +GR_INCLUDE_SUBDIRECTORY(perseus) +endif(ENABLE_PERSEUS) + +######################################################################## # Setup configuration file ######################################################################## ADD_DEFINITIONS(-DHAVE_CONFIG_H=1) diff --git a/lib/config.h.in b/lib/config.h.in index 42e72f1..dac7e39 100644 --- a/lib/config.h.in +++ b/lib/config.h.in @@ -19,6 +19,7 @@ #cmakedefine ENABLE_SOAPY #cmakedefine ENABLE_REDPITAYA #cmakedefine ENABLE_FREESRP +#cmakedefine ENABLE_PERSEUS //provide NAN define for MSVC older than VC12 #if defined(_MSC_VER) && (_MSC_VER < 1800) diff --git a/lib/device.cc b/lib/device.cc index 025a22b..df0d734 100644 --- a/lib/device.cc +++ b/lib/device.cc @@ -90,6 +90,10 @@ #include #endif +#ifdef ENABLE_PERSEUS +#include +#endif + #include "arg_helpers.h" using namespace osmosdr; @@ -194,6 +198,10 @@ devices_t device::find(const device_t &hint) BOOST_FOREACH( std::string dev, soapy_source_c::get_devices() ) devices.push_back( device_t(dev) ); #endif +#ifdef ENABLE_PERSEUS + BOOST_FOREACH( std::string dev, perseus_source_c::get_devices() ) + devices.push_back( device_t(dev) ); +#endif /* software-only sources should be appended at the very end, * hopefully resulting in hardware sources to be shown first diff --git a/lib/perseus/CMakeLists.txt b/lib/perseus/CMakeLists.txt new file mode 100644 index 0000000..f45a10d --- /dev/null +++ b/lib/perseus/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright 2017 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio 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, or (at your option) +# any later version. +# +# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${LIBPERSEUS_INCLUDE_DIRS} +) + +set(perseus_srcs + ${CMAKE_CURRENT_SOURCE_DIR}/perseus_source_c.cc +) + +######################################################################## +# Append gnuradio-osmosdr library sources +######################################################################## +list(APPEND gr_osmosdr_srcs ${perseus_srcs}) +list(APPEND gr_osmosdr_libs ${LIBPERSEUS_LIBRARIES} ${GNURADIO_BLOCKS_LIBRARIES}) diff --git a/lib/perseus/perseus_source_c.cc b/lib/perseus/perseus_source_c.cc new file mode 100644 index 0000000..bf7c9be --- /dev/null +++ b/lib/perseus/perseus_source_c.cc @@ -0,0 +1,613 @@ +/* -*- c++ -*- */ +/* + * Copyright 2018 Andrea Montefusco IW0HDV + * + * GNU Radio 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, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +/* + * config.h is generated by configure. It contains the results + * of probing for features, options etc. It should be the first + * file included in your .cc file. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "perseus_source_c.h" +#include "arg_helpers.h" + + + +using namespace boost::assign; + +#define PERSEUS_FORMAT_ERROR(ret, msg) \ + boost::str( boost::format(msg " (%1%)") % ret ) + +#define PERSEUS_THROW_ON_ERROR(ret, msg) \ + if ( ret != PERSEUS_NOERROR ) \ + { \ + throw std::runtime_error( PERSEUS_FORMAT_ERROR(ret, msg) ); \ + } + +#define PERSEUS_FUNC_STR(func, arg) \ + boost::str(boost::format(func "(%1%)") % arg) + " has failed" + +perseus_source_c_sptr make_perseus_source_c (const std::string & args) +{ + return gnuradio::get_initial_sptr(new perseus_source_c (args)); +} + +/* + * Specify constraints on number of input and output streams. + * This info is used to construct the input and output signatures + * (2nd & 3rd args to gr::block's constructor). The input and + * output signatures are used by the runtime system to + * check that a valid number and type of inputs and outputs + * are connected to this block. In this case, we accept + * only 0 input and 1 output. + */ +static const int MIN_IN = 0; // mininum number of input streams +static const int MAX_IN = 0; // maximum number of input streams +static const int MIN_OUT = 1; // minimum number of output streams +static const int MAX_OUT = 1; // maximum number of output streams + +/* + * The private constructor + */ +perseus_source_c::perseus_source_c (const std::string &args) + : gr::sync_block ("perseus_source_c", + gr::io_signature::make(MIN_IN, MAX_IN, sizeof (gr_complex)), + gr::io_signature::make(MIN_OUT, MAX_OUT, sizeof (gr_complex))), + _dev(NULL), + _sample_rate(0), + _center_freq(0), + _freq_corr(0), + _frun(false), + _cnt(0), + _att_value(0), + _adc_preamp(false), + _adc_dither(false), + _preselector(false) +{ + dict_t dict = params_to_dict(args); + + _dev = NULL; + + // Set debug info dumped to stderr to the maximum verbose level + perseus_set_debug(3); + + // Check how many Perseus receivers are connected to the system + int num_perseus = perseus_init(); + std::cerr << "## Microtelecom Perseus ctor: " << num_perseus << " Perseus receivers found." << std::endl; + + if (num_perseus==0) { + perseus_exit(); + PERSEUS_THROW_ON_ERROR(PERSEUS_DEVNOTFOUND, "No Perseus receivers detected"); + } + // Open the first one... + if ((_dev = perseus_open(0)) == NULL) { + PERSEUS_THROW_ON_ERROR(PERSEUS_DEVNOTFOUND, "Unable to open Perseus device"); + } + // Download the standard firmware to the unit + std::cerr << "Downloading firmware..." << std::endl; + if (int rc = perseus_firmware_download(_dev,NULL)<0) { + PERSEUS_THROW_ON_ERROR (rc, "firmware download error"); + } + + // Dump some information about the receiver (S/N and HW rev) + int flag; + perseus_is_preserie(_dev, &flag); + if (flag != 0) + fprintf(stderr, "The device is a preserie unit"); + else { + eeprom_prodid prodid; + if (perseus_get_product_id(_dev,&prodid)<0) + fprintf(stderr, "get product id error: %s", perseus_errorstr()); + else + fprintf(stderr, "Receiver S/N: %05d-%02hX%02hX-%02hX%02hX-%02hX%02hX - HW Release:%hd.%hd\n", + (uint16_t) prodid.sn, + (uint16_t) prodid.signature[5], + (uint16_t) prodid.signature[4], + (uint16_t) prodid.signature[3], + (uint16_t) prodid.signature[2], + (uint16_t) prodid.signature[1], + (uint16_t) prodid.signature[0], + (uint16_t) prodid.hwrel, + (uint16_t) prodid.hwver); + } + // Printing all sampling rates available ..... + { + int buf[BUFSIZ]; + + if (perseus_get_sampling_rates (_dev, buf, sizeof(buf)/sizeof(buf[0])) < 0) { + fprintf(stderr, "get sampling rates error: %s\n", perseus_errorstr()); + } else { + int i = 0; + while (buf[i]) { + fprintf(stderr, "#%d: sample rate: %d\n", i, buf[i]); + _sample_rates.push_back( std::pair( buf[i], i ) ); + i++; + } + /* since they may (and will) give us an unsorted array we have to sort it here + * to play nice with the monotonic requirement of meta-range later on */ + std::sort(_sample_rates.begin(), _sample_rates.end()); + + for (size_t i = 0; i < _sample_rates.size(); i++) + std::cerr << boost::format("%gM ") % (_sample_rates[i].first / 1e6); + + std::cerr << std::endl; + } + } + // Configure the receiver for 2 MS/s operations + fprintf(stderr, "Configuring FPGA...\n"); + if (int ret = perseus_set_sampling_rate(_dev, 96000) < 0) { // specify the sampling rate value in Samples/second + std::cerr << "fpga configuration error: " << perseus_errorstr() << std::endl; + PERSEUS_THROW_ON_ERROR(ret, "Perseus fpga configuration error"); + } + + perseus_set_attenuator_in_db(_dev, _att_value); + // Enable ADC Dither, Disable ADC Preamp + perseus_set_adc(_dev, _adc_dither == true ? 1:0, _adc_preamp == true ? 1:0); + // set the NCO frequency in the middle of the range allowed + set_center_freq( (get_freq_range().start() + get_freq_range().stop()) / 2.0 ); + set_sample_rate( get_sample_rates().start() ); + + _fifo = new boost::circular_buffer(5000000); + if (!_fifo) { + throw std::runtime_error( std::string(__FUNCTION__) + " " + + "Failed to allocate a sample FIFO!" ); + } +} + +/* + * Our virtual destructor. + */ +perseus_source_c::~perseus_source_c () +{ + if (_dev) { + if (_frun) stop(); + int ret = perseus_close(_dev); + if ( ret != PERSEUS_NOERROR ) { + std::cerr << PERSEUS_FORMAT_ERROR(ret, "Failed to close Perseus") << std::endl; + } else + _dev = NULL; + } + perseus_exit(); + + if (_fifo) { + delete _fifo; + _fifo = NULL; + } +} + + +typedef union { + struct { + int32_t i; + int32_t q; + } __attribute__((__packed__)) iq; + struct { + uint8_t i1; + uint8_t i2; + uint8_t i3; + uint8_t i4; + uint8_t q1; + uint8_t q2; + uint8_t q3; + uint8_t q4; + } __attribute__((__packed__)) ; +} iq_sample; + + +int perseus_source_c::perseus_rx_callback(void *buf, int buf_size) +{ + size_t i, n_avail, to_copy, num_samples; + + uint8_t *samplebuf = (uint8_t*)buf; + + num_samples = buf_size/6; + + if (buf_size % 6) + std::cerr << "FATAL !!!!!!! not on boundary" << std::endl; + + iq_sample s; + + _fifo_lock.lock(); + + n_avail = _fifo->capacity() - _fifo->size(); + to_copy = (n_avail < num_samples ? n_avail : num_samples); + + for (i = 0; i < to_copy; i++ ) { + + s.i1 = s.q1 = 0; + s.i2 = *samplebuf++; + s.i3 = *samplebuf++; + s.i4 = *samplebuf++; + s.q2 = *samplebuf++; + s.q3 = *samplebuf++; + s.q4 = *samplebuf++; + + // convert 24 bit two's complements integers to float in [-1.0 - +1.0] range + // Push sample to the fifo + _fifo->push_back( gr_complex( (float)(s.iq.i) / (INT_MAX - 256), (float)(s.iq.q) / (INT_MAX - 256) ) ); + + } + // keep a counter of processed bytes + _cnt += buf_size; + + _fifo_lock.unlock(); + + /* We have made some new samples available to the consumer in work() */ + if (to_copy) { + //std::cerr << "+" << std::flush; + _samp_avail.notify_one(); + } + + /* Indicate overrun, if necessary */ + if (to_copy < num_samples) + std::cerr << "O" << std::flush; + + return 0; // TODO: return -1 on error/stop +} + +// static method used as callback from the libperseus-sdr library +int perseus_source_c::_perseus_rx_callback(void *buf, int buf_size, void *extra) +{ + perseus_source_c *obj = (perseus_source_c *)extra; + + return obj->perseus_rx_callback((float *)buf, buf_size); + + return 0; +} + + +bool perseus_source_c::start() +{ + int ret; + + if ( ! _dev ) + return false; + + std::cerr << "***** START COUNTER: " << _cnt + << " mod 6 " << (_cnt % 6) << std::endl; + + _frun = true; + + // in order to guarantee samples stream synchronization + // re-set the sample rate each time the stream is started + set_sample_rate( get_sample_rate() ); + + ret = perseus_start_async_input(_dev, + 6 * 1024 * 2 /* buffer size */, + perseus_source_c::_perseus_rx_callback, + (void *)this); + if (ret != PERSEUS_NOERROR) { + std::cerr << "Failed to start RX streaming (" + << ret << ")" << std::endl; + return false; + } else + return true; +} + +bool perseus_source_c::stop() +{ + if ( ! _dev ) + return false; + + _frun = false; + + std::cerr << "***** STOP COUNTER: " << _cnt + << " mod 6 " << (_cnt % 6) << std::endl; + + int ret = perseus_stop_async_input(_dev); + + if ( ret != PERSEUS_NOERROR ) { + std::cerr << "Failed to stop RX streaming (" << ret << ")" << std::endl; + return false; + } else + return true; +} + +int perseus_source_c::work( int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items ) +{ + gr_complex *out = (gr_complex *)output_items[0]; + + if ( _frun == false ) return WORK_DONE; + + boost::unique_lock lock(_fifo_lock); + + /* Wait until we have the requested number of samples */ + int n_samples_avail = _fifo->size(); + + while (n_samples_avail < noutput_items) { + _samp_avail.wait(lock); + n_samples_avail = _fifo->size(); + } + + for(int i = 0; i < noutput_items; ++i) { + out[i] = _fifo->at(0); + _fifo->pop_front(); + } + + return noutput_items; +} + +std::vector perseus_source_c::get_devices() +{ + std::vector devices; + std::string label; + + std::string args = "perseus=0,label='Perseus'"; + devices.push_back( args ); + + return devices; +} + +size_t perseus_source_c::get_num_channels() +{ + return 1; +} + +osmosdr::meta_range_t perseus_source_c::get_sample_rates() +{ + osmosdr::meta_range_t range; + + for (size_t i = 0; i < _sample_rates.size(); i++) { + range += osmosdr::range_t( _sample_rates[i].first ); + std::cerr << "SR: " << i << " : (" + << _sample_rates[i].first << ")" << std::endl; + } + return range; +} + +double perseus_source_c::set_sample_rate( double rate ) +{ + int ret; + if ((ret = perseus_set_sampling_rate(_dev, rate)) < 0) { + PERSEUS_THROW_ON_ERROR(ret, "Unable to set sample rate."); + } else + _sample_rate = rate; + + return get_sample_rate(); +} + +double perseus_source_c::get_sample_rate() +{ + return _sample_rate; +} + +osmosdr::freq_range_t perseus_source_c::get_freq_range( size_t chan ) +{ + osmosdr::freq_range_t range; + + range += osmosdr::range_t( PERSEUS_DDC_FREQ_MIN, PERSEUS_DDC_FREQ_MAX ); + + return range; +} + +double perseus_source_c::set_center_freq( double freq, size_t chan ) +{ + int ret; + + if (_dev) { + ret = + perseus_set_ddc_center_freq(_dev, + (uint64_t)(freq * (1.0 + (_freq_corr * 10e-6))), + _preselector == true ? 1:0); + if ( ret == PERSEUS_NOERROR ) { + _center_freq = freq; + } else { + PERSEUS_THROW_ON_ERROR( ret, PERSEUS_FUNC_STR( "perseus_set_ddc_center_freq", freq ) ) + } + } + + return get_center_freq( chan ); +} + +double perseus_source_c::get_center_freq( size_t chan ) +{ + return _center_freq; +} + +double perseus_source_c::set_freq_corr( double ppm, size_t chan ) +{ + return _freq_corr = ppm; +} + +double perseus_source_c::get_freq_corr( size_t chan ) +{ + return _freq_corr; +} + +std::vector perseus_source_c::get_gain_names( size_t chan ) +{ + std::vector< std::string > names; + + names += "Attenuator"; + names += "ADC preamp"; + names += "ADC dither"; + + return names; +} + +// +// FIXME +// +osmosdr::gain_range_t perseus_source_c::get_gain_range( size_t chan ) +{ + return get_gain_range( "Attenuator", chan ); +} + +osmosdr::gain_range_t perseus_source_c::get_gain_range( const std::string & name, size_t chan ) +{ + if ( "Attenuator" == name ) { + return osmosdr::gain_range_t( -30, 0, 10 ); + } + if ( "ADC preamp" == name ) { + return osmosdr::gain_range_t( 0, 6, 6 ); + } + if ( "ADC dither" == name ) { + return osmosdr::gain_range_t( 0, 1, 1 ); + } + + return osmosdr::gain_range_t(); +} + + +double perseus_source_c::set_attenuator( double gain, size_t chan ) +{ + int ret; + + gain = - gain; + if (_dev && _att_value != gain) { + ret = perseus_set_attenuator_in_db (_dev, gain); + + if ( PERSEUS_NOERROR == ret ) { + _att_value = gain; + } else { + std::cerr << "ERROR: perseus_set_attenuator_in_db: " << gain << std::endl; + } + } + + return _att_value; +} + +double perseus_source_c::set_adc_preamp( double gain, size_t chan ) +{ + std::cerr << "set_adc_preamp: " << gain << std::endl; + + if (_dev) { + _adc_preamp = (gain != 0. ? true : false); + int ret = + perseus_set_adc(_dev, _adc_dither == true ? 1:0, _adc_preamp == true ? 1:0); + if ( PERSEUS_NOERROR != ret ) { + std::cerr << "ERROR: perseus_set_adc" << ret << std::endl; + } + } + return gain; +} + +double perseus_source_c::set_adc_dither( double gain, size_t chan ) +{ + std::cerr << "set_adc_dither: " << gain << std::endl; + + if (_dev) { + _adc_dither = (gain != 0. ? true : false); + int ret = + perseus_set_adc(_dev, _adc_dither == true ? 1:0, _adc_preamp == true ? 1:0); + if ( PERSEUS_NOERROR != ret ) { + std::cerr << "ERROR: perseus_set_adc" << ret << std::endl; + } + } + return gain; +} + +double perseus_source_c::get_attenuator( size_t chan ) +{ + return _att_value; +} + +double perseus_source_c::get_adc_preamp(size_t chan ) +{ + double v = _adc_preamp == true ? 6. : 0.; + std::cerr << "get_adc_preamp: " << _adc_preamp << " value" << v << std::endl; + return v; +} + +double perseus_source_c::get_adc_dither(size_t chan ) +{ + double v = _adc_dither == true ? 1. : 0.; + std::cerr << "get_adc_dither: " << _adc_dither << " value" << v << std::endl; + return v; +} + + +double perseus_source_c::set_gain( double gain, const std::string & name, size_t chan) +{ + if ( "Attenuator" == name ) { + return set_attenuator( gain, chan ); + } + if ( "ADC preamp" == name ) { + return set_adc_preamp( gain, chan ); + } + if ( "ADC dither" == name ) { + return set_adc_dither( gain, chan ); + } + return gain; +} + + +double perseus_source_c::get_gain( const std::string & name, size_t chan ) +{ + if ( "Attenuator" == name ) { + return get_attenuator( chan ); + } + if ( "ADC preamp" == name ) { + return get_adc_preamp( chan ); + } + if ( "ADC dither" == name ) { + return get_adc_dither( chan ); + } + return 0.0; +} + + + +std::vector< std::string > perseus_source_c::get_antennas( size_t chan ) +{ + std::vector< std::string > antennas; + + antennas += "RX"; + antennas += "RX+PRESELECTOR"; + + return antennas; +} + +std::string perseus_source_c::set_antenna( const std::string & antenna, size_t chan ) +{ + if ( antenna == "RX" ) { + _preselector = false; + set_center_freq( get_center_freq() ); + return "RX"; + } + if ( antenna == "RX+PRESELECTOR" ) { + _preselector = true; + set_center_freq( get_center_freq() ); + return "RX+PRESELECTOR"; + } + return get_antenna( chan ); +} + +std::string perseus_source_c::get_antenna( size_t chan ) +{ + std::cerr << "perseus_source_c::get_antenna: " << chan << std::endl; + return "RX"; +} diff --git a/lib/perseus/perseus_source_c.h b/lib/perseus/perseus_source_c.h new file mode 100644 index 0000000..55d3f89 --- /dev/null +++ b/lib/perseus/perseus_source_c.h @@ -0,0 +1,135 @@ +/* -*- c++ -*- */ +/* + * Copyright 2013 Dimitri Stolnikov + * + * This file is part of GNU Radio + * + * GNU Radio 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, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ +#ifndef INCLUDED_PERSEUS_SOURCE_C_H +#define INCLUDED_PERSEUS_SOURCE_C_H + +#include +#include +#include + +#include + +#include "source_iface.h" +#include + +class perseus_source_c; + +typedef boost::shared_ptr perseus_source_c_sptr; + +/*! + * \brief Return a shared_ptr to a new instance of perseus_source_c. + * + * To avoid accidental use of raw pointers, perseus_source_c's + * constructor is private. make_perseus_source_c is the public + * interface for creating new instances. + */ +perseus_source_c_sptr make_perseus_source_c (const std::string & args = ""); + +/*! + * \brief Provides a stream of complex samples. + * \ingroup block + */ +class perseus_source_c : + public gr::sync_block, + public source_iface +{ +private: + // The friend declaration allows make_perseus_source_c to + // access the private constructor. + + friend perseus_source_c_sptr make_perseus_source_c (const std::string & args); + + perseus_source_c (const std::string & args); + +public: + ~perseus_source_c (); + + bool start(); + bool stop(); + + int work( int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items ); + + static std::vector< std::string > get_devices(); + + size_t get_num_channels( void ); + + osmosdr::meta_range_t get_sample_rates( void ); + double set_sample_rate( double rate ); + double get_sample_rate( void ); + + osmosdr::freq_range_t get_freq_range( size_t chan = 0 ); + double set_center_freq( double freq, size_t chan = 0 ); + double get_center_freq( size_t chan = 0 ); + double set_freq_corr( double ppm, size_t chan = 0 ); + double get_freq_corr( size_t chan = 0 ); + + std::vector get_gain_names( size_t chan = 0 ); + osmosdr::gain_range_t get_gain_range( size_t chan = 0 ); + osmosdr::gain_range_t get_gain_range( const std::string & name, size_t chan = 0 ); + + double set_gain( double gain, size_t chan = 0 ) {return gain; } + double get_gain( size_t chan = 0 ) {return _att_value; } + + double set_attenuator( double gain, size_t chan = 0 ); + double get_attenuator( size_t chan = 0 ); + + double set_adc_preamp( double gain, size_t chan = 0 ); + double get_adc_preamp( size_t chan = 0 ); + + double set_adc_dither( double gain, size_t chan = 0 ); + double get_adc_dither( size_t chan = 0 ); + + double set_gain( double gain, const std::string & name, size_t chan = 0 ); + double get_gain( const std::string & name, size_t chan = 0 ); + + std::vector< std::string > get_antennas( size_t chan = 0 ); + std::string set_antenna( const std::string & antenna, size_t chan = 0 ); + std::string get_antenna( size_t chan = 0 ); + + +private: + static int _perseus_rx_callback(void *buf, int buf_size, void *extra); + + int perseus_rx_callback(void *samples, int sample_count); + + perseus_descr *_dev; + + boost::circular_buffer *_fifo; + boost::mutex _fifo_lock; + boost::condition_variable _samp_avail; + + std::vector< std::pair > _sample_rates; + double _sample_rate; + double _center_freq; + double _freq_corr; + + bool _frun; + int _cnt; + double _att_value; + bool _adc_preamp; + bool _adc_dither; + bool _preselector; +}; + +#endif /* INCLUDED_PERSEUS_SOURCE_C_H */ diff --git a/lib/source_impl.cc b/lib/source_impl.cc index a28f314..ead4ef7 100644 --- a/lib/source_impl.cc +++ b/lib/source_impl.cc @@ -92,6 +92,9 @@ #include #endif +#ifdef ENABLE_PERSEUS +#include +#endif #include "arg_helpers.h" #include "source_impl.h" @@ -171,6 +174,9 @@ source_impl::source_impl( const std::string &args ) #ifdef ENABLE_FREESRP dev_types.push_back("freesrp"); #endif +#ifdef ENABLE_PERSEUS + dev_types.push_back("perseus"); +#endif std::cerr << "gr-osmosdr " << GR_OSMOSDR_VERSION << " (" << GR_OSMOSDR_LIBVER << ") " << "gnuradio " << gr::version() << std::endl; @@ -252,7 +258,10 @@ source_impl::source_impl( const std::string &args ) BOOST_FOREACH( std::string dev, freesrp_source_c::get_devices() ) dev_list.push_back( dev ); #endif - +#ifdef ENABLE_PERSEUS + BOOST_FOREACH( std::string dev, perseus_source_c::get_devices() ) + dev_list.push_back( dev ); +#endif // std::cerr << std::endl; // BOOST_FOREACH( std::string dev, dev_list ) // std::cerr << "'" << dev << "'" << std::endl; @@ -383,6 +392,13 @@ source_impl::source_impl( const std::string &args ) } #endif +#ifdef ENABLE_PERSEUS + if ( dict.count("perseus") ) { + perseus_source_c_sptr src = make_perseus_source_c( arg ); + block = src; iface = src.get(); + } +#endif + if ( iface != NULL && long(block.get()) != 0 ) { _devs.push_back( iface ); -- 1.9.1 From shigawire at gmail.com Sun Feb 4 12:23:19 2018 From: shigawire at gmail.com (David Basden) Date: Sun, 4 Feb 2018 23:23:19 +1100 Subject: No subject Message-ID: Hi, I recently found my dongle with the FC0012 again after having lost it for a couple of years, and noticed the tuner wasn't detecting on recent builds (by recent, it looks like at least a year and a half ago. :) ) There were a few other posts around that I saw with the same issue so I dug into the problem a bit. It looks it was a behavioural regression in commit ba64a7459a43652354990855176a7d8dad5b9d54 which was a actually bugfix in the GPIO direction setting code (see below for the commit comment). The bugfix patch references http://lea.hamradio.si/~s57uuu/mischam/rtlsdr/ports.html which The reason this regressed the FC0012 tuner support is that in tuner detection code, before the probe for the FC00012 there are a few lines: /* initialise GPIOs */ rtlsdr_set_gpio_output(dev, 5); /* reset tuner before probing */ rtlsdr_set_gpio_bit(dev, 5, 1); rtlsdr_set_gpio_bit(dev, 5, 0); Looking at one of the early (ugly) patches I wrote trying to get the FC0012 working, This is the original code before cleaned up: https://gist.github.com/dbasden/2171926#file-rtlsdr-fc0012-diff-L123 : + if (tuner_type == TUNER_FC0012) { + dump_rtl_regs(); + DEBUGF("reset fs0012 tuner... "); + /* the fs0012 has it's RESET line hooked up to GPIO5 + * + * If we don't set GPIO5 to an output and leave it floating, + * the tuner never comes up (just stays in RESET state) + * + * GPIO6 controls the V/U band filter, so we should set that + * to an output too + */ + r = rtl_read_reg(SYSB, GPD, 1); + r &= (~(0x30)); rtl_write_reg(SYSB, GPO, r, 1); + r = rtl_read_reg(SYSB, GPOE, 1); + r |= 0x30; rtl_write_reg(SYSB, GPOE, r, 1); + + /* Do reset */ + rtl_set_gpio_bit(5,1); + rtl_set_gpio_bit(5,0); + dump_rtl_regs(); + } This code was eventually abstracted out to The bug in the code above described in http://lea.hamradio.si/~s57uuu/mischam/rtlsdr/ports.html, linked to in the commit log of the ba64a7459a43652354990855176a7d8dad5b9d54 fixing the same bug. The code was cleaned up eventually abstracted into rtl_sdr_set_output(), which had the same typo as my original code: I wrote back the GPD register to GPO. This accidentally worked enough to fix the specific case I was wanting (to stop the RESET line of the FC0012 floating), but didn't actually write to the GPIO direction register. Looking at some of the forum posts, and checking myself, if the older code runs once, the tuner will be just fine until the dongle loses power. Then as GPIO5 is not set up into a state for the FC0012 to be happy, it will not come up again. This seems to have made the problem harder to debug (I don't know if I would have not known where to look if I had not already hit the problem when originally porting the driver) Digging through some more just now (I really don't have a great dev environment right now, and nothing to probe the hardware itself easily available), I checked the state of GPD, GPO and GPOE before the tuner probing, at it looks like the only real side effect from the code above being buggy would be to set bit 1<<4 low. calling rtl_set_gpio_bit(4,0) brought up the FC0012 tuner. Re-reading the old and new code, it looks like it's an off-by-one error in GPIO numbering: * The original code set GPOE on 0x30, which is (1<<4) | (1<<5). * These are referred to in the code above as "GPIO5" as the FC0012 RESET line and "GPIO6" as the V/U band filter control. * rtlsdr_set_gpio_output(5) and rtl_set_gpio_bit(5,0) are switching the V/U band filter, not the RESET line for the FC0012 Sorry, I don't have access to the datasheet for the RTL, so I'm not sure if bit 0 is GPIO0 or GPIO1. In any case, it looks like the solution is to just fix the off-by-one error in the FC0012 probe code. I'll post a patch. Thanks, David (commit for bugfix where fc0012 tuner regressed.) commit ba64a7459a43652354990855176a7d8dad5b9d54 Author: Lucas Teske Date: Wed Aug 17 20:31:33 2016 -0300 lib: fix direction bit in GPIO code source: http://lea.hamradio.si/~s57uuu/mischam/rtlsdr/ports.html * Removed unnecessary comment of old code. Signed-off-by: Fabian P. Schmidt Signed-off-by: Steve Markgraf :040000 040000 12c5ea73db04c45699d7befa2c5916ae074a1999 2d08166fe31338f5ff90186272b40bc6ed3254fa M src (HEAD) davidb at grey:~/Personal/sdr/rtl-sdr$ -------------- next part -------------- An HTML attachment was scrubbed... URL: From shigawire at gmail.com Sun Feb 4 12:50:23 2018 From: shigawire at gmail.com (David Basden) Date: Sun, 4 Feb 2018 23:50:23 +1100 Subject: [PATCH] RTL-SDR: Fix regression in FC00012 tuner startup (needs testing on FC2580) Message-ID: This patch fixes a regression of rtl-sdr dongles with the FC00012 tuner. The code to switch on the FC00012 tuner by pulling it's ~RESET line low ended up with an off-by-one error in a refactor a long time ago, The FC00012 only continued to work by accident as a different bug in rtlsdr_set_gpio_output had a side-effect of pulling low the FC00012's ~RESET line When the rtl_set_gpio_output bug was fixed in ba64a7459a43 the side- effect also went away, leaving the FC00012 tuner in reset, and failing to be detected (or indeed work at all). This patch fixes the original off-by-one error. It's been tested on an FC00012 in a GTek T803 from both warm and cold starts, but needs testing on the FC2580 as both the FC00012 and FC2580 are brought out of reset by a GPIO off the RTL (at least in some designs). If the FC2580 actually uses a different pin, this code will also break. (I'm only going from vague memory that the FC2580 used the same pin.) --- src/librtlsdr.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/librtlsdr.c b/src/librtlsdr.c index b369a5d..678fadd 100644 --- a/src/librtlsdr.c +++ b/src/librtlsdr.c @@ -1565,11 +1565,11 @@ int rtlsdr_open(rtlsdr_dev_t **out_dev, uint32_t index) } /* initialise GPIOs */ - rtlsdr_set_gpio_output(dev, 5); + rtlsdr_set_gpio_output(dev, 4); /* reset tuner before probing */ - rtlsdr_set_gpio_bit(dev, 5, 1); - rtlsdr_set_gpio_bit(dev, 5, 0); + rtlsdr_set_gpio_bit(dev, 4, 1); + rtlsdr_set_gpio_bit(dev, 4, 0); reg = rtlsdr_i2c_read_reg(dev, FC2580_I2C_ADDR, FC2580_CHECK_ADDR); if ((reg & 0x7f) == FC2580_CHECK_VAL) { @@ -1581,7 +1581,7 @@ int rtlsdr_open(rtlsdr_dev_t **out_dev, uint32_t index) reg = rtlsdr_i2c_read_reg(dev, FC0012_I2C_ADDR, FC0012_CHECK_ADDR); if (reg == FC0012_CHECK_VAL) { fprintf(stderr, "Found Fitipower FC0012 tuner\n"); - rtlsdr_set_gpio_output(dev, 6); + rtlsdr_set_gpio_output(dev, 5); dev->tuner_type = RTLSDR_TUNER_FC0012; goto found; } -- 2.13.2 -------------- next part -------------- An HTML attachment was scrubbed... URL: From vk4tec at tech-software.net Sun Feb 4 12:37:01 2018 From: vk4tec at tech-software.net (Andrew Rich) Date: Sun, 4 Feb 2018 22:37:01 +1000 Subject: No subject In-Reply-To: References: Message-ID: <0427A7C9-E124-4A94-AA9A-5016F8AF9409@tech-software.net> Why such a huge email and no subject ? Sent from my iPhone > On 4 Feb 2018, at 10:23 pm, David Basden wrote: > > > Hi, > > I recently found my dongle with the FC0012 again after having lost it for a couple of years, and noticed the tuner wasn't detecting on recent builds (by recent, it looks like at least a year and a half ago. :) ) > > There were a few other posts around that I saw with the same issue so I dug into the problem a bit. It looks it was a behavioural regression in commit ba64a7459a43652354990855176a7d8dad5b9d54 > which was a actually bugfix in the GPIO direction setting code (see below for the commit comment). The bugfix patch references http://lea.hamradio.si/~s57uuu/mischam/rtlsdr/ports.html which > > The reason this regressed the FC0012 tuner support is that in tuner detection code, before the probe for the FC00012 there are a few lines: > > /* initialise GPIOs */ > rtlsdr_set_gpio_output(dev, 5); > > /* reset tuner before probing */ > rtlsdr_set_gpio_bit(dev, 5, 1); > rtlsdr_set_gpio_bit(dev, 5, 0); > > Looking at one of the early (ugly) patches I wrote trying to get the FC0012 working, This is the original code before cleaned up: https://gist.github.com/dbasden/2171926#file-rtlsdr-fc0012-diff-L123 : > > + if (tuner_type == TUNER_FC0012) { > + dump_rtl_regs(); > + DEBUGF("reset fs0012 tuner... "); > + /* the fs0012 has it's RESET line hooked up to GPIO5 > + * > + * If we don't set GPIO5 to an output and leave it floating, > + * the tuner never comes up (just stays in RESET state) > + * > + * GPIO6 controls the V/U band filter, so we should set that > + * to an output too > + */ > + r = rtl_read_reg(SYSB, GPD, 1); > + r &= (~(0x30)); rtl_write_reg(SYSB, GPO, r, 1); > + r = rtl_read_reg(SYSB, GPOE, 1); > + r |= 0x30; rtl_write_reg(SYSB, GPOE, r, 1); > + > + /* Do reset */ > + rtl_set_gpio_bit(5,1); > + rtl_set_gpio_bit(5,0); > + dump_rtl_regs(); > + } > > This code was eventually abstracted out to The bug in the code above described in http://lea.hamradio.si/~s57uuu/mischam/rtlsdr/ports.html, linked to in the commit log of the ba64a7459a43652354990855176a7d8dad5b9d54 fixing the same bug. > > The code was cleaned up eventually abstracted into rtl_sdr_set_output(), which had the same typo as my original code: I wrote back the GPD register to GPO. This accidentally worked enough to fix the specific case I was wanting (to stop the RESET line of the FC0012 floating), but didn't actually write to the GPIO direction register. > > Looking at some of the forum posts, and checking myself, if the older code runs once, the tuner will be just fine until the dongle loses power. Then as GPIO5 is not set up into a state for the FC0012 to be happy, it will not come up again. This seems to have made the problem harder to debug (I don't know if I would have not known where to look if I had not already hit the problem when originally porting the driver) > > > Digging through some more just now (I really don't have a great dev environment right now, and nothing to probe the hardware itself easily available), I checked the state of GPD, GPO and GPOE before the tuner probing, at it looks like the only real side effect from the code above being buggy would be to set bit 1<<4 low. calling rtl_set_gpio_bit(4,0) brought up the FC0012 tuner. Re-reading the old and new code, it looks like it's an off-by-one error in GPIO numbering: > > * The original code set GPOE on 0x30, which is (1<<4) | (1<<5). > * These are referred to in the code above as "GPIO5" as the FC0012 RESET line and > "GPIO6" as the V/U band filter control. > * rtlsdr_set_gpio_output(5) and rtl_set_gpio_bit(5,0) are switching the V/U band filter, not the RESET line for the FC0012 > > Sorry, I don't have access to the datasheet for the RTL, so I'm not sure if bit 0 is GPIO0 or GPIO1. In any case, it looks like the solution is to just fix the off-by-one error in the FC0012 probe code. I'll post a patch. > > Thanks, > > David > > > (commit for bugfix where fc0012 tuner regressed.) > > > commit ba64a7459a43652354990855176a7d8dad5b9d54 > Author: Lucas Teske > Date: Wed Aug 17 20:31:33 2016 -0300 > > lib: fix direction bit in GPIO code > > source: http://lea.hamradio.si/~s57uuu/mischam/rtlsdr/ports.html > > * Removed unnecessary comment of old code. > Signed-off-by: Fabian P. Schmidt > Signed-off-by: Steve Markgraf > > :040000 040000 12c5ea73db04c45699d7befa2c5916ae074a1999 2d08166fe31338f5ff90186272b40bc6ed3254fa M src > (HEAD) davidb at grey:~/Personal/sdr/rtl-sdr$ -------------- next part -------------- An HTML attachment was scrubbed... URL: From shigawire at gmail.com Sun Feb 4 11:39:02 2018 From: shigawire at gmail.com (David Basden) Date: Sun, 4 Feb 2018 22:39:02 +1100 Subject: librtlsdr: FC0012 tuner regression Message-ID: Hi, I recently found my dongle with the FC0012 again after having lost it for a couple of years, and noticed the tuner wasn't detecting on recent builds (by recent, it looks like at least a year and a half ago. :) ) There were a few other posts around that I saw with the same issue so I dug into the problem a bit. It looks it was a behavioural regression in commit ba64a7459a43652354990855176a7d8dad5b9d54 which was a actually bugfix in the GPIO direction setting code (see below for the commit comment). The bugfix patch references http://lea.hamradio.si/~s57uuu/mischam/rtlsdr/ports.html which The reason this regressed the FC0012 tuner support is that in tuner detection code, before the probe for the FC00012 there are a few lines: /* initialise GPIOs */ rtlsdr_set_gpio_output(dev, 5); /* reset tuner before probing */ rtlsdr_set_gpio_bit(dev, 5, 1); rtlsdr_set_gpio_bit(dev, 5, 0); Looking at one of the early (ugly) patches I wrote trying to get the FC0012 working, This is the original code before cleaned up: https://gist.github.com/dbasden/2171926#file-rtlsdr-fc0012-diff-L123 : + if (tuner_type == TUNER_FC0012) { + dump_rtl_regs(); + DEBUGF("reset fs0012 tuner... "); + /* the fs0012 has it's RESET line hooked up to GPIO5 + * + * If we don't set GPIO5 to an output and leave it floating, + * the tuner never comes up (just stays in RESET state) + * + * GPIO6 controls the V/U band filter, so we should set that + * to an output too + */ + r = rtl_read_reg(SYSB, GPD, 1); + r &= (~(0x30)); rtl_write_reg(SYSB, GPO, r, 1); + r = rtl_read_reg(SYSB, GPOE, 1); + r |= 0x30; rtl_write_reg(SYSB, GPOE, r, 1); + + /* Do reset */ + rtl_set_gpio_bit(5,1); + rtl_set_gpio_bit(5,0); + dump_rtl_regs(); + } This code was eventually abstracted out to The bug in the code above described in http://lea.hamradio.si/~s57uuu/mischam/rtlsdr/ports.html, linked to in the commit log of the ba64a7459a43652354990855176a7d8dad5b9d54 fixing the same bug. The code was cleaned up eventually abstracted into rtl_sdr_set_output(), which had the same typo as my original code: I wrote back the GPD register to GPO. This accidentally worked enough to fix the specific case I was wanting (to stop the RESET line of the FC0012 floating), but didn't actually write to the GPIO direction register. This is as far as I've gotten so far. I don't really have a good setup right now for debugging much further right this moment, but I at least know where the problem is. I don't have a copy of the RTL datasheet, or easy access to anything to measure the levels on that line, but assuming that the bugfix is the correct behaviour, and the buggy code is undefined behaviour, it's quite possible that I've managed to get the GPIO5 into a state just enough to get keep the FC0012 out of reset. Looking at some of the forum posts, and checking myself, if the older code runs once, the tuner will be just fine until the dongle loses power. Then as GPIO5 is not set up into a state for the FC0012 to be happy, it will not come up again. This seems to have made the problem harder to debug (I don't know if I would have not known where to look if I had not already hit the problem when originally porting the driver) Flipping the gpio bits the other direction is not working, so I'm going to have to look at it further, but I figured I should at least post to say where the bug was even if I haven't gotten a solution yet. If I'm missing anything obvious there, please let me know Thanks, David (commit for bugfix where fc0012 tuner regressed.) commit ba64a7459a43652354990855176a7d8dad5b9d54 Author: Lucas Teske Date: Wed Aug 17 20:31:33 2016 -0300 lib: fix direction bit in GPIO code source: http://lea.hamradio.si/~s57uuu/mischam/rtlsdr/ports.html * Removed unnecessary comment of old code. Signed-off-by: Fabian P. Schmidt Signed-off-by: Steve Markgraf :040000 040000 12c5ea73db04c45699d7befa2c5916ae074a1999 2d08166fe31338f5ff90186272b40bc6ed3254fa M src (HEAD) davidb at grey:~/Personal/sdr/rtl-sdr$ -------------- next part -------------- An HTML attachment was scrubbed... URL: From shigawire at gmail.com Sun Feb 4 12:06:54 2018 From: shigawire at gmail.com (David Basden) Date: Sun, 4 Feb 2018 23:06:54 +1100 Subject: Fix for FC0012 Message-ID: Hi, I think my messages are getting filtered, but on the chance that someone is reading these as unmoderated, the fix for the FC0012 is to set GPIO 4 to 0. It could be that GPIO4 is the RST, or that it is something else entirely. Looking through the code, and the state of the registers on startup, one of the surprisingly few side effects of writing GPD to GPO would be to set GPIO4 to 0. Multiple bugs working together made the whole thing work. David -------------- next part -------------- An HTML attachment was scrubbed... URL: From zub at linux.fjfi.cvut.cz Mon Feb 5 12:41:46 2018 From: zub at linux.fjfi.cvut.cz (David Kozub) Date: Mon, 5 Feb 2018 13:41:46 +0100 (CET) Subject: [PATCH] RTL-SDR: Fix regression in FC00012 tuner startup (needs testing on FC2580) In-Reply-To: References: Message-ID: On Sun, 4 Feb 2018, David Basden wrote: > This patch fixes a regression of rtl-sdr dongles with the FC00012 tuner.? ?? > > The code to switch on the FC00012 tuner by pulling it's ~RESET line > low ended up with an off-by-one error in a refactor a long time ago, > > The FC00012 only continued to work by accident as a different bug in > rtlsdr_set_gpio_output had a side-effect of pulling low the FC00012's > ~RESET line > > When the rtl_set_gpio_output bug was fixed in ba64a7459a43 the side- > effect also went away, leaving the FC00012 tuner in reset, and failing > to be detected (or indeed work at all). > > This patch fixes the original off-by-one error.? It's been tested > on an FC00012 in a GTek T803 from both warm and cold starts, but needs testing on the?FC2580 as both the FC00012 and FC2580 are brought out of reset by a GPIO off the RTL (at least in some > designs). If the FC2580 actually uses a different pin, this code will also break.? (I'm only going from vague memory that the FC2580 used the same pin.) Hi David, I ran into the same issue and already submitted a patch http://lists.osmocom.org/pipermail/osmocom-sdr/2017-November/001630.html but it seems nobody was interested in integrating it. I wasn't the first to run into this either: http://lists.osmocom.org/pipermail/osmocom-sdr/2017-October/001617.html I see we reached the same conclusions and the patches are also very similar. The one difference is that you also do: > @@ -1581,7 +1581,7 @@ int rtlsdr_open(rtlsdr_dev_t **out_dev, uint32_t index) > ? reg = rtlsdr_i2c_read_reg(dev, FC0012_I2C_ADDR, FC0012_CHECK_ADDR); > ? if (reg == FC0012_CHECK_VAL) { > ? fprintf(stderr, "Found Fitipower FC0012 tuner\n"); > - rtlsdr_set_gpio_output(dev, 6); > + rtlsdr_set_gpio_output(dev, 5); > ? dev->tuner_type = RTLSDR_TUNER_FC0012; > ? goto found; > ? } What is the purpose of this change? I see GPIO 6 is used in fc0012_set_freq(): int fc0012_set_freq(void *dev, uint32_t freq) { /* select V-band/U-band filter */ rtlsdr_set_gpio_bit(dev, 6, (freq > 300000000) ? 1 : 0); return fc0012_set_params(dev, freq, 6000000); } so I think we need to set GPIO 6 to output mode. Best regards, David From cinaed.simson at gmail.com Wed Feb 7 04:53:54 2018 From: cinaed.simson at gmail.com (Cinaed Simson) Date: Tue, 6 Feb 2018 20:53:54 -0800 Subject: rtl-sdr with bias T Message-ID: Hi - there appears to be problem with rtl-sdr with bias T and the RTL2832U OEM Rafael Micro R820T tuner. I get PLL errors when I try to calibrate it using kal - or when I use rtl_test. Using device 0: Generic RTL2832U OEM Found Rafael Micro R820T tuner Exact sample rate is: 270833.002142 Hz [R82XX] PLL not locked! Setting gain: 33.8 dB And I get the same error PLL when I run rtl_test. If I back out of the latest git version to v0.5.3 it works correctly - I can calibrate the dongle using kal and run rtl_test without any PLL errors. This is a known problem? -- Cinaed From chibill110 at gmail.com Wed Feb 7 08:25:34 2018 From: chibill110 at gmail.com (Bill Gaylord) Date: Wed, 7 Feb 2018 02:25:34 -0600 Subject: rtl-sdr with bias T In-Reply-To: References: Message-ID: This has to do with the ordering of the set bandwidth function I believe. (From my experience with it.) because you can set the bandwidth before the freq is set the chip thinks you are trying to do stuff before the PLL is locked. Which is actually true. But this has no real effect on the usage of device from what I can see. > On Feb 6, 2018, at 10:53 PM, Cinaed Simson wrote: > > > Hi - there appears to be problem with rtl-sdr with bias T and the > RTL2832U OEM Rafael Micro R820T tuner. > > I get PLL errors when I try to calibrate it using kal - or when I use > rtl_test. > > Using device 0: Generic RTL2832U OEM > Found Rafael Micro R820T tuner > Exact sample rate is: 270833.002142 Hz > [R82XX] PLL not locked! > Setting gain: 33.8 dB > > And I get the same error PLL when I run rtl_test. > > If I back out of the latest git version to v0.5.3 it works correctly - I > can calibrate the dongle using kal and run rtl_test without any PLL errors. > > This is a known problem? > > -- Cinaed > > From daleranta at icloud.com Wed Feb 7 15:29:56 2018 From: daleranta at icloud.com (dale ranta) Date: Wed, 07 Feb 2018 07:29:56 -0800 Subject: osmosdr::device::find fails to find Rfspace NetSDR on MacOS Sierra Message-ID: <0B420C6F-DFCA-46A4-ABEE-EB3FFDECECA6@icloud.com> When I updated to MacOS Sierra osmosdr::device::find stopped finding my NetSDR. This short program shows the problem - ************************************************ fprintf(stderr,"I am here\n"); osmosdr::devices_t devs = osmosdr::device::find(); for(int k=0;k When I updated to MacOS Sierra osmosdr::device::find stopped finding my NetSDR. This short program shows the problem - ************************************************ fprintf(stderr,"I am here\n"); osmosdr::devices_t devs = osmosdr::device::find(); for(int k=0;k References: Message-ID: On 02/07/2018 12:25 AM, Bill Gaylord wrote: > This has to do with the ordering of the set bandwidth function I believe. (From my experience with it.) because you can set the bandwidth before the freq is set the chip thinks you are trying to do stuff before the PLL is locked. Which is actually true. But this has no real effect on the usage of device from what I can see. Thanks for the reply. In my case, when I use version v0.5git, I can only located 2 independent GSM channels. But when I use v0.5.3, I can locate 13 independent GSM channels. I'm using a R820T from noelec https://www.nooelec.com/store/nesdr-smart-sdr.html I'll make sure I can reproduce the problem on Friday. -- Cinaed > > >> On Feb 6, 2018, at 10:53 PM, Cinaed Simson wrote: >> >> >> Hi - there appears to be problem with rtl-sdr with bias T and the >> RTL2832U OEM Rafael Micro R820T tuner. >> >> I get PLL errors when I try to calibrate it using kal - or when I use >> rtl_test. >> >> Using device 0: Generic RTL2832U OEM >> Found Rafael Micro R820T tuner >> Exact sample rate is: 270833.002142 Hz >> [R82XX] PLL not locked! >> Setting gain: 33.8 dB >> >> And I get the same error PLL when I run rtl_test. >> >> If I back out of the latest git version to v0.5.3 it works correctly - I >> can calibrate the dongle using kal and run rtl_test without any PLL errors. >> >> This is a known problem? >> >> -- Cinaed >> >> > From patrick at wirklich.priv.at Fri Feb 9 07:06:47 2018 From: patrick at wirklich.priv.at (Patrick Strasser-Mikhail) Date: Fri, 09 Feb 2018 08:06:47 +0100 Subject: rtl-sdr with bias T In-Reply-To: References: Message-ID: <69A5B0A3-ABAB-4819-A641-935CF30AAAD3@wirklich.priv.at> Hi! With 'git bisect' you can possibly track this down to a single commit. BR Patrick Am 8. Februar 2018 10:36:26 MEZ schrieb Cinaed Simson : >On 02/07/2018 12:25 AM, Bill Gaylord wrote: >> This has to do with the ordering of the set bandwidth function I >believe. (From my experience with it.) because you can set the >bandwidth before the freq is set the chip thinks you are trying to do >stuff before the PLL is locked. Which is actually true. But this has no >real effect on the usage of device from what I can see. > >Thanks for the reply. > >In my case, when I use version v0.5git, I can only located 2 >independent >GSM channels. > >But when I use v0.5.3, I can locate 13 independent GSM channels. > >I'm using a R820T from noelec > > https://www.nooelec.com/store/nesdr-smart-sdr.html > >I'll make sure I can reproduce the problem on Friday. > > >-- Cinaed > > >> >> >>> On Feb 6, 2018, at 10:53 PM, Cinaed Simson >wrote: >>> >>> >>> Hi - there appears to be problem with rtl-sdr with bias T and the >>> RTL2832U OEM Rafael Micro R820T tuner. >>> >>> I get PLL errors when I try to calibrate it using kal - or when I >use >>> rtl_test. >>> >>> Using device 0: Generic RTL2832U OEM >>> Found Rafael Micro R820T tuner >>> Exact sample rate is: 270833.002142 Hz >>> [R82XX] PLL not locked! >>> Setting gain: 33.8 dB >>> >>> And I get the same error PLL when I run rtl_test. >>> >>> If I back out of the latest git version to v0.5.3 it works correctly >- I >>> can calibrate the dongle using kal and run rtl_test without any PLL >errors. >>> >>> This is a known problem? >>> >>> -- Cinaed >>> >>> >> -- Diese Nachricht wurde von meinem Android-Ger?t mit K-9 Mail gesendet. -------------- next part -------------- An HTML attachment was scrubbed... URL: From cinaed.simson at gmail.com Tue Feb 13 01:30:54 2018 From: cinaed.simson at gmail.com (Cinaed Simson) Date: Mon, 12 Feb 2018 17:30:54 -0800 Subject: rtl-sdr with bias T In-Reply-To: <69A5B0A3-ABAB-4819-A641-935CF30AAAD3@wirklich.priv.at> References: <69A5B0A3-ABAB-4819-A641-935CF30AAAD3@wirklich.priv.at> Message-ID: <673f3a4e-e16d-392d-df36-ab21db17f14a@GMail.COM> On 02/08/2018 11:06 PM, Patrick Strasser-Mikhail wrote: > Hi! > > With 'git bisect' you can possibly track this down to a single commit. > > BR > > Patrick Great - I was wondering how to do that without checking out every commit since v0.5.3! Thanks. -- Cinaed > > Am 8. Februar 2018 10:36:26 MEZ schrieb Cinaed Simson > : > > On 02/07/2018 12:25 AM, Bill Gaylord wrote: > > This has to do with the ordering of the set bandwidth function I > believe. (From my experience with it.) because you can set the > bandwidth before the freq is set the chip thinks you are trying > to do stuff before the PLL is locked. Which is actually true. > But this has no real effect on the usage of device from what I > can see. > > > Thanks for the reply. > > In my case, when I use version v0.5git, I can only located 2 independent > GSM channels. > > But when I use v0.5.3, I can locate 13 independent GSM channels. > > I'm using a R820T from noelec > > https://www.nooelec.com/store/nesdr-smart-sdr.html > > I'll make sure I can reproduce the problem on Friday. > > > -- Cinaed > > > > > On Feb 6, 2018, at 10:53 PM, Cinaed Simson > wrote: > > > Hi - there appears to be problem with rtl-sdr with bias T > and the > RTL2832U OEM Rafael Micro R820T tuner. > > I get PLL errors when I try to calibrate it using kal - or > when I use > rtl_test. > > Using device 0: Generic RTL2832U OEM > Found Rafael Micro R820T tuner > Exact sample rate is: 270833.002142 Hz > [R82XX] PLL not locked! > Setting gain: 33.8 dB > > And I get the same error PLL when I run rtl_test. > > If I back out of the latest git version to v0.5.3 it works > correctly - I > can calibrate the dongle using kal and run rtl_test without > any PLL errors. > > This is a known problem? > > -- Cinaed > > > > > > > -- > Diese Nachricht wurde von meinem Android-Ger?t mit K-9 Mail gesendet. From lidoro at laposte.net Tue Feb 13 12:06:04 2018 From: lidoro at laposte.net (Lidoro) Date: Tue, 13 Feb 2018 13:06:04 +0100 Subject: RTL-SDR : No supported tuner found Message-ID: <725d910a-76d8-f9ca-6908-d913d63689c6@laposte.net> Hello, I try to install rtl-sdr and gnuradio on my computer, on Linux mint 18.1 cinnamon 3.2.7 64 bits. the installation is going well but when I try to execute rtl_test : Found 1 device(s): 0: Realtek, RTL2838UHIDIR, SN: 00000001 Using device 0: Generic RTL2832U OEM No supported tuner found Enabled direct sampling mode, input 1 Supported gain values (1): 0.0 Sampling at 2048000 S/s. I try intallling with a script and with pybombs, i have the same issue. My dongle have RTL2832 chip and FC0012 tuner it works on windows, and it works when i use "linux live gnuradio dvd". And it works if I start on "linux live gnuradio dvd" and reboot after on linux mint. Thank you for your help Best Regards Lidoro -------------- next part -------------- An HTML attachment was scrubbed... URL: From irsyad2276 at icloud.com Thu Feb 22 06:14:06 2018 From: irsyad2276 at icloud.com (Irsyad Irsyad) Date: Thu, 22 Feb 2018 06:14:06 -0000 Subject: Is this compatible for AIS Receiver ? Message-ID: <085435C8-6F80-46B5-B87F-70E039968678@icloud.com> -------------- next part -------------- A non-text attachment was scrubbed... Name: image1.jpeg Type: image/jpeg Size: 2300753 bytes Desc: not available URL: -------------- next part -------------- -------------- next part -------------- A non-text attachment was scrubbed... Name: image2.jpeg Type: image/jpeg Size: 2219511 bytes Desc: not available URL: -------------- next part -------------- Sent from my iPhone