diff options
-rw-r--r-- | host/include/uhd/utils/CMakeLists.txt | 1 | ||||
-rw-r--r-- | host/include/uhd/utils/gain_group.hpp | 85 | ||||
-rw-r--r-- | host/lib/CMakeLists.txt | 1 | ||||
-rw-r--r-- | host/lib/gain_group.cpp | 151 | ||||
-rw-r--r-- | host/test/CMakeLists.txt | 1 | ||||
-rw-r--r-- | host/test/gain_group_test.cpp | 122 |
6 files changed, 361 insertions, 0 deletions
diff --git a/host/include/uhd/utils/CMakeLists.txt b/host/include/uhd/utils/CMakeLists.txt index d484788b2..4a5f20e3c 100644 --- a/host/include/uhd/utils/CMakeLists.txt +++ b/host/include/uhd/utils/CMakeLists.txt @@ -22,6 +22,7 @@ INSTALL(FILES byteswap.hpp byteswap.ipp exception.hpp + gain_group.hpp gain_handler.hpp pimpl.hpp props.hpp diff --git a/host/include/uhd/utils/gain_group.hpp b/host/include/uhd/utils/gain_group.hpp new file mode 100644 index 000000000..3955dfa9a --- /dev/null +++ b/host/include/uhd/utils/gain_group.hpp @@ -0,0 +1,85 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_UTILS_GAIN_GROUP_HPP +#define INCLUDED_UHD_UTILS_GAIN_GROUP_HPP + +#include <uhd/config.hpp> +#include <uhd/types/ranges.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/function.hpp> +#include <boost/utility.hpp> + +namespace uhd{ + +/*! + * A set of function to control a gain element. + */ +struct UHD_API gain_fcns_t{ + boost::function<gain_range_t(void)> get_range; + boost::function<float(void)> get_value; + boost::function<void(float)> set_value; +}; + +class UHD_API gain_group : boost::noncopyable{ +public: + typedef boost::shared_ptr<gain_group> sptr; + + /*! + * Get the overall gain range for this group. + * Overall step is defined as the minimum step size. + * \return a gain range with overall min, max, step + */ + virtual gain_range_t get_range(void) = 0; + + /*! + * Get the overall gain value for this group. + * \return a summation of all the gain values + */ + virtual float get_value(void) = 0; + + /*! + * Set the overall gain value for this group. + * The power will be distributed across individual gain elements. + * The semantics of how to do this are determined by the priority. + * \param gain the gain to set across the group + */ + virtual void set_value(float gain) = 0; + + /*! + * Register a set of gain functions into this group. + * Priority determines how power will be distributed + * with higher priorities getting the power first, + * and lower priorities getting the remainder power. + * \param gain_fcns the set of gain functions + * \param priority the priority of the gain element + */ + virtual void register_fcns( + const gain_fcns_t &gain_fcns, size_t priority = 0 + ) = 0; + + /*! + * Make a new empty gain group. + * \return a gain group object. + */ + static sptr make(void); +}; + +} //namespace uhd + +#endif /* INCLUDED_UHD_UTILS_GAIN_GROUP_HPP */ + diff --git a/host/lib/CMakeLists.txt b/host/lib/CMakeLists.txt index 767029dc4..010478821 100644 --- a/host/lib/CMakeLists.txt +++ b/host/lib/CMakeLists.txt @@ -112,6 +112,7 @@ CONFIGURE_FILE( LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/device.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/gain_group.cpp ${CMAKE_CURRENT_SOURCE_DIR}/gain_handler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/load_modules.cpp ${CMAKE_CURRENT_SOURCE_DIR}/thread_priority.cpp diff --git a/host/lib/gain_group.cpp b/host/lib/gain_group.cpp new file mode 100644 index 000000000..3ae9a285e --- /dev/null +++ b/host/lib/gain_group.cpp @@ -0,0 +1,151 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/gain_group.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/assert.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> +#include <boost/math/special_functions/round.hpp> +#include <algorithm> +#include <vector> +#include <iostream> + +#define rint(x) boost::math::iround(x) + +using namespace uhd; + +static const bool verbose = false; + +static bool compare_by_step_size( + const size_t &rhs, const size_t &lhs, std::vector<gain_fcns_t> &fcns +){ + return fcns.at(rhs).get_range().step > fcns.at(lhs).get_range().step; +} + +/*********************************************************************** + * gain group implementation + **********************************************************************/ +class gain_group_impl : public gain_group{ +public: + gain_group_impl(void){ + /*NOP*/ + } + + gain_range_t get_range(void){ + float overall_min = 0, overall_max = 0, overall_step = 0; + BOOST_FOREACH(const gain_fcns_t &fcns, get_all_fcns()){ + const gain_range_t range = fcns.get_range(); + overall_min += range.min; + overall_max += range.max; + //the overall step is the min (zero is invalid, first run) + if (overall_step == 0) overall_step = range.step; + overall_step = std::min(overall_step, range.step); + } + return gain_range_t(overall_min, overall_max, overall_step); + } + + float get_value(void){ + float overall_gain = 0; + BOOST_FOREACH(const gain_fcns_t &fcns, get_all_fcns()){ + overall_gain += fcns.get_value(); + } + return overall_gain; + } + + void set_value(float gain){ + std::vector<gain_fcns_t> all_fcns = get_all_fcns(); + + //get the max step size among the gains + float max_step = 0; + BOOST_FOREACH(const gain_fcns_t &fcns, all_fcns){ + max_step = std::max(max_step, fcns.get_range().step); + } + + //create gain bucket to distribute power + std::vector<float> gain_bucket; + + //distribute power according to priority (round to max step) + float gain_left_to_distribute = gain; + BOOST_FOREACH(const gain_fcns_t &fcns, all_fcns){ + const gain_range_t range = fcns.get_range(); + gain_bucket.push_back( + max_step*rint(std::clip(gain_left_to_distribute, range.min, range.max)/max_step) + ); + gain_left_to_distribute -= gain_bucket.back(); + } + + //get a list of indexes sorted by step size large to small + std::vector<size_t> indexes_step_size_dec; + for (size_t i = 0; i < all_fcns.size(); i++){ + indexes_step_size_dec.push_back(i); + } + std::sort( + indexes_step_size_dec.begin(), indexes_step_size_dec.end(), + boost::bind(&compare_by_step_size, _1, _2, all_fcns) + ); + UHD_ASSERT_THROW( + all_fcns.at(indexes_step_size_dec.front()).get_range().step >= + all_fcns.at(indexes_step_size_dec.back()).get_range().step + ); + + //distribute the remainder (less than max step) + //fill in the largest step sizes first that are less than the remainder + BOOST_FOREACH(size_t i, indexes_step_size_dec){ + const gain_range_t range = all_fcns.at(i).get_range(); + float additional_gain = range.step*rint( + std::clip(gain_bucket.at(i) + gain_left_to_distribute, range.min, range.max + )/range.step) - gain_bucket.at(i); + gain_bucket.at(i) += additional_gain; + gain_left_to_distribute -= additional_gain; + } + if (verbose) std::cout << "gain_left_to_distribute " << gain_left_to_distribute << std::endl; + + //now write the bucket out to the individual gain values + for (size_t i = 0; i < gain_bucket.size(); i++){ + if (verbose) std::cout << gain_bucket.at(i) << std::endl; + all_fcns.at(i).set_value(gain_bucket.at(i)); + } + } + + void register_fcns( + const gain_fcns_t &gain_fcns, size_t priority + ){ + _registry[priority].push_back(gain_fcns); + } + +private: + //! get the gain function sets in order (highest priority first) + std::vector<gain_fcns_t> get_all_fcns(void){ + std::vector<gain_fcns_t> all_fcns; + BOOST_FOREACH(ssize_t key, std::sorted(_registry.keys())){ + const std::vector<gain_fcns_t> &fcns = _registry[key]; + all_fcns.insert(all_fcns.begin(), fcns.begin(), fcns.end()); + } + return all_fcns; + } + + uhd::dict<size_t, std::vector<gain_fcns_t> > _registry; +}; + +/*********************************************************************** + * gain group factory function + **********************************************************************/ +gain_group::sptr gain_group::make(void){ + return sptr(new gain_group_impl()); +} diff --git a/host/test/CMakeLists.txt b/host/test/CMakeLists.txt index b7dcb741a..25cae6e7f 100644 --- a/host/test/CMakeLists.txt +++ b/host/test/CMakeLists.txt @@ -26,6 +26,7 @@ ADD_EXECUTABLE(main_test convert_types_test.cpp dict_test.cpp error_test.cpp + gain_group_test.cpp gain_handler_test.cpp tune_helper_test.cpp vrt_test.cpp diff --git a/host/test/gain_group_test.cpp b/host/test/gain_group_test.cpp new file mode 100644 index 000000000..4d337afb9 --- /dev/null +++ b/host/test/gain_group_test.cpp @@ -0,0 +1,122 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/test/unit_test.hpp> +#include <uhd/utils/gain_group.hpp> +#include <boost/bind.hpp> +#include <boost/math/special_functions/round.hpp> +#include <iostream> + +#define rint(x) boost::math::iround(x) + +using namespace uhd; + +/*********************************************************************** + * Define gain element classes with needed functions + **********************************************************************/ +class gain_element1{ +public: + + gain_range_t get_range(void){ + return gain_range_t(0, 90, 1); + } + + float get_value(void){ + return _gain; + } + + void set_value(float gain){ + float step = get_range().step; + _gain = step*rint(gain/step); + } + +private: + float _gain; +}; + +class gain_element2{ +public: + + gain_range_t get_range(void){ + return gain_range_t(-20, 10, 0.1); + } + + float get_value(void){ + return _gain; + } + + void set_value(float gain){ + float step = get_range().step; + _gain = step*rint(gain/step); + } + +private: + float _gain; +}; + +//create static instances of gain elements to be shared by the tests +static gain_element1 g1; +static gain_element2 g2; + +static gain_group::sptr get_gain_group(size_t pri1 = 0, size_t pri2 = 0){ + //create instance of gain group + gain_fcns_t gain_fcns; + gain_group::sptr gg(gain_group::make()); + + //load gain group with function sets + gain_fcns.get_range = boost::bind(&gain_element1::get_range, &g1); + gain_fcns.get_value = boost::bind(&gain_element1::get_value, &g1); + gain_fcns.set_value = boost::bind(&gain_element1::set_value, &g1, _1); + gg->register_fcns(gain_fcns, pri1); + + gain_fcns.get_range = boost::bind(&gain_element2::get_range, &g2); + gain_fcns.get_value = boost::bind(&gain_element2::get_value, &g2); + gain_fcns.set_value = boost::bind(&gain_element2::set_value, &g2, _1); + gg->register_fcns(gain_fcns, pri2); + + return gg; +} + +/*********************************************************************** + * Test cases + **********************************************************************/ +static const double tolerance = 0.001; + +BOOST_AUTO_TEST_CASE(test_gain_group_overall){ + gain_group::sptr gg = get_gain_group(); + + //test the overall stuff + gg->set_value(80); + BOOST_CHECK_CLOSE(gg->get_value(), 80, tolerance); + BOOST_CHECK_CLOSE(gg->get_range().min, -20, tolerance); + BOOST_CHECK_CLOSE(gg->get_range().max, 100, tolerance); + BOOST_CHECK_CLOSE(gg->get_range().step, 0.1, tolerance); +} + +BOOST_AUTO_TEST_CASE(test_gain_group_priority){ + gain_group::sptr gg = get_gain_group(0, 1); + + //test the overall stuff + gg->set_value(80); + BOOST_CHECK_CLOSE(gg->get_value(), 80, tolerance); + BOOST_CHECK_CLOSE(gg->get_range().min, -20, tolerance); + BOOST_CHECK_CLOSE(gg->get_range().max, 100, tolerance); + BOOST_CHECK_CLOSE(gg->get_range().step, 0.1, tolerance); + + //test the the higher priority gain got filled first (gain 2) + BOOST_CHECK_CLOSE(g2.get_value(), g2.get_range().max, tolerance); +} |