/*
Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the
Queen in Right of Canada (Communications Research Center Canada)
Copyright (C) 2019
Matthias P. Braendli, matthias.braendli@mpb.li
http://opendigitalradio.org
*/
/*
This file is part of ODR-DabMod.
ODR-DabMod 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.
ODR-DabMod 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 ODR-DabMod. If not, see .
*/
#include "output/UHD.h"
#ifdef HAVE_OUTPUT_UHD
//#define MDEBUG(fmt, args...) fprintf(LOG, fmt , ## args)
#define MDEBUG(fmt, args...)
#include "PcDebug.h"
#include "Log.h"
#include "RemoteControl.h"
#include "Utils.h"
#include
#include
#include
// 3.11.0.0 introduces the API breaking change, where
// uhd::msg is replaced by the new log API
#if UHD_VERSION >= 3110000
# define UHD_HAS_LOG_API 1
# include
# include
#else
# define UHD_HAS_LOG_API 0
# include
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
namespace Output {
// Maximum number of frames that can wait in frames
static const size_t FRAMES_MAX_SIZE = 8;
static std::string stringtrim(const std::string &s)
{
auto wsfront = std::find_if_not(s.begin(), s.end(),
[](int c){ return std::isspace(c);} );
return std::string(wsfront,
std::find_if_not(s.rbegin(),
std::string::const_reverse_iterator(wsfront),
[](int c){ return std::isspace(c);} ).base());
}
#if UHD_HAS_LOG_API == 1
static void uhd_log_handler(const uhd::log::logging_info& info)
{
// do not print very short U messages, nor those of
// verbosity trace or debug
if (info.verbosity >= uhd::log::info and
stringtrim(info.message).size() != 1) {
etiLog.level(debug) << "UHD Message (" <<
(int)info.verbosity << ") " <<
info.component << ": " << info.message;
}
}
#else
static void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg)
{
if (type == uhd::msg::warning) {
etiLog.level(warn) << "UHD Warning: " << msg;
}
else if (type == uhd::msg::error) {
etiLog.level(error) << "UHD Error: " << msg;
}
else {
// do not print very short U messages and such
if (stringtrim(msg).size() != 1) {
etiLog.level(debug) << "UHD Message: " << msg;
}
}
}
#endif // UHD_HAS_LOG_API
UHD::UHD(SDRDeviceConfig& config) :
SDRDevice(),
m_conf(config),
m_running(false)
{
std::stringstream device;
device << m_conf.device;
if (m_conf.masterClockRate != 0) {
if (device.str() != "") {
device << ",";
}
device << "master_clock_rate=" << m_conf.masterClockRate;
}
MDEBUG("OutputUHD::OutputUHD(device: %s) @ %p\n",
device.str().c_str(), this);
#if UHD_HAS_LOG_API == 1
uhd::log::add_logger("dabmod", uhd_log_handler);
try {
uhd::log::set_console_level(uhd::log::fatal);
}
catch (const uhd::key_error&) {
etiLog.level(warn) << "OutputUHD: Could not set UHD console loglevel";
}
#else
uhd::msg::register_handler(uhd_msg_handler);
#endif
uhd::set_thread_priority_safe();
etiLog.log(info, "OutputUHD:Creating the usrp device with: %s...",
device.str().c_str());
m_usrp = uhd::usrp::multi_usrp::make(device.str());
etiLog.log(info, "OutputUHD:Using device: %s...",
m_usrp->get_pp_string().c_str());
if (m_conf.masterClockRate != 0.0) {
double master_clk_rate = m_usrp->get_master_clock_rate();
etiLog.log(debug, "OutputUHD:Checking master clock rate: %f...",
master_clk_rate);
if (fabs(master_clk_rate - m_conf.masterClockRate) >
(m_conf.masterClockRate * 1e-6)) {
throw std::runtime_error("Cannot set USRP master_clock_rate. Aborted.");
}
}
MDEBUG("OutputUHD:Setting REFCLK and PPS input...\n");
if (m_conf.refclk_src == "gpsdo-ettus") {
m_usrp->set_clock_source("gpsdo");
}
else {
m_usrp->set_clock_source(m_conf.refclk_src);
}
m_usrp->set_time_source(m_conf.pps_src);
if (m_conf.subDevice != "") {
m_usrp->set_tx_subdev_spec(uhd::usrp::subdev_spec_t(m_conf.subDevice),
uhd::usrp::multi_usrp::ALL_MBOARDS);
}
etiLog.level(debug) << "UHD clock source is " << m_usrp->get_clock_source(0);
etiLog.level(debug) << "UHD time source is " << m_usrp->get_time_source(0);
m_device_time = std::make_shared(m_usrp, m_conf);
m_usrp->set_tx_rate(m_conf.sampleRate);
etiLog.log(debug, "OutputUHD:Set rate to %d. Actual TX Rate: %f sps...",
m_conf.sampleRate, m_usrp->get_tx_rate());
if (fabs(m_usrp->get_tx_rate() / m_conf.sampleRate) >
m_conf.sampleRate * 1e-6) {
throw std::runtime_error("Cannot set USRP sample rate. Aborted.");
}
if (m_conf.bandwidth > 0) {
m_usrp->set_tx_bandwidth(m_conf.bandwidth);
m_usrp->set_rx_bandwidth(m_conf.bandwidth);
etiLog.level(info) << "OutputUHD:Actual TX bandwidth: " <<
std::fixed << std::setprecision(2) <<
m_usrp->get_tx_bandwidth();
}
tune(m_conf.lo_offset, m_conf.frequency);
m_conf.frequency = m_usrp->get_tx_freq();
etiLog.level(debug) << std::fixed << std::setprecision(3) <<
"OutputUHD:Actual TX frequency: " << m_conf.frequency;
etiLog.level(debug) << std::fixed << std::setprecision(3) <<
"OutputUHD:Actual RX frequency: " << m_usrp->get_tx_freq();
m_usrp->set_tx_gain(m_conf.txgain);
m_conf.txgain = m_usrp->get_tx_gain();
etiLog.log(debug, "OutputUHD:Actual TX Gain: %f", m_conf.txgain);
etiLog.log(debug, "OutputUHD:Mute on missing timestamps: %s",
m_conf.muteNoTimestamps ? "enabled" : "disabled");
m_usrp->set_rx_rate(m_conf.sampleRate);
etiLog.log(debug, "OutputUHD:Actual RX Rate: %f sps.", m_usrp->get_rx_rate());
if (not m_conf.rx_antenna.empty()) {
m_usrp->set_rx_antenna(m_conf.rx_antenna);
}
etiLog.log(debug, "OutputUHD:Actual RX Antenna: %s",
m_usrp->get_rx_antenna().c_str());
if (not m_conf.tx_antenna.empty()) {
m_usrp->set_tx_antenna(m_conf.tx_antenna);
}
etiLog.log(debug, "OutputUHD:Actual TX Antenna: %s",
m_usrp->get_tx_antenna().c_str());
m_usrp->set_rx_gain(m_conf.rxgain);
etiLog.log(debug, "OutputUHD:Actual RX Gain: %f", m_usrp->get_rx_gain());
const uhd::stream_args_t stream_args("fc32"); //complex floats
m_rx_stream = m_usrp->get_rx_stream(stream_args);
m_tx_stream = m_usrp->get_tx_stream(stream_args);
m_running.store(true);
m_async_rx_thread = std::thread(&UHD::print_async_thread, this);
MDEBUG("OutputUHD:UHD ready.\n");
}
UHD::~UHD()
{
stop_threads();
}
void UHD::tune(double lo_offset, double frequency)
{
if (lo_offset != 0.0) {
etiLog.level(info) << std::fixed << std::setprecision(3) <<
"OutputUHD:Setting freq to " << frequency <<
" with LO offset " << lo_offset << "...";
const auto tr = uhd::tune_request_t(frequency, lo_offset);
uhd::tune_result_t result = m_usrp->set_tx_freq(tr);
etiLog.level(debug) << "OutputUHD: TX freq" <<
std::fixed << std::setprecision(0) <<
" Target RF: " << result.target_rf_freq <<
" Actual RF: " << result.actual_rf_freq <<
" Target DSP: " << result.target_dsp_freq <<
" Actual DSP: " << result.actual_dsp_freq;
uhd::tune_result_t result_rx = m_usrp->set_rx_freq(tr);
etiLog.level(debug) << "OutputUHD: RX freq" <<
std::fixed << std::setprecision(0) <<
" Target RF: " << result_rx.target_rf_freq <<
" Actual RF: " << result_rx.actual_rf_freq <<
" Target DSP: " << result_rx.target_dsp_freq <<
" Actual DSP: " << result_rx.actual_dsp_freq;
}
else {
//set the centre frequency
etiLog.level(info) << std::fixed << std::setprecision(3) <<
"OutputUHD:Setting freq to " << frequency << "...";
m_usrp->set_tx_freq(frequency);
m_usrp->set_rx_freq(frequency);
}
}
double UHD::get_tx_freq(void) const
{
return m_usrp->get_tx_freq();
}
void UHD::set_txgain(double txgain)
{
m_usrp->set_tx_gain(txgain);
m_conf.txgain = m_usrp->get_tx_gain();
}
double UHD::get_txgain(void) const
{
return m_usrp->get_tx_gain();
}
void UHD::set_bandwidth(double bandwidth)
{
m_usrp->set_tx_bandwidth(bandwidth);
m_usrp->set_rx_bandwidth(bandwidth);
m_conf.bandwidth = m_usrp->get_tx_bandwidth();
}
double UHD::get_bandwidth(void) const
{
return m_usrp->get_tx_bandwidth();
}
void UHD::transmit_frame(const struct FrameData& frame)
{
const double tx_timeout = 20.0;
const size_t sizeIn = frame.buf.size() / sizeof(complexf);
const complexf* in_data = reinterpret_cast(&frame.buf[0]);
uhd::tx_metadata_t md_tx;
bool tx_allowed = true;
// muting and mutenotimestamp is handled by SDR
if (m_conf.enableSync and frame.ts.timestamp_valid) {
uhd::time_spec_t timespec(
frame.ts.timestamp_sec, frame.ts.pps_offset());
md_tx.time_spec = timespec;
md_tx.has_time_spec = true;
}
else {
md_tx.has_time_spec = false;
}
size_t usrp_max_num_samps = m_tx_stream->get_max_num_samps();
size_t num_acc_samps = 0; //number of accumulated samples
while (tx_allowed and m_running.load() and (num_acc_samps < sizeIn)) {
size_t samps_to_send = std::min(sizeIn - num_acc_samps, usrp_max_num_samps);
const bool eob_because_muting = m_conf.muting;
// ensure the the last packet has EOB set if the timestamps has been
// refreshed and need to be reconsidered. If muting was set, set the
// EOB and quit the loop afterwards, to avoid an underrun.
md_tx.end_of_burst = eob_because_muting or (
frame.ts.timestamp_valid and
m_require_timestamp_refresh and
samps_to_send <= usrp_max_num_samps );
//send a single packet
size_t num_tx_samps = m_tx_stream->send(
&in_data[num_acc_samps],
samps_to_send, md_tx, tx_timeout);
etiLog.log(trace, "UHD,sent %zu of %zu", num_tx_samps, samps_to_send);
num_acc_samps += num_tx_samps;
md_tx.time_spec += uhd::time_spec_t(0, num_tx_samps/m_conf.sampleRate);
if (num_tx_samps == 0) {
etiLog.log(warn,
"OutputUHD unable to write to device, skipping frame!");
break;
}
if (eob_because_muting) {
break;
}
}
num_frames_modulated++;
}
SDRDevice::RunStatistics UHD::get_run_statistics(void) const
{
RunStatistics rs;
rs.num_underruns = num_underflows;
rs.num_overruns = num_overflows;
rs.num_late_packets = num_late_packets;
rs.num_frames_modulated = num_frames_modulated;
if (m_device_time) {
const auto gpsdo_stat = m_device_time->get_gnss_stats();
rs.gpsdo_holdover = gpsdo_stat.holdover;
rs.gpsdo_num_sv = gpsdo_stat.num_sv;
}
return rs;
}
double UHD::get_real_secs(void) const
{
return m_usrp->get_time_now().get_real_secs();
}
void UHD::set_rxgain(double rxgain)
{
m_usrp->set_rx_gain(m_conf.rxgain);
m_conf.rxgain = m_usrp->get_rx_gain();
}
double UHD::get_rxgain() const
{
return m_usrp->get_rx_gain();
}
size_t UHD::receive_frame(
complexf *buf,
size_t num_samples,
frame_timestamp& ts,
double timeout_secs)
{
uhd::stream_cmd_t cmd(
uhd::stream_cmd_t::stream_mode_t::STREAM_MODE_NUM_SAMPS_AND_DONE);
cmd.num_samps = num_samples;
cmd.stream_now = false;
cmd.time_spec = uhd::time_spec_t(ts.timestamp_sec, ts.pps_offset());
m_rx_stream->issue_stream_cmd(cmd);
uhd::rx_metadata_t md_rx;
constexpr double timeout = 60;
size_t samples_read = m_rx_stream->recv(buf, num_samples, md_rx, timeout);
// Update the ts with the effective receive TS
ts.timestamp_sec = md_rx.time_spec.get_full_secs();
ts.timestamp_pps = md_rx.time_spec.get_frac_secs() * 16384000.0;
return samples_read;
}
// Return true if GPS and reference clock inputs are ok
bool UHD::is_clk_source_ok(void) const
{
bool ok = true;
if (refclk_loss_needs_check()) {
try {
if (not m_usrp->get_mboard_sensor("ref_locked", 0).to_bool()) {
ok = false;
etiLog.level(alert) <<
"OutputUHD: External reference clock lock lost !";
if (m_conf.refclk_lock_loss_behaviour == CRASH) {
throw std::runtime_error(
"OutputUHD: External reference clock lock lost.");
}
}
}
catch (const uhd::lookup_error &e) {
suppress_refclk_loss_check = true;
etiLog.log(warn, "OutputUHD: This USRP does not have mboard "
"sensor for ext clock loss. Check disabled.");
}
}
if (m_device_time) {
ok &= m_device_time->verify_time();
}
return ok;
}
const char* UHD::device_name(void) const
{
return "UHD";
}
double UHD::get_temperature(void) const
{
try {
return std::round(m_usrp->get_tx_sensor("temp", 0).to_real());
}
catch (const uhd::lookup_error &e) {
return std::numeric_limits::quiet_NaN();
}
}
bool UHD::refclk_loss_needs_check() const
{
if (suppress_refclk_loss_check) {
return false;
}
return m_conf.refclk_src != "internal";
}
void UHD::stop_threads()
{
m_running.store(false);
if (m_async_rx_thread.joinable()) {
m_async_rx_thread.join();
}
}
void UHD::print_async_thread()
{
while (m_running.load()) {
uhd::async_metadata_t async_md;
if (m_usrp->get_device()->recv_async_msg(async_md, 1)) {
const char* uhd_async_message = "";
bool failure = false;
switch (async_md.event_code) {
case uhd::async_metadata_t::EVENT_CODE_BURST_ACK:
break;
case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW:
uhd_async_message = "Underflow";
num_underflows++;
break;
case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR:
uhd_async_message = "Packet loss between host and device.";
failure = true;
break;
case uhd::async_metadata_t::EVENT_CODE_TIME_ERROR:
uhd_async_message = "Packet had time that was late.";
num_late_packets++;
break;
case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET:
uhd_async_message = "Underflow occurred inside a packet.";
failure = true;
break;
case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST:
uhd_async_message = "Packet loss within a burst.";
failure = true;
break;
default:
uhd_async_message = "unknown event code";
failure = true;
break;
}
if (failure) {
etiLog.level(alert) <<
"Received Async UHD Message '" <<
uhd_async_message << "' at time " <<
async_md.time_spec.get_real_secs();
}
}
auto time_now = std::chrono::steady_clock::now();
if (last_print_time + std::chrono::seconds(1) < time_now) {
const double usrp_time =
m_usrp->get_time_now().get_real_secs();
if ( (num_underflows > num_underflows_previous) or
(num_late_packets > num_late_packets_previous)) {
etiLog.log(info,
"OutputUHD status (usrp time: %f): "
"%d underruns and %d late packets since last status.\n",
usrp_time,
num_underflows - num_underflows_previous,
num_late_packets - num_late_packets_previous);
}
num_underflows_previous = num_underflows;
num_late_packets_previous = num_late_packets;
last_print_time = time_now;
}
}
}
} // namespace Output
#endif // HAVE_OUTPUT_UHD