Skip to content

Instantly share code, notes, and snippets.

@NT7S
Created September 30, 2015 15:10
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save NT7S/c72a781f5798a660f2f3 to your computer and use it in GitHub Desktop.
Simple JT65 beacon for Arduino driving the Si5351
/* 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)
/* 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;
}
//
// 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 <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