Tuesday 28 March 2017

BASIC Tech Group - MyNews 33 - More on JT65, Text input

First the block diagrams of my set-up...

JT65 002 Transmit chain

JT65 001 Receive chain

The transmit AD9850 is set to 7076kHz and sends the JT65 symbols on this and 64 higher frequencies spaced 2.69Hz above. The SDR is set to USB on a dial frequency of 7074.725 to give a received audio sync tone at 1000Hz. These tones are sent in 126 time slots of 327ms, a total message transmission time of 47.8sec.

JT65 comms

JT65 requires text input. But there are a few special messages which are not plain text, but cunningly encoded to send more than the limit of 13 characters. Messages such as a reply to a CQ "VK4EF M0IFA R-13" replying to VK4EF with a Roger, and report of 13dB signal strength. But here is a minimum QSO

Screen Shot 2017 03 28 at 12 41 50 Simple JT65 QSO Messages

I have not yet puzzled out how to encode these special messages on the Arduino, so today I can only send max 13 character normal text messages (Upper Case only plus 0-9). My code has been updated to do this, by entering the message on a serial communication app called iSerialTerm (see Mac App store).

Here's a few photos showing what I am doing...

IMG 0896My Desk Top, SDR Concept RX, iMac running HDSDR and JT65-HF programs, AD9850 synthesiser TX

IMG 0895Close up of the RX and TX. Note the tiny piece of yellow wire which is the TX antenna!

Screen Shot 2017 03 29 at 11 04 12 iSerialTerm app running on my MacBook to communicate via Serial with the Arduino UNO. Note 9600 baud, and line end '\n'.

IMG 0898 The message entered on the MacBook, TX waiting for an on-the-minute timing to start sending

IMG 0899The TX sending the message

IMG 0900 The SDR receiver tuning, centre frequency 7050kHz

Screen Shot 2017 03 28 at 12 00 30The HDSDR signal received

Screen Shot 2017 03 28 at 12 04 16The JT65-HF signal received

Screen Shot 2017 03 28 at 12 03 06 ... and finally the text message received. Voila.

Code

// JT65_ADS_TEXT
// V1.3 29-3-17 added message input/output using iSerialTerm app
// Code based on Feld Hell beacon for Arduino by K6HX
// Timer setup code by LA3PNA.
// AD9850
// W_CLK 8
// FQ_UD 9
// DATA 10
// RESET 11
// LCD I2C bus (16x2)
// SDA = A4
// SCL = A5
// RTC I2C bus
// SDA = A4
// SCL = A5

#include "ADS9850.h"
#include "LiquidCrystal_I2C.h"
#include "Wire.h"

// chose address of 0x27 or 0x3F for LCD
//#define LCDADDR 0x27
#define LCDADDR 0x3F
#define LCDCOLS 16
#define LCDROWS 2

// 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

// send indicator
#define LED_PIN                 13

// 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<= 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 msg[] 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 msg *m at c)ol, r)ow
void dispMsg(uint8_t c, uint8_t r, char *m)
{
  lcd.setCursor(c, r);
  lcd.print(m);
}

// display freq at c)ol, r)ow, f (cHz), d decimal places
void dispFreq(uint8_t c, uint8_t r, float f, float cf, uint8_t d) {
  lcd.setCursor(c, r);
  lcd.print((f + cf / 100.0), d);
  lcd.print("Hz ");
}

// display time at col, row
void dispTime(byte c, byte r) {
  lcd.setCursor(c, r);
  if (hrs < 10)
    lcd.print("0");
  lcd.print(hrs);
  lcd.print(":");
  if (mns < 10)
    lcd.print("0");
  lcd.print(mns);
  lcd.print(":");
  if (sec < 10)
    lcd.print("0");
  lcd.print(sec);
}

No comments: