/*
Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the
Queen in Right of Canada (Communications Research Center Canada)
Copyright (C) 2018
Evariste F5OEO, evaristec@gmail.com
DESCRIPTION:
It is an output driver using the LimeSDR library.
*/
/*
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/Lime.h"
#ifdef HAVE_LIMESDR
#include
#include
#include
#include
#include "Log.h"
#include "Utils.h"
using namespace std;
namespace Output {
static constexpr size_t FRAMES_MAX_SIZE = 2;
Lime::Lime(SDRDeviceConfig& config) :
SDRDevice(),
m_conf(config)
{
etiLog.level(info) <<
"Lime:Creating the device with: " <<
m_conf.device;
int device_count = LMS_GetDeviceList(NULL);
if (device_count < 0)
{
etiLog.level(error) << "Error making LimeSDR device: " << LMS_GetLastErrorMessage();
throw std::runtime_error("Cannot find LimeSDR output device");
}
lms_info_str_t device_list[device_count];
if (LMS_GetDeviceList(device_list) < 0)
{
etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
throw std::runtime_error("Cannot find LimeSDR output device");
}
unsigned int device_i=0; // If several cards, need to get device by configuration
if (LMS_Open(&m_device, device_list[device_i], NULL) < 0)
{
etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
throw std::runtime_error("Cannot open LimeSDR output device");
}
if (LMS_Reset(m_device) < 0)
{
etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
throw std::runtime_error("Cannot reset LimeSDR output device");
}
if (LMS_Init(m_device) < 0)
{
etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
throw std::runtime_error("Cannot init LimeSDR output device");
}
if (LMS_EnableChannel(m_device, LMS_CH_TX, m_channel, true) < 0)
{
etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
throw std::runtime_error("Cannot channel LimeSDR output device");
}
if (LMS_SetSampleRate(m_device, m_conf.masterClockRate, 0) < 0)
{
etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
throw std::runtime_error("Cannot channel LimeSDR output device");
}
float_type host_sample_rate=0.0;
if (LMS_GetSampleRate(m_device, LMS_CH_TX, m_channel, &host_sample_rate, NULL) < 0)
{
etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
throw std::runtime_error("Cannot getsamplerate LimeSDR output device");
}
etiLog.level(info) << "LimeSDR master clock rate set to " <<
std::fixed << std::setprecision(4) <<
host_sample_rate/1000.0 << " kHz";
if (LMS_SetLOFrequency(m_device, LMS_CH_TX,m_channel, m_conf.frequency) < 0)
{
etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
throw std::runtime_error("Cannot Frequency LimeSDR output device");
}
float_type cur_frequency=0.0;
if (LMS_GetLOFrequency(m_device, LMS_CH_TX,m_channel, &cur_frequency)<0)
{
etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
throw std::runtime_error("Cannot GetFrequency LimeSDR output device");
}
etiLog.level(info) << "LimeSDR:Actual frequency: " <<
std::fixed << std::setprecision(3) <<
cur_frequency / 1000.0 << " kHz.";
if (LMS_SetNormalizedGain(m_device, LMS_CH_TX, m_channel, m_conf.txgain/100.0) < 0) //value 0..100 -> Normalize
{
etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
throw std::runtime_error("Cannot Gain LimeSDR output device");
}
if (LMS_SetAntenna(m_device, LMS_CH_TX, m_channel, LMS_PATH_TX2) < 0)
{
etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
throw std::runtime_error("Cannot Antenna LimeSDR output device");
}
double bandwidth_calibrating=2.5e6; // Minimal bandwidth
if (LMS_Calibrate(m_device, LMS_CH_TX, m_channel, bandwidth_calibrating, 0) < 0)
{
etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
throw std::runtime_error("Cannot Gain LimeSDR output device");
}
/*
m_device->setMasterClockRate(m_conf.masterClockRate);
etiLog.level(info) << "SoapySDR master clock rate set to " <<
std::fixed << std::setprecision(4) <<
m_device->getMasterClockRate()/1000.0 << " kHz";
m_device->setSampleRate(SOAPY_SDR_TX, 0, m_conf.sampleRate);
etiLog.level(info) << "SoapySDR:Actual TX rate: " <<
std::fixed << std::setprecision(4) <<
m_device->getSampleRate(SOAPY_SDR_TX, 0) / 1000.0 <<
" ksps.";
tune(m_conf.lo_offset, m_conf.frequency);
m_conf.frequency = m_device->getFrequency(SOAPY_SDR_TX, 0);
etiLog.level(info) << "SoapySDR:Actual frequency: " <<
std::fixed << std::setprecision(3) <<
m_conf.frequency / 1000.0 << " kHz.";
m_device->setGain(SOAPY_SDR_TX, 0, m_conf.txgain);
etiLog.level(info) << "SoapySDR:Actual tx gain: " <<
std::fixed << std::setprecision(2) <<
m_device->getGain(SOAPY_SDR_TX, 0);
if (not m_conf.tx_antenna.empty()) {
m_device->setAntenna(SOAPY_SDR_TX, 0, m_conf.tx_antenna);
}
etiLog.level(info) << "SoapySDR:Actual tx antenna: " <<
m_device->getAntenna(SOAPY_SDR_TX, 0);
if (m_device->hasHardwareTime()) {
using namespace std::chrono;
auto n = system_clock::now();
const long long ticks = duration_cast(n.time_since_epoch()).count();
m_device->setHardwareTime(ticks);
}
const std::vector channels({0});
m_tx_stream = m_device->setupStream(SOAPY_SDR_TX, "CF32", channels);
m_rx_stream = m_device->setupStream(SOAPY_SDR_RX, "CF32", channels);
*/
}
Lime::~Lime()
{
if (m_device != nullptr)
{
//if (m_tx_stream != nullptr)
{
LMS_StopStream(&m_tx_stream);
LMS_DestroyStream(m_device, &m_tx_stream);
}
/*
if (m_rx_stream != nullptr) {
m_device->closeStream(m_rx_stream);
}
*/
LMS_EnableChannel(m_device, LMS_CH_TX, m_channel, false);
LMS_Close(m_device);
}
}
void Lime::tune(double lo_offset, double frequency)
{
/* if (not m_device) throw runtime_error("Soapy device not set up");
SoapySDR::Kwargs offset_arg;
offset_arg["OFFSET"] = to_string(lo_offset);
m_device->setFrequency(SOAPY_SDR_TX, 0, m_conf.frequency, offset_arg);
*/
}
double Lime::get_tx_freq(void) const
{
if (not m_device) throw runtime_error("Lime device not set up");
// TODO lo offset
return 0;//m_device->getFrequency(SOAPY_SDR_TX, 0);
}
void Lime::set_txgain(double txgain)
{
m_conf.txgain = txgain;
if (not m_device) throw runtime_error("Lime device not set up");
//m_device->setGain(SOAPY_SDR_TX, 0, m_conf.txgain);
}
double Lime::get_txgain(void) const
{
if (not m_device) throw runtime_error("Lime device not set up");
return 0;//m_device->getGain(SOAPY_SDR_TX, 0);
}
SDRDevice::RunStatistics Lime::get_run_statistics(void) const
{
RunStatistics rs;
rs.num_underruns = underflows;
rs.num_overruns = overflows;
rs.num_late_packets = late_packets;
rs.num_frames_modulated = num_frames_modulated;
return rs;
}
double Lime::get_real_secs(void) const
{
/*if (m_device) {
long long time_ns = m_device->getHardwareTime();
return time_ns / 1e9;
}
else {
return 0.0;
}*/
return 0.0;
}
void Lime::set_rxgain(double rxgain)
{
/*m_device->setGain(SOAPY_SDR_RX, 0, m_conf.rxgain);
m_conf.rxgain = m_device->getGain(SOAPY_SDR_RX, 0);
*/
}
double Lime::get_rxgain(void) const
{
return 0.0;//m_device->getGain(SOAPY_SDR_RX, 0);
}
size_t Lime::receive_frame(
complexf *buf,
size_t num_samples,
struct frame_timestamp& ts,
double timeout_secs)
{
/*int flags = 0;
long long timeNs = ts.get_ns();
const size_t numElems = num_samples;
void *buffs[1];
buffs[0] = buf;
int ret = m_device->activateStream(m_rx_stream, flags, timeNs, numElems);
if (ret != 0) {
throw std::runtime_error(string("Soapy activate RX stream failed: ") +
SoapySDR::errToStr(ret));
}
m_rx_stream_active = true;
int n_read = m_device->readStream(
m_rx_stream, buffs, num_samples, flags, timeNs);
ret = m_device->deactivateStream(m_rx_stream);
if (ret != 0) {
throw std::runtime_error(string("Soapy deactivate RX stream failed: ") +
SoapySDR::errToStr(ret));
}
m_rx_stream_active = false;
if (n_read < 0) {
throw std::runtime_error(string("Soapy failed to read from RX stream : ") +
SoapySDR::errToStr(ret));
}
ts.set_ns(timeNs);
return n_read;
*/
return 0;
}
bool Lime::is_clk_source_ok() const
{
// TODO
return true;
}
const char* Lime::device_name(void) const
{
return "Lime";
}
double Lime::get_temperature(void) const
{
// TODO Unimplemented
// LimeSDR exports 'lms7_temp'
return std::numeric_limits::quiet_NaN();
}
void Lime::transmit_frame(const struct FrameData& frame)
{
if (not m_device) throw runtime_error("Lime device not set up");
/*
long long int timeNs = frame.ts.get_ns();
// muting and mutenotimestamp is handled by SDR
const bool has_time_spec = (m_conf.enableSync and frame.ts.timestamp_valid);
*/
if (not m_tx_stream_active)
{
unsigned int buffer_size = 200000*2*sizeof(complexf);
m_tx_stream.channel = m_channel;
m_tx_stream.fifoSize = buffer_size;
m_tx_stream.throughputVsLatency = 0.5;
m_tx_stream.isTx = LMS_CH_TX;
m_tx_stream.dataFmt = lms_stream_t::LMS_FMT_F32;
if ( LMS_SetupStream(m_device, &m_tx_stream) < 0 )
{
etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage();
throw std::runtime_error("Cannot Channel Activate LimeSDR output device");
}
LMS_StartStream(&m_tx_stream);
m_tx_stream_active = true;
}
// The frame buffer contains bytes representing FC32 samples
const complexf *buf = reinterpret_cast(frame.buf.data());
const size_t numSamples = frame.buf.size() / sizeof(complexf);
if ((frame.buf.size() % sizeof(complexf)) != 0)
{
throw std::runtime_error("Lime: invalid buffer size");
}
//etiLog.level(info) << "LimeSDR:Buffer " << std::fixed << numSamples << " /" << m_tx_stream.fifoSize ;
auto num_sent = LMS_SendStream( &m_tx_stream, buf, numSamples, NULL, 1000 );
if (num_sent <= 0)
{
etiLog.level(info) << num_sent;
//throw std::runtime_error("Lime: Too Loonnnngg");
}
else
{
//etiLog.level(info) << "OK" << num_sent;
}
/*
else if (num_sent == SOAPY_SDR_OVERFLOW) {
overflows++;
continue;
}
else if (num_sent == SOAPY_SDR_UNDERFLOW) {
underflows++;
continue;
}
if (num_sent < 0) {
etiLog.level(error) << "Unexpected stream error " <<
SoapySDR::errToStr(num_sent);
throw std::runtime_error("Fault in Soapy");
}
timeNs += 1e9 * num_sent/m_conf.sampleRate;
num_acc_samps += num_sent;
if (end_of_burst) {
int ret_deact = m_device->deactivateStream(m_tx_stream);
if (ret_deact != 0) {
throw std::runtime_error(
string("Soapy activate TX stream failed: ") +
SoapySDR::errToStr(ret_deact));
}
m_tx_stream_active = false;
}
if (eob_because_muting) {
break;
}*/
//num_acc_samps += num_sent;
num_frames_modulated++;
}
} // namespace Output
#endif // HAVE_LIMESDR