kopia lustrzana https://github.com/kgoba/ft8_lib
Added FT4 encoding
rodzic
456eefede4
commit
78c3e25a08
|
@ -17,8 +17,8 @@
|
|||
|
||||
#define LOG_LEVEL LOG_INFO
|
||||
|
||||
const int kMin_score = 10; // Minimum sync score threshold for candidates
|
||||
const int kMax_candidates = 150;
|
||||
const int kMin_score = 12; // Minimum sync score threshold for candidates
|
||||
const int kMax_candidates = 120;
|
||||
const int kLDPC_iterations = 25;
|
||||
|
||||
const int kMax_decoded_messages = 50;
|
||||
|
@ -163,7 +163,7 @@ void normalize_signal(float *signal, int num_samples)
|
|||
|
||||
void print_tones(const uint8_t *code_map, const float *log174)
|
||||
{
|
||||
for (int k = 0; k < FT8_N; k += 3)
|
||||
for (int k = 0; k < FT8_LDPC_N; k += 3)
|
||||
{
|
||||
uint8_t max = 0;
|
||||
if (log174[k + 0] > 0)
|
||||
|
|
|
@ -1,13 +1,31 @@
|
|||
#include "constants.h"
|
||||
|
||||
// Costas 7x7 tone pattern
|
||||
// Costas sync tone pattern
|
||||
const uint8_t kFT8_Costas_pattern[7] = {3, 1, 4, 0, 6, 5, 2};
|
||||
const uint8_t kFT4_Costas_pattern[4][4] = {{0, 1, 3, 2},
|
||||
{1, 0, 2, 3},
|
||||
{2, 3, 1, 0},
|
||||
{3, 2, 0, 1}};
|
||||
|
||||
// Gray code map
|
||||
const uint8_t kFT8_Gray_map[8] = {0, 1, 3, 2, 5, 6, 4, 7};
|
||||
const uint8_t kFT4_Gray_map[4] = {0, 1, 3, 2};
|
||||
|
||||
const uint8_t kFT4_XOR_sequence[10] = {
|
||||
0x4Au, // 01001010
|
||||
0x5Eu, // 01011110
|
||||
0x89u, // 10001001
|
||||
0xB4u, // 10110100
|
||||
0xB0u, // 10110000
|
||||
0x8Au, // 10001010
|
||||
0x79u, // 01111001
|
||||
0x55u, // 01010101
|
||||
0xBEu, // 10111110
|
||||
0x28u, // 00101 [000]
|
||||
};
|
||||
|
||||
// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first)
|
||||
const uint8_t kFT8_LDPC_generator[FT8_M][FT8_K_BYTES] = {
|
||||
const uint8_t kFT8_LDPC_generator[FT8_LDPC_M][FT8_LDPC_K_BYTES] = {
|
||||
{0x83, 0x29, 0xce, 0x11, 0xbf, 0x31, 0xea, 0xf5, 0x09, 0xf2, 0x7f, 0xc0},
|
||||
{0x76, 0x1c, 0x26, 0x4e, 0x25, 0xc2, 0x59, 0x33, 0x54, 0x93, 0x13, 0x20},
|
||||
{0xdc, 0x26, 0x59, 0x02, 0xfb, 0x27, 0x7c, 0x64, 0x10, 0xa1, 0xbd, 0xc0},
|
||||
|
@ -95,7 +113,7 @@ const uint8_t kFT8_LDPC_generator[FT8_M][FT8_K_BYTES] = {
|
|||
// Each row describes one LDPC parity check.
|
||||
// Each number is an index into the codeword (1-origin).
|
||||
// The codeword bits mentioned in each row must XOR to zero.
|
||||
const uint8_t kFT8_LDPC_Nm[FT8_M][7] = {
|
||||
const uint8_t kFT8_LDPC_Nm[FT8_LDPC_M][7] = {
|
||||
{4, 31, 59, 91, 92, 96, 153},
|
||||
{5, 32, 60, 93, 115, 146, 0},
|
||||
{6, 24, 61, 94, 122, 151, 0},
|
||||
|
@ -183,7 +201,7 @@ const uint8_t kFT8_LDPC_Nm[FT8_M][7] = {
|
|||
// Each row corresponds to a codeword bit.
|
||||
// The numbers indicate which three LDPC parity checks (rows in Nm) refer to the codeword bit.
|
||||
// 1-origin.
|
||||
const uint8_t kFT8_LDPC_Mn[FT8_N][3] = {
|
||||
const uint8_t kFT8_LDPC_Mn[FT8_LDPC_N][3] = {
|
||||
{16, 45, 73},
|
||||
{25, 51, 62},
|
||||
{33, 58, 78},
|
||||
|
@ -359,7 +377,7 @@ const uint8_t kFT8_LDPC_Mn[FT8_N][3] = {
|
|||
{20, 44, 48},
|
||||
{42, 49, 57}};
|
||||
|
||||
const uint8_t kFT8_LDPC_num_rows[FT8_M] = {
|
||||
const uint8_t kFT8_LDPC_num_rows[FT8_LDPC_M] = {
|
||||
7, 6, 6, 6, 7, 6, 7, 6, 6, 7, 6, 6, 7, 7, 6, 6,
|
||||
6, 7, 6, 7, 6, 7, 6, 6, 6, 7, 6, 6, 6, 7, 6, 6,
|
||||
6, 6, 7, 6, 6, 6, 7, 7, 6, 6, 6, 6, 7, 7, 6, 6,
|
||||
|
|
|
@ -4,16 +4,22 @@
|
|||
#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 (FT8_NS + FT8_ND) ///< Total channel symbols (79)
|
||||
#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 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 LDPC parameters
|
||||
#define FT8_N (174) ///< Number of bits in the encoded message (payload + checksum)
|
||||
#define FT8_K (91) ///< Number of payload bits
|
||||
#define FT8_M (FT8_N - FT8_K) ///< Number of LDPC checksum bits
|
||||
#define FT8_N_BYTES ((FT8_N + 7) / 8) ///< Number of whole bytes needed to store N bits (full message)
|
||||
#define FT8_K_BYTES ((FT8_K + 7) / 8) ///< Number of whole bytes needed to store K bits (payload only)
|
||||
#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)
|
||||
|
||||
// Define CRC parameters
|
||||
#define FT8_CRC_POLYNOMIAL ((uint16_t)0x2757u) ///< CRC-14 polynomial without the leading (MSB) 1
|
||||
|
@ -21,30 +27,34 @@
|
|||
|
||||
/// Costas 7x7 tone pattern for synchronization
|
||||
extern const uint8_t kFT8_Costas_pattern[7];
|
||||
extern const uint8_t kFT4_Costas_pattern[4][4];
|
||||
|
||||
/// Gray code map to encode 8 symbols (tones)
|
||||
extern const uint8_t kFT8_Gray_map[8];
|
||||
extern const uint8_t kFT4_Gray_map[4];
|
||||
|
||||
extern const uint8_t kFT4_XOR_sequence[10];
|
||||
|
||||
/// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first)
|
||||
extern const uint8_t kFT8_LDPC_generator[FT8_M][FT8_K_BYTES];
|
||||
extern const uint8_t kFT8_LDPC_generator[FT8_LDPC_M][FT8_LDPC_K_BYTES];
|
||||
|
||||
/// Column order (permutation) in which the bits in codeword are stored
|
||||
/// (Not really used in FT8 v2 - instead the Nm, Mn and generator matrices are already permuted)
|
||||
extern const uint8_t kFT8_LDPC_column_order[FT8_N];
|
||||
extern const uint8_t kFT8_LDPC_column_order[FT8_LDPC_N];
|
||||
|
||||
/// LDPC(174,91) parity check matrix, containing 83 rows,
|
||||
/// each row describes one parity check,
|
||||
/// each number is an index into the codeword (1-origin).
|
||||
/// The codeword bits mentioned in each row must xor to zero.
|
||||
/// From WSJT-X's ldpc_174_91_c_reordered_parity.f90.
|
||||
extern const uint8_t kFT8_LDPC_Nm[FT8_M][7];
|
||||
extern const uint8_t kFT8_LDPC_Nm[FT8_LDPC_M][7];
|
||||
|
||||
/// Mn from WSJT-X's bpdecode174.f90. Each row corresponds to a codeword bit.
|
||||
/// The numbers indicate which three parity checks (rows in Nm) refer to the codeword bit.
|
||||
/// The numbers use 1 as the origin (first entry).
|
||||
extern const uint8_t kFT8_LDPC_Mn[FT8_N][3];
|
||||
extern const uint8_t kFT8_LDPC_Mn[FT8_LDPC_N][3];
|
||||
|
||||
/// Number of rows (columns in C/C++) in the array Nm.
|
||||
extern const uint8_t kFT8_LDPC_num_rows[FT8_M];
|
||||
extern const uint8_t kFT8_LDPC_num_rows[FT8_LDPC_M];
|
||||
|
||||
#endif // _INCLUDE_CONSTANTS_H_
|
||||
|
|
15
ft8/crc.c
15
ft8/crc.c
|
@ -36,15 +36,24 @@ uint16_t ft8_crc(const uint8_t message[], int num_bits)
|
|||
return remainder & ((TOPBIT << 1) - 1u);
|
||||
}
|
||||
|
||||
uint16_t extract_crc(uint8_t a91[])
|
||||
uint16_t extract_crc(const uint8_t a91[])
|
||||
{
|
||||
uint16_t chksum = ((a91[9] & 0x07) << 11) | (a91[10] << 3) | (a91[11] >> 5);
|
||||
return chksum;
|
||||
}
|
||||
|
||||
void add_crc(uint8_t a91[])
|
||||
void add_crc(const uint8_t payload[], uint8_t a91[])
|
||||
{
|
||||
// Calculate CRC of 12 bytes = 96 bits, see WSJT-X code
|
||||
// Copy 77 bits of payload data
|
||||
for (int i = 0; i < 10; i++)
|
||||
a91[i] = payload[i];
|
||||
|
||||
// Clear 3 bits after the payload to make 82 bits
|
||||
a91[9] &= 0xF8u;
|
||||
a91[10] = 0;
|
||||
|
||||
// Calculate CRC of 82 bits (77 + 5 zeros)
|
||||
// 'The CRC is calculated on the source-encoded message, zero-extended from 77 to 82 bits'
|
||||
uint16_t checksum = ft8_crc(a91, 96 - 14);
|
||||
|
||||
// Store the CRC at the end of 77 bit message
|
||||
|
|
10
ft8/crc.h
10
ft8/crc.h
|
@ -9,10 +9,14 @@
|
|||
// [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[]);
|
||||
/// Extract the FT8 CRC of a packed message (during decoding)
|
||||
/// @param[in] a91 77 bits of payload data + CRC
|
||||
/// @return Extracted CRC
|
||||
uint16_t extract_crc(const uint8_t a91[]);
|
||||
|
||||
/// Add the FT8 CRC to a packed message (during encoding)
|
||||
void add_crc(uint8_t a91[]);
|
||||
/// @param[in] payload 77 bits of payload data
|
||||
/// @param[out] a91 91 bits of payload data + CRC
|
||||
void add_crc(const uint8_t payload[], uint8_t a91[]);
|
||||
|
||||
#endif // _INCLUDE_CRC_H_
|
36
ft8/decode.c
36
ft8/decode.c
|
@ -38,7 +38,7 @@ int find_sync(const waterfall_t *power, int num_candidates, candidate_t heap[],
|
|||
{
|
||||
for (int freq_sub = 0; freq_sub < power->freq_osr; ++freq_sub)
|
||||
{
|
||||
for (int time_offset = -8; time_offset < power->num_blocks - FT8_NN + 8; ++time_offset)
|
||||
for (int time_offset = -12; time_offset < power->num_blocks - FT8_NN + 12; ++time_offset)
|
||||
{
|
||||
for (int freq_offset = 0; freq_offset + 8 < power->num_bins; ++freq_offset)
|
||||
{
|
||||
|
@ -145,26 +145,36 @@ void extract_likelihood(const waterfall_t *power, const candidate_t *cand, float
|
|||
int sym_idx = (k < FT8_ND / 2) ? (k + 7) : (k + 14);
|
||||
int bit_idx = 3 * k;
|
||||
|
||||
// Pointer to 8 bins of the current symbol
|
||||
const uint8_t *ps = power->mag + offset + (sym_idx * sym_stride);
|
||||
int block = cand->time_offset + sym_idx;
|
||||
|
||||
decode_symbol(ps, bit_idx, log174);
|
||||
// Check for time boundaries
|
||||
if ((block < 0) || (block >= power->num_blocks))
|
||||
{
|
||||
log174[bit_idx] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pointer to 8 bins of the current symbol
|
||||
const uint8_t *ps = power->mag + offset + (sym_idx * sym_stride);
|
||||
|
||||
decode_symbol(ps, bit_idx, log174);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the variance of log174
|
||||
float sum = 0;
|
||||
float sum2 = 0;
|
||||
for (int i = 0; i < FT8_N; ++i)
|
||||
for (int i = 0; i < FT8_LDPC_N; ++i)
|
||||
{
|
||||
sum += log174[i];
|
||||
sum2 += log174[i] * log174[i];
|
||||
}
|
||||
float inv_n = 1.0f / FT8_N;
|
||||
float inv_n = 1.0f / FT8_LDPC_N;
|
||||
float variance = (sum2 - (sum * sum * inv_n)) * inv_n;
|
||||
|
||||
// Normalize log174 distribution and scale it with experimentally found coefficient
|
||||
float norm_factor = sqrtf(32.0f / variance);
|
||||
for (int i = 0; i < FT8_N; ++i)
|
||||
for (int i = 0; i < FT8_LDPC_N; ++i)
|
||||
{
|
||||
log174[i] *= norm_factor;
|
||||
}
|
||||
|
@ -172,10 +182,10 @@ void extract_likelihood(const waterfall_t *power, const candidate_t *cand, float
|
|||
|
||||
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
|
||||
float log174[FT8_LDPC_N]; // message bits encoded as likelihood
|
||||
extract_likelihood(power, cand, log174);
|
||||
|
||||
uint8_t plain174[FT8_N]; // message bits (0/1)
|
||||
uint8_t plain174[FT8_LDPC_N]; // message bits (0/1)
|
||||
bp_decode(log174, max_iterations, plain174, &status->ldpc_errors);
|
||||
// ldpc_decode(log174, max_iterations, plain174, &status->ldpc_errors);
|
||||
|
||||
|
@ -184,9 +194,9 @@ bool decode(const waterfall_t *power, const candidate_t *cand, message_t *messag
|
|||
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 payload + CRC (first FT8_LDPC_K bits) packed into a byte array
|
||||
uint8_t a91[FT8_LDPC_K_BYTES];
|
||||
pack_bits(plain174, FT8_LDPC_K, a91);
|
||||
|
||||
// Extract CRC and check it
|
||||
status->crc_extracted = extract_crc(a91);
|
||||
|
@ -321,7 +331,7 @@ static void decode_multi_symbols(const uint8_t *power, int num_bins, int n_syms,
|
|||
// 8 FSK tones = 3 bits
|
||||
for (int i = 0; i < n_bits; ++i)
|
||||
{
|
||||
if (bit_idx + i >= FT8_N)
|
||||
if (bit_idx + i >= FT8_LDPC_N)
|
||||
{
|
||||
// Respect array size
|
||||
break;
|
||||
|
|
164
ft8/encode.c
164
ft8/encode.c
|
@ -24,23 +24,23 @@ void encode174(const uint8_t *message, uint8_t *codeword)
|
|||
// This implementation accesses the generator bits straight from the packed binary representation in kFT8_LDPC_generator
|
||||
|
||||
// Fill the codeword with message and zeros, as we will only update binary ones later
|
||||
for (int j = 0; j < FT8_N_BYTES; ++j)
|
||||
for (int j = 0; j < FT8_LDPC_N_BYTES; ++j)
|
||||
{
|
||||
codeword[j] = (j < FT8_K_BYTES) ? message[j] : 0;
|
||||
codeword[j] = (j < FT8_LDPC_K_BYTES) ? message[j] : 0;
|
||||
}
|
||||
|
||||
// Compute the byte index and bit mask for the first checksum bit
|
||||
uint8_t col_mask = (0x80u >> (FT8_K % 8u)); // bitmask of current byte
|
||||
uint8_t col_idx = FT8_K_BYTES - 1; // index into byte array
|
||||
uint8_t col_mask = (0x80u >> (FT8_LDPC_K % 8u)); // bitmask of current byte
|
||||
uint8_t col_idx = FT8_LDPC_K_BYTES - 1; // index into byte array
|
||||
|
||||
// Compute the LDPC checksum bits and store them in codeword
|
||||
for (int i = 0; i < FT8_M; ++i)
|
||||
for (int i = 0; i < FT8_LDPC_M; ++i)
|
||||
{
|
||||
// Fast implementation of bitwise multiplication and parity checking
|
||||
// Normally nsum would contain the result of dot product between message and kFT8_LDPC_generator[i],
|
||||
// but we only compute the sum modulo 2.
|
||||
uint8_t nsum = 0;
|
||||
for (int j = 0; j < FT8_K_BYTES; ++j)
|
||||
for (int j = 0; j < FT8_LDPC_K_BYTES; ++j)
|
||||
{
|
||||
uint8_t bits = message[j] & kFT8_LDPC_generator[i][j]; // bitwise AND (bitwise multiplication)
|
||||
nsum ^= parity8(bits); // bitwise XOR (addition modulo 2)
|
||||
|
@ -62,74 +62,126 @@ void encode174(const uint8_t *message, uint8_t *codeword)
|
|||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
void genft8(const uint8_t *payload, uint8_t *itone)
|
||||
void genft8(const uint8_t *payload, uint8_t *tones)
|
||||
{
|
||||
uint8_t a91[12]; // Store 77 bits of payload + 14 bits CRC
|
||||
|
||||
// Copy 77 bits of payload data
|
||||
for (int i = 0; i < 10; i++)
|
||||
a91[i] = payload[i];
|
||||
|
||||
// Clear 3 bits after the payload to make 80 bits
|
||||
a91[9] &= 0xF8u;
|
||||
a91[10] = 0;
|
||||
a91[11] = 0;
|
||||
|
||||
// Compute and add CRC at the end of the message
|
||||
// a91 contains 77 bits of payload + 14 bits of CRC
|
||||
add_crc(a91);
|
||||
add_crc(payload, a91);
|
||||
|
||||
uint8_t codeword[22];
|
||||
encode174(a91, codeword);
|
||||
|
||||
// Message structure: S7 D29 S7 D29 S7
|
||||
for (int i = 0; i < 7; ++i)
|
||||
// Total symbols: 79 (FT8_NN)
|
||||
|
||||
uint8_t mask = 0x80u; // Mask to extract 1 bit from codeword
|
||||
int i_byte = 0; // Index of the current byte of the codeword
|
||||
for (int i_tone = 0; i_tone < FT8_NN; ++i_tone)
|
||||
{
|
||||
itone[i] = kFT8_Costas_pattern[i];
|
||||
itone[36 + i] = kFT8_Costas_pattern[i];
|
||||
itone[72 + i] = kFT8_Costas_pattern[i];
|
||||
}
|
||||
|
||||
int k = 7; // Skip over the first set of Costas symbols
|
||||
|
||||
uint8_t mask = 0x80u;
|
||||
int i_byte = 0;
|
||||
for (int j = 0; j < FT8_ND; ++j)
|
||||
{
|
||||
if (j == 29)
|
||||
if ((i_tone >= 0) && (i_tone < 7))
|
||||
{
|
||||
k += 7; // Skip over the second set of Costas symbols
|
||||
tones[i_tone] = kFT8_Costas_pattern[i_tone];
|
||||
}
|
||||
|
||||
// Extract 3 bits from codeword at i-th position
|
||||
uint8_t bits3 = 0;
|
||||
|
||||
if (codeword[i_byte] & mask)
|
||||
bits3 |= 4;
|
||||
if (0 == (mask >>= 1))
|
||||
else if ((i_tone >= 36) && (i_tone < 43))
|
||||
{
|
||||
mask = 0x80;
|
||||
i_byte++;
|
||||
tones[i_tone] = kFT8_Costas_pattern[i_tone - 36];
|
||||
}
|
||||
if (codeword[i_byte] & mask)
|
||||
bits3 |= 2;
|
||||
if (0 == (mask >>= 1))
|
||||
else if ((i_tone >= 72) && (i_tone < 79))
|
||||
{
|
||||
mask = 0x80;
|
||||
i_byte++;
|
||||
tones[i_tone] = kFT8_Costas_pattern[i_tone - 72];
|
||||
}
|
||||
if (codeword[i_byte] & mask)
|
||||
bits3 |= 1;
|
||||
if (0 == (mask >>= 1))
|
||||
else
|
||||
{
|
||||
mask = 0x80;
|
||||
i_byte++;
|
||||
}
|
||||
// Extract 3 bits from codeword at i-th position
|
||||
uint8_t bits3 = 0;
|
||||
|
||||
itone[k] = kFT8_Gray_map[bits3];
|
||||
++k;
|
||||
if (codeword[i_byte] & mask)
|
||||
bits3 |= 4;
|
||||
if (0 == (mask >>= 1))
|
||||
{
|
||||
mask = 0x80u;
|
||||
i_byte++;
|
||||
}
|
||||
if (codeword[i_byte] & mask)
|
||||
bits3 |= 2;
|
||||
if (0 == (mask >>= 1))
|
||||
{
|
||||
mask = 0x80u;
|
||||
i_byte++;
|
||||
}
|
||||
if (codeword[i_byte] & mask)
|
||||
bits3 |= 1;
|
||||
if (0 == (mask >>= 1))
|
||||
{
|
||||
mask = 0x80u;
|
||||
i_byte++;
|
||||
}
|
||||
|
||||
tones[i_tone] = kFT8_Gray_map[bits3];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void genft4(const uint8_t *payload, uint8_t *tones)
|
||||
{
|
||||
uint8_t a91[12]; // Store 77 bits of payload + 14 bits CRC
|
||||
|
||||
// Compute and add CRC at the end of the message
|
||||
// a91 contains 77 bits of payload + 14 bits of CRC
|
||||
add_crc(payload, a91);
|
||||
|
||||
uint8_t codeword[22];
|
||||
encode174(a91, codeword); // 91 bits -> 174 bits
|
||||
|
||||
// Message structure: R S4_1 D29 S4_2 D29 S4_3 D29 S4_4 R
|
||||
// Total symbols: 105 (FT4_NN)
|
||||
|
||||
uint8_t mask = 0x80u; // Mask to extract 1 bit from codeword
|
||||
int i_byte = 0; // Index of the current byte of the codeword
|
||||
for (int i_tone = 0; i_tone < FT4_NN; ++i_tone)
|
||||
{
|
||||
if ((i_tone == 0) || (i_tone == 104))
|
||||
{
|
||||
tones[i_tone] = 0; // R (ramp) symbol
|
||||
}
|
||||
else if ((i_tone >= 1) && (i_tone < 5))
|
||||
{
|
||||
tones[i_tone] = kFT4_Costas_pattern[0][i_tone - 1];
|
||||
}
|
||||
else if ((i_tone >= 34) && (i_tone < 38))
|
||||
{
|
||||
tones[i_tone] = kFT4_Costas_pattern[1][i_tone - 34];
|
||||
}
|
||||
else if ((i_tone >= 67) && (i_tone < 71))
|
||||
{
|
||||
tones[i_tone] = kFT4_Costas_pattern[2][i_tone - 67];
|
||||
}
|
||||
else if ((i_tone >= 100) && (i_tone < 104))
|
||||
{
|
||||
tones[i_tone] = kFT4_Costas_pattern[3][i_tone - 100];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Extract 2 bits from codeword at i-th position
|
||||
uint8_t bits2 = 0;
|
||||
|
||||
if (codeword[i_byte] & mask)
|
||||
bits2 |= 2;
|
||||
if (0 == (mask >>= 1))
|
||||
{
|
||||
mask = 0x80u;
|
||||
i_byte++;
|
||||
}
|
||||
if (codeword[i_byte] & mask)
|
||||
bits2 |= 1;
|
||||
if (0 == (mask >>= 1))
|
||||
{
|
||||
mask = 0x80u;
|
||||
i_byte++;
|
||||
}
|
||||
tones[i_tone] = kFT4_Gray_map[bits2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
25
ft8/encode.h
25
ft8/encode.h
|
@ -3,9 +3,30 @@
|
|||
|
||||
#include <stdint.h>
|
||||
|
||||
// typedef struct
|
||||
// {
|
||||
// uint8_t tones[FT8_NN];
|
||||
// // for waveform readout:
|
||||
// int n_spsym; // Number of waveform samples per symbol
|
||||
// float *pulse; // [3 * n_spsym]
|
||||
// int idx_symbol; // Index of the current symbol
|
||||
// float f0; // Base frequency, Hertz
|
||||
// float signal_rate; // Waveform sample rate, Hertz
|
||||
// } encoder_t;
|
||||
|
||||
// void encoder_init(float signal_rate, float *pulse_buffer);
|
||||
// void encoder_set_f0(float f0);
|
||||
// void encoder_process(const message_t *message); // in: message
|
||||
// void encoder_generate(float *block); // out: block of waveforms
|
||||
|
||||
/// Generate FT8 tone sequence from payload data
|
||||
/// @param[in] payload - 10 byte array consisting of 77 bit payload
|
||||
/// @param[out] itone - array of NN (79) bytes to store the generated tones (encoded as 0..7)
|
||||
void genft8(const uint8_t *payload, uint8_t *itone);
|
||||
/// @param[out] tones - array of FT8_NN (79) bytes to store the generated tones (encoded as 0..7)
|
||||
void genft8(const uint8_t *payload, uint8_t *tones);
|
||||
|
||||
/// Generate FT4 tone sequence from payload data
|
||||
/// @param[in] payload - 10 byte array consisting of 77 bit payload
|
||||
/// @param[out] tones - array of FT4_NN (105) bytes to store the generated tones (encoded as 0..3)
|
||||
void genft4(const uint8_t *payload, uint8_t *tones);
|
||||
|
||||
#endif // _INCLUDE_ENCODE_H_
|
||||
|
|
32
ft8/ldpc.c
32
ft8/ldpc.c
|
@ -54,13 +54,13 @@ void pack_bits(const uint8_t plain[], int num_bits, uint8_t packed[])
|
|||
// ok == 87 means success.
|
||||
void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
|
||||
{
|
||||
float m[FT8_M][FT8_N]; // ~60 kB
|
||||
float e[FT8_M][FT8_N]; // ~60 kB
|
||||
int min_errors = FT8_M;
|
||||
float m[FT8_LDPC_M][FT8_LDPC_N]; // ~60 kB
|
||||
float e[FT8_LDPC_M][FT8_LDPC_N]; // ~60 kB
|
||||
int min_errors = FT8_LDPC_M;
|
||||
|
||||
for (int j = 0; j < FT8_M; j++)
|
||||
for (int j = 0; j < FT8_LDPC_M; j++)
|
||||
{
|
||||
for (int i = 0; i < FT8_N; i++)
|
||||
for (int i = 0; i < FT8_LDPC_N; i++)
|
||||
{
|
||||
m[j][i] = codeword[i];
|
||||
e[j][i] = 0.0f;
|
||||
|
@ -69,7 +69,7 @@ void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
|
|||
|
||||
for (int iter = 0; iter < max_iters; iter++)
|
||||
{
|
||||
for (int j = 0; j < FT8_M; j++)
|
||||
for (int j = 0; j < FT8_LDPC_M; j++)
|
||||
{
|
||||
for (int ii1 = 0; ii1 < kFT8_LDPC_num_rows[j]; ii1++)
|
||||
{
|
||||
|
@ -87,7 +87,7 @@ void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
|
|||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < FT8_N; i++)
|
||||
for (int i = 0; i < FT8_LDPC_N; i++)
|
||||
{
|
||||
float l = codeword[i];
|
||||
for (int j = 0; j < 3; j++)
|
||||
|
@ -108,7 +108,7 @@ void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
|
|||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < FT8_N; i++)
|
||||
for (int i = 0; i < FT8_LDPC_N; i++)
|
||||
{
|
||||
for (int ji1 = 0; ji1 < 3; ji1++)
|
||||
{
|
||||
|
@ -139,7 +139,7 @@ static int ldpc_check(uint8_t codeword[])
|
|||
{
|
||||
int errors = 0;
|
||||
|
||||
for (int m = 0; m < FT8_M; ++m)
|
||||
for (int m = 0; m < FT8_LDPC_M; ++m)
|
||||
{
|
||||
uint8_t x = 0;
|
||||
for (int i = 0; i < kFT8_LDPC_num_rows[m]; ++i)
|
||||
|
@ -156,13 +156,13 @@ static int ldpc_check(uint8_t codeword[])
|
|||
|
||||
void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
|
||||
{
|
||||
float tov[FT8_N][3];
|
||||
float toc[FT8_M][7];
|
||||
float tov[FT8_LDPC_N][3];
|
||||
float toc[FT8_LDPC_M][7];
|
||||
|
||||
int min_errors = FT8_M;
|
||||
int min_errors = FT8_LDPC_M;
|
||||
|
||||
// initialize message data
|
||||
for (int n = 0; n < FT8_N; ++n)
|
||||
for (int n = 0; n < FT8_LDPC_N; ++n)
|
||||
{
|
||||
tov[n][0] = tov[n][1] = tov[n][2] = 0;
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
|
|||
{
|
||||
// Do a hard decision guess (tov=0 in iter 0)
|
||||
int plain_sum = 0;
|
||||
for (int n = 0; n < FT8_N; ++n)
|
||||
for (int n = 0; n < FT8_LDPC_N; ++n)
|
||||
{
|
||||
plain[n] = ((codeword[n] + tov[n][0] + tov[n][1] + tov[n][2]) > 0) ? 1 : 0;
|
||||
plain_sum += plain[n];
|
||||
|
@ -198,7 +198,7 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
|
|||
}
|
||||
|
||||
// Send messages from bits to check nodes
|
||||
for (int m = 0; m < FT8_M; ++m)
|
||||
for (int m = 0; m < FT8_LDPC_M; ++m)
|
||||
{
|
||||
for (int n_idx = 0; n_idx < kFT8_LDPC_num_rows[m]; ++n_idx)
|
||||
{
|
||||
|
@ -217,7 +217,7 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
|
|||
}
|
||||
|
||||
// send messages from check nodes to variable nodes
|
||||
for (int n = 0; n < FT8_N; ++n)
|
||||
for (int n = 0; n < FT8_LDPC_N; ++n)
|
||||
{
|
||||
for (int m_idx = 0; m_idx < 3; ++m_idx)
|
||||
{
|
||||
|
|
85
gen_ft8.c
85
gen_ft8.c
|
@ -12,6 +12,16 @@
|
|||
|
||||
#define LOG_LEVEL LOG_INFO
|
||||
|
||||
#define FT8_SLOT_TIME 15.0f // total length of output waveform in seconds
|
||||
#define FT8_SYMBOL_RATE 6.25f // tone deviation (and symbol rate) in Hz
|
||||
#define FT8_SYMBOL_BT 2.0f // symbol smoothing filter bandwidth factor (BT)
|
||||
|
||||
#define FT4_SLOT_TIME 7.5f // total length of output waveform in seconds
|
||||
#define FT4_SYMBOL_RATE 20.833333f // tone deviation (and symbol rate) in Hz
|
||||
#define FT4_SYMBOL_BT 1.0f // symbol smoothing filter bandwidth factor (BT)
|
||||
|
||||
#define GFSK_CONST_K 5.336446f // pi * sqrt(2 / log(2))
|
||||
|
||||
/// Computes a GFSK smoothing pulse.
|
||||
/// The pulse is theoretically infinitely long, however, here it's truncated at 3 times the symbol length.
|
||||
/// This means the pulse array has to have space for 3*n_spsym elements.
|
||||
|
@ -19,34 +29,36 @@
|
|||
/// @param[in] b Shape parameter (values defined for FT8/FT4)
|
||||
/// @param[out] pulse Output array of pulse samples
|
||||
///
|
||||
void gfsk_pulse(int n_spsym, float b, float *pulse)
|
||||
void gfsk_pulse(int n_spsym, float symbol_bt, float *pulse)
|
||||
{
|
||||
float c = M_PI * sqrtf(2 / logf(2));
|
||||
|
||||
for (int i = 0; i < 3 * n_spsym; ++i)
|
||||
{
|
||||
float t = i / (float)n_spsym - 1.5f;
|
||||
pulse[i] = (erff(c * b * (t + 0.5f)) - erff(c * b * (t - 0.5f))) / 2;
|
||||
float arg1 = GFSK_CONST_K * symbol_bt * (t + 0.5f);
|
||||
float arg2 = GFSK_CONST_K * symbol_bt * (t - 0.5f);
|
||||
pulse[i] = (erff(arg1) - erff(arg2)) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
/// Synthesize waveform data using GFSK phase shaping.
|
||||
/// The output waveform will contain n_sym+2 symbols (extra symbol at the beginning/end).
|
||||
/// The output waveform will contain n_sym symbols.
|
||||
/// @param[in] symbols Array of symbols (tones) (0-7 for FT8)
|
||||
/// @param[in] n_sym Number of symbols in the symbols array
|
||||
/// @param[in] n_sym Number of symbols in the symbol array
|
||||
/// @param[in] f0 Audio frequency in Hertz for the symbol 0 (base frequency)
|
||||
/// @param[in] n_spsym Number of samples per symbol (only integer number of samples supported)
|
||||
/// @param[in] symbol_bt Symbol smoothing filter bandwidth (2 for FT8, 1 for FT4)
|
||||
/// @param[in] symbol_rate Rate of symbols per second, Hertz
|
||||
/// @param[in] signal_rate Sample rate of synthesized signal, Hertz
|
||||
/// @param[out] signal Output array of signal waveform samples (should have space for n_spsym*(n_sym+2) samples)
|
||||
/// @param[out] signal Output array of signal waveform samples (should have space for n_sym*n_spsym samples)
|
||||
///
|
||||
void synth_gfsk(const uint8_t *symbols, int n_sym, float f0, int n_spsym, int signal_rate, float *signal)
|
||||
void synth_gfsk(const uint8_t *symbols, int n_sym, float f0, float symbol_bt, float symbol_rate, int signal_rate, float *signal)
|
||||
{
|
||||
LOG(LOG_DEBUG, "n_spsym = %d\n", n_spsym);
|
||||
int n_wave = n_sym * n_spsym; // Number of output samples
|
||||
int n_spsym = (int)(0.5f + signal_rate / symbol_rate); // Samples per symbol
|
||||
int n_wave = n_sym * n_spsym; // Number of output samples
|
||||
float hmod = 1.0f;
|
||||
|
||||
LOG(LOG_DEBUG, "n_spsym = %d\n", n_spsym);
|
||||
// Compute the smoothed frequency waveform.
|
||||
// Length = (nsym+2)*nsps samples, first and last symbols extended
|
||||
// Length = (nsym+2)*n_spsym samples, first and last symbols extended
|
||||
float dphi_peak = 2 * M_PI * hmod / n_spsym;
|
||||
float dphi[n_wave + 2 * n_spsym];
|
||||
|
||||
|
@ -57,7 +69,7 @@ void synth_gfsk(const uint8_t *symbols, int n_sym, float f0, int n_spsym, int si
|
|||
}
|
||||
|
||||
float pulse[3 * n_spsym];
|
||||
gfsk_pulse(n_spsym, 2.0f, pulse);
|
||||
gfsk_pulse(n_spsym, symbol_bt, pulse);
|
||||
|
||||
for (int i = 0; i < n_sym; ++i)
|
||||
{
|
||||
|
@ -119,9 +131,10 @@ int main(int argc, char **argv)
|
|||
{
|
||||
frequency = atof(argv[3]);
|
||||
}
|
||||
bool is_ft4 = (argc > 4) && (0 == strcmp(argv[4], "-ft4"));
|
||||
|
||||
// First, pack the text data into binary message
|
||||
uint8_t packed[FT8_K_BYTES];
|
||||
uint8_t packed[FT8_LDPC_K_BYTES];
|
||||
int rc = pack77(message, packed);
|
||||
if (rc < 0)
|
||||
{
|
||||
|
@ -137,30 +150,52 @@ int main(int argc, char **argv)
|
|||
}
|
||||
printf("\n");
|
||||
|
||||
if (is_ft4)
|
||||
{
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
packed[i] ^= kFT4_XOR_sequence[i];
|
||||
}
|
||||
}
|
||||
|
||||
int num_tones = (is_ft4) ? FT4_NN : FT8_NN;
|
||||
float symbol_rate = (is_ft4) ? FT4_SYMBOL_RATE : FT8_SYMBOL_RATE;
|
||||
float symbol_bt = (is_ft4) ? FT4_SYMBOL_BT : FT8_SYMBOL_BT;
|
||||
float slot_time = (is_ft4) ? FT4_SLOT_TIME : FT8_SLOT_TIME;
|
||||
|
||||
// Second, encode the binary message as a sequence of FSK tones
|
||||
uint8_t tones[FT8_NN]; // Array of 79 tones (symbols)
|
||||
genft8(packed, tones);
|
||||
uint8_t tones[num_tones]; // Array of 79 tones (symbols)
|
||||
if (is_ft4)
|
||||
{
|
||||
genft4(packed, tones);
|
||||
}
|
||||
else
|
||||
{
|
||||
genft8(packed, tones);
|
||||
}
|
||||
|
||||
printf("FSK tones: ");
|
||||
for (int j = 0; j < FT8_NN; ++j)
|
||||
for (int j = 0; j < num_tones; ++j)
|
||||
{
|
||||
printf("%d", tones[j]);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
// Third, convert the FSK tones into an audio signal
|
||||
const int sample_rate = 12000;
|
||||
const float symbol_rate = 6.25f;
|
||||
const int num_samples = (int)(0.5f + FT8_NN / symbol_rate * sample_rate);
|
||||
const int num_silence = (15 * sample_rate - num_samples) / 2;
|
||||
float signal[num_silence + num_samples + num_silence];
|
||||
for (int i = 0; i < num_silence + num_samples + num_silence; i++)
|
||||
int sample_rate = 12000;
|
||||
int num_samples = (int)(0.5f + num_tones / symbol_rate * sample_rate); // Number of samples in the data signal
|
||||
int num_silence = (slot_time * sample_rate - num_samples) / 2; // Silence padding at both ends to make 15 seconds
|
||||
int num_total_samples = num_silence + num_samples + num_silence; // Number of samples in the padded signal
|
||||
float signal[num_total_samples];
|
||||
for (int i = 0; i < num_silence; i++)
|
||||
{
|
||||
signal[i] = 0;
|
||||
signal[i + num_samples + num_silence] = 0;
|
||||
}
|
||||
|
||||
synth_gfsk(tones, FT8_NN, frequency, sample_rate / symbol_rate, sample_rate, signal + num_silence);
|
||||
save_wav(signal, num_silence + num_samples + num_silence, sample_rate, wav_path);
|
||||
// Synthesize waveform data (signal) and save it as WAV file
|
||||
synth_gfsk(tones, num_tones, frequency, symbol_bt, symbol_rate, sample_rate, signal + num_silence);
|
||||
save_wav(signal, num_total_samples, sample_rate, wav_path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue