This builds on the work started in gr-osmosdr-gqrx arispyhf branch by Alexandru Csete. Further adds support for all the gain stages within the Airspy.
Kieren Rasmussen (3): - Added support for manual gain modes of AirspyHF+ - TODO support for AGC threshold level - Incorrect gain clipping when setting attenuation - Verified working with a git build of GQRX - Split the AirspyHF+ into a separate block in GRC with more relevant parameters exposed
README | 1 + grc/CMakeLists.txt | 1 + grc/gen_airspyhf_blocks.py | 281 ++++++++++++++++++++++++++++++ grc/gen_osmosdr_blocks.py | 2 + lib/airspyhf/airspyhf_source_c.cc | 102 ++++++++++- lib/airspyhf/airspyhf_source_c.h | 9 + 6 files changed, 392 insertions(+), 4 deletions(-) create mode 100644 grc/gen_airspyhf_blocks.py
From: Kieren Rasmussen kieren@wolftechpc.com.au
--- README | 1 + grc/gen_osmosdr_blocks.py | 2 + lib/airspyhf/airspyhf_source_c.cc | 102 ++++++++++++++++++++++++++++-- lib/airspyhf/airspyhf_source_c.h | 9 +++ 4 files changed, 110 insertions(+), 4 deletions(-)
diff --git a/README b/README index 67fa475..6f8ece3 100644 --- a/README +++ b/README @@ -11,6 +11,7 @@ as well supports: * gnuradio .cfile input through libgnuradio-blocks * RFSPACE SDR-IQ, SDR-IP, NetSDR (incl. X2 option) * AirSpy Wideband Receiver through libairspy + * AirSpy HF+ through libairspyhf * CCCamp 2015 rad1o Badge through libhackrf * Great Scott Gadgets HackRF through libhackrf * Nuand LLC bladeRF through libbladeRF library diff --git a/grc/gen_osmosdr_blocks.py b/grc/gen_osmosdr_blocks.py index d1f2b1a..5932395 100644 --- a/grc/gen_osmosdr_blocks.py +++ b/grc/gen_osmosdr_blocks.py @@ -219,6 +219,7 @@ While primarily being developed for the OsmoSDR hardware, this block as well sup * gnuradio .cfile input through libgnuradio-blocks * RFSPACE SDR-IQ, SDR-IP, NetSDR (incl. X2 option) * AirSpy Wideband Receiver through libairspy + * AirSpy HF+ through libairspyhf #end if #if $sourk == 'sink': * gnuradio .cfile output through libgnuradio-blocks @@ -260,6 +261,7 @@ Lines ending with ... mean it's possible to bind devices together by specifying cloudiq=127.0.0.1[:50000] sdr-iq=/dev/ttyUSB0 airspy=0[,bias=0|1][,linearity][,sensitivity] + airspyhf=0 #end if #if $sourk == 'sink': file='/path/to/your file',rate=1e6[,freq=100e6][,append=true][,throttle=true] ... diff --git a/lib/airspyhf/airspyhf_source_c.cc b/lib/airspyhf/airspyhf_source_c.cc index f4b3533..e4dbe5f 100644 --- a/lib/airspyhf/airspyhf_source_c.cc +++ b/lib/airspyhf/airspyhf_source_c.cc @@ -384,38 +384,132 @@ double airspyhf_source_c::get_freq_corr( size_t chan )
std::vectorstd::string airspyhf_source_c::get_gain_names( size_t chan ) { - return std::vectorstd::string(); + std::vectorstd::string names; + names += "LNA"; + names += "ATT"; + + return names; }
osmosdr::gain_range_t airspyhf_source_c::get_gain_range( size_t chan ) { - return osmosdr::gain_range_t(); + // Based on libairspyhf +6db with LNA and 0 to -48dB with Attenuation + return osmosdr::gain_range_t(-48, 6, 6); }
osmosdr::gain_range_t airspyhf_source_c::get_gain_range( const std::string & name, size_t chan ) { + if ("LNA" == name) { + return osmosdr::gain_range_t(0, 6, 6); + } else if ("ATT" == name) { + return osmosdr::gain_range_t(-48, 0, 6); + } return osmosdr::gain_range_t(); }
+bool airspyhf_source_c::set_gain_mode( bool automatic, size_t chan ) +{ + if ( automatic ) { + airspyhf_set_hf_agc( _dev, 1 ); + } else { + airspyhf_set_hf_agc( _dev, 0 ); + } + _auto_gain = automatic; + + return get_gain_mode(chan); +} + +bool airspyhf_source_c::get_gain_mode( size_t chan ) +{ + return _auto_gain; +}
double airspyhf_source_c::set_gain( double gain, size_t chan ) { + osmosdr::gain_range_t gains = get_gain_range(chan); + + if(_dev) { + double clip_gain = gains.clip(gain, true); + + if (clip_gain > 0) { + if (set_lna_gain(clip_gain, chan) == clip_gain && set_att_gain(0, chan) == 0) { + _gain = clip_gain; + } + } else { + if (set_lna_gain(0, chan) == 0 && set_att_gain(clip_gain, chan) == 0) { + _gain = clip_gain; + } + } + } + return gain; }
double airspyhf_source_c::set_gain( double gain, const std::string & name, size_t chan) { + if ("LNA" == name) { + return set_lna_gain(gain, chan); + } else if ("ATT" == name) { + return set_att_gain(gain, chan); + } + return gain; }
double airspyhf_source_c::get_gain( size_t chan ) { - return 0.0; + return _gain; }
double airspyhf_source_c::get_gain( const std::string & name, size_t chan ) { - return 0.0; + if ("LNA" == name) { + return _lna_gain; + } else if ("ATT" == name) { + return _att_gain; + } + + return get_gain(chan); +} + +double airspyhf_source_c::set_lna_gain( double gain, size_t chan ) +{ + int ret = AIRSPYHF_SUCCESS; + osmosdr::gain_range_t gains = get_gain_range( "LNA", chan ); + + if (_dev) { + double clip_gain = gains.clip( gain, true ); + uint8_t value = (uint8_t) ((clip_gain > 0) ? 1 : 0); + + ret = airspyhf_set_hf_lna( _dev, value ); + if ( AIRSPYHF_SUCCESS == ret ) { + _lna_gain = clip_gain; + } else { + AIRSPYHF_THROW_ON_ERROR( ret, AIRSPYHF_FUNC_STR( "airspyhf_set_lna_gain", value ) ) + } + } + + return _lna_gain; +} + +double airspyhf_source_c::set_att_gain( double gain, size_t chan ) +{ + int ret = AIRSPYHF_SUCCESS; + osmosdr::gain_range_t gains = get_gain_range( "LNA", chan ); + + if (_dev) { + double clip_gain = gains.clip( gain, true ); + uint8_t value = (uint8_t) (clip_gain / -6); + + ret = airspyhf_set_hf_att( _dev, value ); + if ( AIRSPYHF_SUCCESS == ret ) { + _att_gain = clip_gain; + } else { + AIRSPYHF_THROW_ON_ERROR( ret, AIRSPYHF_FUNC_STR( "airspyhf_set_att_gain", value ) ) + } + } + + return _lna_gain; }
std::vector< std::string > airspyhf_source_c::get_antennas( size_t chan ) diff --git a/lib/airspyhf/airspyhf_source_c.h b/lib/airspyhf/airspyhf_source_c.h index cfb8c89..d464d07 100644 --- a/lib/airspyhf/airspyhf_source_c.h +++ b/lib/airspyhf/airspyhf_source_c.h @@ -88,11 +88,16 @@ public: std::vectorstd::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_lna_gain( double gain, size_t chan = 0 ); + double set_att_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 ); @@ -112,6 +117,10 @@ private: double _sample_rate; double _center_freq; double _freq_corr; + bool _auto_gain; + double _gain; + double _lna_gain; + double _att_gain; };
#endif /* INCLUDED_AIRSPY_SOURCE_C_H */
From: Kieren Rasmussen kieren@wolftechpc.com.au
--- lib/airspyhf/airspyhf_source_c.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/airspyhf/airspyhf_source_c.cc b/lib/airspyhf/airspyhf_source_c.cc index e4dbe5f..b2965ff 100644 --- a/lib/airspyhf/airspyhf_source_c.cc +++ b/lib/airspyhf/airspyhf_source_c.cc @@ -495,7 +495,7 @@ double airspyhf_source_c::set_lna_gain( double gain, size_t chan ) double airspyhf_source_c::set_att_gain( double gain, size_t chan ) { int ret = AIRSPYHF_SUCCESS; - osmosdr::gain_range_t gains = get_gain_range( "LNA", chan ); + osmosdr::gain_range_t gains = get_gain_range( "ATT", chan );
if (_dev) { double clip_gain = gains.clip( gain, true );
From: Kieren Rasmussen kieren@wolftechpc.com.au
--- grc/CMakeLists.txt | 1 + grc/gen_airspyhf_blocks.py | 281 +++++++++++++++++++++++++++++++++++++ 2 files changed, 282 insertions(+) create mode 100644 grc/gen_airspyhf_blocks.py
diff --git a/grc/CMakeLists.txt b/grc/CMakeLists.txt index 09838c1..92a0e10 100644 --- a/grc/CMakeLists.txt +++ b/grc/CMakeLists.txt @@ -32,6 +32,7 @@ macro(GEN_BLOCK_XML _generator _xml_block) ) endmacro(GEN_BLOCK_XML)
+GEN_BLOCK_XML(gen_airspyhf_blocks.py airspyhf_source.xml) GEN_BLOCK_XML(gen_osmosdr_blocks.py rtlsdr_source.xml) GEN_BLOCK_XML(gen_osmosdr_blocks.py osmosdr_source.xml) GEN_BLOCK_XML(gen_osmosdr_blocks.py osmosdr_sink.xml) diff --git a/grc/gen_airspyhf_blocks.py b/grc/gen_airspyhf_blocks.py new file mode 100644 index 0000000..5878731 --- /dev/null +++ b/grc/gen_airspyhf_blocks.py @@ -0,0 +1,281 @@ +""" +Copyright 2012 Free Software Foundation, Inc. + +This file is part of GNU Radio + +GNU Radio Companion 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 2 +of the License, or (at your option) any later version. + +GNU Radio Companion is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +MAIN_TMPL = """\ +<?xml version="1.0"?> +<block> + <name>$(title) $sourk.title()</name> + <key>$(prefix)_$(sourk)</key> + <category>$($sourk.title())s</category> + <throttle>1</throttle> + <import>import osmosdr</import> + <import>import time</import> + <make>osmosdr.$(sourk)( args="numchan=" + str($nchan) + " " + $args ) +self.$(id).set_sample_rate($sample_rate) +#for $n in range($max_nchan) +#if $nchan() > $n +self.$(id).set_center_freq($freq$(n), $n) +self.$(id).set_freq_corr($corr$(n), $n) +self.$(id).set_iq_balance_mode($iq_balance_mode$(n), $n) +self.$(id).set_gain_mode($gain_mode$(n), $n) +self.$(id).set_gain($lna_gain$(n), "LNA", $n) +self.$(id).set_gain($att_gain$(n), "ATT", $n) +#end if +#end for + </make> + <callback>set_sample_rate($sample_rate)</callback> + #for $n in range($max_nchan) + <callback>set_center_freq($freq$(n), $n)</callback> + <callback>set_freq_corr($corr$(n), $n)</callback> + <callback>set_iq_balance_mode($iq_balance_mode$(n), $n)</callback> + <callback>set_gain_mode($gain_mode$(n), $n)</callback> + <callback>set_gain($lna_gain$(n), "LNA", $n)</callback> + <callback>set_gain($att_gain$(n), "ATT", $n)</callback> + #end for + <param> + <name>$(dir.title())put Type</name> + <key>type</key> + <type>enum</type> + <option> + <name>Complex float32</name> + <key>fc32</key> + <opt>type:fc32</opt> + </option> + </param> + <param> + <name>Device Arguments</name> + <key>args</key> + <value>airspyhf=0</value> + <type>string</type> + <hide> + #if $args() + none + #else + part + #end if + </hide> + </param> + <param> + <name>Num Mboards</name> + <key>num_mboards</key> + <value>1</value> + <type>int</type> + <hide>part</hide> + #for $m in range(1, $max_mboards+1) + <option> + <name>$(m)</name> + <key>$m</key> + </option> + #end for + </param> + <param> + <name>Num Channels</name> + <key>nchan</key> + <value>1</value> + <type>int</type> + #for $n in range(1, $max_nchan+1) + <option> + <name>$(n)</name> + <key>$n</key> + </option> + #end for + </param> + <param> + <name>Sample Rate (sps)</name> + <key>sample_rate</key> + <value>samp_rate</value> + <type>real</type> + </param> + $params + <check>$max_nchan >= $nchan</check> + <check>$nchan > 0</check> + <check>$max_mboards >= $num_mboards</check> + <check>$num_mboards > 0</check> + <check>$nchan >= $num_mboards</check> + <$sourk> + <name>$dir</name> + <type>$type.type</type> + <nports>$nchan</nports> + </$sourk> + <doc> +The AirspyHF $sourk block: + +This block supports: + * AirSpy HF+ through libairspyhf + +Output Type: +This parameter controls the data type of the stream in gnuradio. Only complex float32 samples are supported at the moment. + +Device Arguments: +The device argument is a comma delimited string used to locate devices on your system. Device arguments for multiple devices may be given by separating them with a space. +Use the device id or name/serial (if applicable) to specify a certain device or list of devices. If left blank, the first device found will be used. + +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. + + airspyhf=0 + +Num Channels: +Selects the total number of channels in this multi-device configuration. Required when specifying multiple device arguments. + +Sample Rate: +The sample rate is the number of samples per second output by this block on each channel. + +Frequency: +The center frequency is the frequency the RF chain is tuned to. + +Freq. Corr.: +The frequency correction factor in parts per million (ppm). Set to 0 if unknown. + +IQ Balance Mode: +Controls the behavior of software IQ imbalance corrrection. + Off: Disable correction algorithm (pass through). + Manual: Keep last estimated correction when switched from Automatic to Manual. + Automatic: Periodically find the best solution to compensate for image signals. + +This functionality depends on http://cgit.osmocom.org/cgit/gr-iqbal/ + +Gain Mode: +Chooses between the manual (default) and automatic gain mode where appropriate. +To allow manual control of LNA and Attenuator stages, manual gain mode must be configured. + +LNA Gain: +LNA gain of the device. Either 0dB or 6dB of gain available. + +ATT Gain: +Attenuator gain of the device. Between -48dB and 0dB of gain available with 6dB steps. + +See the OsmoSDR project page for more detailed documentation: +http://sdr.osmocom.org/trac/wiki/GrOsmoSDR +http://sdr.osmocom.org/trac/wiki/rtl-sdr +http://sdr.osmocom.org/trac/ + </doc> +</block> +""" + +PARAMS_TMPL = """ + <param> + <name>Ch$(n): Frequency (Hz)</name> + <key>freq$(n)</key> + <value>100e6</value> + <type>real</type> + <hide>#if $nchan() > $n then 'none' else 'all'#</hide> + </param> + <param> + <name>Ch$(n): Freq. Corr. (ppm)</name> + <key>corr$(n)</key> + <value>0</value> + <type>real</type> + <hide>#if $nchan() > $n then 'none' else 'all'#</hide> + </param> + <param> + <name>Ch$(n): IQ Balance Mode</name> + <key>iq_balance_mode$(n)</key> + <value>0</value> + <type>int</type> + <hide>#if $nchan() > $n then 'none' else 'all'#</hide> + <option> + <name>Off</name> + <key>0</key> + </option> + <option> + <name>Manual</name> + <key>1</key> + </option> + <option> + <name>Automatic</name> + <key>2</key> + </option> + </param> + <param> + <name>Ch$(n): Gain Mode</name> + <key>gain_mode$(n)</key> + <value>False</value> + <type>bool</type> + <hide>#if $nchan() > $n then 'none' else 'all'#</hide> + <option> + <name>Manual</name> + <key>False</key> + </option> + <option> + <name>Automatic</name> + <key>True</key> + </option> + </param> + <param> + <name>Ch$(n): LNA Gain (dB)</name> + <key>lna_gain$(n)</key> + <value>6</value> + <type>real</type> + <hide>#if $nchan() > $n then 'none' else 'all'#</hide> + </param> + <param> + <name>Ch$(n): ATT Gain (dB)</name> + <key>att_gain$(n)</key> + <value>0</value> + <type>real</type> + <hide>#if $nchan() > $n then 'none' else 'all'#</hide> + </param> +""" + + +def parse_tmpl(_tmpl, **kwargs): + from Cheetah import Template + return str(Template.Template(_tmpl, kwargs)) + + +max_num_mboards = 8 +max_num_channels = max_num_mboards + +import os.path + +if __name__ == '__main__': + import sys + + for file in sys.argv[1:]: + head, tail = os.path.split(file) + + if tail.startswith('airspyhf'): + title = 'AirspyHF' + prefix = 'airspyhf' + else: + raise Exception, 'file %s has wrong syntax!' % tail + + if tail.endswith('source.xml'): + sourk = 'source' + dir = 'out' + elif tail.endswith('sink.xml'): + sourk = 'sink' + dir = 'in' + else: + raise Exception, 'is %s a source or sink?' % file + + params = ''.join([parse_tmpl(PARAMS_TMPL, n=n, sourk=sourk) for n in range(max_num_channels)]) + open(file, 'w').write(parse_tmpl(MAIN_TMPL, + max_nchan=max_num_channels, + max_mboards=max_num_mboards, + params=params, + title=title, + prefix=prefix, + sourk=sourk, + dir=dir, + ))