mirror of
https://github.com/f4exb/sdrangel.git
synced 2026-08-01 16:38:06 -04:00
LoRa demod rework (2)
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// (c) 2015 John Greb
|
||||
// (c) 2015 John Greb //
|
||||
// (c) 2020 Edouard Griffiths //
|
||||
// //
|
||||
// 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 //
|
||||
@@ -100,9 +101,15 @@ bool LoRaDemod::handleMessage(const Message& cmd)
|
||||
m_basebandSampleRate = notif.getSampleRate();
|
||||
// Forward to the sink
|
||||
DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
|
||||
qDebug() << "LoRaDemod::handleMessage: DSPSignalNotification";
|
||||
qDebug() << "LoRaDemod::handleMessage: DSPSignalNotification: m_basebandSampleRate: " << m_basebandSampleRate;
|
||||
m_basebandSink->getInputMessageQueue()->push(rep);
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
DSPSignalNotification* repToGUI = new DSPSignalNotification(notif); // make a copy
|
||||
getMessageQueueToGUI()->push(repToGUI);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -136,7 +143,7 @@ bool LoRaDemod::deserialize(const QByteArray& data)
|
||||
void LoRaDemod::applySettings(const LoRaDemodSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "LoRaDemod::applySettings:"
|
||||
<< " m_centerFrequency: " << settings.m_centerFrequency
|
||||
<< " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset
|
||||
<< " m_bandwidthIndex: " << settings.m_bandwidthIndex
|
||||
<< " m_spreadFactor: " << settings.m_spreadFactor
|
||||
<< " m_rgbColor: " << settings.m_rgbColor
|
||||
|
||||
@@ -134,11 +134,11 @@ bool LoRaDemodBaseband::handleMessage(const Message& cmd)
|
||||
void LoRaDemodBaseband::applySettings(const LoRaDemodSettings& settings, bool force)
|
||||
{
|
||||
if ((settings.m_bandwidthIndex != m_settings.m_bandwidthIndex)
|
||||
|| (settings.m_centerFrequency != m_settings.m_centerFrequency) || force)
|
||||
|| (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
|
||||
{
|
||||
m_channelizer->setChannelization(
|
||||
LoRaDemodSettings::bandwidths[settings.m_bandwidthIndex],
|
||||
settings.m_centerFrequency
|
||||
settings.m_inputFrequencyOffset
|
||||
);
|
||||
m_sink.applyChannelSettings(
|
||||
m_channelizer->getChannelSampleRate(),
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019-2020 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "device/deviceuiset.h"
|
||||
#include <QDockWidget>
|
||||
@@ -5,10 +21,12 @@
|
||||
|
||||
#include "ui_lorademodgui.h"
|
||||
#include "dsp/spectrumvis.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "gui/glspectrum.h"
|
||||
#include "gui/glspectrumgui.h"
|
||||
#include "plugin/pluginapi.h"
|
||||
#include "util/simpleserializer.h"
|
||||
#include "dsp/dspengine.h"
|
||||
|
||||
#include "lorademod.h"
|
||||
#include "lorademodgui.h"
|
||||
@@ -58,11 +76,14 @@ QByteArray LoRaDemodGUI::serialize() const
|
||||
|
||||
bool LoRaDemodGUI::deserialize(const QByteArray& data)
|
||||
{
|
||||
if(m_settings.deserialize(data)) {
|
||||
if (m_settings.deserialize(data))
|
||||
{
|
||||
displaySettings();
|
||||
applySettings(true);
|
||||
return true;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
@@ -70,28 +91,72 @@ bool LoRaDemodGUI::deserialize(const QByteArray& data)
|
||||
|
||||
bool LoRaDemodGUI::handleMessage(const Message& message)
|
||||
{
|
||||
(void) message;
|
||||
return false;
|
||||
if (DSPSignalNotification::match(message))
|
||||
{
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) message;
|
||||
int basebandSampleRate = notif.getSampleRate();
|
||||
qDebug() << "LoRaDemodGUI::handleMessage: DSPSignalNotification: m_basebandSampleRate: " << basebandSampleRate;
|
||||
|
||||
if (basebandSampleRate != m_basebandSampleRate)
|
||||
{
|
||||
m_basebandSampleRate = basebandSampleRate;
|
||||
setBandwidths();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void LoRaDemodGUI::viewChanged()
|
||||
void LoRaDemodGUI::handleInputMessages()
|
||||
{
|
||||
Message* message;
|
||||
|
||||
while ((message = getInputMessageQueue()->pop()) != 0)
|
||||
{
|
||||
if (handleMessage(*message)) {
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoRaDemodGUI::channelMarkerChangedByCursor()
|
||||
{
|
||||
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
|
||||
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void LoRaDemodGUI::on_deltaFrequency_changed(qint64 value)
|
||||
{
|
||||
m_channelMarker.setCenterFrequency(value);
|
||||
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void LoRaDemodGUI::channelMarkerHighlightedByCursor()
|
||||
{
|
||||
setHighlighted(m_channelMarker.getHighlighted());
|
||||
}
|
||||
|
||||
void LoRaDemodGUI::on_BW_valueChanged(int value)
|
||||
{
|
||||
if (value < 0) {
|
||||
m_settings.m_bandwidthIndex = 0;
|
||||
} else if (value < LoRaDemodSettings::nb_bandwidths) {
|
||||
} else if (value < LoRaDemodSettings::nbBandwidths) {
|
||||
m_settings.m_bandwidthIndex = value;
|
||||
} else {
|
||||
m_settings.m_bandwidthIndex = LoRaDemodSettings::nb_bandwidths - 1;
|
||||
m_settings.m_bandwidthIndex = LoRaDemodSettings::nbBandwidths - 1;
|
||||
}
|
||||
|
||||
int thisBW = LoRaDemodSettings::bandwidths[value];
|
||||
ui->BWText->setText(QString("%1 Hz").arg(thisBW));
|
||||
m_channelMarker.setBandwidth(thisBW);
|
||||
ui->glSpectrum->setSampleRate(thisBW);
|
||||
ui->glSpectrum->setCenterFrequency(thisBW/2);
|
||||
|
||||
applySettings();
|
||||
}
|
||||
@@ -100,13 +165,18 @@ void LoRaDemodGUI::on_Spread_valueChanged(int value)
|
||||
{
|
||||
m_settings.m_spreadFactor = value;
|
||||
ui->SpreadText->setText(tr("%1").arg(value));
|
||||
int spectrumRate = 1 << m_settings.m_spreadFactor;
|
||||
ui->glSpectrum->setSampleRate(spectrumRate);
|
||||
ui->glSpectrum->setCenterFrequency(spectrumRate/2);
|
||||
ui->spectrumGUI->setFFTSize(m_settings.m_spreadFactor);
|
||||
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void LoRaDemodGUI::on_deBits_valueChanged(int value)
|
||||
{
|
||||
m_settings.m_deBits = value;
|
||||
ui->deBitsText->setText(tr("%1").arg(m_settings.m_deBits));
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void LoRaDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown)
|
||||
{
|
||||
(void) widget;
|
||||
@@ -119,6 +189,7 @@ LoRaDemodGUI::LoRaDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb
|
||||
m_pluginAPI(pluginAPI),
|
||||
m_deviceUISet(deviceUISet),
|
||||
m_channelMarker(this),
|
||||
m_basebandSampleRate(250000),
|
||||
m_doApplySettings(true)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
@@ -128,17 +199,20 @@ LoRaDemodGUI::LoRaDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb
|
||||
m_spectrumVis = new SpectrumVis(SDR_RX_SCALEF, ui->glSpectrum);
|
||||
m_LoRaDemod = (LoRaDemod*) rxChannel; //new LoRaDemod(m_deviceUISet->m_deviceSourceAPI);
|
||||
m_LoRaDemod->setSpectrumSink(m_spectrumVis);
|
||||
m_LoRaDemod->setMessageQueueToGUI(getInputMessageQueue());
|
||||
|
||||
int spectrumRate = 1 << m_settings.m_spreadFactor;
|
||||
ui->glSpectrum->setSampleRate(spectrumRate);
|
||||
ui->glSpectrum->setCenterFrequency(spectrumRate/2);
|
||||
ui->glSpectrum->setDisplayWaterfall(true);
|
||||
ui->glSpectrum->setDisplayMaxHold(true);
|
||||
|
||||
m_channelMarker.setMovable(false);
|
||||
ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
|
||||
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
|
||||
ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
|
||||
|
||||
m_channelMarker.setMovable(true);
|
||||
m_channelMarker.setVisible(true);
|
||||
|
||||
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(viewChanged()));
|
||||
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
|
||||
connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
|
||||
|
||||
m_deviceUISet->registerRxChannelInstance(LoRaDemod::m_channelIdURI, this);
|
||||
m_deviceUISet->addChannelMarker(&m_channelMarker);
|
||||
@@ -149,6 +223,9 @@ LoRaDemodGUI::LoRaDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb
|
||||
m_settings.setChannelMarker(&m_channelMarker);
|
||||
m_settings.setSpectrumGUI(ui->spectrumGUI);
|
||||
|
||||
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
||||
|
||||
setBandwidths();
|
||||
displaySettings();
|
||||
applySettings(true);
|
||||
}
|
||||
@@ -181,15 +258,41 @@ void LoRaDemodGUI::displaySettings()
|
||||
int thisBW = LoRaDemodSettings::bandwidths[m_settings.m_bandwidthIndex];
|
||||
|
||||
m_channelMarker.blockSignals(true);
|
||||
m_channelMarker.setTitle(m_settings.m_title);
|
||||
m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
|
||||
m_channelMarker.setBandwidth(thisBW);
|
||||
m_channelMarker.setCenterFrequency(0);
|
||||
m_channelMarker.blockSignals(false);
|
||||
m_channelMarker.setColor(m_settings.m_rgbColor);
|
||||
setTitleColor(m_settings.m_rgbColor);
|
||||
m_channelMarker.blockSignals(false);
|
||||
|
||||
ui->glSpectrum->setSampleRate(thisBW);
|
||||
ui->glSpectrum->setCenterFrequency(thisBW/2);
|
||||
|
||||
blockApplySettings(true);
|
||||
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
|
||||
ui->BWText->setText(QString("%1 Hz").arg(thisBW));
|
||||
ui->BW->setValue(m_settings.m_bandwidthIndex);
|
||||
ui->Spread->setValue(m_settings.m_spreadFactor);
|
||||
ui->SpreadText->setText(tr("%1").arg(m_settings.m_spreadFactor));
|
||||
ui->deBits->setValue(m_settings.m_deBits);
|
||||
ui->deBitsText->setText(tr("%1").arg(m_settings.m_deBits));
|
||||
ui->spectrumGUI->setFFTSize(m_settings.m_spreadFactor);
|
||||
blockApplySettings(false);
|
||||
}
|
||||
|
||||
void LoRaDemodGUI::setBandwidths()
|
||||
{
|
||||
int maxBandwidth = m_basebandSampleRate;
|
||||
int maxIndex = 0;
|
||||
|
||||
for (; (maxIndex < LoRaDemodSettings::nbBandwidths) && (LoRaDemodSettings::bandwidths[maxIndex] <= maxBandwidth); maxIndex++)
|
||||
{}
|
||||
|
||||
if (maxIndex != 0)
|
||||
{
|
||||
qDebug("LoRaDemodGUI::setBandwidths: avl: %d max: %d", maxBandwidth, LoRaDemodSettings::bandwidths[maxIndex-1]);
|
||||
ui->BW->setMaximum(maxIndex - 1);
|
||||
int index = ui->BW->value();
|
||||
ui->BWText->setText(QString("%1 Hz").arg(LoRaDemodSettings::bandwidths[index]));
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,20 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019-2020 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_LoRaDEMODGUI_H
|
||||
#define INCLUDE_LoRaDEMODGUI_H
|
||||
|
||||
@@ -37,10 +54,14 @@ public:
|
||||
virtual bool handleMessage(const Message& message);
|
||||
|
||||
private slots:
|
||||
void viewChanged();
|
||||
void channelMarkerChangedByCursor();
|
||||
void on_deltaFrequency_changed(qint64 value);
|
||||
void on_BW_valueChanged(int value);
|
||||
void on_Spread_valueChanged(int value);
|
||||
void on_deBits_valueChanged(int value);
|
||||
void onWidgetRolled(QWidget* widget, bool rollDown);
|
||||
void channelMarkerHighlightedByCursor();
|
||||
void handleInputMessages();
|
||||
|
||||
private:
|
||||
Ui::LoRaDemodGUI* ui;
|
||||
@@ -48,6 +69,7 @@ private:
|
||||
DeviceUISet* m_deviceUISet;
|
||||
ChannelMarker m_channelMarker;
|
||||
LoRaDemodSettings m_settings;
|
||||
int m_basebandSampleRate;
|
||||
bool m_doApplySettings;
|
||||
|
||||
LoRaDemod* m_LoRaDemod;
|
||||
@@ -60,6 +82,7 @@ private:
|
||||
void blockApplySettings(bool block);
|
||||
void applySettings(bool force = false);
|
||||
void displaySettings();
|
||||
void setBandwidths();
|
||||
};
|
||||
|
||||
#endif // INCLUDE_LoRaDEMODGUI_H
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>350</width>
|
||||
<height>550</height>
|
||||
<height>500</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>350</width>
|
||||
<height>550</height>
|
||||
<height>500</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
@@ -29,124 +29,298 @@
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>35</y>
|
||||
<y>20</y>
|
||||
<width>331</width>
|
||||
<height>96</height>
|
||||
<height>112</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Settings</string>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>112</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="leftMargin">
|
||||
<property name="windowTitle">
|
||||
<string>RF/demod Settings</string>
|
||||
</property>
|
||||
<widget class="QLabel" name="bwLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>2</x>
|
||||
<y>50</y>
|
||||
<width>22</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>BW</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="spreadLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>2</x>
|
||||
<y>70</y>
|
||||
<width>22</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>SF</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QSlider" name="BW">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>50</y>
|
||||
<width>180</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Bandwidth</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="pageStep">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QSlider" name="Spread">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>70</y>
|
||||
<width>100</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Spreading factor</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>7</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="pageStep">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="sliderPosition">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="SpreadText">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>140</x>
|
||||
<y>70</y>
|
||||
<width>30</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>10</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="BWText">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>240</x>
|
||||
<y>50</y>
|
||||
<width>80</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>7813 Hz</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="deBitsLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>190</x>
|
||||
<y>70</y>
|
||||
<width>22</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>DE</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="deBitsText">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>290</x>
|
||||
<y>70</y>
|
||||
<width>30</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QSlider" name="deBits">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>220</x>
|
||||
<y>70</y>
|
||||
<width>60</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Low data rate optimize (DE) bits</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>2</number>
|
||||
<property name="pageStep">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>2</number>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>2</number>
|
||||
<property name="sliderPosition">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>BW</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="mabel">
|
||||
<property name="text">
|
||||
<string>SF</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSlider" name="BW">
|
||||
<property name="toolTip">
|
||||
<string>Bandwidth</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="pageStep">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSlider" name="Spread">
|
||||
<property name="toolTip">
|
||||
<string>Spreading factor</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="pageStep">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>7</number>
|
||||
</property>
|
||||
<property name="sliderPosition">
|
||||
<number>7</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="SpreadText">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>5</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="BWText">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>7813 Hz</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="layoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>10</y>
|
||||
<width>311</width>
|
||||
<height>26</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="deltaFrequencyLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="deltaFrequencyLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Df</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ValueDialZ" name="deltaFrequency" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Liberation Mono</family>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Demod shift frequency from center in Hz</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="deltaUnits">
|
||||
<property name="text">
|
||||
<string>Hz </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QWidget" name="spectrumContainer" native="true">
|
||||
<property name="geometry">
|
||||
@@ -154,11 +328,11 @@
|
||||
<x>10</x>
|
||||
<y>140</y>
|
||||
<width>331</width>
|
||||
<height>391</height>
|
||||
<height>341</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Channel Spectrum</string>
|
||||
<string>De-chirped Spectrum</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
@@ -181,7 +355,7 @@
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>350</height>
|
||||
<height>300</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
@@ -211,7 +385,15 @@
|
||||
<header>gui/glspectrumgui.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ValueDialZ</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/valuedialz.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<resources>
|
||||
<include location="../../../sdrgui/resources/res.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019-2020 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "lorademodsettings.h"
|
||||
|
||||
#include <QColor>
|
||||
@@ -7,11 +24,11 @@
|
||||
#include "settings/serializable.h"
|
||||
#include "lorademodsettings.h"
|
||||
|
||||
const int LoRaDemodSettings::bandwidths[] = {7813, 15625, 31250, 62500, 125000, 250000, 50000};
|
||||
const int LoRaDemodSettings::nb_bandwidths = 7;
|
||||
const int LoRaDemodSettings::bandwidths[] = {7813, 10417, 15625, 20833, 31250, 41667, 62500, 125000, 250000, 500000};
|
||||
const int LoRaDemodSettings::nbBandwidths = 10;
|
||||
|
||||
LoRaDemodSettings::LoRaDemodSettings() :
|
||||
m_centerFrequency(0),
|
||||
m_inputFrequencyOffset(0),
|
||||
m_channelMarker(0),
|
||||
m_spectrumGUI(0)
|
||||
{
|
||||
@@ -20,8 +37,9 @@ LoRaDemodSettings::LoRaDemodSettings() :
|
||||
|
||||
void LoRaDemodSettings::resetToDefaults()
|
||||
{
|
||||
m_bandwidthIndex = 0;
|
||||
m_spreadFactor = 0;
|
||||
m_bandwidthIndex = 5;
|
||||
m_spreadFactor = 7;
|
||||
m_deBits = 0;
|
||||
m_rgbColor = QColor(255, 0, 255).rgb();
|
||||
m_title = "LoRa Demodulator";
|
||||
}
|
||||
@@ -29,7 +47,7 @@ void LoRaDemodSettings::resetToDefaults()
|
||||
QByteArray LoRaDemodSettings::serialize() const
|
||||
{
|
||||
SimpleSerializer s(1);
|
||||
s.writeS32(1, m_centerFrequency);
|
||||
s.writeS32(1, m_inputFrequencyOffset);
|
||||
s.writeS32(2, m_bandwidthIndex);
|
||||
s.writeS32(3, m_spreadFactor);
|
||||
|
||||
@@ -42,6 +60,7 @@ QByteArray LoRaDemodSettings::serialize() const
|
||||
}
|
||||
|
||||
s.writeString(6, m_title);
|
||||
s.writeS32(7, m_deBits);
|
||||
|
||||
return s.final();
|
||||
}
|
||||
@@ -60,7 +79,7 @@ bool LoRaDemodSettings::deserialize(const QByteArray& data)
|
||||
{
|
||||
QByteArray bytetmp;
|
||||
|
||||
d.readS32(1, &m_centerFrequency, 0);
|
||||
d.readS32(1, &m_inputFrequencyOffset, 0);
|
||||
d.readS32(2, &m_bandwidthIndex, 0);
|
||||
d.readS32(3, &m_spreadFactor, 0);
|
||||
|
||||
@@ -75,6 +94,7 @@ bool LoRaDemodSettings::deserialize(const QByteArray& data)
|
||||
}
|
||||
|
||||
d.readString(6, &m_title, "LoRa Demodulator");
|
||||
d.readS32(7, &m_deBits, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,21 @@
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019-2020 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef PLUGINS_CHANNELRX_DEMODLORA_LORADEMODSETTINGS_H_
|
||||
#define PLUGINS_CHANNELRX_DEMODLORA_LORADEMODSETTINGS_H_
|
||||
|
||||
@@ -11,9 +28,10 @@ class Serializable;
|
||||
|
||||
struct LoRaDemodSettings
|
||||
{
|
||||
int m_centerFrequency;
|
||||
int m_inputFrequencyOffset;
|
||||
int m_bandwidthIndex;
|
||||
int m_spreadFactor;
|
||||
int m_deBits; //!< Low data rate optmize (DE) bits
|
||||
uint32_t m_rgbColor;
|
||||
QString m_title;
|
||||
|
||||
@@ -21,7 +39,7 @@ struct LoRaDemodSettings
|
||||
Serializable *m_spectrumGUI;
|
||||
|
||||
static const int bandwidths[];
|
||||
static const int nb_bandwidths;
|
||||
static const int nbBandwidths;
|
||||
|
||||
LoRaDemodSettings();
|
||||
void resetToDefaults();
|
||||
|
||||
@@ -21,200 +21,83 @@
|
||||
|
||||
#include "dsp/dsptypes.h"
|
||||
#include "dsp/basebandsamplesink.h"
|
||||
#include "dsp/fftengine.h"
|
||||
|
||||
#include "lorademodsink.h"
|
||||
|
||||
const int LoRaDemodSink::DATA_BITS = 6;
|
||||
const int LoRaDemodSink::SAMPLEBITS = LoRaDemodSink::DATA_BITS + 2;
|
||||
const int LoRaDemodSink::LORA_SQUELCH = 3;
|
||||
|
||||
LoRaDemodSink::LoRaDemodSink() :
|
||||
m_spectrumSink(nullptr)
|
||||
m_spectrumSink(nullptr),
|
||||
m_spectrumBuffer(nullptr),
|
||||
m_downChirps(nullptr),
|
||||
m_upChirps(nullptr),
|
||||
m_fftBuffer(nullptr)
|
||||
{
|
||||
m_Bandwidth = LoRaDemodSettings::bandwidths[0];
|
||||
m_bandwidth = LoRaDemodSettings::bandwidths[0];
|
||||
m_channelSampleRate = 96000;
|
||||
m_channelFrequencyOffset = 0;
|
||||
m_nco.setFreq(m_channelFrequencyOffset, m_channelSampleRate);
|
||||
m_interpolator.create(16, m_channelSampleRate, m_Bandwidth/1.9);
|
||||
m_sampleDistanceRemain = (Real) m_channelSampleRate / m_Bandwidth;
|
||||
m_interpolator.create(16, m_channelSampleRate, m_bandwidth / 1.9f);
|
||||
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) m_bandwidth;
|
||||
m_sampleDistanceRemain = 0;
|
||||
|
||||
m_state = LoRaStateReset;
|
||||
m_chirp = 0;
|
||||
m_angle = 0;
|
||||
m_bin = 0;
|
||||
m_result = 0;
|
||||
m_count = 0;
|
||||
m_header = 0;
|
||||
m_time = 0;
|
||||
m_tune = 0;
|
||||
m_chirp0 = 0;
|
||||
|
||||
m_nbSymbols = 1 << m_settings.m_spreadFactor;
|
||||
m_sfftLength = m_nbSymbols / 2;
|
||||
m_fft = FFTEngine::create();
|
||||
m_fftSFD = FFTEngine::create();
|
||||
|
||||
m_loraFilter = new sfft(m_sfftLength);
|
||||
m_negaFilter = new sfft(m_sfftLength);
|
||||
m_mov = new float[4*m_sfftLength];
|
||||
m_mag = new float[m_sfftLength];
|
||||
m_rev = new float[m_sfftLength];
|
||||
m_history = new short[1024];
|
||||
m_finetune = new short[16];
|
||||
initSF(m_settings.m_spreadFactor);
|
||||
}
|
||||
|
||||
LoRaDemodSink::~LoRaDemodSink()
|
||||
{
|
||||
delete m_loraFilter;
|
||||
delete m_negaFilter;
|
||||
delete [] m_mov;
|
||||
delete [] m_history;
|
||||
delete [] m_finetune;
|
||||
delete m_fft;
|
||||
delete m_fftSFD;
|
||||
delete[] m_downChirps;
|
||||
delete[] m_upChirps;
|
||||
delete[] m_spectrumBuffer;
|
||||
}
|
||||
|
||||
void LoRaDemodSink::dumpRaw()
|
||||
void LoRaDemodSink::initSF(unsigned int sf)
|
||||
{
|
||||
short bin, j, max;
|
||||
char text[256];
|
||||
if (m_downChirps) {
|
||||
delete[] m_downChirps;
|
||||
}
|
||||
if (m_upChirps) {
|
||||
delete[] m_upChirps;
|
||||
}
|
||||
if (m_fftBuffer) {
|
||||
delete[] m_fftBuffer;
|
||||
}
|
||||
if (m_spectrumBuffer) {
|
||||
delete[] m_spectrumBuffer;
|
||||
}
|
||||
|
||||
max = m_time / 4 - 3;
|
||||
m_nbSymbols = 1 << sf;
|
||||
m_fftLength = m_nbSymbols;
|
||||
m_fft->configure(m_fftInterpolation*m_fftLength, false);
|
||||
m_fftSFD->configure(m_fftInterpolation*m_fftLength, false);
|
||||
m_state = LoRaStateReset;
|
||||
m_sfdSkip = m_fftLength / 4;
|
||||
m_fftWindow.create(FFTWindow::Function::Kaiser, m_fftLength);
|
||||
m_fftWindow.setKaiserAlpha(M_PI);
|
||||
m_downChirps = new Complex[2*m_nbSymbols]; // Each table is 2 chirps long to allow memcpying from arbitrary offsets.
|
||||
m_upChirps = new Complex[2*m_nbSymbols];
|
||||
m_fftBuffer = new Complex[m_fftInterpolation*m_fftLength];
|
||||
m_spectrumBuffer = new Complex[m_nbSymbols];
|
||||
|
||||
if (max > 140) {
|
||||
max = 140; // about 2 symbols to each char
|
||||
}
|
||||
float halfAngle = M_PI;
|
||||
float phase = -halfAngle;
|
||||
double accumulator = 0;
|
||||
|
||||
for ( j=0; j < max; j++)
|
||||
{
|
||||
bin = (m_history[(j + 1) * 4] + m_tune ) % m_sfftLength;
|
||||
text[j] = toGray(bin >> 1);
|
||||
}
|
||||
|
||||
prng6(text, max);
|
||||
// First block is always 8 symbols
|
||||
interleave6(text, 6);
|
||||
interleave6(&text[8], max);
|
||||
hamming6(text, 6);
|
||||
hamming6(&text[8], max);
|
||||
|
||||
for ( j=0; j < max / 2; j++)
|
||||
{
|
||||
text[j] = (text[j * 2 + 1] << 4) | (0xf & text[j * 2 + 0]);
|
||||
|
||||
if ((text[j] < 32 )||( text[j] > 126)) {
|
||||
text[j] = 0x5f;
|
||||
}
|
||||
}
|
||||
|
||||
text[3] = text[2];
|
||||
text[2] = text[1];
|
||||
text[1] = text[0];
|
||||
text[j] = 0;
|
||||
|
||||
qDebug("LoRaDemodSink::dumpRaw: %s", &text[1]);
|
||||
}
|
||||
|
||||
short LoRaDemodSink::synch(short bin)
|
||||
{
|
||||
short i, j;
|
||||
|
||||
if (bin < 0)
|
||||
{
|
||||
if (m_time > 70) {
|
||||
dumpRaw();
|
||||
}
|
||||
|
||||
m_time = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
m_history[m_time] = bin;
|
||||
|
||||
if (m_time > 12)
|
||||
{
|
||||
if (bin == m_history[m_time - 6])
|
||||
{
|
||||
if (bin == m_history[m_time - 12])
|
||||
{
|
||||
m_tune = m_sfftLength - bin;
|
||||
j = 0;
|
||||
|
||||
for (i=0; i<12; i++) {
|
||||
j += m_finetune[15 & (m_time - i)];
|
||||
}
|
||||
|
||||
if (j < 0) {
|
||||
m_tune += 1;
|
||||
}
|
||||
|
||||
m_tune %= m_sfftLength;
|
||||
m_time = 0;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_time++;
|
||||
m_time &= 1023;
|
||||
|
||||
if (m_time & 3) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (bin + m_tune) % m_sfftLength;
|
||||
}
|
||||
|
||||
int LoRaDemodSink::detect(Complex c, Complex a)
|
||||
{
|
||||
int p, q;
|
||||
short i, result, negresult, movpoint;
|
||||
float peak, negpeak, tfloat;
|
||||
|
||||
m_loraFilter->run(c * a);
|
||||
m_negaFilter->run(c * conj(a));
|
||||
|
||||
// process spectrum twice in FFTLEN
|
||||
if (++m_count & ((1 << DATA_BITS) - 1)) {
|
||||
return m_result;
|
||||
}
|
||||
|
||||
movpoint = 3 & (m_count >> DATA_BITS);
|
||||
|
||||
m_loraFilter->fetch(m_mag);
|
||||
m_negaFilter->fetch(m_rev);
|
||||
peak = negpeak = 0.0f;
|
||||
result = negresult = 0;
|
||||
|
||||
for (i = 0; i < m_sfftLength; i++)
|
||||
{
|
||||
if (m_rev[i] > negpeak)
|
||||
{
|
||||
negpeak = m_rev[i];
|
||||
negresult = i;
|
||||
}
|
||||
|
||||
tfloat = m_mov[i] + m_mov[m_sfftLength + i] +m_mov[2 * m_sfftLength + i]
|
||||
+ m_mov[3 * m_sfftLength + i] + m_mag[i];
|
||||
|
||||
if (tfloat > peak)
|
||||
{
|
||||
peak = tfloat;
|
||||
result = i;
|
||||
}
|
||||
|
||||
m_mov[movpoint * m_sfftLength + i] = m_mag[i];
|
||||
}
|
||||
|
||||
p = (result - 1 + m_sfftLength) % m_sfftLength;
|
||||
q = (result + 1) % m_sfftLength;
|
||||
m_finetune[15 & m_time] = (m_mag[p] > m_mag[q]) ? -1 : 1;
|
||||
|
||||
if (peak < negpeak * LORA_SQUELCH) {
|
||||
result = -1;
|
||||
}
|
||||
|
||||
result = synch(result);
|
||||
|
||||
if (result >= 0) {
|
||||
m_result = result;
|
||||
}
|
||||
|
||||
return m_result;
|
||||
for (int i = 0; i < 2*m_nbSymbols; i++)
|
||||
{
|
||||
accumulator = fmod(accumulator + phase, 2*M_PI);
|
||||
m_downChirps[i] = Complex(std::conj(std::polar(1.0, accumulator)));
|
||||
m_upChirps[i] = Complex(std::polar(1.0, accumulator));
|
||||
phase += (2*halfAngle) / m_nbSymbols;
|
||||
}
|
||||
}
|
||||
|
||||
void LoRaDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
|
||||
@@ -222,8 +105,6 @@ void LoRaDemodSink::feed(const SampleVector::const_iterator& begin, const Sample
|
||||
int newangle;
|
||||
Complex ci;
|
||||
|
||||
m_sampleBuffer.clear();
|
||||
|
||||
for (SampleVector::const_iterator it = begin; it < end; ++it)
|
||||
{
|
||||
Complex c(it->real() / SDR_RX_SCALEF, it->imag() / SDR_RX_SCALEF);
|
||||
@@ -231,31 +112,331 @@ void LoRaDemodSink::feed(const SampleVector::const_iterator& begin, const Sample
|
||||
|
||||
if (m_interpolator.decimate(&m_sampleDistanceRemain, c, &ci))
|
||||
{
|
||||
m_angle = (m_angle + m_chirp) % m_nbSymbols;
|
||||
Complex upRamp(cos(M_PI*2*m_angle/m_nbSymbols), sin(M_PI*2*m_angle/m_nbSymbols));
|
||||
Complex dechirpUp = ci * conj(upRamp); // de-chirp the up ramp to get peamble and data
|
||||
Complex dechirpDown = ci * upRamp; // de-chirp the down ramp to get sync
|
||||
m_sampleBuffer.push_back(Sample(dechirpUp.real() * SDR_RX_SCALEF, dechirpUp.imag() * SDR_RX_SCALEF));
|
||||
|
||||
// Bullshit...
|
||||
// Complex cangle(cos(M_PI*2*m_angle/m_nbSymbols),-sin(M_PI*2*m_angle/m_nbSymbols));
|
||||
// newangle = detect(ci, cangle);
|
||||
// m_bin = (m_bin + newangle) % m_sfftLength;
|
||||
// Complex nangle(cos(M_PI*2*m_bin/m_sfftLength),sin(M_PI*2*m_bin/m_sfftLength));
|
||||
// m_sampleBuffer.push_back(Sample(nangle.real() * 16384, nangle.imag() * 16384));
|
||||
|
||||
m_sampleDistanceRemain += (Real) m_channelSampleRate / m_Bandwidth;
|
||||
m_chirp++;
|
||||
|
||||
if (m_chirp >= m_nbSymbols) {
|
||||
m_chirp = 0;
|
||||
}
|
||||
processSample(ci);
|
||||
m_sampleDistanceRemain += m_interpolatorDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_spectrumSink) {
|
||||
m_spectrumSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), false);
|
||||
}
|
||||
void LoRaDemodSink::processSample(const Complex& ci)
|
||||
{
|
||||
if (m_state == LoRaStateReset) // start over
|
||||
{
|
||||
reset();
|
||||
m_state = LoRaStateDetectPreamble;
|
||||
}
|
||||
else if (m_state == LoRaStateDetectPreamble) // look for preamble
|
||||
{
|
||||
m_fft->in()[m_fftCounter++] = ci * m_downChirps[m_chirp]; // de-chirp the up ramp
|
||||
|
||||
if (m_fftCounter == m_fftLength)
|
||||
{
|
||||
m_fftWindow.apply(m_fft->in());
|
||||
std::fill(m_fft->in()+m_fftLength, m_fft->in()+m_fftInterpolation*m_fftLength, Complex{0.0, 0.0});
|
||||
m_fft->transform();
|
||||
m_fftCounter = 0;
|
||||
|
||||
unsigned int imax = argmax(
|
||||
m_fft->out(),
|
||||
m_fftInterpolation,
|
||||
m_fftLength,
|
||||
m_magsq,
|
||||
m_spectrumBuffer,
|
||||
m_fftInterpolation
|
||||
) / m_fftInterpolation;
|
||||
|
||||
// Debug:
|
||||
// if (m_spectrumSink) {
|
||||
// m_spectrumSink->feed(m_spectrumBuffer, m_nbSymbols);
|
||||
// }
|
||||
|
||||
m_argMaxHistory[m_argMaxHistoryCounter++] = imax;
|
||||
|
||||
if (m_argMaxHistoryCounter == m_requiredPreambleChirps)
|
||||
{
|
||||
m_argMaxHistoryCounter = 0;
|
||||
bool preambleFound = true;
|
||||
|
||||
for (int i = 1; i < m_requiredPreambleChirps; i++)
|
||||
{
|
||||
if (m_argMaxHistory[0] != m_argMaxHistory[i])
|
||||
{
|
||||
preambleFound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (preambleFound)
|
||||
{
|
||||
if (m_spectrumSink) {
|
||||
m_spectrumSink->feed(m_spectrumBuffer, m_nbSymbols);
|
||||
}
|
||||
|
||||
qDebug("LoRaDemodSink::processSample: preamble found: %u|%f", m_argMaxHistory[0], m_magsq);
|
||||
m_chirp0 = m_argMaxHistory[0];
|
||||
m_chirp = m_chirp0;
|
||||
m_fftCounter = 0;
|
||||
m_chirpCount = 0;
|
||||
m_state = LoRaStatePreamble;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_state == LoRaStatePreamble) // preamble found look for SFD start
|
||||
{
|
||||
m_fft->in()[m_fftCounter] = ci * m_downChirps[m_chirp]; // de-chirp the up ramp
|
||||
m_fftSFD->in()[m_fftCounter] = ci * m_upChirps[m_chirp]; // de-chiro the down ramp
|
||||
m_fftCounter++;
|
||||
|
||||
if (m_fftCounter == m_fftLength)
|
||||
{
|
||||
std::copy(m_fftSFD->in(), m_fftSFD->in() + m_fftLength, m_fftBuffer); // save for later
|
||||
|
||||
m_fftWindow.apply(m_fft->in());
|
||||
std::fill(m_fft->in()+m_fftLength, m_fft->in()+m_fftInterpolation*m_fftLength, Complex{0.0, 0.0});
|
||||
m_fft->transform();
|
||||
|
||||
m_fftWindow.apply(m_fftSFD->in());
|
||||
std::fill(m_fftSFD->in()+m_fftLength, m_fftSFD->in()+m_fftInterpolation*m_fftLength, Complex{0.0, 0.0});
|
||||
m_fftSFD->transform();
|
||||
|
||||
m_fftCounter = 0;
|
||||
double magsq, magsqSFD;
|
||||
|
||||
unsigned int imaxSFD = argmax(
|
||||
m_fftSFD->out(),
|
||||
m_fftInterpolation,
|
||||
m_fftLength,
|
||||
magsqSFD,
|
||||
nullptr,
|
||||
m_fftInterpolation
|
||||
) / m_fftInterpolation;
|
||||
|
||||
unsigned int imax = argmax(
|
||||
m_fft->out(),
|
||||
m_fftInterpolation,
|
||||
m_fftLength,
|
||||
magsq,
|
||||
m_spectrumBuffer,
|
||||
m_fftInterpolation
|
||||
) / m_fftInterpolation;
|
||||
|
||||
m_preambleHistory[m_chirpCount] = imax;
|
||||
m_chirpCount++;
|
||||
|
||||
if (magsq < magsqSFD) // preamble drop
|
||||
{
|
||||
if (m_chirpCount < 3) // too early
|
||||
{
|
||||
m_state = LoRaStateReset;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_syncWord = round(m_preambleHistory[m_chirpCount-2] / 8.0);
|
||||
m_syncWord += 16 * round(m_preambleHistory[m_chirpCount-3] / 8.0);
|
||||
qDebug("LoRaDemodSink::processSample: SFD found: up: %u|%f down: %u|%f sync: %x", imax, magsq, imaxSFD, magsqSFD, m_syncWord);
|
||||
m_sfdSkipCounter = 0;
|
||||
m_fftCounter = m_fftLength - m_sfdSkip;
|
||||
std::copy(m_fftBuffer+m_sfdSkip, m_fftBuffer+(m_fftLength-m_sfdSkip), m_fftBuffer); // prepare sliding fft
|
||||
m_state = LoRaStateSlideSFD;
|
||||
m_magsq = magsqSFD;
|
||||
}
|
||||
}
|
||||
else if (m_chirpCount > m_maxSFDSearchChirps) // SFD missed start over
|
||||
{
|
||||
m_state = LoRaStateReset;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_spectrumSink) {
|
||||
m_spectrumSink->feed(m_spectrumBuffer, m_nbSymbols);
|
||||
}
|
||||
|
||||
qDebug("LoRaDemodSink::processSample: SFD search: up: %u|%f down: %u|%f", imax, magsq, imaxSFD, magsqSFD);
|
||||
m_magsq = magsq;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_state == LoRaStateSkipSFD) // Just skip SFD
|
||||
{
|
||||
m_fftCounter++;
|
||||
|
||||
if (m_fftCounter == m_fftLength)
|
||||
{
|
||||
m_fftCounter = m_fftLength - m_sfdSkip;
|
||||
m_sfdSkipCounter++;
|
||||
|
||||
if (m_sfdSkipCounter == m_sfdFourths) // 1.25 SFD chips left
|
||||
{
|
||||
m_chirp = m_chirp0;
|
||||
m_fftCounter = 0;
|
||||
m_chirpCount = 0;
|
||||
int correction = 0;
|
||||
qDebug("LoRaDemodSink::processSample: SFD skipped");
|
||||
m_state = LoRaStateReadPayload; //LoRaStateReadPayload;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_state == LoRaStateSlideSFD) // perform sliding FFTs over the rest of the SFD period
|
||||
{
|
||||
m_fftBuffer[m_fftCounter] = ci * m_upChirps[m_chirp]; // de-chirp the down ramp
|
||||
m_fftCounter++;
|
||||
|
||||
if (m_fftCounter == m_fftLength)
|
||||
{
|
||||
std::copy(m_fftBuffer, m_fftBuffer + m_fftLength, m_fftSFD->in());
|
||||
std::fill(m_fftSFD->in()+m_fftLength, m_fftSFD->in()+m_fftInterpolation*m_fftLength, Complex{0.0, 0.0});
|
||||
m_fftSFD->transform();
|
||||
std::copy(m_fftBuffer+m_sfdSkip, m_fftBuffer+(m_fftLength-m_sfdSkip), m_fftBuffer); // prepare next sliding fft
|
||||
m_fftCounter = m_fftLength - m_sfdSkip;
|
||||
m_sfdSkipCounter++;
|
||||
|
||||
double magsqSFD;
|
||||
|
||||
unsigned int imaxSFD = argmax(
|
||||
m_fftSFD->out(),
|
||||
m_fftInterpolation,
|
||||
m_fftLength,
|
||||
magsqSFD,
|
||||
m_spectrumBuffer,
|
||||
m_fftInterpolation
|
||||
) / m_fftInterpolation;
|
||||
|
||||
qDebug("LoRaDemodSink::processSample: SFD slide %u %u|%f", m_sfdSkipCounter, imaxSFD, magsqSFD);
|
||||
|
||||
if (m_sfdSkipCounter == m_sfdFourths) // 1.25 SFD chips length
|
||||
{
|
||||
m_chirp = m_chirp0;
|
||||
m_fftCounter = 0;
|
||||
m_chirpCount = 0;
|
||||
int correction = 0;
|
||||
qDebug("LoRaDemodSink::processSample: SFD done");
|
||||
m_state = LoRaStateReadPayload; //LoRaStateReadPayload;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_state == LoRaStateReadPayload)
|
||||
{
|
||||
m_fft->in()[m_fftCounter] = ci * m_downChirps[m_chirp]; // de-chirp the up ramp
|
||||
m_fftCounter++;
|
||||
|
||||
if (m_fftCounter == m_fftLength)
|
||||
{
|
||||
m_fftWindow.apply(m_fft->in());
|
||||
std::fill(m_fft->in()+m_fftLength, m_fft->in()+m_fftInterpolation*m_fftLength, Complex{0.0, 0.0});
|
||||
m_fft->transform();
|
||||
m_fftCounter = 0;
|
||||
double magsq;
|
||||
|
||||
unsigned int symbol = round(
|
||||
argmax(
|
||||
m_fft->out(),
|
||||
m_fftInterpolation,
|
||||
m_fftLength,
|
||||
magsq,
|
||||
m_spectrumBuffer,
|
||||
m_fftInterpolation
|
||||
) / ((float) m_fftInterpolation * (1<<m_settings.m_deBits))
|
||||
);
|
||||
|
||||
if (m_spectrumSink) {
|
||||
m_spectrumSink->feed(m_spectrumBuffer, m_nbSymbols);
|
||||
}
|
||||
|
||||
if ((m_chirpCount == 0) || (10.0*magsq > m_magsq))
|
||||
{
|
||||
qDebug("LoRaDemodSink::processSample: symbol %02u: %4u|%11.6f", m_chirpCount, symbol, magsq);
|
||||
m_magsq = magsq;
|
||||
m_chirpCount++;
|
||||
|
||||
if (m_chirpCount > 255)
|
||||
{
|
||||
qDebug("LoRaDemodSink::processSample: message length exceeded");
|
||||
m_state = LoRaStateReset;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug("LoRaDemodSink::processSample: end of message");
|
||||
m_state = LoRaStateReset;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_state = LoRaStateReset;
|
||||
}
|
||||
|
||||
m_chirp++;
|
||||
|
||||
if (m_chirp >= m_chirp0 + m_nbSymbols) {
|
||||
m_chirp = m_chirp0;
|
||||
}
|
||||
}
|
||||
|
||||
void LoRaDemodSink::reset()
|
||||
{
|
||||
m_chirp = 0;
|
||||
m_chirp0 = 0;
|
||||
m_fftCounter = 0;
|
||||
m_argMaxHistoryCounter = 0;
|
||||
m_sfdSkipCounter = 0;
|
||||
}
|
||||
|
||||
unsigned int LoRaDemodSink::argmax(
|
||||
const Complex *fftBins,
|
||||
unsigned int fftMult,
|
||||
unsigned int fftLength,
|
||||
double& magsqMax,
|
||||
Complex *specBuffer,
|
||||
unsigned int specDecim)
|
||||
{
|
||||
magsqMax = 0.0;
|
||||
unsigned int imax;
|
||||
double magSum = 0.0;
|
||||
|
||||
for (unsigned int i = 0; i < fftMult*fftLength; i++)
|
||||
{
|
||||
double magsq = std::norm(fftBins[i]);
|
||||
|
||||
if (magsq > magsqMax)
|
||||
{
|
||||
imax = i;
|
||||
magsqMax = magsq;
|
||||
}
|
||||
|
||||
if (specBuffer)
|
||||
{
|
||||
magSum += magsq;
|
||||
|
||||
if (i % specDecim == specDecim - 1)
|
||||
{
|
||||
specBuffer[i/specDecim] = Complex(std::polar(magSum, 0.0));
|
||||
magSum = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return imax;
|
||||
}
|
||||
|
||||
void LoRaDemodSink::decimateSpectrum(Complex *in, Complex *out, unsigned int size, unsigned int decimation)
|
||||
{
|
||||
for (unsigned int i = 0; i < size; i++)
|
||||
{
|
||||
if (i % decimation == 0) {
|
||||
out[i/decimation] = in[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int LoRaDemodSink::toSigned(int u, int intSize)
|
||||
{
|
||||
if (u > intSize/2) {
|
||||
return u - intSize;
|
||||
} else {
|
||||
return u;
|
||||
}
|
||||
}
|
||||
|
||||
void LoRaDemodSink::applyChannelSettings(int channelSampleRate, int bandwidth, int channelFrequencyOffset, bool force)
|
||||
@@ -271,42 +452,33 @@ void LoRaDemodSink::applyChannelSettings(int channelSampleRate, int bandwidth, i
|
||||
m_nco.setFreq(-channelFrequencyOffset, channelSampleRate);
|
||||
}
|
||||
|
||||
if ((channelSampleRate != m_channelSampleRate) || force)
|
||||
if ((channelSampleRate != m_channelSampleRate) ||
|
||||
(bandwidth != m_bandwidth) || force)
|
||||
{
|
||||
qDebug() << "LoRaDemodSink::applyChannelSettings: m_interpolator.create";
|
||||
m_interpolator.create(16, channelSampleRate, bandwidth / 1.9f);
|
||||
m_sampleDistanceRemain = (Real) channelSampleRate / bandwidth;
|
||||
m_interpolatorDistance = (Real) channelSampleRate / (Real) bandwidth;
|
||||
m_sampleDistanceRemain = 0;
|
||||
qDebug() << "LoRaDemodSink::applyChannelSettings: m_interpolator.create:"
|
||||
<< " m_interpolatorDistance: " << m_interpolatorDistance;
|
||||
}
|
||||
|
||||
m_channelSampleRate = channelSampleRate;
|
||||
m_Bandwidth = bandwidth;
|
||||
m_bandwidth = bandwidth;
|
||||
m_channelFrequencyOffset = channelFrequencyOffset;
|
||||
}
|
||||
|
||||
void LoRaDemodSink::applySettings(const LoRaDemodSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "LoRaDemodSink::applySettings:"
|
||||
<< " m_centerFrequency: " << settings.m_centerFrequency
|
||||
<< " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset
|
||||
<< " m_bandwidthIndex: " << settings.m_bandwidthIndex
|
||||
<< " m_spreadFactor: " << settings.m_spreadFactor
|
||||
<< " m_rgbColor: " << settings.m_rgbColor
|
||||
<< " m_title: " << settings.m_title
|
||||
<< " force: " << force;
|
||||
|
||||
if ((settings.m_spreadFactor != m_settings.m_spreadFactor) || force)
|
||||
{
|
||||
m_nbSymbols = 1 << settings.m_spreadFactor;
|
||||
m_sfftLength = m_nbSymbols / 2;
|
||||
delete m_loraFilter;
|
||||
delete m_negaFilter;
|
||||
delete m_mov;
|
||||
delete m_mag;
|
||||
delete m_rev;
|
||||
m_loraFilter = new sfft(m_sfftLength);
|
||||
m_negaFilter = new sfft(m_sfftLength);
|
||||
m_mov = new float[4*m_sfftLength];
|
||||
m_mag = new float[m_sfftLength];
|
||||
m_rev = new float[m_sfftLength];
|
||||
if ((settings.m_spreadFactor != m_settings.m_spreadFactor) || force) {
|
||||
initSF(settings.m_spreadFactor);
|
||||
}
|
||||
|
||||
m_settings = settings;
|
||||
|
||||
@@ -24,17 +24,19 @@
|
||||
#include "dsp/nco.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "util/message.h"
|
||||
#include "dsp/fftfilt.h"
|
||||
#include "dsp/fftwindow.h"
|
||||
|
||||
#include "lorademodsettings.h"
|
||||
|
||||
class BasebandSampleSink;
|
||||
class FFTEngine;
|
||||
|
||||
class LoRaDemodSink : public ChannelSampleSink {
|
||||
public:
|
||||
LoRaDemodSink();
|
||||
~LoRaDemodSink();
|
||||
|
||||
void initSF(unsigned int sf); //!< Init tables, FFTs, depending on spread factor
|
||||
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
|
||||
|
||||
void setSpectrumSink(BasebandSampleSink* spectrumSink) { m_spectrumSink = spectrumSink; }
|
||||
@@ -42,44 +44,69 @@ public:
|
||||
void applySettings(const LoRaDemodSettings& settings, bool force = false);
|
||||
|
||||
private:
|
||||
enum LoRaState
|
||||
{
|
||||
LoRaStateReset, //!< Reset everything to start all over
|
||||
LoRaStateDetectPreamble, //!< Look for preamble
|
||||
LoRaStatePreamble, //!< Preamble is found and look for SFD start
|
||||
LoRaStateSkipSFD, //!< Skip SFD
|
||||
LoRaStateSlideSFD, //!< Sliding FFTs while going through SFD (not the skip option)
|
||||
LoRaStateReadPayload,
|
||||
LoRaStateTest
|
||||
};
|
||||
|
||||
LoRaDemodSettings m_settings;
|
||||
Real m_Bandwidth;
|
||||
LoRaState m_state;
|
||||
int m_bandwidth;
|
||||
int m_channelSampleRate;
|
||||
int m_channelFrequencyOffset;
|
||||
int m_chirp;
|
||||
int m_angle;
|
||||
int m_bin;
|
||||
int m_result;
|
||||
int m_count;
|
||||
int m_header;
|
||||
int m_time;
|
||||
short m_tune;
|
||||
int m_chirp0;
|
||||
|
||||
sfft* m_loraFilter;
|
||||
sfft* m_negaFilter;
|
||||
float* m_mov;
|
||||
float* m_mag;
|
||||
float* m_rev;
|
||||
short* m_history;
|
||||
short* m_finetune;
|
||||
static const unsigned int m_requiredPreambleChirps = 3; //!< Number of chirps required to estimate preamble
|
||||
static const unsigned int m_maxSFDSearchChirps = 8; //!< Maximum number of chirps when looking for SFD after preamble detection
|
||||
static const unsigned int m_sfdFourths = 5; //!< Number of SFD chip period fourths to skip until payload
|
||||
static const unsigned int m_fftInterpolation = 1; //!< FFT interpolation factor (usually a power of 2)
|
||||
|
||||
FFTEngine *m_fft;
|
||||
FFTEngine *m_fftSFD;
|
||||
FFTWindow m_fftWindow;
|
||||
Complex *m_downChirps;
|
||||
Complex *m_upChirps;
|
||||
Complex *m_fftBuffer;
|
||||
unsigned int m_fftCounter;
|
||||
unsigned int m_argMaxHistory[m_requiredPreambleChirps];
|
||||
unsigned int m_argMaxHistoryCounter;
|
||||
unsigned int m_preambleHistory[m_maxSFDSearchChirps];
|
||||
unsigned int m_syncWord;
|
||||
double m_magsq;
|
||||
unsigned int m_chirpCount; //!< Generic chirp counter
|
||||
unsigned int m_sfdSkip; //!< Number of samples in a SFD skip or slide (1/4) period
|
||||
unsigned int m_sfdSkipCounter; //!< Counter of skip or slide periods
|
||||
|
||||
NCO m_nco;
|
||||
Interpolator m_interpolator;
|
||||
Real m_sampleDistanceRemain;
|
||||
Real m_interpolatorDistance;
|
||||
|
||||
BasebandSampleSink* m_spectrumSink;
|
||||
SampleVector m_sampleBuffer;
|
||||
Complex *m_spectrumBuffer;
|
||||
|
||||
unsigned int m_nbSymbols;
|
||||
unsigned int m_sfftLength;
|
||||
unsigned int m_fftLength;
|
||||
|
||||
static const int DATA_BITS;
|
||||
static const int SAMPLEBITS;
|
||||
static const int LORA_SQUELCH;
|
||||
|
||||
int detect(Complex sample, Complex angle);
|
||||
void dumpRaw(void);
|
||||
short synch (short bin);
|
||||
void processSample(const Complex& ci);
|
||||
void reset();
|
||||
unsigned int argmax(
|
||||
const Complex *fftBins,
|
||||
unsigned int fftMult,
|
||||
unsigned int fftLength,
|
||||
double& magsqMax,
|
||||
Complex *specBuffer,
|
||||
unsigned int specDecim
|
||||
);
|
||||
void decimateSpectrum(Complex *in, Complex *out, unsigned int size, unsigned int decimation);
|
||||
int toSigned(int u, int intSize);
|
||||
|
||||
/*
|
||||
Interleaving is "easiest" if the same number of bits is used per symbol as for FEC
|
||||
|
||||
@@ -63,6 +63,12 @@ public:
|
||||
void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; }
|
||||
MessageQueue *getMessageQueueToGUI() { return m_guiMessageQueue; }
|
||||
|
||||
virtual void feed(const Complex *begin, unsigned int length) //!< Special feed directly with complex array
|
||||
{
|
||||
(void) begin;
|
||||
(void) length;
|
||||
}
|
||||
|
||||
protected:
|
||||
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
|
||||
MessageQueue *m_guiMessageQueue; //!< Input message queue to the GUI
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
FFTWEngine::FFTWEngine() :
|
||||
m_plans(),
|
||||
m_currentPlan(NULL)
|
||||
m_currentPlan(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -14,8 +14,10 @@ FFTWEngine::~FFTWEngine()
|
||||
|
||||
void FFTWEngine::configure(int n, bool inverse)
|
||||
{
|
||||
for(Plans::const_iterator it = m_plans.begin(); it != m_plans.end(); ++it) {
|
||||
if(((*it)->n == n) && ((*it)->inverse == inverse)) {
|
||||
for (Plans::const_iterator it = m_plans.begin(); it != m_plans.end(); ++it)
|
||||
{
|
||||
if (((*it)->n == n) && ((*it)->inverse == inverse))
|
||||
{
|
||||
m_currentPlan = *it;
|
||||
return;
|
||||
}
|
||||
@@ -37,33 +39,40 @@ void FFTWEngine::configure(int n, bool inverse)
|
||||
|
||||
void FFTWEngine::transform()
|
||||
{
|
||||
if(m_currentPlan != NULL)
|
||||
if (m_currentPlan) {
|
||||
fftwf_execute(m_currentPlan->plan);
|
||||
}
|
||||
}
|
||||
|
||||
Complex* FFTWEngine::in()
|
||||
{
|
||||
if(m_currentPlan != NULL)
|
||||
if (m_currentPlan) {
|
||||
return reinterpret_cast<Complex*>(m_currentPlan->in);
|
||||
else return NULL;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Complex* FFTWEngine::out()
|
||||
{
|
||||
if(m_currentPlan != NULL)
|
||||
if (m_currentPlan) {
|
||||
return reinterpret_cast<Complex*>(m_currentPlan->out);
|
||||
else return NULL;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
QMutex FFTWEngine::m_globalPlanMutex;
|
||||
|
||||
void FFTWEngine::freeAll()
|
||||
{
|
||||
for(Plans::iterator it = m_plans.begin(); it != m_plans.end(); ++it) {
|
||||
for (Plans::iterator it = m_plans.begin(); it != m_plans.end(); ++it)
|
||||
{
|
||||
fftwf_destroy_plan((*it)->plan);
|
||||
fftwf_free((*it)->in);
|
||||
fftwf_free((*it)->out);
|
||||
delete *it;
|
||||
}
|
||||
|
||||
m_plans.clear();
|
||||
}
|
||||
|
||||
@@ -81,6 +81,110 @@ void SpectrumVis::feedTriggered(const SampleVector::const_iterator& triggerPoint
|
||||
}*/
|
||||
}
|
||||
|
||||
void SpectrumVis::feed(const Complex *begin, unsigned int length)
|
||||
{
|
||||
if (m_glSpectrum == 0) {
|
||||
return;
|
||||
}
|
||||
if (!m_mutex.tryLock(0)) { // prevent conflicts with configuration process
|
||||
return;
|
||||
}
|
||||
|
||||
Complex c;
|
||||
Real v;
|
||||
|
||||
if (m_avgMode == AvgModeNone)
|
||||
{
|
||||
for (unsigned int i = 0; i < m_fftSize; i++)
|
||||
{
|
||||
if (i < length) {
|
||||
c = begin[i];
|
||||
} else {
|
||||
c = Complex{0,0};
|
||||
}
|
||||
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v = m_linear ? v/m_powFFTDiv : m_mult * log2f(v) + m_ofs;
|
||||
m_powerSpectrum[i] = v;
|
||||
}
|
||||
|
||||
// send new data to visualisation
|
||||
m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize);
|
||||
}
|
||||
else if (m_avgMode == AvgModeMovingAvg)
|
||||
{
|
||||
for (unsigned int i = 0; i < m_fftSize; i++)
|
||||
{
|
||||
if (i < length) {
|
||||
c = begin[i];
|
||||
} else {
|
||||
c = Complex{0,0};
|
||||
}
|
||||
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v = m_movingAverage.storeAndGetAvg(v, i);
|
||||
v = m_linear ? v/m_powFFTDiv : m_mult * log2f(v) + m_ofs;
|
||||
m_powerSpectrum[i] = v;
|
||||
}
|
||||
|
||||
// send new data to visualisation
|
||||
m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize);
|
||||
m_movingAverage.nextAverage();
|
||||
}
|
||||
else if (m_avgMode == AvgModeFixedAvg)
|
||||
{
|
||||
double avg;
|
||||
|
||||
for (unsigned int i = 0; i < m_fftSize; i++)
|
||||
{
|
||||
if (i < length) {
|
||||
c = begin[i];
|
||||
} else {
|
||||
c = Complex{0,0};
|
||||
}
|
||||
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
|
||||
if (m_fixedAverage.storeAndGetAvg(avg, v, i))
|
||||
{ // result available
|
||||
avg = m_linear ? avg/m_powFFTDiv : m_mult * log2f(avg) + m_ofs;
|
||||
m_powerSpectrum[i] = avg;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_fixedAverage.nextAverage()) { // result available
|
||||
m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); // send new data to visualisation
|
||||
}
|
||||
}
|
||||
else if (m_avgMode == AvgModeMax)
|
||||
{
|
||||
double max;
|
||||
|
||||
for (unsigned int i = 0; i < m_fftSize; i++)
|
||||
{
|
||||
if (i < length) {
|
||||
c = begin[i];
|
||||
} else {
|
||||
c = Complex{0,0};
|
||||
}
|
||||
|
||||
v = c.real() * c.real() + c.imag() * c.imag();
|
||||
|
||||
if (m_max.storeAndGetMax(max, v, i))
|
||||
{ // result available
|
||||
max = m_linear ? max/m_powFFTDiv : m_mult * log2f(max) + m_ofs;
|
||||
m_powerSpectrum[i] = max;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_max.nextMax()) { // result available
|
||||
m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); // send new data to visualisation
|
||||
}
|
||||
}
|
||||
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleVector::const_iterator& end, bool positiveOnly)
|
||||
{
|
||||
// if no visualisation is set, send the samples to /dev/null
|
||||
|
||||
@@ -90,6 +90,7 @@ public:
|
||||
void setScalef(MessageQueue* msgQueue, Real scalef);
|
||||
|
||||
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly);
|
||||
virtual void feed(const Complex *begin, unsigned int length); //!< direct FFT feed
|
||||
void feedTriggered(const SampleVector::const_iterator& triggerPoint, const SampleVector::const_iterator& end, bool positiveOnly);
|
||||
virtual void start();
|
||||
virtual void stop();
|
||||
|
||||
@@ -590,6 +590,11 @@ void GLSpectrumGUI::setAveragingToolitp()
|
||||
}
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::setFFTSize(int log2FFTSize)
|
||||
{
|
||||
ui->fftSize->setCurrentIndex(log2FFTSize < 7 ? 0 : log2FFTSize > 12 ? 5 : log2FFTSize - 7); // 128 to 4096 in powers of 2
|
||||
}
|
||||
|
||||
bool GLSpectrumGUI::handleMessage(const Message& message)
|
||||
{
|
||||
if (GLSpectrum::MsgReportSampleRate::match(message))
|
||||
|
||||
@@ -30,6 +30,7 @@ public:
|
||||
~GLSpectrumGUI();
|
||||
|
||||
void setBuddies(MessageQueue* messageQueue, SpectrumVis* spectrumVis, GLSpectrum* glSpectrum);
|
||||
void setFFTSize(int log2FFTSize);
|
||||
|
||||
void resetToDefaults();
|
||||
virtual QByteArray serialize() const;
|
||||
|
||||
Reference in New Issue
Block a user