Fixed FT8 v2.0 encoding
This commit is contained in:
parent
41e7aef8b8
commit
c2f10e8cb2
6 changed files with 95 additions and 49 deletions
2
Makefile
2
Makefile
|
@ -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 $@ $^
|
||||||
|
|
|
@ -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);
|
||||||
|
|
103
encode_91.cpp
103
encode_91.cpp
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
13
gen_ft8.cpp
13
gen_ft8.cpp
|
@ -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) {
|
||||||
|
|
20
pack_77.cpp
20
pack_77.cpp
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
};
|
};
|
Loading…
Reference in a new issue