From fd271eca3590a5cca4055199f10cc7d6d0af5a0a Mon Sep 17 00:00:00 2001 From: Karlis Goba Date: Fri, 26 Oct 2018 11:43:16 +0300 Subject: [PATCH] More of 77 bit protocol --- encode_91.cpp | 263 +++++++++++++++++++++++++++++++++++++ pack77.cpp => pack_77.cpp | 9 +- utils/convert_generator.py | 10 ++ 3 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 encode_91.cpp rename pack77.cpp => pack_77.cpp (96%) diff --git a/encode_91.cpp b/encode_91.cpp new file mode 100644 index 0000000..624009a --- /dev/null +++ b/encode_91.cpp @@ -0,0 +1,263 @@ +#include "encode.h" + +constexpr int N = 174, K = 91, M = N-K; // Define the LDPC sizes + +constexpr uint16_t POLYNOMIAL = 0x2757; // CRC-14 polynomial without the leading (MSB) 1 + +constexpr int K_BYTES = (K + 7) / 8; + +// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first) +const uint8_t G[M][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 }, + { 0x1b, 0x3f, 0x41, 0x78, 0x58, 0xcd, 0x2d, 0xd3, 0x3e, 0xc7, 0xf6, 0x20 }, + { 0x09, 0xfd, 0xa4, 0xfe, 0xe0, 0x41, 0x95, 0xfd, 0x03, 0x47, 0x83, 0xa0 }, + { 0x07, 0x7c, 0xcc, 0xc1, 0x1b, 0x88, 0x73, 0xed, 0x5c, 0x3d, 0x48, 0xa0 }, + { 0x29, 0xb6, 0x2a, 0xfe, 0x3c, 0xa0, 0x36, 0xf4, 0xfe, 0x1a, 0x9d, 0xa0 }, + { 0x60, 0x54, 0xfa, 0xf5, 0xf3, 0x5d, 0x96, 0xd3, 0xb0, 0xc8, 0xc3, 0xe0 }, + { 0xe2, 0x07, 0x98, 0xe4, 0x31, 0x0e, 0xed, 0x27, 0x88, 0x4a, 0xe9, 0x00 }, + { 0x77, 0x5c, 0x9c, 0x08, 0xe8, 0x0e, 0x26, 0xdd, 0xae, 0x56, 0x31, 0x80 }, + { 0xb0, 0xb8, 0x11, 0x02, 0x8c, 0x2b, 0xf9, 0x97, 0x21, 0x34, 0x87, 0xc0 }, + { 0x18, 0xa0, 0xc9, 0x23, 0x1f, 0xc6, 0x0a, 0xdf, 0x5c, 0x5e, 0xa3, 0x20 }, + { 0x76, 0x47, 0x1e, 0x83, 0x02, 0xa0, 0x72, 0x1e, 0x01, 0xb1, 0x2b, 0x80 }, + { 0xff, 0xbc, 0xcb, 0x80, 0xca, 0x83, 0x41, 0xfa, 0xfb, 0x47, 0xb2, 0xe0 }, + { 0x66, 0xa7, 0x2a, 0x15, 0x8f, 0x93, 0x25, 0xa2, 0xbf, 0x67, 0x17, 0x00 }, + { 0xc4, 0x24, 0x36, 0x89, 0xfe, 0x85, 0xb1, 0xc5, 0x13, 0x63, 0xa1, 0x80 }, + { 0x0d, 0xff, 0x73, 0x94, 0x14, 0xd1, 0xa1, 0xb3, 0x4b, 0x1c, 0x27, 0x00 }, + { 0x15, 0xb4, 0x88, 0x30, 0x63, 0x6c, 0x8b, 0x99, 0x89, 0x49, 0x72, 0xe0 }, + { 0x29, 0xa8, 0x9c, 0x0d, 0x3d, 0xe8, 0x1d, 0x66, 0x54, 0x89, 0xb0, 0xe0 }, + { 0x4f, 0x12, 0x6f, 0x37, 0xfa, 0x51, 0xcb, 0xe6, 0x1b, 0xd6, 0xb9, 0x40 }, + { 0x99, 0xc4, 0x72, 0x39, 0xd0, 0xd9, 0x7d, 0x3c, 0x84, 0xe0, 0x94, 0x00 }, + { 0x19, 0x19, 0xb7, 0x51, 0x19, 0x76, 0x56, 0x21, 0xbb, 0x4f, 0x1e, 0x80 }, + { 0x09, 0xdb, 0x12, 0xd7, 0x31, 0xfa, 0xee, 0x0b, 0x86, 0xdf, 0x6b, 0x80 }, + { 0x48, 0x8f, 0xc3, 0x3d, 0xf4, 0x3f, 0xbd, 0xee, 0xa4, 0xea, 0xfb, 0x40 }, + { 0x82, 0x74, 0x23, 0xee, 0x40, 0xb6, 0x75, 0xf7, 0x56, 0xeb, 0x5f, 0xe0 }, + { 0xab, 0xe1, 0x97, 0xc4, 0x84, 0xcb, 0x74, 0x75, 0x71, 0x44, 0xa9, 0xa0 }, + { 0x2b, 0x50, 0x0e, 0x4b, 0xc0, 0xec, 0x5a, 0x6d, 0x2b, 0xdb, 0xdd, 0x00 }, + { 0xc4, 0x74, 0xaa, 0x53, 0xd7, 0x02, 0x18, 0x76, 0x16, 0x69, 0x36, 0x00 }, + { 0x8e, 0xba, 0x1a, 0x13, 0xdb, 0x33, 0x90, 0xbd, 0x67, 0x18, 0xce, 0xc0 }, + { 0x75, 0x38, 0x44, 0x67, 0x3a, 0x27, 0x78, 0x2c, 0xc4, 0x20, 0x12, 0xe0 }, + { 0x06, 0xff, 0x83, 0xa1, 0x45, 0xc3, 0x70, 0x35, 0xa5, 0xc1, 0x26, 0x80 }, + { 0x3b, 0x37, 0x41, 0x78, 0x58, 0xcc, 0x2d, 0xd3, 0x3e, 0xc3, 0xf6, 0x20 }, + { 0x9a, 0x4a, 0x5a, 0x28, 0xee, 0x17, 0xca, 0x9c, 0x32, 0x48, 0x42, 0xc0 }, + { 0xbc, 0x29, 0xf4, 0x65, 0x30, 0x9c, 0x97, 0x7e, 0x89, 0x61, 0x0a, 0x40 }, + { 0x26, 0x63, 0xae, 0x6d, 0xdf, 0x8b, 0x5c, 0xe2, 0xbb, 0x29, 0x48, 0x80 }, + { 0x46, 0xf2, 0x31, 0xef, 0xe4, 0x57, 0x03, 0x4c, 0x18, 0x14, 0x41, 0x80 }, + { 0x3f, 0xb2, 0xce, 0x85, 0xab, 0xe9, 0xb0, 0xc7, 0x2e, 0x06, 0xfb, 0xe0 }, + { 0xde, 0x87, 0x48, 0x1f, 0x28, 0x2c, 0x15, 0x39, 0x71, 0xa0, 0xa2, 0xe0 }, + { 0xfc, 0xd7, 0xcc, 0xf2, 0x3c, 0x69, 0xfa, 0x99, 0xbb, 0xa1, 0x41, 0x20 }, + { 0xf0, 0x26, 0x14, 0x47, 0xe9, 0x49, 0x0c, 0xa8, 0xe4, 0x74, 0xce, 0xc0 }, + { 0x44, 0x10, 0x11, 0x58, 0x18, 0x19, 0x6f, 0x95, 0xcd, 0xd7, 0x01, 0x20 }, + { 0x08, 0x8f, 0xc3, 0x1d, 0xf4, 0xbf, 0xbd, 0xe2, 0xa4, 0xea, 0xfb, 0x40 }, + { 0xb8, 0xfe, 0xf1, 0xb6, 0x30, 0x77, 0x29, 0xfb, 0x0a, 0x07, 0x8c, 0x00 }, + { 0x5a, 0xfe, 0xa7, 0xac, 0xcc, 0xb7, 0x7b, 0xbc, 0x9d, 0x99, 0xa9, 0x00 }, + { 0x49, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xf6, 0x5e, 0xcd, 0xc9, 0x07, 0x60 }, + { 0x19, 0x44, 0xd0, 0x85, 0xbe, 0x4e, 0x7d, 0xa8, 0xd6, 0xcc, 0x7d, 0x00 }, + { 0x25, 0x1f, 0x62, 0xad, 0xc4, 0x03, 0x2f, 0x0e, 0xe7, 0x14, 0x00, 0x20 }, + { 0x56, 0x47, 0x1f, 0x87, 0x02, 0xa0, 0x72, 0x1e, 0x00, 0xb1, 0x2b, 0x80 }, + { 0x2b, 0x8e, 0x49, 0x23, 0xf2, 0xdd, 0x51, 0xe2, 0xd5, 0x37, 0xfa, 0x00 }, + { 0x6b, 0x55, 0x0a, 0x40, 0xa6, 0x6f, 0x47, 0x55, 0xde, 0x95, 0xc2, 0x60 }, + { 0xa1, 0x8a, 0xd2, 0x8d, 0x4e, 0x27, 0xfe, 0x92, 0xa4, 0xf6, 0xc8, 0x40 }, + { 0x10, 0xc2, 0xe5, 0x86, 0x38, 0x8c, 0xb8, 0x2a, 0x3d, 0x80, 0x75, 0x80 }, + { 0xef, 0x34, 0xa4, 0x18, 0x17, 0xee, 0x02, 0x13, 0x3d, 0xb2, 0xeb, 0x00 }, + { 0x7e, 0x9c, 0x0c, 0x54, 0x32, 0x5a, 0x9c, 0x15, 0x83, 0x6e, 0x00, 0x00 }, + { 0x36, 0x93, 0xe5, 0x72, 0xd1, 0xfd, 0xe4, 0xcd, 0xf0, 0x79, 0xe8, 0x60 }, + { 0xbf, 0xb2, 0xce, 0xc5, 0xab, 0xe1, 0xb0, 0xc7, 0x2e, 0x07, 0xfb, 0xe0 }, + { 0x7e, 0xe1, 0x82, 0x30, 0xc5, 0x83, 0xcc, 0xcc, 0x57, 0xd4, 0xb0, 0x80 }, + { 0xa0, 0x66, 0xcb, 0x2f, 0xed, 0xaf, 0xc9, 0xf5, 0x26, 0x64, 0x12, 0x60 }, + { 0xbb, 0x23, 0x72, 0x5a, 0xbc, 0x47, 0xcc, 0x5f, 0x4c, 0xc4, 0xcd, 0x20 }, + { 0xde, 0xd9, 0xdb, 0xa3, 0xbe, 0xe4, 0x0c, 0x59, 0xb5, 0x60, 0x9b, 0x40 }, + { 0xd9, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xe6, 0xde, 0xcd, 0xc9, 0x03, 0x60 }, + { 0x9a, 0xd4, 0x6a, 0xed, 0x5f, 0x70, 0x7f, 0x28, 0x0a, 0xb5, 0xfc, 0x40 }, + { 0xe5, 0x92, 0x1c, 0x77, 0x82, 0x25, 0x87, 0x31, 0x6d, 0x7d, 0x3c, 0x20 }, + { 0x4f, 0x14, 0xda, 0x82, 0x42, 0xa8, 0xb8, 0x6d, 0xca, 0x73, 0x35, 0x20 }, + { 0x8b, 0x8b, 0x50, 0x7a, 0xd4, 0x67, 0xd4, 0x44, 0x1d, 0xf7, 0x70, 0xe0 }, + { 0x22, 0x83, 0x1c, 0x9c, 0xf1, 0x16, 0x94, 0x67, 0xad, 0x04, 0xb6, 0x80 }, + { 0x21, 0x3b, 0x83, 0x8f, 0xe2, 0xae, 0x54, 0xc3, 0x8e, 0xe7, 0x18, 0x00 }, + { 0x5d, 0x92, 0x6b, 0x6d, 0xd7, 0x1f, 0x08, 0x51, 0x81, 0xa4, 0xe1, 0x20 }, + { 0x66, 0xab, 0x79, 0xd4, 0xb2, 0x9e, 0xe6, 0xe6, 0x95, 0x09, 0xe5, 0x60 }, + { 0x95, 0x81, 0x48, 0x68, 0x2d, 0x74, 0x8a, 0x38, 0xdd, 0x68, 0xba, 0xa0 }, + { 0xb8, 0xce, 0x02, 0x0c, 0xf0, 0x69, 0xc3, 0x2a, 0x72, 0x3a, 0xb1, 0x40 }, + { 0xf4, 0x33, 0x1d, 0x6d, 0x46, 0x16, 0x07, 0xe9, 0x57, 0x52, 0x74, 0x60 }, + { 0x6d, 0xa2, 0x3b, 0xa4, 0x24, 0xb9, 0x59, 0x61, 0x33, 0xcf, 0x9c, 0x80 }, + { 0xa6, 0x36, 0xbc, 0xbc, 0x7b, 0x30, 0xc5, 0xfb, 0xea, 0xe6, 0x7f, 0xe0 }, + { 0x5c, 0xb0, 0xd8, 0x6a, 0x07, 0xdf, 0x65, 0x4a, 0x90, 0x89, 0xa2, 0x00 }, + { 0xf1, 0x1f, 0x10, 0x68, 0x48, 0x78, 0x0f, 0xc9, 0xec, 0xdd, 0x80, 0xa0 }, + { 0x1f, 0xbb, 0x53, 0x64, 0xfb, 0x8d, 0x2c, 0x9d, 0x73, 0x0d, 0x5b, 0xa0 }, + { 0xfc, 0xb8, 0x6b, 0xc7, 0x0a, 0x50, 0xc9, 0xd0, 0x2a, 0x5d, 0x03, 0x40 }, + { 0xa5, 0x34, 0x43, 0x30, 0x29, 0xea, 0xc1, 0x5f, 0x32, 0x2e, 0x34, 0xc0 }, + { 0xc9, 0x89, 0xd9, 0xc7, 0xc3, 0xd3, 0xb8, 0xc5, 0x5d, 0x75, 0x13, 0x00 }, + { 0x7b, 0xb3, 0x8b, 0x2f, 0x01, 0x86, 0xd4, 0x66, 0x43, 0xae, 0x96, 0x20 }, + { 0x26, 0x44, 0xeb, 0xad, 0xeb, 0x44, 0xb9, 0x46, 0x7d, 0x1f, 0x42, 0xc0 }, + { 0x60, 0x8c, 0xc8, 0x57, 0x59, 0x4b, 0xfb, 0xb5, 0x5d, 0x69, 0x60, 0x00 } +}; + +// Column order (permutation) in which the bits in codeword are stored +const uint8_t colorder[N] = { + 0, 1, 2, 3, 30, 4, 5, 6, 7, 8, 9, 10, 11, 32, 12, 40, 13, 14, 15, 16, + 17, 18, 37, 45, 29, 19, 20, 21, 41, 22, 42, 31, 33, 34, 44, 35, 47, 51, 50, 43, + 36, 52, 63, 46, 25, 55, 27, 24, 23, 53, 39, 49, 59, 38, 48, 61, 60, 57, 28, 62, + 56, 58, 65, 66, 26, 70, 64, 69, 68, 67, 74, 71, 54, 76, 72, 75, 78, 77, 80, 79, + 73, 83, 84, 81, 82, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, + 100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119, + 120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139, + 140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167,168,169,170,171,172,173 +}; + +// Costas 7x7 tone pattern +const uint8_t ICOS7[] = { 2,5,6,0,4,1,3 }; + + +// Returns 1 if an odd number of bits are set in x, zero otherwise +uint8_t parity8(uint8_t x) { + x ^= x >> 4; // a b c d ae bf cg dh + x ^= x >> 2; // a b ac bd cae dbf aecg bfdh + x ^= x >> 1; // a ab bac acbd bdcae caedbf aecgbfdh + return (x) & 1; +} + + +// Encode an 87-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 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) +// [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) + + int colidx = 0; // track the current column in codeword + + // Fill the codeword with zeroes, as we will only update binary ones later + for (int i = 0; i < (N + 7) / 8; i++) { + codeword[i] = 0; + } + + // 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 + // Normally nsum would contain the result of dot product between message and G[i], + // but we only compute the sum modulo 2. + uint8_t nsum = 0; + for (int j = 0; j < K_BYTES; ++j) { + uint8_t bits = message[j] & G[i][j]; // bitwise AND (bitwise multiplication) + nsum ^= parity8(bits); // bitwise XOR (addition modulo 2) + } + // 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 + 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; + } +} + + +// 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) { + // Adapted from https://barrgroup.com/Embedded-Systems/How-To/CRC-Calculation-C-Code + constexpr int WIDTH = 14; + constexpr uint16_t TOPBIT = (1 << (WIDTH - 1)); + + 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] << (WIDTH - 8)); + ++idx_byte; + } + + // Try to divide the current data bit. + if (remainder & TOPBIT) { + remainder = (remainder << 1) ^ POLYNOMIAL; + } + else { + remainder = (remainder << 1); + } + } + return remainder & ((1 << WIDTH) - 1); +} + + +// Generate FT8 tone sequence from payload data +// [IN] payload - 9 byte array consisting of 72 bit payload (MSB first) +// [IN] i3 - 3 bits containing message type (zero?) +// [OUT] itone - array of NN (79) bytes to store the generated tones (encoded as 0..7) +void genft8(const uint8_t *payload, uint8_t i3, uint8_t *itone) { + 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] &= 0xF8; + + // Calculate CRC of 80 bits (yes, 77 + 3 zero bits), see WSJT-X code + uint16_t checksum = ft8_crc(a91, 80); + + // Store the CRC at the end of 77 bit message + a91[9] |= (uint8_t)(checksum >> 11); + a91[10] = (uint8_t)(checksum >> 3); + + // a87 contains 72 bits of payload + 3 bits of i3 + 12 bits of CRC + uint8_t codeword[22]; + encode174(a91, codeword); + + // Message structure: S7 D29 S7 D29 S7 + for (int i = 0; i < 7; ++i) { + itone[i] = ICOS7[i]; + itone[36 + i] = ICOS7[i]; + itone[72 + i] = ICOS7[i]; + } + + int k = 7; // Skip over the first set of Costas symbols + 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; + ++k; + } +} diff --git a/pack77.cpp b/pack_77.cpp similarity index 96% rename from pack77.cpp rename to pack_77.cpp index f05555a..ff22242 100644 --- a/pack77.cpp +++ b/pack_77.cpp @@ -119,12 +119,19 @@ void pack77_1(const char *msg, uint8_t i3, uint8_t *b77) { // if(index(w(1),'/P').ge.4 .or. index(w(1),'/R').ge.4) ipa=1 // if(index(w(2),'/P').ge.4 .or. index(w(2),'/R').ge.4) ipb=1 - uint32_t n28a, n28b; + int32_t n28a, n28b; uint16_t igrid4; + + const char *call1, *call2; + + n28a = pack28(call1); + n28b = pack28(call2); + // Add ipa and ipb bits to the right n28a <<= 1; // ipa = 0 n28b <<= 1; // ipb = 0 + // Add igrid4 bit to the left //igrid4 |= 0x8000; // ir = 1 // write(c77,1000) n28a,ipa,n28b,ipb,ir,igrid4,i3 diff --git a/utils/convert_generator.py b/utils/convert_generator.py index 4347b9f..ea8de0e 100644 --- a/utils/convert_generator.py +++ b/utils/convert_generator.py @@ -1,6 +1,16 @@ import sys +import re + +rx1 = re.compile(r'"([0-9a-z]+)"', re.I) for line in sys.stdin: line = line.strip() + m = rx1.search(line) + if m == None: + continue + + line = m.group(1) + if len(line) % 2 == 1: + line += '0' b = ['0x' + line[i*2:i*2+2] for i in range(len(line)/2)] print '{ %s },' % (', '.join(b))