Modified Costas loop; descramble sync data before unmapping
This commit is contained in:
parent
cfbbacdd0f
commit
5d097d7df8
@ -40,7 +40,7 @@ public:
|
|||||||
* @param data The input data stream to be transmitted. The `is_voice` parameter controls whether the modem treats it as binary file data,
|
* @param data The input data stream to be transmitted. The `is_voice` parameter controls whether the modem treats it as binary file data,
|
||||||
* or a binary stream from the MELPe (or other) voice codec.
|
* or a binary stream from the MELPe (or other) voice codec.
|
||||||
*/
|
*/
|
||||||
ModemController(const size_t _baud_rate, const bool _is_voice, const bool _is_frequency_hopping, const size_t _interleave_setting, BitStream _data)
|
ModemController(const size_t _baud_rate, const bool _is_voice, const bool _is_frequency_hopping, const size_t _interleave_setting)
|
||||||
: baud_rate(_baud_rate),
|
: baud_rate(_baud_rate),
|
||||||
is_voice(_is_voice),
|
is_voice(_is_voice),
|
||||||
is_frequency_hopping(_is_frequency_hopping),
|
is_frequency_hopping(_is_frequency_hopping),
|
||||||
@ -49,7 +49,6 @@ public:
|
|||||||
scrambler(),
|
scrambler(),
|
||||||
fec_encoder(_baud_rate, _is_frequency_hopping),
|
fec_encoder(_baud_rate, _is_frequency_hopping),
|
||||||
interleaver(_baud_rate, _interleave_setting, _is_frequency_hopping),
|
interleaver(_baud_rate, _interleave_setting, _is_frequency_hopping),
|
||||||
input_data(std::move(_data)),
|
|
||||||
mgd_decoder(_baud_rate, _is_frequency_hopping),
|
mgd_decoder(_baud_rate, _is_frequency_hopping),
|
||||||
modulator(48000, _is_frequency_hopping, 48) {}
|
modulator(48000, _is_frequency_hopping, 48) {}
|
||||||
|
|
||||||
@ -58,7 +57,7 @@ public:
|
|||||||
* @return The scrambled data ready for modulation.
|
* @return The scrambled data ready for modulation.
|
||||||
* @note The modulated signal is generated internally but is intended to be handled externally.
|
* @note The modulated signal is generated internally but is intended to be handled externally.
|
||||||
*/
|
*/
|
||||||
std::vector<int16_t> transmit() {
|
std::vector<int16_t> transmit(BitStream input_data) {
|
||||||
// Step 1: Append EOM Symbols
|
// Step 1: Append EOM Symbols
|
||||||
BitStream eom_appended_data = appendEOMSymbols(input_data);
|
BitStream eom_appended_data = appendEOMSymbols(input_data);
|
||||||
|
|
||||||
@ -84,11 +83,17 @@ public:
|
|||||||
return modulated_signal;
|
return modulated_signal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BitStream receive(const std::vector<int16_t>& passband_signal) {
|
||||||
|
// Step one: Demodulate the passband signal and retrieve decoded symbols
|
||||||
|
std::vector<uint8_t> demodulated_symbols = modulator.demodulate(passband_signal, baud_rate, interleave_setting, is_voice);
|
||||||
|
|
||||||
|
return BitStream();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
size_t baud_rate; ///< The baud rate for the modem.
|
size_t baud_rate; ///< The baud rate for the modem.
|
||||||
bool is_voice; ///< Indicates if the data being transmitted is voice.
|
bool is_voice; ///< Indicates if the data being transmitted is voice.
|
||||||
bool is_frequency_hopping; ///< Indicates if frequency hopping is used.
|
bool is_frequency_hopping; ///< Indicates if frequency hopping is used.
|
||||||
BitStream input_data; ///< The input data stream.
|
|
||||||
size_t interleave_setting; ///< The interleave setting to be used.
|
size_t interleave_setting; ///< The interleave setting to be used.
|
||||||
size_t sample_rate;
|
size_t sample_rate;
|
||||||
|
|
||||||
|
@ -15,14 +15,14 @@ public:
|
|||||||
/**
|
/**
|
||||||
* @brief Constructor initializes the scrambler with a predefined register value.
|
* @brief Constructor initializes the scrambler with a predefined register value.
|
||||||
*/
|
*/
|
||||||
Scrambler() : data_sequence_register(0x0BAD), symbol_count(0) {}
|
Scrambler() : data_sequence_register(0x0BAD), symbol_count(0), preamble_table_index(0) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Scrambles a synchronization preamble using a fixed randomizer sequence.
|
* @brief Scrambles a synchronization preamble using a fixed randomizer sequence.
|
||||||
* @param preamble The synchronization preamble to scramble.
|
* @param preamble The synchronization preamble to scramble.
|
||||||
* @return The scrambled synchronization preamble.
|
* @return The scrambled synchronization preamble.
|
||||||
*/
|
*/
|
||||||
std::vector<uint8_t> scrambleSyncPreamble(const std::vector<uint8_t>& preamble) const {
|
std::vector<uint8_t> scrambleSyncPreamble(const std::vector<uint8_t>& preamble) {
|
||||||
static const std::array<uint8_t, 32> sync_randomizer_sequence = {
|
static const std::array<uint8_t, 32> sync_randomizer_sequence = {
|
||||||
7, 4, 3, 0, 5, 1, 5, 0, 2, 2, 1, 1,
|
7, 4, 3, 0, 5, 1, 5, 0, 2, 2, 1, 1,
|
||||||
5, 7, 4, 3, 5, 0, 2, 6, 2, 1, 6, 2,
|
5, 7, 4, 3, 5, 0, 2, 6, 2, 1, 6, 2,
|
||||||
@ -33,8 +33,9 @@ public:
|
|||||||
scrambled_preamble.reserve(preamble.size()); // Preallocate to improve efficiency
|
scrambled_preamble.reserve(preamble.size()); // Preallocate to improve efficiency
|
||||||
|
|
||||||
for (size_t i = 0; i < preamble.size(); ++i) {
|
for (size_t i = 0; i < preamble.size(); ++i) {
|
||||||
uint8_t scrambled_value = (preamble[i] + sync_randomizer_sequence[i % sync_randomizer_sequence.size()]) % 8;
|
uint8_t scrambled_value = (preamble[i] + sync_randomizer_sequence[preamble_table_index]) % 8;
|
||||||
scrambled_preamble.push_back(scrambled_value);
|
scrambled_preamble.push_back(scrambled_value);
|
||||||
|
preamble_table_index = (preamble_table_index + 1) % sync_randomizer_sequence.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
return scrambled_preamble;
|
return scrambled_preamble;
|
||||||
@ -61,6 +62,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
uint16_t data_sequence_register;
|
uint16_t data_sequence_register;
|
||||||
size_t symbol_count;
|
size_t symbol_count;
|
||||||
|
size_t preamble_table_index;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Generates the next value from the data sequence randomizing generator.
|
* @brief Generates the next value from the data sequence randomizing generator.
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <complex>
|
#include <complex>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@ -10,9 +11,12 @@
|
|||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <fftw3.h>
|
#include <fftw3.h>
|
||||||
|
#include <map>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
#include "costasloop.h"
|
#include "costasloop.h"
|
||||||
#include "filters.h"
|
#include "filters.h"
|
||||||
|
#include "Scrambler.h"
|
||||||
|
|
||||||
static constexpr double CARRIER_FREQ = 1800.0;
|
static constexpr double CARRIER_FREQ = 1800.0;
|
||||||
static constexpr size_t SYMBOL_RATE = 2400;
|
static constexpr size_t SYMBOL_RATE = 2400;
|
||||||
@ -77,9 +81,9 @@ public:
|
|||||||
return final_signal;
|
return final_signal;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> demodulate(const std::vector<int16_t> passband_signal) {
|
std::vector<uint8_t> demodulate(const std::vector<int16_t> passband_signal, size_t& baud_rate, size_t& interleave_setting, bool& is_voice) {
|
||||||
// Carrier recovery. initialize the Costas loop.
|
// Carrier recovery. initialize the Costas loop.
|
||||||
CostasLoop costas_loop(CARRIER_FREQ, sample_rate, symbolMap, 5.0);
|
CostasLoop costas_loop(CARRIER_FREQ, sample_rate, symbolMap, 5.0, 0.05, 0.01);
|
||||||
|
|
||||||
// Convert passband signal to doubles.
|
// Convert passband signal to doubles.
|
||||||
std::vector<double> normalized_passband(passband_signal.size());
|
std::vector<double> normalized_passband(passband_signal.size());
|
||||||
@ -89,23 +93,30 @@ public:
|
|||||||
|
|
||||||
// Downmix passband to baseband
|
// Downmix passband to baseband
|
||||||
std::vector<std::complex<double>> baseband_IQ = costas_loop.process(normalized_passband);
|
std::vector<std::complex<double>> baseband_IQ = costas_loop.process(normalized_passband);
|
||||||
|
std::vector<uint8_t> detected_symbols;
|
||||||
|
|
||||||
// Phase detection and symbol formation
|
// Phase detection and symbol formation
|
||||||
std::vector<uint8_t> baseband_symbols;
|
|
||||||
size_t samples_per_symbol = sample_rate / SYMBOL_RATE;
|
size_t samples_per_symbol = sample_rate / SYMBOL_RATE;
|
||||||
|
bool sync_found = false;
|
||||||
|
size_t sync_segments_detected;
|
||||||
|
|
||||||
|
size_t window_size = 32*15;
|
||||||
|
|
||||||
for (size_t i = 0; i < baseband_IQ.size(); i += samples_per_symbol) {
|
for (size_t i = 0; i < baseband_IQ.size(); i += samples_per_symbol) {
|
||||||
std::complex<double> symbol_avg(0.0, 0.0);
|
std::complex<double> symbol_avg(0.0, 0.0);
|
||||||
for (size_t j = 0; j < samples_per_symbol; ++j) {
|
for (size_t j = 0; j < samples_per_symbol; j++) {
|
||||||
symbol_avg += baseband_IQ[i + j];
|
symbol_avg += baseband_IQ[i + j];
|
||||||
}
|
}
|
||||||
symbol_avg /= static_cast<double>(samples_per_symbol);
|
symbol_avg /= static_cast<double>(samples_per_symbol);
|
||||||
|
|
||||||
// Detect symbol from averaged signal
|
uint8_t detected_symbol = phase_detector.getSymbol(symbol_avg);
|
||||||
baseband_symbols.emplace_back(phase_detector.getSymbol(symbol_avg));
|
detected_symbols.push_back(detected_symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (processSyncSegments(detected_symbols, baud_rate, interleave_setting, is_voice)) {
|
||||||
|
return processDataSymbols(detected_symbols);
|
||||||
}
|
}
|
||||||
|
|
||||||
return baseband_symbols;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -137,6 +148,220 @@ private:
|
|||||||
{gain * std::cos(2.0*M_PI*(7.0/8.0)), gain * std::sin(2.0*M_PI*(7.0/8.0))} // 7 (111) corresponds to I = cos(315), Q = sin(315)
|
{gain * std::cos(2.0*M_PI*(7.0/8.0)), gain * std::sin(2.0*M_PI*(7.0/8.0))} // 7 (111) corresponds to I = cos(315), Q = sin(315)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t extractBestTribit(const std::vector<uint8_t>& stream, const size_t start, const size_t window_size) const {
|
||||||
|
if (start + window_size > stream.size()) {
|
||||||
|
throw std::out_of_range("Window size exceeds symbol stream size.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Scrambler scrambler;
|
||||||
|
std::vector<uint8_t> symbol(stream.begin() + start, stream.begin() + start + window_size);
|
||||||
|
std::vector<uint8_t> descrambled_symbol = scrambler.scrambleSyncPreamble(symbol);
|
||||||
|
|
||||||
|
const size_t split_len = window_size / 4;
|
||||||
|
std::array<uint8_t, 8> tribit_counts = {0}; // Counts for each channel symbol (000 to 111)
|
||||||
|
|
||||||
|
// Loop through each split segment (4 segments)
|
||||||
|
for (size_t i = 0; i < 4; ++i) {
|
||||||
|
// Extract the range for this split
|
||||||
|
size_t segment_start = start + i * split_len;
|
||||||
|
size_t segment_end = segment_start + split_len;
|
||||||
|
|
||||||
|
// Compare this segment to the predefined patterns from the table and map to a channel symbol
|
||||||
|
uint8_t tribit_value = mapSegmentToChannelSymbol(descrambled_symbol, segment_start, segment_end);
|
||||||
|
|
||||||
|
// Increment the corresponding channel symbol count
|
||||||
|
tribit_counts[tribit_value]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the channel symbol with the highest count (majority vote)
|
||||||
|
uint8_t best_symbol = std::distance(tribit_counts.begin(), std::max_element(tribit_counts.begin(), tribit_counts.end()));
|
||||||
|
|
||||||
|
return best_symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to map a segment of the stream back to a channel symbol based on the repeating patterns
|
||||||
|
uint8_t mapSegmentToChannelSymbol(const std::vector<uint8_t>& segment, size_t start, size_t end) const {
|
||||||
|
std::vector<uint8_t> extracted_pattern(segment.begin() + start, segment.begin() + end);
|
||||||
|
|
||||||
|
// Compare the extracted pattern with known patterns from the table
|
||||||
|
if (matchesPattern(extracted_pattern, {0, 0, 0, 0, 0, 0, 0, 0})) return 0b000;
|
||||||
|
if (matchesPattern(extracted_pattern, {0, 4, 0, 4, 0, 4, 0, 4})) return 0b001;
|
||||||
|
if (matchesPattern(extracted_pattern, {0, 0, 4, 4, 0, 0, 4, 4})) return 0b010;
|
||||||
|
if (matchesPattern(extracted_pattern, {0, 4, 4, 0, 0, 4, 4, 0})) return 0b011;
|
||||||
|
if (matchesPattern(extracted_pattern, {0, 0, 0, 0, 4, 4, 4, 4})) return 0b100;
|
||||||
|
if (matchesPattern(extracted_pattern, {0, 4, 0, 4, 4, 0, 4, 0})) return 0b101;
|
||||||
|
if (matchesPattern(extracted_pattern, {0, 0, 4, 4, 4, 4, 0, 0})) return 0b110;
|
||||||
|
if (matchesPattern(extracted_pattern, {0, 4, 4, 0, 4, 0, 0, 4})) return 0b111;
|
||||||
|
|
||||||
|
throw std::invalid_argument("Invalid segment pattern");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to compare two patterns
|
||||||
|
bool matchesPattern(const std::vector<uint8_t>& segment, const std::vector<uint8_t>& pattern) const {
|
||||||
|
return std::equal(segment.begin(), segment.end(), pattern.begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool configureModem(uint8_t D1, uint8_t D2, size_t& baud_rate, size_t& interleave_setting, bool& is_voice) {
|
||||||
|
// Predefine all the valid combinations in a lookup map
|
||||||
|
static const std::map<std::pair<uint8_t, uint8_t>, std::tuple<size_t, size_t, bool>> modemConfig = {
|
||||||
|
{{7, 6}, {4800, 1, false}}, // 4800 bps
|
||||||
|
{{7, 7}, {2400, 1, true}}, // 2400 bps, voice
|
||||||
|
{{6, 4}, {2400, 1, false}}, // 2400 bps, data
|
||||||
|
{{6, 5}, {1200, 1, false}}, // 1200 bps
|
||||||
|
{{6, 6}, {600, 1, false}}, // 600 bps
|
||||||
|
{{6, 7}, {300, 1, false}}, // 300 bps
|
||||||
|
{{7, 4}, {150, 1, false}}, // 150 bps
|
||||||
|
{{7, 5}, {75, 1, false}}, // 75 bps
|
||||||
|
{{4, 4}, {2400, 2, false}}, // 2400 bps, long interleave
|
||||||
|
{{4, 5}, {1200, 2, false}}, // 1200 bps, long interleave
|
||||||
|
{{4, 6}, {600, 2, false}}, // 600 bps, long interleave
|
||||||
|
{{4, 7}, {300, 2, false}}, // 300 bps, long interleave
|
||||||
|
{{5, 4}, {150, 2, false}}, // 150 bps, long interleave
|
||||||
|
{{5, 5}, {75, 2, false}}, // 75 bps, long interleave
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use D1 and D2 to look up the correct configuration
|
||||||
|
auto it = modemConfig.find({D1, D2});
|
||||||
|
if (it != modemConfig.end()) {
|
||||||
|
// Set the parameters if found
|
||||||
|
std::tie(baud_rate, interleave_setting, is_voice) = it->second;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t calculateSegmentCount(const uint8_t C1, const uint8_t C2, const uint8_t C3) {
|
||||||
|
uint8_t extracted_C1 = C1 & 0b11;
|
||||||
|
uint8_t extracted_C2 = C2 & 0b11;
|
||||||
|
uint8_t extracted_C3 = C3 & 0b11;
|
||||||
|
|
||||||
|
uint8_t segment_count = (extracted_C1 << 4) | (extracted_C2 << 2) | extracted_C3;
|
||||||
|
return segment_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool processSegment(const std::vector<uint8_t>& detected_symbols, size_t& start, size_t symbol_size, size_t& segment_count, uint8_t& D1, uint8_t& D2) {
|
||||||
|
size_t sync_pattern_length = 9;
|
||||||
|
|
||||||
|
if (start + symbol_size * sync_pattern_length > detected_symbols.size()) {
|
||||||
|
start = detected_symbols.size();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> window(detected_symbols.begin() + start, detected_symbols.begin() + start + sync_pattern_length * symbol_size);
|
||||||
|
std::vector<uint8_t> extracted_window;
|
||||||
|
for (size_t i = 0; i < sync_pattern_length; i++) {
|
||||||
|
extracted_window.push_back(extractBestTribit(window, i * symbol_size, symbol_size));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matchesPattern(extracted_window, {0, 1, 3, 0, 1, 3, 1, 2, 0})) {
|
||||||
|
start += symbol_size;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
start += sync_pattern_length * symbol_size;
|
||||||
|
size_t D1_index = start + symbol_size;
|
||||||
|
size_t D2_index = D1_index + symbol_size;
|
||||||
|
|
||||||
|
if (D2_index + symbol_size > detected_symbols.size()) {
|
||||||
|
start = detected_symbols.size();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
D1 = extractBestTribit(detected_symbols, D1_index, symbol_size);
|
||||||
|
D2 = extractBestTribit(detected_symbols, D2_index, symbol_size);
|
||||||
|
|
||||||
|
// Process the count symbols (C1, C2, C3)
|
||||||
|
size_t C1_index = D2_index + symbol_size;
|
||||||
|
size_t C2_index = C1_index + symbol_size;
|
||||||
|
size_t C3_index = C2_index + symbol_size;
|
||||||
|
|
||||||
|
if (C3_index + symbol_size > detected_symbols.size()) {
|
||||||
|
start = detected_symbols.size();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t C1 = extractBestTribit(detected_symbols, C1_index, symbol_size);
|
||||||
|
uint8_t C2 = extractBestTribit(detected_symbols, C2_index, symbol_size);
|
||||||
|
uint8_t C3 = extractBestTribit(detected_symbols, C3_index, symbol_size);
|
||||||
|
|
||||||
|
segment_count = calculateSegmentCount(C1, C2, C3);
|
||||||
|
|
||||||
|
// Check for the constant zero pattern
|
||||||
|
size_t constant_zero_index = C3_index + symbol_size;
|
||||||
|
|
||||||
|
if (constant_zero_index + symbol_size > detected_symbols.size()) {
|
||||||
|
start = detected_symbols.size();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint8_t constant_zero = extractBestTribit(detected_symbols, constant_zero_index, symbol_size);
|
||||||
|
|
||||||
|
if (constant_zero != 0) {
|
||||||
|
start = constant_zero_index + symbol_size;
|
||||||
|
return false; // Failed zero check, resync
|
||||||
|
}
|
||||||
|
|
||||||
|
// Successfully processed the segment
|
||||||
|
start = constant_zero_index + symbol_size; // Move start to next segment
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool processSyncSegments(const std::vector<uint8_t>& detected_symbols, size_t& baud_rate, size_t& interleave_setting, bool& is_voice) {
|
||||||
|
size_t symbol_size = 32;
|
||||||
|
size_t start = 0;
|
||||||
|
size_t segment_count = 0;
|
||||||
|
std::map<std::pair<uint8_t, uint8_t>, int> vote_map;
|
||||||
|
const int short_interleave_threshold = 2;
|
||||||
|
const int long_interleave_threshold = 5;
|
||||||
|
|
||||||
|
// Attempt to detect interleave setting dynamically
|
||||||
|
bool interleave_detected = false;
|
||||||
|
int current_threshold = short_interleave_threshold; // Start by assuming short interleave
|
||||||
|
|
||||||
|
while (start + symbol_size * 15 < detected_symbols.size()) {
|
||||||
|
uint8_t D1 = 0, D2 = 0;
|
||||||
|
if (processSegment(detected_symbols, start, symbol_size, segment_count, D1, D2)) {
|
||||||
|
vote_map[{D1, D2}]++;
|
||||||
|
|
||||||
|
// Check if we have enough votes to make a decision based on current interleave assumption
|
||||||
|
if (vote_map.size() >= current_threshold) {
|
||||||
|
auto majority_vote = std::max_element(vote_map.begin(), vote_map.end(), [](const auto& a, const auto& b) { return a.second < b.second; });
|
||||||
|
|
||||||
|
if (configureModem(majority_vote->first.first, majority_vote->first.second, baud_rate, interleave_setting, is_voice)) {
|
||||||
|
interleave_detected = true;
|
||||||
|
break; // Successfully configured modem, exit loop
|
||||||
|
} else {
|
||||||
|
// If configuration fails, retry with the other interleave type
|
||||||
|
if (current_threshold == short_interleave_threshold) {
|
||||||
|
current_threshold = long_interleave_threshold; // Switch to long interleave
|
||||||
|
vote_map.clear(); // Clear the vote map and start fresh
|
||||||
|
start = 0; // Restart segment processing
|
||||||
|
} else {
|
||||||
|
continue; // Both short and long interleave attempts failed, signal is not usable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (segment_count > 0) {
|
||||||
|
while (segment_count > 0 && start < detected_symbols.size()) {
|
||||||
|
uint8_t dummy_D1, dummy_D2;
|
||||||
|
if (!processSegment(detected_symbols, start, symbol_size, segment_count, dummy_D1, dummy_D2)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
start += symbol_size; // Move to the next segment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return interleave_detected;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> processDataSymbols(const std::vector<uint8_t>& detected_symbols) {
|
||||||
|
return std::vector<uint8_t>();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
@ -19,7 +19,7 @@ public:
|
|||||||
/**
|
/**
|
||||||
* @brief Default constructor.
|
* @brief Default constructor.
|
||||||
*/
|
*/
|
||||||
BitStream() : bit_index(0), max_bit_idx(0) {}
|
BitStream() : std::vector<uint8_t>(), bit_index(0), max_bit_idx(0) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Constructs a BitStream from an existing vector of bytes.
|
* @brief Constructs a BitStream from an existing vector of bytes.
|
||||||
|
@ -39,8 +39,8 @@ private:
|
|||||||
|
|
||||||
class CostasLoop {
|
class CostasLoop {
|
||||||
public:
|
public:
|
||||||
CostasLoop(const double _carrier_freq, const double _sample_rate, const std::vector<std::complex<double>>& _symbolMap, const double _vco_gain)
|
CostasLoop(const double _carrier_freq, const double _sample_rate, const std::vector<std::complex<double>>& _symbolMap, const double _vco_gain, const double _alpha, const double _beta)
|
||||||
: carrier_freq(_carrier_freq), sample_rate(_sample_rate), vco_gain(_vco_gain), k_factor(-1 / (_sample_rate * _vco_gain)),
|
: carrier_freq(_carrier_freq), sample_rate(_sample_rate), vco_gain(_vco_gain), alpha(_alpha), beta(_beta), freq_error(0.0), k_factor(-1 / (_sample_rate * _vco_gain)),
|
||||||
prev_in_iir(0), prev_out_iir(0), prev_in_vco(0), feedback(1.0, 0.0),
|
prev_in_iir(0), prev_out_iir(0), prev_in_vco(0), feedback(1.0, 0.0),
|
||||||
error_total(0), out_iir_total(0), in_vco_total(0),
|
error_total(0), out_iir_total(0), in_vco_total(0),
|
||||||
srrc_filter(SRRCFilter(48, _sample_rate, 2400, 0.35)) {}
|
srrc_filter(SRRCFilter(48, _sample_rate, 2400, 0.35)) {}
|
||||||
@ -67,26 +67,22 @@ public:
|
|||||||
std::complex<double> limited = limiter(filtered);
|
std::complex<double> limited = limiter(filtered);
|
||||||
|
|
||||||
// IIR Filter
|
// IIR Filter
|
||||||
double in_iir = std::asin(std::clamp(multiplied.imag() * limited.real() - multiplied.real() * limited.imag(), -1.0, 1.0));
|
double error_real = (limited.real() > 0 ? 1.0 : -1.0) * limited.imag();
|
||||||
error_total += in_iir;
|
double error_imag = (limited.imag() > 0 ? 1.0 : -1.0) * limited.real();
|
||||||
|
double phase_error = error_real - error_imag;
|
||||||
|
phase_error = 0.5 * (std::abs(phase_error + 1) - std::abs(phase_error - 1));
|
||||||
|
|
||||||
double out_iir = 1.0001 * in_iir - prev_in_iir + prev_out_iir;
|
freq_error += beta * phase_error;
|
||||||
prev_in_iir = in_iir;
|
double phase_adjust = alpha * phase_error + freq_error;
|
||||||
prev_out_iir = out_iir;
|
|
||||||
out_iir_total += out_iir;
|
|
||||||
|
|
||||||
// VCO Block
|
current_phase += 2 * M_PI * carrier_freq / sample_rate + k_factor * phase_adjust;
|
||||||
double in_vco = out_iir + prev_in_vco;
|
if (current_phase > M_PI) current_phase -= 2 * M_PI;
|
||||||
in_vco_total += in_vco;
|
else if (current_phase < -M_PI) current_phase += 2 * M_PI;
|
||||||
prev_in_vco = in_vco;
|
|
||||||
|
|
||||||
// Generate feedback signal for next iteration
|
// Generate feedback signal for next iteration
|
||||||
double feedback_real = std::cos(current_phase);
|
double feedback_real = std::cos(current_phase);
|
||||||
double feedback_imag = -std::sin(current_phase);
|
double feedback_imag = -std::sin(current_phase);
|
||||||
feedback = std::complex<double>(feedback_real, feedback_imag);
|
feedback = std::complex<double>(feedback_real, feedback_imag);
|
||||||
|
|
||||||
current_phase += 2 * M_PI * carrier_freq / sample_rate + k_factor * in_vco;
|
|
||||||
if (current_phase > 2 * M_PI) current_phase -= 2 * M_PI;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return output_signal;
|
return output_signal;
|
||||||
@ -105,6 +101,9 @@ private:
|
|||||||
double in_vco_total;
|
double in_vco_total;
|
||||||
SRRCFilter srrc_filter;
|
SRRCFilter srrc_filter;
|
||||||
double vco_gain;
|
double vco_gain;
|
||||||
|
double alpha;
|
||||||
|
double beta;
|
||||||
|
double freq_error;
|
||||||
|
|
||||||
std::complex<double> limiter(const std::complex<double>& sample) const {
|
std::complex<double> limiter(const std::complex<double>& sample) const {
|
||||||
double limited_I = std::clamp(sample.real(), -1.0, 1.0);
|
double limited_I = std::clamp(sample.real(), -1.0, 1.0);
|
||||||
|
68
main.cpp
68
main.cpp
@ -1,6 +1,7 @@
|
|||||||
#include <bitset>
|
#include <bitset>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <random>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <sndfile.h>
|
#include <sndfile.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -8,49 +9,54 @@
|
|||||||
#include "ModemController.h"
|
#include "ModemController.h"
|
||||||
#include "PSKModulator.h"
|
#include "PSKModulator.h"
|
||||||
|
|
||||||
int main() {
|
BitStream generateBernoulliData(size_t length, double p = 0.5) {
|
||||||
// Sample test data
|
BitStream random_data;
|
||||||
std::string sample_string = "The quick brown fox jumps over the lazy dog 1234567890";
|
std::random_device rd;
|
||||||
std::vector<uint8_t> sample_data(sample_string.begin(), sample_string.end());
|
std::mt19937 gen(rd());
|
||||||
|
std::bernoulli_distribution dist(p);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < length * 8; ++i) {
|
||||||
|
random_data.putBit(dist(gen)); // Generates 0 or 1 with probability p
|
||||||
|
}
|
||||||
|
return random_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
// Convert sample data to a BitStream object
|
// Convert sample data to a BitStream object
|
||||||
BitStream input_data(sample_data, sample_data.size() * 8);
|
BitStream input_data = generateBernoulliData(28800);
|
||||||
|
|
||||||
// Configuration for modem
|
// Configuration for modem
|
||||||
size_t baud_rate = 150;
|
size_t baud_rate = 75;
|
||||||
bool is_voice = false; // False indicates data mode
|
bool is_voice = false; // False indicates data mode
|
||||||
bool is_frequency_hopping = false; // Fixed frequency operation
|
bool is_frequency_hopping = false; // Fixed frequency operation
|
||||||
size_t interleave_setting = 1; // Short interleave
|
size_t interleave_setting = 2; // Short interleave
|
||||||
|
|
||||||
// Create ModemController instance
|
// Create ModemController instance
|
||||||
ModemController modem(baud_rate, is_voice, is_frequency_hopping, interleave_setting, input_data);
|
ModemController modem(baud_rate, is_voice, is_frequency_hopping, interleave_setting);
|
||||||
PSKModulator modulator(48000, is_frequency_hopping, 48);
|
|
||||||
|
const char* file_name = "modulated_signal_75bps_longinterleave.wav";
|
||||||
const char* file_name = "modulated_signal_75bps_shortinterleave.wav";
|
|
||||||
|
|
||||||
// Perform transmit operation to generate modulated signal
|
// Perform transmit operation to generate modulated signal
|
||||||
std::vector<int16_t> modulated_signal = modem.transmit();
|
std::vector<int16_t> modulated_signal = modem.transmit(input_data);
|
||||||
|
|
||||||
std::vector<uint8_t> demodulated_symbols = modulator.demodulate(modulated_signal);
|
// Output modulated signal to a WAV file using libsndfile
|
||||||
|
SF_INFO sfinfo;
|
||||||
|
sfinfo.channels = 1;
|
||||||
|
sfinfo.samplerate = 48000;
|
||||||
|
sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
|
||||||
|
|
||||||
//// Output modulated signal to a WAV file using libsndfile
|
SNDFILE* sndfile = sf_open(file_name, SFM_WRITE, &sfinfo);
|
||||||
//SF_INFO sfinfo;
|
if (sndfile == nullptr) {
|
||||||
//sfinfo.channels = 1;
|
std::cerr << "Unable to open WAV file for writing modulated signal: " << sf_strerror(sndfile) << "\n";
|
||||||
//sfinfo.samplerate = 48000;
|
return 1;
|
||||||
//sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
|
}
|
||||||
//
|
|
||||||
//SNDFILE* sndfile = sf_open(file_name, SFM_WRITE, &sfinfo);
|
sf_write_short(sndfile, modulated_signal.data(), modulated_signal.size());
|
||||||
//if (sndfile == nullptr) {
|
sf_close(sndfile);
|
||||||
// std::cerr << "Unable to open WAV file for writing modulated signal: " << sf_strerror(sndfile) << "\n";
|
std::cout << "Modulated signal written to " << file_name << '\n';
|
||||||
// return 1;
|
|
||||||
//}
|
// Success message
|
||||||
//
|
std::cout << "Modem test completed successfully.\n";
|
||||||
//sf_write_short(sndfile, modulated_signal.data(), modulated_signal.size());
|
|
||||||
//sf_close(sndfile);
|
|
||||||
//std::cout << "Modulated signal written to " << file_name << '\n';
|
|
||||||
//
|
|
||||||
//// Success message
|
|
||||||
//std::cout << "Modem test completed successfully.\n";
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user