1. Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.
    Dismiss Notice

Morse encoder/decoder - my first project

Discussion in 'Arduino' started by flat5, Mar 23, 2015.

  1. flat5

    flat5 Member

    Joined:
    Oct 26, 2008
    Messages:
    866
    Likes:
    8
    Location:
    Amsterdam
    Found a sketch and adjusted it a tiny bit. Changed the mic pre amp and audio output.
    Want to add a rotary encoder to set the speed (wpm). Perhaps someone will help me with that. (hint hint)
    Also plan to add a backpack LCD 16x2 display. Is that a bad idea for this project?

    morse_1_analog.ino
    note: This is a plain textfile. It is safe to read.

    IO board & rotary encoder.jpg
     

    Attached Files:

    Last edited: Apr 2, 2015
  2. misterT

    misterT Well-Known Member Most Helpful Member

    Joined:
    Apr 19, 2010
    Messages:
    2,697
    Likes:
    368
    Location:
    Finland
    What kind of signal you have as an input to the Arduino (pin A0)?
     
  3. flat5

    flat5 Member

    Joined:
    Oct 26, 2008
    Messages:
    866
    Likes:
    8
    Location:
    Amsterdam
    In the .ino file there are comments at the top that give some useful info.
    More info:

    http://raronoff.wordpress.com/2010/12/16/morse-endecoder/



    I have not put a scope on the pre amp output but I hope it's a clean audio signal.

    As I understand A0 is a kind of comparator input. It has an input threshold controlled by the software.

    This is a 1 minute video of my decoder. My first UTube :)

    I added punctuation characters and a sounder later.
     
    Last edited: Mar 24, 2015
  4. dave

    Dave New Member

    Joined:
    Jan 12, 1997
    Messages:
    -
    Likes:
    0


     
  5. misterT

    misterT Well-Known Member Most Helpful Member

    Joined:
    Apr 19, 2010
    Messages:
    2,697
    Likes:
    368
    Location:
    Finland

    Ok, so you have the system working and need help adding the "speed adjustment"?

    How do you detect the short and long pulses now?

    Morse code is so simple that it could be very easy to implement "auto detect" for the speed. Anyway, you need to post code.

    EDIT: Ok, of course you need a switch to set the transmit speed..
     
  6. flat5

    flat5 Member

    Joined:
    Oct 26, 2008
    Messages:
    866
    Likes:
    8
    Location:
    Amsterdam
    I don't understand why you do not download the morse_1_analog.ino file.
    It is a text file. It answers your questions.
    Automatic speed sensing would be useful and I have a rough idea how to do it.
    But not the coding details. Encoders often have a push switch on them.
     
  7. misterT

    misterT Well-Known Member Most Helpful Member

    Joined:
    Apr 19, 2010
    Messages:
    2,697
    Likes:
    368
    Location:
    Finland
    Ok.. I download the file. the .ino file extension was suspicious to me :)
     
  8. be80be

    be80be Well-Known Member

    Joined:
    Aug 23, 2008
    Messages:
    4,792
    Likes:
    134
    Location:
    morristown,tn
    Here I don't no why people don't post there code like they did before
    Code (text):

    /*
                  MORSE EN-DE-CODER 1.06

    - A Morse encoder / decoder for the Arduino.


    3 input methods for decoding and encoding:
    -  Audio Morse signals on an analog input (for decoding).
    -  Morse keying on digital input (for decoding).
    -  ASCII text via serial communication (for encoding. Also for adjusting settings).

    2 outputs for encoded and decoded Morse:
    -  Morse-code toggled output pin (for encoded Morse).
    -  ASCII text via serial communication (for decoded Morse) .

    It will enter audio listen mode if nothing is keyed in within a second.

    Serial input:
    Only International Morse Code (letters A-Z, numbers), and space, are
    encoded and sent as Morse code automatically unless whatever the "command"
    character is (default '<') is received.
    No punctuation supported (but the morse table can be extended easily for that)

    Morse en-/de-coder < commands via serial:
    The ASCII "less than" character '<' denotes a command instead of encoding and
    sending Morse. The format is:

       <Lxxx

    where L is a letter and xxx is a three-digit decimal number 000-999.
    One exception is the <i command for info (displays current settings).
    The letter can be upper or lower case W, A, D, E or I. Examples:

       <w008 - sets speed to 8 wpm
       <a900 - sets audio threshold to be 900
       <D020 - sets debounce delay to be 20 milliseconds.
       <e000 - turn off Morse echo back to serial as ASCII and output pin as Morse
       <e001 - turn on Morse echo (any value above 0 will do)
       <i    - info - displays some settings

    Full duplex:
    It can both transmit and receive simultaneously, but serial output will
    be a mixed mess unless echo is turned off.


    NOTE: Values for tolerance (like dot time or dash time divided by 2 etc) are
         more or less arbitrarily/experimentally chosen.

         Sorry the source code is a big mess for now...

    Based on Debounce example from the Arduino site for the morse keyer button
    (http://www.arduino.cc/en/Tutorial/Debounce).
    Loosely based on Morse Decoder 3.5 from 1992 for the Amiga by the same guy.
    Contact: raronzen@gmail.com  (not checked too often..)


    Copyright (C) 2010 raron


       This program is free software: you can redistribute it and/or modify
       it under the terms of the GNU General Public License as published by
       the Free Software Foundation, either version 3 of the License, or
       (at your option) any later version.

       This program is distributed in the hope that it will be useful,
       but WITHOUT ANY WARRANTY; without even the implied warranty of
       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       GNU General Public License for more details.

       You should have received a copy of the GNU General Public License
       along with this program.  If not, see <http://www.gnu.org/licenses/>.


    History:
    1992.01.06 - Morse decoder 3.5 - 68000 Assembler version for the Amiga 600
                   using a binary tree (dichotomic table) for Morse decoding
    2010.11.11 - Rewrite for the Arduino
    2010.11.27 - Added Morse encoding via reverse-dichotomic path tracing.
                   Thus using the same Morse table / binary tree for encoding and decoding.
    2010.11.28 - Added a simple audio Morse signal filter. Added a simple command parser
    2010.11.29 - Added echo on/off command

    2015.3 - some modification by Barry Block to use the 'tone' ability.
             input & output circuits modified
             plan to add a rotary encoder to adjust speed.

    TODO: Make the timings signed long again, to avoid rollover issue
         Avoid calling millis() all the time?
         Make functions or classes out of it sometime...
         Generally tidy it up
    */


    // Simple analog input signal threshold (512 = middle-ish)
    // Set high enough to avoid noise, low enough to get signal
    // ( 512 + noise < AudioThreshold < 512 + signal )
    int AudioThreshold = 400; // works for me using my preamp

    // Word-per-minute speed
    int wpm = 030;

    // Used as a command character to adjust some settings via serial.
    const char MorseCommand = '<';

    // the debounce time. Keep well below dotTime!!
    unsigned long debounceDelay = 20; //this is ok to at least 120wpm

    // Other Morse variables
    unsigned long dotTime = 1200 / wpm;   // morse dot time length in ms
    unsigned long dashTime = 3 * 1200 / wpm;
    unsigned long wordSpace = 7 * 1200 / wpm;


    const int analogPin = 0;       // Analog input pin for audio morse code
    const int morseInPin = 7;      // The Morse keyer button
    const int morseOutPin = 13;   // For Morse code output
    unsigned long markTime = 0;    // timers for mark and space in morse signal
    unsigned long spaceTime = 0;   // E=MC^2 ;p
    boolean morseSpace = false;    // Flag to prevent multiple received spaces
    boolean gotLastSig = true;     // Flag that the last received morse signal is decoded as dot or dash

    // const int morseTreetop = 31;   // character position of the binary morse tree top.
    const int morseTreetop = 63;   // This is for ITU with puncutation, but without non-english extensions

    const int morseTableLength = (morseTreetop * 2) + 1;
    const int morseTreeLevels = log(morseTreetop + 1) / log(2);
    int morseTableJumper = (morseTreetop + 1) / 2;
    int morseTablePointer = morseTreetop;

    // Morse code binary tree table (or, dichotomic search table)
    // char morseTable[] = "5H4S?V3I?F?U??2E?L?R???A?P?W?J1 6B?D?X?N?C?K?Y?T7Z?G?Q?M8??O9?0";

    // This is the table for ITU with punctuation (but without non-english characters - for now)
    char morseTable[] = "*5*H*4*S***V*3*I***F***U?!_**2*E***L\"**R*+.****A***P@**W***J'1* *6-B*=*D*/"
                        "*X***N***C;*!K*()Y***T*7*Z**,G***Q***M:8*****O*9***0*";

    int morseSignals;              // nr of morse signals to send in one morse character
    char morseSignal[] = "......"; // temporary string to hold one morse character's signals to send
    int morseSignalPos = 0;
    int sendingMorseSignalNr = 0;
    unsigned long sendMorseTimer = 0;

    boolean morseEcho = false; // Echoes character to encode back to serial and Morse signal input to output pin
    boolean listeningAudio = false;
    boolean sendingMorse = false;
    boolean morseSignalState = false;
    boolean lastKeyerState = false;

    //unsigned long lastAudioSignalTime = 0;
    unsigned long lastDebounceTime = 0;  // the last time the input pin was toggled



    void setup()
    {
      pinMode(morseInPin, INPUT);
      digitalWrite(morseInPin, HIGH); // internal pullup resistor on
      pinMode(morseOutPin, OUTPUT);
      Serial.begin(9600);
      Serial.println("Morse en-/de-coder by raron");

      // BB added
      Serial.print("Morse speed: ");
      Serial.print (wpm, DEC);
      Serial.print(" wpm (dot=");
      Serial.print (dotTime, DEC);
      Serial.print("ms, dash=");
      Serial.print (dashTime, DEC);
      Serial.print("ms) Debounce: ");
      Serial.print (debounceDelay, DEC);
      Serial.print(" ms. Audio threshold: ");
      Serial.print (AudioThreshold, DEC);
      Serial.println(" <");

      tone(8, 550); // BB duration is always on (port,freq,millseconds)

      markTime = millis();
      spaceTime = millis();
    }



    void loop()
    {
      boolean morseKeyer = !digitalRead(morseInPin); // inverted for active-low input



      // If the switch changed, due to noise or pressing:
      if (morseKeyer != lastKeyerState)
      {
        lastDebounceTime = millis(); // reset timer
        listeningAudio = false;      // disable listen to audio-mode
      }



      // debounce the morse keyer, unless listening to audio morse signal
      if (!listeningAudio && (millis() - lastDebounceTime) > debounceDelay)
      {
        // whatever the reading is at, it's been there for longer
        // than the debounce delay, so take it as the actual current state:
        morseSignalState = morseKeyer;
        // differentiante mark and space times
        if (morseSignalState) markTime = lastDebounceTime; else spaceTime = lastDebounceTime;
      }



      // If no manual morse keying the last second, enter audio listen mode
      if (!morseKeyer && millis() - lastDebounceTime > 1000)  listeningAudio = true;



      // Filter audio morse signal
      if (listeningAudio)
      {
        int audioMorse = analogRead(analogPin);
        unsigned long currentTime = millis();

        // Check for an audio signal...
        if (audioMorse > AudioThreshold)
        {
          // If this is a new morse signal, reset morse signal timer
          if (currentTime - lastDebounceTime > dotTime / 2)
          {
            markTime = currentTime;
            morseSignalState = true; // there is currently a signal
          }
          lastDebounceTime = currentTime;
        } else {
          // if this is a new pause, reset space time
          if (currentTime - lastDebounceTime > dotTime / 2 && morseSignalState == true)
          {
            spaceTime = lastDebounceTime; // not too far off from last received audio
            morseSignalState = false;        // No more signal
          }
        }
      }



      // Morse output, or a feedback when keying.
      if  (!sendingMorse && morseEcho) digitalWrite(morseOutPin, morseSignalState);

      // Encode Morse code or execute commands
      if (Serial.available() > 0 && !sendingMorse)
      {
        char encodeMorseChar = Serial.read();

        // if a command instead, adjust some settings
        if (encodeMorseChar == MorseCommand)
        {
          // An extremely crude and simple command parser
          // Expects (and wait for) 2 or 4 characters (>i or <Cxxx)
          int digits;
          int value = 0;
          do
          {
            digits = Serial.available();
          } while (digits < 1);
          // Read what setting
          char morseSet = Serial.read();
          // Read 3 digits unless setting is i for info
          if (morseSet != 'i' && morseSet != 'I')
          {
            do
            {
              digits = Serial.available();
            } while (digits < 3);
            // convert value from ASCII
            for (int i = 0; i < digits; i++)
            {
              encodeMorseChar = Serial.read();
              value *= 10;
              value += (encodeMorseChar - '0');
            }
          }
          Serial.flush(); // just in case

          // Adjust and print the new setting
          Serial.println();
          switch (morseSet)
          {
            case 'a': // Audio input threshold value
            case 'A':
              AudioThreshold = value;
              if (AudioThreshold < 0) AudioThreshold = 0; // not recommended
              Serial.print(" > Audio threshold:");
              Serial.print (value, DEC);
              Serial.println(" <");
              break;
            case 'd': // Debounce value
            case 'D':
              debounceDelay = (unsigned long) value;
              if (debounceDelay < 0) debounceDelay = 0;
              Serial.print(" > Debounce (ms):");
              Serial.print (value, DEC);
              Serial.println(" <");
              break;
            case 'e': // Turn on / off Morse echo back to serial and Morse output pin
            case 'E':
              if (value > 0)
              {
                morseEcho = true;
                Serial.println(" > Echo: on <");
              } else {
                morseEcho = false;
                Serial.println(" > Echo: off <");
              }
              break;
            case 'w': // Morse speed setting in wpm
            case 'W':
              wpm = value;
              if (wpm <= 0) wpm = 1;
              dotTime = 1200 / wpm;
              dashTime = 3 * 1200 / wpm;
              wordSpace = 7 * 1200 / wpm;
              Serial.print(" > Morse speed (wpm):");
              Serial.print (value, DEC);
              Serial.println(" <");
              break;
            case 'i': // Display info (current settings).
            case 'I':
              Serial.print(" > Morse speed: ");
              Serial.print (wpm, DEC);
              Serial.print(" wpm (dot=");
              Serial.print (dotTime, DEC);
              Serial.print("ms, dash=");
              Serial.print (dashTime, DEC);
              Serial.print("ms) Debounce: ");
              Serial.print (debounceDelay, DEC);
              Serial.print(" ms. Audio threshold: ");
              Serial.print (AudioThreshold, DEC);
              Serial.println(" <");
              break;
            default:
              Serial.print(" > Unrecognized command <");
          }
          // Mark that we have executed a command (dont send morse)
          encodeMorseChar = MorseCommand;
        }

        if (encodeMorseChar != MorseCommand)
        {
          // change to capital letter if not
          if (encodeMorseChar > 'Z') encodeMorseChar -= 'z' - 'Z';

          // Scan for the character to send in the Morse table
          int i;
          for (i = 0; i < morseTableLength; i++) if (morseTable[i] == encodeMorseChar) break;
          int morseTablePos = i + 1; // 1-based position

          // Reverse dichotomic / binary tree path tracing

          // Find out what level in the binary tree the character is
          int test;
          for (i = 0; i < morseTreeLevels; i++)
          {
            test = (morseTablePos + (0x0001 << i)) % (0x0002 << i);
            if (test == 0) break;
          }
          int startLevel = i;
          morseSignals = morseTreeLevels - i; // = the number of dots and/or dashes
          morseSignalPos = 0;

          // Travel the reverse path to the top of the morse table
          if (morseSignals > 0)
          {
            // build the morse signal (backwards from last signal to first)
            for (i = startLevel; i < morseTreeLevels; i++)
            {
              int add = (0x0001 << i);
              test = (morseTablePos + add) / (0x0002 << i);
              if (test & 0x0001 == 1)
              {
                morseTablePos += add;
                // Add a dot to the temporary morse signal string
                morseSignal[morseSignals - 1 - morseSignalPos++] = '.';
              } else {
                morseTablePos -= add;
                // Add a dash to the temporary morse signal string
                morseSignal[morseSignals - 1 - morseSignalPos++] = '-';
              }
            }
          } else {  // unless it was on the top to begin with (A space character)
            morseSignal[0] = ' ';
            morseSignalPos = 1;
            morseSignals = 1; // cheating a little; a wordspace for a "morse signal"
          }
          morseSignal[morseSignalPos] = '\0';

          // Echo back the letter with morse signal as ASCII to serial output
          if (morseEcho)
          {
            Serial.print(encodeMorseChar);
            Serial.print(morseSignal);
            Serial.print(" ");
          }
          if (morseTablePos - 1 != morseTreetop)
          {
            Serial.println();
            Serial.print("..Hm..error? MorseTablePos = ");
            Serial.println(morseTablePos);
          }
          // start sending the character
          sendingMorse = true;
          sendingMorseSignalNr = 0;
          sendMorseTimer = millis();
          if (morseSignal[0] != ' ') digitalWrite(morseOutPin, HIGH);
        }
      }



      // Send Morse signals to output
      if (sendingMorse)
      {
        switch (morseSignal[sendingMorseSignalNr])
        {
          case '.': // Send a dot (actually, stop sending a signal after a "dot time")
            if (millis() - sendMorseTimer >= dotTime)
            {
              digitalWrite(morseOutPin, LOW);
              sendMorseTimer = millis();
              morseSignal[sendingMorseSignalNr] = 'x'; // Mark the signal as sent
            }
            break;
          case '-': // Send a dash (same here, stop sending after a dash worth of time)
            if (millis() - sendMorseTimer >= dashTime)
            {
              digitalWrite(morseOutPin, LOW);
              sendMorseTimer = millis();
              morseSignal[sendingMorseSignalNr] = 'x'; // Mark the signal as sent
            }
            break;
          case 'x': // To make sure there is a pause between signals and letters
            if (sendingMorseSignalNr < morseSignals - 1)
            {
              // Pause between signals in the same letter
              if (millis() - sendMorseTimer >= dotTime)
              {
                sendingMorseSignalNr++;
                digitalWrite(morseOutPin, HIGH); // Start sending the next signal
                sendMorseTimer = millis();       // reset the timer
              }
            } else {
              // Pause between letters
              if (millis() - sendMorseTimer >= dashTime)
              {
                sendingMorseSignalNr++;
                sendMorseTimer = millis();       // reset the timer
              }
            }
            break;
          case ' ': // Pause between words (minus pause between letters - already sent)
          default:  // Just in case its something else
            if (millis() - sendMorseTimer > wordSpace - dashTime) sendingMorse = false;
        }
        if (sendingMorseSignalNr >= morseSignals) sendingMorse = false; // Ready to encode more letters
      }



      // Decode morse code
      if (!morseSignalState)
      {
        if (!gotLastSig)
        {
          if (morseTableJumper > 0)
          {
            // if pause for more than half a dot, get what kind of signal pulse (dot/dash) received last
            if (millis() - spaceTime > dotTime / 2)
            {
              // if signal for more than 1/4 dotTime, take it as a valid morse pulse
              if (spaceTime - markTime > dotTime / 4)
              {
                // if signal for less than half a dash, take it as a dot, else if not, take it as a dash
                // (dashes can be really really long...)
                if (spaceTime - markTime < dashTime / 2) morseTablePointer -= morseTableJumper;
                else morseTablePointer += morseTableJumper;
                morseTableJumper /= 2;
                gotLastSig = true;
              }
            }
          } else { // error if too many pulses in one morse character
            Serial.println("<ERROR: unrecognized signal!>");
            gotLastSig = true;
            morseTableJumper = (morseTreetop + 1) / 2;
            morseTablePointer = morseTreetop;
          }
        }
        // Write out the character if pause is longer than 2/3 dash time (2 dots) and a character received
        // if ((millis() - spaceTime >= (dotTime * 2)) && (morseTableJumper < 16))
        if ((millis() - spaceTime >= (dotTime * 2)) && (morseTableJumper < ((morseTreetop + 1) / 2)))
        {
          char morseChar = morseTable[morseTablePointer];
          Serial.print(morseChar);
          morseTableJumper = (morseTreetop + 1) / 2;
          morseTablePointer = morseTreetop;
        }
        // Write a space if pause is longer than 2/3rd wordspace
        if (millis() - spaceTime > (wordSpace * 2 / 3) && morseSpace == false)
        {
          Serial.print(" ");
          morseSpace = true ; // space written-flag
        }

      } else {
        // while there is a signal, reset some flags
        gotLastSig = false;
        morseSpace = false;
      }



      // save last state of the morse signal for debouncing
      lastKeyerState = morseKeyer;
    }
     
     
  9. flat5

    flat5 Member

    Joined:
    Oct 26, 2008
    Messages:
    866
    Likes:
    8
    Location:
    Amsterdam
    I'm new to this forum and did not see the point of making a long post that might have html errors or something that could cause it to not compile. I'll post the code in the future if that is what is preferred.
     
  10. be80be

    be80be Well-Known Member

    Joined:
    Aug 23, 2008
    Messages:
    4,792
    Likes:
    134
    Location:
    morristown,tn
    That's even more reason to use code tags It keeps your code just like you posted it. Not to many want to download a file wonder why, oh i remember did that a time or two and spent the rest of the day cleaning my hard drive.
     
  11. b.james

    b.james Member

    Joined:
    Feb 4, 2014
    Messages:
    167
    Likes:
    9
    Go to a Linux ststem man. Much less of it going on and the browser mail and file programs are all almost identical. Try a Puppy Linux iso on a CD and boot from CD . Don't like it ? bin the CD
     
  12. b.james

    b.james Member

    Joined:
    Feb 4, 2014
    Messages:
    167
    Likes:
    9
    Had a quick look at code
    Your speed is set by the value of an integer it seems

    int wpm = 030;

    so if you read a pot or whatever variable and set wpm to a suitable value between say 5 and 30 that should slow it no?
    I've got another program with an innovative approach to morse which created an array containing the letters and numbers in a matrix also I will try to find and post it here. My first look failed to find that but I will post it here if I find it

    The trouble with fools is that they do not realise their foolishness.
    These forum sitters you have here are a pain . They don't try to help really . What they do is jump in and confuse the thread.
    You don't know anything about a situation unless you have experienced it specifically yourself ! Period
    I suggest you stop wasting peoples time who come here for an answer.
    Leave the answer to someone who does know.

    In my case the answer was simple but I found it on a more sensible forum -not this circus
     
    Last edited: May 14, 2015
  13. flat5

    flat5 Member

    Joined:
    Oct 26, 2008
    Messages:
    866
    Likes:
    8
    Location:
    Amsterdam
    Thanks, B. I've made some progress.
    After a little experience with the device the code I need does seem to be trivial.
    I've de-bounced it with 2 0.1 caps and 2 4.7k resistors. Put the 5 components on a little board.
    Edit: It seems .1uf is too high. Perhaps .01uf is better.
    I say this because there was a double speak every time I moved the control. Like:
    49
    48

    I solved the problem in software by the line

    delay(1);

    I now increment and decrement by 5. The decoder does not need a precise value.
    Yea for that :)

    This has been most helpful.
    WB7FHC's Morse Code Decoder v. 1.1
     
    Last edited: Mar 25, 2015
  14. flat5

    flat5 Member

    Joined:
    Oct 26, 2008
    Messages:
    866
    Likes:
    8
    Location:
    Amsterdam
    B, if you don't know what speed you are receiving you keep typing a line to the terminal over and over until you are close. I really don't enjoy doing that. This is why I want to use the encoder. An other reason is I don't yet know how to find the speed in software. I know it is not that hard to do. If the dot time is defined everything else follows.
     
  15. BobW

    BobW Active Member

    Joined:
    Apr 28, 2010
    Messages:
    501
    Likes:
    52
    Location:
    Canada
    Given that dots and dashes occur with equal frequency, and that a dash is 3 times longer than a dot, it should be easy enough to automatically adjust for speed by taking a combined average of dots and dashes. Then anything that has a longer duration than the average value is a dash and anything shorter than the average is a dot.
    For example, let's assume that at some particular speed, a dot is 100 ms and therefore a dash will be 3 times that, or 300 ms. If you calculated the combined average of all incoming dots and dashes you would get 200 ms. Hence anything shorter than 200 ms is a dot and anything longer than 200 ms is a dash. That's a pretty good margin of error. The only remaining problem is to buffer all of the incoming data until you get a sufficiently good average value, so that you can go back and decode the very beginning of the message.
     
  16. flat5

    flat5 Member

    Joined:
    Oct 26, 2008
    Messages:
    866
    Likes:
    8
    Location:
    Amsterdam
    As I said, everything follows the dot length.
    The problem for me is not so much the logic, it's the programing details and the syntax.
    It seems everytime I try to program I have to start at the beginning.
    Learn everything again.

    Automatic comes later :)
     
  17. flat5

    flat5 Member

    Joined:
    Oct 26, 2008
    Messages:
    866
    Likes:
    8
    Location:
    Amsterdam
    "Given that dots and dashes occur with equal frequency, ..."

    That is quite clever. I believe I read that before in an article about a Morse decoder using a PIC.
    I don't think he posted his code though. I may be wrong.
     
    Last edited: Apr 4, 2015
  18. flat5

    flat5 Member

    Joined:
    Oct 26, 2008
    Messages:
    866
    Likes:
    8
    Location:
    Amsterdam
    MikeML posted in another thread perhaps what I need to time an event.

     
  19. flat5

    flat5 Member

    Joined:
    Oct 26, 2008
    Messages:
    866
    Likes:
    8
    Location:
    Amsterdam
    [deleted due to lack of interest]
     
    Last edited: Apr 2, 2015
  20. flat5

    flat5 Member

    Joined:
    Oct 26, 2008
    Messages:
    866
    Likes:
    8
    Location:
    Amsterdam
    Have added a rotary encoder to adjust wpm.
    Changed the parser.
    Added a few features.
    If anyone wants to see the revised code post here.

    Have ordered ne567 tone decoder. This is needed to receive code 'off the air'.
    It will follow the mic pre amp.

    I would really like to add the ability to read a file or have one come in from the terminal and produce code to record. My attempt to copy & paste text to the terminal or print to the terminal or emulate typing have failed. I'm sure it's because I'm a rank amateur at programming.

    I tried to add an LCD backpack but it used too much memory variable space.
    If I can get a display to work a computer would not be necessary.

    Hoping someone with experience will help me improve this project.
     
  21. flat5

    flat5 Member

    Joined:
    Oct 26, 2008
    Messages:
    866
    Likes:
    8
    Location:
    Amsterdam
    Updated version of the project. Next will be an NE567 tone decoder frontend so 'off the air' decoding should work.
    As a sideline, am trying to find an automatic code speed function. Have the software find the incoming code speed.
    Might have to change analogPin 0 to pulseIn pin7. Have not yet checked to see how much of a re-write that would be.
    Code (text):

    /*
      MORSE EN-DE-CODER 1.06
      A Morse encoder / decoder for the Arduino.
      April, 26, 2015
    */

    // BB variables
    unsigned int pitch = 800;
    boolean refreshScreen = false;
    const byte pinA = 2;      // encoder pin A to Arduino pin 2 which is also interrupt pin 0 which we will use
    const byte pinB = 3;      // encoder pin B to Arduino pin 3 which is also interrupt pin 1 but we won't use it
    byte state = 0;           // will store two bits for pins A & B on the encoder which we will get from the pins above
    int bump[] = {0, 0, -1, 1};
    int level = 0;              // rotary encoder
    boolean switchState = 1;    // encoder pushswitch
    // switch stuff unused!
    int encoderSwitchPin = 4;   //push button switch
    //boolean switchState = 1;    // encoder pushswitch
    int increment = 4;          // rotary encoder increment - 1
    // BB -----------------------------------

    // Simple analog input signal threshold (512 = middle-ish)
    // Set high enough to avoid noise, low enough to get signal
    // ( 512 + noise < AudioThreshold < 512 + signal )
    int AudioThreshold = 400; // (BB) works for me using my preamp
    unsigned int wpm = 20; // Word-per-minute speed
    const char MorseCommand = '<'; // Used as a command character to adjust some settings via serial.
    unsigned long debounceDelay = 20; // the debounce time. Keep well below dotTime!! 20 this is ok to at least 200wpm
    // Other Morse variables
    unsigned long dotTime = 1200 / wpm;   // morse dot time length in ms
    unsigned long dashTime = 3 * 1200 / wpm;
    unsigned long wordSpace = 7 * 1200 / wpm;
    const int analogPin = 0;       // Analog input pin for audio morse code
    const int morseInPin = 7;      // The Morse keyer button
    const int morseOutPin = 13;    // For Morse code output
    unsigned long markTime = 0;    // timers for mark and space in morse signal
    unsigned long spaceTime = 0;   // E=MC^2 ;p
    boolean morseSpace = false;    // Flag to prevent multiple received spaces
    boolean gotLastSig = true;     // Flag that the last received morse signal is decoded as dot or dash
    const int morseTreetop = 63;   // This is for ITU with puncutation, but without non-english extensions
    const int morseTableLength = (morseTreetop * 2) + 1;
    const int morseTreeLevels = log(morseTreetop + 1) / log(2);
    int morseTableJumper = (morseTreetop + 1) / 2;
    int morseTablePointer = morseTreetop;

    // This is the table for ITU with punctuation (but without non-english characters - for now)
    char morseTable[] = "*5*H*4*S***V*3*I***F***U?!_**2*E***L\"**R*+.****A***P@**W***J'1* *6-B*=*D*/"
                        "*X***N***C;*!K*()Y***T*7*Z**,G***Q***M:8*****O*9***0*";

    int morseSignals;              // nr of morse signals to send in one morse character
    char morseSignal[] = "......"; // temporary string to hold one morse character's signals to send
    int morseSignalPos = 0;
    int sendingMorseSignalNr = 0;
    unsigned long sendMorseTimer = 0;
    boolean morseEcho = false; // Echoes character to encode back to serial and Morse signal input to output pin
    boolean listeningAudio = false;
    boolean sendingMorse = false;
    boolean morseSignalState = false;
    boolean lastKeyerState = false;
    unsigned long lastDebounceTime = 0;  // the last time the input pin was toggled

    void setup()
    {
      // BB rotary encoder stuff ------------------------------------
      pinMode(pinA, INPUT);   // reads Pin A of the encoder
      pinMode(pinB, INPUT);   // reads Pin B of the encoder
      digitalWrite(pinA, HIGH);
      digitalWrite(pinB, HIGH);
      level = wpm;            // starting point for encoder
      tone(8, pitch); // (BB) duration is always on (port,freq,millseconds)
      pinMode(encoderSwitchPin, INPUT); //switch
      digitalWrite(encoderSwitchPin, HIGH); //turn pullup resistor on
      // Set up to call our knob function any time pinA rises
      attachInterrupt(0, knobTurned, RISING);  // calls our 'knobTurned()' function when pinA goes from LOW to HIGH
      pinMode(morseInPin, INPUT);
      digitalWrite(morseInPin, HIGH); // internal pullup resistor on
      pinMode(morseOutPin, OUTPUT);
      // ------------------------------------------------------------
      Serial.begin(9600);
      Serial.println("Morse en-/de-coder by raron. Customed by Flat5");
      displayCurrentSettings(); // default settings at this point (BB added)
      markTime = millis();
      spaceTime = millis();
    }

    void loop()
    {
      switchState == digitalRead(encoderSwitchPin);
      if (switchState != HIGH) {
        knobTurned();
      }
      boolean morseKeyer = !digitalRead(morseInPin); // inverted for active-low input
      // If the switch changed, due to noise or pressing:
      if (morseKeyer != lastKeyerState)
      {
        lastDebounceTime = millis(); // reset timer
        listeningAudio = false;      // disable listen to audio-mode
      }

      // debounce the morse keyer, unless listening to audio morse signal
      if (!listeningAudio && (millis() - lastDebounceTime) > debounceDelay)
      {
        // whatever the reading is at, it's been there for longer
        // than the debounce delay, so take it as the actual current state:
        morseSignalState = morseKeyer;
        // differentiante mark and space times
        if (morseSignalState) markTime = lastDebounceTime; else spaceTime = lastDebounceTime;
      }

      // If no manual morse keying the last second, enter audio listen mode
      if (!morseKeyer && millis() - lastDebounceTime > 1000)  listeningAudio = true;

      // Filter audio morse signal
      if (listeningAudio)
      {
        int audioMorse = analogRead(analogPin);
        unsigned long currentTime = millis();

        // Check for an audio signal...
        if (audioMorse > AudioThreshold)
        {
          // If this is a new morse signal, reset morse signal timer
          if (currentTime - lastDebounceTime > dotTime / 2)
          {
            markTime = currentTime;
            morseSignalState = true; // there is currently a signal
          }
          lastDebounceTime = currentTime;
        } else {
          // if this is a new pause, reset space time
          if (currentTime - lastDebounceTime > dotTime / 2 && morseSignalState == true)
          {
            spaceTime = lastDebounceTime; // not too far off from last received audio
            morseSignalState = false;        // No more signal
          }
        }
      }

      // Morse output, or a feedback when keying.
      if  (!sendingMorse && morseEcho) digitalWrite(morseOutPin, morseSignalState);

      // Encode Morse code or execute commands
      if (Serial.available() > 0 && !sendingMorse)
      {
        char encodeMorseChar = Serial.read();
        // if a command instead, adjust some settings
        if (encodeMorseChar == MorseCommand)
        {
          // parser BB modified ----------------------------------------------------------------------------------
          int digits;
          int value = 0;
          int var = wpm; // If wpm is very fast keyboard input requires very fast typing. After command input re-set wpm
          wpm = 5;       // for now
          do
          {
            digits = Serial.available();
          }
          while (digits < 1);
          // Read what setting
          char morseSet = Serial.read();
          do
          {
            digits = Serial.available();
          }
          while (digits < 0);
          value = Serial.parseInt();
          Serial.flush(); // just in case
          // Adjust and print the new setting
          if (refreshScreen == true) // BB added
          {
            Serial.write(27); // ESC
            Serial.write("[2J"); // clear screen
            Serial.write(27); // ESC
            Serial.write("[H"); // cursor to home
          }
          else
          {
            Serial.println();
          }
          switch (morseSet)
          {
            case '>':
              refreshScreen;
              break;

            case 'a': case 'A': // Audio input threshold value
              AudioThreshold = value;
              if (AudioThreshold < 0) AudioThreshold = 0; // not recommended
              Serial.print(" > Audio threshold:");
              Serial.print (value, DEC);
              Serial.println(" <");
              break;

            case 'd': case 'D': // Debounce value
              debounceDelay = (unsigned long) value;
              if (debounceDelay < 0) debounceDelay = 0;
              Serial.print(" > Debounce (ms):");
              Serial.print (value, DEC);
              Serial.println(" <");
              break;

            case 'e': case 'E': // Turn on / off Morse echo back to serial and Morse output pin
              if (value > 0)
              {
                morseEcho = true;
                Serial.println(" > Echo: on <");
              } else {
                morseEcho = false;
                Serial.println(" > Echo: off <");
              }
              break;

            // BB added ------------------------------------

            case 'p': case 'P':
              pitch = value;
              if (pitch == 0)
              {
                pitch = 0;
                noTone(8);
                Serial.println(" > Tone echo is now off. <");
                break;
              }
              if (pitch < 31)
              {
                Serial.println(" > 31 is minimum <");
                break;
              }
              if (pitch > 24000)
              {
                Serial.print(" > 24000 is max <");
                break;
              }
              Serial.print(" > Tone freq. is ");
              Serial.print(pitch, DEC);
              Serial.println(" <");
              tone(8, pitch);
              break;

            case 'r': case 'R': // clear screen. works only with real terminals otherwise puts garbage on screen.
              if (value > 0)    // r1 to enable | r to disable
              {
                refreshScreen = true;
                Serial.write(27); // ESC
                Serial.write("[2J"); // clear screen
                Serial.write(27); // ESC
              }
              else
              {
                refreshScreen = false;
              }
              break;

            case 'h': case 'H': case '?':
              help();
              break;

            case 't': case 'T':
              if (value > 0 && value < 201)
              {
                increment = value - 1;
                Serial.print("> Increment: ");
                Serial.println(value);
              }
              else
              {
                Serial.println("> out of range: 1-200");
              }
              break;
            //--------------------------------------------- BB

            case 'w': case 'W': // Morse speed setting in wpm
              wpm = value;
              var = value;      // re-set wpm again at end of command parsing
              if (wpm <= 4)
              {
                wpm = 5;
                var = 5;
                Serial.println(" > 5 min <");
              }
              if (wpm > 200)
              {
                wpm = 200;
                var = 200;
                Serial.println(" > 200 max <");
              }
              setDotDash();
              Serial.print(" > wpm: ");
              Serial.print (wpm, DEC);
              Serial.println(" <");
              break;

            case 'i': case 'I': // Display info (current settings).
              wpm = var;
              displayCurrentSettings();
              break;

            default:
              Serial.print(" > Unrecognized command <");
          }
          // Mark that we have executed a command (don't send morse)
          encodeMorseChar = MorseCommand;
          wpm = var; // done typing, re-set wpm
        }
        // end of command parsing ------------------------------------------------------------
        if (encodeMorseChar != MorseCommand)
        {
          // change to capital letter if not
          if (encodeMorseChar > 'Z') encodeMorseChar -= 'z' - 'Z';

          // Scan for the character to send in the Morse table
          int i;
          for (i = 0; i < morseTableLength; i++) if (morseTable[i] == encodeMorseChar) break;
          int morseTablePos = i + 1; // 1-based position

          // Reverse dichotomic / binary tree path tracing

          // Find out what level in the binary tree the character is
          int test;
          for (i = 0; i < morseTreeLevels; i++)
          {
            test = (morseTablePos + (0x0001 << i)) % (0x0002 << i);
            if (test == 0) break;
          }
          int startLevel = i;
          morseSignals = morseTreeLevels - i; // = the number of dots and/or dashes
          morseSignalPos = 0;

          // Travel the reverse path to the top of the morse table
          if (morseSignals > 0)
          {
            // build the morse signal (backwards from last signal to first)
            for (i = startLevel; i < morseTreeLevels; i++)
            {
              int add = (0x0001 << i);
              test = (morseTablePos + add) / (0x0002 << i);
              if (test & 0x0001 == 1)
              {
                morseTablePos += add;
                // Add a dot to the temporary morse signal string
                morseSignal[morseSignals - 1 - morseSignalPos++] = '.';
              } else {
                morseTablePos -= add;
                // Add a dash to the temporary morse signal string
                morseSignal[morseSignals - 1 - morseSignalPos++] = '-';
              }
            }
          } else {  // unless it was on the top to begin with (A space character)
            morseSignal[0] = ' ';
            morseSignalPos = 1;
            morseSignals = 1; // cheating a little; a wordspace for a "morse signal"
          }
          morseSignal[morseSignalPos] = '\0';

          // Echo back the letter with morse signal as ASCII to serial output
          if (morseEcho)
          {
            Serial.print(encodeMorseChar);
            Serial.print(morseSignal);
            Serial.print(" ");
          }
          if (morseTablePos - 1 != morseTreetop)

          {
            Serial.println();
            // Serial.print("..Hm..error? MorseTablePos = ");
            // Serial.println(morseTablePos);
          }
          // start sending the character
          sendingMorse = true;
          sendingMorseSignalNr = 0;
          sendMorseTimer = millis();
          if (morseSignal[0] != ' ') digitalWrite(morseOutPin, HIGH);
        }
      }

      // Send Morse signals to output
      if (sendingMorse)
      {
        switch (morseSignal[sendingMorseSignalNr])
        {
          case '.': // Send a dot (actually, stop sending a signal after a "dot time")
            if (millis() - sendMorseTimer >= dotTime)
            {
              digitalWrite(morseOutPin, LOW);
              sendMorseTimer = millis();
              morseSignal[sendingMorseSignalNr] = 'x'; // Mark the signal as sent
            }
            break;
          case '-': // Send a dash (same here, stop sending after a dash worth of time)
            if (millis() - sendMorseTimer >= dashTime)
            {
              digitalWrite(morseOutPin, LOW);
              sendMorseTimer = millis();
              morseSignal[sendingMorseSignalNr] = 'x'; // Mark the signal as sent
            }
            break;
          case 'x': // To make sure there is a pause between signals and letters
            if (sendingMorseSignalNr < morseSignals - 1)
            {
              // Pause between signals in the same letter
              if (millis() - sendMorseTimer >= dotTime)
              {
                sendingMorseSignalNr++;
                digitalWrite(morseOutPin, HIGH); // Start sending the next signal
                sendMorseTimer = millis();       // reset the timer
              }
            } else {
              // Pause between letters
              if (millis() - sendMorseTimer >= dashTime)
              {
                sendingMorseSignalNr++;
                sendMorseTimer = millis();       // reset the timer
              }
            }
            break;
          case ' ': // Pause between words (minus pause between letters - already sent)
          default:  // Just in case its something else
            if (millis() - sendMorseTimer > wordSpace - dashTime) sendingMorse = false;
        }
        if (sendingMorseSignalNr >= morseSignals) sendingMorse = false; // Ready to encode more letters
      }

      // Decode morse code
      if (!morseSignalState)
      {
        if (!gotLastSig)
        {
          if (morseTableJumper > 0)
          {
            // if pause for more than half a dot, get what kind of signal pulse (dot/dash) received last
            if (millis() - spaceTime > dotTime / 2)
            {
              // if signal for more than 1/4 dotTime, take it as a valid morse pulse
              if (spaceTime - markTime > dotTime / 4)
              {
                // if signal for less than half a dash, take it as a dot, else if not, take it as a dash
                // (dashes can be really really long...)
                if (spaceTime - markTime < dashTime / 2) morseTablePointer -= morseTableJumper;
                else morseTablePointer += morseTableJumper;
                morseTableJumper /= 2;
                gotLastSig = true;
              }
            }
          } else { // error if too many pulses in one morse character
            Serial.println("<ERROR: unrecognized signal!>");
            gotLastSig = true;
            morseTableJumper = (morseTreetop + 1) / 2;
            morseTablePointer = morseTreetop;
          }
        }
        // Write out the character if pause is longer than 2/3 dash time (2 dots) and a character received
        // if ((millis() - spaceTime >= (dotTime * 2)) && (morseTableJumper < 16))
        if ((millis() - spaceTime >= (dotTime * 2)) && (morseTableJumper < ((morseTreetop + 1) / 2)))
        {
          char morseChar = morseTable[morseTablePointer];
          Serial.print(morseChar);
          morseTableJumper = (morseTreetop + 1) / 2;
          morseTablePointer = morseTreetop;
        }
        // Write a space if pause is longer than 2/3rd wordspace
        if (millis() - spaceTime > (wordSpace * 2 / 3) && morseSpace == false)
        {
          Serial.print(" ");
          morseSpace = true ; // space written-flag
        }

      } else {
        // while there is a signal, reset some flags
        gotLastSig = false;
        morseSpace = false;
      }

      // save last state of the morse signal for debouncing
      lastKeyerState = morseKeyer;
    }

    // end loop ----------------------------------------------------

    void displayCurrentSettings() {
      Serial.println();
      Serial.print("Morse speed: ");
      Serial.print (wpm, DEC);
      Serial.print(" wpm (dot=");
      Serial.print (dotTime, DEC);
      Serial.print("ms, dash=");
      Serial.print (dashTime, DEC);
      Serial.println("ms)");
      Serial.print("Rotary Encoder increment: ");
      Serial.println(increment + 1);
      Serial.print("Audio threshold: ");
      Serial.println(AudioThreshold, DEC);
      Serial.print("Tone freq: ");
      Serial.print(pitch);
      Serial.println("Hz");
      Serial.print("Debounce: ");
      Serial.print (debounceDelay, DEC);
      Serial.println(" ms.");
      Serial.print("Screen Refresh ");
      if (refreshScreen == 0) { Serial.println("off"); }
      else { Serial.print("on"); }
      Serial.println();
    }

    void knobTurned() {
      level = wpm;
      state = 0;    // reset this value each time
      state = state + digitalRead(pinA);   // add the state of Pin A
      state <<= 1;  // shift the bit over one spot
      state = state + digitalRead(pinB);   // add the state of Pin B
      delay(2); // (BB) using .01uf caps & 4.7k resistors for debounce. It is causing a double speak.
      level = level + bump[state];
      if (level > wpm)
      {
        wpm = level + increment;  // increment wpm by 5 or used defined
        if (wpm > 200) { wpm = 200; }
      }
      else
      {
        wpm = level - increment;      // decrement wpm by 5 or used defined
        if (wpm < 5)    { wpm = 5; }
        if (wpm >= 200) { wpm = 5; } // int can't go below 0 so rolls over
      }
      setDotDash();
      if (refreshScreen == true) { refresh_Screen; }
      Serial.println();
      Serial.print(wpm);
      delay(2); // debounce
    }

    void setDotDash()
    {
      dotTime = 1200 / wpm;
      dashTime = 3 * 1200 / wpm;
      wordSpace = 7 * 1200 / wpm;
    }

    void refresh_Screen()
    {
      Serial.write(27); // ESC
      Serial.write("[2J"); // clear screen
      Serial.write(27); // ESC
      Serial.write("[H"); // cursor to home
    }

    void help() // Help Screen
    {
      Serial.println();
      Serial.println("To change paramaters");
      Serial.println("Options begin with: <");
      Serial.println();
      Serial.println("Display current settings: <i");
      Serial.println("This help screen: <h");
      Serial.println("Morse speed: <m5-200");
      Serial.println("Rotary Encoder increment: <t1-200");
      Serial.println("ms. Debounce: <d0-50");
      Serial.println("ms. Audio threshold: <a300-600");
      Serial.println("Hz. Tone pitch: <p31-24000 or <0 for off");
      Serial.println("Screen Refresh for real terminals: <r (off) <r1 (on)");
      Serial.println("To refresh screen: <>");
      Serial.println();
      Serial.println("Reset monitor or Arduino to restore default settings");
    }

    /*
    3 input methods for decoding and encoding:
    -  Audio Morse signals on an analog input (for decoding).
    -  Morse keying on digital input (for decoding).
    -  ASCII text via serial communication (for encoding. Also for adjusting settings).

    2 outputs for encoded and decoded Morse:
    -  Morse-code toggled output pin (for encoded Morse).
    -  ASCII text via serial communication (for decoded Morse) .

    It will enter audio listen mode if nothing is keyed in within a second.

    Serial input:
     Only International Morse Code (letters A-Z, numbers), and space, are
     encoded and sent as Morse code automatically unless whatever the "command"
     character is (default '<') is received.
     No punctuation supported (but the morse table can be extended easily for that)

    Morse en-/de-coder < commands via serial:
     The ASCII "less than" character '<' denotes a command instead of encoding and
     sending Morse. The format is:

       <Lxxx

     where L is a letter and xxx is a three-digit decimal number 000-999.
     One exception is the <i command for info (displays current settings).
     The letter can be upper or lower case W, A, D, E or I. Examples:

       <w008 - sets speed to 8 wpm
       <a900 - sets audio threshold to be 900
       <D020 - sets debounce delay to be 20 milliseconds.
       <e000 - turn off Morse echo back to serial as ASCII and output pin as Morse
       <e001 - turn on Morse echo (any value above 0 will do)
       <i    - info - displays some settings

    Full duplex:
     It can both transmit and receive simultaneously, but serial output will
     be a mixed mess unless echo is turned off.

    NOTE: Values for tolerance (like dot time or dash time divided by 2 etc) are
         more or less arbitrarily/experimentally chosen.

         Sorry the source code is a big mess for now...

    Based on Debounce example from the Arduino site for the morse keyer button
    (http://www.arduino.cc/en/Tutorial/Debounce).
    Loosely based on Morse Decoder 3.5 from 1992 for the Amiga by the same guy.
    Contact: raronzen@gmail.com  (not checked too often..)

    Copyright (C) 2010 raron

       This program is free software: you can redistribute it and/or modify
       it under the terms of the GNU General Public License as published by
       the Free Software Foundation, either version 3 of the License, or
       (at your option) any later version.

       This program is distributed in the hope that it will be useful,
       but WITHOUT ANY WARRANTY; without even the implied warranty of
       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       GNU General Public License for more details.

       You should have received a copy of the GNU General Public License
       along with this program.  If not, see <http://www.gnu.org/licenses/>.

    History:
    1992.01.06 - Morse decoder 3.5 - 68000 Assembler version for the Amiga 600
                   using a binary tree (dichotomic table) for Morse decoding
    2010.11.11 - Rewrite for the Arduino
    2010.11.27 - Added Morse encoding via reverse-dichotomic path tracing.
                   Thus using the same Morse table / binary tree for encoding and decoding.
    2010.11.28 - Added a simple audio Morse signal filter. Added a simple command parser
    2010.11.29 - Added echo on/off command

    2015.3 - some modification by Barry Block to use the 'tone' ability.
             input & output circuits modified
             plan to add a rotary encoder to adjust speed.

    TODO: Make the timings signed long again, to avoid rollover issue
         Avoid calling millis() all the time?
         Make functions or classes out of it sometime...
         Generally tidy it up
    Not enough variable memory to include the LCD backpack routines. :-(
    */
     
    Credits for most of the code are in post above.
     
    Last edited: Apr 26, 2015

Share This Page