mirror of
https://github.com/saymrwulf/uhd.git
synced 2026-05-16 21:10:10 +00:00
Note: template_lvbitx.{cpp,hpp} need to be excluded from the list of
files that clang-format gets applied against.
386 lines
14 KiB
C++
386 lines
14 KiB
C++
//
|
|
// Copyright 2010-2012 Ettus Research LLC
|
|
// Copyright 2018 Ettus Research, a National Instruments Company
|
|
//
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
//
|
|
|
|
// No RX IO Pins Used
|
|
|
|
#include "max2112_regs.hpp"
|
|
#include <uhd/types/dict.hpp>
|
|
#include <uhd/types/ranges.hpp>
|
|
#include <uhd/types/sensors.hpp>
|
|
#include <uhd/usrp/dboard_base.hpp>
|
|
#include <uhd/usrp/dboard_manager.hpp>
|
|
#include <uhd/utils/algorithm.hpp>
|
|
#include <uhd/utils/assert_has.hpp>
|
|
#include <uhd/utils/log.hpp>
|
|
#include <uhd/utils/static.hpp>
|
|
#include <boost/assign/list_of.hpp>
|
|
#include <boost/format.hpp>
|
|
#include <boost/math/special_functions/round.hpp>
|
|
#include <boost/thread.hpp>
|
|
#include <functional>
|
|
#include <utility>
|
|
|
|
using namespace uhd;
|
|
using namespace uhd::usrp;
|
|
using namespace boost::assign;
|
|
|
|
/***********************************************************************
|
|
* The DBSRX2 constants
|
|
**********************************************************************/
|
|
static const freq_range_t dbsrx2_freq_range(0.8e9, 2.3e9);
|
|
|
|
// Multiplied by 2.0 for conversion to complex bandpass from lowpass
|
|
static const freq_range_t dbsrx2_bandwidth_range(2.0 * 4.0e6, 2.0 * 40.0e6);
|
|
|
|
static const int dbsrx2_ref_divider = 4; // Hitachi HMC426 divider (U7)
|
|
|
|
static const std::vector<std::string> dbsrx2_antennas = list_of("J3");
|
|
|
|
static const uhd::dict<std::string, gain_range_t> dbsrx2_gain_ranges =
|
|
map_list_of("GC1", gain_range_t(0, 73, 0.05))("BBG", gain_range_t(0, 15, 1));
|
|
|
|
/***********************************************************************
|
|
* The DBSRX2 dboard class
|
|
**********************************************************************/
|
|
class dbsrx2 : public rx_dboard_base
|
|
{
|
|
public:
|
|
dbsrx2(ctor_args_t args);
|
|
virtual ~dbsrx2(void);
|
|
|
|
private:
|
|
double _lo_freq;
|
|
double _bandwidth;
|
|
uhd::dict<std::string, double> _gains;
|
|
max2112_write_regs_t _max2112_write_regs;
|
|
max2112_read_regs_t _max2112_read_regs;
|
|
uint8_t _max2112_addr()
|
|
{ // 0x60 or 0x61 depending on which side
|
|
return (this->get_iface()->get_special_props().mangle_i2c_addrs) ? 0x60 : 0x61;
|
|
}
|
|
|
|
double set_lo_freq(double target_freq);
|
|
double set_gain(double gain, const std::string& name);
|
|
double set_bandwidth(double bandwidth);
|
|
|
|
void send_reg(uint8_t start_reg, uint8_t stop_reg)
|
|
{
|
|
start_reg = uint8_t(uhd::clip(int(start_reg), 0x0, 0xB));
|
|
stop_reg = uint8_t(uhd::clip(int(stop_reg), 0x0, 0xB));
|
|
|
|
for (uint8_t start_addr = start_reg; start_addr <= stop_reg;
|
|
start_addr += sizeof(uint32_t) - 1) {
|
|
int num_bytes = int(stop_reg - start_addr + 1) > int(sizeof(uint32_t)) - 1
|
|
? sizeof(uint32_t) - 1
|
|
: stop_reg - start_addr + 1;
|
|
|
|
// create buffer for register data (+1 for start address)
|
|
byte_vector_t regs_vector(num_bytes + 1);
|
|
|
|
// first byte is the address of first register
|
|
regs_vector[0] = start_addr;
|
|
|
|
// get the register data
|
|
for (int i = 0; i < num_bytes; i++) {
|
|
regs_vector[1 + i] = _max2112_write_regs.get_reg(start_addr + i);
|
|
UHD_LOGGER_TRACE("DBSRX")
|
|
<< boost::format("DBSRX2: send reg 0x%02x, value 0x%04x, start_addr "
|
|
"= 0x%04x, num_bytes %d")
|
|
% int(start_addr + i) % int(regs_vector[1 + i])
|
|
% int(start_addr) % num_bytes;
|
|
}
|
|
|
|
// send the data
|
|
this->get_iface()->write_i2c(_max2112_addr(), regs_vector);
|
|
}
|
|
}
|
|
|
|
void read_reg(uint8_t start_reg, uint8_t stop_reg)
|
|
{
|
|
static const uint8_t status_addr = 0xC;
|
|
start_reg = uint8_t(uhd::clip(int(start_reg), 0x0, 0xD));
|
|
stop_reg = uint8_t(uhd::clip(int(stop_reg), 0x0, 0xD));
|
|
|
|
for (uint8_t start_addr = start_reg; start_addr <= stop_reg;
|
|
start_addr += sizeof(uint32_t)) {
|
|
int num_bytes = int(stop_reg - start_addr + 1) > int(sizeof(uint32_t))
|
|
? sizeof(uint32_t)
|
|
: stop_reg - start_addr + 1;
|
|
|
|
// create address to start reading register data
|
|
byte_vector_t address_vector(1);
|
|
address_vector[0] = start_addr;
|
|
|
|
// send the address
|
|
this->get_iface()->write_i2c(_max2112_addr(), address_vector);
|
|
|
|
// create buffer for register data
|
|
byte_vector_t regs_vector(num_bytes);
|
|
|
|
// read from i2c
|
|
regs_vector = this->get_iface()->read_i2c(_max2112_addr(), num_bytes);
|
|
|
|
for (uint8_t i = 0; i < num_bytes; i++) {
|
|
if (i + start_addr >= status_addr) {
|
|
_max2112_read_regs.set_reg(i + start_addr, regs_vector[i]);
|
|
}
|
|
UHD_LOGGER_TRACE("DBSRX")
|
|
<< boost::format("DBSRX2: read reg 0x%02x, value 0x%04x, start_addr "
|
|
"= 0x%04x, num_bytes %d")
|
|
% int(start_addr + i) % int(regs_vector[i]) % int(start_addr)
|
|
% num_bytes;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Get the lock detect status of the LO.
|
|
* \return sensor for locked
|
|
*/
|
|
sensor_value_t get_locked(void)
|
|
{
|
|
read_reg(0xC, 0xD);
|
|
|
|
// mask and return lock detect
|
|
bool locked =
|
|
(_max2112_read_regs.ld & _max2112_read_regs.vasa & _max2112_read_regs.vase)
|
|
!= 0;
|
|
|
|
UHD_LOGGER_TRACE("DBSRX") << boost::format("DBSRX2 locked: %d") % locked;
|
|
|
|
return sensor_value_t("LO", locked, "locked", "unlocked");
|
|
}
|
|
};
|
|
|
|
/***********************************************************************
|
|
* Register the DBSRX2 dboard
|
|
**********************************************************************/
|
|
// FIXME 0x67 is the default i2c address on USRP2
|
|
// need to handle which side for USRP1 with different address
|
|
static dboard_base::sptr make_dbsrx2(dboard_base::ctor_args_t args)
|
|
{
|
|
return dboard_base::sptr(new dbsrx2(args));
|
|
}
|
|
|
|
UHD_STATIC_BLOCK(reg_dbsrx2_dboard)
|
|
{
|
|
// register the factory function for the rx dbid
|
|
dboard_manager::register_dboard(0x0012, &make_dbsrx2, "DBSRX2");
|
|
}
|
|
|
|
/***********************************************************************
|
|
* Structors
|
|
**********************************************************************/
|
|
dbsrx2::dbsrx2(ctor_args_t args) : rx_dboard_base(args)
|
|
{
|
|
// send initial register settings
|
|
send_reg(0x0, 0xB);
|
|
// for (uint8_t addr=0; addr<=12; addr++) this->send_reg(addr, addr);
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Register properties
|
|
////////////////////////////////////////////////////////////////////
|
|
this->get_rx_subtree()->create<std::string>("name").set("DBSRX");
|
|
this->get_rx_subtree()
|
|
->create<sensor_value_t>("sensors/lo_locked")
|
|
.set_publisher(std::bind(&dbsrx2::get_locked, this));
|
|
for (const std::string& name : dbsrx2_gain_ranges.keys()) {
|
|
this->get_rx_subtree()
|
|
->create<double>("gains/" + name + "/value")
|
|
.set_coercer(std::bind(&dbsrx2::set_gain, this, std::placeholders::_1, name))
|
|
.set(dbsrx2_gain_ranges[name].start());
|
|
this->get_rx_subtree()
|
|
->create<meta_range_t>("gains/" + name + "/range")
|
|
.set(dbsrx2_gain_ranges[name]);
|
|
}
|
|
this->get_rx_subtree()
|
|
->create<double>("freq/value")
|
|
.set_coercer(std::bind(&dbsrx2::set_lo_freq, this, std::placeholders::_1))
|
|
.set(dbsrx2_freq_range.start());
|
|
this->get_rx_subtree()->create<meta_range_t>("freq/range").set(dbsrx2_freq_range);
|
|
this->get_rx_subtree()
|
|
->create<std::string>("antenna/value")
|
|
.set(dbsrx2_antennas.at(0));
|
|
this->get_rx_subtree()
|
|
->create<std::vector<std::string>>("antenna/options")
|
|
.set(dbsrx2_antennas);
|
|
this->get_rx_subtree()->create<std::string>("connection").set("QI");
|
|
this->get_rx_subtree()->create<bool>("enabled").set(true); // always enabled
|
|
this->get_rx_subtree()->create<bool>("use_lo_offset").set(false);
|
|
|
|
double codec_rate = this->get_iface()->get_codec_rate(dboard_iface::UNIT_RX);
|
|
|
|
this->get_rx_subtree()
|
|
->create<double>("bandwidth/value")
|
|
.set_coercer(std::bind(&dbsrx2::set_bandwidth, this, std::placeholders::_1))
|
|
.set(2.0
|
|
* (0.8 * codec_rate
|
|
/ 2.0)); // bandwidth in lowpass, convert to complex bandpass
|
|
// default to anti-alias at different codec_rate
|
|
this->get_rx_subtree()
|
|
->create<meta_range_t>("bandwidth/range")
|
|
.set(dbsrx2_bandwidth_range);
|
|
|
|
// enable only the clocks we need
|
|
this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true);
|
|
|
|
// set the gpio directions and atr controls (identically)
|
|
this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, 0x0); // All unused in atr
|
|
this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0x0); // All Inputs
|
|
|
|
get_locked();
|
|
}
|
|
|
|
dbsrx2::~dbsrx2(void) {}
|
|
|
|
|
|
/***********************************************************************
|
|
* Tuning
|
|
**********************************************************************/
|
|
double dbsrx2::set_lo_freq(double target_freq)
|
|
{
|
|
// target_freq = dbsrx2_freq_range.clip(target_freq);
|
|
|
|
// variables used in the calculation below
|
|
int scaler = target_freq >= 1125e6 ? 2 : 4;
|
|
double ref_freq = this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX);
|
|
int R, intdiv, fracdiv, ext_div;
|
|
double N;
|
|
|
|
// compute tuning variables
|
|
ext_div = dbsrx2_ref_divider; // 12MHz < ref_freq/ext_divider < 30MHz
|
|
|
|
R = 1; // Divide by 1 is the only tested value
|
|
|
|
N = (target_freq * R * ext_div) / (ref_freq); // actual spec range is (19, 251)
|
|
intdiv = int(std::floor(N)); // if (intdiv < 19 or intdiv > 251) continue;
|
|
fracdiv = boost::math::iround((N - intdiv) * double(1 << 20));
|
|
|
|
// calculate the actual freq from the values above
|
|
N = double(intdiv) + double(fracdiv) / double(1 << 20);
|
|
_lo_freq = (N * ref_freq) / (R * ext_div);
|
|
|
|
// load new counters into registers
|
|
_max2112_write_regs.set_n_divider(intdiv);
|
|
_max2112_write_regs.set_f_divider(fracdiv);
|
|
_max2112_write_regs.r_divider = R;
|
|
_max2112_write_regs.d24 = scaler == 4 ? max2112_write_regs_t::D24_DIV4
|
|
: max2112_write_regs_t::D24_DIV2;
|
|
|
|
// debug output of calculated variables
|
|
UHD_LOGGER_TRACE("DBSRX")
|
|
<< boost::format("DBSRX2 tune:\n")
|
|
<< boost::format(" R=%d, N=%f, scaler=%d, ext_div=%d\n") % R % N % scaler
|
|
% ext_div
|
|
<< boost::format(" int=%d, frac=%d, d24=%d\n") % intdiv % fracdiv
|
|
% int(_max2112_write_regs.d24)
|
|
<< boost::format(" Ref Freq=%fMHz\n") % (ref_freq / 1e6)
|
|
<< boost::format(" Target Freq=%fMHz\n") % (target_freq / 1e6)
|
|
<< boost::format(" Actual Freq=%fMHz\n") % (_lo_freq / 1e6);
|
|
|
|
// send the registers 0x0 through 0x7
|
|
// writing register 0x4 (F divider LSB) starts the VCO auto seletion so it must be
|
|
// written last
|
|
send_reg(0x5, 0x7);
|
|
send_reg(0x0, 0x4);
|
|
|
|
// FIXME: probably unnecessary to call get_locked here
|
|
// get_locked();
|
|
|
|
return _lo_freq;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* Gain Handling
|
|
**********************************************************************/
|
|
/*!
|
|
* Convert a requested gain for the BBG vga into the integer register value.
|
|
* The gain passed into the function will be set to the actual value.
|
|
* \param gain the requested gain in dB
|
|
* \return 4 bit the register value
|
|
*/
|
|
static int gain_to_bbg_vga_reg(double& gain)
|
|
{
|
|
int reg = boost::math::iround(dbsrx2_gain_ranges["BBG"].clip(gain));
|
|
|
|
gain = double(reg);
|
|
|
|
UHD_LOGGER_TRACE("DBSRX") << boost::format("DBSRX2 BBG Gain:\n")
|
|
<< boost::format(" %f dB, bbg: %d") % gain % reg;
|
|
|
|
return reg;
|
|
}
|
|
|
|
/*!
|
|
* Convert a requested gain for the GC1 rf vga into the dac_volts value.
|
|
* The gain passed into the function will be set to the actual value.
|
|
* \param gain the requested gain in dB
|
|
* \return dac voltage value
|
|
*/
|
|
static double gain_to_gc1_rfvga_dac(double& gain)
|
|
{
|
|
// clip the input
|
|
gain = dbsrx2_gain_ranges["GC1"].clip(gain);
|
|
|
|
// voltage level constants
|
|
static const double max_volts = 0.5, min_volts = 2.7;
|
|
static const double slope =
|
|
(max_volts - min_volts) / dbsrx2_gain_ranges["GC1"].stop();
|
|
|
|
// calculate the voltage for the aux dac
|
|
double dac_volts = gain * slope + min_volts;
|
|
|
|
UHD_LOGGER_TRACE("DBSRX") << boost::format("DBSRX2 GC1 Gain:\n")
|
|
<< boost::format(" %f dB, dac_volts: %f V") % gain
|
|
% dac_volts;
|
|
|
|
// the actual gain setting
|
|
gain = (dac_volts - min_volts) / slope;
|
|
|
|
return dac_volts;
|
|
}
|
|
|
|
double dbsrx2::set_gain(double gain, const std::string& name)
|
|
{
|
|
assert_has(dbsrx2_gain_ranges.keys(), name, "dbsrx2 gain name");
|
|
if (name == "BBG") {
|
|
_max2112_write_regs.bbg = gain_to_bbg_vga_reg(gain);
|
|
send_reg(0x9, 0x9);
|
|
} else if (name == "GC1") {
|
|
// write the new voltage to the aux dac
|
|
this->get_iface()->write_aux_dac(
|
|
dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_A, gain_to_gc1_rfvga_dac(gain));
|
|
} else
|
|
UHD_THROW_INVALID_CODE_PATH();
|
|
_gains[name] = gain;
|
|
|
|
return gain;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* Bandwidth Handling
|
|
**********************************************************************/
|
|
double dbsrx2::set_bandwidth(double bandwidth)
|
|
{
|
|
// clip the input
|
|
bandwidth = dbsrx2_bandwidth_range.clip(bandwidth);
|
|
|
|
// convert complex bandpass to lowpass bandwidth
|
|
bandwidth = bandwidth / 2.0;
|
|
|
|
_max2112_write_regs.lp = int((bandwidth / 1e6 - 4) / 0.29 + 12);
|
|
_bandwidth = double(4 + (_max2112_write_regs.lp - 12) * 0.29) * 1e6;
|
|
|
|
UHD_LOGGER_TRACE("DBSRX") << boost::format("DBSRX2 Bandwidth:\n")
|
|
<< boost::format(" %f MHz, lp: %f V")
|
|
% (_bandwidth / 1e6) % int(_max2112_write_regs.lp);
|
|
|
|
this->send_reg(0x8, 0x8);
|
|
|
|
// convert lowpass back to complex bandpass bandwidth
|
|
return 2.0 * _bandwidth;
|
|
}
|