diff options
-rw-r--r-- | gr-uhd/apps/uhd_app.py | 47 | ||||
-rwxr-xr-x | gr-uhd/apps/uhd_fft | 1 | ||||
-rw-r--r-- | gr-uhd/apps/uhd_siggen_base.py | 74 | ||||
-rwxr-xr-x | gr-uhd/apps/uhd_siggen_gui | 38 |
4 files changed, 137 insertions, 23 deletions
diff --git a/gr-uhd/apps/uhd_app.py b/gr-uhd/apps/uhd_app.py index f666e1e28f..d878a063c5 100644 --- a/gr-uhd/apps/uhd_app.py +++ b/gr-uhd/apps/uhd_app.py @@ -28,7 +28,13 @@ LONG_TPL = """{prefix} Motherboard: {mb_id} ({mb_serial}) {prefix} Antenna: {ant} """ +# PyLint can't reliably detect C++ exports in modules, so let's disable that +# pylint: disable=no-member + class UHDApp: + GAIN_TYPE_GAIN = 'dB' + GAIN_TYPE_POWER = 'power_dbm' + " Base class for simple UHD-based applications " def __init__(self, prefix=None, args=None): self.prefix = prefix @@ -37,7 +43,7 @@ class UHDApp: if self.args.sync == 'auto' and len(self.args.channels) > 1: self.args.sync = 'pps' self.antenna = None - self.gain_range = None + self.gain_range = None # Can also be power range self.samp_rate = None self.has_lo_sensor = None self.async_msgq = None @@ -55,6 +61,7 @@ class UHDApp: self.lo_export = None self.usrp = None self.lo_source_channel = None + self.gain_type = self.GAIN_TYPE_GAIN def vprint(self, *args): """ @@ -179,8 +186,20 @@ class UHDApp: )) self.antenna = self.usrp.get_antenna(0) # Set receive daughterboard gain: - self.set_gain(args.gain) - self.gain_range = self.usrp.get_gain_range(0) + if args.power: + if args.gain is not None: + print("[ERROR] Providing both --gain and --power is invalid!") + sys.exit(1) + self.gain_type = self.GAIN_TYPE_POWER + if not self.usrp.has_power_reference(0): + print("[ERROR] Device does not have power reference capabilities!") + sys.exit(1) + self.set_power_reference(args.power) + self.gain_range = self.usrp.get_power_range(0) + else: + self.set_gain(args.gain) + self.gain_range = self.usrp.get_gain_range(0) + self.gain_type = self.GAIN_TYPE_GAIN # Set frequency (tune request takes lo_offset): if hasattr(args, 'lo_offset') and args.lo_offset is not None: treq = uhd.tune_request(args.freq, args.lo_offset) @@ -268,11 +287,20 @@ class UHDApp: chan=chan, g=self.usrp.get_gain(i) )) else: - self.vprint("Setting gain to {g} dB.".format(g=gain)) + self.vprint("Setting gain to {g:.2f} dB.".format(g=gain)) for chan in range(len(self.channels)): self.usrp.set_gain(gain, chan) self.gain = self.usrp.get_gain(0) + def set_power_reference(self, power_dbm): + """ + Safe power-ref-setter. + """ + assert power_dbm is not None + self.vprint("Setting ref power to {g:.2f} dBm.".format(g=power_dbm)) + self.usrp.set_power_reference(power_dbm) + self.gain = self.usrp.get_power_reference(0) + def set_freq(self, freq, skip_sync=False): """ Safely tune all channels to freq. @@ -364,12 +392,11 @@ class UHDApp: help="Sample rate") group.add_argument("-g", "--gain", type=eng_arg.eng_float, default=None, help="Gain (default is midpoint)") - group.add_argument("--gain-type", choices=('db', 'normalized'), default='db', - help="Gain Type (applies to -g)") - group.add_argument("-p", "--power-ref", type=eng_arg.eng_float, default=None, - help="Reference power level (in dBm). " + group.add_argument("-p", "--power", type=eng_arg.eng_float, default=None, + help="(Reference) power level (in dBm). " "Not supported by all devices (see UHD manual). " - "Will fail if not supported. Precludes --gain. ") + "Will fail if not supported. Precludes --gain. " + "Behaviour may differ between applications.") if not skip_freq: group.add_argument("-f", "--freq", type=eng_arg.eng_float, default=None, required=True, help="Set carrier frequency to FREQ", @@ -390,8 +417,6 @@ class UHDApp: group.add_argument("--otw-format", choices=['sc16', 'sc12', 'sc8'], default='sc16', help="Choose over-the-wire data format") group.add_argument("--stream-args", default="", help="Set additional stream arguments") - group.add_argument("-m", "--amplitude", type=eng_arg.eng_float, default=0.15, - help="Set output amplitude to AMPL (0.0-1.0)", metavar="AMPL") group.add_argument("-v", "--verbose", action="count", help="Use verbose console output") group.add_argument("--show-async-msg", action="store_true", help="Show asynchronous message notifications from UHD") diff --git a/gr-uhd/apps/uhd_fft b/gr-uhd/apps/uhd_fft index cc870e991c..f9b51723c6 100755 --- a/gr-uhd/apps/uhd_fft +++ b/gr-uhd/apps/uhd_fft @@ -89,7 +89,6 @@ class uhd_fft(UHDApp, gr.top_block, Qt.QWidget): self.fft_size = args.fft_size self.freq = args.freq self.gain = args.gain - self.gain_type = args.gain_type self.samp_rate = args.samp_rate self.spec = args.spec self.stream_args = args.stream_args diff --git a/gr-uhd/apps/uhd_siggen_base.py b/gr-uhd/apps/uhd_siggen_base.py index 38d197e3ad..daf8184f56 100644 --- a/gr-uhd/apps/uhd_siggen_base.py +++ b/gr-uhd/apps/uhd_siggen_base.py @@ -22,6 +22,9 @@ from gnuradio import analog from gnuradio import blocks from gnuradio.gr.pubsub import pubsub +# PyLint can't reliably detect C++ exports in modules, so let's disable that +# pylint: disable=no-member + DESC_KEY = 'desc' SAMP_RATE_KEY = 'samp_rate' LINK_RATE_KEY = 'link_rate' @@ -35,7 +38,6 @@ WAVEFORM_FREQ_KEY = 'waveform_freq' WAVEFORM_OFFSET_KEY = 'waveform_offset' WAVEFORM2_FREQ_KEY = 'waveform2_freq' FREQ_RANGE_KEY = 'freq_range' -GAIN_RANGE_KEY = 'gain_range' TYPE_KEY = 'type' WAVEFORMS = { @@ -52,9 +54,25 @@ class USRPSiggen(gr.top_block, pubsub, UHDApp): GUI-unaware GNU Radio flowgraph. This may be used either with command line applications or GUI applications. """ + MIN_AMP_POWER_MODE = .001 + def __init__(self, args): gr.top_block.__init__(self) pubsub.__init__(self) + if not 0.0 <= args.amplitude <= 1.0: + raise ValueError( + "Invalid value for amplitude: {}. Must be in [0.0, 1.0]" + .format(args.amplitude)) + # If the power argument is given, we need to turn that into a power + # *reference* level. This is a bit of a hack because we're assuming + # knowledge of UHDApp (i.e. we're leaking abstractions). But it's simple + # and harmless enough. + if args.power: + if args.amplitude < self.MIN_AMP_POWER_MODE: + raise RuntimeError( + "[ERROR] Invalid amplitude: In power mode, amplitude must be " + "larger than {}!".format(self.MIN_AMP_POWER_MODE)) + args.power -= 20 * math.log10(args.amplitude) UHDApp.__init__(self, args=args, prefix="UHD-SIGGEN") self.extra_sink = None @@ -78,8 +96,7 @@ class USRPSiggen(gr.top_block, pubsub, UHDApp): self.publish(SAMP_RATE_KEY, lambda: self.usrp.get_samp_rate()) self.publish(DESC_KEY, lambda: self.usrp_description) self.publish(FREQ_RANGE_KEY, lambda: self.usrp.get_freq_range(self.channels[0])) - self.publish(GAIN_RANGE_KEY, lambda: self.usrp.get_gain_range(self.channels[0])) - self.publish(GAIN_KEY, lambda: self.usrp.get_gain(self.channels[0])) + self.publish(GAIN_KEY, lambda: self.get_gain_or_power()) self[SAMP_RATE_KEY] = args.samp_rate self[TX_FREQ_KEY] = args.freq @@ -92,12 +109,13 @@ class USRPSiggen(gr.top_block, pubsub, UHDApp): #subscribe set methods self.subscribe(SAMP_RATE_KEY, self.set_samp_rate) - self.subscribe(GAIN_KEY, self.set_gain) + self.subscribe(GAIN_KEY, self.set_gain_or_power) self.subscribe(TX_FREQ_KEY, self.set_freq) self.subscribe(AMPLITUDE_KEY, self.set_amplitude) self.subscribe(WAVEFORM_FREQ_KEY, self.set_waveform_freq) self.subscribe(WAVEFORM2_FREQ_KEY, self.set_waveform2_freq) self.subscribe(TYPE_KEY, self.set_waveform) + self.subscribe(RF_FREQ_KEY, self.update_gain_range) #force update on pubsub keys for key in (SAMP_RATE_KEY, GAIN_KEY, TX_FREQ_KEY, @@ -234,8 +252,51 @@ class USRPSiggen(gr.top_block, pubsub, UHDApp): else: return True # Waveform not yet set self.vprint("Set amplitude to:", amplitude) + self.update_gain_range() return True + def get_gain_or_power(self): + """ + Depending on gain type, return either a power level or the current gain + """ + if self.gain_type == self.GAIN_TYPE_GAIN: + return self.usrp.get_gain(self.channels[0]) + return self.usrp.get_power_reference(self.channels[0]) \ + + 20 * math.log10(self[AMPLITUDE_KEY]) + + def set_gain_or_power(self, gain_or_power): + """ + Call this if a gain or power value changed, but you're not sure which it + is. + + If it's a power, we subtract the signal offset to generate a reference + power. + """ + if self.gain_type == self.GAIN_TYPE_POWER: + self.set_power_reference( + gain_or_power - 20 * math.log10(self[AMPLITUDE_KEY])) + else: + self.set_gain(gain_or_power) + + def update_gain_range(self): + """ + Update self.gain_range. + """ + if self.gain_type == self.GAIN_TYPE_POWER: + if self[AMPLITUDE_KEY] < self.MIN_AMP_POWER_MODE: + raise RuntimeError( + "[ERROR] Invalid amplitude: In power mode, amplitude must be " + "larger than {}!".format(self.MIN_AMP_POWER_MODE)) + power_range = self.usrp.get_power_range(self.channels[0]) + ampl_offset = 20 * math.log10(self[AMPLITUDE_KEY]) + self.gain_range = uhd.meta_range( + math.floor(power_range.start() + ampl_offset), + math.ceil(power_range.stop() + ampl_offset), + power_range.step() + ) + self.vprint("Updated power range to {:.2f} ... {:.2f} dBm.".format( + self.gain_range.start(), self.gain_range.stop())) + def setup_argparser(): """ @@ -246,6 +307,11 @@ def setup_argparser(): tx_or_rx="Tx", ) group = parser.add_argument_group('Siggen Arguments') + group.add_argument("-m", "--amplitude", type=eng_arg.eng_float, default=0.15, + help="Set output amplitude to AMPL (0.0-1.0). Note that " + "if --power is given, UHD will attempt to match the " + "output power regardless of the amplitude.", + metavar="AMPL") group.add_argument("-x", "--waveform-freq", type=eng_arg.eng_float, default=0.0, help="Set baseband waveform frequency to FREQ") group.add_argument("-y", "--waveform2-freq", type=eng_arg.eng_float, default=0.0, diff --git a/gr-uhd/apps/uhd_siggen_gui b/gr-uhd/apps/uhd_siggen_gui index 26d1bf647c..81bbd5a029 100755 --- a/gr-uhd/apps/uhd_siggen_gui +++ b/gr-uhd/apps/uhd_siggen_gui @@ -23,6 +23,7 @@ Signal Generator App import sys import threading import time +import math from PyQt5 import Qt from PyQt5.QtCore import pyqtSlot import sip # Needs to be imported after PyQt5, could fail otherwise @@ -190,7 +191,11 @@ class uhd_siggen_gui(Qt.QWidget): self._freq2_offset_win.setEnabled(self._sg[uhd_siggen.TYPE_KEY] in self._freq2_enable_on) self.top_grid_layout.addWidget(self._freq2_offset_win, 4, 3, 1, 2) ### Amplitude - self._amplitude_range = Range(0, 1, .001, .7, 200) + min_ampl = \ + self._sg.MIN_AMP_POWER_MODE \ + if self._sg.gain_type == self._sg.GAIN_TYPE_POWER else 0 + self._amplitude_range = \ + Range(min_ampl, 1, .001, self._sg[uhd_siggen.AMPLITUDE_KEY], 200) self._amplitude_win = RangeWidget( self._amplitude_range, self.set_amplitude, @@ -199,18 +204,22 @@ class uhd_siggen_gui(Qt.QWidget): float ) self.top_grid_layout.addWidget(self._amplitude_win, 5, 0, 1, 5) - ### Gain + ### Gain or power self._gain_range = Range( - self.usrp.get_gain_range(self._sg.channels[0]).start(), - self.usrp.get_gain_range(self._sg.channels[0]).stop(), + math.floor(self._sg.gain_range.start()), + math.ceil(self._sg.gain_range.stop()), .5, - self.usrp.get_gain(self._sg.channels[0]), + self._sg.get_gain_or_power(), 200., ) self._gain_win = RangeWidget( self._gain_range, - self._sg.set_gain, - "TX Gain", "counter_slider", float + self.set_gain_or_power, + "TX Gain (dB)" + if self._sg.gain_type == self._sg.GAIN_TYPE_GAIN + else "TX Power (dBm)", + "counter_slider", + float ) self.top_grid_layout.addWidget(self._gain_win, 6, 0, 1, 5) ### Samp rate, LO sync, Antenna Select @@ -375,6 +384,8 @@ class uhd_siggen_gui(Qt.QWidget): if idx == 0: self.set_label_dsp_freq(tune_res.actual_dsp_freq) self.set_label_rf_freq(tune_res.actual_rf_freq) + if self._sg.gain_type == self._sg.GAIN_TYPE_POWER: + self._sg[uhd_siggen.RF_FREQ_KEY] = self._sg[uhd_siggen.RF_FREQ_KEY] def set_freq1_offset(self, freq1_offset): """ Execute when the freq1 slider is moved """ @@ -386,7 +397,20 @@ class uhd_siggen_gui(Qt.QWidget): def set_amplitude(self, amplitude): """ Execute when the amplitude slider is moved """ + # Here's a catch-22: The current power is calculated using the amplitude + # so we need to stash it before the amplitude changes, then reapply + # later. + power = self._sg[uhd_siggen.GAIN_KEY] self._sg[uhd_siggen.AMPLITUDE_KEY] = amplitude + if self._sg.gain_type == self._sg.GAIN_TYPE_POWER: + self._sg[uhd_siggen.GAIN_KEY] = power + + def set_gain_or_power(self, gain_or_power): + """ + Execute when the gain/power slider is moved + """ + self._sg.set_gain_or_power(gain_or_power) + self._gain_win.d_widget.setValue(self._sg.get_gain_or_power()) def set_sync_phases(self, sync): """ Execute when the sync-phases button is pushed """ |