LimeSDR support (9)

This commit is contained in:
f4exb 2017-04-15 10:31:16 +02:00
parent 3fa6c06d6f
commit 0204cca9e3
4 changed files with 259 additions and 26 deletions

View File

@ -180,13 +180,13 @@ Some SDR hardware have Rx/Tx capabilities in a SISO or MIMO configuration. That
Note that the following would also work for multiple sample channels Rx or Tx only devices but none exists or is supported at this moment.
In SDRangel there is a complete receiver or transmitter per I/Q sample flow. These transmitters and receivers are visually represented by the Rn and Tn tabs in the main window. They are created and disposed in the way explained in the previous paragraph using the source or sink selection confirmation button. In fact it acts as if each receiver or transmitter was controlled independently. In single input or single output (none at the moment) devices this is a true independence but with SISO or MIMO devices this cannot be the case and although each receiver or transmitter looks like it was handled independently there are things in common that have to be taken into account. For example it all cases the device handle should be unique and opening and closing the device has to be done only once per physical device usage cycle.
In SDRangel there is a complete receiver or transmitter per I/Q sample flow. These transmitters and receivers are visually represented by the Rn and Tn tabs in the main window. They are created and disposed in the way explained in the previous paragraph using the source or sink selection confirmation button. In fact it acts as if each receiver or transmitter was controlled independently. In single input or single output (none at the moment) devices this is a true independence but with SISO or MIMO devices this cannot be the case and although each receiver or transmitter looks like it was handled independently there are things in common that have to be taken into account. For example in all cases the device handle should be unique and opening and closing the device has to be done only once per physical device usage cycle.
This is where the "buddies list" come into play. Receivers exhibit a generic interface in the form of the DeviceSourceAPI class and transmitter have the DeviceSinkAPI with the same role. Through these APIs some information and control can flow between receivers and trasmitters. The point is that all receivers and/or transmitters pertaining to the same physical device must "know" each other in order to be able to exchange information or control each other. For this purpose the DeviceSourceAPI or DeviceSinkAPI maintain a list of DeviceSourceAPI siblings and a list of DeviceSinkAPI siblings called "buddies". Thus any multi flow Rx/Tx configuration can be handled.
The exact behaviour of these coupled receivers and/or transmitters is dependent on the hardware therefore a generic pointer attached to the API can be used to convey any kind of class or structure taylored for the exact hardware use case. Through this structure the state of the receiver or transmitter can be exposed therefore there is one structure per receiver and transmitter in the device interface. This structure may contain pointers to common areas (dynamically allocated) related to the physical device. One such "area" is the device handle which is present in all cases.
Normally the first buddy would create the common areas (through new) and the last would delete them (through delete) and the superstructure would be on the stack of each buddy. Thus through the superstructure copy a buddy would gain access to common areas from another (already present) buddy along with static information from the other buddy. Exchange of dynamic information between buddies is done using message passing.
Normally the first buddy would create the common areas (through new) and the last would delete them (through delete) and the indovidual structure (superstructure) would be on the stack of each buddy. Thus by copying this superstructure a buddy would gain access to common areas from another (already present) buddy along with static information from the other buddy (such as which hadrware Rx or Tx channel it uses in a MIMO architecture). Exchange of dynamic information between buddies is done using message passing.
The degree of entanglement between the different coupled flows in a single hardware can be very different:
@ -202,6 +202,7 @@ The degree of entanglement between the different coupled flows in a single hardw
- Rx share the same sample rate, hardware decimation factor and center frequency
- Tx share the same sample rate, hardware interpolation factor and center frequency
- Rx and Tx share the same base sample rate (decimation/interpolation apart)
- Independent Rx and Tx center frequencies
- Independent gains, bandwidths, etc... per Rx or Tx channel
The openDevice and closeDevice methods of the device interface are designed specifically for each physical device type in order to manage the common resources appropriately at receiver or transmitter construction and destruction. For example opening and closing the device and the related device handle is always done this way:
@ -212,4 +213,6 @@ The openDevice and closeDevice methods of the device interface are designed spec
- if there is no buddy open the device and store the handle in the common area so it will be visible for future buddies
- closeDevice:
- if there are no buddies then effectively close the device else just zero out the own copy of the device handle
- if there are no buddies then effectively close the device else just zero out the own copy of the device handle
Exchange of dynamic information when necessary such as sample rate or center frequency is done by message passing between buddies.

View File

@ -117,31 +117,11 @@ bool LimeSDRInput::openDevice()
return false;
}
// set up the stream
m_streamId.channel = m_deviceShared.m_channel; //channel number
m_streamId.channel.fifoSize = 1024 * 1024; //fifo size in samples TODO: adjust if necessary
m_streamId.throughputVsLatency = 1.0; //optimize for max throughput
m_streamId.isTx = false; //RX channel
m_streamId.dataFmt = lms_stream_t::LMS_FMT_I12; //12-bit integers
if (LMS_SetupStream(m_deviceShared.m_deviceParams->getDevice(), &m_streamId) != 0)
{
qCritical("LimeSDRInput::openDevice: cannot setup the stream on Rx channel %u", m_deviceShared.m_channel);
return false;
}
// TODO: start / stop streaming is done in the thread. You will need to pass the stream Id to the thread at thread creation
return true;
}
void LimeSDRInput::closeDevice()
{
// destroy the stream
LMS_DestroyStream(m_deviceShared.m_deviceParams->getDevice(), &m_streamId);
// release the channel
if (LMS_EnableChannel(m_deviceShared.m_deviceParams->getDevice(), LMS_CH_RX, m_deviceShared.m_channel, false) != 0)
@ -163,20 +143,35 @@ void LimeSDRInput::closeDevice()
bool LimeSDRInput::start()
{
if (!m_deviceShared.m_dev) {
if (!m_deviceShared.m_deviceParams->getDevice()) {
return false;
}
if (m_running) stop();
if ((m_limeSDRInputThread = new LimeSDRInputThread(m_deviceShared.m_deviceParams->getDevice(), &m_sampleFifo)) == 0)
// set up the stream
m_streamId.channel = m_deviceShared.m_channel; //channel number
m_streamId.fifoSize = 1024 * 128; //fifo size in samples
m_streamId.throughputVsLatency = 1.0; //optimize for max throughput
m_streamId.isTx = false; //RX channel
m_streamId.dataFmt = lms_stream_t::LMS_FMT_I12; //12-bit integers
if (LMS_SetupStream(m_deviceShared.m_deviceParams->getDevice(), &m_streamId) != 0)
{
qCritical("LimeSDRInput::start: cannot setup the stream on Rx channel %u", m_deviceShared.m_channel);
return false;
}
// start / stop streaming is done in the thread.
if ((m_limeSDRInputThread = new LimeSDRInputThread(&m_streamId, &m_sampleFifo)) == 0)
{
qFatal("LimeSDRInput::start: out of memory");
stop();
return false;
}
m_limeSDRInputThread->setSamplerate(m_deviceShared.m_deviceParams->m_sampleRate);
m_limeSDRInputThread->setLog2Decimation(m_settings.m_log2SoftDecim);
m_limeSDRInputThread->setFcPos((int) m_settings.m_fcPos);
@ -197,6 +192,9 @@ void LimeSDRInput::stop()
m_limeSDRInputThread = 0;
}
// destroy the stream
LMS_DestroyStream(m_deviceShared.m_deviceParams->getDevice(), &m_streamId);
m_running = false;
}

View File

@ -0,0 +1,169 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 //
// //
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "limesdrinputthread.h"
LimeSDRInputThread::LimeSDRInputThread(lms_stream_t* stream, SampleSinkFifo* sampleFifo, QObject* parent) :
QThread(parent),
m_running(false),
m_stream(stream),
m_convertBuffer(LIMESDR_BLOCKSIZE),
m_sampleFifo(sampleFifo),
m_log2Decim(0),
m_fcPos(0)
{
}
LimeSDRInputThread::~LimeSDRInputThread()
{
stopWork();
}
void LimeSDRInputThread::startWork()
{
m_startWaitMutex.lock();
start();
while(!m_running)
m_startWaiter.wait(&m_startWaitMutex, 100);
m_startWaitMutex.unlock();
}
void LimeSDRInputThread::stopWork()
{
m_running = false;
wait();
}
void LimeSDRInputThread::setLog2Decimation(unsigned int log2_decim)
{
m_log2Decim = log2_decim;
}
void LimeSDRInputThread::setFcPos(int fcPos)
{
m_fcPos = fcPos;
}
void LimeSDRInputThread::run()
{
int res;
lms_stream_meta_t metadata; //Use metadata for additional control over sample receive function behaviour
metadata.flushPartialPacket = false; //Do not discard data remainder when read size differs from packet size
metadata.waitForTimestamp = false; //Do not wait for specific timestamps
m_running = true;
m_startWaiter.wakeAll();
while (m_running)
{
if ((res = LMS_RecvStream(m_stream, (void *) m_buf, LIMESDR_BLOCKSIZE, &metadata, 1000)) < 0)
{
qCritical("LimeSDRInputThread::run read error: %s", strerror(errno));
break;
}
callback(m_buf, 2 * LIMESDR_BLOCKSIZE);
}
m_running = false;
}
// Decimate according to specified log2 (ex: log2=4 => decim=16)
void LimeSDRInputThread::callback(const qint16* buf, qint32 len)
{
SampleVector::iterator it = m_convertBuffer.begin();
if (m_log2Decim == 0)
{
m_decimators.decimate1(&it, buf, len);
}
else
{
if (m_fcPos == 0) // Infra
{
switch (m_log2Decim)
{
case 1:
m_decimators.decimate2_inf(&it, buf, len);
break;
case 2:
m_decimators.decimate4_inf(&it, buf, len);
break;
case 3:
m_decimators.decimate8_inf(&it, buf, len);
break;
case 4:
m_decimators.decimate16_inf(&it, buf, len);
break;
case 5:
m_decimators.decimate32_inf(&it, buf, len);
break;
default:
break;
}
}
else if (m_fcPos == 1) // Supra
{
switch (m_log2Decim)
{
case 1:
m_decimators.decimate2_sup(&it, buf, len);
break;
case 2:
m_decimators.decimate4_sup(&it, buf, len);
break;
case 3:
m_decimators.decimate8_sup(&it, buf, len);
break;
case 4:
m_decimators.decimate16_sup(&it, buf, len);
break;
case 5:
m_decimators.decimate32_sup(&it, buf, len);
break;
default:
break;
}
}
else if (m_fcPos == 2) // Center
{
switch (m_log2Decim)
{
case 1:
m_decimators.decimate2_cen(&it, buf, len);
break;
case 2:
m_decimators.decimate4_cen(&it, buf, len);
break;
case 3:
m_decimators.decimate8_cen(&it, buf, len);
break;
case 4:
m_decimators.decimate16_cen(&it, buf, len);
break;
case 5:
m_decimators.decimate32_cen(&it, buf, len);
break;
default:
break;
}
}
}
m_sampleFifo->write(m_convertBuffer.begin(), it);
}

View File

@ -0,0 +1,63 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 //
// //
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef PLUGINS_SAMPLESOURCE_LIMESDRINPUT_LIMESDRINPUTTHREAD_H_
#define PLUGINS_SAMPLESOURCE_LIMESDRINPUT_LIMESDRINPUTTHREAD_H_
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include "dsp/samplesinkfifo.h"
#include "dsp/decimators.h"
#define LIMESDR_BLOCKSIZE (1<<14) //complex samples per buffer ~10k (16k)
class LimeSDRInputThread : public QThread
{
Q_OBJECT
public:
LimeSDRInputThread(lms_stream_t* stream, SampleSinkFifo* sampleFifo, QObject* parent = 0);
~LimeSDRInputThread();
void startWork();
void stopWork();
void setLog2Decimation(unsigned int log2_decim);
void setFcPos(int fcPos);
private:
QMutex m_startWaitMutex;
QWaitCondition m_startWaiter;
bool m_running;
lms_stream_t* m_stream;
qint16 m_buf[2*LIMESDR_BLOCKSIZE]; //must hold I+Q values of each sample hence 2xcomplex size
SampleVector m_convertBuffer;
SampleSinkFifo* m_sampleFifo;
unsigned int m_log2Decim; // soft decimation
int m_fcPos;
Decimators<qint16, SDR_SAMP_SZ, 12> m_decimators;
void run();
void callback(const qint16* buf, qint32 len);
};
#endif /* PLUGINS_SAMPLESOURCE_LIMESDRINPUT_LIMESDRINPUTTHREAD_H_ */