From a5bdb272402fb012c0a97556cf50a69128e4c573 Mon Sep 17 00:00:00 2001 From: Dimitri Stolnikov Date: Sun, 28 Apr 2013 12:36:03 +0200 Subject: [PATCH] hackrf: add TX support (wip) features: - gain control for AMP & VGA - frequency error correction - automatic baseband filter - up to 20M sampling rate limitations: - no DC offset correction implemented (yet) - high sampling rates may not work on slow machines the following TX named gain stages are available: RF: MGA-81563, switchable 0 or 14dB IF: MAX2837 VGA, 0 to 47dB in 1dB steps --- grc/CMakeLists.txt | 2 +- grc/gen_osmosdr_blocks.py | 12 +- include/osmosdr/CMakeLists.txt | 2 +- include/osmosdr/osmosdr_sink_c.h | 228 +++++++++++ lib/hackrf/CMakeLists.txt | 1 + lib/hackrf/hackrf_sink_c.cc | 666 ++++++++++++++++++++++++++++++- lib/hackrf/hackrf_sink_c.h | 98 ++++- lib/osmosdr_sink_c_impl.cc | 474 +++++++++++++++++++++- lib/osmosdr_sink_c_impl.h | 55 ++- lib/osmosdr_snk_iface.h | 261 ++++++++++++ swig/osmosdr_swig.i | 6 +- 11 files changed, 1763 insertions(+), 42 deletions(-) create mode 100644 lib/osmosdr_snk_iface.h diff --git a/grc/CMakeLists.txt b/grc/CMakeLists.txt index 0f253b5..aa15df3 100644 --- a/grc/CMakeLists.txt +++ b/grc/CMakeLists.txt @@ -34,7 +34,7 @@ endmacro(GEN_BLOCK_XML) GEN_BLOCK_XML(gen_osmosdr_blocks.py rtlsdr_source_c.xml) GEN_BLOCK_XML(gen_osmosdr_blocks.py osmosdr_source_c.xml) -#GEN_BLOCK_XML(gen_osmosdr_blocks.py osmosdr_sink_c.xml) +GEN_BLOCK_XML(gen_osmosdr_blocks.py osmosdr_sink_c.xml) add_custom_target(osmosdr_grc_xml_blocks ALL DEPENDS ${xml_blocks}) diff --git a/grc/gen_osmosdr_blocks.py b/grc/gen_osmosdr_blocks.py index 84d1e15..fe083e3 100644 --- a/grc/gen_osmosdr_blocks.py +++ b/grc/gen_osmosdr_blocks.py @@ -23,7 +23,7 @@ MAIN_TMPL = """\ $(title) $sourk.title() $(prefix)_$(sourk)_c - Sources + $($sourk.title())s 1 import osmosdr osmosdr.$(sourk)_c( args="nchan=" + str(\$nchan) + " " + \$args ) @@ -104,7 +104,7 @@ self.\$(id).set_bandwidth(\$bw$(n), $n) \$nchan -The OsmoSDR $sourk.title() block: +The OSMOCOM block: While primarily being developed for the OsmoSDR hardware, this block as well supports: @@ -131,6 +131,7 @@ Examples: Optional arguments are placed into [] brackets, remove the brackets before using them! Specific variable values are separated with a |, choose one of them. Variable values containing spaces shall be enclosed in '' as demonstrated in examples section below. Lines ending with ... mean it's possible to bind devices together by specifying multiple device arguments separated with a space. +Source Mode: fcd=0 hackrf=0 miri=0[,buffers=32] ... @@ -143,6 +144,9 @@ Lines ending with ... mean it's possible to bind devices together by specifying osmosdr=0[,mcr=64e6][,nchan=5][,buffers=32][,buflen=N*512] ... file='/path/to/your file',rate=1e6[,freq=100e6][,repeat=true][,throttle=true] ... +Sink Mode: + hackrf=0[,buffers=32] + Num Channels: Selects the total number of channels in this multi-device configuration. Required when specifying multiple device arguments. @@ -310,10 +314,10 @@ if __name__ == '__main__': head, tail = os.path.split(file) if tail.startswith('rtlsdr'): - title = 'RTLSDR' + title = 'RTL-SDR' prefix = 'rtlsdr' elif tail.startswith('osmosdr'): - title = 'OsmoSDR' + title = 'OSMOCOM' prefix = 'osmosdr' else: raise Exception, 'file %s has wrong syntax!'%tail diff --git a/include/osmosdr/CMakeLists.txt b/include/osmosdr/CMakeLists.txt index c59bd01..3616384 100644 --- a/include/osmosdr/CMakeLists.txt +++ b/include/osmosdr/CMakeLists.txt @@ -26,6 +26,6 @@ install(FILES osmosdr_ranges.h osmosdr_device.h osmosdr_source_c.h -# osmosdr_sink_c.h + osmosdr_sink_c.h DESTINATION include/osmosdr ) diff --git a/include/osmosdr/osmosdr_sink_c.h b/include/osmosdr/osmosdr_sink_c.h index ebdd489..d76a6ba 100644 --- a/include/osmosdr/osmosdr_sink_c.h +++ b/include/osmosdr/osmosdr_sink_c.h @@ -21,6 +21,7 @@ #define INCLUDED_OSMOSDR_SINK_C_H #include +#include #include class osmosdr_sink_c; @@ -56,7 +57,234 @@ OSMOSDR_API osmosdr_sink_c_sptr osmosdr_make_sink_c ( const std::string & args = class OSMOSDR_API osmosdr_sink_c : virtual public gr_hier_block2 { public: + /*! + * Get the number of channels the underlying radio hardware offers. + * \return the number of available channels + */ + virtual size_t get_num_channels( void ) = 0; + /*! + * Get the possible sample rates for the underlying radio hardware. + * \return a range of rates in Sps + */ + virtual osmosdr::meta_range_t get_sample_rates( void ) = 0; + + /*! + * Set the sample rate for the underlying radio hardware. + * This also will select the appropriate IF bandpass, if applicable. + * \param rate a new rate in Sps + */ + virtual double set_sample_rate( double rate ) = 0; + + /*! + * Get the sample rate for the underlying radio hardware. + * This is the actual sample rate and may differ from the rate set. + * \return the actual rate in Sps + */ + virtual double get_sample_rate( void ) = 0; + + /*! + * Get the tunable frequency range for the underlying radio hardware. + * \param chan the channel index 0 to N-1 + * \return the frequency range in Hz + */ + virtual osmosdr::freq_range_t get_freq_range( size_t chan = 0 ) = 0; + + /*! + * Tune the underlying radio hardware to the desired center frequency. + * This also will select the appropriate RF bandpass. + * \param freq the desired frequency in Hz + * \param chan the channel index 0 to N-1 + * \return the actual frequency in Hz + */ + virtual double set_center_freq( double freq, size_t chan = 0 ) = 0; + + /*! + * Get the center frequency the underlying radio hardware is tuned to. + * This is the actual frequency and may differ from the frequency set. + * \param chan the channel index 0 to N-1 + * \return the frequency in Hz + */ + virtual double get_center_freq( size_t chan = 0 ) = 0; + + /*! + * Set the frequency correction value in parts per million. + * \param ppm the desired correction value in parts per million + * \param chan the channel index 0 to N-1 + * \return correction value in parts per million + */ + virtual double set_freq_corr( double ppm, size_t chan = 0 ) = 0; + + /*! + * Get the frequency correction value. + * \param chan the channel index 0 to N-1 + * \return correction value in parts per million + */ + virtual double get_freq_corr( size_t chan = 0 ) = 0; + + /*! + * Get the gain stage names of the underlying radio hardware. + * \param chan the channel index 0 to N-1 + * \return a vector of strings containing the names of gain stages + */ + virtual std::vector get_gain_names( size_t chan = 0 ) = 0; + + /*! + * Get the settable overall gain range for the underlying radio hardware. + * \param chan the channel index 0 to N-1 + * \return the gain range in dB + */ + virtual osmosdr::gain_range_t get_gain_range( size_t chan = 0 ) = 0; + + /*! + * Get the settable gain range for a specific gain stage. + * \param name the name of the gain stage + * \param chan the channel index 0 to N-1 + * \return the gain range in dB + */ + virtual osmosdr::gain_range_t get_gain_range( const std::string & name, + size_t chan = 0 ) = 0; + + /*! + * Set the gain mode for the underlying radio hardware. + * This might be supported only for certain hardware types. + * \param automatic the gain mode (true means automatic gain mode) + * \param chan the channel index 0 to N-1 + * \return the actual gain mode + */ + virtual bool set_gain_mode( bool automatic, size_t chan = 0 ) = 0; + + /*! + * Get the gain mode selected for the underlying radio hardware. + * \param chan the channel index 0 to N-1 + * \return the actual gain mode (true means automatic gain mode) + */ + virtual bool get_gain_mode( size_t chan = 0 ) = 0; + + /*! + * Set the gain for the underlying radio hardware. + * This function will automatically distribute the desired gain value over + * available gain stages in an appropriate way and return the actual value. + * \param gain the gain in dB + * \param chan the channel index 0 to N-1 + * \return the actual gain in dB + */ + virtual double set_gain( double gain, size_t chan = 0 ) = 0; + + /*! + * Set the named gain on the underlying radio hardware. + * \param gain the gain in dB + * \param name the name of the gain stage + * \param chan the channel index 0 to N-1 + * \return the actual gain in dB + */ + virtual double set_gain( double gain, + const std::string & name, + size_t chan = 0 ) = 0; + + /*! + * Get the actual gain setting of the underlying radio hardware. + * \param chan the channel index 0 to N-1 + * \return the actual gain in dB + */ + virtual double get_gain( size_t chan = 0 ) = 0; + + /*! + * Get the actual gain setting of a named stage. + * \param name the name of the gain stage + * \param chan the channel index 0 to N-1 + * \return the actual gain in dB + */ + virtual double get_gain( const std::string & name, size_t chan = 0 ) = 0; + + /*! + * Set the IF gain for the underlying radio hardware. + * This function will automatically distribute the desired gain value over + * available IF gain stages in an appropriate way and return the actual value. + * \param gain the gain in dB + * \param chan the channel index 0 to N-1 + * \return the actual gain in dB + */ + virtual double set_if_gain( double gain, size_t chan = 0 ) = 0; + + /*! + * Set the BB gain for the underlying radio hardware. + * This function will automatically distribute the desired gain value over + * available BB gain stages in an appropriate way and return the actual value. + * \param gain the gain in dB + * \param chan the channel index 0 to N-1 + * \return the actual gain in dB + */ + virtual double set_bb_gain( double gain, size_t chan = 0 ) = 0; + + /*! + * Get the available antennas of the underlying radio hardware. + * \param chan the channel index 0 to N-1 + * \return a vector of strings containing the names of available antennas + */ + virtual std::vector< std::string > get_antennas( size_t chan = 0 ) = 0; + + /*! + * Select the active antenna of the underlying radio hardware. + * \param antenna name of the antenna to be selected + * \param chan the channel index 0 to N-1 + * \return the actual antenna's name + */ + virtual std::string set_antenna( const std::string & antenna, + size_t chan = 0 ) = 0; + + /*! + * Get the actual underlying radio hardware antenna setting. + * \param chan the channel index 0 to N-1 + * \return the actual antenna's name + */ + virtual std::string get_antenna( size_t chan = 0 ) = 0; + + enum IQBalanceMode { + IQBalanceOff = 0, + IQBalanceManual, + IQBalanceAutomatic + }; + + /*! + * Set the RX frontend IQ balance mode. + * + * \param mode iq balance correction mode: 0 = Off, 1 = Manual, 2 = Automatic + * \param chan the channel index 0 to N-1 + */ + virtual void set_iq_balance_mode( int mode, size_t chan = 0 ) = 0; + + /*! + * Set the RX frontend IQ balance correction. + * Use this to adjust the magnitude and phase of I and Q. + * + * \param correction the complex correction value + * \param chan the channel index 0 to N-1 + */ + virtual void set_iq_balance( const std::complex &correction, + size_t chan = 0 ) = 0; + + /*! + * Set the bandpass filter on the radio frontend. + * \param bandwidth the filter bandwidth in Hz + * \param chan the channel index 0 to N-1 + * \return the actual filter bandwidth in Hz + */ + virtual double set_bandwidth( double bandwidth, size_t chan = 0 ) = 0; + + /*! + * Get the actual bandpass filter setting on the radio frontend. + * \param chan the channel index 0 to N-1 + * \return the actual filter bandwidth in Hz + */ + virtual double get_bandwidth( size_t chan = 0 ) = 0; + + /*! + * Get the possible bandpass filter settings on the radio frontend. + * \param chan the channel index 0 to N-1 + * \return a range of bandwidths in Hz + */ + virtual osmosdr::meta_range_t get_bandwidth_range( size_t chan = 0 ) = 0; }; #endif /* INCLUDED_OSMOSDR_SINK_C_H */ diff --git a/lib/hackrf/CMakeLists.txt b/lib/hackrf/CMakeLists.txt index 7eb7076..31f991b 100644 --- a/lib/hackrf/CMakeLists.txt +++ b/lib/hackrf/CMakeLists.txt @@ -28,6 +28,7 @@ include_directories( set(hackrf_srcs ${CMAKE_CURRENT_SOURCE_DIR}/hackrf_source_c.cc + ${CMAKE_CURRENT_SOURCE_DIR}/hackrf_sink_c.cc ) ######################################################################## diff --git a/lib/hackrf/hackrf_sink_c.cc b/lib/hackrf/hackrf_sink_c.cc index 011000a..58a49cb 100644 --- a/lib/hackrf/hackrf_sink_c.cc +++ b/lib/hackrf/hackrf_sink_c.cc @@ -27,15 +27,91 @@ #include "config.h" #endif -#include "hackrf_sink_c.h" +#include +#include + +#include +#include +#include +#include + #include -/* - * Create a new instance of hackrf_sink_c and return - * a boost shared_ptr. This is effectively the public constructor. - */ -hackrf_sink_c_sptr -make_hackrf_sink_c (const std::string &args) +#include "hackrf_sink_c.h" + +#include "osmosdr_arg_helpers.h" + +using namespace boost::assign; + +#define BUF_LEN (16 * 32 * 512) /* must be multiple of 512 */ +#define BUF_NUM 32 + +#define BYTES_PER_SAMPLE 2 /* HackRF device consumes 8 bit unsigned IQ data */ + +static inline bool cb_init(circular_buffer_t *cb, size_t capacity, size_t sz) +{ + cb->buffer = malloc(capacity * sz); + if(cb->buffer == NULL) + return false; // handle error + cb->buffer_end = (char *)cb->buffer + capacity * sz; + cb->capacity = capacity; + cb->count = 0; + cb->sz = sz; + cb->head = cb->buffer; + cb->tail = cb->buffer; + return true; +} + +static inline void cb_free(circular_buffer_t *cb) +{ + if (cb->buffer) { + free(cb->buffer); + cb->buffer = NULL; + } + // clear out other fields too, just to be safe + cb->buffer_end = 0; + cb->capacity = 0; + cb->count = 0; + cb->sz = 0; + cb->head = 0; + cb->tail = 0; +} + +static inline bool cb_has_room(circular_buffer_t *cb) +{ + if(cb->count == cb->capacity) + return false; + return true; +} + +static inline bool cb_push_back(circular_buffer_t *cb, const void *item) +{ + if(cb->count == cb->capacity) + return false; // handle error + memcpy(cb->head, item, cb->sz); + cb->head = (char *)cb->head + cb->sz; + if(cb->head == cb->buffer_end) + cb->head = cb->buffer; + cb->count++; + return true; +} + +static inline bool cb_pop_front(circular_buffer_t *cb, void *item) +{ + if(cb->count == 0) + return false; // handle error + memcpy(item, cb->tail, cb->sz); + cb->tail = (char *)cb->tail + cb->sz; + if(cb->tail == cb->buffer_end) + cb->tail = cb->buffer; + cb->count--; + return true; +} + +int hackrf_sink_c::_usage = 0; +boost::mutex hackrf_sink_c::_usage_mutex; + +hackrf_sink_c_sptr make_hackrf_sink_c (const std::string & args) { return gnuradio::get_initial_sptr(new hackrf_sink_c (args)); } @@ -47,7 +123,7 @@ make_hackrf_sink_c (const std::string &args) * 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 1 input and 0 output. + * only 0 input and 1 output. */ static const int MIN_IN = 1; // mininum number of input streams static const int MAX_IN = 1; // maximum number of input streams @@ -60,9 +136,83 @@ static const int MAX_OUT = 0; // maximum number of output streams hackrf_sink_c::hackrf_sink_c (const std::string &args) : gr_sync_block ("hackrf_sink_c", gr_make_io_signature (MIN_IN, MAX_IN, sizeof (gr_complex)), - gr_make_io_signature (MIN_OUT, MAX_OUT, sizeof (gr_complex))) + gr_make_io_signature (MIN_OUT, MAX_OUT, sizeof (gr_complex))), + _dev(NULL), + _buf(NULL), + _sample_rate(0), + _center_freq(0), + _freq_corr(0), + _auto_gain(false), + _amp_gain(0), + _vga_gain(0) { + int ret; + uint16_t val; + dict_t dict = params_to_dict(args); + + _buf_num = 0; + + if (dict.count("buffers")) + _buf_num = boost::lexical_cast< unsigned int >( dict["buffers"] ); + + if (0 == _buf_num) + _buf_num = BUF_NUM; + + { + boost::mutex::scoped_lock lock( _usage_mutex ); + + if ( _usage == 0 ) + hackrf_init(); /* call only once before the first open */ + + _usage++; + } + + _dev = NULL; + ret = hackrf_open( &_dev ); + if (ret != HACKRF_SUCCESS) + throw std::runtime_error("Failed to open HackRF device."); + + uint8_t board_id; + ret = hackrf_board_id_read( _dev, &board_id ); + if (ret != HACKRF_SUCCESS) + throw std::runtime_error("Failed to get board id."); + + char version[40]; + memset(version, 0, sizeof(version)); + ret = hackrf_version_string_read( _dev, version, sizeof(version)); + if (ret != HACKRF_SUCCESS) + throw std::runtime_error("Failed to read version string."); +#if 0 + read_partid_serialno_t serial_number; + ret = hackrf_board_partid_serialno_read( _dev, &serial_number ); + if (ret != HACKRF_SUCCESS) + throw std::runtime_error("Failed to read serial number."); +#endif + std::cerr << "Using " << hackrf_board_id_name(hackrf_board_id(board_id)) << " " + << "with firmware " << version << " " + << std::endl; + + if ( BUF_NUM != _buf_num ) { + std::cerr << "Using " << _buf_num << " buffers of size " << BUF_LEN << "." + << std::endl; + } + + set_sample_rate( 5000000 ); + + set_gain( 0 ); /* disable AMP gain stage */ + + hackrf_max2837_read( _dev, 29, &val ); + val |= 0x3; /* enable TX VGA control over SPI */ + hackrf_max2837_write( _dev, 29, val ); + + set_if_gain( 16 ); /* preset to a reasonable default (non-GRC use case) */ + + _buf = (unsigned char *) malloc( BUF_LEN ); + + cb_init( &_cbuf, _buf_num, BUF_LEN ); + +// _thread = gruel::thread(_hackrf_wait, this); } /* @@ -70,5 +220,503 @@ hackrf_sink_c::hackrf_sink_c (const std::string &args) */ hackrf_sink_c::~hackrf_sink_c () { + if (_dev) { +// _thread.join(); + hackrf_close( _dev ); + _dev = NULL; + { + boost::mutex::scoped_lock lock( _usage_mutex ); + + _usage--; + + if ( _usage == 0 ) + hackrf_exit(); /* call only once after last close */ + } + } + + if (_buf) { + free(_buf); + _buf = NULL; + } + + cb_free( &_cbuf ); +} + +int hackrf_sink_c::_hackrf_tx_callback(hackrf_transfer *transfer) +{ + hackrf_sink_c *obj = (hackrf_sink_c *)transfer->tx_ctx; + return obj->hackrf_tx_callback(transfer->buffer, transfer->valid_length); +} + +int hackrf_sink_c::hackrf_tx_callback(unsigned char *buffer, uint32_t length) +{ +#if 0 + for (unsigned int i = 0; i < length; ++i) /* simulate noise */ + *buffer++ = rand() % 255; +#else + { + boost::mutex::scoped_lock lock( _buf_mutex ); + + if ( ! cb_pop_front( &_cbuf, buffer ) ) { + memset(buffer, 0, length); + std::cerr << "U" << std::flush; + } else { +// std::cerr << ":" << std::flush; + _buf_cond.notify_one(); + } + } + +#endif + + return 0; // TODO: return -1 on error/stop +} + +void hackrf_sink_c::_hackrf_wait(hackrf_sink_c *obj) +{ + obj->hackrf_wait(); +} + +void hackrf_sink_c::hackrf_wait() +{ +} + +bool hackrf_sink_c::start() +{ + if ( ! _dev ) + return false; + + _buf_used = 0; + + int ret = hackrf_start_tx( _dev, _hackrf_tx_callback, (void *)this ); + if (ret != HACKRF_SUCCESS) { + std::cerr << "Failed to start TX streaming (" << ret << ")" << std::endl; + return false; + } + + while ( ! hackrf_is_streaming( _dev ) ); + + return (bool) hackrf_is_streaming( _dev ); +} + +bool hackrf_sink_c::stop() +{ + if ( ! _dev ) + return false; + + int ret = hackrf_stop_tx( _dev ); + if (ret != HACKRF_SUCCESS) { + std::cerr << "Failed to stop TX streaming (" << ret << ")" << std::endl; + return false; + } + + while ( hackrf_is_streaming( _dev ) ); + + /* FIXME: hackrf_stop_tx should wait until the device is ready for a start */ + usleep(100000); /* required if we want to immediately start() again */ + + return ! (bool) hackrf_is_streaming( _dev ); +} + +int hackrf_sink_c::work( int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items ) +{ + const gr_complex *in = (const gr_complex *) input_items[0]; + + { + boost::mutex::scoped_lock lock( _buf_mutex ); + + while ( ! cb_has_room(&_cbuf) ) + _buf_cond.wait( lock ); + } + + unsigned char *buf = _buf + _buf_used; + + int items_consumed = 0; + unsigned int prev_buf_used = _buf_used; + + for (int i = 0; i < noutput_items; i++) { + if ( _buf_used + BYTES_PER_SAMPLE > BUF_LEN ) { + { + boost::mutex::scoped_lock lock( _buf_mutex ); + + if ( ! cb_push_back( &_cbuf, _buf ) ) { + _buf_used = prev_buf_used; + items_consumed = 0; + std::cerr << "O" << std::flush; + break; + } else { +// std::cerr << "." << std::flush; + } + } + + _buf_used = 0; + break; + } + + *buf++ = (in[i].real() + 1.0) * 127; + *buf++ = (in[i].imag() + 1.0) * 127; + + _buf_used += BYTES_PER_SAMPLE; + items_consumed++; + } + + noutput_items = items_consumed; + + // Tell runtime system how many input items we consumed on + // each input stream. + consume_each(noutput_items); + + // Tell runtime system how many output items we produced. + return 0; +} + +std::vector hackrf_sink_c::get_devices() +{ + std::vector devices; + std::string label; + + for (unsigned int i = 0; i < 1 /* TODO: missing libhackrf api */; i++) { + std::string args = "hackrf=" + boost::lexical_cast< std::string >( i ); + + label.clear(); + + label = "HackRF Jawbreaker"; /* TODO: missing libhackrf api */ + + boost::algorithm::trim(label); + + args += ",label='" + label + "'"; + devices.push_back( args ); + } + + return devices; +} + +size_t hackrf_sink_c::get_num_channels() +{ + return 1; +} + +osmosdr::meta_range_t hackrf_sink_c::get_sample_rates() +{ + osmosdr::meta_range_t range; + + range += osmosdr::range_t( 5e6 ); /* out of spec but appears to work */ + range += osmosdr::range_t( 10e6 ); + range += osmosdr::range_t( 12.5e6 ); + range += osmosdr::range_t( 16e6 ); + range += osmosdr::range_t( 20e6 ); /* confirmed to work on fast machines */ + + return range; +} + +double hackrf_sink_c::set_sample_rate(double rate) +{ + int ret; + + if (_dev) { + ret = hackrf_sample_rate_set( _dev, uint32_t(rate) ); + if ( HACKRF_SUCCESS == ret ) { + _sample_rate = rate; + set_bandwidth( rate ); + } else { + throw std::runtime_error( std::string( __FUNCTION__ ) + " has failed" ); + } + } + + return get_sample_rate(); +} + +double hackrf_sink_c::get_sample_rate() +{ + return _sample_rate; +} + +osmosdr::freq_range_t hackrf_sink_c::get_freq_range( size_t chan ) +{ + osmosdr::freq_range_t range; + + range += osmosdr::range_t( 30e6, 6e9 ); + + return range; +} + +double hackrf_sink_c::set_center_freq( double freq, size_t chan ) +{ + int ret; + + #define APPLY_PPM_CORR(val, ppm) ((val) * (1.0 + (ppm) * 0.000001)) + + if (_dev) { + double corr_freq = APPLY_PPM_CORR( freq, _freq_corr ); + ret = hackrf_set_freq( _dev, uint64_t(corr_freq) ); + if ( HACKRF_SUCCESS == ret ) { + _center_freq = freq; + } else { + throw std::runtime_error( std::string( __FUNCTION__ ) + " has failed" ); + } + } + + return get_center_freq( chan ); +} + +double hackrf_sink_c::get_center_freq( size_t chan ) +{ + return _center_freq; +} + +double hackrf_sink_c::set_freq_corr( double ppm, size_t chan ) +{ + _freq_corr = ppm; + + set_center_freq( _center_freq ); + + return get_freq_corr( chan ); +} + +double hackrf_sink_c::get_freq_corr( size_t chan ) +{ + return _freq_corr; +} + +std::vector hackrf_sink_c::get_gain_names( size_t chan ) +{ + std::vector< std::string > names; + + names += "RF"; + names += "IF"; + + return names; +} + +osmosdr::gain_range_t hackrf_sink_c::get_gain_range( size_t chan ) +{ + return get_gain_range( "RF", chan ); +} + +osmosdr::gain_range_t hackrf_sink_c::get_gain_range( const std::string & name, size_t chan ) +{ + if ( "RF" == name ) { + return osmosdr::gain_range_t( 0, 14, 14 ); + } + + if ( "IF" == name ) { + return osmosdr::gain_range_t( 0, 47, 1 ); + } + + return osmosdr::gain_range_t(); +} + +bool hackrf_sink_c::set_gain_mode( bool automatic, size_t chan ) +{ + _auto_gain = automatic; + + return get_gain_mode(chan); +} + +bool hackrf_sink_c::get_gain_mode( size_t chan ) +{ + return _auto_gain; +} + +double hackrf_sink_c::set_gain( double gain, size_t chan ) +{ + osmosdr::gain_range_t rf_gains = get_gain_range( "RF", chan ); + + if (_dev) { + double clip_gain = rf_gains.clip( gain, true ); + + std::map reg_vals; + reg_vals[ 0 ] = 0; + reg_vals[ 14 ] = 1; + + if ( reg_vals.count( clip_gain ) ) { + int value = reg_vals[ clip_gain ]; +#if 0 + std::cerr << "amp gain: " << gain + << " clip_gain: " << clip_gain + << " value: " << value + << std::endl; +#endif + if ( hackrf_set_amp_enable( _dev, value ) == HACKRF_SUCCESS ) + _amp_gain = clip_gain; + } + } + + return _amp_gain; +} + +double hackrf_sink_c::set_gain( double gain, const std::string & name, size_t chan) +{ + if ( "RF" == name ) { + return set_gain( gain, chan ); + } + + if ( "IF" == name ) { + return set_if_gain( gain, chan ); + } + + return set_gain( gain, chan ); +} + +double hackrf_sink_c::get_gain( size_t chan ) +{ + return _amp_gain; +} + +double hackrf_sink_c::get_gain( const std::string & name, size_t chan ) +{ + if ( "RF" == name ) { + return get_gain( chan ); + } + + if ( "IF" == name ) { + return _vga_gain; + } + + return get_gain( chan ); +} + +double hackrf_sink_c::set_if_gain( double gain, size_t chan ) +{ + osmosdr::gain_range_t if_gains = get_gain_range( "IF", chan ); + + double clip_gain = if_gains.clip( gain, true ); + double rel_atten = fabs( if_gains.stop() - clip_gain ); + + std::vector< osmosdr::gain_range_t > if_attens; + + if_attens += osmosdr::gain_range_t(0, 1, 1); /* chapter 1.5: TX Gain Control */ + if_attens += osmosdr::gain_range_t(0, 2, 2); + if_attens += osmosdr::gain_range_t(0, 4, 4); + if_attens += osmosdr::gain_range_t(0, 8, 8); + if_attens += osmosdr::gain_range_t(0, 16, 16); + if_attens += osmosdr::gain_range_t(0, 16, 16); + + std::map< int, double > attens; + + /* initialize with min attens */ + for (unsigned int i = 0; i < if_attens.size(); i++) { + attens[ i + 1 ] = if_attens[ i ].start(); + } + + double atten = rel_atten; + + for (int i = if_attens.size() - 1; i >= 0; i--) { + osmosdr::gain_range_t range = if_attens[ i ]; + + if ( atten - range.stop() >= 0 ) { + atten -= range.stop(); + attens[ i + 1 ] = range.stop(); + } + } +#if 0 + std::cerr << rel_atten << " => "; double sum = 0; + for (unsigned int i = 0; i < attens.size(); i++) { + sum += attens[ i + 1 ]; + std::cerr << attens[ i + 1 ] << " "; + } + std::cerr << " = " << sum << std::endl; +#endif + if (_dev) { + int value = 0; + for (unsigned int stage = 1; stage <= attens.size(); stage++) { + if ( attens[ stage ] != 0 ) + value |= 1 << (stage - 1); + } +#if 0 + std::cerr << "vga gain: " << gain + << " clip_gain: " << clip_gain + << " rel_atten: " << rel_atten + << " value: " << value + << std::endl; +#endif + uint16_t val; + hackrf_max2837_read( _dev, 29, &val ); + + val = (val & 0xf) | ((value & 0x3f) << 4); + + if ( hackrf_max2837_write( _dev, 29, val ) == HACKRF_SUCCESS ) + _vga_gain = clip_gain; + } + + return _vga_gain; +} + +double hackrf_sink_c::set_bb_gain(double gain, size_t chan) +{ + return 0; +} + +std::vector< std::string > hackrf_sink_c::get_antennas( size_t chan ) +{ + std::vector< std::string > antennas; + + antennas += get_antenna( chan ); + + return antennas; +} + +std::string hackrf_sink_c::set_antenna( const std::string & antenna, size_t chan ) +{ + return get_antenna( chan ); +} + +std::string hackrf_sink_c::get_antenna( size_t chan ) +{ + return "ANT"; +} + +double hackrf_sink_c::set_bandwidth( double bandwidth, size_t chan ) +{ + int ret; +// osmosdr::meta_range_t bandwidths = get_bandwidth_range( chan ); + + if ( bandwidth == 0.0 ) /* bandwidth of 0 means automatic filter selection */ + bandwidth = _sample_rate; + + if ( _dev ) { + /* compute best default value depending on sample rate (auto filter) */ + uint32_t bw = hackrf_compute_baseband_filter_bw( uint32_t(bandwidth) ); + ret = hackrf_baseband_filter_bandwidth_set( _dev, bw ); + if ( HACKRF_SUCCESS == ret ) { + _bandwidth = bw; + } else { + throw std::runtime_error( std::string( __FUNCTION__ ) + " has failed" ); + } + } + + return _bandwidth; +} + +double hackrf_sink_c::get_bandwidth( size_t chan ) +{ + return _bandwidth; +} + +osmosdr::meta_range_t hackrf_sink_c::get_bandwidth_range( size_t chan ) +{ + osmosdr::meta_range_t bandwidths; + + // TODO: read out from libhackrf when an API is available + + bandwidths += osmosdr::range_t( 1750000 ); + bandwidths += osmosdr::range_t( 2500000 ); + bandwidths += osmosdr::range_t( 3500000 ); + bandwidths += osmosdr::range_t( 5000000 ); + bandwidths += osmosdr::range_t( 5500000 ); + bandwidths += osmosdr::range_t( 6000000 ); + bandwidths += osmosdr::range_t( 7000000 ); + bandwidths += osmosdr::range_t( 8000000 ); + bandwidths += osmosdr::range_t( 9000000 ); + bandwidths += osmosdr::range_t( 10000000 ); + bandwidths += osmosdr::range_t( 12000000 ); + bandwidths += osmosdr::range_t( 14000000 ); + bandwidths += osmosdr::range_t( 15000000 ); + bandwidths += osmosdr::range_t( 20000000 ); + bandwidths += osmosdr::range_t( 24000000 ); + bandwidths += osmosdr::range_t( 28000000 ); + + return bandwidths; } diff --git a/lib/hackrf/hackrf_sink_c.h b/lib/hackrf/hackrf_sink_c.h index a0a045e..4c0ca5f 100644 --- a/lib/hackrf/hackrf_sink_c.h +++ b/lib/hackrf/hackrf_sink_c.h @@ -20,10 +20,29 @@ #ifndef INCLUDED_HACKRF_SINK_C_H #define INCLUDED_HACKRF_SINK_C_H -#include +#include +#include + +#include +#include + +#include + +#include "osmosdr_snk_iface.h" class hackrf_sink_c; +typedef struct circular_buffer +{ + void *buffer; // data buffer + void *buffer_end; // end of data buffer + size_t capacity; // maximum number of items in the buffer + size_t count; // number of items in the buffer + size_t sz; // size of each item in the buffer + void *head; // pointer to head + void *tail; // pointer to tail +} circular_buffer_t; + /* * We use boost::shared_ptr's instead of raw pointers for all access * to gr_blocks (and many other data structures). The shared_ptr gets @@ -46,7 +65,9 @@ typedef boost::shared_ptr hackrf_sink_c_sptr; */ hackrf_sink_c_sptr make_hackrf_sink_c (const std::string & args = ""); -class hackrf_sink_c +class hackrf_sink_c : + public gr_sync_block, + public osmosdr_snk_iface { private: // The friend declaration allows hackrf_make_sink_c to @@ -56,7 +77,78 @@ private: hackrf_sink_c (const std::string & args); // private constructor public: - ~hackrf_source_c (); // public destructor + ~hackrf_sink_c (); // public destructor + + 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 ); + bool set_gain_mode( bool automatic, size_t chan = 0 ); + bool get_gain_mode( 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 ); + + double set_if_gain( double gain, size_t chan = 0 ); + double set_bb_gain( double gain, 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 ); + + double set_bandwidth( double bandwidth, size_t chan = 0 ); + double get_bandwidth( size_t chan = 0 ); + osmosdr::meta_range_t get_bandwidth_range( size_t chan = 0 ); + +private: + static int _hackrf_tx_callback(hackrf_transfer* transfer); + int hackrf_tx_callback(unsigned char *buffer, uint32_t length); + static void _hackrf_wait(hackrf_sink_c *obj); + void hackrf_wait(); + + static int _usage; + static boost::mutex _usage_mutex; + + std::vector _lut; + + hackrf_device *_dev; +// gruel::thread _thread; + + circular_buffer_t _cbuf; + unsigned char *_buf; + unsigned int _buf_num; + unsigned int _buf_used; + boost::mutex _buf_mutex; + boost::condition_variable _buf_cond; + + double _sample_rate; + double _center_freq; + double _freq_corr; + bool _auto_gain; + double _amp_gain; + double _vga_gain; + double _bandwidth; }; #endif /* INCLUDED_HACKRF_SINK_C_H */ diff --git a/lib/osmosdr_sink_c_impl.cc b/lib/osmosdr_sink_c_impl.cc index dce2f08..6152069 100644 --- a/lib/osmosdr_sink_c_impl.cc +++ b/lib/osmosdr_sink_c_impl.cc @@ -1,6 +1,6 @@ /* -*- c++ -*- */ /* - * Copyright 2012 Dimitri Stolnikov + * Copyright 2013 Dimitri Stolnikov * * 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 @@ -27,8 +27,22 @@ #include "config.h" #endif -#include -#include +#include +#include +#include +#include + +#include "osmosdr_sink_c_impl.h" + +#ifdef ENABLE_HACKRF +#include "hackrf_sink_c.h" +#endif + +#include "osmosdr_arg_helpers.h" + +/* This avoids throws in ctor of gr_hier_block2, as gnuradio is unable to deal + with this behavior in a clean way. The GR maintainer Rondeau has been informed. */ +#define WORKAROUND_GR_HIER_BLOCK2_BUG /* * Create a new instance of osmosdr_sink_c_impl and return @@ -40,27 +54,449 @@ osmosdr_make_sink_c (const std::string &args) return gnuradio::get_initial_sptr(new osmosdr_sink_c_impl (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 1 input and 0 output. - */ -static const int MIN_IN = 1; // mininum number of input streams -static const int MAX_IN = 1; // maximum number of input streams -static const int MIN_OUT = 0; // minimum number of output streams -static const int MAX_OUT = 0; // maximum number of output streams - /* * The private constructor */ osmosdr_sink_c_impl::osmosdr_sink_c_impl (const std::string &args) : gr_hier_block2 ("osmosdr_sink_c_impl", - gr_make_io_signature (MIN_IN, MAX_IN, sizeof (gr_complex)), - gr_make_io_signature (MIN_OUT, MAX_OUT, sizeof (gr_complex))) + args_to_io_signature(args), + gr_make_io_signature (0, 0, 0)) +{ + size_t channel = 0; + bool device_specified = false; + + std::vector< std::string > arg_list = args_to_vector(args); + + std::vector< std::string > dev_types; + +#ifdef ENABLE_HACKRF + dev_types.push_back("hackrf"); +#endif + + std::cerr << "gr-osmosdr " + << GR_OSMOSDR_VERSION " (" GR_OSMOSDR_LIBVER ") " + << "gnuradio " << gr_version() << std::endl; + std::cerr << "built-in device types: "; + BOOST_FOREACH(std::string dev_type, dev_types) + std::cerr << dev_type << " "; + std::cerr << std::endl << std::flush; + + BOOST_FOREACH(std::string arg, arg_list) { + dict_t dict = params_to_dict(arg); + BOOST_FOREACH(std::string dev_type, dev_types) { + if ( dict.count( dev_type ) ) { + device_specified = true; + break; + } + } + } +#ifdef WORKAROUND_GR_HIER_BLOCK2_BUG + try { +#endif + std::vector< std::string > dev_list; +#ifdef ENABLE_HACKRF + BOOST_FOREACH( std::string dev, hackrf_sink_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; + + if (!device_specified) { + if ( dev_list.size() ) + arg_list.push_back( dev_list.front() ); + else + throw std::runtime_error("No supported devices found to pick from."); + } + + BOOST_FOREACH(std::string arg, arg_list) { + + dict_t dict = params_to_dict(arg); + +// std::cerr << std::endl; +// BOOST_FOREACH( dict_t::value_type &entry, dict ) +// std::cerr << "'" << entry.first << "' = '" << entry.second << "'" << std::endl; + + osmosdr_snk_iface *iface = NULL; + gr_basic_block_sptr block; + +#ifdef ENABLE_HACKRF + if ( dict.count("hackrf") ) { + hackrf_sink_c_sptr src = make_hackrf_sink_c( arg ); + block = src; iface = src.get(); + } +#endif + + if ( iface != NULL && long(block.get()) != 0 ) { + _devs.push_back( iface ); + + for (size_t i = 0; i < iface->get_num_channels(); i++) { + connect(self(), channel++, block, i); + } + } else if ( (iface != NULL) || (long(block.get()) != 0) ) + throw std::runtime_error("Eitner iface or block are NULL."); + + } + + if (!_devs.size()) + throw std::runtime_error("No devices specified via device arguments."); +#ifdef WORKAROUND_GR_HIER_BLOCK2_BUG + } catch ( std::exception &ex ) { + std::cerr << std::endl << "FATAL: " << ex.what() << std::endl << std::endl; + + size_t missing_chans = output_signature()->max_streams() - channel; + + std::cerr << "Trying to fill up " << missing_chans + << " missing channel(s) with null sinks.\n" + << "This is being done to prevent the application from crashing\n" + << "due to a gnuradio bug. The maintainers have been informed.\n" + << std::endl; + + for (size_t i = 0; i < missing_chans; i++) { + /* we try to prevent the whole application from crashing by faking + * the missing hardware (channels) with a null sink */ + gr_null_sink_sptr null_sink = gr_make_null_sink( sizeof(gr_complex) ); + + gr_throttle::sptr throttle = gr_make_throttle(sizeof(gr_complex), 1e6); + + connect(self(), channel++, throttle, 0); + connect(throttle, 0, null_sink, 0); + } + } +#endif +} + +size_t osmosdr_sink_c_impl::get_num_channels() +{ + size_t channels = 0; + + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + channels += dev->get_num_channels(); + + return channels; +} + +#define NO_DEVICES_MSG "FATAL: No device(s) available to work with." + +osmosdr::meta_range_t osmosdr_sink_c_impl::get_sample_rates() +{ + osmosdr::meta_range_t rates(0, 0, 0); + + if (!_devs.empty()) + rates = _devs[0]->get_sample_rates(); // assume same devices used in the group +#if 0 + else + throw std::runtime_error(NO_DEVICES_MSG); +#endif + return rates; +} + +double osmosdr_sink_c_impl::set_sample_rate(double rate) +{ + double sample_rate = 0; + + if (_sample_rate != rate) { +#if 0 + if (_devs.empty()) + throw std::runtime_error(NO_DEVICES_MSG); +#endif + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + sample_rate = dev->set_sample_rate(rate); + + _sample_rate = sample_rate; + } + + return sample_rate; +} + +double osmosdr_sink_c_impl::get_sample_rate() +{ + double sample_rate = 0; + + if (!_devs.empty()) + sample_rate = _devs[0]->get_sample_rate(); // assume same devices used in the group +#if 0 + else + throw std::runtime_error(NO_DEVICES_MSG); +#endif + return sample_rate; +} + +osmosdr::freq_range_t osmosdr_sink_c_impl::get_freq_range( size_t chan ) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + return dev->get_freq_range( dev_chan ); + + return osmosdr::freq_range_t(); +} + +double osmosdr_sink_c_impl::set_center_freq( double freq, size_t chan ) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + if ( _center_freq[ chan ] != freq ) { + _center_freq[ chan ] = freq; + return dev->set_center_freq( freq, dev_chan ); + } + + return 0; +} + +double osmosdr_sink_c_impl::get_center_freq( size_t chan ) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + return dev->get_center_freq( dev_chan ); + + return 0; +} + +double osmosdr_sink_c_impl::set_freq_corr( double ppm, size_t chan ) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + if ( _freq_corr[ chan ] != ppm ) { + _freq_corr[ chan ] = ppm; + return dev->set_freq_corr( ppm, dev_chan ); + } + + return 0; +} + +double osmosdr_sink_c_impl::get_freq_corr( size_t chan ) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + return dev->get_freq_corr( dev_chan ); + + return 0; +} + +std::vector osmosdr_sink_c_impl::get_gain_names( size_t chan ) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + return dev->get_gain_names( dev_chan ); + + return std::vector< std::string >(); +} + +osmosdr::gain_range_t osmosdr_sink_c_impl::get_gain_range( size_t chan ) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + return dev->get_gain_range( dev_chan ); + + return osmosdr::gain_range_t(); +} + +osmosdr::gain_range_t osmosdr_sink_c_impl::get_gain_range( const std::string & name, size_t chan ) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + return dev->get_gain_range( name, dev_chan ); + + return osmosdr::gain_range_t(); +} + +bool osmosdr_sink_c_impl::set_gain_mode( bool automatic, size_t chan ) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + if ( _gain_mode[ chan ] != automatic ) { + _gain_mode[ chan ] = automatic; + bool mode = dev->set_gain_mode( automatic, dev_chan ); + if (!automatic) // reapply gain value when switched to manual mode + dev->set_gain( _gain[ chan ], dev_chan ); + return mode; + } + + return false; +} + +bool osmosdr_sink_c_impl::get_gain_mode( size_t chan ) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + return dev->get_gain_mode( dev_chan ); + + return false; +} + +double osmosdr_sink_c_impl::set_gain( double gain, size_t chan ) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + if ( _gain[ chan ] != gain ) { + _gain[ chan ] = gain; + return dev->set_gain( gain, dev_chan ); + } + + return 0; +} + +double osmosdr_sink_c_impl::set_gain( double gain, const std::string & name, size_t chan) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + return dev->set_gain( gain, name, dev_chan ); + + return 0; +} + +double osmosdr_sink_c_impl::get_gain( size_t chan ) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + return dev->get_gain( dev_chan ); + + return 0; +} + +double osmosdr_sink_c_impl::get_gain( const std::string & name, size_t chan ) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + return dev->get_gain( name, dev_chan ); + + return 0; +} + +double osmosdr_sink_c_impl::set_if_gain( double gain, size_t chan ) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + if ( _if_gain[ chan ] != gain ) { + _if_gain[ chan ] = gain; + return dev->set_if_gain( gain, dev_chan ); + } + + return 0; +} + +double osmosdr_sink_c_impl::set_bb_gain( double gain, size_t chan ) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + if ( _bb_gain[ chan ] != gain ) { + _bb_gain[ chan ] = gain; + return dev->set_bb_gain( gain, dev_chan ); + } + + return 0; +} + +std::vector< std::string > osmosdr_sink_c_impl::get_antennas( size_t chan ) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + return dev->get_antennas( dev_chan ); + + return std::vector< std::string >(); +} + +std::string osmosdr_sink_c_impl::set_antenna( const std::string & antenna, size_t chan ) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + if ( _antenna[ chan ] != antenna ) { + _antenna[ chan ] = antenna; + return dev->set_antenna( antenna, dev_chan ); + } + + return ""; +} + +std::string osmosdr_sink_c_impl::get_antenna( size_t chan ) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + return dev->get_antenna( dev_chan ); + + return ""; +} + +void osmosdr_sink_c_impl::set_iq_balance_mode( int mode, size_t chan ) { } + +void osmosdr_sink_c_impl::set_iq_balance( const std::complex &correction, size_t chan ) +{ + +} + +double osmosdr_sink_c_impl::set_bandwidth( double bandwidth, size_t chan ) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + if ( _bandwidth[ chan ] != bandwidth ) { + _bandwidth[ chan ] = bandwidth; + return dev->set_bandwidth( bandwidth, dev_chan ); + } + + return 0; +} + +double osmosdr_sink_c_impl::get_bandwidth( size_t chan ) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + return dev->get_bandwidth( dev_chan ); + + return 0; +} + +osmosdr::meta_range_t osmosdr_sink_c_impl::get_bandwidth_range( size_t chan ) +{ + size_t channel = 0; + BOOST_FOREACH( osmosdr_snk_iface *dev, _devs ) + for (size_t dev_chan = 0; dev_chan < dev->get_num_channels(); dev_chan++) + if ( chan == channel++ ) + return dev->get_bandwidth_range( dev_chan ); + + return osmosdr::meta_range_t(); +} diff --git a/lib/osmosdr_sink_c_impl.h b/lib/osmosdr_sink_c_impl.h index 49457d0..9194168 100644 --- a/lib/osmosdr_sink_c_impl.h +++ b/lib/osmosdr_sink_c_impl.h @@ -1,6 +1,6 @@ /* -*- c++ -*- */ /* - * Copyright 2012 Dimitri Stolnikov + * Copyright 2013 Dimitri Stolnikov * * 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 @@ -20,11 +20,50 @@ #ifndef INCLUDED_OSMOSDR_SINK_C_IMPL_H #define INCLUDED_OSMOSDR_SINK_C_IMPL_H -#include +#include "osmosdr/osmosdr_sink_c.h" + +#include "osmosdr_snk_iface.h" + +#include class osmosdr_sink_c_impl : public osmosdr_sink_c { public: + 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 ); + bool set_gain_mode( bool automatic, size_t chan = 0 ); + bool get_gain_mode( 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 ); + + double set_if_gain( double gain, size_t chan = 0 ); + double set_bb_gain( double gain, 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 ); + + void set_iq_balance_mode( int mode, size_t chan = 0 ); + void set_iq_balance( const std::complex &correction, size_t chan = 0 ); + + double set_bandwidth( double bandwidth, size_t chan = 0 ); + double get_bandwidth( size_t chan = 0 ); + osmosdr::meta_range_t get_bandwidth_range( size_t chan = 0 ); private: osmosdr_sink_c_impl (const std::string & args); // private constructor @@ -32,6 +71,18 @@ private: // The friend declaration allows osmosdr_make_sink_c to // access the private constructor. friend osmosdr_sink_c_sptr osmosdr_make_sink_c (const std::string & args); + + std::vector< osmosdr_snk_iface * > _devs; + + double _sample_rate; + std::map< size_t, double > _center_freq; + std::map< size_t, double > _freq_corr; + std::map< size_t, bool > _gain_mode; + std::map< size_t, double > _gain; + std::map< size_t, double > _if_gain; + std::map< size_t, double > _bb_gain; + std::map< size_t, std::string > _antenna; + std::map< size_t, double > _bandwidth; }; #endif /* INCLUDED_OSMOSDR_SINK_C_IMPL_H */ diff --git a/lib/osmosdr_snk_iface.h b/lib/osmosdr_snk_iface.h new file mode 100644 index 0000000..4a45529 --- /dev/null +++ b/lib/osmosdr_snk_iface.h @@ -0,0 +1,261 @@ +/* -*- c++ -*- */ +/* + * Copyright 2012 Dimitri Stolnikov + * + * 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 OSMOSDR_SNK_IFACE_H +#define OSMOSDR_SNK_IFACE_H + +#include +#include + +class osmosdr_snk_iface; + +typedef boost::shared_ptr osmosdr_snk_iface_sptr; + +/*! + * TODO: document + * + */ +class osmosdr_snk_iface +{ +public: + /*! + * Get the number of channels the underlying radio hardware offers. + * \return the number of available channels + */ + virtual size_t get_num_channels( void ) = 0; + + /*! + * Get the possible sample rates for the underlying radio hardware. + * \return a range of rates in Sps + */ + virtual osmosdr::meta_range_t get_sample_rates( void ) = 0; + + /*! + * Set the sample rate for the underlying radio hardware. + * This also will select the appropriate IF bandpass, if applicable. + * \param rate a new rate in Sps + */ + virtual double set_sample_rate( double rate ) = 0; + + /*! + * Get the sample rate for the underlying radio hardware. + * This is the actual sample rate and may differ from the rate set. + * \return the actual rate in Sps + */ + virtual double get_sample_rate( void ) = 0; + + /*! + * Get the tunable frequency range for the underlying radio hardware. + * \param chan the channel index 0 to N-1 + * \return the frequency range in Hz + */ + virtual osmosdr::freq_range_t get_freq_range( size_t chan = 0 ) = 0; + + /*! + * Tune the underlying radio hardware to the desired center frequency. + * This also will select the appropriate RF bandpass. + * \param freq the desired frequency in Hz + * \param chan the channel index 0 to N-1 + * \return the actual frequency in Hz + */ + virtual double set_center_freq( double freq, size_t chan = 0 ) = 0; + + /*! + * Get the center frequency the underlying radio hardware is tuned to. + * This is the actual frequency and may differ from the frequency set. + * \param chan the channel index 0 to N-1 + * \return the frequency in Hz + */ + virtual double get_center_freq( size_t chan = 0 ) = 0; + + /*! + * Set the frequency correction value in parts per million. + * \param ppm the desired correction value in parts per million + * \param chan the channel index 0 to N-1 + * \return correction value in parts per million + */ + virtual double set_freq_corr( double ppm, size_t chan = 0 ) = 0; + + /*! + * Get the frequency correction value. + * \param chan the channel index 0 to N-1 + * \return correction value in parts per million + */ + virtual double get_freq_corr( size_t chan = 0 ) = 0; + + /*! + * Get the gain stage names of the underlying radio hardware. + * \param chan the channel index 0 to N-1 + * \return a vector of strings containing the names of gain stages + */ + virtual std::vector get_gain_names( size_t chan = 0 ) = 0; + + /*! + * Get the settable overall gain range for the underlying radio hardware. + * \param chan the channel index 0 to N-1 + * \return the gain range in dB + */ + virtual osmosdr::gain_range_t get_gain_range( size_t chan = 0 ) = 0; + + /*! + * Get the settable gain range for a specific gain stage. + * \param name the name of the gain stage + * \param chan the channel index 0 to N-1 + * \return the gain range in dB + */ + virtual osmosdr::gain_range_t get_gain_range( const std::string & name, + size_t chan = 0 ) = 0; + + /*! + * Set the gain mode for the underlying radio hardware. + * This might be supported only for certain hardware types. + * \param automatic the gain mode (true means automatic gain mode) + * \param chan the channel index 0 to N-1 + * \return the actual gain mode + */ + virtual bool set_gain_mode( bool automatic, size_t chan = 0 ) { return false; } + + /*! + * Get the gain mode selected for the underlying radio hardware. + * \param chan the channel index 0 to N-1 + * \return the actual gain mode (true means automatic gain mode) + */ + virtual bool get_gain_mode( size_t chan = 0 ) { return false; } + + /*! + * Set the gain for the underlying radio hardware. + * This function will automatically distribute the desired gain value over + * available gain stages in an appropriate way and return the actual value. + * \param gain the gain in dB + * \param chan the channel index 0 to N-1 + * \return the actual gain in dB + */ + virtual double set_gain( double gain, size_t chan = 0 ) = 0; + + /*! + * Set the named gain on the underlying radio hardware. + * \param gain the gain in dB + * \param name the name of the gain stage + * \param chan the channel index 0 to N-1 + * \return the actual gain in dB + */ + virtual double set_gain( double gain, + const std::string & name, + size_t chan = 0 ) = 0; + + /*! + * Get the actual gain setting of the underlying radio hardware. + * \param chan the channel index 0 to N-1 + * \return the actual gain in dB + */ + virtual double get_gain( size_t chan = 0 ) = 0; + + /*! + * Get the actual gain setting of a named stage. + * \param name the name of the gain stage + * \param chan the channel index 0 to N-1 + * \return the actual gain in dB + */ + virtual double get_gain( const std::string & name, size_t chan = 0 ) = 0; + + /*! + * Set the IF gain for the underlying radio hardware. + * This function will automatically distribute the desired gain value over + * available IF gain stages in an appropriate way and return the actual value. + * \param gain the gain in dB + * \param chan the channel index 0 to N-1 + * \return the actual gain in dB + */ + virtual double set_if_gain( double gain, size_t chan = 0 ) { return 0; } + + /*! + * Set the BB gain for the underlying radio hardware. + * This function will automatically distribute the desired gain value over + * available BB gain stages in an appropriate way and return the actual value. + * \param gain the gain in dB + * \param chan the channel index 0 to N-1 + * \return the actual gain in dB + */ + virtual double set_bb_gain( double gain, size_t chan = 0 ) { return 0; } + + /*! + * Get the available antennas of the underlying radio hardware. + * \param chan the channel index 0 to N-1 + * \return a vector of strings containing the names of available antennas + */ + virtual std::vector< std::string > get_antennas( size_t chan = 0 ) = 0; + + /*! + * Select the active antenna of the underlying radio hardware. + * \param chan the channel index 0 to N-1 + * \return the actual antenna's name + */ + virtual std::string set_antenna( const std::string & antenna, + size_t chan = 0 ) = 0; + + /*! + * Get the actual underlying radio hardware antenna setting. + * \param chan the channel index 0 to N-1 + * \return the actual antenna's name + */ + virtual std::string get_antenna( size_t chan = 0 ) = 0; + + /*! + * Set the TX frontend IQ balance mode. + * + * \param mode iq balance correction mode: 0 = Off, 1 = Manual, 2 = Automatic + * \param chan the channel index 0 to N-1 + */ + virtual void set_iq_balance_mode( int mode, size_t chan = 0 ) { } + + /*! + * Set the TX frontend IQ balance correction. + * Use this to adjust the magnitude and phase of I and Q. + * + * \param correction the complex correction value + * \param chan the channel index 0 to N-1 + */ + virtual void set_iq_balance( const std::complex &correction, size_t chan = 0 ) { } + + /*! + * Set the bandpass filter on the radio frontend. + * \param bandwidth the filter bandwidth in Hz + * \param chan the channel index 0 to N-1 + * \return the actual filter bandwidth in Hz + */ + virtual double set_bandwidth( double bandwidth, size_t chan = 0 ) { return 0; } + + /*! + * Get the actual bandpass filter setting on the radio frontend. + * \param chan the channel index 0 to N-1 + * \return the actual filter bandwidth in Hz + */ + virtual double get_bandwidth( size_t chan = 0 ) { return 0; } + + /*! + * Get the possible bandpass filter settings on the radio frontend. + * \param chan the channel index 0 to N-1 + * \return a range of bandwidths in Hz + */ + virtual osmosdr::meta_range_t get_bandwidth_range( size_t chan = 0 ) + { return osmosdr::meta_range_t(); } +}; + +#endif // OSMOSDR_SNK_IFACE_H diff --git a/swig/osmosdr_swig.i b/swig/osmosdr_swig.i index b37752f..2135599 100644 --- a/swig/osmosdr_swig.i +++ b/swig/osmosdr_swig.i @@ -13,7 +13,7 @@ %{ #include "osmosdr/osmosdr_device.h" #include "osmosdr/osmosdr_source_c.h" -//#include "osmosdr/osmosdr_sink_c.h" +#include "osmosdr/osmosdr_sink_c.h" %} %template(string_vector_t) std::vector; @@ -34,8 +34,8 @@ GR_SWIG_BLOCK_MAGIC(osmosdr,source_c); %include "osmosdr/osmosdr_source_c.h" -//GR_SWIG_BLOCK_MAGIC(osmosdr,sink_c); -//%include "osmosdr/osmosdr_sink_c.h" +GR_SWIG_BLOCK_MAGIC(osmosdr,sink_c); +%include "osmosdr/osmosdr_sink_c.h" #if SWIGGUILE %scheme %{