umtrx: added calibration back in

This commit is contained in:
Josh Blum
2014-04-16 14:01:42 -07:00
parent 20526c8f48
commit ccb9a97734
7 changed files with 306 additions and 13 deletions

View File

@@ -39,6 +39,7 @@ list(APPEND UMTRX_SOURCES
cores/tx_dsp_core_200.cpp
cores/time64_core_200.cpp
cores/validate_subdev_spec.cpp
cores/apply_corrections.cpp
)
########################################################################

View File

@@ -0,0 +1,192 @@
//
// Copyright 2011 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 "apply_corrections.hpp"
#include <uhd/usrp/dboard_eeprom.hpp>
#include <uhd/utils/paths.hpp>
#include <uhd/utils/msg.hpp>
#include <uhd/utils/csv.hpp>
#include <uhd/types/dict.hpp>
#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>
#include <boost/thread/mutex.hpp>
#include <cstdio>
#include <complex>
#include <fstream>
namespace fs = boost::filesystem;
boost::mutex corrections_mutex;
/***********************************************************************
* Helper routines
**********************************************************************/
static double linear_interp(double x, double x0, double y0, double x1, double y1){
return y0 + (x - x0)*(y1 - y0)/(x1 - x0);
}
/***********************************************************************
* FE apply corrections implementation
**********************************************************************/
struct fe_cal_t{
double lo_freq;
double iq_corr_real;
double iq_corr_imag;
};
static bool fe_cal_comp(fe_cal_t a, fe_cal_t b){
return (a.lo_freq < b.lo_freq);
}
static uhd::dict<std::string, std::vector<fe_cal_t> > fe_cal_cache;
static bool is_same_freq(const double f1, const double f2)
{
const double epsilon = 0.1;
return ((f1 - epsilon) < f2 and (f1 + epsilon) > f2);
}
static std::complex<double> get_fe_correction(
const std::string &key, const double lo_freq
){
const std::vector<fe_cal_t> &datas = fe_cal_cache[key];
if (datas.empty()) throw uhd::runtime_error("empty calibration table " + key);
//search for lo freq
size_t lo_index = 0;
size_t hi_index = datas.size()-1;
for (size_t i = 0; i < datas.size(); i++){
if (is_same_freq(datas[i].lo_freq, lo_freq))
{
hi_index = i;
lo_index = i;
break;
}
if (datas[i].lo_freq > lo_freq){
hi_index = i;
break;
}
lo_index = i;
}
if (lo_index == 0) return std::complex<double>(datas[lo_index].iq_corr_real, datas[lo_index].iq_corr_imag);
if (hi_index == lo_index) return std::complex<double>(datas[hi_index].iq_corr_real, datas[hi_index].iq_corr_imag);
//interpolation time
return std::complex<double>(
linear_interp(lo_freq, datas[lo_index].lo_freq, datas[lo_index].iq_corr_real, datas[hi_index].lo_freq, datas[hi_index].iq_corr_real),
linear_interp(lo_freq, datas[lo_index].lo_freq, datas[lo_index].iq_corr_imag, datas[hi_index].lo_freq, datas[hi_index].iq_corr_imag)
);
}
static void apply_fe_corrections(
uhd::property_tree::sptr sub_tree,
const uhd::fs_path &db_path,
const uhd::fs_path &fe_path,
const std::string &file_prefix,
const double lo_freq
){
//extract eeprom serial
const uhd::usrp::dboard_eeprom_t db_eeprom = sub_tree->access<uhd::usrp::dboard_eeprom_t>(db_path).get();
//make the calibration file path
const fs::path cal_data_path = fs::path(uhd::get_app_path()) / ".uhd" / "cal" / (file_prefix + db_eeprom.serial + ".csv");
if (not fs::exists(cal_data_path)) return;
//parse csv file or get from cache
if (not fe_cal_cache.has_key(cal_data_path.string())){
std::ifstream cal_data(cal_data_path.string().c_str());
const uhd::csv::rows_type rows = uhd::csv::to_rows(cal_data);
bool read_data = false, skip_next = false;;
std::vector<fe_cal_t> datas;
BOOST_FOREACH(const uhd::csv::row_type &row, rows){
if (not read_data and not row.empty() and row[0] == "DATA STARTS HERE"){
read_data = true;
skip_next = true;
continue;
}
if (not read_data) continue;
if (skip_next){
skip_next = false;
continue;
}
fe_cal_t data;
std::sscanf(row[0].c_str(), "%lf" , &data.lo_freq);
std::sscanf(row[1].c_str(), "%lf" , &data.iq_corr_real);
std::sscanf(row[2].c_str(), "%lf" , &data.iq_corr_imag);
datas.push_back(data);
}
std::sort(datas.begin(), datas.end(), fe_cal_comp);
fe_cal_cache[cal_data_path.string()] = datas;
UHD_MSG(status) << "Loaded " << cal_data_path.string() << std::endl;
}
sub_tree->access<std::complex<double> >(fe_path)
.set(get_fe_correction(cal_data_path.string(), lo_freq));
}
/***********************************************************************
* Wrapper routines with nice try/catch + print
**********************************************************************/
void uhd::usrp::apply_tx_fe_corrections(
property_tree::sptr sub_tree, //starts at mboards/x
const std::string &slot, //name of dboard slot
const double lo_freq //actual lo freq
){
boost::mutex::scoped_lock l(corrections_mutex);
try{
apply_fe_corrections(
sub_tree,
"dboards/" + slot + "/tx_eeprom",
"tx_frontends/" + slot + "/iq_balance/value",
"tx_iq_cal_v0.2_",
lo_freq
);
apply_fe_corrections(
sub_tree,
"dboards/" + slot + "/tx_eeprom",
"tx_frontends/" + slot + "/dc_offset/value",
"tx_dc_cal_v0.2_",
lo_freq
);
}
catch(const std::exception &e){
UHD_MSG(error) << "Failure in apply_tx_fe_corrections: " << e.what() << std::endl;
}
}
void uhd::usrp::apply_rx_fe_corrections(
property_tree::sptr sub_tree, //starts at mboards/x
const std::string &slot, //name of dboard slot
const double lo_freq //actual lo freq
){
boost::mutex::scoped_lock l(corrections_mutex);
try{
apply_fe_corrections(
sub_tree,
"dboards/" + slot + "/rx_eeprom",
"rx_frontends/" + slot + "/iq_balance/value",
"rx_iq_cal_v0.2_",
lo_freq
);
}
catch(const std::exception &e){
UHD_MSG(error) << "Failure in apply_rx_fe_corrections: " << e.what() << std::endl;
}
}

View File

@@ -0,0 +1,41 @@
//
// Copyright 2011 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_LIBUHD_USRP_COMMON_APPLY_CORRECTIONS_HPP
#define INCLUDED_LIBUHD_USRP_COMMON_APPLY_CORRECTIONS_HPP
#include <uhd/config.hpp>
#include <uhd/property_tree.hpp>
#include <string>
namespace uhd{ namespace usrp{
void apply_tx_fe_corrections(
property_tree::sptr sub_tree, //starts at mboards/x
const std::string &slot, //name of dboard slot
const double tx_lo_freq //actual lo freq
);
void apply_rx_fe_corrections(
property_tree::sptr sub_tree, //starts at mboards/x
const std::string &slot, //name of dboard slot
const double rx_lo_freq //actual lo freq
);
}} //namespace uhd::usrp
#endif /* INCLUDED_LIBUHD_USRP_COMMON_APPLY_CORRECTIONS_HPP */

View File

@@ -174,6 +174,16 @@ public:
return lms_tx_gain_ranges[name];
}
uint8_t get_tx_vga1dc_i_int(void)
{
return lms.get_tx_vga1dc_i_int();
}
uint8_t get_tx_vga1dc_q_int(void)
{
return lms.get_tx_vga1dc_i_int();
}
double set_freq(dboard_iface::unit_t unit, double f) {
if (verbosity>0) printf("lms6002d_ctrl_impl::set_freq(%f)\n", f);
unsigned ref_freq = _clock_rate;
@@ -563,15 +573,5 @@ lms6002d_ctrl_impl::lms6002d_ctrl_impl(uhd::spi_iface::sptr spiface, const int l
// Perform autocalibration
lms.auto_calibration(_clock_rate, 0xf);
// UmTRX specific calibration
/*
this->get_tx_subtree()->create<uint8_t>("lms6002d/tx_dc_i/value")
.subscribe(boost::bind(&lms6002d_ctrl_impl::_set_tx_vga1dc_i_int, this, _1))
.publish(boost::bind(&umtrx_lms6002d_dev::get_tx_vga1dc_i_int, &lms));
this->get_tx_subtree()->create<uint8_t>("lms6002d/tx_dc_q/value")
.subscribe(boost::bind(&lms6002d_ctrl_impl::_set_tx_vga1dc_q_int, this, _1))
.publish(boost::bind(&umtrx_lms6002d_dev::get_tx_vga1dc_q_int, &lms));
*/
}

View File

@@ -45,6 +45,12 @@ public:
virtual uhd::gain_range_t get_rx_gain_range(const std::string &name) = 0;
virtual uhd::gain_range_t get_tx_gain_range(const std::string &name) = 0;
virtual uint8_t _set_tx_vga1dc_i_int(uint8_t offset) = 0;
virtual uint8_t _set_tx_vga1dc_q_int(uint8_t offset) = 0;
virtual uint8_t get_tx_vga1dc_i_int(void) = 0;
virtual uint8_t get_tx_vga1dc_q_int(void) = 0;
};
#endif /* INCLUDED_LMS6002D_CTRL_HPP */

View File

@@ -17,6 +17,7 @@
#include "umtrx_impl.hpp"
#include "umtrx_regs.hpp"
#include "cores/apply_corrections.hpp"
#include <uhd/utils/log.hpp>
#include <uhd/utils/msg.hpp>
#include <uhd/types/sensors.hpp>
@@ -249,8 +250,8 @@ umtrx_impl::umtrx_impl(const device_addr_t &device_addr)
////////////////////////////////////////////////////////////////////
// create RF frontend interfacing
////////////////////////////////////////////////////////////////////
_lms_ctrl["A"] = lms6002d_ctrl::make(_iface, SPI_SS_LMS1, SPI_SS_AUX1, this->get_master_clock_rate()/2.0);
_lms_ctrl["B"] = lms6002d_ctrl::make(_iface, SPI_SS_LMS2, SPI_SS_AUX2, this->get_master_clock_rate()/2.0);
_lms_ctrl["A"] = lms6002d_ctrl::make(_iface, SPI_SS_LMS1, SPI_SS_AUX1, this->get_master_clock_rate());
_lms_ctrl["B"] = lms6002d_ctrl::make(_iface, SPI_SS_LMS2, SPI_SS_AUX2, this->get_master_clock_rate());
BOOST_FOREACH(const std::string &fe_name, _lms_ctrl.keys())
{
@@ -338,10 +339,39 @@ umtrx_impl::umtrx_impl(const device_addr_t &device_addr)
_tree->create<meta_range_t>(tx_rf_fe_path / "bandwidth" / "range")
.publish(boost::bind(&lms6002d_ctrl::get_tx_bw_range, ctrl));
//TODO // UmTRX specific calibration
//bind frontend corrections to the dboard freq props
_tree->access<double>(tx_rf_fe_path / "freq" / "value")
.subscribe(boost::bind(&umtrx_impl::set_tx_fe_corrections, this, "0", fe_name, _1));
_tree->access<double>(rx_rf_fe_path / "freq" / "value")
.subscribe(boost::bind(&umtrx_impl::set_rx_fe_corrections, this, "0", fe_name, _1));
//tx cal props
_tree->create<uint8_t>(tx_rf_fe_path / "lms6002d" / "tx_dc_i" / "value")
.subscribe(boost::bind(&lms6002d_ctrl::_set_tx_vga1dc_i_int, ctrl, _1))
.publish(boost::bind(&lms6002d_ctrl::get_tx_vga1dc_i_int, ctrl));
_tree->create<uint8_t>(tx_rf_fe_path / "lms6002d" / "tx_dc_q" / "value")
.subscribe(boost::bind(&lms6002d_ctrl::_set_tx_vga1dc_q_int, ctrl, _1))
.publish(boost::bind(&lms6002d_ctrl::get_tx_vga1dc_q_int, ctrl));
//set Tx DC calibration values, which are read from mboard EEPROM
std::string tx_name = (fe_name=="A")?"tx1":"tx2";
if (_iface->mb_eeprom.has_key(tx_name+"-vga1-dc-i") and not _iface->mb_eeprom[tx_name+"-vga1-dc-i"].empty()) {
_tree->access<uint8_t>(tx_rf_fe_path / "lms6002d" / "tx_dc_i" / "value")
.set(boost::lexical_cast<int>(_iface->mb_eeprom[tx_name+"-vga1-dc-i"]));
}
if (_iface->mb_eeprom.has_key(tx_name+"-vga1-dc-q") and not _iface->mb_eeprom[tx_name+"-vga1-dc-q"].empty()) {
_tree->access<uint8_t>(tx_rf_fe_path / "lms6002d" / "tx_dc_q" / "value")
.set(boost::lexical_cast<int>(_iface->mb_eeprom[tx_name+"-vga1-dc-q"]));
}
}
//set TCXO DAC calibration value, which is read from mboard EEPROM
if (_iface->mb_eeprom.has_key("tcxo-dac") and not _iface->mb_eeprom["tcxo-dac"].empty()) {
_tree->create<uint16_t>(mb_path / "tcxo_dac" / "value")
.subscribe(boost::bind(&umtrx_impl::set_tcxo_dac, this, _iface, _1))
.set(boost::lexical_cast<uint16_t>(_iface->mb_eeprom["tcxo-dac"]));
}
////////////////////////////////////////////////////////////////////
// post config tasks
@@ -384,3 +414,22 @@ void umtrx_impl::time64_self_test(void)
}
void umtrx_impl::update_clock_source(const std::string &){}
void umtrx_impl::set_rx_fe_corrections(const std::string &mb, const std::string &board, const double lo_freq){
apply_rx_fe_corrections(this->get_tree()->subtree("/mboards/" + mb), board, lo_freq);
}
void umtrx_impl::set_tx_fe_corrections(const std::string &mb, const std::string &board, const double lo_freq){
apply_tx_fe_corrections(this->get_tree()->subtree("/mboards/" + mb), board, lo_freq);
}
void umtrx_impl::set_tcxo_dac(const umtrx_iface::sptr &iface, const uint16_t val){
if (verbosity>0) printf("umtrx_impl::set_tcxo_dac(%d)\n", val);
iface->send_zpu_action(UMTRX_ZPU_REQUEST_SET_VCTCXO_DAC, val);
}
uint16_t umtrx_impl::get_tcxo_dac(const umtrx_iface::sptr &iface){
uint16_t val = iface->send_zpu_action(UMTRX_ZPU_REQUEST_GET_VCTCXO_DAC, 0);
if (verbosity>0) printf("umtrx_impl::get_tcxo_dac(): %d\n", val);
return (uint16_t)val;
}

View File

@@ -104,6 +104,10 @@ private:
void update_tx_samp_rate(const size_t, const double rate);
void time64_self_test(void);
void update_rates(void);
void set_rx_fe_corrections(const std::string &mb, const std::string &board, const double);
void set_tx_fe_corrections(const std::string &mb, const std::string &board, const double);
void set_tcxo_dac(const umtrx_iface::sptr &, const uint16_t val);
uint16_t get_tcxo_dac(const umtrx_iface::sptr &);
//streaming
std::vector<boost::weak_ptr<uhd::rx_streamer> > _rx_streamers;