Skip to content

Instantly share code, notes, and snippets.

@NT7S
Last active July 14, 2019 12:49
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save NT7S/5c4d15769d5036fb258e to your computer and use it in GitHub Desktop.
Save NT7S/5c4d15769d5036fb258e to your computer and use it in GitHub Desktop.
Simple JT9 beacon for Arduino driving the Si5351
//
// 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 <ak6l@ak6l.org>. 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
}
@ZS5LT
Copy link

ZS5LT commented Nov 4, 2015

Thank you for the magic code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment