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.
I have some 2x16 displays and backpacks. The Uno does not have enough memory (was it variable? I think so) for all of it to compile. Well, as I remember, it did compile with a warning but the program did not run.

I wonder if it is possible to use two Unos to run an application.
Perhaps one to just handle the display or if there is a board to do this.
 
The backpack I'm thinking would just use the serial your sending to the computer there would be no extra code
 
Can you write an example?
Would this need to be converted?
Serial.print("LA TI DA");

Seems I have the Nokia-5110 LCD display.
Breadboard time...once the level shifters arrive.
 
Last edited:
Still working on the 567 tone decoder. Fine tuning some capacitor values and types. Would using back to back germanium diodes at the input help or hinder, etc.
Code update. Lot of memory saved by a trick I just found!
Changing 'Serial.print("something")' to Serial.print(F("something")) really makes a difference if you have lots of such statements!
Code:
/*
  sketch_morse_3.1_analog
  MORSE EN-DE-CODER 1.06
  A Morse encoder / decoder for the Arduino.
  May, 11, 2015
*/

// BB variables
unsigned int pitch = 800;
boolean refreshScreen = true; //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 pin state
// switch stuff unused!
int encoderSwitchPin = 4;  //encoder pin
//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*"; //BB 52 characters, I think

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 'knobTurned()' function when pinA goes from LOW to HIGH
  pinMode(morseInPin, INPUT);
  digitalWrite(morseInPin, HIGH); // internal pullup resistor on
  pinMode(morseOutPin, OUTPUT);
  // ------------------------------------------------------------
  Serial.begin(115200); // 9600 115200 300 2  <------- if garbage on screen, check terminal baud rate ***
  Serial.println(F("Morse en-/de-coder by raron. Customed by Flat5"));
  help();
  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
  // BB added
  if (refreshScreen == true) refresh_Screen();
  else Serial.println();
  switch (morseSet)
  {
  case '>':
  refresh_Screen();
  break;

  case 'a': case 'A': // Audio input threshold value
  AudioThreshold = value;
  if (AudioThreshold < 0) AudioThreshold = 0; // not recommended
  Serial.print(F(" > 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(F(" > Debounce (ms):"));
  Serial.print (value, DEC);
  Serial.println(F(" <"));
  break;

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

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

  case 'p': case 'P': // tone frequency
  pitch = value;
  if (pitch == 0)
  {
  pitch = 0;
  noTone(8);
  Serial.println(F(" > Tone echo is now off. <"));
  break;
  }
  if (pitch < 31)
  {
  Serial.println(F(" > 31 is minimum <"));
  break;
  }
  if (pitch > 24000)
  {
  Serial.print(F(" > 24000 is max <"));
  break;
  }
  Serial.print(F(" > Tone freq. is "));
  Serial.print(pitch, DEC);
  Serial.println(F(" <"));
  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.println(F(" > Screen refresh enabled <"));
  }
  else
  {
  refreshScreen = false;
  Serial.println(F(" >Screen refresh disabled <"));
  }
  break;

  case 'h': case 'H': case '?': case 'm': case 'M': // display options and current settings
  help();
  displayCurrentSettings();
  break;

  case 't': case 'T': // encoder increment
  if (value > 0 && value < 201)
  {
  increment = value - 1;
  Serial.print(F("> Increment: "));
  Serial.println(value);
  Serial.print(F(" <"));
  }
  else
  {
  Serial.println(F("> 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(F(" > 5 min <"));
  }
  if (wpm > 200)
  {
  wpm = 200;
  var = 200;
  Serial.println(F(" > 200 max <"));
  }
  setDotDash();
  Serial.print(F(" > wpm: "));
  Serial.print (wpm, DEC);
  Serial.println(F(" <"));
  break;

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

  default:
  Serial.print(F(" > 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(F("..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(F("<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 < ((morseTreetop+1)/2)))
  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(F("Morse speed: ")); Serial.print (wpm, DEC); Serial.print(F(" wpm (dot=")); Serial.print (dotTime, DEC);
  Serial.print(F("ms, dash=")); Serial.print (dashTime, DEC); Serial.println(F("ms)"));
  Serial.print(F("Rotary Encoder increment: ")); Serial.println(increment + 1);
  Serial.print(F("Audio threshold: ")); Serial.println(AudioThreshold, DEC);
  Serial.print(F("Tone freq: ")); Serial.print(pitch); Serial.println(F("Hz"));
  Serial.print(F("Debounce: ")); Serial.print(debounceDelay, DEC); Serial.println(F(" ms."));
  Serial.print(F("Screen Refresh ")); if (refreshScreen == 0) Serial.println(F("off")); else Serial.print(F("on"));
  Serial.println();
}

void knobTurned() { // rotary encoder
  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; if (wpm > 200) wpm = 200; // increment wpm by 5 or user defined. stop at 200
  else  wpm = level - increment; if (wpm < 5) wpm = 5;  // decrement wpm by 5 or user defined. stop at 5
  setDotDash();
  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()
{
  if (refreshScreen == true)
  {
  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(F("To change paramaters"));
  Serial.println(F("Options begin with: <\n"));
  Serial.println(F("This help screen: <h"));
  Serial.println(F("Morse speed: <w5-200"));
  Serial.println(F("Rotary Encoder increment: <t1-200"));
  Serial.println(F("Hz. Tone pitch: <p31-24000 or <0 for off"));
  Serial.println(F("Display current settings: <i"));
  Serial.println(F("ms. Debounce: <d0-50"));
  Serial.println(F("ms. Audio threshold: <a300-600"));
  Serial.println(F("Screen Refresh for real terminals: <r (off) <r1 (on)"));
  Serial.println(F("To refresh screen: <>\n"));
  Serial.println(F("Reset monitor or Arduino to restore default settings"));
}

/*
Some of these comments are outdated (BB)

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. :-(
*/
 
Last edited:
up-dated input interface using a tone decoder to improve 'off the air' detection.
Not finished. Suggestions very welcome.
Edit: Maybe finished :)

Arduino Morse Interface v2(3).png
NE567 tone decoder board pic1.jpg
 
Last edited:
Status
Not open for further replies.

New Articles From Microcontroller Tips

Back
Top