kopia lustrzana https://github.com/kgoba/ft8_lib
Simplified decoder interface and decode_ft8.c
rodzic
1690b55a62
commit
ac9510b993
6
Makefile
6
Makefile
|
@ -11,13 +11,13 @@ all: $(TARGETS)
|
|||
run_tests: test
|
||||
@./test
|
||||
|
||||
gen_ft8: gen_ft8.o ft8/constants.o ft8/text.o ft8/pack.o ft8/encode.o common/wave.o
|
||||
gen_ft8: gen_ft8.o ft8/constants.o ft8/text.o ft8/pack.o ft8/encode.o ft8/crc.o common/wave.o
|
||||
$(CXX) $(LDFLAGS) -o $@ $^
|
||||
|
||||
test: test.o ft8/pack.o ft8/encode.o ft8/text.o ft8/constants.o fft/kiss_fftr.o fft/kiss_fft.o
|
||||
test: test.o ft8/pack.o ft8/encode.o ft8/crc.o ft8/text.o ft8/constants.o fft/kiss_fftr.o fft/kiss_fft.o
|
||||
$(CXX) $(LDFLAGS) -o $@ $^
|
||||
|
||||
decode_ft8: decode_ft8.o fft/kiss_fftr.o fft/kiss_fft.o ft8/decode.o ft8/encode.o ft8/ldpc.o ft8/unpack.o ft8/text.o ft8/constants.o common/wave.o
|
||||
decode_ft8: decode_ft8.o fft/kiss_fftr.o fft/kiss_fft.o ft8/decode.o ft8/encode.o ft8/crc.o ft8/ldpc.o ft8/unpack.o ft8/text.o ft8/constants.o common/wave.o
|
||||
$(CXX) $(LDFLAGS) -o $@ $^
|
||||
|
||||
clean:
|
||||
|
|
145
decode_ft8.c
145
decode_ft8.c
|
@ -9,6 +9,7 @@
|
|||
#include "ft8/decode.h"
|
||||
#include "ft8/constants.h"
|
||||
#include "ft8/encode.h"
|
||||
#include "ft8/crc.h"
|
||||
|
||||
#include "common/wave.h"
|
||||
#include "common/debug.h"
|
||||
|
@ -21,7 +22,6 @@ const int kMax_candidates = 120;
|
|||
const int kLDPC_iterations = 25;
|
||||
|
||||
const int kMax_decoded_messages = 50;
|
||||
const int kMax_message_length = 25;
|
||||
|
||||
const int kFreq_osr = 2;
|
||||
const int kTime_osr = 2;
|
||||
|
@ -68,7 +68,7 @@ static float max2(float a, float b)
|
|||
}
|
||||
|
||||
// Compute FFT magnitudes (log power) for each timeslot in the signal
|
||||
void extract_power(const float signal[], MagArray *power)
|
||||
void extract_power(const float signal[], waterfall_t *power)
|
||||
{
|
||||
const int block_size = 2 * power->num_bins; // Average over 2 bins per FSK tone
|
||||
const int subblock_size = block_size / power->time_osr;
|
||||
|
@ -79,6 +79,9 @@ void extract_power(const float signal[], MagArray *power)
|
|||
for (int i = 0; i < nfft; ++i)
|
||||
{
|
||||
window[i] = hann_i(i, nfft);
|
||||
// window[i] = (i < block_size) ? hamming_i(i, block_size) : 0;
|
||||
// window[i] = blackman_i(i, nfft);
|
||||
// window[i] = hamming_i(i, nfft);
|
||||
}
|
||||
|
||||
size_t fft_work_size;
|
||||
|
@ -94,7 +97,7 @@ void extract_power(const float signal[], MagArray *power)
|
|||
|
||||
int offset = 0;
|
||||
float max_mag = -100.0f;
|
||||
for (int i = 0; i < power->num_blocks; ++i)
|
||||
for (int idx_block = 0; idx_block < power->num_blocks; ++idx_block)
|
||||
{
|
||||
// Loop over two possible time offsets (0 and block_size/2)
|
||||
for (int time_sub = 0; time_sub < power->time_osr; ++time_sub)
|
||||
|
@ -104,26 +107,26 @@ void extract_power(const float signal[], MagArray *power)
|
|||
float mag_db[nfft / 2 + 1];
|
||||
|
||||
// Extract windowed signal block
|
||||
for (int j = 0; j < nfft; ++j)
|
||||
for (int pos = 0; pos < nfft; ++pos)
|
||||
{
|
||||
timedata[j] = window[j] * signal[(i * block_size) + (j + time_sub * subblock_size)];
|
||||
timedata[pos] = window[pos] * signal[(idx_block * block_size) + (pos + time_sub * subblock_size)];
|
||||
}
|
||||
|
||||
kiss_fftr(fft_cfg, timedata, freqdata);
|
||||
|
||||
// Compute log magnitude in decibels
|
||||
for (int j = 0; j < nfft / 2 + 1; ++j)
|
||||
for (int idx_bin = 0; idx_bin < nfft / 2 + 1; ++idx_bin)
|
||||
{
|
||||
float mag2 = (freqdata[j].i * freqdata[j].i) + (freqdata[j].r * freqdata[j].r);
|
||||
mag_db[j] = 10.0f * log10f(1E-10f + mag2 * fft_norm * fft_norm);
|
||||
float mag2 = (freqdata[idx_bin].i * freqdata[idx_bin].i) + (freqdata[idx_bin].r * freqdata[idx_bin].r);
|
||||
mag_db[idx_bin] = 10.0f * log10f(1E-10f + mag2 * fft_norm * fft_norm);
|
||||
}
|
||||
|
||||
// Loop over two possible frequency bin offsets (for averaging)
|
||||
for (int freq_sub = 0; freq_sub < power->freq_osr; ++freq_sub)
|
||||
{
|
||||
for (int j = 0; j < power->num_bins; ++j)
|
||||
for (int pos = 0; pos < power->num_bins; ++pos)
|
||||
{
|
||||
float db = mag_db[j * power->freq_osr + freq_sub];
|
||||
float db = mag_db[pos * power->freq_osr + freq_sub];
|
||||
// Scale decibels to unsigned 8-bit range and clamp the value
|
||||
int scaled = (int)(2 * (db + 120));
|
||||
|
||||
|
@ -207,7 +210,7 @@ int main(int argc, char **argv)
|
|||
|
||||
// Compute FFT over the whole signal and store it
|
||||
uint8_t mag_power[num_blocks * kFreq_osr * kTime_osr * num_bins];
|
||||
MagArray power = {
|
||||
waterfall_t power = {
|
||||
.num_blocks = num_blocks,
|
||||
.num_bins = num_bins,
|
||||
.time_osr = kTime_osr,
|
||||
|
@ -216,103 +219,85 @@ int main(int argc, char **argv)
|
|||
extract_power(signal, &power);
|
||||
|
||||
// Find top candidates by Costas sync score and localize them in time and frequency
|
||||
Candidate candidate_list[kMax_candidates];
|
||||
candidate_t candidate_list[kMax_candidates];
|
||||
int num_candidates = find_sync(&power, kMax_candidates, candidate_list, kMin_score);
|
||||
|
||||
// TODO: sort the candidates by strongest sync first?
|
||||
|
||||
// Go over candidates and attempt to decode messages
|
||||
char decoded[kMax_decoded_messages][kMax_message_length];
|
||||
// Hash table for decoded messages (to check for duplicates)
|
||||
int num_decoded = 0;
|
||||
message_t decoded[kMax_decoded_messages];
|
||||
message_t *decoded_hashtable[kMax_decoded_messages];
|
||||
|
||||
// Initialize hash table pointers
|
||||
for (int i = 0; i < kMax_decoded_messages; ++i)
|
||||
{
|
||||
decoded_hashtable[i] = NULL;
|
||||
}
|
||||
|
||||
// Go over candidates and attempt to decode messages
|
||||
for (int idx = 0; idx < num_candidates; ++idx)
|
||||
{
|
||||
const Candidate *cand = &candidate_list[idx];
|
||||
const candidate_t *cand = &candidate_list[idx];
|
||||
if (cand->score < kMin_score)
|
||||
continue;
|
||||
|
||||
float freq_hz = (cand->freq_offset + (float)cand->freq_sub / kFreq_osr) * kFSK_dev;
|
||||
float time_sec = (cand->time_offset + (float)cand->time_sub / kTime_osr) / kFSK_dev;
|
||||
|
||||
float log174[FT8_N];
|
||||
extract_likelihood(&power, cand, log174);
|
||||
|
||||
// Try partial decodes with truncated end of message
|
||||
// (to check if successful decoding can be done prior to receiving the whole message)
|
||||
for (int bits_received = 100; bits_received < 174; ++bits_received)
|
||||
message_t message;
|
||||
decode_status_t status;
|
||||
if (!decode(&power, cand, &message, kLDPC_iterations, &status))
|
||||
{
|
||||
// bp_decode() produces better decodes, uses way less memory
|
||||
uint8_t plain[FT8_N];
|
||||
float log174_masked[FT8_N];
|
||||
int n_errors = 0;
|
||||
|
||||
// mask trailing bits with 0 likelihood (p(1)=p(0)=0.5)
|
||||
for (int m = 0; m < 174; ++m)
|
||||
if (status.ldpc_errors > 0)
|
||||
{
|
||||
log174_masked[m] = (m < bits_received) ? log174[m] : 0;
|
||||
LOG(LOG_DEBUG, "LDPC decode: %d errors\n", status.ldpc_errors);
|
||||
}
|
||||
bp_decode(log174_masked, kLDPC_iterations, plain, &n_errors);
|
||||
// ldpc_decode(log174_masked, kLDPC_iterations, plain, &n_errors);
|
||||
|
||||
if (n_errors > 0)
|
||||
else if (status.crc_calculated != status.crc_extracted)
|
||||
{
|
||||
LOG(LOG_DEBUG, "ldpc_decode() = %d (%.0f Hz)\n", n_errors, freq_hz);
|
||||
continue;
|
||||
LOG(LOG_DEBUG, "CRC mismatch!\n");
|
||||
}
|
||||
|
||||
int sum_plain = 0;
|
||||
for (int i = 0; i < FT8_N; ++i)
|
||||
else if (status.unpack_status != 0)
|
||||
{
|
||||
sum_plain += plain[i];
|
||||
LOG(LOG_DEBUG, "Error while unpacking!\n");
|
||||
}
|
||||
if (sum_plain == 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG(LOG_DEBUG, "Checking hash table for %4.1fs / %4.1fHz [%d]...\n", time_sec, freq_hz, cand->score);
|
||||
int hash_idx = message.hash % kMax_decoded_messages;
|
||||
bool found_empty_slot = false;
|
||||
bool found_duplicate = false;
|
||||
do
|
||||
{
|
||||
if (decoded_hashtable[hash_idx] == NULL)
|
||||
{
|
||||
// All zeroes message
|
||||
continue;
|
||||
LOG(LOG_DEBUG, "Found an empty slot\n");
|
||||
found_empty_slot = true;
|
||||
}
|
||||
|
||||
// Extract payload + CRC (first FT8_K bits)
|
||||
uint8_t a91[FT8_K_BYTES];
|
||||
pack_bits(plain, FT8_K, a91);
|
||||
|
||||
// Extract CRC and check it
|
||||
uint16_t chksum = ((a91[9] & 0x07) << 11) | (a91[10] << 3) | (a91[11] >> 5);
|
||||
a91[9] &= 0xF8;
|
||||
a91[10] = 0;
|
||||
a91[11] = 0;
|
||||
uint16_t chksum2 = ft8_crc(a91, 96 - 14);
|
||||
if (chksum != chksum2)
|
||||
else if ((decoded_hashtable[hash_idx]->hash == message.hash) && (0 == strcmp(decoded_hashtable[hash_idx]->text, message.text)))
|
||||
{
|
||||
LOG(LOG_DEBUG, "Checksum: message = %04x, CRC = %04x\n", chksum, chksum2);
|
||||
continue;
|
||||
LOG(LOG_DEBUG, "Found a duplicate [%s]\n", message.text);
|
||||
found_duplicate = true;
|
||||
}
|
||||
|
||||
char message[kMax_message_length];
|
||||
if (unpack77(a91, message) < 0)
|
||||
else
|
||||
{
|
||||
continue;
|
||||
LOG(LOG_DEBUG, "Hash table clash!\n");
|
||||
// Move on to check the next entry in hash table
|
||||
hash_idx = (hash_idx + 1) % kMax_decoded_messages;
|
||||
}
|
||||
} while (!found_empty_slot && !found_duplicate);
|
||||
|
||||
// Check for duplicate messages (TODO: use hashing)
|
||||
bool found = false;
|
||||
for (int i = 0; i < num_decoded; ++i)
|
||||
{
|
||||
if (0 == strcmp(decoded[i], message))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found_empty_slot)
|
||||
{
|
||||
// Fill the empty hashtable slot
|
||||
memcpy(&decoded[hash_idx], &message, sizeof(message));
|
||||
decoded_hashtable[hash_idx] = &decoded[hash_idx];
|
||||
++num_decoded;
|
||||
|
||||
if (!found && num_decoded < kMax_decoded_messages)
|
||||
{
|
||||
strcpy(decoded[num_decoded], message);
|
||||
++num_decoded;
|
||||
|
||||
// Fake WSJT-X-like output for now
|
||||
int snr = 0; // TODO: compute SNR
|
||||
printf("000000 %3d [%2d] %4.1f %4d ~ %s\n", cand->score, bits_received, time_sec, (int)(freq_hz + 0.5f), message);
|
||||
continue;
|
||||
}
|
||||
// Fake WSJT-X-like output for now
|
||||
int snr = 0; // TODO: compute SNR
|
||||
printf("000000 %3d %4.1f %4d ~ %s\n", cand->score, time_sec, (int)(freq_hz + 0.5f), message.text);
|
||||
}
|
||||
}
|
||||
LOG(LOG_INFO, "Decoded %d messages\n", num_decoded);
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
#include "crc.h"
|
||||
#include "constants.h"
|
||||
|
||||
#define TOPBIT (1u << (FT8_CRC_WIDTH - 1))
|
||||
|
||||
// Compute 14-bit CRC for a sequence of given number of bits
|
||||
// Adapted from https://barrgroup.com/Embedded-Systems/How-To/CRC-Calculation-C-Code
|
||||
// [IN] message - byte sequence (MSB first)
|
||||
// [IN] num_bits - number of bits in the sequence
|
||||
uint16_t ft8_crc(const uint8_t message[], int num_bits)
|
||||
{
|
||||
uint16_t remainder = 0;
|
||||
int idx_byte = 0;
|
||||
|
||||
// Perform modulo-2 division, a bit at a time.
|
||||
for (int idx_bit = 0; idx_bit < num_bits; ++idx_bit)
|
||||
{
|
||||
if (idx_bit % 8 == 0)
|
||||
{
|
||||
// Bring the next byte into the remainder.
|
||||
remainder ^= (message[idx_byte] << (FT8_CRC_WIDTH - 8));
|
||||
++idx_byte;
|
||||
}
|
||||
|
||||
// Try to divide the current data bit.
|
||||
if (remainder & TOPBIT)
|
||||
{
|
||||
remainder = (remainder << 1) ^ FT8_CRC_POLYNOMIAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
remainder = (remainder << 1);
|
||||
}
|
||||
}
|
||||
|
||||
return remainder & ((TOPBIT << 1) - 1u);
|
||||
}
|
||||
|
||||
uint16_t extract_crc(uint8_t a91[])
|
||||
{
|
||||
uint16_t chksum = ((a91[9] & 0x07) << 11) | (a91[10] << 3) | (a91[11] >> 5);
|
||||
return chksum;
|
||||
}
|
||||
|
||||
void add_crc(uint8_t a91[])
|
||||
{
|
||||
// Calculate CRC of 12 bytes = 96 bits, see WSJT-X code
|
||||
uint16_t checksum = ft8_crc(a91, 96 - 14);
|
||||
|
||||
// Store the CRC at the end of 77 bit message
|
||||
a91[9] |= (uint8_t)(checksum >> 11);
|
||||
a91[10] = (uint8_t)(checksum >> 3);
|
||||
a91[11] = (uint8_t)(checksum << 5);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef _INCLUDE_CRC_H_
|
||||
#define _INCLUDE_CRC_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// Compute 14-bit CRC for a sequence of given number of bits
|
||||
// [IN] message - byte sequence (MSB first)
|
||||
// [IN] num_bits - number of bits in the sequence
|
||||
uint16_t ft8_crc(const uint8_t message[], int num_bits);
|
||||
|
||||
/// Check the FT8 CRC of a packed message (during decoding)
|
||||
uint16_t extract_crc(uint8_t a91[]);
|
||||
|
||||
/// Add the FT8 CRC to a packed message (during encoding)
|
||||
void add_crc(uint8_t a91[]);
|
||||
|
||||
#endif // _INCLUDE_CRC_H_
|
84
ft8/decode.c
84
ft8/decode.c
|
@ -1,22 +1,32 @@
|
|||
#include "decode.h"
|
||||
#include "constants.h"
|
||||
#include "crc.h"
|
||||
#include "ldpc.h"
|
||||
#include "unpack.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <math.h>
|
||||
|
||||
/// Compute log likelihood log(p(1) / p(0)) of 174 message bits for later use in soft-decision LDPC decoding
|
||||
/// @param[in] power Waterfall data collected during message slot
|
||||
/// @param[in] cand Candidate to extract the message from
|
||||
/// @param[in] code_map Symbol encoding map
|
||||
/// @param[out] log174 Output of decoded log likelihoods for each of the 174 message bits
|
||||
static void extract_likelihood(const waterfall_t *power, const candidate_t *cand, float *log174);
|
||||
|
||||
static float max2(float a, float b);
|
||||
static float max4(float a, float b, float c, float d);
|
||||
static void heapify_down(Candidate heap[], int heap_size);
|
||||
static void heapify_up(Candidate heap[], int heap_size);
|
||||
static void heapify_down(candidate_t heap[], int heap_size);
|
||||
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 MagArray *power, int block, int time_sub, int freq_sub, int bin)
|
||||
static int get_index(const waterfall_t *power, int block, int time_sub, int freq_sub, int bin)
|
||||
{
|
||||
return ((((block * power->time_osr) + time_sub) * power->freq_osr + freq_sub) * power->num_bins) + bin;
|
||||
}
|
||||
|
||||
int find_sync(const MagArray *power, int num_candidates, Candidate heap[], int min_score)
|
||||
int find_sync(const waterfall_t *power, int num_candidates, candidate_t heap[], int min_score)
|
||||
{
|
||||
int heap_size = 0;
|
||||
int num_alt = power->time_osr * power->freq_osr;
|
||||
|
@ -28,9 +38,9 @@ int find_sync(const MagArray *power, int num_candidates, Candidate heap[], int m
|
|||
{
|
||||
for (int freq_sub = 0; freq_sub < power->freq_osr; ++freq_sub)
|
||||
{
|
||||
for (int time_offset = -7; time_offset < power->num_blocks - FT8_NN + 7; ++time_offset)
|
||||
for (int time_offset = -8; time_offset < power->num_blocks - FT8_NN + 8; ++time_offset)
|
||||
{
|
||||
for (int freq_offset = 0; freq_offset < power->num_bins - 8; ++freq_offset)
|
||||
for (int freq_offset = 0; freq_offset + 8 < power->num_bins; ++freq_offset)
|
||||
{
|
||||
int score = 0;
|
||||
|
||||
|
@ -116,7 +126,7 @@ int find_sync(const MagArray *power, int num_candidates, Candidate heap[], int m
|
|||
return heap_size;
|
||||
}
|
||||
|
||||
void extract_likelihood(const MagArray *power, const Candidate *cand, float *log174)
|
||||
void extract_likelihood(const waterfall_t *power, const candidate_t *cand, float *log174)
|
||||
{
|
||||
int num_alt = power->time_osr * power->freq_osr;
|
||||
int offset = get_index(power, cand->time_offset, cand->time_sub, cand->freq_sub, cand->freq_offset);
|
||||
|
@ -140,23 +150,67 @@ void extract_likelihood(const MagArray *power, const Candidate *cand, float *log
|
|||
// Compute the variance of log174
|
||||
float sum = 0;
|
||||
float sum2 = 0;
|
||||
float inv_n = 1.0f / FT8_N;
|
||||
for (int i = 0; i < FT8_N; ++i)
|
||||
{
|
||||
sum += log174[i];
|
||||
sum2 += log174[i] * log174[i];
|
||||
}
|
||||
float variance = (sum2 - sum * sum * inv_n) * inv_n;
|
||||
float inv_n = 1.0f / FT8_N;
|
||||
float variance = (sum2 - (sum * sum * inv_n)) * inv_n;
|
||||
|
||||
// Normalize log174 such that sigma = 2.83 (Why? It's in WSJT-X, ft8b.f90)
|
||||
// Seems to be 2.83 = sqrt(8). Experimentally sqrt(16) works better.
|
||||
float norm_factor = sqrtf(16.0f / variance);
|
||||
// Seems to be 2.83 = sqrt(8). Experimentally sqrt(32) works better.
|
||||
float norm_factor = sqrtf(32.0f / variance);
|
||||
for (int i = 0; i < FT8_N; ++i)
|
||||
{
|
||||
log174[i] *= norm_factor;
|
||||
}
|
||||
}
|
||||
|
||||
bool decode(const waterfall_t *power, const candidate_t *cand, message_t *message, int max_iterations, decode_status_t *status)
|
||||
{
|
||||
float log174[FT8_N]; // message bits encoded as likelihood
|
||||
extract_likelihood(power, cand, log174);
|
||||
|
||||
uint8_t plain174[FT8_N]; // message bits (0/1)
|
||||
bp_decode(log174, max_iterations, plain174, &status->ldpc_errors);
|
||||
// ldpc_decode(log174, kLDPC_iterations, plain174, &n_errors);
|
||||
|
||||
if (status->ldpc_errors > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract payload + CRC (first FT8_K bits) packed into a byte array
|
||||
uint8_t a91[FT8_K_BYTES];
|
||||
pack_bits(plain174, FT8_K, a91);
|
||||
|
||||
// Extract CRC and check it
|
||||
status->crc_extracted = extract_crc(a91);
|
||||
// TODO: not sure why the zeroing of message is needed and also why CRC over 96-14 bits?
|
||||
a91[9] &= 0xF8;
|
||||
a91[10] = 0;
|
||||
a91[11] = 0;
|
||||
status->crc_calculated = ft8_crc(a91, 96 - 14);
|
||||
|
||||
if (status->crc_extracted != status->crc_calculated)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
status->unpack_status = unpack77(a91, message->text);
|
||||
|
||||
if (status->unpack_status < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reuse binary message CRC as hash value for the message
|
||||
message->hash = status->crc_extracted;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static float max2(float a, float b)
|
||||
{
|
||||
return (a >= b) ? a : b;
|
||||
|
@ -167,7 +221,7 @@ static float max4(float a, float b, float c, float d)
|
|||
return max2(max2(a, b), max2(c, d));
|
||||
}
|
||||
|
||||
static void heapify_down(Candidate heap[], int heap_size)
|
||||
static void heapify_down(candidate_t heap[], int heap_size)
|
||||
{
|
||||
// heapify from the root down
|
||||
int current = 0;
|
||||
|
@ -190,14 +244,14 @@ static void heapify_down(Candidate heap[], int heap_size)
|
|||
break;
|
||||
}
|
||||
|
||||
Candidate tmp = heap[largest];
|
||||
candidate_t tmp = heap[largest];
|
||||
heap[largest] = heap[current];
|
||||
heap[current] = tmp;
|
||||
current = largest;
|
||||
}
|
||||
}
|
||||
|
||||
static void heapify_up(Candidate heap[], int heap_size)
|
||||
static void heapify_up(candidate_t heap[], int heap_size)
|
||||
{
|
||||
// heapify from the last node up
|
||||
int current = heap_size - 1;
|
||||
|
@ -209,7 +263,7 @@ static void heapify_up(Candidate heap[], int heap_size)
|
|||
break;
|
||||
}
|
||||
|
||||
Candidate tmp = heap[parent];
|
||||
candidate_t tmp = heap[parent];
|
||||
heap[parent] = heap[current];
|
||||
heap[current] = tmp;
|
||||
current = parent;
|
||||
|
|
40
ft8/decode.h
40
ft8/decode.h
|
@ -2,6 +2,7 @@
|
|||
#define _INCLUDE_DECODE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/// Input structure to find_sync() function. This structure describes stored waterfall data over the whole message slot.
|
||||
/// Fields time_osr and freq_osr specify additional oversampling rate for time and frequency resolution.
|
||||
|
@ -16,7 +17,7 @@ typedef struct
|
|||
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]
|
||||
} MagArray;
|
||||
} waterfall_t;
|
||||
|
||||
/// Output structure of find_sync() and input structure of extract_likelihood().
|
||||
/// Holds the position of potential start of a message in time and frequency.
|
||||
|
@ -27,23 +28,42 @@ typedef struct
|
|||
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
|
||||
} Candidate;
|
||||
} 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
|
||||
} 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;
|
||||
} decode_status_t;
|
||||
|
||||
/// Localize top N candidates in frequency and time according to their sync strength (looking at Costas symbols)
|
||||
/// We treat and organize the candidate list as a min-heap (empty initially).
|
||||
/// @param[in] power Waterfall data collected during message slot
|
||||
/// @param[in] sync_pattern Synchronization pattern
|
||||
/// @param[in] num_candidates Number of maximum candidates (size of heap array)
|
||||
/// @param[in,out] heap Array of Candidate type entries (with num_candidates allocated entries)
|
||||
/// @param[in] min_score Minimal score allowed for trimming unlikely candidates (can be zero for no effect)
|
||||
/// @param[in,out] heap Array of candidate_t type entries (with num_candidates allocated entries)
|
||||
/// @param[in] min_score Minimal score allowed for pruning unlikely candidates (can be zero for no effect)
|
||||
/// @return Number of candidates filled in the heap
|
||||
int find_sync(const MagArray *power, int num_candidates, Candidate heap[], int min_score);
|
||||
int find_sync(const waterfall_t *power, int num_candidates, candidate_t heap[], int min_score);
|
||||
|
||||
/// Compute log likelihood log(p(1) / p(0)) of 174 message bits for later use in soft-decision LDPC decoding
|
||||
/// Attempt to decode a message candidate. Extracts the bit probabilities, runs LDPC decoder, checks CRC and unpacks the message in plain text.
|
||||
/// @param[in] power Waterfall data collected during message slot
|
||||
/// @param[in] cand Candidate to extract the message from
|
||||
/// @param[in] code_map Symbol encoding map
|
||||
/// @param[out] log174 Output of decoded log likelihoods for each of the 174 message bits
|
||||
void extract_likelihood(const MagArray *power, const Candidate *cand, float *log174);
|
||||
/// @param[in] cand Candidate to decode
|
||||
/// @param[out] message message_t structure that will receive the decoded message
|
||||
/// @param[in] max_iterations Maximum allowed LDPC iterations (lower number means faster decode, but less precise)
|
||||
/// @param[out] status decode_status_t structure that will be filled with the status of various decoding steps
|
||||
/// @return True if the decoding was successful, false otherwise (check status for details)
|
||||
bool decode(const waterfall_t *power, const candidate_t *cand, message_t *message, int max_iterations, decode_status_t *status);
|
||||
|
||||
#endif // _INCLUDE_DECODE_H_
|
||||
|
|
47
ft8/encode.c
47
ft8/encode.c
|
@ -1,10 +1,9 @@
|
|||
#include "encode.h"
|
||||
#include "constants.h"
|
||||
#include "crc.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#define TOPBIT (1u << (FT8_CRC_WIDTH - 1))
|
||||
|
||||
// Returns 1 if an odd number of bits are set in x, zero otherwise
|
||||
uint8_t parity8(uint8_t x)
|
||||
{
|
||||
|
@ -75,39 +74,6 @@ void encode174(const uint8_t *message, uint8_t *codeword)
|
|||
}
|
||||
}
|
||||
|
||||
// Compute 14-bit CRC for a sequence of given number of bits
|
||||
// Adapted from https://barrgroup.com/Embedded-Systems/How-To/CRC-Calculation-C-Code
|
||||
// [IN] message - byte sequence (MSB first)
|
||||
// [IN] num_bits - number of bits in the sequence
|
||||
uint16_t ft8_crc(uint8_t *message, int num_bits)
|
||||
{
|
||||
uint16_t remainder = 0;
|
||||
int idx_byte = 0;
|
||||
|
||||
// Perform modulo-2 division, a bit at a time.
|
||||
for (int idx_bit = 0; idx_bit < num_bits; ++idx_bit)
|
||||
{
|
||||
if (idx_bit % 8 == 0)
|
||||
{
|
||||
// Bring the next byte into the remainder.
|
||||
remainder ^= (message[idx_byte] << (FT8_CRC_WIDTH - 8));
|
||||
++idx_byte;
|
||||
}
|
||||
|
||||
// Try to divide the current data bit.
|
||||
if (remainder & TOPBIT)
|
||||
{
|
||||
remainder = (remainder << 1) ^ FT8_CRC_POLYNOMIAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
remainder = (remainder << 1);
|
||||
}
|
||||
}
|
||||
|
||||
return remainder & ((TOPBIT << 1) - 1u);
|
||||
}
|
||||
|
||||
// Generate FT8 tone sequence from payload data
|
||||
// [IN] payload - 10 byte array consisting of 77 bit payload (MSB first)
|
||||
// [OUT] itone - array of NN (79) bytes to store the generated tones (encoded as 0..7)
|
||||
|
@ -124,15 +90,10 @@ void genft8(const uint8_t *payload, uint8_t *itone)
|
|||
a91[10] = 0;
|
||||
a91[11] = 0;
|
||||
|
||||
// Calculate CRC of 12 bytes = 96 bits, see WSJT-X code
|
||||
uint16_t checksum = ft8_crc(a91, 96 - 14);
|
||||
// Compute and add CRC at the end of the message
|
||||
// a91 contains 77 bits of payload + 14 bits of CRC
|
||||
add_crc(a91);
|
||||
|
||||
// Store the CRC at the end of 77 bit message
|
||||
a91[9] |= (uint8_t)(checksum >> 11);
|
||||
a91[10] = (uint8_t)(checksum >> 3);
|
||||
a91[11] = (uint8_t)(checksum << 5);
|
||||
|
||||
// a87 contains 77 bits of payload + 14 bits of CRC
|
||||
uint8_t codeword[22];
|
||||
encode174(a91, codeword);
|
||||
|
||||
|
|
|
@ -19,9 +19,4 @@ void genft8(const uint8_t *payload, uint8_t *itone);
|
|||
// * codeword - array of 174 bits stored as 22 bytes (MSB first)
|
||||
void encode174(const uint8_t *message, uint8_t *codeword);
|
||||
|
||||
// Compute 14-bit CRC for a sequence of given number of bits
|
||||
// [IN] message - byte sequence (MSB first)
|
||||
// [IN] num_bits - number of bits in the sequence
|
||||
uint16_t ft8_crc(uint8_t *message, int num_bits);
|
||||
|
||||
#endif // _INCLUDE_ENCODE_H_
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
static int ldpc_check(uint8_t codeword[]);
|
||||
static float fast_tanh(float x);
|
||||
|
@ -177,12 +178,20 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
|
|||
for (int iter = 0; iter < max_iters; ++iter)
|
||||
{
|
||||
float zn[FT8_N];
|
||||
int plain_sum = 0;
|
||||
|
||||
// Update bit log likelihood ratios (tov=0 in iter 0)
|
||||
for (int i = 0; i < FT8_N; ++i)
|
||||
{
|
||||
zn[i] = codeword[i] + tov[i][0] + tov[i][1] + tov[i][2];
|
||||
plain[i] = (zn[i] > 0) ? 1 : 0;
|
||||
plain_sum += plain[i];
|
||||
}
|
||||
|
||||
if (plain_sum == 0)
|
||||
{
|
||||
// message converged to all-zeros, which is prohibited
|
||||
break;
|
||||
}
|
||||
|
||||
// Check to see if we have a codeword (check before we do any iter)
|
||||
|
|
Ładowanie…
Reference in New Issue