diff options
author | Martin Braun <martin.braun@ettus.com> | 2020-03-03 16:10:06 -0800 |
---|---|---|
committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2020-04-02 11:55:17 -0500 |
commit | 3fe5ccf700a0c6f27dca9511386460194dcc593c (patch) | |
tree | 19d394faac0581601d3d6e5d35a7a82e325bf245 /host/lib/cal | |
parent | 1e016e49e82c430197a90a018fbc7613cc654088 (diff) | |
download | uhd-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.txt | 2 | ||||
-rw-r--r-- | host/lib/cal/cal_python.hpp | 38 | ||||
-rw-r--r-- | host/lib/cal/iq_cal.cpp | 197 |
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); +} |