From c2f10e8cb2142587f2c23351d68abfca49160252 Mon Sep 17 00:00:00 2001 From: Karlis Goba Date: Mon, 29 Oct 2018 15:28:46 +0200 Subject: [PATCH] Fixed FT8 v2.0 encoding --- Makefile | 2 +- encode.cpp | 4 +- encode_91.cpp | 103 +++++++++++++++++++++++++++++++++----------------- gen_ft8.cpp | 13 +++++-- pack_77.cpp | 20 +++++++--- pack_77.h | 2 +- 6 files changed, 95 insertions(+), 49 deletions(-) diff --git a/Makefile b/Makefile index d8b0936..b50ff1b 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ CXXFLAGS = -std=c++14 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 $@ $^ diff --git a/encode.cpp b/encode.cpp index f0a2a9d..7081897 100644 --- a/encode.cpp +++ b/encode.cpp @@ -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 a87[9] = ((i3 & 0x07) << 5); - // Calculate CRC of 76 bits (yes, 72 + 3 + 1 zero bit), see WSJT-X code - uint16_t checksum = ft8_crc(a87, 76); + // Calculate CRC of 11 bytes = 88 bits, see WSJT-X code + uint16_t checksum = ft8_crc(a87, 88 - 12); // Store the CRC at the end of 75 bit message (yes, 72 + 3) uint16_t tmp = (checksum << 1); diff --git a/encode_91.cpp b/encode_91.cpp index a23a226..197b03f 100644 --- a/encode_91.cpp +++ b/encode_91.cpp @@ -1,5 +1,7 @@ #include "encode.h" +#include + namespace ft8_v2 { 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 -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 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 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. -// After creating the codeword, the columns are re-ordered according to -// "colorder" to make the codeword compatible with the parity-check matrix // 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) void encode174(const uint8_t *message, uint8_t *codeword) { // 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 - // 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: - // itmp(1:M)=pchecks - // itmp(M+1:N)=message(1:K) - // codeword(colorder+1)=itmp(1:N) + // codeword(1:K)=message + // codeword(K+1:N)=pchecks + + 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 @@ -149,6 +153,22 @@ void encode174(const uint8_t *message, uint8_t *codeword) { 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 for (int i = 0; i < M; ++i) { // do i=1,M // 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 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)); } ++colidx; } - // 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 - codeword[col/8] |= (1 << (7 - col%8)); - } - ++colidx; - - // Roll the bitmask to the right - mask >>= 1; - if (mask == 0) mask = 0x80; + printf("Result "); + for (int i = 0; i < (N + 7) / 8; ++i) { + printf("%02x ", codeword[i]); } + printf("\n"); } @@ -192,6 +204,12 @@ uint16_t ft8_crc(uint8_t *message, int num_bits) { constexpr int WIDTH = 14; 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; int idx_byte = 0; @@ -211,6 +229,7 @@ uint16_t ft8_crc(uint8_t *message, int num_bits) { remainder = (remainder << 1); } } + printf("CRC = %04xh\n", 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 a91[9] &= 0xF8; + a91[10] = 0; + a91[11] = 0; - // Calculate CRC of 80 bits (yes, 77 + 3 zero bits), see WSJT-X code - uint16_t checksum = ft8_crc(a91, 80); + // Calculate CRC of 12 bytes = 96 bits, see WSJT-X code + 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 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]; @@ -247,18 +271,27 @@ void genft8(const uint8_t *payload, uint8_t *itone) { } 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 if (j == 29) { k += 7; // Skip over the second set of Costas symbols } + // Extract 3 bits from codeword at i-th position - itone[k] = 0; - int i = 3*j; - if (codeword[i/8] & (1 << (7 - i%8))) itone[k] |= 4; - ++i; - if (codeword[i/8] & (1 << (7 - i%8))) itone[k] |= 2; - ++i; - if (codeword[i/8] & (1 << (7 - i%8))) itone[k] |= 1; + uint8_t bits3 = 0; + + if (codeword[i_byte] & mask) bits3 |= 4; + if (0 == (mask >>= 1)) { mask = 0x80; i_byte++; } + if (codeword[i_byte] & mask) bits3 |= 2; + if (0 == (mask >>= 1)) { mask = 0x80; i_byte++; } + 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; } } diff --git a/gen_ft8.cpp b/gen_ft8.cpp index 3903006..988bfc2 100644 --- a/gen_ft8.cpp +++ b/gen_ft8.cpp @@ -6,6 +6,9 @@ #include "pack.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) { // 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]; // First, pack the text data into 72-bit binary message - uint8_t packed[9]; - int rc = packmsg(message, packed); + uint8_t packed[10]; + //int rc = packmsg(message, packed); + int rc = ft8_v2::pack77(message, packed); if (rc < 0) { printf("Cannot parse message!\n"); printf("RC = %d\n", rc); @@ -178,14 +182,15 @@ int main(int argc, char **argv) { } printf("Packed data: "); - for (int j = 0; j < 9; ++j) { + for (int j = 0; j < 10; ++j) { printf("%02x ", packed[j]); } printf("\n"); // Second, encode the binary message as a sequence of FSK tones 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: "); for (int j = 0; j < NN; ++j) { diff --git a/pack_77.cpp b/pack_77.cpp index 3f751b2..8044355 100644 --- a/pack_77.cpp +++ b/pack_77.cpp @@ -3,6 +3,8 @@ #include "text.h" +#include + namespace ft8_v2 { // 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 && (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 int32_t n28 = i0; n28 = n28 * 36 + i1; @@ -84,6 +87,7 @@ int32_t pack28(const char *callsign) { n28 = n28 * 27 + i3; n28 = n28 * 27 + i4; n28 = n28 * 27 + i5; + printf("Pack28: n28=%d (%04xh)\n", n28, n28); return NTOKENS + MAX22 + n28; } @@ -144,10 +148,10 @@ uint16_t packgrid(const char *grid4) { is_digit(grid4[2]) && is_digit(grid4[3])) { //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[1] - 'A'); - igrid4 = igrid4 * 18 + (grid4[0] - 'A'); + igrid4 = igrid4 * 10 + (grid4[3] - '0'); 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 // 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[1] = (n28a >> 13); b77[2] = (n28a >> 5); b77[3] = (uint8_t)(n28a << 3) | (uint8_t)(n28b >> 26); b77[4] = (n28b >> 18); - b77[5] = (n28a >> 10); - b77[6] = (n28a >> 2); - b77[7] = (uint8_t)(n28a << 6) | (uint8_t)(igrid4 >> 10); + b77[5] = (n28b >> 10); + b77[6] = (n28b >> 2); + b77[7] = (uint8_t)(n28b << 6) | (uint8_t)(igrid4 >> 10); b77[8] = (igrid4 >> 2); b77[9] = (uint8_t)(igrid4 << 6) | (uint8_t)(i3 << 3); diff --git a/pack_77.h b/pack_77.h index ba606b9..29d4e97 100644 --- a/pack_77.h +++ b/pack_77.h @@ -7,6 +7,6 @@ namespace ft8_v2 { // Pack FT8 text message into 72 bits // [IN] msg - FT8 message (e.g. "CQ TE5T KN01") // [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); }; \ No newline at end of file