Here’s the code for JT65 with OLED display and text input via a USB connected terminal program, such as MacOS iSerialTerm or the Arduino IDE Serial Monitor window.
Code
// JT65_ADS_TEXT_OLED
// V2.5 9-4-17 serial input/output, update to U8g2lib
// Code based on Feld Hell beacon for Arduino by K6HX
// Timer setup code by LA3PNA.
// TX 7060000, Dial 7074830
// AD9850
// W_CLK 8
// FQ_UD 9
// DATA 10
// RESET 11
// OLED 128x64
// SDA = A4
// SCL = A5
// RTC I2C bus
// SDA = A4
// SCL = A5
#include "ADS9850.h"
#include "U8g2lib.h"
#include "Wire.h"
// RTC address
#define RTCADDR 0x68
// Stuff specific to the general (integer) version of the Reed-Solomon codecs
#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)
#define TONE_SPACING 269 // ~2.6917 Hz
#define SUBMODE_A 5812 // CTC value for JT65A
#define SYMBOL_COUNT 126
// AD9850 pins
#define W_CLK 8
#define FQ_UD 9
#define DATA 10
#define RESET 11
typedef unsigned int data_t;
/* 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 */
};
// RTC time
byte sec, mns, hrs;
// trigger TX (sec)
uint8_t trigger;
bool tx;
// Frequency variables
double freqHz = 7076000; // nominal dial is 1270Hz lower
double freqChz = 0;
uint8_t phase = 0;
// message to send
char message[20] = "";
// encode variables
uint8_t tx_buffer[SYMBOL_COUNT];
static void *rs;
// timer flag
volatile bool proceed = false;
// AD9850 object
ADS9850 ads;
// oled object
U8G2_SH1106_128X64_NONAME_1_HW_I2C oled(U8G2_R0, U8X8_PIN_NONE, SCL, SDA);
void setup()
{
Serial.begin(9600);
// init I2C
Wire.begin();
// oled init, sets I2C addr to 0x3C
oled.begin();
// init AD9850, init freq and off to start
ads.begin(W_CLK, FQ_UD, DATA, RESET);
ads.setFreq(freqHz, freqChz, phase);
ads.down();
// 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, 64us ticks
TIMSK1 = (1 << OCIE1A); // Enable timer compare interrupt.
OCR1A = SUBMODE_A; // Set up interrupt trigger count;
interrupts(); // Re-enable interrupts.
trigger = 1; // trigger on each minute
tx = false;
// Initialize the Reed-Solomon encoder
rs = (struct rs *)(intptr_t)init_rs_int(6, 0x43, 3, 1, 51, 0);
}
void loop() {
// get time
getRTC();
dispUpdate();
// message?
if (getMsg(message) == true) {
// echo message
Serial.println(message);
// display message & time, trigger time?
do {
getRTC();
dispUpdate();
} while (mns % trigger != 0 || sec != 0);
// send
tx = true; // indicate TX on display
encode(message);
tx = false;
// clear message & buffer
clearBuf(message);
}
}
// picture loop, display init data
void dispUpdate() {
oled.firstPage();
do {
dispMsg(15, 0, "JT65_ADS_TEXT");
dispMsg(15, 15, message);
dispFreq(15, 30, freqHz, freqChz, 2);
if (tx == false) dispTime(30, 50);
else dispMsg(40, 50, "TX");
} while ( oled.nextPage() );
}
// Loop through the string, transmitting one character at a time.
void encode(char * tx_string)
{
uint8_t i;
// encode the message
jt65_encode(tx_string, tx_buffer);
// Now do the rest of the message
for (i = 0; i < SYMBOL_COUNT; i++)
{
ads.setFreq(freqHz, freqChz + (tx_buffer[i] * TONE_SPACING), phase);
proceed = false;
while (!proceed);
}
// Turn off the output
ads.down();
}
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;
}
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;
}
}
// timer interrupt veector
ISR(TIMER1_COMPA_vect)
{
proceed = true;
}
// Reed Soloman encoder by KA9Q
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);
}
// init RS enc, symb size, poly coeff, first root, primative, no roots, padding
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;
}
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++;
}
}
}
// get time from RTC, convert bcd to decimal
void getRTC()
{
// Reset the register pointer
Wire.beginTransmission(RTCADDR);
byte zero = 0x00;
Wire.write(zero);
Wire.endTransmission();
// request 1st 3 bytes from the RTC address
Wire.requestFrom(RTCADDR, 3);
// get the s/m/h time data
sec = bcdToDec(Wire.read());
mns = bcdToDec(Wire.read());
hrs = bcdToDec(Wire.read() & 0b111111); // mask 24 hour time bit
}
// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
return ( (val / 16 * 10) + (val % 16) );
}
// get input message[] U.C.
bool getMsg(char *m)
{
char ch;
int n;
n = 0;
if (Serial.available() > 0) { // if input
while (Serial.available() > 0) { // get input
ch = Serial.read(); // use upper case as input
if (ch == '\n') ch = '\0'; // end of text
m[n++] = ch;
delay(20); // let USB catch up
}
return true; // got input
}
return false; // no input
}
// clear msg and buffer
void clearBuf(char *m) {
m[0] = '\0';
while (Serial.available() > 0) Serial.read();
}
// display freq at x, y, f (Hz), cf (cHz), d)ecimal places
void dispFreq(u8g2_uint_t x, u8g2_uint_t y, double f, double cf, uint8_t d) {
double fd;
char buf[100];
// sets font, cursor position and displays freq
oled.setFont(u8g2_font_10x20_tf); // font
oled.setFontPosTop(); // origin top
fd = f + cf / 100; // calc freq
oled.setCursor(x, y);
oled.print(fd / 1000, d);
oled.print("kHz");
}
// display message at at x), y), *m)essage
void dispMsg(u8g2_uint_t x, u8g2_uint_t y, char *m) {
// sets font, cursor position and displays message
oled.setFont(u8g2_font_7x13_tf); // font
oled.setFontPosTop();
oled.setCursor(x, y);
oled.print(m);
}
// display time HH:MM:SS at x), y)
void dispTime(u8g2_uint_t x, u8g2_uint_t y) {
// sets font, cursor position and displays message
oled.setFont(u8g_font_7x14); // fix font for now
oled.setFontPosTop();
oled.setCursor(x, y);
if (hrs < 10)
oled.print("0");
oled.print(hrs);
oled.print(":");
if (mns < 10)
oled.print("0");
oled.print(mns);
oled.print(":");
if (sec < 10)
oled.print("0");
oled.print(sec);
}
No comments:
Post a Comment