/////////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2017 Edouard Griffiths, F4EXB // // // // 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 as 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 V3 for more details. // // // // You should have received a copy of the GNU General Public License // // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include "dsp/dsptypes.h" #include "dsp/wfir.h" #include "deviceplutosdr.h" #include "deviceplutosdrbox.h" DevicePlutoSDRBox::DevicePlutoSDRBox(const std::string& uri) : m_devSampleRate(0), m_LOppmTenths(0), m_lpfFIREnable(false), m_lpfFIRBW(100.0f), m_lpfFIRlog2Decim(0), m_lpfFIRRxGain(0), m_lpfFIRTxGain(0), m_ctx(0), m_devPhy(0), m_devRx(0), m_devTx(0), m_chnRx0i(0), m_chnRx0q(0), m_chnTx0i(0), m_chnTx0q(0), m_rxBuf(0), m_txBuf(0), m_xoInitial(0), m_temp(0.0f) { m_ctx = iio_create_context_from_uri(uri.c_str()); if (m_ctx) { m_devPhy = iio_context_find_device(m_ctx, "ad9361-phy"); m_devRx = iio_context_find_device(m_ctx, "cf-ad9361-lpc"); m_devTx = iio_context_find_device(m_ctx, "cf-ad9361-dds-core-lpc"); } else { qCritical("DevicePlutoSDRBox::DevicePlutoSDRBox: cannot create context for uri: %s", uri.c_str()); } m_valid = m_ctx && m_devPhy && m_devRx && m_devTx; if (m_valid) { getXO(); // int nb_channels = iio_device_get_channels_count(m_devRx); // for (int i = 0; i < nb_channels; i++) { // iio_channel_disable(iio_device_get_channel(m_devRx, i)); // } // nb_channels = iio_device_get_channels_count(m_devTx); // for (int i = 0; i < nb_channels; i++) { // iio_channel_disable(iio_device_get_channel(m_devTx, i)); // } } } DevicePlutoSDRBox::~DevicePlutoSDRBox() { deleteRxBuffer(); deleteTxBuffer(); closeRx(); closeTx(); if (m_ctx) { iio_context_destroy(m_ctx); } } bool DevicePlutoSDRBox::probeURI(const std::string& uri) { bool retVal; struct iio_context *ctx; ctx = iio_create_context_from_uri(uri.c_str()); retVal = (ctx != 0); if (ctx) { iio_context_destroy(ctx); } return retVal; } void DevicePlutoSDRBox::set_params(DeviceType devType, const std::vector& params) { iio_device *dev; switch (devType) { case DEVICE_PHY: dev = m_devPhy; break; case DEVICE_RX: dev = m_devRx; break; case DEVICE_TX: dev = m_devTx; break; default: dev = m_devPhy; break; } for (std::vector::const_iterator it = params.begin(); it != params.end(); ++it) { struct iio_channel *chn = 0; const char *attr = 0; std::size_t pos; int ret; int type; pos = it->find('='); if (pos == std::string::npos) { std::cerr << "DevicePlutoSDRBox::set_params: Malformed line: " << *it << std::endl; continue; } std::string key = it->substr(0, pos); std::string val = it->substr(pos + 1, std::string::npos); ret = iio_device_identify_filename(dev, key.c_str(), &chn, &attr); if (ret) { std::cerr << "DevicePlutoSDRBox::set_params: Parameter not recognized: " << key << std::endl; continue; } if (chn) { ret = iio_channel_attr_write(chn, attr, val.c_str()); type = 0; } else if (iio_device_find_attr(dev, attr)) { ret = iio_device_attr_write(dev, attr, val.c_str()); type = 1; } else { ret = iio_device_debug_attr_write(dev, attr, val.c_str()); type = 2; } if (ret < 0) { std::string item; char errstr[256]; switch (type) { case 0: item = "channel"; break; case 1: item = "device"; break; case 2: item = "debug"; break; default: item = "unknown"; break; } iio_strerror(-ret, errstr, 255); std::cerr << "DevicePlutoSDRBox::set_params: Unable to write " << item << " attribute " << key << "=" << val << ": " << errstr << " (" << ret << ") " << std::endl; } else { std::cerr << "DevicePlutoSDRBox::set_params: set attribute " << key << "=" << val << ": " << ret << std::endl; } } } bool DevicePlutoSDRBox::get_param(DeviceType devType, const std::string ¶m, std::string &value) { struct iio_channel *chn = 0; const char *attr = 0; char valuestr[256]; int ret; ssize_t nchars; iio_device *dev; switch (devType) { case DEVICE_PHY: dev = m_devPhy; break; case DEVICE_RX: dev = m_devRx; break; case DEVICE_TX: dev = m_devTx; break; default: dev = m_devPhy; break; } ret = iio_device_identify_filename(dev, param.c_str(), &chn, &attr); if (ret) { std::cerr << "DevicePlutoSDRBox::get_param: Parameter not recognized: " << param << std::endl; return false; } if (chn) { nchars = iio_channel_attr_read(chn, attr, valuestr, 256); } else if (iio_device_find_attr(dev, attr)) { nchars = iio_device_attr_read(dev, attr, valuestr, 256); } else { nchars = iio_device_debug_attr_read(dev, attr, valuestr, 256); } if (nchars < 0) { std::cerr << "DevicePlutoSDRBox::get_param: Unable to read attribute " << param << ": " << nchars << std::endl; return false; } else { value.assign(valuestr); return true; } } void DevicePlutoSDRBox::setFilter(const std::string &filterConfigStr) { int ret; ret = iio_device_attr_write_raw(m_devPhy, "filter_fir_config", filterConfigStr.c_str(), filterConfigStr.size()); if (ret < 0) { std::cerr << "DevicePlutoSDRBox::set_filter: Unable to set: " << filterConfigStr << ": " << ret << std::endl; } } bool DevicePlutoSDRBox::openRx() { if (!m_valid) { return false; } if (!m_chnRx0i) { m_chnRx0i = iio_device_find_channel(m_devRx, "voltage0", false); } if (m_chnRx0i) { iio_channel_enable(m_chnRx0i); const struct iio_data_format *df = iio_channel_get_data_format(m_chnRx0i); qDebug("DevicePlutoSDRBox::openRx channel I: length: %u bits: %u shift: %u signed: %s be: %s with_scale: %s scale: %lf repeat: %u", df->length, df->bits, df->shift, df->is_signed ? "true" : "false", df->is_be ? "true" : "false", df->with_scale? "true" : "false", df->scale, df->repeat); } else { std::cerr << "DevicePlutoSDRBox::openRx: failed" << std::endl; return false; } if (!m_chnRx0q) { m_chnRx0q = iio_device_find_channel(m_devRx, "voltage1", false); } if (m_chnRx0q) { iio_channel_enable(m_chnRx0q); const struct iio_data_format* df = iio_channel_get_data_format(m_chnRx0q); qDebug("DevicePlutoSDRBox::openRx channel Q: length: %u bits: %u shift: %u signed: %s be: %s with_scale: %s scale: %lf repeat: %u", df->length, df->bits, df->shift, df->is_signed ? "true" : "false", df->is_be ? "true" : "false", df->with_scale ? "true" : "false", df->scale, df->repeat); return true; } else { std::cerr << "DevicePlutoSDRBox::openRx: failed" << std::endl; return false; } } bool DevicePlutoSDRBox::openTx() { if (!m_valid) { return false; } if (!m_chnTx0i) { m_chnTx0i = iio_device_find_channel(m_devTx, "voltage0", true); } if (m_chnTx0i) { iio_channel_enable(m_chnTx0i); const struct iio_data_format *df = iio_channel_get_data_format(m_chnTx0i); qDebug("DevicePlutoSDRBox::openTx: channel I: length: %u bits: %u shift: %u signed: %s be: %s with_scale: %s scale: %lf repeat: %u", df->length, df->bits, df->shift, df->is_signed ? "true" : "false", df->is_be ? "true" : "false", df->with_scale? "true" : "false", df->scale, df->repeat); } else { std::cerr << "DevicePlutoSDRBox::openTx: failed to open I channel" << std::endl; return false; } if (!m_chnTx0q) { m_chnTx0q = iio_device_find_channel(m_devTx, "voltage1", true); } if (m_chnTx0q) { iio_channel_enable(m_chnTx0q); const struct iio_data_format *df = iio_channel_get_data_format(m_chnTx0q); qDebug("DevicePlutoSDRBox::openTx: channel Q: length: %u bits: %u shift: %u signed: %s be: %s with_scale: %s scale: %lf repeat: %u", df->length, df->bits, df->shift, df->is_signed ? "true" : "false", df->is_be ? "true" : "false", df->with_scale? "true" : "false", df->scale, df->repeat); return true; } else { std::cerr << "DevicePlutoSDRBox::openTx: failed to open Q channel" << std::endl; return false; } } void DevicePlutoSDRBox::closeRx() { if (m_chnRx0i) { iio_channel_disable(m_chnRx0i); } if (m_chnRx0q) { iio_channel_disable(m_chnRx0q); } } void DevicePlutoSDRBox::closeTx() { if (m_chnTx0i) { iio_channel_disable(m_chnTx0i); } if (m_chnTx0q) { iio_channel_disable(m_chnTx0q); } } struct iio_buffer *DevicePlutoSDRBox::createRxBuffer(unsigned int size, bool cyclic) { if (m_devRx) { m_rxBuf = iio_device_create_buffer(m_devRx, size, cyclic ? '\1' : '\0'); } else { m_rxBuf = 0; } return m_rxBuf; } struct iio_buffer *DevicePlutoSDRBox::createTxBuffer(unsigned int size, bool cyclic) { if (m_devTx) { m_txBuf = iio_device_create_buffer(m_devTx, size, cyclic ? '\1' : '\0'); } else { m_txBuf = 0; } return m_txBuf; } void DevicePlutoSDRBox::deleteRxBuffer() { if (m_rxBuf) { iio_buffer_destroy(m_rxBuf); m_rxBuf = 0; } } void DevicePlutoSDRBox::deleteTxBuffer() { if (m_txBuf) { iio_buffer_destroy(m_txBuf); m_txBuf = 0; } } ssize_t DevicePlutoSDRBox::getRxSampleSize() { if (m_devRx) { return iio_device_get_sample_size(m_devRx); } else { return 0; } } ssize_t DevicePlutoSDRBox::getTxSampleSize() { if (m_devTx) { return iio_device_get_sample_size(m_devTx); } else { return 0; } } ssize_t DevicePlutoSDRBox::rxBufferRefill() { if (m_rxBuf) { return iio_buffer_refill(m_rxBuf); } else { return 0; } } ssize_t DevicePlutoSDRBox::txBufferPush() { if (m_txBuf) { return iio_buffer_push(m_txBuf); } else { return 0; } } std::ptrdiff_t DevicePlutoSDRBox::rxBufferStep() { if (m_rxBuf) { return iio_buffer_step(m_rxBuf); } else { return 0; } } char* DevicePlutoSDRBox::rxBufferEnd() { if (m_rxBuf) { return (char *) iio_buffer_end(m_rxBuf); } else { return 0; } } char* DevicePlutoSDRBox::rxBufferFirst() { if (m_rxBuf) { return (char *) iio_buffer_first(m_rxBuf, m_chnRx0i); } else { return 0; } } std::ptrdiff_t DevicePlutoSDRBox::txBufferStep() { if (m_txBuf) { return iio_buffer_step(m_txBuf); } else { return 0; } } char* DevicePlutoSDRBox::txBufferEnd() { if (m_txBuf) { return (char *) iio_buffer_end(m_txBuf); } else { return 0; } } char* DevicePlutoSDRBox::txBufferFirst() { if (m_txBuf) { return (char *) iio_buffer_first(m_txBuf, m_chnTx0i); } else { return 0; } } void DevicePlutoSDRBox::txChannelConvert(int16_t *dst, int16_t *src) { if (m_chnTx0i) { iio_channel_convert_inverse(m_chnTx0i, &dst[0], &src[0]); } if (m_chnTx0q) { iio_channel_convert_inverse(m_chnTx0q, &dst[1], &src[1]); } } bool DevicePlutoSDRBox::getRxSampleRates(SampleRates& sampleRates) { std::string srStr; if (get_param(DEVICE_PHY, "rx_path_rates", srStr)) { qDebug("DevicePlutoSDRBox::getRxSampleRates: %s", srStr.c_str()); return parseSampleRates(srStr, sampleRates); } else { return false; } } bool DevicePlutoSDRBox::getTxSampleRates(SampleRates& sampleRates) { std::string srStr; if (get_param(DEVICE_PHY, "tx_path_rates", srStr)) { return parseSampleRates(srStr, sampleRates); } else { return false; } } bool DevicePlutoSDRBox::parseSampleRates(const std::string& rateStr, SampleRates& sampleRates) { // Rx: "BBPLL:983040000 ADC:245760000 R2:122880000 R1:61440000 RF:30720000 RXSAMP:30720000" // Tx: "BBPLL:983040000 DAC:122880000 T2:122880000 T1:61440000 TF:30720000 TXSAMP:30720000" std::regex desc_regex("BBPLL:(.+) ..C:(.+) .2:(.+) .1:(.+) .F:(.+) .XSAMP:(.+)"); std::smatch desc_match; std::regex_search(rateStr, desc_match, desc_regex); std::string valueStr; if (desc_match.size() == 7) { try { sampleRates.m_bbRateHz = boost::lexical_cast(desc_match[1]); sampleRates.m_addaConnvRate = boost::lexical_cast(desc_match[2]); sampleRates.m_hb3Rate = boost::lexical_cast(desc_match[3]); sampleRates.m_hb2Rate = boost::lexical_cast(desc_match[4]); sampleRates.m_hb1Rate = boost::lexical_cast(desc_match[5]); sampleRates.m_firRate = boost::lexical_cast(desc_match[6]); return true; } catch (const boost::bad_lexical_cast &e) { qWarning("DevicePlutoSDRBox::parseSampleRates: bad conversion to numeric"); return false; } } else { return false; } } void DevicePlutoSDRBox::setSampleRate(uint32_t sampleRate) { char buff[100]; std::vector params; snprintf(buff, sizeof(buff), "in_voltage_sampling_frequency=%d", sampleRate); params.push_back(std::string(buff)); snprintf(buff, sizeof(buff), "out_voltage_sampling_frequency=%d", sampleRate); params.push_back(std::string(buff)); set_params(DEVICE_PHY, params); m_devSampleRate = sampleRate; } /** * @param sampleRate baseband sample rate (S/s) * @param log2IntDec FIR interpolation or decimation factor * @param use Rx or Tx. Applies to the rest of the parameters * @param bw FIR filter bandwidth at approximately -6 dB cutoff (Hz) * @param gain FIR filter gain (dB) */ void DevicePlutoSDRBox::setFIR(uint32_t sampleRate, uint32_t log2IntDec, DeviceUse use, uint32_t bw, int gain) { SampleRates sampleRates; std::ostringstream ostr; uint32_t nbTaps; double normalizedBW; uint32_t intdec = 1<<(log2IntDec > 2 ? 2 : log2IntDec); // update gain parameter if (use == USE_RX) { m_lpfFIRRxGain = gain; } else { m_lpfFIRTxGain = gain; } // set a dummy minimal filter first to get the sample rates right setFIREnable(false); // disable first formatFIRHeader(ostr, intdec); formatFIRCoefficients(ostr, 16, 0.5); setFilter(ostr.str()); ostr.str(""); // reset string stream setFIREnable(true); // re-enable setSampleRate(sampleRate); // set to new sample rate if (!getRxSampleRates(sampleRates)) { return; } setFIREnable(false); // disable again uint32_t nbGroups = sampleRates.m_addaConnvRate / 16; nbTaps = nbGroups*8 > 128 ? 128 : nbGroups*8; nbTaps = intdec == 1 ? (nbTaps > 64 ? 64 : nbTaps) : nbTaps; normalizedBW = ((float) bw) / sampleRates.m_hb1Rate; normalizedBW = normalizedBW < DevicePlutoSDR::firBWLowLimitFactor ? DevicePlutoSDR::firBWLowLimitFactor : normalizedBW > DevicePlutoSDR::firBWHighLimitFactor ? DevicePlutoSDR::firBWHighLimitFactor : normalizedBW; qDebug("DevicePlutoSDRBox::setFIR: intdec: %u gain: %d nbTaps: %u BWin: %u BW: %f (%f)", intdec, gain, nbTaps, bw, normalizedBW*sampleRates.m_hb1Rate, normalizedBW); // set the right filter formatFIRHeader(ostr, intdec); formatFIRCoefficients(ostr, nbTaps, normalizedBW); setFilter(ostr.str()); m_lpfFIRlog2Decim = log2IntDec; m_lpfFIRBW = bw; // enable and set sample rate will be done by the caller } void DevicePlutoSDRBox::setFIREnable(bool enable) { char buff[100]; std::vector params; snprintf(buff, sizeof(buff), "in_out_voltage_filter_fir_en=%d", enable ? 1 : 0); params.push_back(std::string(buff)); set_params(DEVICE_PHY, params); m_lpfFIREnable = enable; } void DevicePlutoSDRBox::setLOPPMTenths(int ppmTenths) { char buff[100]; std::vector params; int64_t newXO = m_xoInitial + ((m_xoInitial*ppmTenths) / 10000000L); snprintf(buff, sizeof(buff), "xo_correction=%ld", (long int) newXO); params.push_back(std::string(buff)); set_params(DEVICE_PHY, params); m_LOppmTenths = ppmTenths; } void DevicePlutoSDRBox::formatFIRHeader(std::ostringstream& ostr,uint32_t intdec) { ostr << "RX 3 GAIN " << m_lpfFIRRxGain << " DEC " << intdec << std::endl; ostr << "TX 3 GAIN " << m_lpfFIRTxGain << " INT " << intdec << std::endl; } void DevicePlutoSDRBox::formatFIRCoefficients(std::ostringstream& ostr, uint32_t nbTaps, double normalizedBW) { double *fcoeffs = new double[nbTaps]; WFIR::BasicFIR(fcoeffs, nbTaps, WFIR::LPF, normalizedBW, 0.0, normalizedBW < 0.2 ? WFIR::wtHAMMING : WFIR::wtBLACKMAN_HARRIS, 0.0); for (unsigned int i = 0; i < nbTaps; i++) { ostr << (int16_t) (fcoeffs[i] * 32768.0f) << ", " << (int16_t) (fcoeffs[i] * 32768.0f) << std::endl; } delete[] fcoeffs; } void DevicePlutoSDRBox::getXO() { std::string valueStr; get_param(DEVICE_PHY, "xo_correction", valueStr); try { m_xoInitial = boost::lexical_cast(valueStr); qDebug("DevicePlutoSDRBox::getXO: %ld", m_xoInitial); } catch (const boost::bad_lexical_cast &e) { qWarning("DevicePlutoSDRBox::getXO: cannot get initial XO correction"); } } bool DevicePlutoSDRBox::getRxGain(int& gaindB, unsigned int chan) { chan = chan % 2; char buff[30]; snprintf(buff, sizeof(buff), "in_voltage%d_hardwaregain", chan); std::string gainStr; get_param(DEVICE_PHY, buff, gainStr); std::regex gain_regex("(.+)\\.(.+) dB"); std::smatch gain_match; std::regex_search(gainStr, gain_match, gain_regex); if (gain_match.size() == 3) { try { gaindB = boost::lexical_cast(gain_match[1]); return true; } catch (const boost::bad_lexical_cast &e) { qWarning("DevicePlutoSDRBox::getRxGain: bad conversion to numeric"); return false; } } else { return false; } } bool DevicePlutoSDRBox::getRxRSSI(std::string& rssiStr, unsigned int chan) { chan = chan % 2; char buff[20]; snprintf(buff, sizeof(buff), "in_voltage%d_rssi", chan); return get_param(DEVICE_PHY, buff, rssiStr); } bool DevicePlutoSDRBox::getTxRSSI(std::string& rssiStr, unsigned int chan) { chan = chan % 2; char buff[20]; snprintf(buff, sizeof(buff), "out_voltage%d_rssi", chan); return get_param(DEVICE_PHY, buff, rssiStr); } void DevicePlutoSDRBox::getRxLORange(uint64_t& minLimit, uint64_t& maxLimit) { // values are returned in Hz qint64 stepLimit; std::string rangeStr; char buff[50]; snprintf(buff, sizeof(buff), "out_altvoltage0_RX_LO_frequency_available"); if (get_param(DEVICE_PHY, buff, rangeStr)) { std::istringstream instream(rangeStr.substr(1, rangeStr.size() - 2)); instream >> minLimit >> stepLimit >> maxLimit; } else { minLimit = DevicePlutoSDR::rxLOLowLimitFreq; maxLimit = DevicePlutoSDR::rxLOHighLimitFreq; } } void DevicePlutoSDRBox::getTxLORange(uint64_t& minLimit, uint64_t& maxLimit) { // values are returned in Hz qint64 stepLimit; std::string rangeStr; char buff[50]; snprintf(buff, sizeof(buff), "out_altvoltage1_TX_LO_frequency_available"); if (get_param(DEVICE_PHY, buff, rangeStr)) { std::istringstream instream(rangeStr.substr(1, rangeStr.size() - 2)); instream >> minLimit >> stepLimit >> maxLimit; } else { minLimit = DevicePlutoSDR::txLOLowLimitFreq; maxLimit = DevicePlutoSDR::txLOHighLimitFreq; } } void DevicePlutoSDRBox::getbbLPRxRange(uint32_t& minLimit, uint32_t& maxLimit) { // values are returned in Hz of RF (complex channel) bandwidth qint32 stepLimit; std::string rangeStr; char buff[50]; snprintf(buff, sizeof(buff), "in_voltage_rf_bandwidth_available"); if (get_param(DEVICE_PHY, buff, rangeStr)) { std::istringstream instream(rangeStr.substr(1, rangeStr.size() - 2)); instream >> minLimit >> stepLimit >> maxLimit; } else { minLimit = DevicePlutoSDR::bbLPRxLowLimitFreq; maxLimit = DevicePlutoSDR::bbLPRxHighLimitFreq; } } void DevicePlutoSDRBox::getbbLPTxRange(uint32_t& minLimit, uint32_t& maxLimit) { // values are returned in Hz qint32 stepLimit; std::string rangeStr; char buff[50]; snprintf(buff, sizeof(buff), "out_voltage_rf_bandwidth_available"); if (get_param(DEVICE_PHY, buff, rangeStr)) { std::istringstream instream(rangeStr.substr(1, rangeStr.size() - 2)); instream >> minLimit >> stepLimit >> maxLimit; } else { minLimit = DevicePlutoSDR::bbLPTxLowLimitFreq; maxLimit = DevicePlutoSDR::bbLPTxHighLimitFreq; } } bool DevicePlutoSDRBox::fetchTemp() { std::string temp_mC_str; if (get_param(DEVICE_PHY, "in_temp0_input", temp_mC_str)) { try { uint32_t temp_mC = boost::lexical_cast(temp_mC_str); m_temp = temp_mC / 1000.0; return true; } catch (const boost::bad_lexical_cast &e) { std::cerr << "PlutoSDRDevice::getTemp: bad conversion to numeric" << std::endl; return false; } } else { return false; } } bool DevicePlutoSDRBox::getRateGovernors(std::string& rateGovernors) { return get_param(DEVICE_PHY, "trx_rate_governor", rateGovernors); }