Last active
July 14, 2019 12:49
-
-
Save NT7S/5c4d15769d5036fb258e to your computer and use it in GitHub Desktop.
Simple JT9 beacon for Arduino driving the Si5351
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Simple JT9 beacon for Arduino, with the Etherkit Si5351A Breakout | |
// Board, by Jason Milldrum NT7S. | |
// | |
// Transmit an abritrary message of up to 13 valid characters | |
// (a Type 6 message). | |
// | |
// Original code based on Feld Hell beacon for Arduino by Mark | |
// Vandewettering K6HX, adapted for the Si5351A by Robert | |
// Liesenfeld AK6L <[email protected]>. Timer setup | |
// code by Thomas Knutsen LA3PNA. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining | |
// a copy of this software and associated documentation files (the | |
// "Software"), to deal in the Software without restriction, including | |
// without limitation the rights to use, copy, modify, merge, publish, | |
// distribute, sublicense, and/or sell copies of the Software, and to | |
// permit persons to whom the Software is furnished to do so, subject | |
// to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be | |
// included in all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR | |
// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF | |
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
// | |
#include <si5351.h> | |
#include <string.h> | |
#include "Wire.h" | |
#define TONE_SPACING 174 // ~1.736 Hz | |
#define SUBMODE_1 9000 // CTC value for JT9-1 | |
#define SYMBOL_COUNT 85 | |
#define LED_PIN 13 | |
// Global variables | |
Si5351 si5351; | |
unsigned long freq = 14078600; | |
char message[14] = "NT7S CN85"; | |
uint8_t tx_buffer[SYMBOL_COUNT]; | |
// Global variables used in ISRs | |
volatile bool proceed = false; | |
// Timer interrupt vector. This toggles the variable we use to gate | |
// each column of output to ensure accurate timing. Called whenever | |
// Timer1 hits the count set below in setup(). | |
ISR(TIMER1_COMPA_vect) | |
{ | |
proceed = true; | |
} | |
uint8_t 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 gray_code(uint8_t c) | |
{ | |
return (c >> 1) ^ c; | |
} | |
void jt9_encode(char * message, uint8_t symbols[SYMBOL_COUNT]) | |
{ | |
uint8_t i, j, k; | |
// Convert all chars to uppercase | |
for(i = 0; i < 13; i++) | |
{ | |
if(islower(message[i])) | |
{ | |
message[i] = toupper(message[i]); | |
} | |
} | |
// Collapse multiple spaces down to one | |
// Pad the message with trailing spaces | |
uint8_t len = strlen(message); | |
if(len < 13) | |
{ | |
for(i = len; i < 13; i++) | |
{ | |
message[i] = ' '; | |
} | |
} | |
// Bit packing | |
// ----------- | |
uint8_t c[13]; | |
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; | |
// Convolutional encoding | |
// ---------------------- | |
// Parity bits are generated from clocking our packed bits into | |
// a LFSR. | |
uint8_t s[206]; | |
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; | |
for(i = 0; i < 13; 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; | |
//printf("%d %d %x\n", i, j, input_bit); | |
// 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 >= 206) | |
{ | |
break; | |
} | |
} | |
} | |
// Interleaving | |
// ------------ | |
uint8_t d[206]; | |
uint8_t j0[206]; | |
uint8_t n; | |
k = 0; | |
//n = 0; | |
// Build the interleave table | |
for(i = 0; i < 255; i++) | |
{ | |
n = 0; | |
for(j = 0; j < 8; j++) | |
{ | |
n = (n << 1) + ((i >> j) & 1); | |
} | |
if(n < 206) | |
{ | |
j0[k] = n; | |
k++; | |
} | |
if(k >= 206) | |
{ | |
break; | |
} | |
} | |
// Now do the interleave | |
for(i = 0; i < 206; i++) | |
{ | |
d[j0[i]] = s[i]; | |
} | |
// Pack bits into 3-bit symbols | |
// ---------------------------- | |
uint8_t a[69]; | |
k = 0; | |
memset(a, 0, 69); | |
for(i = 0; i < 69; i++) | |
{ | |
a[i] = (d[k] & 1) << 2; | |
k++; | |
a[i] |= ((d[k] & 1) << 1); | |
k++; | |
a[i] |= (d[k] & 1); | |
k++; | |
} | |
// Gray Code | |
// --------- | |
uint8_t g[69]; | |
for(i = 0; i < 69; i++) | |
{ | |
g[i] = gray_code(a[i]); | |
} | |
/* Merge with sync vector */ | |
uint8_t o[85]; | |
const uint8_t sync_vector[85] = | |
{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}; | |
j = 0; | |
for(i = 0; i < 85; i++) | |
{ | |
if(sync_vector[i]) | |
{ | |
symbols[i] = 0; | |
} | |
else | |
{ | |
symbols[i] = g[j] + 1; | |
j++; | |
} | |
} | |
} | |
// Loop through the string, transmitting one character at a time. | |
void encode(char * tx_string) | |
{ | |
uint8_t i; | |
jt9_encode(tx_string, tx_buffer); | |
// Reset the tone to 0 and turn on the output | |
si5351.output_enable(SI5351_CLK0, 1); | |
digitalWrite(LED_PIN, HIGH); | |
// Now do the rest of the message | |
for(i = 0; i < SYMBOL_COUNT; i++) | |
{ | |
si5351.set_freq((freq * 100) + (tx_buffer[i] * TONE_SPACING), 0, SI5351_CLK0); | |
proceed = false; | |
while(!proceed); | |
} | |
// Turn off the output | |
si5351.output_enable(SI5351_CLK0, 0); | |
digitalWrite(LED_PIN, LOW); | |
} | |
void setup() | |
{ | |
// Use the Arduino's on-board LED as a keying indicator. | |
pinMode(LED_PIN, OUTPUT); | |
digitalWrite(LED_PIN, LOW); | |
//Serial.begin(57600); | |
// Initialize the Si5351 | |
// Change the 2nd parameter in init if using a ref osc other | |
// than 25 MHz | |
si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0); | |
// Set CLK0 output | |
si5351.set_correction(0); | |
si5351.set_freq(freq * 100, 0, SI5351_CLK0); | |
si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA); // Set for max power if desired | |
si5351.output_enable(SI5351_CLK0, 0); // Disable the clock initially | |
// Set up Timer1 for interrupts every symbol period. | |
noInterrupts(); // Turn off interrupts. | |
TCCR1A = 0; // Set entire TCCR1A register to 0; disconnects | |
// interrupt output pins, sets normal waveform | |
// mode. We're just using Timer1 as a counter. | |
TCNT1 = 0; // Initialize counter value to 0. | |
TCCR1B = (1 << CS12) | // Set CS12 and CS10 bit to set prescale | |
(1 << CS10) | // to /1024 | |
(1 << WGM12); // turn on CTC | |
// which gives, 64 us ticks | |
TIMSK1 = (1 << OCIE1A); // Enable timer compare interrupt. | |
OCR1A = SUBMODE_1; // Set up interrupt trigger count; | |
interrupts(); // Re-enable interrupts. | |
// Send the message on startup. | |
// Needs to be synced to a minute boundary. | |
encode(message); | |
} | |
void loop() | |
{ | |
// Nothing | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you for the magic code.