mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-29 11:18:56 -05:00
LimeSDR support (9)
This commit is contained in:
parent
3fa6c06d6f
commit
0204cca9e3
@ -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.
|
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.
|
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.
|
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:
|
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
|
- Rx share the same sample rate, hardware decimation factor and center frequency
|
||||||
- Tx share the same sample rate, hardware interpolation 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)
|
- 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
|
- 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:
|
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:
|
||||||
@ -213,3 +214,5 @@ The openDevice and closeDevice methods of the device interface are designed spec
|
|||||||
|
|
||||||
- closeDevice:
|
- 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.
|
@ -117,31 +117,11 @@ bool LimeSDRInput::openDevice()
|
|||||||
return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LimeSDRInput::closeDevice()
|
void LimeSDRInput::closeDevice()
|
||||||
{
|
{
|
||||||
// destroy the stream
|
|
||||||
|
|
||||||
LMS_DestroyStream(m_deviceShared.m_deviceParams->getDevice(), &m_streamId);
|
|
||||||
|
|
||||||
// release the channel
|
// release the channel
|
||||||
|
|
||||||
if (LMS_EnableChannel(m_deviceShared.m_deviceParams->getDevice(), LMS_CH_RX, m_deviceShared.m_channel, false) != 0)
|
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()
|
bool LimeSDRInput::start()
|
||||||
{
|
{
|
||||||
if (!m_deviceShared.m_dev) {
|
if (!m_deviceShared.m_deviceParams->getDevice()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_running) stop();
|
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");
|
qFatal("LimeSDRInput::start: out of memory");
|
||||||
stop();
|
stop();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_limeSDRInputThread->setSamplerate(m_deviceShared.m_deviceParams->m_sampleRate);
|
|
||||||
m_limeSDRInputThread->setLog2Decimation(m_settings.m_log2SoftDecim);
|
m_limeSDRInputThread->setLog2Decimation(m_settings.m_log2SoftDecim);
|
||||||
m_limeSDRInputThread->setFcPos((int) m_settings.m_fcPos);
|
m_limeSDRInputThread->setFcPos((int) m_settings.m_fcPos);
|
||||||
|
|
||||||
@ -197,6 +192,9 @@ void LimeSDRInput::stop()
|
|||||||
m_limeSDRInputThread = 0;
|
m_limeSDRInputThread = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// destroy the stream
|
||||||
|
LMS_DestroyStream(m_deviceShared.m_deviceParams->getDevice(), &m_streamId);
|
||||||
|
|
||||||
m_running = false;
|
m_running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
169
plugins/samplesource/limesdrinput/limesdrinputthread.cpp
Normal file
169
plugins/samplesource/limesdrinput/limesdrinputthread.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
|
63
plugins/samplesource/limesdrinput/limesdrinputthread.h
Normal file
63
plugins/samplesource/limesdrinput/limesdrinputthread.h
Normal 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_ */
|
Loading…
Reference in New Issue
Block a user