/*! \page page_power Power Level Controls \tableofcontents \section power_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 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 ~~~{.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 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 ~~~{.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 calbration must be finely tuned, it is necessary to manually calbrate 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_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: