mirror of
https://github.com/saymrwulf/uhd.git
synced 2026-05-16 21:10:10 +00:00
293 lines
14 KiB
Text
293 lines
14 KiB
Text
/*! \page page_power Power Level Controls
|
|
|
|
\tableofcontents
|
|
|
|
\section power_overview Power Level Controls: Overview
|
|
|
|
Starting with UHD 4, UHD comes with reference power level APIs. These allow to
|
|
not just set a relative gain level, but configure a USRP to transmit a certain
|
|
power, or to estimate received power levels.
|
|
|
|
\b DISCLAIMER: USRPs are not factory-calibrated test and measurement devices,
|
|
but general purpose SDR devices. As such, they are not intended to replace
|
|
factory-calibrated power meters or signal generators, nor are they suitable
|
|
devices for doing so.
|
|
|
|
The actual transmitted or received power also depends on the transmitted signal
|
|
itself. The absolute power setting is thus a *reference power level*.
|
|
The reference power level maps a digital signal level (in dBFS) to an absolute,
|
|
analog power level (in dBm). The power level setting is the analog power level
|
|
that corresponds to a 0 dBFS digital signal.
|
|
|
|
Note that most USRPs do not support setting reference power levels. Refer to the
|
|
individual device manual pages for more information.
|
|
|
|
\section power_ref_levels RX and TX reference power levels
|
|
|
|
<b>RX reference power level:</b>
|
|
|
|
The RX reference power level is defined as such: When a signal with this power
|
|
level is applied to the RF input, the digital signal will be at a 0 dBFS power
|
|
level.
|
|
|
|
If a tone with this power level is applied to the RF input, it will be
|
|
full-scale in the digital representation.
|
|
|
|
Example: The reference power level is set to -4 dBm by calling
|
|
~~~{.cpp}
|
|
U->set_rx_power_reference(-4.0, 0);
|
|
~~~
|
|
|
|
Then, a signal is captured. It turns out the signal is a sine wave with
|
|
an amplitude of 0.5, which means its power is 6 dB lower than a signal
|
|
with an amplitude of 1.0 (or -6 dBFS). Based on the reference power level, we
|
|
can thus infer that the tone injected into the RF port is:
|
|
|
|
(-4 dBm - 6 dB) = -10 dBm
|
|
|
|
<b>TX reference power level:</b>
|
|
|
|
The TX reference power level is defined as such: When a 0 dBFS signal is
|
|
transmitted, it will leave the RF output at the selected reference power level.
|
|
In other contexts, this value is sometimes referred to as "peak power level",
|
|
because it's the maximum power that can be transmitted.
|
|
|
|
If a fullscale tone is transmitted, the transmitted analog tone will have the
|
|
reference power level.
|
|
|
|
Example: The reference power level is set to -4 dBm by calling
|
|
~~~{.cpp}
|
|
U->set_tx_power_reference(-4.0, 0);
|
|
~~~
|
|
|
|
Then, we generate a tone that is a sine wave with amplitude 0.5 and
|
|
transmit it using this device and channel. With this amplitude, the
|
|
signal is 6 dB lower in power than a sine wave with amplitude 1.0.
|
|
Based on the reference power level, we can thus infer that the tone
|
|
leaving the RF port has the following absolute power:
|
|
|
|
(-4 dBm - 6 dB) = -10 dBm
|
|
|
|
\section power_amp_rate Amplitude Ranges and Clipping
|
|
|
|
In practice, it is never possible to cleanly transmit or receive a signal at
|
|
0 dBFS, so the reference levels need to be appropriately normalized. Because
|
|
all USRPs have a different amplitude range they can transmit/receive before
|
|
clipping or distortion occurs, the APIs are designed to be agnostic of the
|
|
hardware. The derivation of the actual analog power is thus the responsibility
|
|
of the application (i.e., the code that also calls `recv()` and `send()`), which has
|
|
access to the sample values. UHD itself doesn't inspect the sample values for
|
|
performance reasons, and thus can only manage reference levels.
|
|
|
|
\section power_tune_owner Retaining gain or power levels across frequency
|
|
|
|
When tuning a USRP (i.e., changing its frequency) it may be necessary to also
|
|
adjust gain stages. Due to physical limitations, the same output power is not
|
|
always available (or possible) at different frequencies. This means that the
|
|
output power may vary when retuning. The relative gain range is usually the same
|
|
across frequencies, though.
|
|
|
|
UHD will try to maintain the relative gain setting, or the power level setting.
|
|
The choice is determined by which API was called last. If the gain level was set
|
|
last (i.e., by either calling `set_tx_gain()` or `set_rx_gain()`), then the gain
|
|
level will be kept constant after retuning, but the power level will likely have
|
|
changed. If the power reference level was set last (by either calling
|
|
`set_rx_power_reference()` or `set_tx_power_reference()`), then UHD will attempt
|
|
to maintain a constant power reference level, which means UHD will likely have
|
|
to modify the relative gain values. To get the exact power level after a retune,
|
|
read back the current power reference level (`get_rx_power_reference()` or
|
|
`get_tx_power_reference()`).
|
|
|
|
The following example shows how the APIs behave for reception. For transmission,
|
|
the behaviour is the same (just replace 'rx' with 'tx' in the API calls). Note
|
|
that the determination of relative gain or power is retained over retunes is
|
|
independent for TX and RX.
|
|
|
|
~~~{.cpp}
|
|
// usrp is a multi_usrp object, f0 and f1 are valid frequencies
|
|
usrp->set_rx_frequency(f0);
|
|
usrp->set_rx_gain(10);
|
|
// This should print '10', or the closest coerced value:
|
|
std::cout << usrp->get_rx_gain() << std::endl;
|
|
// This can print anything, depending on device and calibration data:
|
|
std::cout << usrp->get_rx_power_reference() << std::endl;
|
|
usrp->set_rx_frequency(f1);
|
|
// This should still print '10', or the closest coerced value:
|
|
std::cout << usrp->get_rx_gain() << std::endl;
|
|
// This can print anything, and possibly not the same value as before
|
|
std::cout << usrp->get_rx_power_reference() << std::endl;
|
|
usrp->set_rx_power_reference(-20);
|
|
// This should print -20, assuming the device can transmit at that power:
|
|
std::cout << usrp->get_rx_power_reference() << std::endl;
|
|
// This will print the current gain value, whatever that is
|
|
std::cout << usrp->get_rx_gain() << std::endl;
|
|
usrp->set_rx_frequency(f0);
|
|
// This should still print -20, or the closest coerced value
|
|
std::cout << usrp->get_rx_power_reference() << std::endl;
|
|
// This will print the current gain value, possibly not the same as before
|
|
std::cout << usrp->get_rx_gain() << std::endl;
|
|
~~~
|
|
|
|
\section power_implementation Device Implementations of Power Level APIs
|
|
|
|
Under the hood, this API call will affect any gain stage that it needs to
|
|
affect. It is possible to read back the current gain settings by calling
|
|
`get_tx_gain()` or `get_rx_gain()`. However, changing the gain settings by calling
|
|
`set_tx_gain()` or `set_rx_gain()` will cause the power level to change.
|
|
|
|
The specific implementation of this API is very device-specific. Refer to the
|
|
individual USRP manual pages for more details.
|
|
|
|
\section power_storage Calibration Table Storage
|
|
|
|
UHD needs to store calibration tables to be able to map reference power levels
|
|
to settings for the individual gain stages of each USRP and/or daughterboard.
|
|
These tables can be stored in three different ways:
|
|
|
|
- Hard-coded as part of UHD. If a gain table is hard-coded as part of UHD, it
|
|
means that the gain table is considered an average table for a given device.
|
|
Its accuracy may vary a lot, as it is not calibrated to a specific device
|
|
and/or environment.
|
|
- In the device EEPROM. By storing calibration data in an EEPROM on the device,
|
|
it is possible to use the same calibration data even when using different host
|
|
computers. Calibration data in EEPROMs can also be updated to account for
|
|
different environments, aging of components, or anything else.
|
|
- On a file. Calibration data in a file is the most flexible, it can be replaced
|
|
easily. It is however local to the computer that stores the calibration data.
|
|
|
|
Access to the calibration data usually is done by using the uhd::usrp::cal::database
|
|
class. Calibration data is identified by two identifiers, a key, and a serial.
|
|
|
|
The \b key is a string that identifies a particular hardware path for an RF signal.
|
|
For example, the B200 and B210 use the key `b2xx_power_cal_rx_rx2` to identify
|
|
the RF path from the RX2 SMA connector on a B200mini all the way to the RFIC. On
|
|
the B210, the same key is used for channels A and B, because the RF paths are
|
|
identical (the PCB traces are symmetrical, and the components are the same as well).
|
|
On the B200mini however, the RF path from RX2 to the RFIC is different (the PCB
|
|
is smaller, for example) and thus the key is different (`b2xxmini_power_cal_rx_rx2`).
|
|
|
|
The \b serial is usually derived from the serial of the DUT itself, but may also
|
|
include other information, such as the channel. On the B210, the calibration
|
|
serial consists of the motherboard serial plus the channel identifier (for
|
|
example, if the device had a serial of `FFF1234`, the two calibration serials
|
|
would be `FFF1234#A` and `FFF1234#B`. This way, the two channels can have their
|
|
own calibration data.
|
|
|
|
The key and serial that are used for a specific device can be queried from the
|
|
device by either calling uhd::usrp::multi_usrp::get_usrp_rx_info() when using
|
|
the multi_usrp API, or calling uhd::rfnoc::radio_control::get_rx_power_ref_keys().
|
|
Equivalent calls for TX calibration are also available.
|
|
|
|
If calibration data is hard-coded as part of UHD, the serial doesn't apply.
|
|
That is because the only calibration data hard-coded in UHD is data that can be
|
|
applied to all devices, and has been vetted against several measurement data
|
|
sets. Such data will carry a much higher calibration error than specifically
|
|
generated calibration data.
|
|
|
|
\section power_usercal Calibrating your own device
|
|
|
|
If UHD does not ship its own calibration data for a device, or if the power
|
|
calibration must be finely tuned, it is necessary to manually calibrate the device.
|
|
|
|
In order to calibrate the transmit power, a calibrated power meter is required.
|
|
To calibrate the receive power, a calibrated signal generator is required. Note
|
|
that it is possible to use a calibrated USRP as power meter / signal generator.
|
|
A calibrated USRP can thus be used to calibrate another USRP.
|
|
|
|
The calibration is performed using the `uhd_power_cal.py` utility, which is
|
|
usually installed into the utilities directory (for example, `/usr/lib/uhd/utils`
|
|
on some Linux distributions). It requires the Python API of UHD.
|
|
|
|
The tool will control both the DUT (i.e., the USRP that is to be calibrated) as
|
|
well as the measurement device (power meter or signal generator).
|
|
UHD ships with some drivers for measurement devices, but can be extended for
|
|
others easily (see \ref power_usercal_extend).
|
|
|
|
In order to run a calibration, the measurement device and the DUT need to be
|
|
connected to the host PC on which the calbration measurement is performed.
|
|
The following command will calibrate a B200 transmit power using a VISA-based
|
|
power meter:
|
|
|
|
uhd_power_cal.py --args type=b200 -d tx --meas-dev visa
|
|
|
|
By default, it will try and calibrate all channels (for a B210). The calibration
|
|
can be further constrained by limiting the frequency range, and the gain/frequency
|
|
steps (for more coarse or fine calibration data).
|
|
|
|
The tool has hard-coded some sensible defaults for most devices, such as frequency
|
|
and gain steps.
|
|
|
|
\subsection power_cal_switch Calibrating multiple paths at once
|
|
|
|
`uhd_power_cal.py` is able to calibrate all RF paths in a single run. Depending
|
|
on the measurement setup it might be necessary to change the cabling between
|
|
the measurement runs for the different paths. For this purpose the script
|
|
supports a switch parameter.
|
|
|
|
If no switch is selected the script chooses the "manual" switch as default which
|
|
asks the user to upate the cabeling before a new RF path is measured. If you
|
|
do not have the need to change cabling (e.g. calibrating a single path only)
|
|
you can pass `--switch-option mode=auto` to prevent the script stopping before
|
|
the real measurement.
|
|
|
|
UHD facilitates devices that supports the niswitch API. These devices are
|
|
enabled using `--switch niswitch`. In default setup the port `comA` is used
|
|
to switch between the RF paths. To use port `X` pass `--switch-option port=comX`
|
|
as parameter. The channels of the switch must be cabled in the order the script
|
|
runs the calibration, so the first RF path goes to `chX1` the second to `chX2`
|
|
and so on. If your setup does not follow this rule you have to pass the order
|
|
of channels to the script using `--channels ch1,ch2` where the channels are
|
|
given in the order they are connected to the switch.
|
|
|
|
\subsection power_usercal_extend Extending the calibration utility for custom drivers
|
|
|
|
\subsubsection power_usercal_extend_visa VISA/SCPI based devices
|
|
|
|
Measurement devices using SCPI commands are particularly easy to add. UHD uses
|
|
PyVISA to access VISA-based devices, so make sure to follow the PyVISA manual
|
|
to set that driver up. For example, USB-based power meters may require setting
|
|
additional privileges or system settings in order for user-space utilities to
|
|
be able to communicate with them.
|
|
|
|
Assuming PyVISA is working, and your VISA-based measurement device is reachable
|
|
from PyVISA, exposing your VISA-based device is done by creating a Python
|
|
module for your device. Assume the file is called `myvisa.py` and has the
|
|
following content:
|
|
|
|
~~~{.py}
|
|
from uhd.usrp.cal.visa import VISADevice
|
|
|
|
class MyVISADevice(VISADevice):
|
|
res_ids = {r'::1234::': "My VISA Device"}
|
|
|
|
def init_power_meter(self):
|
|
self.res.write("INIT") # Put appropriate SCPI commands here
|
|
|
|
# ... all other API calls ...
|
|
~~~
|
|
|
|
Now you can run the power calibration utility as such:
|
|
|
|
uhd_power_cal.py --meas-dev visa -o import=myvisa [other args]
|
|
|
|
This will try and import `myvisa`, and will automatically detect classes within
|
|
that file. If the VISA device used for calibration matches the resource ID (in
|
|
this example, it needs to contain the substring `::1234::`), this class will be
|
|
chosen. On success, the utility should print a string like
|
|
"Found VISA device: My VISA Device".
|
|
|
|
The file `visa.py` within the UHD Python module is a useful reference for
|
|
writing custom VISA drivers.
|
|
|
|
\subsubsection power_usercal_extend_generic Other measurement devices
|
|
|
|
If a measurement device is not a VISA device, the procedure above can still
|
|
be used. The only requirement is that the devices can be controlled from Python.
|
|
The driver classes must derive either from `uhd.usrp.cal.meas_device.PowerMeterBase`
|
|
or `uhd.usrp.cal.meas_device.PowerGeneratorBase`, respectively.
|
|
|
|
The file `meas_device.py` in the UHD Python modulues contains the default
|
|
drivers, as well as examples and further documentation.
|
|
|
|
*/
|
|
// vim:ft=doxygen:
|