Thursday 16 February 2017

BASIC Tech Group - MyNews 24 - Feld-Hell TX

I have already put on this blog sequential multi-frequency Hellschreiber (HELL-S/MT). The original system however uses pulses at a single frequency. It also uses 7x14 fonts (14 half-pixels). So I have a new Arduino sketch (below) with the 7x14 font and transmitting on a single frequency. NT7S has a version that uses timer interrupts running at 245Hz for half-pixels giving rate of 122.5 baud the official timing for FELD-HELL to send the pixels for 4.0816ms which I have used here.

The hardware is the same as before: an Arduino UNO and a Si5351 DDS frequency synthesiser.

IMG 0778

IMG 0779

Here is the reception (which is this case can be done on LSB or USB) on my simple DC RX,

IMG 0776

IMG 0777

and displayed using the Mac version of CocoaModem 2.0, a program able to receive many digital modes...

Screen Shot 2017 02 20 at 13 50 07

Screen Shot 2017 02 20 at 13 49 53

Due to the digital output of the Si5351 there are a lot of pulse sidebands and harmonics which would need to be filtered out.

// HELL_FELD_7x14
// ====== V3 20-2-17
// font is 7 cols x 14 rows of 1/2 pixels
// read from left to right, bottom up
// bottom 2 1/2 pixel rows and top 2 1/2 pixel rows are blank
// 2 x right cols are blank
// so a char occupies 5 cols x 10 half pixels
// characters are transmitted in 400ms, or 400/7 = 57.14ms per col,
// or 57.14/14 per row = 4.0816ms/pixel
// baud rate is 1/(2 x 4.0816) = 122.5 baud, throughput 2.5ch/sec or 25wpm
// =====
// Si5351 I2C bus
// SDA = A4
// SCL = A5
// LCD I2C bus (16x2)
// SDA = A4
// SCL = A5

// Si5351, LCD I2C libraries
#include "si5351.h"
#include "LiquidCrystal_I2C.h"

// LCD 0x27 or 0x3F
//#define LCDADDR 0x3F
#define LCDADDR 0x27
#define LCDCOLS 16
#define LCDROWS 2

// frequencies, base (cHz)
#define BASEFREQ 707700000  // freqs 7077-7084, 10137-10144, 14060QRP, 14063-14069kHz

// Si5351 module corrections
#define CORRA 18400
#define CORRB 20000

volatile bool proceed = false;

typedef struct glyph {
  char ch ;
  word col[7] ;
} Glyph ;

// table of glyphs 7x14 for each character
Glyph glyphtab[] = {
  {' ', {0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}},
  {'A', {0x07fc, 0x0e60, 0x0c60, 0x0e60, 0x07fc, 0x0000, 0x0000}},
  {'B', {0x0c0c, 0x0ffc, 0x0ccc, 0x0ccc, 0x0738, 0x0000, 0x0000}},
  {'C', {0x0ffc, 0x0c0c, 0x0c0c, 0x0c0c, 0x0c0c, 0x0000, 0x0000}},
  {'D', {0x0c0c, 0x0ffc, 0x0c0c, 0x0c0c, 0x07f8, 0x0000, 0x0000}},
  {'E', {0x0ffc, 0x0ccc, 0x0ccc, 0x0c0c, 0x0c0c, 0x0000, 0x0000}},
  {'F', {0x0ffc, 0x0cc0, 0x0cc0, 0x0c00, 0x0c00, 0x0000, 0x0000}},
  {'G', {0x0ffc, 0x0c0c, 0x0c0c, 0x0ccc, 0x0cfc, 0x0000, 0x0000}},
  {'H', {0x0ffc, 0x00c0, 0x00c0, 0x00c0, 0x0ffc, 0x0000, 0x0000}},
  {'I', {0x0000, 0x0000, 0x0ffc, 0x0000, 0x0000, 0x0000, 0x0000}},
  {'J', {0x003c, 0x000c, 0x000c, 0x000c, 0x0ffc, 0x0000, 0x0000}},
  {'K', {0x0ffc, 0x00c0, 0x00e0, 0x0330, 0x0e1c, 0x0000, 0x0000}},
  {'L', {0x0ffc, 0x000c, 0x000c, 0x000c, 0x000c, 0x0000, 0x0000}},
  {'M', {0x0ffc, 0x0600, 0x0300, 0x0600, 0x0ffc, 0x0000, 0x0000}},
  {'N', {0x0ffc, 0x0700, 0x01c0, 0x0070, 0x0ffc, 0x0000, 0x0000}},
  {'O', {0x0ffc, 0x0c0c, 0x0c0c, 0x0c0c, 0x0ffc, 0x0000, 0x0000}},
  {'P', {0x0c0c, 0x0ffc, 0x0ccc, 0x0cc0, 0x0780, 0x0000, 0x0000}},
  {'Q', {0x0ffc, 0x0c0c, 0x0c3c, 0x0ffc, 0x000f, 0x0000, 0x0000}},
  {'R', {0x0ffc, 0x0cc0, 0x0cc0, 0x0cf0, 0x079c, 0x0000, 0x0000}},
  {'S', {0x078c, 0x0ccc, 0x0ccc, 0x0ccc, 0x0c78, 0x0000, 0x0000}},
  {'T', {0x0c00, 0x0c00, 0x0ffc, 0x0c00, 0x0c00, 0x0000, 0x0000}},
  {'U', {0x0ff8, 0x000c, 0x000c, 0x000c, 0x0ff8, 0x0000, 0x0000}},
  {'V', {0x0ffc, 0x0038, 0x00e0, 0x0380, 0x0e00, 0x0000, 0x0000}},
  {'W', {0x0ff8, 0x000c, 0x00f8, 0x000c, 0x0ff8, 0x0000, 0x0000}},
  {'X', {0x0e1c, 0x0330, 0x01e0, 0x0330, 0x0e1c, 0x0000, 0x0000}},
  {'Y', {0x0e00, 0x0380, 0x00fc, 0x0380, 0x0e00, 0x0000, 0x0000}},
  {'Z', {0x0c1c, 0x0c7c, 0x0ccc, 0x0f8c, 0x0e0c, 0x0000, 0x0000}},
  {'0', {0x07f8, 0x0c0c, 0x0c0c, 0x0c0c, 0x07f8, 0x0000, 0x0000}},
  {'1', {0x0300, 0x0600, 0x0ffc, 0x0000, 0x0000, 0x0000, 0x0000}},
  {'2', {0x061c, 0x0c3c, 0x0ccc, 0x078c, 0x000c, 0x0000, 0x0000}},
  {'3', {0x0006, 0x1806, 0x198c, 0x1f98, 0x00f0, 0x0000, 0x0000}},
  {'4', {0x1fe0, 0x0060, 0x0060, 0x0ffc, 0x0060, 0x0000, 0x0000}},
  {'5', {0x000c, 0x000c, 0x1f8c, 0x1998, 0x18f0, 0x0000, 0x0000}},
  {'6', {0x07fc, 0x0c66, 0x18c6, 0x00c6, 0x007c, 0x0000, 0x0000}},
  {'7', {0x181c, 0x1870, 0x19c0, 0x1f00, 0x1c00, 0x0000, 0x0000}},
  {'8', {0x0f3c, 0x19e6, 0x18c6, 0x19e6, 0x0f3c, 0x0000, 0x0000}},
  {'9', {0x0f80, 0x18c6, 0x18cc, 0x1818, 0x0ff0, 0x0000, 0x0000}},
  {'*', {0x018c, 0x0198, 0x0ff0, 0x0198, 0x018c, 0x0000, 0x0000}},
  {'.', {0x001c, 0x001c, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}},
  {'?', {0x1800, 0x1800, 0x19ce, 0x1f00, 0x0000, 0x0000, 0x0000}},
  {'!', {0x1f9c, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}},
  {'(', {0x01e0, 0x0738, 0x1c0e, 0x0000, 0x0000, 0x0000, 0x0000}},
  {')', {0x1c0e, 0x0738, 0x01e0, 0x0000, 0x0000, 0x0000, 0x0000}},
  {'#', {0x0330, 0x0ffc, 0x0330, 0x0ffc, 0x0330, 0x0000, 0x0000}},
  {'$', {0x078c, 0x0ccc, 0x1ffe, 0x0ccc, 0x0c78, 0x0000, 0x0000}},
  {'/', {0x001c, 0x0070, 0x01c0, 0x0700, 0x1c00, 0x0000, 0x0000}},
} ;

#define NGLYPHS (sizeof(glyphtab)/sizeof(glyphtab[0]))

// freq variable
uint64_t freq = BASEFREQ; // base or bottom freq of pixels

// message to send
char msg[] = "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG"; // max 16 char for LCD display, can transmit longer

// dds object
Si5351 dds;

// LCD object
LiquidCrystal_I2C lcd(LCDADDR, LCDCOLS, LCDROWS);

void setup() {
  // init I2C Wire & lcd
  lcd.begin();

  // init dds
  dds.init(SI5351_CRYSTAL_LOAD_8PF, 0,CORRB); // corr shield "B"

  // set drive output 8mA (about 4dBm into 50R)
  dds.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);

  dds.set_freq(freq, SI5351_CLK0);                   // output freq

  // disable all outputs
  dds.output_enable(SI5351_CLK0, 0);
  dds.output_enable(SI5351_CLK1, 0);
  dds.output_enable(SI5351_CLK2, 0);

  // initial display
  dispMsg(0, 0, " Hellschreiber  ");        // title & clear line

  // Set up Timer1 for interrupts at 245 Hz = 2 x 122.5 baud, as using 1/2 rows
  // 245Hz equals 4.01816ms / row
  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 << CS10) |   // Set CS10 bit; this specifies no pre-scaling
           (1 << WGM12);        //   of the system clock.
  TIMSK1 = (1 << OCIE1A);  // Enable timer compare interrupt.
  OCR1A = 0xFF1A;          // Set up interrupt trigger count;
  //   16MHz clock / 0xFF1A counts == 245.00045938Hz
  interrupts();            // Re-enable interrupts.

  delay(5000);
}

// main loop, look up index of each msg character in glyphtab, send
void loop() {
  unsigned int c;

  dispMsg(0, 0, "                 ");        // clear title
  dispMsg(0, 1, msg);                        // disp msg
  dispFreq(1, 0, freq, 1);                   // display freq
  dispMsg(11, 0, " TX  ");

  c = 0;                                     // start at character 0
  while (msg[c] != '\0') {                   // up to end of string '\0'
    sendChar(msg[c]);                   // look up index in glyphtab
    c++;                                     // next character
  }

  dispMsg(0, 1, "                ");         // clear line
  dispMsg(0, 0, " Hellschreiber  ");         // replace title

  delay(5000);
}

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

// find index of character in glyphtab then send the pixels
void sendChar(char c) {
  int ndx, x, y;

  for (ndx = 0; ndx < NGLYPHS; ndx++) {                // scan for index
    if (glyphtab[ndx].ch == c) {                       // if found, send
      for (x = 0; x < 7; x++) {                        // scan 7 cols 0 - 6
        for (y = 0; y < 14; y++) {                     // scan 14 rows 0 - 13
          if (glyphtab[ndx].col[x] & (1 << y)) {       // look for '1's
            dds.output_enable(SI5351_CLK0, 1);         // 1 = pixel on
          }
          else {
            dds.output_enable(SI5351_CLK0, 0);         // 0 = pixel off
          }
          
          // wait for timer interrupt
          while (!proceed); // timer interrupt set proceed = true
          
          // reset flag
          noInterrupts();   // Turn off interrupts; just good practice...
          proceed = false;  // reset the flag for the next character...
          interrupts();     // and re-enable interrupts.
        }
      }
    }
    
  }
}


// display freq in kHz, col c, row r, d decimal places
void dispFreq(uint8_t c, uint8_t r, uint64_t f, uint8_t d)
{
  lcd.setCursor(c, r); // clear last freq display
  lcd.print((float)f / 100000, d); // convert to float for print function
  lcd.print("kHz  "); // + trailing spaces to clear previous display
}

// display char msg at col c, row r
void dispMsg(uint8_t c, uint8_t r, char *m)
{
  lcd.setCursor(c, r);
  lcd.print(m);
}

No comments: