/*! \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:
