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:
Niccolò Izzo 2021-12-22 21:00:55 +01:00 committed by Silvano Seva
parent 396f66a1f3
commit 3163dd49d7
15 changed files with 1256 additions and 13 deletions

View File

@ -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)

View File

@ -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 */

View File

@ -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 */

View File

@ -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,

View File

@ -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 */

View File

@ -139,4 +139,7 @@
#define GPS_EN GPIOD,8
#define GPS_DATA GPIOD,9
/* M17 demodulation */
#define M17_SPS 5
#endif

View File

@ -83,4 +83,7 @@
#define SOFTPOT_RX 0x2E
#define SOFTPOT_TX 0x2F
/* M17 demodulation */
#define M17_SPS 5
#endif

View File

@ -54,3 +54,6 @@
/* Push-to-talk switch */
#define PTT_SW "PTT_SW",11
/* M17 demodulation */
#define M17_SPS 10

14
scripts/plot_m17_demod_csv.py Executable file
View File

@ -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()

14
scripts/plot_m17_demod_csv2.py Executable file
View File

@ -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()

View File

@ -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;
}

59
tests/unit/M17_rrc.cpp Normal file
View File

@ -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.

View File

@ -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