aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/cal
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2020-03-03 16:10:06 -0800
committerAaron Rossetto <aaron.rossetto@ni.com>2020-04-02 11:55:17 -0500
commit3fe5ccf700a0c6f27dca9511386460194dcc593c (patch)
tree19d394faac0581601d3d6e5d35a7a82e325bf245 /host/lib/cal
parent1e016e49e82c430197a90a018fbc7613cc654088 (diff)
downloaduhd-3fe5ccf700a0c6f27dca9511386460194dcc593c.tar.gz
uhd-3fe5ccf700a0c6f27dca9511386460194dcc593c.tar.bz2
uhd-3fe5ccf700a0c6f27dca9511386460194dcc593c.zip
uhd: cal: Add iq_cal calibration data container class
This class can be used to store calibration coefficients for the X300 DC offset and IQ imbalance calibration. Note: This also modifies Doxyfile.in to not document files generated by flatc.
Diffstat (limited to 'host/lib/cal')
-rw-r--r--host/lib/cal/CMakeLists.txt2
-rw-r--r--host/lib/cal/cal_python.hpp38
-rw-r--r--host/lib/cal/iq_cal.cpp197
3 files changed, 237 insertions, 0 deletions
diff --git a/host/lib/cal/CMakeLists.txt b/host/lib/cal/CMakeLists.txt
index 5c2c8a617..fb1f4b0e3 100644
--- a/host/lib/cal/CMakeLists.txt
+++ b/host/lib/cal/CMakeLists.txt
@@ -7,7 +7,9 @@
########################################################################
# This file included, use CMake directory variables
########################################################################
+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
)
diff --git a/host/lib/cal/cal_python.hpp b/host/lib/cal/cal_python.hpp
index 0fe87046f..39a13d94c 100644
--- a/host/lib/cal/cal_python.hpp
+++ b/host/lib/cal/cal_python.hpp
@@ -8,6 +8,8 @@
#define INCLUDED_UHD_CAL_PYTHON_HPP
#include <uhd/cal/database.hpp>
+#include <uhd/cal/interpolation.hpp>
+#include <uhd/cal/iq_cal.hpp>
std::vector<uint8_t> pybytes_to_vector(const py::bytes& data)
{
@@ -56,6 +58,42 @@ void export_cal(py::module& m)
[](const std::string& key, const std::string& serial, const py::bytes data) {
database::write_cal_data(key, serial, pybytes_to_vector(data));
});
+
+ py::enum_<interp_mode>(m, "interp_mode")
+ .value("NEAREST_NEIGHBOR", interp_mode::NEAREST_NEIGHBOR)
+ .value("LINEAR", interp_mode::LINEAR);
+
+ py::class_<container, std::shared_ptr<container>>(m, "container")
+ .def("get_name", &container::get_name)
+ .def("get_serial", &container::get_serial)
+ .def("get_timestamp", &container::get_name)
+ .def("serialize",
+ [](std::shared_ptr<container>& self) {
+ return vector_to_pybytes(self->serialize());
+ })
+ .def("deserialize", [](std::shared_ptr<container>& self, const py::bytes data) {
+ self->deserialize(pybytes_to_vector(data));
+ });
+
+ py::class_<iq_cal, container, iq_cal::sptr>(m, "iq_cal")
+ .def(py::init([](const std::string& name,
+ const std::string& serial,
+ const uint64_t timestamp) {
+ return iq_cal::make(name, serial, timestamp);
+ }))
+ .def(py::init([]() { return iq_cal::make(); }))
+ .def(py::init([](const py::bytes data) {
+ return container::make<iq_cal>(pybytes_to_vector(data));
+ }))
+ .def("set_interp_mode", &iq_cal::set_interp_mode)
+ .def("get_cal_coeff", &iq_cal::get_cal_coeff)
+ .def("set_cal_coeff",
+ &iq_cal::set_cal_coeff,
+ py::arg("freq"),
+ py::arg("coeff"),
+ py::arg("suppression_abs") = 0,
+ py::arg("suppression_delta") = 0)
+ .def("clear", &iq_cal::clear);
}
#endif /* INCLUDED_UHD_CAL_PYTHON_HPP */
diff --git a/host/lib/cal/iq_cal.cpp b/host/lib/cal/iq_cal.cpp
new file mode 100644
index 000000000..e1ed8c9cb
--- /dev/null
+++ b/host/lib/cal/iq_cal.cpp
@@ -0,0 +1,197 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/cal/iq_cal.hpp>
+#include <uhd/cal/iq_cal_generated.h>
+#include <uhd/exception.hpp>
+#include <uhd/utils/math.hpp>
+#include <map>
+#include <string>
+
+using namespace uhd::usrp::cal;
+
+constexpr int VERSION_MAJOR = 1;
+constexpr int VERSION_MINOR = 0;
+
+/***********************************************************************
+ * Helper routines
+ **********************************************************************/
+class iq_cal_impl : public iq_cal
+{
+public:
+ iq_cal_impl(const std::string& name = "",
+ const std::string& serial = "",
+ const uint64_t timestamp = 0)
+ : _name(name)
+ , _serial(serial)
+ , _timestamp(timestamp)
+ , _interp(interp_mode::LINEAR)
+ {
+ }
+
+ std::string get_name() const
+ {
+ return _name;
+ }
+
+ //! Return the name of this calibration table
+ std::string get_serial() const
+ {
+ return _serial;
+ }
+
+ //! Timestamp of acquisition time
+ uint64_t get_timestamp() const
+ {
+ return _timestamp;
+ }
+
+ void set_interp_mode(const interp_mode interp)
+ {
+ UHD_ASSERT_THROW(
+ interp == interp_mode::LINEAR || interp == interp_mode::NEAREST_NEIGHBOR);
+ _interp = interp;
+ }
+
+ std::complex<double> get_cal_coeff(const double freq) const
+ {
+ UHD_ASSERT_THROW(!_coeffs.empty());
+ // Find the first coefficient in the map that maps to a larger frequency
+ // than freq (or equal)
+ auto next_coeff = _coeffs.lower_bound(freq);
+ if (next_coeff == _coeffs.end()) {
+ // This means freq is larger than our biggest key, and thus we
+ // can't interpolate. We return the coeffs of the largest key.
+ return _coeffs.rbegin()->second;
+ }
+ if (next_coeff == _coeffs.begin()) {
+ // This means freq is smaller than our smallest key, and thus we
+ // can't interpolate. We return the coeffs of the smallest key.
+ return _coeffs.begin()->second;
+ }
+ // Stash away freqs and coeffs for easier code
+ const auto hi_freq = next_coeff->first;
+ const auto hi_coeff = next_coeff->second;
+ next_coeff--;
+ const auto lo_coeff = next_coeff->second;
+ const auto lo_freq = next_coeff->first; // lo == low, not LO
+ // Now, we're guaranteed to be between two points
+ if (_interp == interp_mode::NEAREST_NEIGHBOR) {
+ return (hi_freq - freq) < (freq - lo_freq) ? hi_coeff : lo_coeff;
+ }
+ using uhd::math::linear_interp;
+ return std::complex<double>(
+ linear_interp<double>(
+ freq, lo_freq, lo_coeff.real(), hi_freq, hi_coeff.real()),
+ linear_interp<double>(
+ freq, lo_freq, lo_coeff.imag(), hi_freq, hi_coeff.imag()));
+ }
+
+ void set_cal_coeff(const double freq,
+ const std::complex<double> coeff,
+ const double suppression_abs = 0,
+ const double suppression_delta = 0)
+ {
+ _coeffs[freq] = coeff;
+ _supp[freq] = {suppression_abs, suppression_delta};
+ }
+
+ void clear()
+ {
+ _coeffs.clear();
+ _supp.clear();
+ }
+
+ /**************************************************************************
+ * Container API (Serialization/Deserialization)
+ *************************************************************************/
+ std::vector<uint8_t> serialize()
+ {
+ // This is a magic value to estimate the amount of space the builder will
+ // have to reserve on top of the coeff data.
+ // Worst case is we get this too low, and the builder will have to do a
+ // single reallocation later.
+ constexpr size_t RESERVE_HDR_BYTES = 20;
+ const size_t initial_size_bytes =
+ sizeof(IQCalCoeff) * _coeffs.size() + RESERVE_HDR_BYTES;
+ flatbuffers::FlatBufferBuilder builder(initial_size_bytes);
+ // Convert the coefficients to a vector of IQCalCoeff
+ std::vector<IQCalCoeff> fb_coeffs;
+ fb_coeffs.reserve(_coeffs.size());
+ std::transform(_coeffs.cbegin(),
+ _coeffs.cend(),
+ std::back_inserter(fb_coeffs),
+ [&](const coeffs_type::value_type& coeff) {
+ const double freq = coeff.first;
+ const auto this_coeff = coeff.second;
+ return IQCalCoeff(freq,
+ this_coeff.real(),
+ this_coeff.imag(),
+ _supp.count(freq) ? _supp.at(freq).first : 0.0,
+ _supp.count(freq) ? _supp.at(freq).second : 0.0);
+ });
+ // Now load it all into the FlatBuffer
+ const auto metadata = CreateMetadataDirect(builder,
+ _name.c_str(),
+ _serial.c_str(),
+ _timestamp,
+ VERSION_MAJOR,
+ VERSION_MINOR);
+ auto cal_table = CreateIQCalCoeffsDirect(builder, metadata, &fb_coeffs);
+ FinishIQCalCoeffsBuffer(builder, cal_table);
+ 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 (!VerifyIQCalCoeffsBuffer(verifier)) {
+ throw uhd::runtime_error("iq_cal: Invalid data provided!");
+ }
+ auto cal_table = GetIQCalCoeffs(static_cast<const void*>(data.data()));
+ // TODO we can handle this more nicely
+ UHD_ASSERT_THROW(cal_table->metadata()->version_major() == VERSION_MAJOR);
+ _name = std::string(cal_table->metadata()->name()->c_str());
+ _serial = std::string(cal_table->metadata()->serial()->c_str());
+ _timestamp = cal_table->metadata()->timestamp();
+ auto coeffs = cal_table->coeffs();
+ for (auto it = coeffs->begin(); it != coeffs->end(); ++it) {
+ _coeffs[it->freq()] = {it->coeff_real(), it->coeff_imag()};
+ // Suppression levels are really not necessary for runtime.
+ // TODO: Come up with a way to skip this step when loading data for
+ // runtime (and not future storage)
+ _supp[it->freq()] = {it->suppression_abs(), it->suppression_delta()};
+ }
+ }
+
+
+private:
+ std::string _name;
+ std::string _serial;
+ uint64_t _timestamp;
+ using coeffs_type = std::map<double, std::complex<double>>;
+ coeffs_type _coeffs;
+ // Abs suppression, delta suppression
+ std::map<double, std::pair<double, double>> _supp;
+
+ interp_mode _interp;
+};
+
+
+iq_cal::sptr iq_cal::make()
+{
+ return std::make_shared<iq_cal_impl>();
+}
+
+iq_cal::sptr iq_cal::make(
+ const std::string& name, const std::string& serial, const uint64_t timestamp)
+{
+ return std::make_shared<iq_cal_impl>(name, serial, timestamp);
+}