diff --git a/meson.build b/meson.build index 37b8bc06..344c0abc 100644 --- a/meson.build +++ b/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) diff --git a/openrtx/include/protocols/M17/M17Demodulator.h b/openrtx/include/protocols/M17/M17Demodulator.h index d20e438c..cadcbeab 100644 --- a/openrtx/include/protocols/M17/M17Demodulator.h +++ b/openrtx/include/protocols/M17/M17Demodulator.h @@ -27,9 +27,20 @@ #error This header is C++ only! #endif +#include +#include +#include +#include +#include + 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& nextFrame(); + const std::array &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 */ diff --git a/openrtx/include/protocols/M17/M17Utils.h b/openrtx/include/protocols/M17/M17Utils.h index 8fa24024..1266e7de 100644 --- a/openrtx/include/protocols/M17/M17Utils.h +++ b/openrtx/include/protocols/M17/M17Utils.h @@ -29,6 +29,7 @@ #include #include #include +#include /** @@ -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 (array, 2 * pos , 0); + setBit (array, 2 * pos + 1, 1); + break; + case +1: + setBit (array, 2 * pos , 0); + setBit (array, 2 * pos + 1, 0); + break; + case -1: + setBit (array, 2 * pos , 1); + setBit (array, 2 * pos + 1, 0); + break; + case -3: + setBit (array, 2 * pos , 1); + setBit (array, 2 * pos + 1, 1); + break; + default: + assert("Error: storing unknown M17 symbol!"); + } +} + #endif /* M17_UTILS_H */ diff --git a/openrtx/src/protocols/M17/M17DSP.cpp b/openrtx/src/protocols/M17/M17DSP.cpp index c719fb4c..cda8d40e 100644 --- a/openrtx/src/protocols/M17/M17DSP.cpp +++ b/openrtx/src/protocols/M17/M17DSP.cpp @@ -24,7 +24,7 @@ namespace M17 { /* - * Coefficients for M17 RRC filter + * Coefficients for M17 RRC filter for 48KHz sample rate */ const std::array rrc_taps = { -0.009265784007800534, -0.006136551625729697, -0.001125978562075172, diff --git a/openrtx/src/protocols/M17/M17Demodulator.cpp b/openrtx/src/protocols/M17/M17Demodulator.cpp new file mode 100644 index 00000000..afbefd50 --- /dev/null +++ b/openrtx/src/protocols/M17/M17Demodulator.cpp @@ -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 * + ***************************************************************************/ + +#include +#include +#include +#include +#include + +#if defined(PLATFORM_MD3x0) || defined(PLATFORM_MDUV3x0) +#include +#elif defined(PLATFORM_LINUX) +#include +#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 &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(*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 */ diff --git a/platform/targets/MD-3x0/hwconfig.h b/platform/targets/MD-3x0/hwconfig.h index b4f44814..2de8d66f 100644 --- a/platform/targets/MD-3x0/hwconfig.h +++ b/platform/targets/MD-3x0/hwconfig.h @@ -139,4 +139,7 @@ #define GPS_EN GPIOD,8 #define GPS_DATA GPIOD,9 +/* M17 demodulation */ +#define M17_SPS 5 + #endif diff --git a/platform/targets/Module17/hwconfig.h b/platform/targets/Module17/hwconfig.h index e92102e3..722091cc 100644 --- a/platform/targets/Module17/hwconfig.h +++ b/platform/targets/Module17/hwconfig.h @@ -83,4 +83,7 @@ #define SOFTPOT_RX 0x2E #define SOFTPOT_TX 0x2F +/* M17 demodulation */ +#define M17_SPS 5 + #endif diff --git a/platform/targets/linux/hwconfig.h b/platform/targets/linux/hwconfig.h index 1ebc2fa3..12d0b97d 100644 --- a/platform/targets/linux/hwconfig.h +++ b/platform/targets/linux/hwconfig.h @@ -54,3 +54,6 @@ /* Push-to-talk switch */ #define PTT_SW "PTT_SW",11 + +/* M17 demodulation */ +#define M17_SPS 10 diff --git a/scripts/plot_m17_demod_csv.py b/scripts/plot_m17_demod_csv.py new file mode 100755 index 00000000..38fd376d --- /dev/null +++ b/scripts/plot_m17_demod_csv.py @@ -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() diff --git a/scripts/plot_m17_demod_csv2.py b/scripts/plot_m17_demod_csv2.py new file mode 100755 index 00000000..ff85ec46 --- /dev/null +++ b/scripts/plot_m17_demod_csv2.py @@ -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() diff --git a/tests/unit/M17_demodulator.cpp b/tests/unit/M17_demodulator.cpp new file mode 100644 index 00000000..39c04de2 --- /dev/null +++ b/tests/unit/M17_demodulator.cpp @@ -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 * + ***************************************************************************/ + +// Test private methods +#define private public + +#include +#include +#include +#include +#include +#include +#include +#include + +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(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; +} diff --git a/tests/unit/M17_rrc.cpp b/tests/unit/M17_rrc.cpp new file mode 100644 index 00000000..2f838ced --- /dev/null +++ b/tests/unit/M17_rrc.cpp @@ -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 * + ***************************************************************************/ + +#include +#include +#include +#include + +#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; +} diff --git a/tests/unit/assets/M17_test_baseband.raw b/tests/unit/assets/M17_test_baseband.raw new file mode 100644 index 00000000..751bd9fa Binary files /dev/null and b/tests/unit/assets/M17_test_baseband.raw differ diff --git a/tests/unit/assets/M17_test_baseband_short.raw b/tests/unit/assets/M17_test_baseband_short.raw new file mode 100644 index 00000000..b9d556a1 Binary files /dev/null and b/tests/unit/assets/M17_test_baseband_short.raw differ diff --git a/tests/unit/assets/M17_test_baseband_syncwords.txt b/tests/unit/assets/M17_test_baseband_syncwords.txt new file mode 100644 index 00000000..06f9c074 --- /dev/null +++ b/tests/unit/assets/M17_test_baseband_syncwords.txt @@ -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