Sunday 17 May 2015

Universal HELL sketch (Hellschrieber)

Here is the code to build a Hellschreiber transmitter. The button changes the band, the encoder tunes in 10Hz steps and a message is sent when entered on the Arduino IDE monitor. The basis of this code was found on the internet, so I am not so clever as all that!

Photo 05 17 2015 22 07 25

Photo 05 17 2015 22 08 14

Code

// Universal_VFO_HELL outputs Hellschrieber message
// keys LP PA or 403020 PA on pin D12, VFO runs continuously

// ----- SHIELD CONNECTIONS
// DDS I2C SI5351
// SCL = A5
// SDA = A4
// I2C address 0x60
// ------
// display I2C LCD 16 * 2
// o A5 SCL (y)
// o A4 SDA (or)
// o +5     (r)
// o GND    (bwn)
// I2C address 0x27
// -----
// encoder KY-040
// o D2 DT  (y)
// o D3 CLK (g)
// o +5     (r)
// o GND    (bwn)

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

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

// rotary Encoder pins 2 & 3 (DT & CLK)
#define DT 2
#define CLK 3
#define SW 4

// RX & TX enable
#define RX 13
#define TX 12

#define MSGSIZE 30

// dds object
Si5351 dds;

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

// rotary Encoder object
Rotary rot = Rotary(DT, CLK);

// message, new data in flag
char msg[MSGSIZE];
bool newMsg = false;

// band
byte band;

// freq
uint32_t freq;
uint32_t startFreq[] = {707700000, 1014400000, 1406000000};


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

const Glyph glyphtab[] PROGMEM = {
  {' ', {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', {0x0ffc, 0x0000, 0x0000, 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]))

void setup()
{
  Serial.begin(9600);
  
  // init LCD & backlight on
  lcd.init();
  lcd.backlight();
  
  pinMode(SW, INPUT_PULLUP);
  pinMode(RX, OUTPUT) ;
  pinMode(TX, OUTPUT) ;
  
  // init dds si5351 module, "0" = default 25MHz XTAL
  dds.init(SI5351_CRYSTAL_LOAD_8PF, 0);
  dds.output_enable(SI5351_CLK0, 0); // all disable
  dds.output_enable(SI5351_CLK1, 0);
  dds.output_enable(SI5351_CLK2, 0);
  
  band = 0;
  freq = startFreq[band];
  
  // CLK0 output at freq
  dds.set_freq(freq, 0ULL, SI5351_CLK0); // VFO tuned to startfreq

  dispMsg(0, 0, "HELL            ");
  dispFreq(5, 0, freq, 2);
}

void loop()
{
  tune();
  chgBand();
  getMsg();
  encodeIt();
}

void tune()
{
  unsigned char dir; // enc direction

  dir = rot.process(); // read encoder
  if (dir != DIR_NONE) // turned?
  {
    if (dir == DIR_CW) freq += 1000; // increment freq +/-10Hz
    else freq -= 1000;
    dispFreq(5, 0, freq, 2); // update freq disp
  }
}

void chgBand()
{
    if (digitalRead(SW) == LOW)
  {
    while (!digitalRead(SW)); // wait for release

    if (band == 2)
    {
      band = 0;
    }
    else
    {
      band++;
    }
    freq = startFreq[band];
    dispFreq(5, 0, freq, 2); // update freq disp
  }
}

void encodeIt()
{
  byte n;
  
  n = 0;
  if (newMsg == true)
  {

    while (msg[n] != '\0')
    {
      dispChar(n, 1, msg[n]); // display message, char by char
      encodeChar(msg[n++]);
    }
    newMsg = false;
    dispMsg(0, 0, "HELL            "); // reset display
    dispFreq(5, 0, freq, 2);
    dispMsg(0, 1, "                ");
  }
}

void encodeChar(int ch)
{
  int i, x, y, fch ;
  word fbits ;

  // search continues all the way to the end, to keep timing right
  for (i = 0; i < NGLYPHS; i++)
  {
    fch = pgm_read_byte(&glyphtab[i].ch);

    if (fch == ch)
    {
      for (x = 0; x < 7; x++)
      {
        fbits = pgm_read_word(&(glyphtab[i].col[x]));

        for (y = 0; y < 14; y++)
        {
          if (fbits & (1 << y))
            digitalWrite(TX, LOW) ; // TX = LOW
          else
            digitalWrite(TX, HIGH) ;

          delayMicroseconds(4045L) ; // adjust for bit timing
        }
      }
    }
  }
}

// get input msg
void getMsg()
{
  static byte ndx = 0; // ndx into msg[]
  char in;
  
  while (Serial.available() > 0 && newMsg == false)
  {
    in = Serial.read();

    if (in != '\n')
    {
      if (in >= 97 & in <= 122) in = in - 32; // to uc
      msg[ndx] = in;
      ndx++;
    }
    else
    {
      msg[ndx] = '\0';
      ndx = 0;
      Serial.println(msg);
      newMsg = true;
    }
  }
}

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

// display a number at col c, row r
void dispNum(uint8_t c, uint8_t r, uint16_t n)
{
  lcd.setCursor(c, r);
  lcd.print(n);
}

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

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

No comments: