Monday, May 1, 2017

The Arduino based Super Simple Keyer

     I decided to build my own keyer.  I needed a new Arduino project and this seemed like the perfect one.    I had a few ideas for the design.

     I wanted a keyer that would take multiple inputs,  paddles, straight key, and computer keyboard.  The idea being you can send however you like, without needing to change around cables, just send.

     There are many arduino morse projects around already.  I suppose I could have just downloaded an existing one, dropped it in and ran with it.  But I wanted to tackle the challenge of coding it myself.



     Once I'd sorted the hardware out,  I started to work on the software.   It turned out to be a bigger challenge than I'd expected!   It's tricky, the timing of reading the paddles and keying.   I came up with an interesting solution to timing when it comes to keying the rig.

     You see, the main loop in the arduino sketch can vary in how fast it executes, based on how much work it has to do.   My solution was to use a timer in the chip and schedule an interrupt.

     All processors have timers for this.   They run at a set speed and can interrupt the processor, forcing it to execute a small routine at precise intervals.  This is useful in robotics, for example, like in a flying drone.  You would need adjustments to the motor speeds to be occurring fast and regularly.

    So, in my keyer,  there is an interrupt that is running a keying routine once every millisecond.  That's 1000 times per second,  precisely.   That allows me to control the timing of the keying exactly and produce clean, consistent Morse out to the radio.    This routing watches a buffer that I load with events using the characters period,  dash, and comma.    Period and dash are obviously the Morse elements, and the comma is a delay equivalent to a dot.

    So to send an 'A' for example,  you'd load the event buffer with  .,-,   dot comma dash comma.   The interrupt routine would immediately key the transmitter with cleanly spaced precise Morse.

    The hardware is extremely simple.  The schematic is below.   The pins chosen are arbitrary and can be changed to suit your build if needed.  They are referenced in the first few lines of the code and you change those to reflect the pins you use.   The keying is handled by a simple transistor switch.  You could use an opto-isolator if you wish,  but I've had no trouble keying modern rigs this way.





   The software is below.  Simply copy and paste it into the Arduino IDE to upload it to your board.  Upon startup it will briefly key the radio for a fraction of a second and then be ready to accept input.  I wired an additional input jack for a straight key.  That was simply parallel to the keying output and done for convenience.

    For keyboard input, the code monitors the serial input.  When the Arduino is powered by a USB connection to your computer, it will create a serial port.   Simply run a terminal program and connect it to that port to allow you to send keyboard CW.  You want to set your terminal program to 9600 baud, no parity, 1 stop bit and 8 data bits.  Type to send at the current speed.  Simple.

   The code is up to version 1.4.   Paddle routines have been heavily worked on,  2 memories have been added.    shft-alt-1 to load memory one,  alt-1 to send memory one.  Same for memory 2,  shft-alt-2 and alt-2.

Code pasted below:

/*

  Super Simple Keyer Ver 1.4  Arduino based keyer
  Written Apr. 30, 2017 by Kevin Loughin, KB9RLW

Ver 1
  Initial release, keyer working ok, not iambic

Ver 1.2
  First iambic code, dependent on timing, not perfect

Ver 1.3
  Rewrite of keyer logic, iambic and dot dash buffering working!

Ver 1.4
  Tweaked paddles a bit.  Added 2 memories.  shft-alt-1 to set mem 1, shft-alt-2 to set memory 2
  alt-1 to send memory 1, alt-2 to send memory 2.
  Tweaked character spacing for keyboard decode. Added BT using the minus sign.

*/

//  Pins we're going to use.  Change as needed.
const int wpmraw = A7;  //Pot sense for speed
const int dotin = 4;  //dot sense pin
const int dashin = 2; //dash sense pin
const int keyout = 13; //keying output

// declaring variables we'll use in program

int delayval = 150;  //delay between elements in milliseconds
String event = "";  //event contains the elements currently being sent, .=dot -=dash ,=space
String memory1 = ""; //user memory 1
String memory2 = ""; //user memory 2
String membuff; // buffer for memory sending
int dotlen = 150;  //current dot length in milliseconds
int dashlen = (dotlen * 3);  //dash length in milliseconds
int active = 0; //keying handler flag
int delaycount = 0; // delay counter in interrup handler
int dotstate = 1; // dot paddle input pins reading
int dotread = 0;
int dashstate = 1; //dash paddle input pins reading
int lastdotstate = 1;
int lastdashstate = 1;
int dashread;
int lastdashdebounce;
int lastdotdebounce;
int eventbuffer = 0;  // event buffering
int dashhalf;
int lastchar = 1;
int c;
int dotbuffer;
int dashbuffer;
int dashdelay;
int dotdelay;
int flag;

void setup() {
  // Turn off keying pin right away

  pinMode (keyout, OUTPUT);
  digitalWrite(keyout, LOW);


  // setup input pins
  pinMode (dotin, INPUT);
  pinMode (dashin, INPUT);
  digitalWrite(dotin, HIGH);
  digitalWrite(dashin, HIGH);
  Serial.begin(9600);

  //  setup the interrupt
    OCR0A = 0xAF;
  TIMSK0 |= _BV(OCIE0A);
}


//  interrupt handler
SIGNAL(TIMER0_COMPA_vect)
{
 if (  event.length() > 0  ) //do we have anything to do?
 {
   if ( !active ) //are we not currently sending?
   {
     if ( event.charAt(0) == '.')  //do we need to send a dot?
     {
      digitalWrite(keyout, HIGH); //key on
      delaycount = dotlen;
      active = 1;
      lastchar = 1;
     }
     if ( event.charAt(0) == '-' ) //do we need to send a dash?
     {
      digitalWrite(keyout, HIGH); //key on
      delaycount = dashlen;
      active = 1;
      lastchar = 2;
   
     }
     if ( event.charAt(0) == ',' )  //do we need to delay between elements?
     {
       delaycount = dotlen;
      digitalWrite(keyout, LOW); //key off
      active = 1;
      }

     }
   
    }
    if ( active ) // we're already active, count and reset when done
     {
       delaycount--;
        if ( !delaycount )  //are we done with this element?
        {
          active = 0;
          digitalWrite(keyout, LOW); //just make sure the key is off
          event.remove(0, 1);
        }
      }
  }


void loop()
{
  // read the speed and set timing

dotlen = ( 200 - (.146 * analogRead(wpmraw)) );
dashlen = ( 3 * dotlen );

//  Read paddle inputs

          // read the dash pin
  dashread = digitalRead(dashin);
   if ( dashread != lastdashstate )
      lastdashdebounce = millis();
   
    if ( (millis() - lastdashdebounce) > 40 )
    {
      if ( dashread != dashstate )
        dashstate = dashread;
    }
    lastdashstate = dashread;
 
  // read the dot pin
  dotread = digitalRead(dotin);
 if (dotread != lastdotstate)
      lastdotdebounce = millis();
   
    if ((millis() - lastdotdebounce) > 40)
    {
      if (dotread != dotstate)
        dotstate = dotread;
    }
    lastdotstate = dotread;


// Keyer logic

while (!dotstate || !dashstate )  //   if paddles pressed, do
  {
    if ( event.length() < 3 )  // don't bother unless there's no more than 1 chars in buffer
    {

      if ( !dotstate && !dashstate ) // both paddles?
      {
        if ( lastchar == 1 && ( delaycount < dashlen ))
        {
          event = String(event + "-,");
          lastchar = 2;break;
        }
        if ( lastchar == 2 && ( delaycount < dashlen ))
        {
          event = String(event + ".,");
          lastchar = 1;break;
        }
      }

      if ( !dotstate && dashstate )  // only dot paddle
      {
        if ( lastchar == 1 && ( delaycount < dashlen) && event.length() < 1)
        {
          event = String(event + ".,");break;
        }
        if ( lastchar == 2 && event.length() < 3)
        {
          event = String(event + ".,");break;
        }
      }

      if ( dotstate && !dashstate )  // only dash paddle
      {
        if ( lastchar == 1 && (delaycount < dotlen))
        {
          event = String(event + "-,");break;
        }
        if ( lastchar == 2 && (delaycount < (dotlen * 2)) & event.length() < 2)
        {
          event = String(event + "-,");break;
        }
      }
    }
break;
  }
delay(12);

//  Keyboard routines

while(Serial.available())
 {
  c = Serial.read();
  Serial.write(c);
  // check for special keys
  if ( c == 27 ) // alt key pressed
    {
      flag = 1;
      c = Serial.read();
    }
  if ( flag && c == 33 ) // shft-alt-1, memory 1 define
    {
    loadmemory(1);
    flag = 0;
    c = 0;
    }
  if ( flag && c == 64 ) // shift-alt-2, memory 2 define
    {
      loadmemory(2);
      flag = 0;
      c = 0;
    }
  if ( flag && c == 49 ) // alt 1, send memory 1
    {
      sendmemory(1);
      flag = 0;
      c = 0;
    }
  if ( flag && c == 50 ) // alt 2, send memory 2
    {
      sendmemory(2);
      flag = 0;
      c = 0;
    }
 
  decoder(c);  // call the keyboard decoder to load this event
 }
}

void loadmemory(int num) // interact with user to load a memory
{
  String inData;
  char received;
  Serial.println( "." );
  Serial.print( "Enter the contents for memory " );
  Serial.println( num );
  Serial.println();

    while ( received != 13 )
    {
      if (Serial.available())
      {
        received = Serial.read();
        Serial.write(received);
        inData += received;
      }
    }
  if ( num == 1 ) // loading memory1
    {
      memory1 = inData;
    }
  if ( num == 2 ) // loading memory2
    {
      memory2 = inData;
    }
  Serial.println();
  Serial.print( "Memory " );
  Serial.print( num );
  Serial.println( " loaded with" );
  Serial.println( inData );
  inData == "";
}

void sendmemory(int num) // send memory number num
{
  if ( num == 1 )
    membuff = memory1;
  else
    membuff = memory2;
  Serial.println( "." );
  Serial.println( membuff );
  for(int leng = membuff.length(); leng > 0; leng--)
    {
      decoder(membuff.charAt( membuff.length() - leng ));
    }
}
void decoder(int in) // function to decode char for sending and load send buffer
{
       switch(in)
  {
      case ' ' : event = String(event + ",,"); break;
      case 'a' : event = String(event + ".,-,,,"); break;
      case 'b' : event = String(event + "-,.,.,.,,,"); break;
      case 'c' : event = String(event + "-,.,-,.,,,"); break;
      case 'd' : event = String(event + "-,.,.,,,"); break;
      case 'e' : event = String(event + ".,,,"); break;
      case 'f' : event = String(event + ".,.,-,.,,,"); break;
      case 'g' : event = String(event + "-,-,.,,,"); break;
      case 'h' : event = String(event + ".,.,.,.,,,"); break;
      case 'i' : event = String(event + ".,.,,,"); break;
      case 'j' : event = String(event + ".,-,-,-,,,"); break;
      case 'k' : event = String(event + "-,.,-,,,"); break;
      case 'l' : event = String(event + ".,-,.,.,,,"); break;
      case 'm' : event = String(event + "-,-,,,"); break;
      case 'n' : event = String(event + "-,.,,,"); break;
      case 'o' : event = String(event + "-,-,-,,,"); break;
      case 'p' : event = String(event + ".,-,-,.,,,"); break;
      case 'q' : event = String(event + "-,-,.,-,,,"); break;
      case 'r' : event = String(event + ".,-,.,,,"); break;
      case 's' : event = String(event + ".,.,.,,,"); break;
      case 't' : event = String(event + "-,,,"); break;
      case 'u' : event = String(event + ".,.,-,,,"); break;
      case 'v' : event = String(event + ".,.,.,-,,,"); break;
      case 'w' : event = String(event + ".,-,-,,,"); break;
      case 'x' : event = String(event + "-,.,.,-,,,"); break;
      case 'y' : event = String(event + "-,.,-,-,,,"); break;
      case 'z' : event = String(event + "-,-,.,.,,,"); break;
      case 'A' : event = String(event + ".,-,,,"); break;
      case 'B' : event = String(event + "-,.,.,.,,,"); break;
      case 'C' : event = String(event + "-,.,-,.,,,"); break;
      case 'D' : event = String(event + "-,.,.,,,"); break;
      case 'E' : event = String(event + ".,,,"); break;
      case 'F' : event = String(event + ".,.,-,.,,,"); break;
      case 'G' : event = String(event + "-,-,.,,,"); break;
      case 'H' : event = String(event + ".,.,.,.,,,"); break;
      case 'I' : event = String(event + ".,.,,,"); break;
      case 'J' : event = String(event + ".,-,-,-,,,"); break;
      case 'K' : event = String(event + "-,.,-,,,"); break;
      case 'L' : event = String(event + ".,-,.,.,,,"); break;
      case 'M' : event = String(event + "-,-,,,"); break;
      case 'N' : event = String(event + "-,.,,,"); break;
      case 'O' : event = String(event + "-,-,-,,,"); break;
      case 'P' : event = String(event + ".,-,-,.,,,"); break;
      case 'Q' : event = String(event + "-,.,-,-,,,"); break;
      case 'R' : event = String(event + ".,-,.,,,"); break;
      case 'S' : event = String(event + ".,.,.,,,"); break;
      case 'T' : event = String(event + "-,,,"); break;
      case 'U' : event = String(event + ".,.,-,,,"); break;
      case 'V' : event = String(event + ".,.,.,-,,,"); break;
      case 'W' : event = String(event + ".,-,-,,,"); break;
      case 'X' : event = String(event + "-,.,.,-,,,"); break;
      case 'Y' : event = String(event + "-,.,-,-,,,"); break;
      case 'Z' : event = String(event + "-,-,.,.,,,"); break;
      case '0' : event = String(event + "-,-,-,-,-,,,"); break;
      case '1' : event = String(event + ".,-,-,-,-,,,"); break;
      case '2' : event = String(event + ".,.,-,-,-,,,"); break;
      case '3' : event = String(event + ".,.,.,-,-,,,"); break;
      case '4' : event = String(event + ".,.,.,.,-,,,"); break;
      case '5' : event = String(event + ".,.,.,.,.,,,"); break;
      case '6' : event = String(event + "-,.,.,.,.,,,"); break;
      case '7' : event = String(event + "-,-,.,.,.,,,"); break;
      case '8' : event = String(event + "-,-,-,.,.,,,"); break;
      case '9' : event = String(event + "-,-,-,-,.,,,"); break;
      case '.' : event = String(event + ".,-,.,-,.,-,,,"); break;
      case '-' : event = String(event + "-,.,.,.,-,,,"); break;
      case '/' : event = String(event + "-,.,.,-,.,,,"); break;
  }
}



No comments:

Post a Comment