From 3fe5ccf700a0c6f27dca9511386460194dcc593c Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Tue, 3 Mar 2020 16:10:06 -0800 Subject: 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. --- host/lib/cal/CMakeLists.txt | 2 + host/lib/cal/cal_python.hpp | 38 +++++++++ host/lib/cal/iq_cal.cpp | 197 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 237 insertions(+) create mode 100644 host/lib/cal/iq_cal.cpp (limited to 'host/lib/cal') 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 +#include +#include std::vector 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_(m, "interp_mode") + .value("NEAREST_NEIGHBOR", interp_mode::NEAREST_NEIGHBOR) + .value("LINEAR", interp_mode::LINEAR); + + py::class_>(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& self) { + return vector_to_pybytes(self->serialize()); + }) + .def("deserialize", [](std::shared_ptr& self, const py::bytes data) { + self->deserialize(pybytes_to_vector(data)); + }); + + py::class_(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(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 +#include +#include +#include +#include +#include + +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 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( + linear_interp( + freq, lo_freq, lo_coeff.real(), hi_freq, hi_coeff.real()), + linear_interp( + freq, lo_freq, lo_coeff.imag(), hi_freq, hi_coeff.imag())); + } + + void set_cal_coeff(const double freq, + const std::complex 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 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 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(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& 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(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>; + coeffs_type _coeffs; + // Abs suppression, delta suppression + std::map> _supp; + + interp_mode _interp; +}; + + +iq_cal::sptr iq_cal::make() +{ + return std::make_shared(); +} + +iq_cal::sptr iq_cal::make( + const std::string& name, const std::string& serial, const uint64_t timestamp) +{ + return std::make_shared(name, serial, timestamp); +} -- cgit v1.2.3