diff --git a/examples/Si5351JTDemo/Si5351JTDemo.ino b/examples/Si5351JTDemo/Si5351JTDemo.ino index b9cd7e4..02a233d 100644 --- a/examples/Si5351JTDemo/Si5351JTDemo.ino +++ b/examples/Si5351JTDemo/Si5351JTDemo.ino @@ -47,16 +47,19 @@ #define JT65_TONE_SPACING 269 // ~2.69 Hz #define JT4_TONE_SPACING 437 // ~4.37 Hz #define WSPR_TONE_SPACING 146 // ~1.46 Hz +#define FSQ_TONE_SPACING 174 // ~1.74 Hz #define JT9_CTC 9000 // CTC value for JT9-1 #define JT65_CTC 5812 // CTC value for JT65A #define JT4_CTC 3578 // CTC value for JT4 #define WSPR_CTC 10672 // CTC value for WSPR +#define FSQ_2_CTC 7812 // CTC value for 2 baud FSQ #define JT9_DEFAULT_FREQ 14078600UL #define JT65_DEFAULT_FREQ 14077500UL #define JT4_DEFAULT_FREQ 14077500UL #define WSPR_DEFAULT_FREQ 14097100UL +#define FSQ_DEFAULT_FREQ 7105350UL; // Base freq is 1350 Hz higher than dial freq in USB #define DEFAULT_MODE MODE_JT4 @@ -65,7 +68,8 @@ #define LED_PIN 13 // Enumerations -enum mode {MODE_JT9, MODE_JT65, MODE_JT4, MODE_WSPR}; +enum mode {MODE_JT9, MODE_JT65, MODE_JT4, MODE_WSPR, MODE_FSQ_2, MODE_FSQ_3, + MODE_FSQ_4_5, MODE_FSQ_6}; // Class instantiation Si5351 si5351; @@ -116,6 +120,12 @@ void encode() case MODE_WSPR: jtencode.wspr_encode(call, loc, dbm, tx_buffer); break; + case MODE_FSQ_2: + case MODE_FSQ_3: + case MODE_FSQ_4_5: + case MODE_FSQ_6: + jtencode.fsq_encode(call, message, tx_buffer); + break; } // Reset the tone to the base frequency and turn on the output @@ -145,6 +155,9 @@ void setup() // Use a button connected to pin 12 as a transmit trigger pinMode(BUTTON, INPUT_PULLUP); + // Set the mode to use + cur_mode = MODE_FSQ_2; + //Serial.begin(57600); // Set the proper frequency, tone spacing, symbol count, and @@ -175,6 +188,11 @@ void setup() symbol_count = WSPR_SYMBOL_COUNT; // From the library defines tone_spacing = WSPR_TONE_SPACING; break; + case MODE_FSQ_2: + freq = FSQ_DEFAULT_FREQ; + ctc = FSQ_2_CTC; + tone_spacing = FSQ_TONE_SPACING; + break; } // Initialize the Si5351 diff --git a/src/JTEncode.cpp b/src/JTEncode.cpp index ddb13b4..3ac236c 100644 --- a/src/JTEncode.cpp +++ b/src/JTEncode.cpp @@ -27,6 +27,14 @@ #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])) + +uint8_t JTEncode::crc8_table[256]; /* Public Class Members */ @@ -34,6 +42,9 @@ JTEncode::JTEncode(void) { // Initialize the Reed-Solomon encoder rs_inst = (struct rs *)(intptr_t)init_rs_int(6, 0x43, 3, 1, 51, 0); + + // Initialize the CRC8 table + init_crc8(); } /* @@ -173,7 +184,7 @@ void JTEncode::jt4_encode(char * message, uint8_t * symbols) */ void JTEncode::wspr_encode(char * call, char * loc, uint8_t dbm, uint8_t * symbols) { - // Ensure that the message text conforms to standards + // Ensure that the message text conforms to standards // -------------------------------------------------- wspr_message_prep(call, loc, dbm); @@ -196,6 +207,181 @@ void JTEncode::wspr_encode(char * call, char * loc, uint8_t dbm, uint8_t * symbo wspr_merge_sync_vector(s, symbols); } +/* + * fsq_dir_encode(char * from_call, char * from_call, char * message, uint8_t * symbols) + * + * Takes an arbitrary message and returns a FSQ channel symbol table. + * + * from_call - Callsign of issuing station + * message - Null-terminated message string, no greater than 200 chars in length + * symbols - Array of channel symbols to transmit retunred 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. + * + */ +void JTEncode::fsq_encode(char * from_call, char * message, uint8_t * symbols) +{ + uint16_t symbol_pos = 0; + uint8_t i, fch, vcode1, vcode2, tone; + uint8_t cur_tone = 0; + char tx_buffer[255]; + char * tx_message; + + // Clearn the transmit message buffer + memset(tx_buffer, 0, 255); + + // Create the message to be transmitted + sprintf(tx_buffer, " \n%s: %s", from_call, message); + + tx_message = 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 = pgm_read_byte(&fsq_code_table[i].ch); + + if(fch == ch) + { + // Found the character, now fetch the varicode chars + vcode1 = pgm_read_byte(&(fsq_code_table[i].var[0])); + vcode2 = pgm_read_byte(&(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(char * from_call, char * to_call, char * cmd, char * message, uint8_t * symbols) + * + * Takes an arbitrary message and returns a FSQ channel symbol table. + * + * from_call - Callsign from which message is directed + * to_call - Callsign to which message is directed + * cmd - Directed command + * message - Null-terminated message string, no greater than 200 chars in length + * symbols - Array of channel symbols to transmit retunred 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. + * + */ +void JTEncode::fsq_dir_encode(char * from_call, char * to_call, char * cmd, char * message, uint8_t * symbols) +{ + uint16_t symbol_pos = 0; + uint8_t i, fch, vcode1, vcode2, tone, from_call_crc; + uint8_t cur_tone = 0; + char tx_buffer[255]; + char * tx_message; + + // Generate a CRC on from_call + // --------------------------- + from_call_crc = crc8(from_call); + + // Clearn the transmit message buffer + memset(tx_buffer, 0, 255); + + // 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(tx_buffer, " \n%s:%02x%s%s%s%s", from_call, from_call_crc, to_call, cmd, message, " \b "); + + tx_message = 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 = pgm_read_byte(&fsq_code_table[i].ch); + + if(fch == ch) + { + // Found the character, now fetch the varicode chars + vcode1 = pgm_read_byte(&(fsq_code_table[i].var[0])); + vcode2 = pgm_read_byte(&(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; +} + /* Private Class Members */ uint8_t JTEncode::jt_code(char c) @@ -798,3 +984,35 @@ void JTEncode::rs_encode(uint8_t * data, uint8_t * symbols) symbols[i + 51] = dat1[11 - i]; } } + +void JTEncode::init_crc8(void) +{ + int i,j; + uint8_t crc; + + for(i = 0; i < 256; i++) + { + crc = i; + for(j = 0; j < 8; j++) + { + crc = (crc << 1) ^ ((crc & 0x80) ? 0x07 : 0); + } + crc8_table[i] = crc & 0xFF; + } +} + +uint8_t JTEncode::crc8(char * text) +{ + uint8_t crc = '\0'; + uint8_t ch; + + int i; + for(i = 0; i < strlen(text); i++) + { + ch = text[i]; + crc = crc8_table[(crc) ^ ch]; + crc &= 0xFF; + } + + return crc; +} diff --git a/src/JTEncode.h b/src/JTEncode.h index 61f055d..a36d2be 100644 --- a/src/JTEncode.h +++ b/src/JTEncode.h @@ -25,6 +25,7 @@ #include "rs_common.h" #include +#include #define JT65_SYMBOL_COUNT 126 #define JT9_SYMBOL_COUNT 85 @@ -38,6 +39,125 @@ #define JT4_BIT_COUNT 206 #define WSPR_BIT_COUNT 162 +// Define the structure of a varicode table +typedef struct fsq_varicode +{ + uint8_t ch; + uint8_t var[2]; +} Varicode; + +// The FSQ varicode table, based on the FSQ Varicode V3.0 +// document provided by Murray Greenman, ZL1BPU + +const Varicode fsq_code_table[] PROGMEM = +{ + {' ', {00, 00}}, // space + {'!', {11, 30}}, + {'"', {12, 30}}, + {'#', {13, 30}}, + {'$', {14, 30}}, + {'%', {15, 30}}, + {'&', {16, 30}}, + {'\'', {17, 30}}, + {'(', {18, 30}}, + {')', {19, 30}}, + {'*', {20, 30}}, + {'+', {21, 30}}, + {',', {27, 29}}, + {'-', {22, 30}}, + {'.', {27, 00}}, + {'/', {23, 30}}, + {'0', {10, 30}}, + {'1', {01, 30}}, + {'2', {02, 30}}, + {'3', {03, 30}}, + {'4', {04, 30}}, + {'5', {05, 30}}, + {'6', {06, 30}}, + {'7', {07, 30}}, + {'8', {8, 30}}, + {'9', {9, 30}}, + {':', {24, 30}}, + {';', {25, 30}}, + {'<', {26, 30}}, + {'=', {00, 31}}, + {'>', {27, 30}}, + {'?', {28, 29}}, + {'@', {00, 29}}, + {'A', {01, 29}}, + {'B', {02, 29}}, + {'C', {03, 29}}, + {'D', {04, 29}}, + {'E', {05, 29}}, + {'F', {06, 29}}, + {'G', {07, 29}}, + {'H', {8, 29}}, + {'I', {9, 29}}, + {'J', {10, 29}}, + {'K', {11, 29}}, + {'L', {12, 29}}, + {'M', {13, 29}}, + {'N', {14, 29}}, + {'O', {15, 29}}, + {'P', {16, 29}}, + {'Q', {17, 29}}, + {'R', {18, 29}}, + {'S', {19, 29}}, + {'T', {20, 29}}, + {'U', {21, 29}}, + {'V', {22, 29}}, + {'W', {23, 29}}, + {'X', {24, 29}}, + {'Y', {25, 29}}, + {'Z', {26, 29}}, + {'[', {01, 31}}, + {'\\', {02, 31}}, + {']', {03, 31}}, + {'^', {04, 31}}, + {'_', {05, 31}}, + {'`', {9, 31}}, + {'a', {01, 00}}, + {'b', {02, 00}}, + {'c', {03, 00}}, + {'d', {04, 00}}, + {'e', {05, 00}}, + {'f', {06, 00}}, + {'g', {07, 00}}, + {'h', {8, 00}}, + {'i', {9, 00}}, + {'j', {10, 00}}, + {'k', {11, 00}}, + {'l', {12, 00}}, + {'m', {13, 00}}, + {'n', {14, 00}}, + {'o', {15, 00}}, + {'p', {16, 00}}, + {'q', {17, 00}}, + {'r', {18, 00}}, + {'s', {19, 00}}, + {'t', {20, 00}}, + {'u', {21, 00}}, + {'v', {22, 00}}, + {'w', {23, 00}}, + {'x', {24, 00}}, + {'y', {25, 00}}, + {'z', {26, 00}}, + {'{', {06, 31}}, + {'|', {07, 31}}, + {'}', {8, 31}}, + {'~', {00, 30}}, + {127, {28, 31}}, // DEL + {13, {28, 00}}, // CR + {10, {28, 00}}, // LF + {0, {28, 30}}, // IDLE + {241, {10, 31}}, // plus/minus + {246, {11, 31}}, // division sign + {248, {12, 31}}, // degrees sign + {158, {13, 31}}, // multiply sign + {156, {14, 31}}, // pound sterling sign + {8, {27, 31}} // BS +}; + class JTEncode { public: @@ -46,6 +166,8 @@ public: void jt9_encode(char *, uint8_t *); void jt4_encode(char *, uint8_t *); void wspr_encode(char *, char *, uint8_t, uint8_t *); + void fsq_encode(char *, char *, uint8_t *); + void fsq_dir_encode(char *, char *, char *, char *, uint8_t *); private: uint8_t jt_code(char); uint8_t wspr_code(char); @@ -69,8 +191,11 @@ private: void encode_rs_int(void *,data_t *, data_t *); void free_rs_int(void *); void * init_rs_int(int, int, int, int, int, int); + void init_crc8(void); + uint8_t crc8(char *); void * rs_inst; char callsign[7]; char locator[5]; uint8_t power; + static uint8_t crc8_table[256]; };