diff options
author | Martin Braun <martin.braun@ettus.com> | 2020-08-12 15:05:46 +0200 |
---|---|---|
committer | Michael Dickens <michael.dickens@ettus.com> | 2020-09-10 14:17:50 -0400 |
commit | 9e4836dc10b2a6ca537d8c290ac668d94692cbe4 (patch) | |
tree | 95957067b5f9e3a3d0489d4a486bdb2e515d7dea | |
parent | cc4854828366b921f4efbe46e5524be7fe3a6cf8 (diff) |
uhd: Update uhd_siggen_gui to allow setting absolute power
Note: This requires a USRP with power API enabled.
Summary of changes:
- uhd_app now provides the --power argument and applies it as
a reference power level. It clashes with --gain. UHDApp now has two
'modes' for setting the power: Via gain (the previous, standard way)
or by setting a power. UHDApp calls all the right APIs for each use
case.
- uhd_siggen_base is now aware of these settings and will make sure the
output power is correct by combining the requested power and the
requested amplitude (meaning, it sets a ref power that is at the
correct level above the requested power level).
- The GUI can now set either power or gain, depending on command line
arguments. When changing the amplitude in power mode, the ref power is
also changed so the output power (in dBm) stays the same when the
amplitude changes.
- siggen now checks for invalid amplitudes (must be in 0 <= amplitude <=
1).
- --amplitude was moved from uhd_app.py to uhd_siggen_base.py, where it
belongs. uhd_fft for example does not have that argument.
- If --amplitude is set to 0.0, then it now fails in power mode. It's
still possible in gain mode.
-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 """ |