1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
|
//
// Copyright 2015 Ettus Research
// Copyright 2018 Ettus Research, a National Instruments Company
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#include <uhd/utils/log.hpp>
#include <uhdlib/usrp/common/ad936x_manager.hpp>
#include <boost/functional/hash.hpp>
#include <chrono>
#include <cmath>
#include <list>
#include <memory>
#include <string>
#include <thread>
using namespace uhd;
using namespace uhd::usrp;
/****************************************************************************
* Default values
***************************************************************************/
const double ad936x_manager::DEFAULT_GAIN = 0;
const double ad936x_manager::DEFAULT_BANDWIDTH = ad9361_device_t::AD9361_MAX_BW;
const double ad936x_manager::DEFAULT_TICK_RATE = 16e6;
const double ad936x_manager::DEFAULT_FREQ = 100e6; // Hz
const uint32_t ad936x_manager::DEFAULT_DECIM = 128;
const uint32_t ad936x_manager::DEFAULT_INTERP = 128;
const bool ad936x_manager::DEFAULT_AUTO_DC_OFFSET = true;
const bool ad936x_manager::DEFAULT_AUTO_IQ_BALANCE = true;
const bool ad936x_manager::DEFAULT_AGC_ENABLE = false;
class ad936x_manager_impl : public ad936x_manager
{
public:
/************************************************************************
* Structor
***********************************************************************/
ad936x_manager_impl(const ad9361_ctrl::sptr& codec_ctrl, const size_t n_frontends)
: _codec_ctrl(codec_ctrl), _n_frontends(n_frontends)
{
if (_n_frontends < 1 or _n_frontends > 2) {
throw uhd::runtime_error(
str(boost::format(
"AD936x device can only have either 1 or 2 frontends, not %d.")
% _n_frontends));
}
for (size_t i = 1; i <= _n_frontends; i++) {
const std::string rx_fe_str = str(boost::format("RX%d") % i);
const std::string tx_fe_str = str(boost::format("TX%d") % i);
_rx_frontends.push_back(rx_fe_str);
_tx_frontends.push_back(tx_fe_str);
_bw[rx_fe_str] = 0.0;
_bw[tx_fe_str] = 0.0;
}
}
/************************************************************************
* API Calls
***********************************************************************/
void init_codec() override
{
for (const std::string& rx_fe : _rx_frontends) {
_codec_ctrl->set_gain(rx_fe, DEFAULT_GAIN);
_codec_ctrl->set_bw_filter(rx_fe, DEFAULT_BANDWIDTH);
_codec_ctrl->tune(rx_fe, DEFAULT_FREQ);
_codec_ctrl->set_dc_offset_auto(rx_fe, DEFAULT_AUTO_DC_OFFSET);
_codec_ctrl->set_iq_balance_auto(rx_fe, DEFAULT_AUTO_IQ_BALANCE);
_codec_ctrl->set_agc(rx_fe, DEFAULT_AGC_ENABLE);
}
for (const std::string& tx_fe : _tx_frontends) {
_codec_ctrl->set_gain(tx_fe, DEFAULT_GAIN);
_codec_ctrl->set_bw_filter(tx_fe, DEFAULT_BANDWIDTH);
_codec_ctrl->tune(tx_fe, DEFAULT_FREQ);
}
}
//
// loopback_self_test checks the integrity of the FPGA->AD936x->FPGA sample interface.
// The AD936x is put in loopback mode that sends the TX data unchanged to the RX side.
// A test value is written to the codec_idle register in the TX side of the radio.
// The readback register is then used to capture the values on the TX and RX sides
// simultaneously for comparison. It is a reasonably effective test for AC timing
// since I/Q Ch0/Ch1 alternate over the same wires. Note, however, that it uses
// whatever timing is configured at the time the test is called rather than select
// worst case conditions to stress the interface.
//
void loopback_self_test(std::function<void(uint32_t)> poker_functor,
std::function<uint64_t()> peeker_functor) override
{
// Put AD936x in loopback mode
_codec_ctrl->data_port_loopback(true);
UHD_LOGGER_DEBUG("AD936X") << "Performing CODEC loopback test... ";
size_t hash = size_t(time(NULL));
// Allow some time for AD936x to enter loopback mode.
// There is no clear statement in the documentation of how long it takes,
// but UG-570 does say to "allow six ADC_CLK/64 clock cycles of flush time"
// when leaving the TX or RX states. That works out to ~75us at the
// minimum clock rate of 5 MHz, which lines up with test results.
// Sleeping 1ms is far more than enough.
std::this_thread::sleep_for(std::chrono::milliseconds(1));
constexpr size_t NUM_LOOPBACK_ITERS = 100;
for (size_t i = 0; i < NUM_LOOPBACK_ITERS; i++) {
// Create test word
boost::hash_combine(hash, i);
const uint32_t word32 = uint32_t(hash) & 0xfff0fff0;
// Write test word to codec_idle idle register (on TX side)
poker_functor(word32);
// Read back values
const uint64_t rb_word64 = peeker_functor();
const uint32_t rb_tx = uint32_t(rb_word64 >> 32);
const uint32_t rb_rx = uint32_t(rb_word64 & 0xffffffff);
// Compare TX and RX values to test word
const bool test_fail = word32 != rb_tx or word32 != rb_rx;
if (test_fail) {
UHD_LOGGER_ERROR("AD936X")
<< "CODEC loopback test failed! "
<< boost::format("Expected: 0x%08X Received (TX/RX): 0x%08X/0x%08X")
% word32 % rb_tx % rb_rx;
throw uhd::runtime_error("CODEC loopback test failed.");
}
}
UHD_LOGGER_DEBUG("AD936X") << "CODEC loopback test passed.";
// Zero out the idle data.
poker_functor(0);
// Take AD936x out of loopback mode
_codec_ctrl->data_port_loopback(false);
}
double get_auto_tick_rate(const double lcm_rate, size_t num_chans) override
{
UHD_ASSERT_THROW(num_chans >= 1 and num_chans <= _n_frontends);
const uhd::meta_range_t rate_range = _codec_ctrl->get_clock_rate_range();
const double min_tick_rate = rate_range.start();
const double max_tick_rate = rate_range.stop() / num_chans;
// Check if the requested rate is within available limits:
if (uhd::math::fp_compare::fp_compare_delta<double>(
lcm_rate, uhd::math::FREQ_COMPARISON_DELTA_HZ)
> uhd::math::fp_compare::fp_compare_delta<double>(
max_tick_rate, uhd::math::FREQ_COMPARISON_DELTA_HZ)) {
throw uhd::value_error(
str(boost::format("[ad936x_manager] Cannot get determine a tick rate if "
"sampling rate exceeds maximum tick rate (%f > %f)")
% lcm_rate % max_tick_rate));
}
// **** Choose the new rate ****
// Rules for choosing the tick rate:
// Choose a rate that is a power of 2 larger than the sampling rate,
// but at least 4. Cannot exceed the max tick rate, of course, but must
// be larger than the minimum tick rate.
// An equation that does all that is:
//
// f_auto = r * 2^floor(log2(f_max/r))
// = lcm_rate * multiplier
//
// where r is the base rate and f_max is the maximum tick rate. The case
// where floor() yields 1 must be caught.
// We use shifts here instead of 2^x because exp2() is not available in all
// compilers, also this guarantees no rounding issues. The type cast to int32_t
// serves as floor():
int32_t multiplier = (1 << int32_t(std::log2(max_tick_rate / lcm_rate)));
if (multiplier == 2 and lcm_rate >= min_tick_rate) {
// Don't bother (see above)
multiplier = 1;
}
const double new_rate = lcm_rate * multiplier;
UHD_ASSERT_THROW(uhd::math::fp_compare::fp_compare_delta<double>(
new_rate, uhd::math::FREQ_COMPARISON_DELTA_HZ)
>= uhd::math::fp_compare::fp_compare_delta<double>(
min_tick_rate, uhd::math::FREQ_COMPARISON_DELTA_HZ));
UHD_ASSERT_THROW(uhd::math::fp_compare::fp_compare_delta<double>(
new_rate, uhd::math::FREQ_COMPARISON_DELTA_HZ)
<= uhd::math::fp_compare::fp_compare_delta<double>(
max_tick_rate, uhd::math::FREQ_COMPARISON_DELTA_HZ));
return new_rate;
}
bool check_bandwidth(double rate, const std::string dir) override
{
double bw = _bw[dir == "Rx" ? "RX1" : "TX1"];
if (bw == 0.) // 0 indicates bandwidth is default value.
{
double max_bw = ad9361_device_t::AD9361_MAX_BW;
double min_bw = ad9361_device_t::AD9361_MIN_BW;
if (rate > max_bw) {
UHD_LOGGER_WARNING("AD936X")
<< "Selected " << dir << " sample rate (" << (rate / 1e6)
<< " MHz) is greater than\n"
<< "analog frontend filter bandwidth (" << (max_bw / 1e6) << " MHz).";
} else if (rate < min_bw) {
UHD_LOGGER_WARNING("AD936X")
<< "Selected " << dir << " sample rate (" << (rate / 1e6)
<< " MHz) is less than\n"
<< "analog frontend filter bandwidth (" << (min_bw / 1e6) << " MHz).";
}
}
return (rate <= bw);
}
void populate_frontend_subtree(uhd::property_tree::sptr subtree,
const std::string& key,
uhd::direction_t dir) override
{
subtree->create<std::string>("name").set("FE-" + key);
// Sensors
subtree->create<sensor_value_t>("sensors/temp").set_publisher([this]() {
return this->_codec_ctrl->get_temperature();
});
if (dir == RX_DIRECTION) {
subtree->create<sensor_value_t>("sensors/rssi").set_publisher([this, key]() {
return this->_codec_ctrl->get_rssi(key);
});
}
// Gains
for (const std::string& name : ad9361_ctrl::get_gain_names(key)) {
subtree->create<meta_range_t>(uhd::fs_path("gains") / name / "range")
.set(ad9361_ctrl::get_gain_range(key));
subtree->create<double>(uhd::fs_path("gains") / name / "value")
.set(ad936x_manager::DEFAULT_GAIN)
.set_coercer([this, key](const double gain) {
return this->_codec_ctrl->set_gain(key, gain);
});
}
// FE Settings
subtree->create<std::string>("connection").set("IQ");
subtree->create<bool>("enabled").set(true);
subtree->create<bool>("use_lo_offset").set(false);
// Analog Bandwidths
subtree->create<double>("bandwidth/value")
.set(DEFAULT_BANDWIDTH)
.set_coercer([this, key](double bw) { return set_bw_filter(key, bw); });
subtree->create<meta_range_t>("bandwidth/range").set_publisher([key]() {
return ad9361_ctrl::get_bw_filter_range();
});
// LO Tuning
subtree->create<meta_range_t>("freq/range").set_publisher([]() {
return ad9361_ctrl::get_rf_freq_range();
});
subtree->create<double>("freq/value")
.set_publisher([this, key]() { return this->_codec_ctrl->get_freq(key); })
.set_coercer([this, key](const double freq) {
return this->_codec_ctrl->tune(key, freq);
});
// Frontend corrections
if (dir == RX_DIRECTION) {
subtree->create<bool>("dc_offset/enable")
.set(ad936x_manager::DEFAULT_AUTO_DC_OFFSET)
.add_coerced_subscriber([this, key](const bool enable) {
this->_codec_ctrl->set_dc_offset_auto(key, enable);
});
subtree->create<bool>("iq_balance/enable")
.set(ad936x_manager::DEFAULT_AUTO_IQ_BALANCE)
.add_coerced_subscriber([this, key](const bool enable) {
this->_codec_ctrl->set_iq_balance_auto(key, enable);
});
// AGC setup
const std::list<std::string> mode_strings{"slow", "fast"};
subtree->create<bool>("gain/agc/enable")
.set(DEFAULT_AGC_ENABLE)
.add_coerced_subscriber([this, key](const bool enable) {
this->_codec_ctrl->set_agc(key, enable);
});
subtree->create<std::string>("gain/agc/mode/value")
.add_coerced_subscriber([this, key](const std::string& value) {
this->_codec_ctrl->set_agc_mode(key, value);
})
.set(mode_strings.front());
subtree->create<std::list<std::string>>("gain/agc/mode/options")
.set(mode_strings);
}
// Frontend filters
for (const auto& filter_name : _codec_ctrl->get_filter_names(key)) {
subtree
->create<filter_info_base::sptr>(
uhd::fs_path("filters") / filter_name / "value")
.set_publisher([this, key, filter_name]() {
return this->_codec_ctrl->get_filter(key, filter_name);
})
.add_coerced_subscriber(
[this, key, filter_name](filter_info_base::sptr filter_info) {
this->_codec_ctrl->set_filter(key, filter_name, filter_info);
});
}
}
private:
//! Store a pointer to an actual AD936x control object
ad9361_ctrl::sptr _codec_ctrl;
//! Do we have 1 or 2 frontends?
const size_t _n_frontends;
//! List of valid RX frontend names (RX1, RX2)
std::vector<std::string> _rx_frontends;
//! List of valid TX frontend names (TX1, TX2)
std::vector<std::string> _tx_frontends;
//! Current bandwidths
std::map<std::string, double> _bw;
//! Function to set bandwidth so it is tracked here
double set_bw_filter(const std::string& which, const double bw)
{
double actual_bw = _codec_ctrl->set_bw_filter(which, bw);
_bw[which] = actual_bw;
return actual_bw;
}
}; /* class ad936x_manager_impl */
ad936x_manager::sptr ad936x_manager::make(
const ad9361_ctrl::sptr& codec_ctrl, const size_t n_frontends)
{
return std::make_shared<ad936x_manager_impl>(codec_ctrl, n_frontends);
}
|