aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--host/include/uhd/cal/CMakeLists.txt5
-rw-r--r--host/include/uhd/cal/pwr_cal.fbs46
-rw-r--r--host/include/uhd/cal/pwr_cal.hpp151
-rw-r--r--host/include/uhd/cal/pwr_cal_generated.h340
-rw-r--r--host/lib/cal/CMakeLists.txt1
-rw-r--r--host/lib/cal/cal_python.hpp46
-rw-r--r--host/lib/cal/pwr_cal.cpp347
-rw-r--r--host/python/uhd/usrp/cal/libtypes.py1
-rw-r--r--host/tests/CMakeLists.txt1
-rw-r--r--host/tests/cal_data_gain_pwr_test.cpp134
10 files changed, 1071 insertions, 1 deletions
diff --git a/host/include/uhd/cal/CMakeLists.txt b/host/include/uhd/cal/CMakeLists.txt
index 8d8dfa11e..38b4885ef 100644
--- a/host/include/uhd/cal/CMakeLists.txt
+++ b/host/include/uhd/cal/CMakeLists.txt
@@ -5,16 +5,19 @@
#
UHD_INSTALL(FILES
- database.hpp
container.hpp
+ database.hpp
iq_cal.hpp
+ pwr_cal.hpp
iq_cal_generated.h
+ pwr_cal_generated.h
DESTINATION ${INCLUDE_DIR}/uhd/cal
COMPONENT headers
)
UHD_INSTALL(FILES
iq_cal.fbs
+ pwr_cal.fbs
DESTINATION ${PKG_DATA_DIR}/cal
COMPONENT headers
)
diff --git a/host/include/uhd/cal/pwr_cal.fbs b/host/include/uhd/cal/pwr_cal.fbs
new file mode 100644
index 000000000..7cf66feee
--- /dev/null
+++ b/host/include/uhd/cal/pwr_cal.fbs
@@ -0,0 +1,46 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+// Calibration Table for power calibration coefficents.
+// See also pwr_cal.hpp
+
+include "cal_metadata.fbs";
+
+namespace uhd.usrp.cal;
+
+// Maps a gain value (dB) to a power value (dBm)
+struct PowerMap
+{
+ gain: double (key);
+ power_dbm: double;
+}
+
+// For a given frequency, stores all gain -> power measurements, as well as a
+// max and min power level the device can reasonably produce.
+table FreqPowerMap
+{
+ freq: ulong (key);
+ powers: [PowerMap];
+ min_power: double; // Minimum power achievable at this frequency
+ max_power: double; // Maximum power achievable at this frequency
+}
+
+// Given a temperature, store a gain table
+table TempFreqMap
+{
+ temperature: int (key); // Temperature in C
+ freqs: [FreqPowerMap];
+}
+
+table PowerCal
+{
+ metadata: Metadata;
+ temp_freq_map: [TempFreqMap];
+ ref_gain: int = -1;
+}
+
+root_type PowerCal;
+file_identifier "dB/m"; // dB per dBm
diff --git a/host/include/uhd/cal/pwr_cal.hpp b/host/include/uhd/cal/pwr_cal.hpp
new file mode 100644
index 000000000..b30c6dfc2
--- /dev/null
+++ b/host/include/uhd/cal/pwr_cal.hpp
@@ -0,0 +1,151 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include <uhd/cal/container.hpp>
+#include <uhd/config.hpp>
+#include <uhd/types/ranges.hpp>
+#include <boost/optional.hpp>
+#include <map>
+
+namespace uhd { namespace usrp { namespace cal {
+
+/*! Class that stores power levels (in dBm) at various gains, frequencies, and
+ * temperatures.
+ *
+ * This container class is suitable for all cases where devices want to store a
+ * gain-to-power mapping from a single, overall gain value.
+ *
+ * The underlying data structure stores the power level for every gain/frequency
+ * point that is part of this data set. It can also do a reverse look-up of gain
+ * values for a given power value.
+ *
+ * The interpolation algorithms assume a monotonic gain/power profile, i.e.,
+ * f(gain) = power is monotonically increasing. If the power is not monotonically
+ * increasing, the interpolation algorithms still work, but get_gain() might
+ * produce a greater than necessary interpolation error. For that case, this
+ * class provides get_gain_coerced(), which helps with both coercion of the
+ * interpolated gain into a gain value that can be used at the call site, as
+ * well as minimizing the effect on the application using this container.
+ *
+ * All interpolation algorithms first interpolate temperature by finding the
+ * nearest available temperature data. For example, if the data set includes
+ * calibration data for 40C and 50C, and the actual temperature is measured at
+ * 48C, then the data set for 50C is used, and the data set for 40C is not
+ * considered at all.
+ * Within a data set, frequency and gain are interpolated in two dimensions (the
+ * same is true for frequency and power for get_gain() and get_gain_coerced())
+ * using a bilinear interpolation.
+ */
+class UHD_API pwr_cal : public container
+{
+public:
+ using sptr = std::shared_ptr<pwr_cal>;
+
+ /*! Add or update a power level data point
+ *
+ * Note: Power measurements can only be written atomically. It is not
+ * possible to add individual gain/power points using this method.
+ *
+ * \param gain_power_map A mapping gain -> power (dB -> dBm) for all
+ * measured gain points for this frequency.
+ * \param max_power The maximum available power for this frequency.
+ * \param min_power The minimum available power for this frequency.
+ * \param freq The frequency at which this power level was measured
+ * \param temperature The temperature at which this power level was measured,
+ * in Celsius. This parameter is optional, the return
+ * value for get_temperature() is used if no temperature
+ * is provided here.
+ */
+ virtual void add_power_table(const std::map<double, double>& gain_power_map,
+ const double max_power,
+ const double min_power,
+ const double freq,
+ const boost::optional<int> temperature = boost::none) = 0;
+
+ /*! Clear all stored values
+ *
+ * Note: All of the getters will throw a uhd::assertion_error if called after
+ * clearing the data.
+ */
+ virtual void clear() = 0;
+
+ /*! Set the current default temperature
+ *
+ * Some of the API calls have a optional temperature argument. However, in
+ * practice, it is often useful to set an ambient temperature once, or only
+ * when it has changed, and then use a default temperature instead of
+ * providing the temperature on every call. This sets the default
+ * temperature.
+ */
+ virtual void set_temperature(const int temperature) = 0;
+
+ //! Return the current default temperature
+ virtual int get_temperature() const = 0;
+
+ /*! Set the reference gain point, i.e., the gain value where by definition
+ * the difference between linearized power and measured power is zero.
+ *
+ * Currently unused.
+ */
+ virtual void set_ref_gain(const double gain) = 0;
+
+ /*! Return the current reference gain point.
+ *
+ * Currently unused.
+ */
+ virtual double get_ref_gain() const = 0;
+
+ /*! Return the min and max power available for this frequency
+ */
+ virtual uhd::meta_range_t get_power_limits(const double freq,
+ const boost::optional<int> temperature = boost::none) const = 0;
+
+ /*! Returns the power at a gain value.
+ *
+ * This will interpolate from the given data set to obtain a power value if
+ * gain and frequency are not exactly within the given data set.
+ *
+ * \param gain The gain at which we are checking the power
+ * \param freq The frequency at which we are checking the power
+ * \param temperature The temperature at which we are checking the power. If
+ * none is given, uses the current default temperature
+ * (see set_temperature()).
+ */
+ virtual double get_power(const double gain,
+ const double freq,
+ const boost::optional<int> temperature = boost::none) const = 0;
+
+ /*! Look up a gain value from a power value.
+ *
+ * This is the reverse function to get_power(). Like get_power(), it will
+ * interpolate in two dimensions (linearly) if the requested power value is
+ * not part of the measurement data.
+ *
+ * Note: \p power_dbm is coerced into the available power range using
+ * get_power_limits().
+ *
+ * \param power_dbm The power (in dBm) at which we are getting the gain value for
+ * \param freq The frequency at which we are finding the gain
+ * \param temperature The temperature at which we are finding the gain. If
+ * none is given, uses the current default temperature
+ * (see set_temperature()).
+ */
+ virtual double get_gain(const double power_dbm,
+ const double freq,
+ const boost::optional<int> temperature = boost::none) const = 0;
+
+ //! Factory for new cal data sets
+ static sptr make(
+ const std::string& name, const std::string& serial, const uint64_t timestamp);
+
+ //! Default factory
+ static sptr make();
+};
+
+}}} // namespace uhd::usrp::cal
+
diff --git a/host/include/uhd/cal/pwr_cal_generated.h b/host/include/uhd/cal/pwr_cal_generated.h
new file mode 100644
index 000000000..f7ea8d8a7
--- /dev/null
+++ b/host/include/uhd/cal/pwr_cal_generated.h
@@ -0,0 +1,340 @@
+// automatically generated by the FlatBuffers compiler, do not modify
+
+
+#ifndef FLATBUFFERS_GENERATED_PWRCAL_UHD_USRP_CAL_H_
+#define FLATBUFFERS_GENERATED_PWRCAL_UHD_USRP_CAL_H_
+
+#include "flatbuffers/flatbuffers.h"
+
+#include "cal_metadata_generated.h"
+
+namespace uhd {
+namespace usrp {
+namespace cal {
+
+struct PowerMap;
+
+struct FreqPowerMap;
+struct FreqPowerMapBuilder;
+
+struct TempFreqMap;
+struct TempFreqMapBuilder;
+
+struct PowerCal;
+struct PowerCalBuilder;
+
+FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(8) PowerMap FLATBUFFERS_FINAL_CLASS {
+ private:
+ double gain_;
+ double power_dbm_;
+
+ public:
+ PowerMap() {
+ memset(static_cast<void *>(this), 0, sizeof(PowerMap));
+ }
+ PowerMap(double _gain, double _power_dbm)
+ : gain_(flatbuffers::EndianScalar(_gain)),
+ power_dbm_(flatbuffers::EndianScalar(_power_dbm)) {
+ }
+ double gain() const {
+ return flatbuffers::EndianScalar(gain_);
+ }
+ bool KeyCompareLessThan(const PowerMap *o) const {
+ return gain() < o->gain();
+ }
+ int KeyCompareWithValue(double val) const {
+ return static_cast<int>(gain() > val) - static_cast<int>(gain() < val);
+ }
+ double power_dbm() const {
+ return flatbuffers::EndianScalar(power_dbm_);
+ }
+};
+FLATBUFFERS_STRUCT_END(PowerMap, 16);
+
+struct FreqPowerMap FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+ typedef FreqPowerMapBuilder Builder;
+ enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
+ VT_FREQ = 4,
+ VT_POWERS = 6,
+ VT_MIN_POWER = 8,
+ VT_MAX_POWER = 10
+ };
+ uint64_t freq() const {
+ return GetField<uint64_t>(VT_FREQ, 0);
+ }
+ bool KeyCompareLessThan(const FreqPowerMap *o) const {
+ return freq() < o->freq();
+ }
+ int KeyCompareWithValue(uint64_t val) const {
+ return static_cast<int>(freq() > val) - static_cast<int>(freq() < val);
+ }
+ const flatbuffers::Vector<const uhd::usrp::cal::PowerMap *> *powers() const {
+ return GetPointer<const flatbuffers::Vector<const uhd::usrp::cal::PowerMap *> *>(VT_POWERS);
+ }
+ double min_power() const {
+ return GetField<double>(VT_MIN_POWER, 0.0);
+ }
+ double max_power() const {
+ return GetField<double>(VT_MAX_POWER, 0.0);
+ }
+ bool Verify(flatbuffers::Verifier &verifier) const {
+ return VerifyTableStart(verifier) &&
+ VerifyField<uint64_t>(verifier, VT_FREQ) &&
+ VerifyOffset(verifier, VT_POWERS) &&
+ verifier.VerifyVector(powers()) &&
+ VerifyField<double>(verifier, VT_MIN_POWER) &&
+ VerifyField<double>(verifier, VT_MAX_POWER) &&
+ verifier.EndTable();
+ }
+};
+
+struct FreqPowerMapBuilder {
+ typedef FreqPowerMap Table;
+ flatbuffers::FlatBufferBuilder &fbb_;
+ flatbuffers::uoffset_t start_;
+ void add_freq(uint64_t freq) {
+ fbb_.AddElement<uint64_t>(FreqPowerMap::VT_FREQ, freq, 0);
+ }
+ void add_powers(flatbuffers::Offset<flatbuffers::Vector<const uhd::usrp::cal::PowerMap *>> powers) {
+ fbb_.AddOffset(FreqPowerMap::VT_POWERS, powers);
+ }
+ void add_min_power(double min_power) {
+ fbb_.AddElement<double>(FreqPowerMap::VT_MIN_POWER, min_power, 0.0);
+ }
+ void add_max_power(double max_power) {
+ fbb_.AddElement<double>(FreqPowerMap::VT_MAX_POWER, max_power, 0.0);
+ }
+ explicit FreqPowerMapBuilder(flatbuffers::FlatBufferBuilder &_fbb)
+ : fbb_(_fbb) {
+ start_ = fbb_.StartTable();
+ }
+ FreqPowerMapBuilder &operator=(const FreqPowerMapBuilder &);
+ flatbuffers::Offset<FreqPowerMap> Finish() {
+ const auto end = fbb_.EndTable(start_);
+ auto o = flatbuffers::Offset<FreqPowerMap>(end);
+ return o;
+ }
+};
+
+inline flatbuffers::Offset<FreqPowerMap> CreateFreqPowerMap(
+ flatbuffers::FlatBufferBuilder &_fbb,
+ uint64_t freq = 0,
+ flatbuffers::Offset<flatbuffers::Vector<const uhd::usrp::cal::PowerMap *>> powers = 0,
+ double min_power = 0.0,
+ double max_power = 0.0) {
+ FreqPowerMapBuilder builder_(_fbb);
+ builder_.add_max_power(max_power);
+ builder_.add_min_power(min_power);
+ builder_.add_freq(freq);
+ builder_.add_powers(powers);
+ return builder_.Finish();
+}
+
+inline flatbuffers::Offset<FreqPowerMap> CreateFreqPowerMapDirect(
+ flatbuffers::FlatBufferBuilder &_fbb,
+ uint64_t freq = 0,
+ std::vector<uhd::usrp::cal::PowerMap> *powers = nullptr,
+ double min_power = 0.0,
+ double max_power = 0.0) {
+ auto powers__ = powers ? _fbb.CreateVectorOfSortedStructs<uhd::usrp::cal::PowerMap>(powers) : 0;
+ return uhd::usrp::cal::CreateFreqPowerMap(
+ _fbb,
+ freq,
+ powers__,
+ min_power,
+ max_power);
+}
+
+struct TempFreqMap FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+ typedef TempFreqMapBuilder Builder;
+ enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
+ VT_TEMPERATURE = 4,
+ VT_FREQS = 6
+ };
+ int32_t temperature() const {
+ return GetField<int32_t>(VT_TEMPERATURE, 0);
+ }
+ bool KeyCompareLessThan(const TempFreqMap *o) const {
+ return temperature() < o->temperature();
+ }
+ int KeyCompareWithValue(int32_t val) const {
+ return static_cast<int>(temperature() > val) - static_cast<int>(temperature() < val);
+ }
+ const flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::FreqPowerMap>> *freqs() const {
+ return GetPointer<const flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::FreqPowerMap>> *>(VT_FREQS);
+ }
+ bool Verify(flatbuffers::Verifier &verifier) const {
+ return VerifyTableStart(verifier) &&
+ VerifyField<int32_t>(verifier, VT_TEMPERATURE) &&
+ VerifyOffset(verifier, VT_FREQS) &&
+ verifier.VerifyVector(freqs()) &&
+ verifier.VerifyVectorOfTables(freqs()) &&
+ verifier.EndTable();
+ }
+};
+
+struct TempFreqMapBuilder {
+ typedef TempFreqMap Table;
+ flatbuffers::FlatBufferBuilder &fbb_;
+ flatbuffers::uoffset_t start_;
+ void add_temperature(int32_t temperature) {
+ fbb_.AddElement<int32_t>(TempFreqMap::VT_TEMPERATURE, temperature, 0);
+ }
+ void add_freqs(flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::FreqPowerMap>>> freqs) {
+ fbb_.AddOffset(TempFreqMap::VT_FREQS, freqs);
+ }
+ explicit TempFreqMapBuilder(flatbuffers::FlatBufferBuilder &_fbb)
+ : fbb_(_fbb) {
+ start_ = fbb_.StartTable();
+ }
+ TempFreqMapBuilder &operator=(const TempFreqMapBuilder &);
+ flatbuffers::Offset<TempFreqMap> Finish() {
+ const auto end = fbb_.EndTable(start_);
+ auto o = flatbuffers::Offset<TempFreqMap>(end);
+ return o;
+ }
+};
+
+inline flatbuffers::Offset<TempFreqMap> CreateTempFreqMap(
+ flatbuffers::FlatBufferBuilder &_fbb,
+ int32_t temperature = 0,
+ flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::FreqPowerMap>>> freqs = 0) {
+ TempFreqMapBuilder builder_(_fbb);
+ builder_.add_freqs(freqs);
+ builder_.add_temperature(temperature);
+ return builder_.Finish();
+}
+
+inline flatbuffers::Offset<TempFreqMap> CreateTempFreqMapDirect(
+ flatbuffers::FlatBufferBuilder &_fbb,
+ int32_t temperature = 0,
+ std::vector<flatbuffers::Offset<uhd::usrp::cal::FreqPowerMap>> *freqs = nullptr) {
+ auto freqs__ = freqs ? _fbb.CreateVectorOfSortedTables<uhd::usrp::cal::FreqPowerMap>(freqs) : 0;
+ return uhd::usrp::cal::CreateTempFreqMap(
+ _fbb,
+ temperature,
+ freqs__);
+}
+
+struct PowerCal FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+ typedef PowerCalBuilder Builder;
+ enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
+ VT_METADATA = 4,
+ VT_TEMP_FREQ_MAP = 6,
+ VT_REF_GAIN = 8
+ };
+ const Metadata *metadata() const {
+ return GetPointer<const Metadata *>(VT_METADATA);
+ }
+ const flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::TempFreqMap>> *temp_freq_map() const {
+ return GetPointer<const flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::TempFreqMap>> *>(VT_TEMP_FREQ_MAP);
+ }
+ int32_t ref_gain() const {
+ return GetField<int32_t>(VT_REF_GAIN, -1);
+ }
+ bool Verify(flatbuffers::Verifier &verifier) const {
+ return VerifyTableStart(verifier) &&
+ VerifyOffset(verifier, VT_METADATA) &&
+ verifier.VerifyTable(metadata()) &&
+ VerifyOffset(verifier, VT_TEMP_FREQ_MAP) &&
+ verifier.VerifyVector(temp_freq_map()) &&
+ verifier.VerifyVectorOfTables(temp_freq_map()) &&
+ VerifyField<int32_t>(verifier, VT_REF_GAIN) &&
+ verifier.EndTable();
+ }
+};
+
+struct PowerCalBuilder {
+ typedef PowerCal Table;
+ flatbuffers::FlatBufferBuilder &fbb_;
+ flatbuffers::uoffset_t start_;
+ void add_metadata(flatbuffers::Offset<Metadata> metadata) {
+ fbb_.AddOffset(PowerCal::VT_METADATA, metadata);
+ }
+ void add_temp_freq_map(flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::TempFreqMap>>> temp_freq_map) {
+ fbb_.AddOffset(PowerCal::VT_TEMP_FREQ_MAP, temp_freq_map);
+ }
+ void add_ref_gain(int32_t ref_gain) {
+ fbb_.AddElement<int32_t>(PowerCal::VT_REF_GAIN, ref_gain, -1);
+ }
+ explicit PowerCalBuilder(flatbuffers::FlatBufferBuilder &_fbb)
+ : fbb_(_fbb) {
+ start_ = fbb_.StartTable();
+ }
+ PowerCalBuilder &operator=(const PowerCalBuilder &);
+ flatbuffers::Offset<PowerCal> Finish() {
+ const auto end = fbb_.EndTable(start_);
+ auto o = flatbuffers::Offset<PowerCal>(end);
+ return o;
+ }
+};
+
+inline flatbuffers::Offset<PowerCal> CreatePowerCal(
+ flatbuffers::FlatBufferBuilder &_fbb,
+ flatbuffers::Offset<Metadata> metadata = 0,
+ flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::TempFreqMap>>> temp_freq_map = 0,
+ int32_t ref_gain = -1) {
+ PowerCalBuilder builder_(_fbb);
+ builder_.add_ref_gain(ref_gain);
+ builder_.add_temp_freq_map(temp_freq_map);
+ builder_.add_metadata(metadata);
+ return builder_.Finish();
+}
+
+inline flatbuffers::Offset<PowerCal> CreatePowerCalDirect(
+ flatbuffers::FlatBufferBuilder &_fbb,
+ flatbuffers::Offset<Metadata> metadata = 0,
+ std::vector<flatbuffers::Offset<uhd::usrp::cal::TempFreqMap>> *temp_freq_map = nullptr,
+ int32_t ref_gain = -1) {
+ auto temp_freq_map__ = temp_freq_map ? _fbb.CreateVectorOfSortedTables<uhd::usrp::cal::TempFreqMap>(temp_freq_map) : 0;
+ return uhd::usrp::cal::CreatePowerCal(
+ _fbb,
+ metadata,
+ temp_freq_map__,
+ ref_gain);
+}
+
+inline const uhd::usrp::cal::PowerCal *GetPowerCal(const void *buf) {
+ return flatbuffers::GetRoot<uhd::usrp::cal::PowerCal>(buf);
+}
+
+inline const uhd::usrp::cal::PowerCal *GetSizePrefixedPowerCal(const void *buf) {
+ return flatbuffers::GetSizePrefixedRoot<uhd::usrp::cal::PowerCal>(buf);
+}
+
+inline const char *PowerCalIdentifier() {
+ return "dB/m";
+}
+
+inline bool PowerCalBufferHasIdentifier(const void *buf) {
+ return flatbuffers::BufferHasIdentifier(
+ buf, PowerCalIdentifier());
+}
+
+inline bool VerifyPowerCalBuffer(
+ flatbuffers::Verifier &verifier) {
+ return verifier.VerifyBuffer<uhd::usrp::cal::PowerCal>(PowerCalIdentifier());
+}
+
+inline bool VerifySizePrefixedPowerCalBuffer(
+ flatbuffers::Verifier &verifier) {
+ return verifier.VerifySizePrefixedBuffer<uhd::usrp::cal::PowerCal>(PowerCalIdentifier());
+}
+
+inline void FinishPowerCalBuffer(
+ flatbuffers::FlatBufferBuilder &fbb,
+ flatbuffers::Offset<uhd::usrp::cal::PowerCal> root) {
+ fbb.Finish(root, PowerCalIdentifier());
+}
+
+inline void FinishSizePrefixedPowerCalBuffer(
+ flatbuffers::FlatBufferBuilder &fbb,
+ flatbuffers::Offset<uhd::usrp::cal::PowerCal> root) {
+ fbb.FinishSizePrefixed(root, PowerCalIdentifier());
+}
+
+} // namespace cal
+} // namespace usrp
+} // namespace uhd
+
+#endif // FLATBUFFERS_GENERATED_PWRCAL_UHD_USRP_CAL_H_
diff --git a/host/lib/cal/CMakeLists.txt b/host/lib/cal/CMakeLists.txt
index fb1f4b0e3..a50d654ff 100644
--- a/host/lib/cal/CMakeLists.txt
+++ b/host/lib/cal/CMakeLists.txt
@@ -11,5 +11,6 @@ include_directories(${CMAKE_SOURCE_DIR}/lib/deps/flatbuffers/include)
LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/database.cpp
${CMAKE_CURRENT_SOURCE_DIR}/iq_cal.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/pwr_cal.cpp
)
diff --git a/host/lib/cal/cal_python.hpp b/host/lib/cal/cal_python.hpp
index e8f19eef9..5de9e8288 100644
--- a/host/lib/cal/cal_python.hpp
+++ b/host/lib/cal/cal_python.hpp
@@ -9,7 +9,16 @@
#include <uhd/cal/database.hpp>
#include <uhd/cal/iq_cal.hpp>
+#include <uhd/cal/pwr_cal.hpp>
#include <uhd/utils/interpolation.hpp>
+#include <pybind11/stl.h>
+
+namespace pybind11 { namespace detail {
+template <typename T>
+struct type_caster<boost::optional<T>> : optional_caster<boost::optional<T>>
+{
+};
+}}
std::vector<uint8_t> pybytes_to_vector(const py::bytes& data)
{
@@ -94,6 +103,43 @@ void export_cal(py::module& m)
py::arg("suppression_abs") = 0,
py::arg("suppression_delta") = 0)
.def("clear", &iq_cal::clear);
+
+ py::class_<pwr_cal, container, pwr_cal::sptr>(m, "pwr_cal")
+ .def(py::init([](const std::string& name,
+ const std::string& serial,
+ const uint64_t timestamp) {
+ return pwr_cal::make(name, serial, timestamp);
+ }))
+ .def(py::init([]() { return pwr_cal::make(); }))
+ .def(py::init([](const py::bytes data) {
+ return container::make<pwr_cal>(pybytes_to_vector(data));
+ }))
+ .def("add_power_table",
+ &pwr_cal::add_power_table,
+ py::arg("gain_power_map"),
+ py::arg("max_power"),
+ py::arg("min_power"),
+ py::arg("freq"),
+ py::arg("temperature") = boost::optional<int>())
+ .def("clear", &pwr_cal::clear)
+ .def("set_temperature", &pwr_cal::set_temperature)
+ .def("get_temperature", &pwr_cal::get_temperature)
+ .def("set_ref_gain", &pwr_cal::set_ref_gain)
+ .def("get_ref_gain", &pwr_cal::get_ref_gain)
+ .def("get_power_limits",
+ &pwr_cal::get_power_limits,
+ py::arg("freq"),
+ py::arg("temperature") = boost::optional<int>())
+ .def("get_power",
+ &pwr_cal::get_power,
+ py::arg("gain"),
+ py::arg("freq"),
+ py::arg("temperature") = boost::optional<int>())
+ .def("get_gain",
+ &pwr_cal::get_gain,
+ py::arg("power_dbm"),
+ py::arg("freq"),
+ py::arg("temperature") = boost::optional<int>());
}
#endif /* INCLUDED_UHD_CAL_PYTHON_HPP */
diff --git a/host/lib/cal/pwr_cal.cpp b/host/lib/cal/pwr_cal.cpp
new file mode 100644
index 000000000..3ff199598
--- /dev/null
+++ b/host/lib/cal/pwr_cal.cpp
@@ -0,0 +1,347 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/cal/pwr_cal.hpp>
+#include <uhd/cal/pwr_cal_generated.h>
+#include <uhd/exception.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/math.hpp>
+#include <uhdlib/utils/interpolation.hpp>
+#include <map>
+#include <string>
+
+using namespace uhd::usrp::cal;
+using namespace uhd::math;
+
+namespace {
+
+//! We use the NIST normal temperature
+constexpr int NORMAL_TEMPERATURE = 20;
+
+constexpr size_t VERSION_MAJOR = 1;
+constexpr size_t VERSION_MINOR = 0;
+
+//! Return map with keys as values and vice versa
+template <typename map_type>
+map_type reverse_map(const map_type& map)
+{
+ map_type result;
+ std::transform(map.cbegin(),
+ map.cend(),
+ std::inserter(result, result.end()),
+ [](const typename map_type::value_type& entry) {
+ return std::pair<typename map_type::mapped_type, typename map_type::key_type>(
+ entry.second, entry.first);
+ });
+ return result;
+}
+
+} // namespace
+
+
+class pwr_cal_impl : public pwr_cal
+{
+public:
+ pwr_cal_impl(const std::string& name = "",
+ const std::string& serial = "",
+ const uint64_t timestamp = 0)
+ : _name(name), _serial(serial), _timestamp(timestamp)
+ {
+ }
+
+ /**************************************************************************
+ * Container API (Basics)
+ *************************************************************************/
+ std::string get_name() const
+ {
+ return _name;
+ }
+
+ std::string get_serial() const
+ {
+ return _serial;
+ }
+
+ uint64_t get_timestamp() const
+ {
+ return _timestamp;
+ }
+
+ /**************************************************************************
+ * Specific APIs
+ *************************************************************************/
+ void add_power_table(const std::map<double, double>& gain_power_map,
+ const double min_power,
+ const double max_power,
+ const double freq,
+ const boost::optional<int> temperature = boost::none)
+ {
+ if (min_power > max_power) {
+ throw uhd::runtime_error(
+ std::string("Invalid min/max power levels: Min power must be smaller "
+ "than max power! (Is: "
+ + std::to_string(min_power) + " dBm, "
+ + std::to_string(max_power) + " dBm)"));
+ }
+ const int temp = bool(temperature) ? temperature.get() : _default_temp;
+ _data[temp][static_cast<uint64_t>(freq)] = {
+ gain_power_map, reverse_map(gain_power_map), min_power, max_power};
+ }
+
+ // Note: This is very similar to at_bilin_interp(), but we can't use that
+ // because we mix types in the gain tables (we have uint64_t and double, and
+ // a struct).
+ double get_power(const double gain,
+ const double freq,
+ const boost::optional<int> temperature = boost::none) const
+ {
+ UHD_ASSERT_THROW(!_data.empty());
+ const uint64_t freqi = static_cast<uint64_t>(freq);
+ const auto& table = _get_table(temperature);
+
+ const auto f_iters = get_bounding_iterators(table, freqi);
+ const uint64_t f1i = f_iters.first->first;
+ const uint64_t f2i = f_iters.second->first;
+ // Frequency is out of bounds
+ if (f1i == f2i) {
+ return at_lin_interp(table.at(f1i).g2p, gain);
+ }
+ const double f1 = static_cast<double>(f1i);
+ const double f2 = static_cast<double>(f2i);
+ const auto gain_iters = get_bounding_iterators(table.at(f1).g2p, gain);
+ const double gain1 = gain_iters.first->first;
+ const double gain2 = gain_iters.second->first;
+ // Gain is out of bounds
+ if (gain1 == gain2) {
+ return linear_interp(freq,
+ f1,
+ table.at(f1i).g2p.at(gain1),
+ f2,
+ table.at(f2i).g2p.at(gain1));
+ }
+
+ // Both gain and freq are within bounds: Bi-Linear interpolation
+ // Find power values
+ const auto power11 = table.at(f1i).g2p.at(gain1);
+ const auto power12 = table.at(f1i).g2p.at(gain2);
+ const auto power21 = table.at(f2i).g2p.at(gain1);
+ const auto power22 = table.at(f2i).g2p.at(gain2);
+
+ return bilinear_interp(
+ freq, gain, f1, gain1, f2, gain2, power11, power12, power21, power22);
+ }
+
+ void clear()
+ {
+ _data.clear();
+ }
+
+ void set_temperature(const int temperature)
+ {
+ _default_temp = temperature;
+ }
+
+ int get_temperature() const
+ {
+ return _default_temp;
+ }
+
+ void set_ref_gain(const double gain)
+ {
+ _ref_gain = gain;
+ }
+
+ double get_ref_gain() const
+ {
+ return _ref_gain;
+ }
+
+ uhd::meta_range_t get_power_limits(
+ const double freq, const boost::optional<int> temperature = boost::none) const
+ {
+ const auto table = at_nearest(_get_table(temperature), uint64_t(freq));
+ return uhd::meta_range_t(table.min_power, table.max_power);
+ }
+
+ double get_gain(const double power_dbm,
+ const double freq,
+ const boost::optional<int> temperature = boost::none) const
+ {
+ UHD_ASSERT_THROW(!_data.empty());
+ const uint64_t freqi = static_cast<uint64_t>(freq);
+ const auto& table = _get_table(temperature);
+ const double power_coerced = get_power_limits(freq, temperature).clip(power_dbm);
+
+ const auto f_iters = get_bounding_iterators(table, freqi);
+ const uint64_t f1i = f_iters.first->first;
+ const uint64_t f2i = f_iters.second->first;
+ // Frequency is out of bounds
+ if (f1i == f2i) {
+ return at_lin_interp(table.at(f1i).p2g, power_coerced);
+ }
+ const double f1 = static_cast<double>(f1i);
+ const double f2 = static_cast<double>(f2i);
+ const auto pwr_iters = get_bounding_iterators(table.at(f1).p2g, power_coerced);
+ const double pwr1 = pwr_iters.first->first;
+ const double pwr2 = pwr_iters.second->first;
+ // Power is out of bounds (this shouldn't happen after coercing, but this
+ // is just another good sanity check on our data)
+ if (pwr1 == pwr2) {
+ return linear_interp(freq,
+ f1,
+ table.at(f1i).p2g.at(pwr1),
+ f2,
+ table.at(f2i).p2g.at(pwr1));
+ }
+
+ // Both gain and freq are within bounds: Bi-Linear interpolation
+ // Find power values
+ const auto gain11 = table.at(f1i).p2g.at(pwr1);
+ const auto gain12 = table.at(f1i).p2g.at(pwr2);
+ const auto gain21 = table.at(f2i).p2g.at(pwr1);
+ const auto gain22 = table.at(f2i).p2g.at(pwr2);
+
+ return bilinear_interp(
+ freq, power_coerced, f1, pwr1, f2, pwr2, gain11, gain12, gain21, gain22);
+ }
+
+ /**************************************************************************
+ * Container API (Serialization/Deserialization)
+ *************************************************************************/
+ std::vector<uint8_t> serialize()
+ {
+ const size_t initial_size_bytes = 1024 * 20; // 20 kiB as an initial guess
+ flatbuffers::FlatBufferBuilder builder(initial_size_bytes);
+
+ std::vector<flatbuffers::Offset<TempFreqMap>> temp_freq_map;
+ temp_freq_map.reserve(_data.size());
+ for (auto& temp_freq_pair : _data) {
+ const int temperature = temp_freq_pair.first;
+ std::vector<flatbuffers::Offset<FreqPowerMap>> freq_gain_map;
+ for (auto& freq_gain_pair : temp_freq_pair.second) {
+ const uint64_t freq = freq_gain_pair.first;
+ const double min_power = freq_gain_pair.second.min_power;
+ const double max_power = freq_gain_pair.second.max_power;
+ std::vector<PowerMap> gain_power_map;
+ for (auto& gain_power_pair : freq_gain_pair.second.g2p) {
+ gain_power_map.push_back(
+ PowerMap(gain_power_pair.first, gain_power_pair.second));
+ }
+
+ freq_gain_map.push_back(CreateFreqPowerMapDirect(
+ builder, freq, &gain_power_map, min_power, max_power));
+ }
+
+ temp_freq_map.push_back(
+ CreateTempFreqMapDirect(builder, temperature, &freq_gain_map));
+ }
+
+ // Now load it all into the FlatBuffer
+ auto metadata = CreateMetadataDirect(builder,
+ _name.c_str(),
+ _serial.c_str(),
+ _timestamp,
+ VERSION_MAJOR,
+ VERSION_MINOR);
+ auto power_cal =
+ CreatePowerCalDirect(builder, metadata, &temp_freq_map, get_ref_gain());
+ FinishPowerCalBuffer(builder, power_cal);
+ const size_t table_size = builder.GetSize();
+ const uint8_t* table = builder.GetBufferPointer();
+ return std::vector<uint8_t>(table, table + table_size);
+ }
+
+ // This will amend the existing table. If that's not desired, then it is
+ // necessary to call clear() ahead of time.
+ void deserialize(const std::vector<uint8_t>& data)
+ {
+ auto verifier = flatbuffers::Verifier(data.data(), data.size());
+ if (!VerifyPowerCalBuffer(verifier)) {
+ throw uhd::runtime_error("pwr_cal: Invalid data provided!");
+ }
+ auto cal_table = GetPowerCal(static_cast<const void*>(data.data()));
+ if (cal_table->metadata()->version_major() != VERSION_MAJOR) {
+ throw uhd::runtime_error("pwr_cal: Compat number mismatch!");
+ }
+ if (cal_table->metadata()->version_minor() != VERSION_MINOR) {
+ UHD_LOG_WARNING("CAL",
+ "pwr_cal: Expected compat number "
+ << VERSION_MAJOR << "." << VERSION_MINOR << ", got "
+ << cal_table->metadata()->version_major() << "."
+ << cal_table->metadata()->version_minor());
+ }
+ _name = std::string(cal_table->metadata()->name()->c_str());
+ _serial = std::string(cal_table->metadata()->serial()->c_str());
+ _timestamp = cal_table->metadata()->timestamp();
+ if (cal_table->ref_gain() >= 0.0) {
+ _ref_gain = cal_table->ref_gain();
+ }
+
+ auto temp_freq_map = cal_table->temp_freq_map();
+ for (auto it = temp_freq_map->begin(); it != temp_freq_map->end(); ++it) {
+ const int temperature = it->temperature();
+ auto freq_gain_map = it->freqs();
+ for (auto f_it = freq_gain_map->begin(); f_it != freq_gain_map->end();
+ ++f_it) {
+ std::map<double, double> power;
+ auto power_map = f_it->powers();
+ for (auto g_it = power_map->begin(); g_it != power_map->end(); ++g_it) {
+ power.insert({g_it->gain(), g_it->power_dbm()});
+ }
+ add_power_table(power,
+ f_it->min_power(),
+ f_it->max_power(),
+ f_it->freq(),
+ temperature);
+ }
+ }
+ }
+
+
+private:
+ // We map the gain to power, and power to gain, in different data structures.
+ // This is suboptimal w.r.t. memory usage (it duplicates the keys/values),
+ // but helps us with the algorithms above.
+ // This could also be solved with a Boost.Bimap, but it doesn't seem worth
+ // the additional dependency.
+ struct pwr_cal_table
+ {
+ std::map<double, double> g2p; //!< Maps gain to power
+ std::map<double, double> p2g; //!< Maps power to gain
+ double min_power;
+ double max_power;
+ };
+
+ using freq_table_map = std::map<uint64_t /* freq */, pwr_cal_table>;
+
+ freq_table_map _get_table(const boost::optional<int> temperature) const
+ {
+ const int temp = bool(temperature) ? temperature.get() : _default_temp;
+ return at_nearest(_data, temp);
+ }
+
+ std::string _name;
+ std::string _serial;
+ uint64_t _timestamp;
+
+ //! The actual gain table
+ std::map<int /* temp */, freq_table_map> _data;
+ double _ref_gain = 0.0;
+ int _default_temp = NORMAL_TEMPERATURE;
+};
+
+
+pwr_cal::sptr pwr_cal::make()
+{
+ return std::make_shared<pwr_cal_impl>();
+}
+
+pwr_cal::sptr pwr_cal::make(
+ const std::string& name, const std::string& serial, const uint64_t timestamp)
+{
+ return std::make_shared<pwr_cal_impl>(name, serial, timestamp);
+}
+
diff --git a/host/python/uhd/usrp/cal/libtypes.py b/host/python/uhd/usrp/cal/libtypes.py
index b75165965..1eb9ff360 100644
--- a/host/python/uhd/usrp/cal/libtypes.py
+++ b/host/python/uhd/usrp/cal/libtypes.py
@@ -17,5 +17,6 @@ from ... import libpyuhd as lib
database = lib.cal.database
Source = lib.cal.source
IQCal = lib.cal.iq_cal
+PwrCal = lib.cal.pwr_cal
InterpMode = lib.cal.interp_mode
# pylint: enable=invalid-name
diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt
index 4ed94de2c..3723119fa 100644
--- a/host/tests/CMakeLists.txt
+++ b/host/tests/CMakeLists.txt
@@ -27,6 +27,7 @@ set(test_sources
cast_test.cpp
cal_database_test.cpp
cal_data_iq_test.cpp
+ cal_data_gain_pwr_test.cpp
chdr_test.cpp
constrained_device_args_test.cpp
convert_test.cpp
diff --git a/host/tests/cal_data_gain_pwr_test.cpp b/host/tests/cal_data_gain_pwr_test.cpp
new file mode 100644
index 000000000..e066a96f3
--- /dev/null
+++ b/host/tests/cal_data_gain_pwr_test.cpp
@@ -0,0 +1,134 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/cal/pwr_cal.hpp>
+#include <uhd/exception.hpp>
+#include <boost/test/unit_test.hpp>
+#include <iostream>
+
+using namespace uhd::usrp::cal;
+
+BOOST_AUTO_TEST_CASE(test_pwr_cal_api)
+{
+ const std::string name = "Mock Gain/Power Data";
+ const std::string serial = "ABC1234";
+ const uint64_t timestamp = 0x12340000;
+
+ auto gain_power_data = pwr_cal::make(name, serial, timestamp);
+ BOOST_CHECK_EQUAL(gain_power_data->get_name(), name);
+ BOOST_CHECK_EQUAL(gain_power_data->get_serial(), serial);
+ BOOST_CHECK_EQUAL(gain_power_data->get_timestamp(), timestamp);
+
+ constexpr int NEW_TEMP = 25;
+ gain_power_data->set_temperature(NEW_TEMP);
+ BOOST_CHECK_EQUAL(gain_power_data->get_temperature(), NEW_TEMP);
+
+ // Write a table...
+ gain_power_data->add_power_table({{0.0, -30.0}, {10.0, -20.0}}, -40.0, -10.0, 1e9);
+ // ...read it back...
+ BOOST_CHECK_EQUAL(gain_power_data->get_power(0.0, 1e9), -30.0);
+ BOOST_CHECK_EQUAL(gain_power_data->get_power(10.0, 1e9), -20.0);
+ // ...and make sure any other index will do the same
+ BOOST_CHECK_EQUAL(gain_power_data->get_power(47.2, 23e6), -20.0);
+
+ // Let's say power goes down 10 dB per octave
+ gain_power_data->add_power_table({{0.0, -40.0}, {10.0, -30.0}}, -40.0, -10.0, 2e9);
+ // Do uninterpolated readbacks again
+ BOOST_CHECK_EQUAL(gain_power_data->get_power(10.0, 1e9), -20.0);
+ BOOST_CHECK_EQUAL(gain_power_data->get_power(47.2, 23e6), -20.0);
+ // Now interpolate
+ BOOST_CHECK_CLOSE(gain_power_data->get_power(5.0, 1.5e9), -30.0, 1e-6);
+
+ // Some ref gain checks
+ BOOST_CHECK_EQUAL(gain_power_data->get_ref_gain(), 0.0);
+ gain_power_data->set_ref_gain(5.0);
+ BOOST_CHECK_EQUAL(gain_power_data->get_ref_gain(), 5.0);
+
+ // Clear, make sure the container doesn't work anymore, and add the value
+ // back for future tests
+ gain_power_data->clear();
+ BOOST_REQUIRE_THROW(gain_power_data->get_power(47.2, 23e6), uhd::exception);
+ gain_power_data->add_power_table({{10.0, -20.0}}, -20.0, -20.0, 1e9);
+
+ constexpr int ROOM_TEMP = 20;
+ gain_power_data->set_temperature(ROOM_TEMP);
+ BOOST_CHECK_EQUAL(gain_power_data->get_temperature(), ROOM_TEMP);
+ // Shouldn't change anything yet
+ BOOST_CHECK_EQUAL(gain_power_data->get_power(10.0, 1e9), -20.0);
+ // Power shall go down 5 dB at this new temperature (whoa)
+ gain_power_data->add_power_table({{10.0, -25.0}}, -20.0, -20.0, 1e9);
+ BOOST_CHECK_EQUAL(gain_power_data->get_power(10.0, 1e9), -25.0);
+ // And if we have to interpolate, temp compensation uses NN:
+ BOOST_CHECK_EQUAL(gain_power_data->get_power(10.0, 1e9, 21), -25.0);
+
+ // Now clear, then add a more useful data set. We can stay at ROOM_TEMP.
+ gain_power_data->clear();
+ constexpr double power_offset = -20.0; // 0 dB shall map to -20 dBm
+ constexpr double lin_error = 0.1; // The linearization error shall increase by 0.1
+ // dB per dB gain
+ constexpr double ref_freq = 1e9;
+ std::map<double, double> test_gain_power;
+ constexpr double max_gain = 10.0;
+ for (double gain = 0.0; gain <= max_gain; gain += 1.0) {
+ test_gain_power[gain] = gain + power_offset + lin_error * gain;
+ }
+ const double min_power = test_gain_power.cbegin()->second;
+ const double max_power = test_gain_power.crbegin()->second;
+ gain_power_data->add_power_table(test_gain_power, min_power, max_power, ref_freq);
+ // Quick check
+ BOOST_CHECK_EQUAL(gain_power_data->get_power(0.0, ref_freq), power_offset);
+ const std::pair<double, double> expected_limits{min_power, max_power};
+ const auto limits = gain_power_data->get_power_limits(ref_freq);
+ BOOST_CHECK_EQUAL(limits.start(), min_power);
+ BOOST_CHECK_EQUAL(limits.stop(), max_power);
+
+ BOOST_CHECK_CLOSE(gain_power_data->get_gain(-20.0, ref_freq), 0.0, 1e-6);
+ BOOST_CHECK_CLOSE(
+ gain_power_data->get_gain(test_gain_power.crbegin()->second, ref_freq),
+ test_gain_power.crbegin()->first,
+ 1e-6);
+ BOOST_CHECK_CLOSE(gain_power_data->get_gain(-19.0, ref_freq), 1/1.1, 1e-6);
+}
+
+BOOST_AUTO_TEST_CASE(test_pwr_cal_serdes)
+{
+ const std::string name = "Mock Gain/Power Data";
+ const std::string serial = "ABC1234";
+ const uint64_t timestamp = 0x12340000;
+ auto gain_power_data_blueprint = pwr_cal::make(name, serial, timestamp);
+
+ constexpr double power_offset = -20.0;
+ constexpr double lin_error = 0.1;
+ constexpr double ref_freq = 1e9;
+ std::map<double, double> test_gain_power;
+ for (double gain = 0.0; gain < 10.0; gain += 1.0) {
+ test_gain_power[gain] = gain + power_offset + lin_error * gain;
+ }
+ const double min_power = test_gain_power[0.0];
+ const double max_power = test_gain_power[9.0];
+ gain_power_data_blueprint->add_power_table(
+ test_gain_power, min_power, max_power, ref_freq);
+
+ const auto serialized = gain_power_data_blueprint->serialize();
+ auto pwr_cal_data = container::make<pwr_cal>(serialized);
+
+ BOOST_CHECK_EQUAL(pwr_cal_data->get_name(), name);
+ BOOST_CHECK_EQUAL(pwr_cal_data->get_serial(), serial);
+ BOOST_CHECK_EQUAL(pwr_cal_data->get_timestamp(), timestamp);
+
+ for (auto& gp : test_gain_power) {
+ BOOST_CHECK_EQUAL(
+ pwr_cal_data->get_power(gp.first, ref_freq),
+ gp.second);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_pwr_cal_des_fail)
+{
+ std::vector<uint8_t> not_actual_data(42, 23);
+
+ BOOST_REQUIRE_THROW(container::make<pwr_cal>(not_actual_data), uhd::runtime_error);
+}