Add M17 demodulator code and tests
Added implementation of the M17 4FSK demodulator, including clock recovery, phase detection, and quantization algorithms. A testsuite is also included to do regression testing against a reference baseband pre-generated and the corresponding reference bitstream. A 1% BER is still present due to a fault likely in the RRC filtering, since the eye diagram of the filtered stream is bad. TG-81
This commit is contained in:
parent
396f66a1f3
commit
3163dd49d7
11
meson.build
11
meson.build
|
|
@ -42,6 +42,7 @@ openrtx_src = ['openrtx/src/core/state.c',
|
|||
'openrtx/src/protocols/M17/M17DSP.cpp',
|
||||
'openrtx/src/protocols/M17/M17Callsign.cpp',
|
||||
'openrtx/src/protocols/M17/M17Modulator.cpp',
|
||||
'openrtx/src/protocols/M17/M17Demodulator.cpp',
|
||||
'openrtx/src/protocols/M17/M17Transmitter.cpp',
|
||||
'openrtx/src/protocols/M17/M17LinkSetupFrame.cpp']
|
||||
|
||||
|
|
@ -614,5 +615,15 @@ m17_viterbi_test = executable('m17_viterbi_test',
|
|||
sources : unit_test_src + ['tests/unit/M17_viterbi.cpp'],
|
||||
kwargs : unit_test_opts)
|
||||
|
||||
m17_demodulator_test = executable('m17_demodulator_test',
|
||||
sources: unit_test_src + ['tests/unit/M17_demodulator.cpp'],
|
||||
kwargs: unit_test_opts)
|
||||
|
||||
m17_rrc_test = executable('m17_rrc_test',
|
||||
sources: unit_test_src + ['tests/unit/M17_rrc.cpp'],
|
||||
kwargs: unit_test_opts)
|
||||
|
||||
test('M17 Golay Unit Test', m17_golay_test)
|
||||
test('M17 Viterbi Unit Test', m17_viterbi_test)
|
||||
test('M17 Demodulator Test', m17_demodulator_test)
|
||||
test('M17 RRC Test', m17_rrc_test)
|
||||
|
|
|
|||
|
|
@ -27,9 +27,20 @@
|
|||
#error This header is C++ only!
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <interfaces/audio_stream.h>
|
||||
#include <hwconfig.h>
|
||||
|
||||
namespace M17
|
||||
{
|
||||
|
||||
typedef struct {
|
||||
int32_t index;
|
||||
bool lsf;
|
||||
} sync_t;
|
||||
|
||||
class M17Demodulator
|
||||
{
|
||||
public:
|
||||
|
|
@ -65,34 +76,138 @@ public:
|
|||
void stopBasebandSampling();
|
||||
|
||||
/**
|
||||
* Returns the next frame decoded from the baseband signal
|
||||
* Returns the a frame decoded from the baseband signal.
|
||||
*/
|
||||
const std::array<uint8_t, 48>& nextFrame();
|
||||
const std::array<uint8_t, 48> &getFrame();
|
||||
|
||||
};
|
||||
/**
|
||||
* Returns a flag indicating whether the decoded frame is an LSF.
|
||||
*/
|
||||
bool isFrameLSF();
|
||||
|
||||
/**
|
||||
* Demodulates data from the ADC and fills the idle frame
|
||||
* Everytime this function is called a whole ADC buffer is consumed
|
||||
*/
|
||||
void update();
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* M17 syncword symbols after RRC sampled at 5 samples per symbol;
|
||||
/*
|
||||
* We are sampling @ 24KHz so an M17 frame has half the samples,
|
||||
* our input buffer contains half M17 frame.
|
||||
*/
|
||||
uint8_t syncword[] = { };
|
||||
// TODO: Select correct RRC filter according to sample rate
|
||||
static constexpr size_t M17_RX_SAMPLE_RATE = 24000;
|
||||
static constexpr size_t M17_FRAME_SAMPLES_24K = 960;
|
||||
static constexpr size_t M17_FRAME_SYMBOLS = 192;
|
||||
static constexpr size_t M17_SYNCWORD_SYMBOLS = 8;
|
||||
static constexpr size_t M17_CONV_THRESHOLD = 50000;
|
||||
static constexpr size_t M17_SAMPLES_PER_SYMBOL = M17_SPS;
|
||||
static constexpr size_t M17_INPUT_BUF_SIZE = M17_FRAME_SAMPLES_24K / 2;
|
||||
static constexpr size_t M17_FRAME_BYTES = M17_FRAME_SYMBOLS / 4;
|
||||
static constexpr float conv_stats_alpha = 0.0009765625f;
|
||||
static constexpr float conv_threshold_factor = 3.65;
|
||||
static constexpr float qnt_maxmin_alpha = 0.999f;
|
||||
|
||||
using dataBuffer_t = std::array< int16_t, M17_FRAME_SAMPLES >;
|
||||
using dataFrame_t = std::array< int8_t, M17_FRAME_SYMBOLS >;
|
||||
/**
|
||||
* M17 syncwords;
|
||||
*/
|
||||
int8_t lsf_syncword[M17_SYNCWORD_SYMBOLS] = { +3, +3, +3, +3, -3, -3, +3, -3 };
|
||||
int8_t stream_syncword[M17_SYNCWORD_SYMBOLS] = { -3, -3, -3, -3, +3, +3, -3, +3 };
|
||||
|
||||
using dataBuffer_t = std::array< int16_t, M17_FRAME_SAMPLES_24K >;
|
||||
using dataFrame_t = std::array< uint8_t, M17_FRAME_BYTES >;
|
||||
|
||||
/*
|
||||
* Buffers
|
||||
*/
|
||||
streamId basebandId; ///< Id of the baseband input stream.
|
||||
int16_t *baseband_buffer; ///< Buffer for baseband audio handling.
|
||||
dataBuffer_t *activeBuffer; ///< Half baseband buffer, in reception.
|
||||
dataBuffer_t *idleBuffer; ///< Half baseband buffer, to be processed.
|
||||
dataBlock_t baseband; ///< Half buffer, free to be processed.
|
||||
uint16_t *rawFrame; ///< Analog values to be quantized.
|
||||
uint16_t rawFrameIndex; ///< Index for filling the raw frame.
|
||||
dataFrame_t *activeFrame; ///< Half frame, in demodulation.
|
||||
dataFrame_t *idleFrame; ///< Half frame, free to be processed.
|
||||
bool isLSF; ///< Indicates that we demodualated an LSF.
|
||||
bool locked; ///< A syncword was detected.
|
||||
|
||||
/*
|
||||
* State variables
|
||||
*/
|
||||
bool m17RxEnabled; ///< M17 Reception Enabled
|
||||
|
||||
/*
|
||||
* Convolution statistics computation
|
||||
*/
|
||||
float conv_ema = 0.0f;
|
||||
float conv_emvar = 0.0f;
|
||||
|
||||
/*
|
||||
* Quantization statistics computation
|
||||
*/
|
||||
float qnt_max = 0.0f;
|
||||
float qnt_min = 0.0f;
|
||||
|
||||
/**
|
||||
* Finds the index of the next syncword in the baseband stream.
|
||||
* Resets the exponential mean and variance/stddev computation.
|
||||
*/
|
||||
void resetCorrelationStats();
|
||||
|
||||
/**
|
||||
* Updates the mean and variance with the given value.
|
||||
*
|
||||
* @param value: value to be added to the exponential moving
|
||||
* average/variance computation
|
||||
*/
|
||||
void updateCorrelationStats(int32_t value);
|
||||
|
||||
/**
|
||||
* Returns the standard deviation from all the passed values.
|
||||
*
|
||||
* @returns float numerical value of the standard deviation
|
||||
*/
|
||||
float getCorrelationStddev();
|
||||
|
||||
void resetQuantizationStats();
|
||||
|
||||
void updateQuantizationStats(uint32_t offset);
|
||||
|
||||
float getQuantizationMax();
|
||||
|
||||
float getQuantizationMin();
|
||||
|
||||
/**
|
||||
* Computes the convolution between a stride of samples starting from
|
||||
* a given offset and a target waveform.
|
||||
*
|
||||
* @param offset: the offset in the active buffer where to start the stride
|
||||
* @param target: a buffer containing the target waveform to be convoluted
|
||||
* @param target_size: the number of symbols of the target waveform
|
||||
* @return uint16_t numerical value of the convolution
|
||||
*/
|
||||
int32_t convolution(size_t offset, int8_t *target, size_t target_size);
|
||||
|
||||
/**
|
||||
* Finds the index of the next frame syncword in the baseband stream.
|
||||
*
|
||||
* @param baseband: buffer containing the sampled baseband signal
|
||||
* @param offset: offset of the buffer after which syncword are searched
|
||||
* @return uint16_t index of the first syncword in the buffer after the offset
|
||||
*/ uint16_t nextSyncWord(int16_t *baseband);
|
||||
*/
|
||||
sync_t nextFrameSync(uint32_t offset);
|
||||
|
||||
/**
|
||||
* Takes the value from the input baseband at a given offsets and quantizes
|
||||
* it leveraging the quantization max and min hold statistics.
|
||||
*
|
||||
* @param offset: the offset in the input baseband
|
||||
* @return int8_t quantized symbol
|
||||
*/
|
||||
int8_t quantize(int32_t offset);
|
||||
|
||||
};
|
||||
|
||||
} /* M17 */
|
||||
|
||||
#endif /* M17_DEMODULATOR_H */
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -69,4 +70,40 @@ inline void setBit(std::array< uint8_t, N >& array, const size_t pos,
|
|||
(bit ? mask : 0x00);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function allowing to set the value of a symbol on an array
|
||||
* of bytes. Symbols are packed putting the most significant bit first,
|
||||
* symbols are filled from the least significant bit pair to the most
|
||||
* significant bit pair.
|
||||
*
|
||||
* \param array: byte array.
|
||||
* \param pos: symbol position inside the array.
|
||||
* \param symbol: symbol to be set, either -3, -1, +1, +3.
|
||||
*/
|
||||
template < size_t N >
|
||||
inline void setSymbol(std::array< uint8_t, N >& array, const size_t pos,
|
||||
const int8_t symbol)
|
||||
{
|
||||
switch(symbol) {
|
||||
case +3:
|
||||
setBit<N> (array, 2 * pos , 0);
|
||||
setBit<N> (array, 2 * pos + 1, 1);
|
||||
break;
|
||||
case +1:
|
||||
setBit<N> (array, 2 * pos , 0);
|
||||
setBit<N> (array, 2 * pos + 1, 0);
|
||||
break;
|
||||
case -1:
|
||||
setBit<N> (array, 2 * pos , 1);
|
||||
setBit<N> (array, 2 * pos + 1, 0);
|
||||
break;
|
||||
case -3:
|
||||
setBit<N> (array, 2 * pos , 1);
|
||||
setBit<N> (array, 2 * pos + 1, 1);
|
||||
break;
|
||||
default:
|
||||
assert("Error: storing unknown M17 symbol!");
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* M17_UTILS_H */
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ namespace M17
|
|||
{
|
||||
|
||||
/*
|
||||
* Coefficients for M17 RRC filter
|
||||
* Coefficients for M17 RRC filter for 48KHz sample rate
|
||||
*/
|
||||
const std::array<float, 79> rrc_taps = {
|
||||
-0.009265784007800534, -0.006136551625729697, -0.001125978562075172,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,247 @@
|
|||
/***************************************************************************
|
||||
* Copyright (C) 2021 by Federico Amedeo Izzo IU2NUO, *
|
||||
* Niccolò Izzo IU2KIN *
|
||||
* Frederik Saraci IU2NRO *
|
||||
* Silvano Seva IU2KWO *
|
||||
* *
|
||||
* 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; either 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 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 <M17/M17Demodulator.h>
|
||||
#include <M17/M17DSP.h>
|
||||
#include <M17/M17Utils.h>
|
||||
#include <interfaces/audio_stream.h>
|
||||
#include <math.h>
|
||||
|
||||
#if defined(PLATFORM_MD3x0) || defined(PLATFORM_MDUV3x0)
|
||||
#include <toneGenerator_MDx.h>
|
||||
#elif defined(PLATFORM_LINUX)
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
namespace M17
|
||||
{
|
||||
|
||||
M17Demodulator::M17Demodulator()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
M17Demodulator::~M17Demodulator()
|
||||
{
|
||||
terminate();
|
||||
}
|
||||
|
||||
void M17Demodulator::init()
|
||||
{
|
||||
/*
|
||||
* Allocate a chunk of memory to contain two complete buffers for baseband
|
||||
* audio. Split this chunk in two separate blocks for double buffering using
|
||||
* placement new.
|
||||
*/
|
||||
|
||||
baseband_buffer = new int16_t[M17_FRAME_SAMPLES_24K];
|
||||
baseband = { nullptr, 0 };
|
||||
activeFrame = new dataFrame_t;
|
||||
rawFrame = new uint16_t[M17_FRAME_SYMBOLS];
|
||||
idleFrame = new dataFrame_t;
|
||||
}
|
||||
|
||||
void M17Demodulator::terminate()
|
||||
{
|
||||
// Delete the buffers and deallocate memory.
|
||||
delete[] baseband_buffer;
|
||||
delete activeFrame;
|
||||
delete[] rawFrame;
|
||||
delete idleFrame;
|
||||
}
|
||||
|
||||
void M17Demodulator::startBasebandSampling()
|
||||
{
|
||||
|
||||
basebandId = inputStream_start(SOURCE_RTX, PRIO_RX,
|
||||
baseband_buffer,
|
||||
M17_INPUT_BUF_SIZE,
|
||||
BUF_CIRC_DOUBLE,
|
||||
M17_RX_SAMPLE_RATE);
|
||||
}
|
||||
|
||||
void M17Demodulator::stopBasebandSampling()
|
||||
{
|
||||
inputStream_stop(basebandId);
|
||||
}
|
||||
|
||||
void M17Demodulator::resetCorrelationStats() {
|
||||
conv_ema = 0.0f;
|
||||
conv_emvar = 10000000000.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Algorithms taken from
|
||||
* https://fanf2.user.srcf.net/hermes/doc/antiforgery/stats.pdf
|
||||
*/
|
||||
void M17Demodulator::updateCorrelationStats(int32_t value)
|
||||
{
|
||||
float delta = (float) value - conv_ema;
|
||||
float incr = conv_stats_alpha * delta;
|
||||
conv_ema += incr;
|
||||
conv_emvar = (1.0f - conv_stats_alpha) * (conv_emvar + delta * incr);
|
||||
}
|
||||
|
||||
float M17Demodulator::getCorrelationStddev()
|
||||
{
|
||||
return sqrt(conv_emvar);
|
||||
}
|
||||
|
||||
void M17Demodulator::resetQuantizationStats() {
|
||||
qnt_max = 0.0f;
|
||||
}
|
||||
|
||||
void M17Demodulator::updateQuantizationStats(uint32_t offset)
|
||||
{
|
||||
auto value = baseband.data[offset];
|
||||
if (value > qnt_max) {
|
||||
qnt_max = value;
|
||||
} else
|
||||
qnt_max *= qnt_maxmin_alpha;
|
||||
if (value < qnt_min) {
|
||||
qnt_min = value;
|
||||
} else
|
||||
qnt_min *= qnt_maxmin_alpha;
|
||||
}
|
||||
|
||||
float M17Demodulator::getQuantizationMax()
|
||||
{
|
||||
return qnt_max;
|
||||
}
|
||||
|
||||
float M17Demodulator::getQuantizationMin()
|
||||
{
|
||||
return qnt_min;
|
||||
}
|
||||
|
||||
int32_t M17Demodulator::convolution(size_t offset,
|
||||
int8_t *target,
|
||||
size_t target_size)
|
||||
{
|
||||
// Compute convolution
|
||||
int32_t conv = 0;
|
||||
for(uint32_t i = 0; i < target_size; i++)
|
||||
{
|
||||
conv += (int32_t) target[i] *
|
||||
(int32_t) this->baseband.data[offset + i * M17_SAMPLES_PER_SYMBOL];
|
||||
}
|
||||
return conv;
|
||||
}
|
||||
|
||||
sync_t M17Demodulator::nextFrameSync(uint32_t offset)
|
||||
{
|
||||
sync_t syncword = { -1, false };
|
||||
// Find peaks in the correlation between the baseband and the frame syncword
|
||||
// Leverage the fact LSF syncword is the opposite of the frame syncword
|
||||
// to detect both syncwords at once.
|
||||
for(uint32_t i = offset; syncword.index == -1 && i < baseband.len; i++)
|
||||
{
|
||||
int32_t conv = convolution(i, stream_syncword, M17_SYNCWORD_SYMBOLS);
|
||||
updateCorrelationStats(conv);
|
||||
// Positive correlation peak -> frame syncword
|
||||
if (conv > getCorrelationStddev() * conv_threshold_factor)
|
||||
{
|
||||
syncword.lsf = false;
|
||||
syncword.index = i;
|
||||
}
|
||||
// Negative correlation peak -> LSF syncword
|
||||
else if (conv < -(getCorrelationStddev() * conv_threshold_factor))
|
||||
{
|
||||
syncword.lsf = true;
|
||||
syncword.index = i;
|
||||
}
|
||||
}
|
||||
return syncword;
|
||||
}
|
||||
|
||||
int8_t M17Demodulator::quantize(int32_t offset)
|
||||
{
|
||||
if (baseband.data[offset] > getQuantizationMax() * 2 / 3)
|
||||
return +3;
|
||||
else if (baseband.data[offset] < getQuantizationMin() * 2 / 3)
|
||||
return -3;
|
||||
else if (baseband.data[offset] > 0)
|
||||
return +1;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
const std::array<uint8_t, 48> &M17Demodulator::getFrame()
|
||||
{
|
||||
return *activeFrame;
|
||||
}
|
||||
|
||||
bool M17Demodulator::isFrameLSF()
|
||||
{
|
||||
return isLSF;
|
||||
}
|
||||
|
||||
void M17Demodulator::update()
|
||||
{
|
||||
// Read samples from the ADC
|
||||
baseband = inputStream_getData(basebandId);
|
||||
|
||||
if(baseband.data != NULL)
|
||||
{
|
||||
// Apply RRC on the baseband buffer
|
||||
for(size_t i = 0; i < baseband.len; i++)
|
||||
{
|
||||
float elem = static_cast< float >(baseband.data[i]);
|
||||
baseband.data[i] = static_cast< int16_t >(M17::rrc(elem) * 0.10);
|
||||
}
|
||||
|
||||
// If we locked a syncword just demodulate samples
|
||||
if (locked)
|
||||
{
|
||||
|
||||
}
|
||||
else // Otherwise find next syncword
|
||||
{
|
||||
M17::sync_t syncword = { -1, false };
|
||||
uint16_t offset = 0;
|
||||
syncword = nextFrameSync(offset);
|
||||
if (syncword.index != -1)
|
||||
{
|
||||
locked = true;
|
||||
// Set a flag to mark LSF
|
||||
isLSF = syncword.lsf;
|
||||
// Next syncword does not overlap with current syncword
|
||||
offset = syncword.index + M17_SAMPLES_PER_SYMBOL;
|
||||
// Slice the input buffer to extract a frame and quantize
|
||||
for(uint16_t i = 0; i < M17_FRAME_SYMBOLS; i++)
|
||||
{
|
||||
// Quantize
|
||||
uint16_t symbol_index = syncword.index + 2 +
|
||||
M17_SAMPLES_PER_SYMBOL * i;
|
||||
updateQuantizationStats(baseband.data[symbol_index]);
|
||||
int8_t symbol = quantize(symbol_index);
|
||||
setSymbol<M17_FRAME_BYTES>(*activeFrame, i, symbol);
|
||||
// If the frame buffer is full switch active and idle frame
|
||||
if (rawFrameIndex == M17_FRAME_SYMBOLS)
|
||||
std::swap(activeFrame, idleFrame);
|
||||
}
|
||||
// If we have some samples left, try to decode the syncword
|
||||
// If decoding fails, signal lock is lost
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} /* M17 */
|
||||
|
|
@ -139,4 +139,7 @@
|
|||
#define GPS_EN GPIOD,8
|
||||
#define GPS_DATA GPIOD,9
|
||||
|
||||
/* M17 demodulation */
|
||||
#define M17_SPS 5
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -83,4 +83,7 @@
|
|||
#define SOFTPOT_RX 0x2E
|
||||
#define SOFTPOT_TX 0x2F
|
||||
|
||||
/* M17 demodulation */
|
||||
#define M17_SPS 5
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -54,3 +54,6 @@
|
|||
|
||||
/* Push-to-talk switch */
|
||||
#define PTT_SW "PTT_SW",11
|
||||
|
||||
/* M17 demodulation */
|
||||
#define M17_SPS 10
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
#! /usr/bin/env python3
|
||||
|
||||
import pandas as pd
|
||||
from matplotlib import pyplot as plt
|
||||
|
||||
plt.rcParams["figure.autolayout"] = True
|
||||
df = pd.read_csv("./build_linux/M17_clock_recovery_output_1.csv")
|
||||
print("Contents in csv file:\n", df)
|
||||
#plt.plot(df.index, df.Input)
|
||||
#plt.plot(df.index, df.RRCSignal)
|
||||
plt.plot(df.index, df.LSFConvolution)
|
||||
plt.plot(df.index, df.FrameConvolution)
|
||||
plt.plot(df.index, df.Stddev)
|
||||
plt.show()
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
#! /usr/bin/env python3
|
||||
|
||||
import pandas as pd
|
||||
from matplotlib import pyplot as plt
|
||||
|
||||
plt.rcParams["figure.autolayout"] = True
|
||||
df = pd.read_csv("./build_linux/M17_clock_recovery_output_2.csv")
|
||||
print("Contents in csv file:\n", df)
|
||||
plt.plot(df.index, df.RRCSignal)
|
||||
plt.plot(df.index, df.SyncDetect)
|
||||
plt.plot(df.index, df.QntMax)
|
||||
plt.plot(df.index, df.QntMin)
|
||||
plt.plot(df.index, df.Symbol)
|
||||
plt.show()
|
||||
|
|
@ -0,0 +1,237 @@
|
|||
/***************************************************************************
|
||||
* Copyright (C) 2021 by Federico Amedeo Izzo IU2NUO, *
|
||||
* Niccolò Izzo IU2KIN *
|
||||
* Frederik Saraci IU2NRO *
|
||||
* Silvano Seva IU2KWO *
|
||||
* *
|
||||
* *
|
||||
* 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; either 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 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/> *
|
||||
***************************************************************************/
|
||||
|
||||
// Test private methods
|
||||
#define private public
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <M17/M17DSP.h>
|
||||
#include <M17/M17Demodulator.h>
|
||||
#include <M17/M17Utils.h>
|
||||
#include <interfaces/audio_stream.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
/**
|
||||
* Test the different demodulation steps
|
||||
*/
|
||||
|
||||
int main()
|
||||
{
|
||||
// Open file
|
||||
FILE *baseband_file = fopen("../tests/unit/assets/M17_test_baseband.raw", "rb");
|
||||
FILE *baseband_out = fopen("M17_test_baseband_rrc.raw", "wb");
|
||||
if (!baseband_file)
|
||||
{
|
||||
perror("Error in reading test baseband");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get file size
|
||||
fseek(baseband_file, 0L, SEEK_END);
|
||||
long baseband_size = ftell(baseband_file);
|
||||
unsigned baseband_samples = baseband_size / 2;
|
||||
fseek(baseband_file, 0L, SEEK_SET);
|
||||
printf("Baseband is %ld bytes!\n", baseband_size);
|
||||
|
||||
// Read data from input file
|
||||
int16_t *baseband_buffer = (int16_t *) malloc(baseband_size);
|
||||
if (!baseband_buffer)
|
||||
{
|
||||
perror("Error in memory allocation");
|
||||
return -1;
|
||||
}
|
||||
size_t read_items = fread(baseband_buffer, 2, baseband_samples, baseband_file);
|
||||
if (read_items != baseband_samples)
|
||||
{
|
||||
perror("Error in reading input file");
|
||||
return -1;
|
||||
}
|
||||
fclose(baseband_file);
|
||||
|
||||
// Apply RRC on the baseband buffer
|
||||
int16_t *filtered_buffer = (int16_t *) malloc(baseband_size);
|
||||
for(size_t i = 0; i < baseband_samples; i++)
|
||||
{
|
||||
float elem = static_cast< float >(baseband_buffer[i]);
|
||||
filtered_buffer[i] = static_cast< int16_t >(M17::rrc(0.10 * elem));
|
||||
}
|
||||
fwrite(filtered_buffer, baseband_samples, 2, baseband_out);
|
||||
fclose(baseband_out);
|
||||
|
||||
M17::M17Demodulator m17Demodulator = M17::M17Demodulator();
|
||||
m17Demodulator.init();
|
||||
dataBlock_t baseband = { nullptr, 0 };
|
||||
baseband.data = baseband_buffer;
|
||||
baseband.len = baseband_samples;
|
||||
dataBlock_t old_baseband = m17Demodulator.baseband;
|
||||
m17Demodulator.baseband = baseband;
|
||||
baseband.data = filtered_buffer;
|
||||
|
||||
FILE *output_csv_1 = fopen("M17_clock_recovery_output_1.csv", "w");
|
||||
fprintf(output_csv_1, "Input,RRCSignal,LSFConvolution,FrameConvolution,Stddev\n");
|
||||
// Test convolution
|
||||
m17Demodulator.resetCorrelationStats();
|
||||
for(unsigned i = 0; i < baseband_samples - m17Demodulator.M17_SYNCWORD_SYMBOLS * M17_SPS; i++)
|
||||
{
|
||||
int32_t lsf_conv =
|
||||
m17Demodulator.convolution(i,
|
||||
m17Demodulator.lsf_syncword,
|
||||
m17Demodulator.M17_SYNCWORD_SYMBOLS);
|
||||
int32_t stream_conv =
|
||||
m17Demodulator.convolution(i,
|
||||
m17Demodulator.stream_syncword,
|
||||
m17Demodulator.M17_SYNCWORD_SYMBOLS);
|
||||
m17Demodulator.updateCorrelationStats(stream_conv);
|
||||
fprintf(output_csv_1, "%" PRId16 ",%" PRId16 ",%d,%d,%f\n",
|
||||
baseband_buffer[i],
|
||||
baseband.data[i],
|
||||
lsf_conv,
|
||||
stream_conv,
|
||||
3.65 * m17Demodulator.getCorrelationStddev());
|
||||
}
|
||||
fclose(output_csv_1);
|
||||
|
||||
// Test syncword detection
|
||||
printf("Testing syncword detection!\n");
|
||||
FILE *syncword_ref = fopen("../tests/unit/assets/M17_test_baseband_syncwords.txt", "r");
|
||||
int32_t offset = 0;
|
||||
M17::sync_t syncword = { -1, false };
|
||||
m17Demodulator.resetCorrelationStats();
|
||||
int i = 0;
|
||||
do
|
||||
{
|
||||
int expected_syncword = 0;
|
||||
fscanf(syncword_ref, "%d\n", &expected_syncword);
|
||||
syncword = m17Demodulator.nextFrameSync(offset);
|
||||
offset = syncword.index + m17Demodulator.M17_SYNCWORD_SYMBOLS * M17_SPS;
|
||||
if (syncword.index != expected_syncword)
|
||||
{
|
||||
fprintf(stderr, "Error in syncwords detection #%d!\n", i);
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("SYNC: %d...OK!\n", syncword.index);
|
||||
}
|
||||
i++;
|
||||
} while (syncword.index != -1);
|
||||
fclose(syncword_ref);
|
||||
|
||||
FILE *output_csv_2 = fopen("M17_clock_recovery_output_2.csv", "w");
|
||||
fprintf(output_csv_2, "RRCSignal,SyncDetect,QntMax,QntMin,Symbol\n");
|
||||
uint32_t detect = 0, symbol = 0;
|
||||
offset = 0;
|
||||
syncword = { -1, false };
|
||||
m17Demodulator.resetCorrelationStats();
|
||||
syncword = m17Demodulator.nextFrameSync(offset);
|
||||
for(unsigned i = 0; i < baseband_samples - m17Demodulator.M17_SYNCWORD_SYMBOLS * M17_SPS; i++)
|
||||
{
|
||||
if ((int) i == syncword.index + 2) {
|
||||
if (syncword.lsf)
|
||||
detect = -40000;
|
||||
else
|
||||
detect = 40000;
|
||||
syncword = m17Demodulator.nextFrameSync(syncword.index + m17Demodulator.M17_SYNCWORD_SYMBOLS * M17_SPS);
|
||||
} else if (((int) i % 10) == ((syncword.index + 2) % 10)) {
|
||||
m17Demodulator.updateQuantizationStats(i);
|
||||
symbol = m17Demodulator.quantize(i) * 10000;
|
||||
detect = 30000;
|
||||
} else
|
||||
{
|
||||
detect = 0;
|
||||
symbol = 0;
|
||||
}
|
||||
fprintf(output_csv_2, "%" PRId16 ",%d,%f,%f,%d\n",
|
||||
m17Demodulator.baseband.data[i],
|
||||
detect,
|
||||
m17Demodulator.getQuantizationMax() * 2 / 3,
|
||||
m17Demodulator.getQuantizationMin() * 2 / 3,
|
||||
symbol);
|
||||
}
|
||||
fclose(output_csv_2);
|
||||
|
||||
// Test symbol quantization
|
||||
FILE *symbols_ref = fopen("../tests/unit/assets/M17_test_baseband.bin", "rb");
|
||||
// Skip preamble
|
||||
fseek(symbols_ref, 0x30, SEEK_SET);
|
||||
uint32_t failed_bytes = 0, total_bytes = 0;
|
||||
syncword = { -1, false };
|
||||
offset = 0;
|
||||
syncword = m17Demodulator.nextFrameSync(offset);
|
||||
std::array< uint8_t, m17Demodulator.M17_FRAME_BYTES > frame;
|
||||
// Preheat quantizer
|
||||
for(int i = 0; i < 10; i++)
|
||||
m17Demodulator.updateQuantizationStats(i);
|
||||
if (syncword.index != -1)
|
||||
{
|
||||
// Next syncword does not overlap with current syncword
|
||||
offset = syncword.index + m17Demodulator.M17_SAMPLES_PER_SYMBOL;
|
||||
// Slice the input buffer to extract a frame and quantize
|
||||
for(uint32_t j = 0;
|
||||
syncword.index + 2 + m17Demodulator.M17_SAMPLES_PER_SYMBOL * (j + i) < m17Demodulator.baseband.len;
|
||||
j+= m17Demodulator.M17_FRAME_SYMBOLS)
|
||||
{
|
||||
for(uint16_t i = 0; i < m17Demodulator.M17_FRAME_SYMBOLS; i++)
|
||||
{
|
||||
// Quantize
|
||||
uint32_t symbol_index = syncword.index + 2 +
|
||||
m17Demodulator.M17_SAMPLES_PER_SYMBOL * (j + i);
|
||||
m17Demodulator.updateQuantizationStats(symbol_index);
|
||||
int8_t symbol = m17Demodulator.quantize(symbol_index);
|
||||
setSymbol<m17Demodulator.M17_FRAME_BYTES>(frame, i, symbol);
|
||||
}
|
||||
for(uint16_t i = 0; i < m17Demodulator.M17_FRAME_BYTES; i+=2) {
|
||||
if (i % 16 == 0)
|
||||
printf("\n");
|
||||
printf(" %02X%02X", frame[i], frame[i+1]);
|
||||
// Check with reference bitstream
|
||||
uint8_t ref_byte = 0x00;
|
||||
fread(&ref_byte, 1, 1, symbols_ref);
|
||||
if (frame[i] != ref_byte)
|
||||
{
|
||||
printf("Mismatch byte #%u!\n", i);
|
||||
failed_bytes++;
|
||||
}
|
||||
fread(&ref_byte, 1, 1, symbols_ref);
|
||||
if (frame[i+1] != ref_byte)
|
||||
{
|
||||
printf("Mismatch byte #%u!\n", i);
|
||||
failed_bytes++;
|
||||
}
|
||||
total_bytes += 1;
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
printf("Failed decoding %d/%d bytes!\n", failed_bytes, total_bytes);
|
||||
|
||||
// TODO: keep aside end of last buffer and use at beginning of the next buffer
|
||||
// TODO: when stream is over pad with zeroes to avoid corrupting the last symbols
|
||||
|
||||
m17Demodulator.baseband = old_baseband;
|
||||
free(baseband_buffer);
|
||||
free(filtered_buffer);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/***************************************************************************
|
||||
* Copyright (C) 2021 by Federico Amedeo Izzo IU2NUO, *
|
||||
* Niccolò Izzo IU2KIN *
|
||||
* Frederik Saraci IU2NRO *
|
||||
* Silvano Seva IU2KWO *
|
||||
* *
|
||||
* *
|
||||
* 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; either 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 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 <limits.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <M17/M17DSP.h>
|
||||
|
||||
#define IMPULSE_SIZE 4096
|
||||
|
||||
using namespace std;
|
||||
|
||||
/**
|
||||
* Test the different demodulation steps
|
||||
*/
|
||||
|
||||
int main()
|
||||
{
|
||||
// Open file
|
||||
FILE *baseband_out = fopen("M17_rrc_impulse_response.raw", "wb");
|
||||
if (!baseband_out)
|
||||
{
|
||||
perror("Error in opening output file");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Allocate impulse signal
|
||||
int16_t impulse[IMPULSE_SIZE] = { 0 };
|
||||
impulse[0] = SHRT_MAX;
|
||||
|
||||
// Apply RRC on impulse signal
|
||||
int16_t filtered_impulse[IMPULSE_SIZE] = { 0 };
|
||||
for(size_t i = 0; i < IMPULSE_SIZE; i++)
|
||||
{
|
||||
float elem = static_cast< float >(impulse[i]);
|
||||
filtered_impulse[i] = static_cast< int16_t >(M17::rrc(0.10 * elem));
|
||||
}
|
||||
fwrite(filtered_impulse, IMPULSE_SIZE, 1, baseband_out);
|
||||
fclose(baseband_out);
|
||||
return 0;
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,500 @@
|
|||
1956
|
||||
3876
|
||||
5797
|
||||
7717
|
||||
9637
|
||||
10718
|
||||
11557
|
||||
13158
|
||||
13477
|
||||
15397
|
||||
17318
|
||||
19237
|
||||
21157
|
||||
23077
|
||||
24997
|
||||
26109
|
||||
26917
|
||||
28837
|
||||
29618
|
||||
30757
|
||||
32677
|
||||
34597
|
||||
36517
|
||||
37258
|
||||
38437
|
||||
40357
|
||||
42277
|
||||
44197
|
||||
46117
|
||||
48037
|
||||
49957
|
||||
51877
|
||||
53797
|
||||
55718
|
||||
57637
|
||||
59557
|
||||
61477
|
||||
63397
|
||||
65317
|
||||
67237
|
||||
69157
|
||||
71077
|
||||
72998
|
||||
74918
|
||||
76837
|
||||
78757
|
||||
80677
|
||||
82597
|
||||
84517
|
||||
86437
|
||||
88357
|
||||
90277
|
||||
92197
|
||||
94117
|
||||
96037
|
||||
97957
|
||||
99877
|
||||
101797
|
||||
103716
|
||||
105637
|
||||
106839
|
||||
107557
|
||||
109477
|
||||
111397
|
||||
113316
|
||||
115237
|
||||
117157
|
||||
119077
|
||||
120997
|
||||
122917
|
||||
124837
|
||||
126757
|
||||
127859
|
||||
128677
|
||||
130597
|
||||
132517
|
||||
134437
|
||||
136357
|
||||
138277
|
||||
140197
|
||||
142117
|
||||
144037
|
||||
145957
|
||||
147878
|
||||
149797
|
||||
151717
|
||||
153637
|
||||
155557
|
||||
157477
|
||||
159397
|
||||
160288
|
||||
161317
|
||||
163237
|
||||
165157
|
||||
167077
|
||||
168997
|
||||
170917
|
||||
172837
|
||||
174757
|
||||
176677
|
||||
178597
|
||||
180377
|
||||
180517
|
||||
182437
|
||||
184357
|
||||
186277
|
||||
188197
|
||||
190118
|
||||
191897
|
||||
192037
|
||||
193957
|
||||
195877
|
||||
197797
|
||||
199717
|
||||
201637
|
||||
203557
|
||||
205477
|
||||
207397
|
||||
209317
|
||||
211237
|
||||
213157
|
||||
215077
|
||||
216998
|
||||
218917
|
||||
220837
|
||||
222757
|
||||
224259
|
||||
224677
|
||||
226597
|
||||
228517
|
||||
230437
|
||||
232357
|
||||
234277
|
||||
236197
|
||||
238117
|
||||
240037
|
||||
241957
|
||||
243877
|
||||
245797
|
||||
247717
|
||||
249499
|
||||
249637
|
||||
251557
|
||||
253477
|
||||
255397
|
||||
257317
|
||||
259237
|
||||
261157
|
||||
263077
|
||||
264997
|
||||
266917
|
||||
268837
|
||||
270337
|
||||
270757
|
||||
272676
|
||||
274597
|
||||
276517
|
||||
278437
|
||||
280357
|
||||
282277
|
||||
284197
|
||||
286117
|
||||
288037
|
||||
289957
|
||||
291877
|
||||
292957
|
||||
293797
|
||||
295717
|
||||
297637
|
||||
299557
|
||||
301477
|
||||
303397
|
||||
305317
|
||||
306937
|
||||
307237
|
||||
309157
|
||||
311077
|
||||
312997
|
||||
314917
|
||||
316837
|
||||
318757
|
||||
320677
|
||||
322597
|
||||
324517
|
||||
326437
|
||||
328279
|
||||
328359
|
||||
330277
|
||||
332197
|
||||
334117
|
||||
336037
|
||||
337957
|
||||
339877
|
||||
341797
|
||||
343717
|
||||
344498
|
||||
345637
|
||||
347557
|
||||
349477
|
||||
351397
|
||||
353317
|
||||
355237
|
||||
357157
|
||||
359077
|
||||
360998
|
||||
362918
|
||||
364837
|
||||
366757
|
||||
368677
|
||||
370597
|
||||
372517
|
||||
374437
|
||||
376356
|
||||
378277
|
||||
380197
|
||||
382117
|
||||
384037
|
||||
385957
|
||||
387877
|
||||
389797
|
||||
391716
|
||||
393637
|
||||
395557
|
||||
397477
|
||||
399397
|
||||
401317
|
||||
403237
|
||||
405157
|
||||
407077
|
||||
407238
|
||||
408997
|
||||
410917
|
||||
412837
|
||||
414757
|
||||
416677
|
||||
418597
|
||||
420517
|
||||
422437
|
||||
424357
|
||||
426277
|
||||
428197
|
||||
430117
|
||||
432037
|
||||
433957
|
||||
435877
|
||||
437797
|
||||
439717
|
||||
441637
|
||||
443558
|
||||
445477
|
||||
447397
|
||||
449317
|
||||
451237
|
||||
453157
|
||||
455077
|
||||
456696
|
||||
456996
|
||||
458917
|
||||
460837
|
||||
462757
|
||||
464677
|
||||
466597
|
||||
468297
|
||||
468517
|
||||
470438
|
||||
472357
|
||||
474277
|
||||
476197
|
||||
478117
|
||||
480037
|
||||
481957
|
||||
483877
|
||||
485797
|
||||
487717
|
||||
489637
|
||||
491557
|
||||
493477
|
||||
495397
|
||||
497317
|
||||
499237
|
||||
501158
|
||||
503077
|
||||
504997
|
||||
506917
|
||||
508837
|
||||
510757
|
||||
512677
|
||||
514597
|
||||
516517
|
||||
518437
|
||||
520357
|
||||
522278
|
||||
524197
|
||||
526117
|
||||
527028
|
||||
528037
|
||||
529957
|
||||
531877
|
||||
533797
|
||||
535717
|
||||
537637
|
||||
539557
|
||||
541477
|
||||
543397
|
||||
545318
|
||||
547237
|
||||
549157
|
||||
551077
|
||||
552997
|
||||
554917
|
||||
556837
|
||||
558757
|
||||
560677
|
||||
562597
|
||||
564517
|
||||
566437
|
||||
568357
|
||||
570277
|
||||
572197
|
||||
574117
|
||||
576037
|
||||
577957
|
||||
579877
|
||||
581797
|
||||
583717
|
||||
585637
|
||||
587557
|
||||
589477
|
||||
591397
|
||||
593317
|
||||
595237
|
||||
597157
|
||||
599077
|
||||
600997
|
||||
602917
|
||||
604837
|
||||
606757
|
||||
608676
|
||||
610597
|
||||
612517
|
||||
614437
|
||||
616358
|
||||
618277
|
||||
620197
|
||||
622117
|
||||
624037
|
||||
625957
|
||||
627457
|
||||
627877
|
||||
629797
|
||||
631717
|
||||
633637
|
||||
635557
|
||||
637477
|
||||
639397
|
||||
639599
|
||||
641317
|
||||
643237
|
||||
645157
|
||||
647077
|
||||
648997
|
||||
650917
|
||||
652837
|
||||
654757
|
||||
656677
|
||||
658597
|
||||
660516
|
||||
662437
|
||||
664357
|
||||
666277
|
||||
668197
|
||||
670117
|
||||
672037
|
||||
673957
|
||||
675878
|
||||
677797
|
||||
679716
|
||||
681637
|
||||
683557
|
||||
685477
|
||||
687397
|
||||
689316
|
||||
691236
|
||||
693157
|
||||
695078
|
||||
696996
|
||||
698917
|
||||
700837
|
||||
702757
|
||||
704677
|
||||
706597
|
||||
708517
|
||||
708649
|
||||
709738
|
||||
710437
|
||||
712357
|
||||
714277
|
||||
716197
|
||||
718117
|
||||
720037
|
||||
721957
|
||||
723877
|
||||
725797
|
||||
727717
|
||||
729637
|
||||
731558
|
||||
733477
|
||||
735397
|
||||
737317
|
||||
739237
|
||||
741157
|
||||
743077
|
||||
744997
|
||||
746917
|
||||
748837
|
||||
750756
|
||||
752677
|
||||
754597
|
||||
756517
|
||||
758437
|
||||
760357
|
||||
762276
|
||||
764197
|
||||
766117
|
||||
768037
|
||||
769958
|
||||
771878
|
||||
773797
|
||||
775717
|
||||
777637
|
||||
779556
|
||||
781477
|
||||
783397
|
||||
785317
|
||||
787237
|
||||
789157
|
||||
791077
|
||||
792997
|
||||
794917
|
||||
796837
|
||||
798757
|
||||
800677
|
||||
802597
|
||||
804517
|
||||
806437
|
||||
808357
|
||||
810277
|
||||
812198
|
||||
814117
|
||||
816037
|
||||
817957
|
||||
819877
|
||||
821797
|
||||
823717
|
||||
825637
|
||||
827557
|
||||
829477
|
||||
831397
|
||||
833317
|
||||
835237
|
||||
837157
|
||||
838878
|
||||
839077
|
||||
840997
|
||||
842917
|
||||
844837
|
||||
846757
|
||||
848677
|
||||
850597
|
||||
852517
|
||||
854437
|
||||
856357
|
||||
858277
|
||||
860197
|
||||
862118
|
||||
864037
|
||||
865958
|
||||
867876
|
||||
869797
|
||||
871717
|
||||
873637
|
||||
875557
|
||||
877477
|
||||
879397
|
||||
881317
|
||||
883237
|
||||
885157
|
||||
887077
|
||||
888997
|
||||
890917
|
||||
892837
|
||||
894757
|
||||
896677
|
||||
898597
|
||||
900517
|
||||
902438
|
||||
904357
|
||||
905688
|
||||
906276
|
||||
-1
|
||||
Loading…
Reference in New Issue