/*
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:
.
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 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("");
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;
}