Further steps towards FT4 decoding

pull/16/merge
Karlis Goba 2021-12-08 23:33:52 +02:00
rodzic 26decf92f7
commit acea17221e
5 zmienionych plików z 175 dodań i 101 usunięć

Wyświetl plik

@ -13,6 +13,7 @@ AllowShortCaseLabelsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AllowShortFunctionsOnASingleLine: false
AlignTrailingComments: true
BreakConstructorInitializers: BeforeColon
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 0

Wyświetl plik

@ -169,7 +169,7 @@ int main(int argc, char** argv)
// Compute DSP parameters that depend on the sample rate
const int num_bins = (int)(sample_rate / (2 * kFSK_dev)); // number bins of FSK tone width that the spectrum can be divided into
const int block_size = (int)(sample_rate / kFSK_dev); // samples corresponding to one FSK symbol
const int block_size = (int)(sample_rate / kFSK_dev); // samples corresponding to one FSK symbol
const int subblock_size = block_size / kTime_osr;
const int nfft = block_size * kFreq_osr;
const int num_blocks = (num_samples - nfft + subblock_size) / block_size;
@ -183,7 +183,8 @@ int main(int argc, char** argv)
.num_bins = num_bins,
.time_osr = kTime_osr,
.freq_osr = kFreq_osr,
.mag = mag_power
.mag = mag_power,
.block_stride = (kTime_osr * kFreq_osr * num_bins)
};
extract_power(signal, &power, block_size);

Wyświetl plik

@ -4,20 +4,24 @@
#include <stdint.h>
// Define FT8 symbol counts
#define FT8_ND (58) ///< Data symbols
#define FT8_NS (21) ///< Sync symbols (3 @ Costas 7x7)
#define FT8_NN (79) ///< Total channel symbols (FT8_NS + FT8_ND)
#define FT8_ND (58) ///< Data symbols
#define FT8_NN (79) ///< Total channel symbols (FT8_NS + FT8_ND)
#define FT8_LENGTH_SYNC (7) ///< Length of each sync group
#define FT8_NUM_SYNC (3) ///< Number of sync groups
#define FT8_SYNC_OFFSET (36) ///< Offset between sync groups
// Define FT4 symbol counts
#define FT4_ND (87) ///< Data symbols
#define FT4_NS (16) ///< Sync symbols (3 @ Costas 7x7)
#define FT4_NR (2) ///< Ramp symbols (beginning + end)
#define FT4_NN (105) ///< Total channel symbols (FT4_NS + FT4_ND + FT4_NR)
#define FT4_ND (87) ///< Data symbols
#define FT4_NR (2) ///< Ramp symbols (beginning + end)
#define FT4_NN (105) ///< Total channel symbols (FT4_NS + FT4_ND + FT4_NR)
#define FT4_LENGTH_SYNC (4) ///< Length of each sync group
#define FT4_NUM_SYNC (4) ///< Number of sync groups
#define FT4_SYNC_OFFSET (33) ///< Offset between sync groups
// Define LDPC parameters
#define FT8_LDPC_N (174) ///< Number of bits in the encoded message (payload with LDPC checksum bits)
#define FT8_LDPC_K (91) ///< Number of payload bits (including CRC)
#define FT8_LDPC_M (83) ///< Number of LDPC checksum bits (FT8_LDPC_N - FT8_LDPC_K)
#define FT8_LDPC_N (174) ///< Number of bits in the encoded message (payload with LDPC checksum bits)
#define FT8_LDPC_K (91) ///< Number of payload bits (including CRC)
#define FT8_LDPC_M (83) ///< Number of LDPC checksum bits (FT8_LDPC_N - FT8_LDPC_K)
#define FT8_LDPC_N_BYTES ((FT8_LDPC_N + 7) / 8) ///< Number of whole bytes needed to store 174 bits (full message)
#define FT8_LDPC_K_BYTES ((FT8_LDPC_K + 7) / 8) ///< Number of whole bytes needed to store 91 bits (payload + CRC only)

Wyświetl plik

@ -28,107 +28,175 @@ static void heapify_up(candidate_t heap[], int heap_size);
static void decode_symbol(const uint8_t* power, int bit_idx, float* log174);
static void decode_multi_symbols(const uint8_t* power, int num_bins, int n_syms, int bit_idx, float* log174);
static int get_index(const waterfall_t* power, int block, int time_sub, int freq_sub, int bin)
static int get_index(const waterfall_t* power, const candidate_t* candidate)
{
return ((((block * power->time_osr) + time_sub) * power->freq_osr + freq_sub) * power->num_bins) + bin;
int offset = candidate->time_offset;
offset = (offset * power->time_osr) + candidate->time_sub;
offset = (offset * power->freq_osr) + candidate->freq_sub;
offset = (offset * power->num_bins) + candidate->freq_offset;
return offset;
}
static int ft8_sync_score(const waterfall_t* power, const candidate_t* candidate)
{
int score = 0;
int num_average = 0;
// Get the pointer to symbol 0 of the candidate
const uint8_t* mag_cand = power->mag + get_index(power, candidate);
// Compute average score over sync symbols (m+k = 0-7, 36-43, 72-79)
for (int m = 0; m < FT8_NUM_SYNC; ++m)
{
for (int k = 0; k < FT8_LENGTH_SYNC; ++k)
{
int block = (FT8_SYNC_OFFSET * m) + k; // relative to the message
int block_abs = candidate->time_offset + block; // relative to the captured signal
// Check for time boundaries
if (block_abs < 0)
continue;
if (block_abs >= power->num_blocks)
break;
// Get the pointer to symbol 'block' of the candidate
const uint8_t* p8 = mag_cand + (block * power->block_stride);
// Weighted difference between the expected and all other symbols
// Does not work as well as the alternative score below
// score += 8 * p8[kFT8_Costas_pattern[k]] -
// p8[0] - p8[1] - p8[2] - p8[3] -
// p8[4] - p8[5] - p8[6] - p8[7];
// ++num_average;
// Check only the neighbors of the expected symbol frequency- and time-wise
int sm = kFT8_Costas_pattern[k]; // Index of the expected bin
if (sm > 0)
{
// look at one frequency bin lower
score += p8[sm] - p8[sm - 1];
++num_average;
}
if (sm < 7)
{
// look at one frequency bin higher
score += p8[sm] - p8[sm + 1];
++num_average;
}
if ((k > 0) && (block_abs > 0))
{
// look one symbol back in time
score += p8[sm] - p8[sm - power->block_stride];
++num_average;
}
if (((k + 1) < FT8_LENGTH_SYNC) && ((block_abs + 1) < power->num_blocks))
{
// look one symbol forward in time
score += p8[sm] - p8[sm + power->block_stride];
++num_average;
}
}
}
if (num_average > 0)
score /= num_average;
return score;
}
static int ft4_sync_score(const waterfall_t* power, const candidate_t* candidate)
{
int score = 0;
int num_average = 0;
// Get the pointer to symbol 0 of the candidate
const uint8_t* mag_cand = power->mag + get_index(power, candidate);
// Compute average score over sync symbols (block = 1-4, 34-37, 67-70, 100-103)
for (int m = 0; m < FT4_NUM_SYNC; ++m)
{
for (int k = 0; k < FT4_LENGTH_SYNC; ++k)
{
int block = 1 + (FT4_SYNC_OFFSET * m) + k;
int block_abs = candidate->time_offset + block;
// Check for time boundaries
if (block_abs < 0)
continue;
if (block_abs >= power->num_blocks)
break;
// Get the pointer to symbol 'block' of the candidate
const uint8_t* p4 = mag_cand + (block * power->block_stride);
// Check only the neighbors of the expected symbol frequency- and time-wise
int sm = kFT4_Costas_pattern[m][k]; // Index of the expected bin
if (sm > 0)
{
// look at one frequency bin lower
score += p4[sm] - p4[sm - 1];
++num_average;
}
if (sm < 3)
{
// look at one frequency bin higher
score += p4[sm] - p4[sm + 1];
++num_average;
}
if ((k > 0) && (block_abs > 0))
{
// look one symbol back in time
score += p4[sm] - p4[sm - power->block_stride];
++num_average;
}
if (((k + 1) < FT4_LENGTH_SYNC) && ((block_abs + 1) < power->num_blocks))
{
// look one symbol forward in time
score += p4[sm] - p4[sm + power->block_stride];
++num_average;
}
}
}
if (num_average > 0)
score /= num_average;
return score;
}
int ft8_find_sync(const waterfall_t* power, int num_candidates, candidate_t heap[], int min_score)
{
int heap_size = 0;
int sym_stride = power->time_osr * power->freq_osr * power->num_bins;
candidate_t candidate;
// Here we allow time offsets that exceed signal boundaries, as long as we still have all data bits.
// I.e. we can afford to skip the first 7 or the last 7 Costas symbols, as long as we track how many
// sync symbols we included in the score, so the score is averaged.
for (int time_sub = 0; time_sub < power->time_osr; ++time_sub)
for (candidate.time_sub = 0; candidate.time_sub < power->time_osr; ++candidate.time_sub)
{
for (int freq_sub = 0; freq_sub < power->freq_osr; ++freq_sub)
for (candidate.freq_sub = 0; candidate.freq_sub < power->freq_osr; ++candidate.freq_sub)
{
for (int time_offset = -12; time_offset < 24; ++time_offset)
for (candidate.time_offset = -12; candidate.time_offset < 24; ++candidate.time_offset)
{
for (int freq_offset = 0; freq_offset + 7 < power->num_bins; ++freq_offset)
for (candidate.freq_offset = 0; (candidate.freq_offset + 7) < power->num_bins; ++candidate.freq_offset)
{
int score = 0;
int num_average = 0;
candidate.score = ft8_sync_score(power, &candidate);
// Compute average score over sync symbols (m+k = 0-7, 36-43, 72-79)
for (int m = 0; m <= 72; m += 36)
{
for (int k = 0; k < 7; ++k)
{
int block = time_offset + m + k;
// Check for time boundaries
if (block < 0)
continue;
if (block >= power->num_blocks)
break;
int offset = get_index(power, block, time_sub, freq_sub, freq_offset);
const uint8_t* p8 = power->mag + offset;
// Weighted difference between the expected and all other symbols
// Does not work as well as the alternative score below
// score += 8 * p8[kFT8_Costas_pattern[k]] -
// p8[0] - p8[1] - p8[2] - p8[3] -
// p8[4] - p8[5] - p8[6] - p8[7];
// ++num_average;
// Check only the neighbors of the expected symbol frequency- and time-wise
int sm = kFT8_Costas_pattern[k]; // Index of the expected bin
if (sm > 0)
{
// look at one frequency bin lower
score += p8[sm] - p8[sm - 1];
++num_average;
}
if (sm < 7)
{
// look at one frequency bin higher
score += p8[sm] - p8[sm + 1];
++num_average;
}
if ((k > 0) && (block > 0))
{
// look one symbol back in time
score += p8[sm] - p8[sm - sym_stride];
++num_average;
}
if ((k < 6) && ((block + 1) < power->num_blocks))
{
// look one symbol forward in time
score += p8[sm] - p8[sm + sym_stride];
++num_average;
}
}
}
if (num_average > 0)
score /= num_average;
if (score < min_score)
if (candidate.score < min_score)
continue;
// If the heap is full AND the current candidate is better than
// the worst in the heap, we remove the worst and make space
if (heap_size == num_candidates && score > heap[0].score)
if (heap_size == num_candidates && candidate.score > heap[0].score)
{
heap[0] = heap[heap_size - 1];
--heap_size;
heapify_down(heap, heap_size);
}
// If there's free space in the heap, we add the current candidate
if (heap_size < num_candidates)
{
heap[heap_size].score = score;
heap[heap_size].time_offset = time_offset;
heap[heap_size].freq_offset = freq_offset;
heap[heap_size].time_sub = time_sub;
heap[heap_size].freq_sub = freq_sub;
heap[heap_size] = candidate;
++heap_size;
heapify_up(heap, heap_size);
}
}
@ -152,8 +220,7 @@ int ft8_find_sync(const waterfall_t* power, int num_candidates, candidate_t heap
void ft8_extract_likelihood(const waterfall_t* power, const candidate_t* cand, float* log174)
{
int sym_stride = power->time_osr * power->freq_osr * power->num_bins;
int offset = get_index(power, cand->time_offset, cand->time_sub, cand->freq_sub, cand->freq_offset);
const uint8_t* mag_cand = power->mag + get_index(power, cand);
// Go over FSK tones and skip Costas sync symbols
const int n_syms = 1;
@ -175,7 +242,7 @@ void ft8_extract_likelihood(const waterfall_t* power, const candidate_t* cand, f
else
{
// Pointer to 8 bins of the current symbol
const uint8_t* ps = power->mag + offset + (sym_idx * sym_stride);
const uint8_t* ps = mag_cand + (sym_idx * power->block_stride);
decode_symbol(ps, bit_idx, log174);
}

Wyświetl plik

@ -12,39 +12,40 @@
/// Values freq_osr > 1 mean the tone spacing is further subdivided by FFT analysis.
typedef struct
{
int num_blocks; ///< number of total blocks (symbols) in terms of 160 ms time periods
int num_bins; ///< number of FFT bins in terms of 6.25 Hz
int time_osr; ///< number of time subdivisions
int freq_osr; ///< number of frequency subdivisions
uint8_t* mag; ///< FFT magnitudes stored as uint8_t[blocks][time_osr][freq_osr][num_bins]
int num_blocks; ///< number of total blocks (symbols) in terms of 160 ms time periods
int num_bins; ///< number of FFT bins in terms of 6.25 Hz
int time_osr; ///< number of time subdivisions
int freq_osr; ///< number of frequency subdivisions
uint8_t* mag; ///< FFT magnitudes stored as uint8_t[blocks][time_osr][freq_osr][num_bins]
int block_stride; ///< Helper value = time_osr * freq_osr * num_bins
} waterfall_t;
/// Output structure of ft8_find_sync() and input structure of ft8_decode().
/// Holds the position of potential start of a message in time and frequency.
typedef struct
{
int16_t score; ///< Candidate score (non-negative number; higher score means higher likelihood)
int16_t score; ///< Candidate score (non-negative number; higher score means higher likelihood)
int16_t time_offset; ///< Index of the time block
int16_t freq_offset; ///< Index of the frequency bin
uint8_t time_sub; ///< Index of the time subdivision used
uint8_t freq_sub; ///< Index of the frequency subdivision used
uint8_t time_sub; ///< Index of the time subdivision used
uint8_t freq_sub; ///< Index of the frequency subdivision used
} candidate_t;
/// Structure that holds the decoded message
typedef struct
{
// TODO: check again that this size is enough
char text[25]; // plain text
uint16_t hash; // hash value to be used in hash table and quick checking for duplicates
char text[25]; ///< Plain text
uint16_t hash; ///< Hash value to be used in hash table and quick checking for duplicates
} message_t;
/// Structure that contains the status of various steps during decoding of a message
typedef struct
{
int ldpc_errors;
uint16_t crc_extracted;
uint16_t crc_calculated;
int unpack_status;
int ldpc_errors; ///< Number of LDPC errors during decoding
uint16_t crc_extracted; ///< CRC value recovered from the message
uint16_t crc_calculated; ///< CRC value calculated over the payload
int unpack_status; ///< Return value of the unpack routine
} decode_status_t;
/// Localize top N candidates in frequency and time according to their sync strength (looking at Costas symbols)