Monday, 10 April 2017

BASIC Tech Group - MyNews 37 - Updated JT65 with OLED display

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: