/* * JTEncode.cpp - JT65/JT9/WSPR/FSQ encoder library for Arduino * * Copyright (C) 2015-2018 Jason Milldrum * * Based on the algorithms presented in the WSJT software suite. * Thanks to Andy Talbot G4JNT for the whitepaper on the WSPR encoding * process that helped me to understand all of this. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "JTEncode.h" #include "crc14.h" #include "generator.h" #include #include #include #include #include // Define an upper bound on the number of glyphs. Defining it this // way allows adding characters without having to update a hard-coded // upper bound. #define NGLYPHS (sizeof(fsq_code_table)/sizeof(fsq_code_table[0])) /* Public Class Members */ JTEncode::JTEncode(void) { // Initialize the Reed-Solomon encoder rs_inst = (struct rs *)(intptr_t)init_rs_int(6, 0x43, 3, 1, 51, 0); } /* * jt65_encode(const char * message, uint8_t * symbols) * * Takes an arbitrary message of up to 13 allowable characters and returns * a channel symbol table. * * message - Plaintext Type 6 message. * symbols - Array of channel symbols to transmit returned by the method. * Ensure that you pass a uint8_t array of at least size JT65_SYMBOL_COUNT to the method. * */ uint8_t jt65_buffer[JT65_ENCODE_COUNT]; void JTEncode::jt65_encode(const char * msg, uint8_t * symbols) { char message[14]; memset(message, 0, 14); strcpy(message, msg); // Ensure that the message text conforms to standards // -------------------------------------------------- jt_message_prep(message); // Bit packing // ----------- uint8_t c[12]; jt65_bit_packing(message, c); // Reed-Solomon encoding // --------------------- rs_encode(c, jt65_buffer); // Interleaving // ------------ jt65_interleave(jt65_buffer); // Gray Code // --------- jt_gray_code(jt65_buffer, JT65_ENCODE_COUNT); // Merge with sync vector // ---------------------- jt65_merge_sync_vector(jt65_buffer, symbols); } /* * jt9_encode(const char * message, uint8_t * symbols) * * Takes an arbitrary message of up to 13 allowable characters and returns * a channel symbol table. * * message - Plaintext Type 6 message. * symbols - Array of channel symbols to transmit returned by the method. * Ensure that you pass a uint8_t array of at least size JT9_SYMBOL_COUNT to the method. * */ uint8_t jt9_buffer_s[JT9_BIT_COUNT]; uint8_t jt9_buffer_a[JT9_ENCODE_COUNT]; void JTEncode::jt9_encode(const char * msg, uint8_t * symbols) { char message[14]; memset(message, 0, 14); strcpy(message, msg); // Ensure that the message text conforms to standards // -------------------------------------------------- jt_message_prep(message); // Bit packing // ----------- uint8_t c[13]; jt9_bit_packing(message, c); // Convolutional Encoding // --------------------- convolve(c, jt9_buffer_s, 13, JT9_BIT_COUNT); // Interleaving // ------------ jt9_interleave(jt9_buffer_s); // Pack into 3-bit symbols // ----------------------- jt9_packbits(jt9_buffer_s, jt9_buffer_a); // Gray Code // --------- jt_gray_code(jt9_buffer_a, JT9_ENCODE_COUNT); // Merge with sync vector // ---------------------- jt9_merge_sync_vector(jt9_buffer_a, symbols); } /* * jt4_encode(const char * message, uint8_t * symbols) * * Takes an arbitrary message of up to 13 allowable characters and returns * a channel symbol table. * * message - Plaintext Type 6 message. * symbols - Array of channel symbols to transmit returned by the method. * Ensure that you pass a uint8_t array of at least size JT9_SYMBOL_COUNT to the method. * */ uint8_t jt4_buffer_s[JT4_SYMBOL_COUNT]; void JTEncode::jt4_encode(const char * msg, uint8_t * symbols) { char message[14]; memset(message, 0, 14); strcpy(message, msg); // Ensure that the message text conforms to standards // -------------------------------------------------- jt_message_prep(message); // Bit packing // ----------- uint8_t c[13]; jt9_bit_packing(message, c); // Convolutional Encoding // --------------------- convolve(c, jt4_buffer_s, 13, JT4_BIT_COUNT); // Interleaving // ------------ jt9_interleave(jt4_buffer_s); memmove(jt4_buffer_s + 1, jt4_buffer_s, JT4_BIT_COUNT); jt4_buffer_s[0] = 0; // Append a 0 bit to start of sequence // Merge with sync vector // ---------------------- jt4_merge_sync_vector(jt4_buffer_s, symbols); } /* * wspr_encode(const char * call, const char * loc, const uint8_t dbm, uint8_t * symbols) * * Takes an arbitrary message of up to 13 allowable characters and returns * * call - Callsign (6 characters maximum). * loc - Maidenhead grid locator (4 characters maximum). * dbm - Output power in dBm. * symbols - Array of channel symbols to transmit returned by the method. * Ensure that you pass a uint8_t array of at least size WSPR_SYMBOL_COUNT to the method. * */ uint8_t wspr_buffer_s[WSPR_SYMBOL_COUNT]; void JTEncode::wspr_encode(const char * call, const char * loc, const uint8_t dbm, uint8_t * symbols) { char call_[7]; char loc_[5]; uint8_t dbm_ = dbm; strcpy(call_, call); strcpy(loc_, loc); // Ensure that the message text conforms to standards // -------------------------------------------------- wspr_message_prep(call_, loc_, dbm_); // Bit packing // ----------- uint8_t c[11]; wspr_bit_packing(c); // Convolutional Encoding // --------------------- convolve(c, wspr_buffer_s, 11, WSPR_BIT_COUNT); // Interleaving // ------------ wspr_interleave(wspr_buffer_s); // Merge with sync vector // ---------------------- wspr_merge_sync_vector(wspr_buffer_s, symbols); } /* * fsq_encode(const char * from_call, const char * message, uint8_t * symbols) * * Takes an arbitrary message and returns a FSQ channel symbol table. * * from_call - Callsign of issuing station (maximum size: 20) * message - Null-terminated message string, no greater than 130 chars in length * symbols - Array of channel symbols to transmit returned by the method. * Ensure that you pass a uint8_t array of at least the size of the message * plus 5 characters to the method. Terminated in 0xFF. * */ char fsq_tx_buffer[155]; void JTEncode::fsq_encode(const char * from_call, const char * message, uint8_t * symbols) { char * tx_message; uint16_t symbol_pos = 0; uint8_t i, fch, vcode1, vcode2, tone; uint8_t cur_tone = 0; // Clear out the transmit buffer // ----------------------------- memset(fsq_tx_buffer, 0, 155); // Create the message to be transmitted // ------------------------------------ sprintf(fsq_tx_buffer, " \n%s: %s", from_call, message); tx_message = fsq_tx_buffer; // Iterate through the message and encode // -------------------------------------- while(*tx_message != '\0') { for(i = 0; i < NGLYPHS; i++) { uint8_t ch = (uint8_t)*tx_message; // Check each element of the varicode table to see if we've found the // character we're trying to send. fch = fsq_code_table[i].ch; if(fch == ch) { // Found the character, now fetch the varicode chars vcode1 = fsq_code_table[i].var[0]; vcode2 = fsq_code_table[i].var[1]; // Transmit the appropriate tone per a varicode char if(vcode2 == 0) { // If the 2nd varicode char is a 0 in the table, // we are transmitting a lowercase character, and thus // only transmit one tone for this character. // Generate tone cur_tone = ((cur_tone + vcode1 + 1) % 33); symbols[symbol_pos++] = cur_tone; } else { // If the 2nd varicode char is anything other than 0 in // the table, then we need to transmit both // Generate 1st tone cur_tone = ((cur_tone + vcode1 + 1) % 33); symbols[symbol_pos++] = cur_tone; // Generate 2nd tone cur_tone = ((cur_tone + vcode2 + 1) % 33); symbols[symbol_pos++] = cur_tone; } break; // We've found and transmitted the char, // so exit the for loop } } tx_message++; } // Message termination // ---------------- symbols[symbol_pos] = 0xff; } /* * fsq_dir_encode(const char * from_call, const char * to_call, const char cmd, const char * message, uint8_t * symbols) * * Takes an arbitrary message and returns a FSQ channel symbol table. * * from_call - Callsign from which message is directed (maximum size: 20) * to_call - Callsign to which message is directed (maximum size: 20) * cmd - Directed command * message - Null-terminated message string, no greater than 100 chars in length * symbols - Array of channel symbols to transmit returned by the method. * Ensure that you pass a uint8_t array of at least the size of the message * plus 5 characters to the method. Terminated in 0xFF. * */ char fsq_dir_tx_buffer[155]; void JTEncode::fsq_dir_encode(const char * from_call, const char * to_call, const char cmd, const char * message, uint8_t * symbols) { char * tx_message; uint16_t symbol_pos = 0; uint8_t i, fch, vcode1, vcode2, tone, from_call_crc; uint8_t cur_tone = 0; // Generate a CRC on from_call // --------------------------- from_call_crc = crc8(from_call); // Clear out the transmit buffer // ----------------------------- memset(fsq_dir_tx_buffer, 0, 155); // Create the message to be transmitted // We are building a directed message here. // FSQ very specifically needs " \b " in // directed mode to indicate EOT. A single backspace won't do it. sprintf(fsq_dir_tx_buffer, " \n%s:%02x%s%c%s%s", from_call, from_call_crc, to_call, cmd, message, " \b "); tx_message = fsq_dir_tx_buffer; // Iterate through the message and encode // -------------------------------------- while(*tx_message != '\0') { for(i = 0; i < NGLYPHS; i++) { uint8_t ch = (uint8_t)*tx_message; // Check each element of the varicode table to see if we've found the // character we're trying to send. fch = fsq_code_table[i].ch; if(fch == ch) { // Found the character, now fetch the varicode chars vcode1 = fsq_code_table[i].var[0]; vcode2 = fsq_code_table[i].var[1]; // Transmit the appropriate tone per a varicode char if(vcode2 == 0) { // If the 2nd varicode char is a 0 in the table, // we are transmitting a lowercase character, and thus // only transmit one tone for this character. // Generate tone cur_tone = ((cur_tone + vcode1 + 1) % 33); symbols[symbol_pos++] = cur_tone; } else { // If the 2nd varicode char is anything other than 0 in // the table, then we need to transmit both // Generate 1st tone cur_tone = ((cur_tone + vcode1 + 1) % 33); symbols[symbol_pos++] = cur_tone; // Generate 2nd tone cur_tone = ((cur_tone + vcode2 + 1) % 33); symbols[symbol_pos++] = cur_tone; } break; // We've found and transmitted the char, // so exit the for loop } } tx_message++; } // Message termination // ---------------- symbols[symbol_pos] = 0xff; } /* * ft8_encode(const char * message, uint8_t * symbols) * * Takes an arbitrary message of up to 13 allowable characters or a telemetry message * of up to 18 hexadecimal digit (in string format) and returns a channel symbol table. * Encoded for the FT8 protocol used in WSJT-X v2.0 and beyond (79 channel symbols). * * message - Type 0.0 free text message or Type 0.5 telemetry message. * symbols - Array of channel symbols to transmit returned by the method. * Ensure that you pass a uint8_t array of at least size FT8_SYMBOL_COUNT to the method. * */ uint8_t ft8_buffer_s[FT8_BIT_COUNT]; void JTEncode::ft8_encode(const char * msg, uint8_t * symbols) { uint8_t i; char message[19]; memset(message, 0, 19); strcpy(message, msg); // Bit packing // ----------- uint8_t c[77]; memset(c, 0, 77); ft8_bit_packing(message, c); // Message Encoding // ---------------- ft8_encode(c, ft8_buffer_s); // Merge with sync vector // ---------------------- ft8_merge_sync_vector(ft8_buffer_s, symbols); } /* Private Class Members */ uint8_t JTEncode::jt_code(char c) { // Validate the input then return the proper integer code. // Return 255 as an error code if the char is not allowed. if(isdigit(c)) { return (uint8_t)(c - 48); } else if(c >= 'A' && c <= 'Z') { return (uint8_t)(c - 55); } else if(c == ' ') { return 36; } else if(c == '+') { return 37; } else if(c == '-') { return 38; } else if(c == '.') { return 39; } else if(c == '/') { return 40; } else if(c == '?') { return 41; } else { return 255; } } uint8_t JTEncode::ft_code(char c) { /* Validate the input then return the proper integer code */ // Return 255 as an error code if the char is not allowed if(isdigit(c)) { return (uint8_t)(c) - 47; } else if(c >= 'A' && c <= 'Z') { return (uint8_t)(c) - 54; } else if(c == ' ') { return 0; } else if(c == '+') { return 37; } else if(c == '-') { return 38; } else if(c == '.') { return 39; } else if(c == '/') { return 40; } else if(c == '?') { return 41; } else { return 255; } } uint8_t JTEncode::wspr_code(char c) { // Validate the input then return the proper integer code. // Return 255 as an error code if the char is not allowed. if(isdigit(c)) { return (uint8_t)(c - 48); } else if(c == ' ') { return 36; } else if(c >= 'A' && c <= 'Z') { return (uint8_t)(c - 55); } else { return 255; } } uint8_t JTEncode::gray_code(uint8_t c) { return (c >> 1) ^ c; } int8_t JTEncode::hex2int(char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; return -1; } void JTEncode::jt_message_prep(char * message) { uint8_t i; // Pad the message with trailing spaces uint8_t len = strlen(message); if(len < 13) { for(i = len; i <= 13; i++) { message[i] = ' '; } } // Convert all chars to uppercase for(i = 0; i < 13; i++) { if(islower(message[i])) { message[i] = toupper(message[i]); } } } void JTEncode::ft_message_prep(char * message) { uint8_t i; char temp_msg[14]; snprintf(temp_msg, 14, "%13s", message); // Convert all chars to uppercase for(i = 0; i < 13; i++) { if(islower(temp_msg[i])) { temp_msg[i] = toupper(temp_msg[i]); } } strcpy(message, temp_msg); } void JTEncode::wspr_message_prep(char * call, char * loc, uint8_t dbm) { // Callsign validation and padding // ------------------------------- // If only the 2nd character is a digit, then pad with a space. // If this happens, then the callsign will be truncated if it is // longer than 5 characters. if((call[1] >= '0' && call[1] <= '9') && (call[2] < '0' || call[2] > '9')) { memmove(call + 1, call, 5); call[0] = ' '; } // Now the 3rd charcter in the callsign must be a digit if(call[2] < '0' || call[2] > '9') { // TODO: need a better way to handle this call[2] = '0'; } // Ensure that the only allowed characters are digits and // uppercase letters uint8_t i; for(i = 0; i < 6; i++) { call[i] = toupper(call[i]); if(!(isdigit(call[i]) || isupper(call[i]))) { call[i] = ' '; } } memcpy(callsign, call, 6); // Grid locator validation for(i = 0; i < 4; i++) { loc[i] = toupper(loc[i]); if(!(isdigit(loc[i]) || (loc[i] >= 'A' && loc[i] <= 'R'))) { memcpy(loc, "AA00", 5); //loc = "AA00"; } } memcpy(locator, loc, 4); // Power level validation // Only certain increments are allowed if(dbm > 60) { dbm = 60; } const uint8_t valid_dbm[19] = {0, 3, 7, 10, 13, 17, 20, 23, 27, 30, 33, 37, 40, 43, 47, 50, 53, 57, 60}; for(i = 0; i < 19; i++) { if(dbm == valid_dbm[i]) { power = dbm; } } // If we got this far, we have an invalid power level, so we'll round down for(i = 1; i < 19; i++) { if(dbm < valid_dbm[i] && dbm >= valid_dbm[i - 1]) { power = valid_dbm[i - 1]; } } } void JTEncode::jt65_bit_packing(char * message, uint8_t * c) { uint32_t n1, n2, n3; // Find the N values n1 = jt_code(message[0]); n1 = n1 * 42 + jt_code(message[1]); n1 = n1 * 42 + jt_code(message[2]); n1 = n1 * 42 + jt_code(message[3]); n1 = n1 * 42 + jt_code(message[4]); n2 = jt_code(message[5]); n2 = n2 * 42 + jt_code(message[6]); n2 = n2 * 42 + jt_code(message[7]); n2 = n2 * 42 + jt_code(message[8]); n2 = n2 * 42 + jt_code(message[9]); n3 = jt_code(message[10]); n3 = n3 * 42 + jt_code(message[11]); n3 = n3 * 42 + jt_code(message[12]); // Pack bits 15 and 16 of N3 into N1 and N2, // then mask reset of N3 bits n1 = (n1 << 1) + ((n3 >> 15) & 1); n2 = (n2 << 1) + ((n3 >> 16) & 1); n3 = n3 & 0x7fff; // Set the freeform message flag n3 += 32768; c[0] = (n1 >> 22) & 0x003f; c[1] = (n1 >> 16) & 0x003f; c[2] = (n1 >> 10) & 0x003f; c[3] = (n1 >> 4) & 0x003f; c[4] = ((n1 & 0x000f) << 2) + ((n2 >> 26) & 0x0003); c[5] = (n2 >> 20) & 0x003f; c[6] = (n2 >> 14) & 0x003f; c[7] = (n2 >> 8) & 0x003f; c[8] = (n2 >> 2) & 0x003f; c[9] = ((n2 & 0x0003) << 4) + ((n3 >> 12) & 0x000f); c[10] = (n3 >> 6) & 0x003f; c[11] = n3 & 0x003f; } void JTEncode::jt9_bit_packing(char * message, uint8_t * c) { uint32_t n1, n2, n3; // Find the N values n1 = jt_code(message[0]); n1 = n1 * 42 + jt_code(message[1]); n1 = n1 * 42 + jt_code(message[2]); n1 = n1 * 42 + jt_code(message[3]); n1 = n1 * 42 + jt_code(message[4]); n2 = jt_code(message[5]); n2 = n2 * 42 + jt_code(message[6]); n2 = n2 * 42 + jt_code(message[7]); n2 = n2 * 42 + jt_code(message[8]); n2 = n2 * 42 + jt_code(message[9]); n3 = jt_code(message[10]); n3 = n3 * 42 + jt_code(message[11]); n3 = n3 * 42 + jt_code(message[12]); // Pack bits 15 and 16 of N3 into N1 and N2, // then mask reset of N3 bits n1 = (n1 << 1) + ((n3 >> 15) & 1); n2 = (n2 << 1) + ((n3 >> 16) & 1); n3 = n3 & 0x7fff; // Set the freeform message flag n3 += 32768; // 71 message bits to pack, plus 1 bit flag for freeform message. // 31 zero bits appended to end. // N1 and N2 are 28 bits each, N3 is 16 bits // A little less work to start with the least-significant bits c[3] = (uint8_t)((n1 & 0x0f) << 4); n1 = n1 >> 4; c[2] = (uint8_t)(n1 & 0xff); n1 = n1 >> 8; c[1] = (uint8_t)(n1 & 0xff); n1 = n1 >> 8; c[0] = (uint8_t)(n1 & 0xff); c[6] = (uint8_t)(n2 & 0xff); n2 = n2 >> 8; c[5] = (uint8_t)(n2 & 0xff); n2 = n2 >> 8; c[4] = (uint8_t)(n2 & 0xff); n2 = n2 >> 8; c[3] |= (uint8_t)(n2 & 0x0f); c[8] = (uint8_t)(n3 & 0xff); n3 = n3 >> 8; c[7] = (uint8_t)(n3 & 0xff); c[9] = 0; c[10] = 0; c[11] = 0; c[12] = 0; } void JTEncode::wspr_bit_packing(uint8_t * c) { uint32_t n, m; n = wspr_code(callsign[0]); n = n * 36 + wspr_code(callsign[1]); n = n * 10 + wspr_code(callsign[2]); n = n * 27 + (wspr_code(callsign[3]) - 10); n = n * 27 + (wspr_code(callsign[4]) - 10); n = n * 27 + (wspr_code(callsign[5]) - 10); m = ((179 - 10 * (locator[0] - 'A') - (locator[2] - '0')) * 180) + (10 * (locator[1] - 'A')) + (locator[3] - '0'); m = (m * 128) + power + 64; // Callsign is 28 bits, locator/power is 22 bits. // A little less work to start with the least-significant bits c[3] = (uint8_t)((n & 0x0f) << 4); n = n >> 4; c[2] = (uint8_t)(n & 0xff); n = n >> 8; c[1] = (uint8_t)(n & 0xff); n = n >> 8; c[0] = (uint8_t)(n & 0xff); c[6] = (uint8_t)((m & 0x03) << 6); m = m >> 2; c[5] = (uint8_t)(m & 0xff); m = m >> 8; c[4] = (uint8_t)(m & 0xff); m = m >> 8; c[3] |= (uint8_t)(m & 0x0f); c[7] = 0; c[8] = 0; c[9] = 0; c[10] = 0; } void JTEncode::ft8_bit_packing(char* message, uint8_t* codeword) { // Just encoding type 0 free text and type 0.5 telemetry for now // The bit packing algorithm is: // sum(message(pos) * 42^pos) uint8_t i3 = 0; uint8_t n3 = 0; uint8_t qa[10]; uint8_t qb[10]; char c18[19]; bool telem = false; char temp_msg[19]; memset(qa, 0, 10); memset(qb, 0, 10); uint8_t i, j, x, i0; uint32_t ireg = 0; // See if this is a telemetry message // Has to be hex digits, can be no more than 18 for(i = 0; i < 19; ++i) { if(message[i] == 0 || message[i] == ' ') { break; } else if(hex2int(message[i]) == -1) { telem = false; break; } else { c18[i] = message[i]; telem = true; } } // If telemetry if(telem) { // Get the first 18 hex digits for(i = 0; i < strlen(message); ++i) { i0 = i; if(message[i] == ' ') { --i0; break; } } memset(c18, 0, 19); memmove(c18, message, i0 + 1); snprintf(temp_msg, 19, "%*s", 18, c18); // Convert all chars to uppercase for(i = 0; i < strlen(temp_msg); i++) { if(islower(temp_msg[i])) { temp_msg[i] = toupper(temp_msg[i]); } } strcpy(message, temp_msg); uint8_t temp_int; temp_int = message[0] == ' ' ? 0 : hex2int(message[0]); for(i = 1; i < 4; ++i) { codeword[i - 1] = (((temp_int << i) & 0x8) >> 3) & 1; } temp_int = message[1] == ' ' ? 0 : hex2int(message[1]); for(i = 0; i < 4; ++i) { codeword[i + 3] = (((temp_int << i) & 0x8) >> 3) & 1; } for(i = 0; i < 8; ++i) { if(message[2 * i + 2] == ' ') { temp_int = 0; } else { temp_int = hex2int(message[2 * i + 2]); } for(j = 0; j < 4; ++j) { codeword[(i + 1) * 8 + j - 1] = (((temp_int << j) & 0x8) >> 3) & 1; } if(message[2 * i + 3] == ' ') { temp_int = 0; } else { temp_int = hex2int(message[2 * i + 3]); } for(j = 0; j < 4; ++j) { codeword[(i + 1) * 8 + j + 3] = (((temp_int << j) & 0x8) >> 3) & 1; } } i3 = 0; n3 = 5; } else { ft_message_prep(message); for(i = 0; i < 13; ++i) { x = ft_code(message[i]); // mult ireg = 0; for(j = 0; j < 9; ++j) { ireg = (uint8_t)qa[j] * 42 + (uint8_t)((ireg >> 8) & 0xff); qb[j] = (uint8_t)(ireg & 0xff); } qb[9] = (uint8_t)((ireg >> 8) & 0xff); // add ireg = x << 8; for(j = 0; j < 9; ++j) { ireg = (uint8_t)qb[j] + (uint8_t)((ireg >> 8) & 0xff); qa[j] = (uint8_t)(ireg & 0xff); } qa[9] = (uint8_t)((ireg >> 8) & 0xff); } // Format bits to output array for(i = 1; i < 8; ++i) { codeword[i - 1] = (((qa[8] << i) & 0x80) >> 7) & 1; } for(i = 0; i < 8; ++i) { for(j = 0; j < 8; ++j) { codeword[(i + 1) * 8 + j - 1] = (((qa[7 - i] << j) & 0x80) >> 7) & 1; } } } // Write the message type bits at the end of the array for(i = 0; i < 3; ++i) { codeword[i + 71] = (n3 >> i) & 1; } for(i = 0; i < 3; ++i) { codeword[i + 74] = (i3 >> i) & 1; } } uint8_t jt65_buffer_d[JT65_ENCODE_COUNT]; void JTEncode::jt65_interleave(uint8_t * s) { uint8_t i, j; // Interleave for(i = 0; i < 9; i++) { for(j = 0; j < 7; j++) { jt65_buffer_d[(j * 9) + i] = s[(i * 7) + j]; } } memcpy(s, jt65_buffer_d, JT65_ENCODE_COUNT); } uint8_t jt9_buffer_d[JT9_BIT_COUNT]; void JTEncode::jt9_interleave(uint8_t * s) { uint8_t i, j; // Do the interleave for(i = 0; i < JT9_BIT_COUNT; i++) { //#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__) #if defined(__arm__) jt9_buffer_d[jt9i[i]] = s[i]; #else j = pgm_read_byte(&jt9i[i]); jt9_buffer_d[j] = s[i]; #endif } memcpy(s, jt9_buffer_d, JT9_BIT_COUNT); } uint8_t wspr_buffer_d[WSPR_BIT_COUNT]; void JTEncode::wspr_interleave(uint8_t * s) { uint8_t rev, index_temp, i, j, k; i = 0; for(j = 0; j < 255; j++) { // Bit reverse the index index_temp = j; rev = 0; for(k = 0; k < 8; k++) { if(index_temp & 0x01) { rev = rev | (1 << (7 - k)); } index_temp = index_temp >> 1; } if(rev < WSPR_BIT_COUNT) { wspr_buffer_d[rev] = s[i]; i++; } if(i >= WSPR_BIT_COUNT) { break; } } memcpy(s, wspr_buffer_d, WSPR_BIT_COUNT); } void JTEncode::jt9_packbits(uint8_t * d, uint8_t * a) { uint8_t i, k; k = 0; memset(a, 0, JT9_ENCODE_COUNT); for(i = 0; i < JT9_ENCODE_COUNT; i++) { a[i] = (d[k] & 1) << 2; k++; a[i] |= ((d[k] & 1) << 1); k++; a[i] |= (d[k] & 1); k++; } } void JTEncode::jt_gray_code(uint8_t * g, uint8_t symbol_count) { uint8_t i; for(i = 0; i < symbol_count; i++) { g[i] = gray_code(g[i]); } } void JTEncode::ft8_encode(uint8_t* codeword, uint8_t* symbols) { const uint8_t FT8_N = 174; const uint8_t FT8_K = 91; const uint8_t FT8_M = FT8_N - FT8_K; uint8_t tempchar[FT8_K]; uint8_t message91[FT8_K]; uint8_t pchecks[FT8_M]; uint8_t i1_msg_bytes[12]; uint8_t i, j; uint16_t ncrc14; crc_t crc; crc_cfg_t crc_cfg; crc_cfg.reflect_in = 0; crc_cfg.xor_in = 0; crc_cfg.reflect_out = 0; crc_cfg.xor_out = 0; crc = crc_init(&crc_cfg); // Add 14-bit CRC to form 91-bit message memset(tempchar, 0, 91); memcpy(tempchar, codeword, 77); tempchar[77] = 0; tempchar[78] = 0; tempchar[79] = 0; memset(i1_msg_bytes, 0, 12); for(i = 0; i < 10; ++i) { for(j = 0; j < 8; ++j) { i1_msg_bytes[i] <<= 1; i1_msg_bytes[i] |= tempchar[i * 8 + j]; } } ncrc14 = crc_update(&crc_cfg, crc, (unsigned char *)i1_msg_bytes, 12); crc = crc_finalize(&crc_cfg, crc); for(i = 0; i < 14; ++i) { if((((ncrc14 << (i + 2)) & 0x8000) >> 15) & 1) { tempchar[i + 77] = 1; } else { tempchar[i + 77] = 0; } } memcpy(message91, tempchar, 91); for(i = 0; i < FT8_M; ++i) { uint32_t nsum = 0; for(j = 0; j < FT8_K; ++j) { #if defined(__arm__) uint8_t bits = generator_bits[i][j / 8]; #else uint8_t bits = pgm_read_byte(&(generator_bits[i][j / 8])); #endif bits <<= (j % 8); bits &= 0x80; bits >>= 7; bits &= 1; nsum += (message91[j] * bits); } pchecks[i] = nsum % 2; } memcpy(symbols, message91, FT8_K); memcpy(symbols + FT8_K, pchecks, FT8_M); } const uint8_t jt65_sync_vector[JT65_SYMBOL_COUNT] = {1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}; void JTEncode::jt65_merge_sync_vector(uint8_t * g, uint8_t * symbols) { uint8_t i, j = 0; for(i = 0; i < JT65_SYMBOL_COUNT; i++) { if(jt65_sync_vector[i]) { symbols[i] = 0; } else { symbols[i] = g[j] + 2; j++; } } } const uint8_t jt9_sync_vector[JT9_SYMBOL_COUNT] = {1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1}; void JTEncode::jt9_merge_sync_vector(uint8_t * g, uint8_t * symbols) { uint8_t i, j = 0; for(i = 0; i < JT9_SYMBOL_COUNT; i++) { if(jt9_sync_vector[i]) { symbols[i] = 0; } else { symbols[i] = g[j] + 1; j++; } } } void JTEncode::jt4_merge_sync_vector(uint8_t * g, uint8_t * symbols) { uint8_t i; const uint8_t sync_vector[JT4_SYMBOL_COUNT] = {0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,1 ,0 ,1 ,1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1}; for(i = 0; i < JT4_SYMBOL_COUNT; i++) { symbols[i] = sync_vector[i] + (2 * g[i]); } } const uint8_t wspr_sync_vector[WSPR_SYMBOL_COUNT] = {1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0}; void JTEncode::wspr_merge_sync_vector(uint8_t * g, uint8_t * symbols) { uint8_t i; for(i = 0; i < WSPR_SYMBOL_COUNT; i++) { symbols[i] = wspr_sync_vector[i] + (2 * g[i]); } } void JTEncode::ft8_merge_sync_vector(uint8_t* symbols, uint8_t* output) { const uint8_t costas7x7[7] = {3, 1, 4, 0, 6, 5, 2}; const uint8_t graymap[8] = {0, 1, 3, 2, 5, 6, 4, 7}; uint8_t i, j, k, idx; // Insert Costas sync arrays memcpy(output, costas7x7, 7); memcpy(output + 36, costas7x7, 7); memcpy(output + FT8_SYMBOL_COUNT - 7, costas7x7, 7); k = 6; for(j = 0; j < 58; ++j) // 58 data symbols { i = 3 * j; ++k; if(j == 29) { k += 7; } idx = symbols[i] * 4 + symbols[i + 1] * 2 + symbols[i + 2]; output[k] = graymap[idx]; } } void JTEncode::convolve(uint8_t * c, uint8_t * s, uint8_t message_size, uint8_t bit_size) { uint32_t reg_0 = 0; uint32_t reg_1 = 0; uint32_t reg_temp = 0; uint8_t input_bit, parity_bit; uint8_t bit_count = 0; uint8_t i, j, k; for(i = 0; i < message_size; i++) { for(j = 0; j < 8; j++) { // Set input bit according the MSB of current element input_bit = (((c[i] << j) & 0x80) == 0x80) ? 1 : 0; // Shift both registers and put in the new input bit reg_0 = reg_0 << 1; reg_1 = reg_1 << 1; reg_0 |= (uint32_t)input_bit; reg_1 |= (uint32_t)input_bit; // AND Register 0 with feedback taps, calculate parity reg_temp = reg_0 & 0xf2d05351; parity_bit = 0; for(k = 0; k < 32; k++) { parity_bit = parity_bit ^ (reg_temp & 0x01); reg_temp = reg_temp >> 1; } s[bit_count] = parity_bit; bit_count++; // AND Register 1 with feedback taps, calculate parity reg_temp = reg_1 & 0xe4613c47; parity_bit = 0; for(k = 0; k < 32; k++) { parity_bit = parity_bit ^ (reg_temp & 0x01); reg_temp = reg_temp >> 1; } s[bit_count] = parity_bit; bit_count++; if(bit_count >= bit_size) { break; } } } } void JTEncode::rs_encode(uint8_t * data, uint8_t * symbols) { // Adapted from wrapkarn.c in the WSJT-X source code uint8_t dat1[12]; uint8_t b[51]; uint8_t sym[JT65_ENCODE_COUNT]; uint8_t i; // Reverse data order for the Karn codec. for(i = 0; i < 12; i++) { dat1[i] = data[11 - i]; } // Compute the parity symbols encode_rs_int(rs_inst, dat1, b); // Move parity symbols and data into symbols array, in reverse order. for (i = 0; i < 51; i++) { sym[50 - i] = b[i]; } for (i = 0; i < 12; i++) { sym[i + 51] = dat1[11 - i]; } memcpy(symbols, sym, JT65_ENCODE_COUNT); } uint8_t JTEncode::crc8(const char * text) { uint8_t crc = '\0'; uint8_t ch; int i; for(i = 0; i < strlen(text); i++) { ch = text[i]; //#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__) #if defined(__arm__) crc = crc8_table[(crc) ^ ch]; #else crc = pgm_read_byte(&(crc8_table[(crc) ^ ch])); #endif crc &= 0xFF; } return crc; }