Fixed FT8 v2.0 encoding

This commit is contained in:
Karlis Goba 2018-10-29 15:28:46 +02:00
parent 41e7aef8b8
commit c2f10e8cb2
6 changed files with 95 additions and 49 deletions

View file

@ -1,5 +1,5 @@
CXXFLAGS = -std=c++14 CXXFLAGS = -std=c++14
LDFLAGS = -lm LDFLAGS = -lm
gen_ft8: gen_ft8.o encode.o pack.o text.o gen_ft8: gen_ft8.o encode.o pack.o text.o pack_77.o encode_91.o
$(CXX) $(LDFLAGS) -o $@ $^ $(CXX) $(LDFLAGS) -o $@ $^

View file

@ -228,8 +228,8 @@ void genft8(const uint8_t *payload, uint8_t i3, uint8_t *itone) {
// Append 3 bits of i3 at the end of 72 bit payload // Append 3 bits of i3 at the end of 72 bit payload
a87[9] = ((i3 & 0x07) << 5); a87[9] = ((i3 & 0x07) << 5);
// Calculate CRC of 76 bits (yes, 72 + 3 + 1 zero bit), see WSJT-X code // Calculate CRC of 11 bytes = 88 bits, see WSJT-X code
uint16_t checksum = ft8_crc(a87, 76); uint16_t checksum = ft8_crc(a87, 88 - 12);
// Store the CRC at the end of 75 bit message (yes, 72 + 3) // Store the CRC at the end of 75 bit message (yes, 72 + 3)
uint16_t tmp = (checksum << 1); uint16_t tmp = (checksum << 1);

View file

@ -1,5 +1,7 @@
#include "encode.h" #include "encode.h"
#include <stdio.h>
namespace ft8_v2 { namespace ft8_v2 {
constexpr int N = 174, K = 91, M = N-K; // Define the LDPC sizes constexpr int N = 174, K = 91, M = N-K; // Define the LDPC sizes
@ -109,8 +111,10 @@ const uint8_t colorder[N] = {
}; };
// Costas 7x7 tone pattern // Costas 7x7 tone pattern
const uint8_t ICOS7[] = { 2,5,6,0,4,1,3 }; const uint8_t ICOS7[] = { 3,1,4,0,6,5,2 };
// Gray code map
const uint8_t GRAY[8] = { 0,1,3,2,5,6,4,7 };
// Returns 1 if an odd number of bits are set in x, zero otherwise // Returns 1 if an odd number of bits are set in x, zero otherwise
uint8_t parity8(uint8_t x) { uint8_t parity8(uint8_t x) {
@ -121,26 +125,26 @@ uint8_t parity8(uint8_t x) {
} }
// Encode an 87-bit message and return a 174-bit codeword. // Encode a 91-bit message and return a 174-bit codeword.
// The generator matrix has dimensions (87,87). // The generator matrix has dimensions (87,87).
// The code is a (174,87) regular ldpc code with column weight 3. // The code is a (174,91) regular ldpc code with column weight 3.
// The code was generated using the PEG algorithm. // The code was generated using the PEG algorithm.
// After creating the codeword, the columns are re-ordered according to
// "colorder" to make the codeword compatible with the parity-check matrix
// Arguments: // Arguments:
// [IN] message - array of 87 bits stored as 11 bytes (MSB first) // [IN] message - array of 91 bits stored as 12 bytes (MSB first)
// [OUT] codeword - array of 174 bits stored as 22 bytes (MSB first) // [OUT] codeword - array of 174 bits stored as 22 bytes (MSB first)
void encode174(const uint8_t *message, uint8_t *codeword) { void encode174(const uint8_t *message, uint8_t *codeword) {
// Here we don't generate the generator bit matrix as in WSJT-X implementation // Here we don't generate the generator bit matrix as in WSJT-X implementation
// Instead we access the generator bits straight from the binary representation in G // Instead we access the generator bits straight from the binary representation in G
// Also we don't use the itmp temporary buffer, instead filling codeword bit by bit
// in the reordered order as we compute the result.
// For reference: // For reference:
// itmp(1:M)=pchecks // codeword(1:K)=message
// itmp(M+1:N)=message(1:K) // codeword(K+1:N)=pchecks
// codeword(colorder+1)=itmp(1:N)
printf("Encode ");
for (int i = 0; i < K_BYTES; ++i) {
printf("%02x ", message[i]);
}
printf("\n");
int colidx = 0; // track the current column in codeword int colidx = 0; // track the current column in codeword
@ -149,6 +153,22 @@ void encode174(const uint8_t *message, uint8_t *codeword) {
codeword[i] = 0; codeword[i] = 0;
} }
// Compute the second part of itmp (M+1:N) and store the result in codeword
uint8_t mask = 0x80; // Rolling mask starting with the MSB
for (int j = 0; j < K; ++j) {
// Copy the j-th bit from message to codeword
if (message[j/8] & mask) {
//uint8_t col = colorder[colidx]; // Index of the bit to set
uint8_t col = colidx;
codeword[col/8] |= (1 << (7 - col%8));
}
++colidx;
// Roll the bitmask to the right
mask >>= 1;
if (mask == 0) mask = 0x80;
}
// Compute the first part of itmp (1:M) and store the result in codeword // Compute the first part of itmp (1:M) and store the result in codeword
for (int i = 0; i < M; ++i) { // do i=1,M for (int i = 0; i < M; ++i) { // do i=1,M
// Fast implementation of bitwise multiplication and parity checking // Fast implementation of bitwise multiplication and parity checking
@ -161,26 +181,18 @@ void encode174(const uint8_t *message, uint8_t *codeword) {
} }
// Check if we need to set a bit in codeword // Check if we need to set a bit in codeword
if (nsum % 2) { // pchecks(i)=mod(nsum,2) if (nsum % 2) { // pchecks(i)=mod(nsum,2)
uint8_t col = colorder[colidx]; // Index of the bit to set //uint8_t col = colorder[colidx]; // Index of the bit to set
uint8_t col = colidx;
codeword[col/8] |= (1 << (7 - col%8)); codeword[col/8] |= (1 << (7 - col%8));
} }
++colidx; ++colidx;
} }
// Compute the second part of itmp (M+1:N) and store the result in codeword printf("Result ");
uint8_t mask = 0x80; // Rolling mask starting with the MSB for (int i = 0; i < (N + 7) / 8; ++i) {
for (int j = 0; j < K; ++j) { printf("%02x ", codeword[i]);
// Copy the j-th bit from message to codeword
if (message[j/8] & mask) {
uint8_t col = colorder[colidx]; // Index of the bit to set
codeword[col/8] |= (1 << (7 - col%8));
}
++colidx;
// Roll the bitmask to the right
mask >>= 1;
if (mask == 0) mask = 0x80;
} }
printf("\n");
} }
@ -192,6 +204,12 @@ uint16_t ft8_crc(uint8_t *message, int num_bits) {
constexpr int WIDTH = 14; constexpr int WIDTH = 14;
constexpr uint16_t TOPBIT = (1 << (WIDTH - 1)); constexpr uint16_t TOPBIT = (1 << (WIDTH - 1));
printf("CRC, %d bits: ", num_bits);
for (int i = 0; i < (num_bits + 7) / 8; ++i) {
printf("%02x ", message[i]);
}
printf("\n");
uint16_t remainder = 0; uint16_t remainder = 0;
int idx_byte = 0; int idx_byte = 0;
@ -211,6 +229,7 @@ uint16_t ft8_crc(uint8_t *message, int num_bits) {
remainder = (remainder << 1); remainder = (remainder << 1);
} }
} }
printf("CRC = %04xh\n", remainder & ((1 << WIDTH) - 1));
return remainder & ((1 << WIDTH) - 1); return remainder & ((1 << WIDTH) - 1);
} }
@ -227,13 +246,18 @@ void genft8(const uint8_t *payload, uint8_t *itone) {
// Clear 3 bits after the payload to make 80 bits // Clear 3 bits after the payload to make 80 bits
a91[9] &= 0xF8; a91[9] &= 0xF8;
a91[10] = 0;
a91[11] = 0;
// Calculate CRC of 80 bits (yes, 77 + 3 zero bits), see WSJT-X code // Calculate CRC of 12 bytes = 96 bits, see WSJT-X code
uint16_t checksum = ft8_crc(a91, 80); uint16_t checksum = ft8_crc(a91, 96 - 14);
// 3dcf = 0011 1101 1100 1111
// 111 1011 1001 111
// Store the CRC at the end of 77 bit message // Store the CRC at the end of 77 bit message
a91[9] |= (uint8_t)(checksum >> 11); a91[9] |= (uint8_t)(checksum >> 11);
a91[10] = (uint8_t)(checksum >> 3); a91[10] = (uint8_t)(checksum >> 3);
a91[11] = (uint8_t)(checksum << 5);
// a87 contains 77 bits of payload + 14 bits of CRC // a87 contains 77 bits of payload + 14 bits of CRC
uint8_t codeword[22]; uint8_t codeword[22];
@ -247,18 +271,27 @@ void genft8(const uint8_t *payload, uint8_t *itone) {
} }
int k = 7; // Skip over the first set of Costas symbols int k = 7; // Skip over the first set of Costas symbols
uint8_t mask = 0x80;
int i_byte = 0;
for (int j = 0; j < ND; ++j) { // do j=1,ND for (int j = 0; j < ND; ++j) { // do j=1,ND
if (j == 29) { if (j == 29) {
k += 7; // Skip over the second set of Costas symbols k += 7; // Skip over the second set of Costas symbols
} }
// Extract 3 bits from codeword at i-th position // Extract 3 bits from codeword at i-th position
itone[k] = 0; uint8_t bits3 = 0;
int i = 3*j;
if (codeword[i/8] & (1 << (7 - i%8))) itone[k] |= 4; if (codeword[i_byte] & mask) bits3 |= 4;
++i; if (0 == (mask >>= 1)) { mask = 0x80; i_byte++; }
if (codeword[i/8] & (1 << (7 - i%8))) itone[k] |= 2; if (codeword[i_byte] & mask) bits3 |= 2;
++i; if (0 == (mask >>= 1)) { mask = 0x80; i_byte++; }
if (codeword[i/8] & (1 << (7 - i%8))) itone[k] |= 1; if (codeword[i_byte] & mask) bits3 |= 1;
if (0 == (mask >>= 1)) { mask = 0x80; i_byte++; }
// Before Gray: 2560413 00000000117314000020544743734 2560413 74502537003717523102453566214 2560413
// After Gray: 2560413 00000000117215000030655752725 2560413 75603627002717632103562644315 2560413
itone[k] = GRAY[bits3];
++k; ++k;
} }
} }

View file

@ -6,6 +6,9 @@
#include "pack.h" #include "pack.h"
#include "encode.h" #include "encode.h"
#include "pack_77.h"
#include "encode_91.h"
void convert_8bit_to_6bit(uint8_t *dst, const uint8_t *src, int nBits) { void convert_8bit_to_6bit(uint8_t *dst, const uint8_t *src, int nBits) {
// Zero-fill the destination array as we will only be setting bits later // Zero-fill the destination array as we will only be setting bits later
@ -169,8 +172,9 @@ int main(int argc, char **argv) {
const char *wav_path = argv[2]; const char *wav_path = argv[2];
// First, pack the text data into 72-bit binary message // First, pack the text data into 72-bit binary message
uint8_t packed[9]; uint8_t packed[10];
int rc = packmsg(message, packed); //int rc = packmsg(message, packed);
int rc = ft8_v2::pack77(message, packed);
if (rc < 0) { if (rc < 0) {
printf("Cannot parse message!\n"); printf("Cannot parse message!\n");
printf("RC = %d\n", rc); printf("RC = %d\n", rc);
@ -178,14 +182,15 @@ int main(int argc, char **argv) {
} }
printf("Packed data: "); printf("Packed data: ");
for (int j = 0; j < 9; ++j) { for (int j = 0; j < 10; ++j) {
printf("%02x ", packed[j]); printf("%02x ", packed[j]);
} }
printf("\n"); printf("\n");
// Second, encode the binary message as a sequence of FSK tones // Second, encode the binary message as a sequence of FSK tones
uint8_t tones[NN]; // NN = 79, lack of better name at the moment uint8_t tones[NN]; // NN = 79, lack of better name at the moment
genft8(packed, 0, tones); //genft8(packed, 0, tones);
ft8_v2::genft8(packed, tones);
printf("FSK tones: "); printf("FSK tones: ");
for (int j = 0; j < NN; ++j) { for (int j = 0; j < NN; ++j) {

View file

@ -3,6 +3,8 @@
#include "text.h" #include "text.h"
#include <stdio.h>
namespace ft8_v2 { namespace ft8_v2 {
// TODO: This is wasteful, should figure out something more elegant // TODO: This is wasteful, should figure out something more elegant
@ -77,6 +79,7 @@ int32_t pack28(const char *callsign) {
(i2 = index(A3, c6[2])) >= 0 && (i3 = index(A4, c6[3])) >= 0 && (i2 = index(A3, c6[2])) >= 0 && (i3 = index(A4, c6[3])) >= 0 &&
(i4 = index(A4, c6[4])) >= 0 && (i5 = index(A4, c6[5])) >= 0) (i4 = index(A4, c6[4])) >= 0 && (i5 = index(A4, c6[5])) >= 0)
{ {
printf("Pack28: idx=[%d, %d, %d, %d, %d, %d]\n", i0, i1, i2, i3, i4, i5);
// This is a standard callsign // This is a standard callsign
int32_t n28 = i0; int32_t n28 = i0;
n28 = n28 * 36 + i1; n28 = n28 * 36 + i1;
@ -84,6 +87,7 @@ int32_t pack28(const char *callsign) {
n28 = n28 * 27 + i3; n28 = n28 * 27 + i3;
n28 = n28 * 27 + i4; n28 = n28 * 27 + i4;
n28 = n28 * 27 + i5; n28 = n28 * 27 + i5;
printf("Pack28: n28=%d (%04xh)\n", n28, n28);
return NTOKENS + MAX22 + n28; return NTOKENS + MAX22 + n28;
} }
@ -144,10 +148,10 @@ uint16_t packgrid(const char *grid4) {
is_digit(grid4[2]) && is_digit(grid4[3])) is_digit(grid4[2]) && is_digit(grid4[3]))
{ {
//if (w(3).eq.'R ') ir=1 //if (w(3).eq.'R ') ir=1
uint16_t igrid4 = (grid4[3] - '0'); uint16_t igrid4 = (grid4[0] - 'A');
igrid4 = igrid4 * 18 + (grid4[1] - 'A');
igrid4 = igrid4 * 10 + (grid4[2] - '0'); igrid4 = igrid4 * 10 + (grid4[2] - '0');
igrid4 = igrid4 * 10 + (grid4[1] - 'A'); igrid4 = igrid4 * 10 + (grid4[3] - '0');
igrid4 = igrid4 * 18 + (grid4[0] - 'A');
return igrid4; return igrid4;
} }
@ -208,14 +212,18 @@ int pack77_1(const char *msg, uint8_t *b77) {
// write(c77,1000) n28a,ipa,n28b,ipb,ir,igrid4,i3 // write(c77,1000) n28a,ipa,n28b,ipb,ir,igrid4,i3
// 1000 format(2(b28.28,b1),b1,b15.15,b3.3) // 1000 format(2(b28.28,b1),b1,b15.15,b3.3)
// 00 00 00 27 b3 00 01 0b 27 8f b9 e0
// (00 00 00 2) 0 (f0 85 ab e) (0)
// 7842D5F
b77[0] = (n28a >> 21); b77[0] = (n28a >> 21);
b77[1] = (n28a >> 13); b77[1] = (n28a >> 13);
b77[2] = (n28a >> 5); b77[2] = (n28a >> 5);
b77[3] = (uint8_t)(n28a << 3) | (uint8_t)(n28b >> 26); b77[3] = (uint8_t)(n28a << 3) | (uint8_t)(n28b >> 26);
b77[4] = (n28b >> 18); b77[4] = (n28b >> 18);
b77[5] = (n28a >> 10); b77[5] = (n28b >> 10);
b77[6] = (n28a >> 2); b77[6] = (n28b >> 2);
b77[7] = (uint8_t)(n28a << 6) | (uint8_t)(igrid4 >> 10); b77[7] = (uint8_t)(n28b << 6) | (uint8_t)(igrid4 >> 10);
b77[8] = (igrid4 >> 2); b77[8] = (igrid4 >> 2);
b77[9] = (uint8_t)(igrid4 << 6) | (uint8_t)(i3 << 3); b77[9] = (uint8_t)(igrid4 << 6) | (uint8_t)(i3 << 3);

View file

@ -7,6 +7,6 @@ namespace ft8_v2 {
// Pack FT8 text message into 72 bits // Pack FT8 text message into 72 bits
// [IN] msg - FT8 message (e.g. "CQ TE5T KN01") // [IN] msg - FT8 message (e.g. "CQ TE5T KN01")
// [OUT] c77 - 10 byte array to store the 77 bit payload (MSB first) // [OUT] c77 - 10 byte array to store the 77 bit payload (MSB first)
int pack77(const char *msg, uint8_t *c77) int pack77(const char *msg, uint8_t *c77);
}; };