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
gr3.6
Dimitri Stolnikov 10 years ago
parent 882b6da6ac
commit a5bdb27240
  1. 2
      grc/CMakeLists.txt
  2. 12
      grc/gen_osmosdr_blocks.py
  3. 2
      include/osmosdr/CMakeLists.txt
  4. 228
      include/osmosdr/osmosdr_sink_c.h
  5. 1
      lib/hackrf/CMakeLists.txt
  6. 666
      lib/hackrf/hackrf_sink_c.cc
  7. 98
      lib/hackrf/hackrf_sink_c.h
  8. 474
      lib/osmosdr_sink_c_impl.cc
  9. 55
      lib/osmosdr_sink_c_impl.h
  10. 261
      lib/osmosdr_snk_iface.h
  11. 6
      swig/osmosdr_swig.i

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

@ -23,7 +23,7 @@ MAIN_TMPL = """\
<block>
<name>$(title) $sourk.title()</name>
<key>$(prefix)_$(sourk)_c</key>
<category>Sources</category>
<category>$($sourk.title())s</category>
<throttle>1</throttle>
<import>import osmosdr</import>
<make>osmosdr.$(sourk)_c( args="nchan=" + str(\$nchan) + " " + \$args )
@ -104,7 +104,7 @@ self.\$(id).set_bandwidth(\$bw$(n), $n)
<nports>\$nchan</nports>
</$sourk>
<doc>
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

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

@ -21,6 +21,7 @@
#define INCLUDED_OSMOSDR_SINK_C_H
#include <osmosdr/osmosdr_api.h>
#include <osmosdr/osmosdr_ranges.h>
#include <gnuradio/gr_hier_block2.h>
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<std::string> 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<double> &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 */

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

@ -27,15 +27,91 @@
#include "config.h"
#endif
#include "hackrf_sink_c.h"
#include <stdexcept>
#include <iostream>
#include <boost/assign.hpp>
#include <boost/format.hpp>
#include <boost/detail/endian.hpp>
#include <boost/algorithm/string.hpp>
#include <gnuradio/gr_io_signature.h>
/*
* 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<std::string> hackrf_sink_c::get_devices()
{
std::vector<std::string> 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<std::string> 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<double, int> 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;
}

@ -20,10 +20,29 @@
#ifndef INCLUDED_HACKRF_SINK_C_H
#define INCLUDED_HACKRF_SINK_C_H
#include <boost/shared_ptr.hpp>
#include <gruel/thread.h>
#include <gnuradio/gr_sync_block.h>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition_variable.hpp>
#include <libhackrf/hackrf.h>
#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> 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<std::string> 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<gr_complex> _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 */

@ -1,6 +1,6 @@
/* -*- c++ -*- */
/*
* Copyright 2012 Dimitri Stolnikov <horiz0n@gmx.net>
* Copyright 2013 Dimitri Stolnikov <horiz0n@gmx.net>
*
* 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 <osmosdr_sink_c_impl.h>
#include <gr_io_signature.h>
#include <gnuradio/gr_io_signature.h>
#include <gnuradio/gr_constants.h>
#include <gnuradio/gr_throttle.h>
#include <gnuradio/gr_null_sink.h>
#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;
}
<