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/M17DSP.cpp',
|
||||||
'openrtx/src/protocols/M17/M17Callsign.cpp',
|
'openrtx/src/protocols/M17/M17Callsign.cpp',
|
||||||
'openrtx/src/protocols/M17/M17Modulator.cpp',
|
'openrtx/src/protocols/M17/M17Modulator.cpp',
|
||||||
|
'openrtx/src/protocols/M17/M17Demodulator.cpp',
|
||||||
'openrtx/src/protocols/M17/M17Transmitter.cpp',
|
'openrtx/src/protocols/M17/M17Transmitter.cpp',
|
||||||
'openrtx/src/protocols/M17/M17LinkSetupFrame.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'],
|
sources : unit_test_src + ['tests/unit/M17_viterbi.cpp'],
|
||||||
kwargs : unit_test_opts)
|
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 Golay Unit Test', m17_golay_test)
|
||||||
test('M17 Viterbi Unit Test', m17_viterbi_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!
|
#error This header is C++ only!
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <interfaces/audio_stream.h>
|
||||||
|
#include <hwconfig.h>
|
||||||
|
|
||||||
namespace M17
|
namespace M17
|
||||||
{
|
{
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t index;
|
||||||
|
bool lsf;
|
||||||
|
} sync_t;
|
||||||
|
|
||||||
class M17Demodulator
|
class M17Demodulator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -65,34 +76,138 @@ public:
|
||||||
void stopBasebandSampling();
|
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:
|
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.
|
int16_t *baseband_buffer; ///< Buffer for baseband audio handling.
|
||||||
dataBuffer_t *activeBuffer; ///< Half baseband buffer, in reception.
|
dataBlock_t baseband; ///< Half buffer, free to be processed.
|
||||||
dataBuffer_t *idleBuffer; ///< Half baseband buffer, 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 *activeFrame; ///< Half frame, in demodulation.
|
||||||
dataFrame_t *idleFrame; ///< Half frame, free to be processed.
|
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 baseband: buffer containing the sampled baseband signal
|
||||||
* @param offset: offset of the buffer after which syncword are searched
|
* @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
|
* @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 */
|
#endif /* M17_DEMODULATOR_H */
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <array>
|
#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);
|
(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 */
|
#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 = {
|
const std::array<float, 79> rrc_taps = {
|
||||||
-0.009265784007800534, -0.006136551625729697, -0.001125978562075172,
|
-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_EN GPIOD,8
|
||||||
#define GPS_DATA GPIOD,9
|
#define GPS_DATA GPIOD,9
|
||||||
|
|
||||||
|
/* M17 demodulation */
|
||||||
|
#define M17_SPS 5
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -83,4 +83,7 @@
|
||||||
#define SOFTPOT_RX 0x2E
|
#define SOFTPOT_RX 0x2E
|
||||||
#define SOFTPOT_TX 0x2F
|
#define SOFTPOT_TX 0x2F
|
||||||
|
|
||||||
|
/* M17 demodulation */
|
||||||
|
#define M17_SPS 5
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -54,3 +54,6 @@
|
||||||
|
|
||||||
/* Push-to-talk switch */
|
/* Push-to-talk switch */
|
||||||
#define PTT_SW "PTT_SW",11
|
#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