USRP Hardware Driver and USRP Manual Version: 4.1.0.3
UHD and USRP Manual
Power Level Controls

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.

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.

RX and TX reference power levels

RX reference power level:

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

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

TX reference power level:

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

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

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.

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.

// 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;

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.

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 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 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.

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 Extending the calibration utility for custom drivers).

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.

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.

Extending the calibration utility for custom drivers

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:

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.

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.