Monday, September 1, 2014

Arduino Morse Code Decoder

I came across this Arduino Morse Code Decoder Sketch by Budd WB7FHC HERE and it look interesting and very well documented so I thought I would try it out and run some tests on it because I may have an application for such a decoder.

Using a straight key with it was a little tough but I was able to get it to copy me fine as long as I was sending perfectly at about 10 WPM. I tried increasing the speed but it seems to get easily confused requiring a reset.
I then decided to connect a keyer to it so it was receiving perfect machine generated Morse. I used my WINKEYER USB. I started at 10 WPM and was able to increase the speed in 2-3 WPM increments and it was able to copy 100% all the way to 99 WPM!

I still need to do more testing but the way the code is built, it seems to needs to start at a slow speed before handling higher speed code. I did find that from a reset it will copy perfectly at 10 WPM every time with virtually no training. It seems the training part of the code is more useful when dealing with a straight key and the variation that a human may cause.

I came across some minor issues in the code while I was running it regarding decoding punctuation. The fix required a change to the "mySet" array and also the punctuation code. If you look at the printPunctuation code on Budds original sketch and compare to my code below I just did a direct print instead of assigning it to "pMark". I also made it so when it decodes a "@" sign it sends a newline to the terminal.

There are some other changes I would like to make that would enable this code to work in an application I am pondering... a CW transponder. The idea would be to send it my call-sign and a command, it would decode it and if correct would respond with an appropriate CW response back (transpond). For example, if I send it... WA6PZB WA6PZB CMD WX CMD WX it would respond back with WA6PZB WA6PZB RESP 72F RESP 72F. Here I am requesting the temperature or weather (WX) and it is responding with 72 degrees F. I am expecting to send everything twice in and attempt to get the signal through under varying conditions since Morse has no error correction.


 /* Barnacle Budd's Morse Code Decoder v. 0.1  
   (c) 2011, Budd Churchward - WB7FHC  
   Minor modification by WA6PZB 8/30/2014  
   Hook a button or telegraph key up to your Arduino  
   and this program will copy your Morse Code and display  
   the characters in your Monitor window.  
   The program will automatically adjust to the speed of code that  
   you are sending. The first few characters may come out wrong.  
   The software tracks the speed of the sender's dahs to make  
   its adjustments. The more dahs you send at the beginning  
   the sooner it locks into solid copy.  
   After a reset, the following text is very difficult to lock in on:  
   'SHE IS HIS SISTER' because there are only two dahs in the whole  
   phrase and they come near the end. However, if you reset and then  
   send 'CALL ME WOODY' it will match your speed quite quickly.  
 */  
 int myKey=14;  // We are borrowing Analog Pin 0 and using it as digital  
 int speaker=11; // Speaker will be hooked between pin 11 and ground  
 int val=0;          // A value for key up and down  
 int myTone=640;       // Frequency of our tone  
 boolean ditOrDah=true;    // We have a full dit or a full dah  
 int dit=100;         // If we loop less than this with keydown it's a dit else a dah  
 int averageDah=150;     // Start with this value we will adjusted it each time he sends a dah  
 boolean characterDone=true; // A full character has been sent  
 int myBounce=2;       // Handles normal keybounce but we needed to do more later  
 int downTime=0;       // We are going to count the cycles we loop while key is down  
 long FullWait=10000;   // This value will be set by the sender's speed - the gap between letters  
 long WaitWait=FullWait; // WaitWait is for the gap between dits and dahs  
 long newWord=0;     // For the gap between words  
 int nearLineEnd=40;   // How far do you want to type across your monitor window?  
 int letterCount=0;    // To keep track of how many characters have been printed on the line  
 int myNum=0; // We will turn the dits and dahs into a data stream and parse  
        // a value that we will store here  
 // The place a letter appears here matches the value we parse out of the code  
 char mySet[] ="##TEMNAIOGKDWRUS##QZYCXBJP#L#FVH09#8###7#:###/#61#######2###3#45";  
 void setup() {  
  pinMode(myKey, INPUT);  
  pinMode(speaker,OUTPUT);  
  // initialize the serial communication:  
  Serial.begin(9600);  
 }  
  void loop() {  
   val=digitalRead(myKey); // Is it up or is it down?  
   if (val) keyIsDown();  // Any value here means it is down.  
   if (!val) keyIsUp();   // Should be 0 when it is up.  
  }  
  void keyIsDown() {  
   tone(speaker,myTone); // Turn on the sound  
   WaitWait=FullWait;   // Reset our Key Up countdown  
   downTime++;  //Count how long the key is down  
  if (myNum==0) {    // myNum will equal zero at the beginning of a character  
    myNum=1;     // This is our start bit - it only does this once per letter  
   }  
  characterDone=false; // we aren't finished with the character yet, there could be more  
  ditOrDah=false;    // we don't know what it is yet - key is still down  
  delay(myBounce);   // short delay to keep the real world in synch with Arduino  
  }  
  void keyIsUp() {  
   noTone(speaker);   // Turn off the sound  
  if (newWord>0) newWord--;   // Counting down to spot gap between words  
  if (newWord==1) printSpace(); // Found the gap, print a space but only once next time it will be 0  
  if (!ditOrDah) {       // We don't know if it was a dit or a dah yet, so ...  
    shiftBits();       // let's go find out! And do our Magic with the bits  
   }  
  if (!characterDone) {  
    WaitWait--;        // We are counting down   
    if (WaitWait==0) {    // Bingo, keyUp just timed out! A full letter has been sent  
     WaitWait=FullWait;   // Reset our keyUp counter  
     printCharacter();    // Go figure out what character it was and print it  
     characterDone=true;   // We got him, we're done here  
     myNum=0;        // This sets us up for getting the next start bit  
    }  
    downTime=0;        // Reset our keyDown counter  
   }  
 }  
 void printSpace() {  
  letterCount++;         // we're counting the number of characters on the line   
  if (letterCount>nearLineEnd) { // when we get past our threshold we do this:  
   Serial.println();       // jump down to a new line  
   letterCount=0;        // reset our character counter  
   return;            // Go back to loop(), we're done here.  
  }   
  Serial.print(' ');       // print a space on the monitor window  
 }  
 void printCharacter() {        
  FullWait=averageDah*100;    // the keyUp counter gets reset based on sender's speed  
  newWord=FullWait*5;       // word gap counter is also adjusted by sender's speed  
  letterCount++;         // we're counting the number of characters on the line  
  if (myNum>63) {   
   printPunctuation();      // The value we parsed is bigger than our character array  
                  // It is probably a punctuation mark so go figure it out.  
   return;            // Go back to the main loop(), we're done here.  
  }  
  Serial.print(mySet[myNum]);   // Print the letter that is in this spot in our character set  
 }  
 void printPunctuation() {  
  byte pMark='#'; // Just in case nothing matches  
  if (myNum==71) Serial.print(":");  
  if (myNum==76) Serial.print(",");  
  if (myNum==84) Serial.print("!");  
  if (myNum==94) Serial.print("-");  
  if (myNum==101) Serial.println(); // the @ sign used for newline  
  if (myNum==106) Serial.print(".");  
  if (myNum==115) Serial.print("?");  
 }  
 void shiftBits() {  
  ditOrDah=true;    // we will know which one in two lines  
  if (downTime<dit/3) return; // ignore my keybounce  
  if (downTime<dit) {  
   // We got a dit  
   myNum = myNum << 1; // shift bits left  
   myNum++;       // add one because it is a dit  
  }  
  else {  
   // We got a dah  
   myNum = myNum << 1; // shift bits left  
   // The next three lines handle the automatic speed adjustment:  
   averageDah=(downTime+averageDah)/2; // running average of dahs  
   dit=averageDah/3;          // normal dit would be this  
   dit=dit*2;              // double it to get the threshold between dits and dahs  
  }  
 }  

1 comment: