mirror of
https://github.com/fairwaves/UHD-Fairwaves.git
synced 2025-11-03 13:33:15 +00:00
628 lines
24 KiB
C++
628 lines
24 KiB
C++
/***********************************************************************
|
|
* The interface to lms6002d that get registered into the property tree
|
|
**********************************************************************/
|
|
|
|
#include "lms6002d_ctrl.hpp"
|
|
#include "lms6002d.hpp"
|
|
#include "cores/adf4350_regs.hpp"
|
|
|
|
#include <uhd/utils/log.hpp>
|
|
#include <uhd/utils/static.hpp>
|
|
#include <uhd/utils/assert_has.hpp>
|
|
#include <uhd/utils/algorithm.hpp>
|
|
#include <uhd/utils/msg.hpp>
|
|
#include <uhd/types/ranges.hpp>
|
|
#include <uhd/types/sensors.hpp>
|
|
#include <uhd/types/dict.hpp>
|
|
#include <uhd/usrp/dboard_base.hpp>
|
|
#include <uhd/usrp/dboard_manager.hpp>
|
|
#include <boost/assign/list_of.hpp>
|
|
#include <boost/format.hpp>
|
|
#include <boost/thread.hpp>
|
|
#include <boost/array.hpp>
|
|
#include <boost/math/special_functions/round.hpp>
|
|
#include <boost/thread/recursive_mutex.hpp>
|
|
#include <utility>
|
|
#include <cmath>
|
|
#include <cfloat>
|
|
#include <limits>
|
|
|
|
using namespace uhd;
|
|
using namespace uhd::usrp;
|
|
using namespace boost::assign;
|
|
|
|
/***********************************************************************
|
|
* The LMS6002D dboard constants
|
|
**********************************************************************/
|
|
static const freq_range_t lms_freq_range(0.2325e9, 3.72e9);
|
|
|
|
//Multiplied by 2 for conversion to complex bandpass from lowpass
|
|
static const freq_range_t lms_bandwidth_range = list_of
|
|
(range_t(2 * 0.5 * 1e6)) // A hack. See implementation for details.
|
|
(range_t(2 * 0.75 * 1e6))
|
|
(range_t(2 * 0.875 * 1e6))
|
|
(range_t(2 * 1.25 * 1e6))
|
|
(range_t(2 * 1.375 * 1e6))
|
|
(range_t(2 * 1.5 * 1e6))
|
|
(range_t(2 * 1.92 * 1e6))
|
|
(range_t(2 * 2.5 * 1e6))
|
|
(range_t(2 * 2.75 * 1e6))
|
|
(range_t(2 * 3 * 1e6))
|
|
(range_t(2 * 3.5 * 1e6))
|
|
(range_t(2 * 4.375 * 1e6))
|
|
(range_t(2 * 5 * 1e6))
|
|
(range_t(2 * 6 * 1e6))
|
|
(range_t(2 * 7 * 1e6))
|
|
(range_t(2 * 10 * 1e6))
|
|
(range_t(2 * 14 * 1e6))
|
|
;
|
|
|
|
static const std::vector<std::string> lms_tx_antennas = list_of("TX0")("TX1")("TX2")("CAL");
|
|
|
|
static const std::vector<std::string> lms_rx_antennas = list_of("RX0")("RX1")("RX2")("RX3")("CAL");
|
|
|
|
static const uhd::dict<std::string, gain_range_t> lms_tx_gain_ranges = map_list_of
|
|
//listing VGA2 first means that overall gain is first distributed to VGA2
|
|
("VGA2", gain_range_t(0, 25, double(1.0)))
|
|
("VGA1", gain_range_t(-35, -4, double(1.0)))
|
|
// Use a single control to manually control how VGA1/VGA2 are set
|
|
// ("VGA", gain_range_t(-35+0, -4+25, double(1.0)))
|
|
;
|
|
|
|
static const uhd::dict<std::string, gain_range_t> lms_rx_gain_ranges = map_list_of
|
|
// VGA1 follows the approximation of [dB] = 5 + 20*log10(127/(127-Code)) where 0 <= Code <= 120
|
|
("VGA1", gain_range_t( list_of
|
|
(range_t(5.00))(range_t(5.07))(range_t(5.14))(range_t(5.21))(range_t(5.28))
|
|
(range_t(5.35))(range_t(5.42))(range_t(5.49))(range_t(5.57))(range_t(5.64))
|
|
(range_t(5.71))(range_t(5.79))(range_t(5.86))(range_t(5.94))(range_t(6.01))
|
|
(range_t(6.09))(range_t(6.17))(range_t(6.25))(range_t(6.33))(range_t(6.41))
|
|
(range_t(6.49))(range_t(6.57))(range_t(6.65))(range_t(6.74))(range_t(6.82))
|
|
(range_t(6.90))(range_t(6.99))(range_t(7.08))(range_t(7.16))(range_t(7.25))
|
|
(range_t(7.34))(range_t(7.43))(range_t(7.52))(range_t(7.61))(range_t(7.71))
|
|
(range_t(7.80))(range_t(7.90))(range_t(7.99))(range_t(8.09))(range_t(8.19))
|
|
(range_t(8.29))(range_t(8.39))(range_t(8.49))(range_t(8.59))(range_t(8.69))
|
|
(range_t(8.80))(range_t(8.91))(range_t(9.01))(range_t(9.12))(range_t(9.23))
|
|
(range_t(9.35))(range_t(9.46))(range_t(9.57))(range_t(9.69))(range_t(9.81))
|
|
(range_t(9.93))(range_t(10.05))(range_t(10.17))(range_t(10.30))(range_t(10.43))
|
|
(range_t(10.55))(range_t(10.69))(range_t(10.82))(range_t(10.95))(range_t(11.09))
|
|
(range_t(11.23))(range_t(11.37))(range_t(11.51))(range_t(11.66))(range_t(11.81))
|
|
(range_t(11.96))(range_t(12.11))(range_t(12.27))(range_t(12.43))(range_t(12.59))
|
|
(range_t(12.76))(range_t(12.92))(range_t(13.10))(range_t(13.27))(range_t(13.45))
|
|
(range_t(13.63))(range_t(13.82))(range_t(14.01))(range_t(14.21))(range_t(14.41))
|
|
(range_t(14.61))(range_t(14.82))(range_t(15.03))(range_t(15.25))(range_t(15.48))
|
|
(range_t(15.71))(range_t(15.95))(range_t(16.19))(range_t(16.45))(range_t(16.71))
|
|
(range_t(16.97))(range_t(17.25))(range_t(17.53))(range_t(17.83))(range_t(18.13))
|
|
(range_t(18.45))(range_t(18.78))(range_t(19.12))(range_t(19.47))(range_t(19.84))
|
|
(range_t(20.23))(range_t(20.63))(range_t(21.06))(range_t(21.50))(range_t(21.97))
|
|
(range_t(22.47))(range_t(22.99))(range_t(23.55))(range_t(24.15))(range_t(24.80))
|
|
(range_t(25.49))(range_t(26.25))(range_t(27.08))(range_t(27.99))(range_t(29.01))
|
|
(range_t(30.17))
|
|
))
|
|
// We limit Rx VGA2 to 30dB, as docs say higher values are dangerous
|
|
("VGA2", gain_range_t(0, 30, double(3.0)))
|
|
// ToDo: Add LNA control here
|
|
;
|
|
|
|
|
|
static int verbosity = 0;
|
|
|
|
// LMS6002D interface class for UmTRX
|
|
|
|
class umtrx_lms6002d_dev: public lms6002d_dev {
|
|
uhd::spi_iface::sptr _spiface;
|
|
const int _slaveno;
|
|
public:
|
|
umtrx_lms6002d_dev(uhd::spi_iface::sptr spiface, const int slaveno) : _spiface(spiface), _slaveno(slaveno) {};
|
|
|
|
virtual void write_reg(uint8_t addr, uint8_t data) {
|
|
if (verbosity>2) printf("umtrx_lms6002d_dev::write_reg(addr=0x%x, data=0x%x)\n", addr, data);
|
|
uint16_t command = (((uint16_t)0x80 | (uint16_t)addr) << 8) | (uint16_t)data;
|
|
_spiface->write_spi(_slaveno, spi_config_t::EDGE_RISE, command, 16);
|
|
}
|
|
virtual uint8_t read_reg(uint8_t addr) {
|
|
if(addr > 127) return 0; // incorrect address, 7 bit long expected
|
|
uint8_t data = _spiface->read_spi(_slaveno, spi_config_t::EDGE_RISE, addr << 8, 16);
|
|
if (verbosity>2) printf("umtrx_lms6002d_dev::read_reg(addr=0x%x) data=0x%x\n", addr, data);
|
|
return data;
|
|
}
|
|
};
|
|
|
|
// LMS6002D virtual daughter board for UmTRX
|
|
|
|
class lms6002d_ctrl_impl : public lms6002d_ctrl {
|
|
public:
|
|
lms6002d_ctrl_impl(uhd::spi_iface::sptr spiface, const int lms_spi_number, const int adf4350_spi_number, const double clock_rate);
|
|
|
|
double set_rx_freq(const double freq)
|
|
{
|
|
return this->set_freq(dboard_iface::UNIT_RX, freq);
|
|
}
|
|
|
|
double set_tx_freq(const double freq)
|
|
{
|
|
return this->set_freq(dboard_iface::UNIT_TX, freq);
|
|
}
|
|
|
|
bool set_rx_enabled(const bool enb)
|
|
{
|
|
return this->set_enabled(dboard_iface::UNIT_RX, enb);
|
|
}
|
|
|
|
bool set_tx_enabled(const bool enb)
|
|
{
|
|
return this->set_enabled(dboard_iface::UNIT_TX, enb);
|
|
}
|
|
|
|
uhd::sensor_value_t get_rx_pll_locked()
|
|
{
|
|
boost::recursive_mutex::scoped_lock l(_mutex);
|
|
return uhd::sensor_value_t("LO", lms.get_rx_pll_locked(), "locked", "unlocked");
|
|
}
|
|
|
|
uhd::sensor_value_t get_tx_pll_locked()
|
|
{
|
|
boost::recursive_mutex::scoped_lock l(_mutex);
|
|
return uhd::sensor_value_t("LO", lms.get_tx_pll_locked(), "locked", "unlocked");
|
|
}
|
|
|
|
uhd::freq_range_t get_rx_bw_range(void)
|
|
{
|
|
return lms_bandwidth_range;
|
|
}
|
|
|
|
uhd::freq_range_t get_tx_bw_range(void)
|
|
{
|
|
return lms_bandwidth_range;
|
|
}
|
|
|
|
uhd::freq_range_t get_rx_freq_range(void)
|
|
{
|
|
return lms_freq_range;
|
|
}
|
|
|
|
uhd::freq_range_t get_tx_freq_range(void)
|
|
{
|
|
return lms_freq_range;
|
|
}
|
|
|
|
std::vector<std::string> get_tx_antennas(void)
|
|
{
|
|
return lms_tx_antennas;
|
|
}
|
|
|
|
std::vector<std::string> get_rx_antennas(void)
|
|
{
|
|
return lms_rx_antennas;
|
|
}
|
|
|
|
std::vector<std::string> get_tx_gains(void)
|
|
{
|
|
return lms_tx_gain_ranges.keys();
|
|
}
|
|
|
|
std::vector<std::string> get_rx_gains(void)
|
|
{
|
|
return lms_rx_gain_ranges.keys();
|
|
}
|
|
|
|
uhd::gain_range_t get_rx_gain_range(const std::string &name)
|
|
{
|
|
return lms_rx_gain_ranges[name];
|
|
}
|
|
|
|
uhd::gain_range_t get_tx_gain_range(const std::string &name)
|
|
{
|
|
return lms_tx_gain_ranges[name];
|
|
}
|
|
|
|
uint8_t get_tx_vga1dc_i_int(void)
|
|
{
|
|
boost::recursive_mutex::scoped_lock l(_mutex);
|
|
return lms.get_tx_vga1dc_i_int();
|
|
}
|
|
|
|
uint8_t get_tx_vga1dc_q_int(void)
|
|
{
|
|
boost::recursive_mutex::scoped_lock l(_mutex);
|
|
return lms.get_tx_vga1dc_q_int();
|
|
}
|
|
|
|
protected:
|
|
|
|
double set_freq(dboard_iface::unit_t unit, double f) {
|
|
boost::recursive_mutex::scoped_lock l(_mutex);
|
|
if (verbosity>0) printf("lms6002d_ctrl_impl::set_freq(%f)\n", f);
|
|
unsigned ref_freq = _clock_rate;
|
|
double actual_freq = 0;
|
|
if (unit==dboard_iface::UNIT_TX) {
|
|
actual_freq = lms.tx_pll_tune(ref_freq, f);
|
|
} else if (unit==dboard_iface::UNIT_RX) {
|
|
#if 1
|
|
actual_freq = lms.rx_pll_tune(ref_freq, f);
|
|
#else
|
|
// New beta version of the code for UmSEL support.
|
|
const double umsel_if = 359.5e6;
|
|
double actual_lms_freq = lms.rx_pll_tune(ref_freq, umsel_if);
|
|
if (verbosity>0) printf("lms6002d_ctrl_impl::set_freq() actual_lms_freq=%f\n", actual_lms_freq);
|
|
double adf4350_freq = f - actual_lms_freq;
|
|
actual_freq = tune_adf4350(adf4350_freq);
|
|
if (verbosity>0) printf("lms6002d_ctrl_impl::set_freq() adf4350 freq=%f\n", actual_freq);
|
|
actual_freq += actual_lms_freq;
|
|
if (verbosity>0) printf("lms6002d_ctrl_impl::set_freq() actual_freq=%f\n", actual_freq);
|
|
#endif
|
|
} else {
|
|
assert(!"Wrong units_t value passed to lms6002d_ctrl_impl::set_freq()");
|
|
}
|
|
if (actual_freq<0)
|
|
actual_freq = 0;
|
|
if (verbosity>0) printf("lms6002d_ctrl_impl::set_freq() actual_freq=%f\n", actual_freq);
|
|
return actual_freq;
|
|
}
|
|
|
|
bool set_enabled(dboard_iface::unit_t unit, bool en) {
|
|
boost::recursive_mutex::scoped_lock l(_mutex);
|
|
if (verbosity>0) printf("lms6002d_ctrl_impl::set_enabled(%d)\n", en);
|
|
if (unit==dboard_iface::UNIT_RX) {
|
|
if (en)
|
|
lms.rx_enable();
|
|
else
|
|
lms.rx_disable();
|
|
} else { // unit==dboard_iface::UNIT_TX
|
|
if (en)
|
|
lms.tx_enable();
|
|
else
|
|
lms.tx_disable();
|
|
}
|
|
//dump();
|
|
return en;
|
|
}
|
|
|
|
double set_rx_gain(double gain, const std::string &name) {
|
|
boost::recursive_mutex::scoped_lock l(_mutex);
|
|
if (verbosity>0) printf("lms6002d_ctrl_impl::set_rx_gain(%f, %s)\n", gain, name.c_str());
|
|
assert_has(lms_rx_gain_ranges.keys(), name, "LMS6002D rx gain name");
|
|
if(name == "VGA1"){
|
|
lms.set_rx_vga1gain(gain);
|
|
return lms.get_rx_vga1gain();
|
|
} else if(name == "VGA2"){
|
|
lms.set_rx_vga2gain(gain);
|
|
return lms.get_rx_vga2gain();
|
|
} else UHD_THROW_INVALID_CODE_PATH();
|
|
return gain;
|
|
}
|
|
|
|
void set_rx_ant(const std::string &ant) {
|
|
boost::recursive_mutex::scoped_lock l(_mutex);
|
|
if (verbosity>0) printf("lms6002d_ctrl_impl::set_rx_ant(%s)\n", ant.c_str());
|
|
//validate input
|
|
assert_has(lms_rx_antennas, ant, "LMS6002D rx antenna name");
|
|
|
|
if (ant == "CAL") {
|
|
// Enable RF loopback if disabled
|
|
if (!rf_loopback_enabled) {
|
|
if (verbosity>0) printf("lms6002d_ctrl_impl::set_rx_ant(%s) enabling RF loopback for LNA%d\n", ant.c_str(), lms.get_rx_lna());
|
|
lms.rf_loopback_enable(lms.get_rx_lna());
|
|
rf_loopback_enabled = true;
|
|
}
|
|
} else {
|
|
// Disable RF loopback if enabled
|
|
if (rf_loopback_enabled) {
|
|
lms.rf_loopback_disable();
|
|
rf_loopback_enabled = false;
|
|
}
|
|
|
|
if (ant == "RX0") {
|
|
lms.set_rx_lna(0);
|
|
} else if (ant == "RX1") {
|
|
lms.set_rx_lna(1);
|
|
} else if (ant == "RX2") {
|
|
lms.set_rx_lna(2);
|
|
} else if (ant == "RX3") {
|
|
lms.set_rx_lna(3);
|
|
} else {
|
|
UHD_THROW_INVALID_CODE_PATH();
|
|
}
|
|
}
|
|
}
|
|
|
|
double set_rx_bandwidth(double bandwidth) {
|
|
boost::recursive_mutex::scoped_lock l(_mutex);
|
|
if (verbosity>0) printf("lms6002d_ctrl_impl::set_rx_bandwidth(%f)\n", bandwidth);
|
|
// Get the closest available bandwidth
|
|
bandwidth = lms_bandwidth_range.clip(bandwidth);
|
|
|
|
// convert complex bandpass to lowpass bandwidth and to kHz
|
|
lms.set_rx_lpf(int(bandwidth/2/1e3));
|
|
|
|
return bandwidth;
|
|
}
|
|
|
|
double set_tx_gain(double gain, const std::string &name) {
|
|
boost::recursive_mutex::scoped_lock l(_mutex);
|
|
if (verbosity>0) printf("lms6002d_ctrl_impl::set_tx_gain(%f, %s)\n", gain, name.c_str());
|
|
//validate input
|
|
assert_has(lms_tx_gain_ranges.keys(), name, "LMS6002D tx gain name");
|
|
|
|
if (name == "VGA1") {
|
|
if (verbosity>1) printf("db_lms6002d::set_tx_gain() VGA1=%d\n", int(gain));
|
|
lms.set_tx_vga1gain(int(gain));
|
|
return lms.get_tx_vga1gain();
|
|
} else if (name == "VGA2") {
|
|
if (verbosity>1) printf("db_lms6002d::set_tx_gain() VGA2=%d\n", int(gain));
|
|
lms.set_tx_vga2gain(int(gain));
|
|
return lms.get_tx_vga2gain();
|
|
} else {
|
|
UHD_THROW_INVALID_CODE_PATH();
|
|
}
|
|
|
|
// Return combined gain
|
|
return tx_vga1gain+tx_vga2gain;
|
|
}
|
|
|
|
void set_tx_ant(const std::string &ant) {
|
|
boost::recursive_mutex::scoped_lock l(_mutex);
|
|
if (verbosity>0) printf("lms6002d_ctrl_impl::set_tx_ant(%s)\n", ant.c_str());
|
|
//validate input
|
|
assert_has(lms_tx_antennas, ant, "LMS6002D tx antenna ant");
|
|
|
|
if (ant == "TX0") {
|
|
lms.set_tx_pa(0);
|
|
} else if (ant == "TX1") {
|
|
lms.set_tx_pa(1);
|
|
} else if (ant == "TX2") {
|
|
lms.set_tx_pa(2);
|
|
} else if (ant == "CAL") {
|
|
// TODO: Turn on loopback
|
|
lms.set_tx_pa(0);
|
|
} else {
|
|
UHD_THROW_INVALID_CODE_PATH();
|
|
}
|
|
}
|
|
|
|
double set_tx_bandwidth(double bandwidth) {
|
|
boost::recursive_mutex::scoped_lock l(_mutex);
|
|
if (verbosity>0) printf("lms6002d_ctrl_impl::set_tx_bandwidth(%f)\n", bandwidth);
|
|
// Get the closest available bandwidth
|
|
bandwidth = lms_bandwidth_range.clip(bandwidth);
|
|
|
|
// convert complex bandpass to lowpass bandwidth and to kHz
|
|
lms.set_tx_lpf(int(bandwidth/2/1e3));
|
|
|
|
return bandwidth;
|
|
}
|
|
|
|
uint8_t _set_tx_vga1dc_i_int(uint8_t offset) {
|
|
boost::recursive_mutex::scoped_lock l(_mutex);
|
|
if (verbosity>0) printf("lms6002d_ctrl_impl::set_tx_vga1dc_i_int(%d)\n", offset);
|
|
lms.set_tx_vga1dc_i_int(offset);
|
|
return offset;
|
|
}
|
|
|
|
uint8_t _set_tx_vga1dc_q_int(uint8_t offset) {
|
|
boost::recursive_mutex::scoped_lock l(_mutex);
|
|
if (verbosity>0) printf("lms6002d_ctrl_impl::set_tx_vga1dc_q_int(%d)\n", offset);
|
|
lms.set_tx_vga1dc_q_int(offset);
|
|
return offset;
|
|
}
|
|
|
|
private:
|
|
umtrx_lms6002d_dev lms; // Interface to the LMS chip.
|
|
int tx_vga1gain, tx_vga2gain; // Stored values of Tx VGA1 and VGA2 gains.
|
|
bool rf_loopback_enabled; // Whether RF loopback is enabled.
|
|
|
|
// Tune ADF4350 on an UmSEL
|
|
double tune_adf4350(double target_freq);
|
|
|
|
uhd::spi_iface::sptr _spiface;
|
|
const int _lms_spi_number;
|
|
const int _adf4350_spi_number;
|
|
const double _clock_rate;
|
|
|
|
boost::recursive_mutex _mutex;
|
|
};
|
|
|
|
lms6002d_ctrl::sptr lms6002d_ctrl::make(uhd::spi_iface::sptr spiface, const int lms_spi_number, const int adf4350_spi_number, const double clock_rate)
|
|
{
|
|
return sptr(new lms6002d_ctrl_impl(spiface, lms_spi_number, adf4350_spi_number, clock_rate));
|
|
}
|
|
|
|
/***********************************************************************
|
|
* Tuning
|
|
**********************************************************************/
|
|
double lms6002d_ctrl_impl::tune_adf4350(double target_freq) {
|
|
UHD_LOGV(often) << boost::format(
|
|
"UmSEL tune: target frequency %f Mhz"
|
|
) % (target_freq/1e6) << std::endl;
|
|
|
|
//clip the input
|
|
// TODO::::::::::::::::::::::::::::::::
|
|
// target_freq = sbx_freq_range.clip(target_freq);
|
|
|
|
//map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler)
|
|
static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of
|
|
(0,23) //adf4350_regs_t::PRESCALER_4_5
|
|
(1,75) //adf4350_regs_t::PRESCALER_8_9
|
|
;
|
|
|
|
//map rf divider select output dividers to enums
|
|
static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of
|
|
(1, adf4350_regs_t::RF_DIVIDER_SELECT_DIV1)
|
|
(2, adf4350_regs_t::RF_DIVIDER_SELECT_DIV2)
|
|
(4, adf4350_regs_t::RF_DIVIDER_SELECT_DIV4)
|
|
(8, adf4350_regs_t::RF_DIVIDER_SELECT_DIV8)
|
|
(16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16)
|
|
;
|
|
|
|
double actual_freq, pfd_freq;
|
|
// TODO:: May be *2? Check
|
|
double ref_freq = _clock_rate * 2;
|
|
int R=0, BS=0, N=0, FRAC=0, MOD=0;
|
|
int RFdiv = 1;
|
|
adf4350_regs_t::reference_divide_by_2_t T = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED;
|
|
adf4350_regs_t::reference_doubler_t D = adf4350_regs_t::REFERENCE_DOUBLER_DISABLED;
|
|
|
|
//Reference doubler for 50% duty cycle
|
|
// if ref_freq < 12.5MHz enable regs.reference_divide_by_2
|
|
if(ref_freq <= 12.5e6) D = adf4350_regs_t::REFERENCE_DOUBLER_ENABLED;
|
|
|
|
//increase RF divider until acceptable VCO frequency
|
|
//start with target_freq*2 because mixer has divide by 2
|
|
double vco_freq = target_freq;
|
|
while (vco_freq < 2.2e9) {
|
|
vco_freq *= 2;
|
|
RFdiv *= 2;
|
|
}
|
|
|
|
//use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler)
|
|
adf4350_regs_t::prescaler_t prescaler = vco_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5;
|
|
|
|
/*
|
|
* The goal here is to loop though possible R dividers,
|
|
* band select clock dividers, N (int) dividers, and FRAC
|
|
* (frac) dividers.
|
|
*
|
|
* Calculate the N and F dividers for each set of values.
|
|
* The loop exists when it meets all of the constraints.
|
|
* The resulting loop values are loaded into the registers.
|
|
*
|
|
* from pg.21
|
|
*
|
|
* f_pfd = f_ref*(1+D)/(R*(1+T))
|
|
* f_vco = (N + (FRAC/MOD))*f_pfd
|
|
* N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD
|
|
* f_rf = f_vco/RFdiv)
|
|
* f_actual = f_rf/2
|
|
*/
|
|
for(R = 1; R <= 1023; R+=1){
|
|
//PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T)
|
|
pfd_freq = ref_freq*(1+D)/(R*(1+T));
|
|
|
|
//keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth)
|
|
if (pfd_freq > 25e6) continue;
|
|
|
|
//ignore fractional part of tuning
|
|
N = int(std::floor(vco_freq/pfd_freq));
|
|
|
|
//keep N > minimum int divider requirement
|
|
if (N < prescaler_to_min_int_div[prescaler]) continue;
|
|
|
|
for(BS=1; BS <= 255; BS+=1){
|
|
//keep the band select frequency at or below 100KHz
|
|
//constraint on band select clock
|
|
if (pfd_freq/BS > 100e3) continue;
|
|
goto done_loop;
|
|
}
|
|
} done_loop:
|
|
|
|
//Fractional-N calculation
|
|
MOD = 4095; //max fractional accuracy
|
|
FRAC = int((vco_freq/pfd_freq - N)*MOD);
|
|
|
|
//Reference divide-by-2 for 50% duty cycle
|
|
// if R even, move one divide by 2 to to regs.reference_divide_by_2
|
|
if(R % 2 == 0){
|
|
T = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED;
|
|
R /= 2;
|
|
}
|
|
|
|
//actual frequency calculation
|
|
actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))/RFdiv);
|
|
|
|
// TODO:: get_locked!
|
|
// UHD_LOGV(often)
|
|
std::cout
|
|
<< boost::format("UmSEL Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f") % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl
|
|
<< boost::format("UmSEL tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d, LD=%s"
|
|
) % R % BS % N % FRAC % MOD % T % D % RFdiv % true /* this->get_locked(unit).to_pp_string() */ << std::endl
|
|
<< boost::format("UmSEL Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f"
|
|
) % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl;
|
|
|
|
//load the register values
|
|
adf4350_regs_t regs;
|
|
|
|
// TODO:: What?
|
|
// if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq)))
|
|
// regs.output_power = adf4350_regs_t::OUTPUT_POWER_2DBM;
|
|
// else
|
|
regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM;
|
|
|
|
regs.frac_12_bit = FRAC;
|
|
regs.int_16_bit = N;
|
|
regs.mod_12_bit = MOD;
|
|
regs.prescaler = prescaler;
|
|
regs.r_counter_10_bit = R;
|
|
regs.reference_divide_by_2 = T;
|
|
regs.reference_doubler = D;
|
|
regs.band_select_clock_div = BS;
|
|
UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv));
|
|
regs.rf_divider_select = rfdivsel_to_enum[RFdiv];
|
|
|
|
regs.mute_till_lock_detect = adf4350_regs_t::MUTE_TILL_LOCK_DETECT_MUTE_ENABLED;
|
|
regs.charge_pump_current = adf4350_regs_t::CHARGE_PUMP_CURRENT_2_50MA;
|
|
regs.double_buffer = adf4350_regs_t::DOUBLE_BUFFER_ENABLED;
|
|
regs.muxout = adf4350_regs_t::MUXOUT_3STATE;
|
|
regs.low_noise_and_spur = adf4350_regs_t::LOW_NOISE_AND_SPUR_LOW_NOISE;
|
|
|
|
|
|
|
|
//write the registers
|
|
//correct power-up sequence to write registers (5, 4, 3, 2, 1, 0)
|
|
|
|
#if 0
|
|
for(addr=5; addr>=0; addr--){
|
|
// UHD_LOGV(often)
|
|
std::cout << boost::format(
|
|
"UmSEL SPI Reg (0x%02x): 0x%08x"
|
|
) % addr % regs.get_reg(addr) << std::endl;
|
|
this->get_iface()->write_spi(
|
|
uhd::usrp::dboard_iface::UNIT_SYNT, spi_config_t::EDGE_RISE,
|
|
regs.get_reg(addr), 32
|
|
);
|
|
}
|
|
#else
|
|
_spiface->write_spi(_adf4350_spi_number, spi_config_t::EDGE_RISE, 0x0580000|5, 32);
|
|
_spiface->write_spi(_adf4350_spi_number, spi_config_t::EDGE_RISE, 0x0BFF4F8|4, 32);
|
|
_spiface->write_spi(_adf4350_spi_number, spi_config_t::EDGE_RISE, 0x0040000|3, 32);
|
|
_spiface->write_spi(_adf4350_spi_number, spi_config_t::EDGE_RISE, 0x1006E40|2, 32);
|
|
_spiface->write_spi(_adf4350_spi_number, spi_config_t::EDGE_RISE, 0x8008208|1, 32);
|
|
_spiface->write_spi(_adf4350_spi_number, spi_config_t::EDGE_RISE, (325<<15)|(1<<3)|0, 32);
|
|
#endif
|
|
|
|
//return the actual frequency
|
|
// UHD_LOGV(often) << boost::format(
|
|
std::cout << boost::format(
|
|
"UmSEL tune: actual frequency %f Mhz"
|
|
) % (actual_freq/1e6) << std::endl;
|
|
return actual_freq;
|
|
}
|
|
|
|
// LMS RX dboard configuration
|
|
|
|
lms6002d_ctrl_impl::lms6002d_ctrl_impl(uhd::spi_iface::sptr spiface, const int lms_spi_number, const int adf4350_spi_number, const double clock_rate) :
|
|
lms(umtrx_lms6002d_dev(spiface, lms_spi_number)),
|
|
tx_vga1gain(lms.get_tx_vga1gain()),
|
|
tx_vga2gain(lms.get_tx_vga2gain()),
|
|
rf_loopback_enabled(false),
|
|
_spiface(spiface),
|
|
_lms_spi_number(lms_spi_number),
|
|
_adf4350_spi_number(adf4350_spi_number),
|
|
_clock_rate(clock_rate)
|
|
{
|
|
////////////////////////////////////////////////////////////////////
|
|
// LMS6002D initialization
|
|
////////////////////////////////////////////////////////////////////
|
|
lms.init();
|
|
// Set proper values for Tx and Rx Fsync and IQ interleaving.
|
|
lms.set_txrx_polarity_and_interleaving(0, lms6002d_dev::INTERLEAVE_IQ, 1, lms6002d_dev::INTERLEAVE_QI);
|
|
// Rx and Tx will be enabled/disabled during the property tree initialization
|
|
// at later steps of initialization, so it doesn't hurt that we enable them here.
|
|
lms.rx_enable();
|
|
lms.tx_enable();
|
|
// -10dB is a good value for calibration if don't know a target gain yet
|
|
lms.set_tx_vga1gain(-10);
|
|
|
|
// Perform autocalibration
|
|
lms.auto_calibration(_clock_rate, 0xf);
|
|
}
|
|
|