Continue to Site

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.

  • 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.

Morse encoder/decoder - my first project

Status
Not open for further replies.

flat5

Member
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
 

Attachments

  • morse_1_analog.ino
    17.6 KB · Views: 1,260
  • Arduino Morse Interface.png
    Arduino Morse Interface.png
    7.6 KB · Views: 3,590
  • IMG_3101 edit.jpg
    IMG_3101 edit.jpg
    114.2 KB · Views: 1,533
Last edited:
What kind of signal you have as an input to the Arduino (pin A0)?
 
In the .ino file there are comments at the top that give some useful info.
More info:

https://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:
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..
 
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.
 
Ok.. I download the file. the .ino file extension was suspicious to me :)
 
Here I don't no why people don't post there code like they did before
Code:
/*
              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;
}
 
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.
 
might have html errors
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.
 
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.
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
 
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:
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:
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.
 
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.
 
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 :)
 
"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:
MikeML posted in another thread perhaps what I need to time an event.

This shows how to time a pulse that comes from a digital input. Modifying it so the pulse comes from the AD is simple. This shows how to use the millis() function to time things. You can use it to count things just as easily.


Code:
const byte Squelch = 2;                   // Receiver Squelch is connected to pin 2

const byte Led = 13;                        // Uno's built-in LED, used to indicate for debugging

long  time=0;                                   // declare time to be long

...                                                       // Wait for Rising Edge and return Duration of the Pulse
{ while(!(digitalRead(Squelch)))    //Wait for rising Edge      

  time = millis();                              //store time now 
  digitalWrite(Led,true);                 //Turn On Led
  while(digitalRead(Squelch))        //Wait for falling Edge    

  Dur=millis()-time;                       //Compute duration of On time
 
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.
 
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:
/*
  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:
Status
Not open for further replies.

New Articles From Microcontroller Tips

Back
Top