Created
September 30, 2015 15:10
-
-
Save NT7S/c72a781f5798a660f2f3 to your computer and use it in GitHub Desktop.
Simple JT65 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
/* Stuff specific to the general (integer) version of the Reed-Solomon codecs | |
* | |
* Copyright 2003, Phil Karn, KA9Q | |
* May be used under the terms of the GNU Lesser General Public License (LGPL) | |
*/ | |
typedef unsigned int data_t; | |
#define MODNN(x) modnn(rs,x) | |
#define MM (rs->mm) | |
#define NN (rs->nn) | |
#define ALPHA_TO (rs->alpha_to) | |
#define INDEX_OF (rs->index_of) | |
#define GENPOLY (rs->genpoly) | |
#define NROOTS (rs->nroots) | |
#define FCR (rs->fcr) | |
#define PRIM (rs->prim) | |
#define IPRIM (rs->iprim) | |
#define PAD (rs->pad) | |
#define A0 (NN) |
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
/* Stuff common to all the general-purpose Reed-Solomon codecs | |
* Copyright 2004 Phil Karn, KA9Q | |
* May be used under the terms of the GNU Lesser General Public License (LGPL) | |
*/ | |
#include "int.h" | |
/* Reed-Solomon codec control block */ | |
struct rs { | |
int mm; /* Bits per symbol */ | |
int nn; /* Symbols per block (= (1<<mm)-1) */ | |
data_t *alpha_to; /* log lookup table */ | |
data_t *index_of; /* Antilog lookup table */ | |
data_t *genpoly; /* Generator polynomial */ | |
int nroots; /* Number of generator roots = number of parity symbols */ | |
int fcr; /* First consecutive root, index form */ | |
int prim; /* Primitive element, index form */ | |
int iprim; /* prim-th root of 1, index form */ | |
int pad; /* Padding bytes in shortened block */ | |
}; | |
static inline int modnn(struct rs *rs,int x){ | |
while (x >= rs->nn) { | |
x -= rs->nn; | |
x = (x >> rs->mm) + (x & rs->nn); | |
} | |
return x; | |
} |
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 <ctype.h> | |
#include <stdlib.h> | |
#include "int.h" | |
#include "rs_common.h" | |
#include "Wire.h" | |
#define TONE_SPACING 269 // ~2.6917 Hz | |
#define SUBMODE_A 5812 // CTC value for JT65A | |
#define SYMBOL_COUNT 126 | |
#define LED_PIN 13 | |
//typedef unsigned int data_t; | |
// Global variables | |
Si5351 si5351; | |
unsigned long freq = 14077100; | |
char message[14] = "NT7S CN85"; | |
uint8_t tx_buffer[SYMBOL_COUNT]; | |
static void * rs; | |
// Global variables used in ISRs | |
volatile bool proceed = false; | |
/* Reed-Solomon encoder | |
* Copyright 2003, Phil Karn, KA9Q | |
* May be used under the terms of the GNU Lesser General Public License (LGPL) | |
*/ | |
void encode_rs_int(void *p,data_t *data, data_t *parity) | |
{ | |
struct rs *rs = (struct rs *)p; | |
#undef A0 | |
#define A0 (NN) /* Special reserved value encoding zero in index form */ | |
int i, j; | |
data_t feedback; | |
memset(parity,0,NROOTS*sizeof(data_t)); | |
for(i=0;i<NN-NROOTS-PAD;i++){ | |
feedback = INDEX_OF[data[i] ^ parity[0]]; | |
if(feedback != A0){ /* feedback term is non-zero */ | |
#ifdef UNNORMALIZED | |
/* This line is unnecessary when GENPOLY[NROOTS] is unity, as it must | |
* always be for the polynomials constructed by init_rs() | |
*/ | |
feedback = MODNN(NN - GENPOLY[NROOTS] + feedback); | |
#endif | |
for(j=1;j<NROOTS;j++) | |
parity[j] ^= ALPHA_TO[MODNN(feedback + GENPOLY[NROOTS-j])]; | |
} | |
/* Shift */ | |
memmove(&parity[0],&parity[1],sizeof(data_t)*(NROOTS-1)); | |
if(feedback != A0) | |
parity[NROOTS-1] = ALPHA_TO[MODNN(feedback + GENPOLY[0])]; | |
else | |
parity[NROOTS-1] = 0; | |
} | |
} | |
void free_rs_int(void *p) | |
{ | |
struct rs *rs = (struct rs *)p; | |
free(rs->alpha_to); | |
free(rs->index_of); | |
free(rs->genpoly); | |
free(rs); | |
} | |
/* Initialize a Reed-Solomon codec | |
* symsize = symbol size, bits | |
* gfpoly = Field generator polynomial coefficients | |
* fcr = first root of RS code generator polynomial, index form | |
* prim = primitive element to generate polynomial roots | |
* nroots = RS code generator polynomial degree (number of roots) | |
* pad = padding bytes at front of shortened block | |
*/ | |
void *init_rs_int(int symsize,int gfpoly,int fcr,int prim, | |
int nroots,int pad){ | |
struct rs *rs; | |
//#undef NULL | |
//#define NULL ((void *)0) | |
int i, j, sr,root,iprim; | |
rs = ((struct rs *)0); | |
/* Check parameter ranges */ | |
if(symsize < 0 || symsize > 8*sizeof(data_t)){ | |
goto done; | |
} | |
if(fcr < 0 || fcr >= (1<<symsize)) | |
goto done; | |
if(prim <= 0 || prim >= (1<<symsize)) | |
goto done; | |
if(nroots < 0 || nroots >= (1<<symsize)) | |
goto done; /* Can't have more roots than symbol values! */ | |
if(pad < 0 || pad >= ((1<<symsize) -1 - nroots)) | |
goto done; /* Too much padding */ | |
rs = (struct rs *)calloc(1,sizeof(struct rs)); | |
if(rs == NULL) | |
goto done; | |
rs->mm = symsize; | |
rs->nn = (1<<symsize)-1; | |
rs->pad = pad; | |
rs->alpha_to = (data_t *)malloc(sizeof(data_t)*(rs->nn+1)); | |
if(rs->alpha_to == NULL){ | |
free(rs); | |
rs = ((struct rs *)0); | |
goto done; | |
} | |
rs->index_of = (data_t *)malloc(sizeof(data_t)*(rs->nn+1)); | |
if(rs->index_of == NULL){ | |
free(rs->alpha_to); | |
free(rs); | |
rs = ((struct rs *)0); | |
goto done; | |
} | |
/* Generate Galois field lookup tables */ | |
rs->index_of[0] = A0; /* log(zero) = -inf */ | |
rs->alpha_to[A0] = 0; /* alpha**-inf = 0 */ | |
sr = 1; | |
for(i=0;i<rs->nn;i++){ | |
rs->index_of[sr] = i; | |
rs->alpha_to[i] = sr; | |
sr <<= 1; | |
if(sr & (1<<symsize)) | |
sr ^= gfpoly; | |
sr &= rs->nn; | |
} | |
if(sr != 1){ | |
/* field generator polynomial is not primitive! */ | |
free(rs->alpha_to); | |
free(rs->index_of); | |
free(rs); | |
rs = ((struct rs *)0); | |
goto done; | |
} | |
/* Form RS code generator polynomial from its roots */ | |
rs->genpoly = (data_t *)malloc(sizeof(data_t)*(nroots+1)); | |
if(rs->genpoly == NULL){ | |
free(rs->alpha_to); | |
free(rs->index_of); | |
free(rs); | |
rs = ((struct rs *)0); | |
goto done; | |
} | |
rs->fcr = fcr; | |
rs->prim = prim; | |
rs->nroots = nroots; | |
/* Find prim-th root of 1, used in decoding */ | |
for(iprim=1;(iprim % prim) != 0;iprim += rs->nn) | |
; | |
rs->iprim = iprim / prim; | |
rs->genpoly[0] = 1; | |
for (i = 0,root=fcr*prim; i < nroots; i++,root += prim) { | |
rs->genpoly[i+1] = 1; | |
/* Multiply rs->genpoly[] by @**(root + x) */ | |
for (j = i; j > 0; j--){ | |
if (rs->genpoly[j] != 0) | |
rs->genpoly[j] = rs->genpoly[j-1] ^ rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[j]] + root)]; | |
else | |
rs->genpoly[j] = rs->genpoly[j-1]; | |
} | |
/* rs->genpoly[0] can never be zero */ | |
rs->genpoly[0] = rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[0]] + root)]; | |
} | |
/* convert rs->genpoly[] to index form for quicker encoding */ | |
for (i = 0; i <= nroots; i++) | |
rs->genpoly[i] = rs->index_of[rs->genpoly[i]]; | |
done:; | |
return rs; | |
} | |
// END KA9Q Reed-Solomon library | |
// 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 rs_encode(uint8_t * data, uint8_t * symbols) | |
{ | |
unsigned int dat1[12]; | |
unsigned int b[51]; | |
unsigned int 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, dat1, b); | |
// Move parity symbols and data into symbols array, in reverse order. | |
for (i = 0; i < 51; i++) | |
{ | |
symbols[50-i] = b[i]; | |
} | |
for (i = 0; i < 12; i++) | |
{ | |
symbols[i + 51] = dat1[11 - i]; | |
} | |
} | |
void jt65_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[12]; | |
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; | |
// Reed-Solomon encoding | |
// --------------------- | |
uint8_t s[63]; | |
k = 0; | |
rs_encode(c, s); | |
// Interleaving | |
// ------------ | |
uint8_t d[63]; | |
uint8_t d1[7][9]; | |
// Fill temp d1 array | |
for(i = 0; i < 9; i++) | |
{ | |
for(j = 0; j < 7; j++) | |
{ | |
d1[i][j] = s[(i * 7) + j]; | |
} | |
} | |
// Interleave and translate back to 1D destination array | |
for(i = 0; i < 7; i++) | |
{ | |
for(j = 0; j < 9; j++) | |
{ | |
d[(i * 9) + j] = d1[j][i]; | |
} | |
} | |
// Gray Code | |
// --------- | |
uint8_t g[63]; | |
for(i = 0; i < 63; i++) | |
{ | |
g[i] = gray_code(d[i]); | |
} | |
// Merge with sync vector | |
// ---------------------- | |
const uint8_t sync_vector[126] = | |
{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}; | |
j = 0; | |
for(i = 0; i < 126; i++) | |
{ | |
if(sync_vector[i]) | |
{ | |
symbols[i] = 0; | |
} | |
else | |
{ | |
symbols[i] = g[j] + 2; | |
j++; | |
} | |
} | |
} | |
// Loop through the string, transmitting one character at a time. | |
void encode(char * tx_string) | |
{ | |
uint8_t i; | |
jt65_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(-6190); | |
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_A; // Set up interrupt trigger count; | |
interrupts(); // Re-enable interrupts. | |
// Initialize the Reed-Solomon encoder | |
rs = (struct rs *)(intptr_t)init_rs_int(6, 0x43, 3, 1, 51, 0); | |
// 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