summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2020-08-12 15:05:46 +0200
committerMichael Dickens <michael.dickens@ettus.com>2020-09-10 14:17:50 -0400
commit9e4836dc10b2a6ca537d8c290ac668d94692cbe4 (patch)
tree95957067b5f9e3a3d0489d4a486bdb2e515d7dea
parentcc4854828366b921f4efbe46e5524be7fe3a6cf8 (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.py47
-rwxr-xr-xgr-uhd/apps/uhd_fft1
-rw-r--r--gr-uhd/apps/uhd_siggen_base.py74
-rwxr-xr-xgr-uhd/apps/uhd_siggen_gui38
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 """