Morse Code Reader

In a previous post, I have shown how to send Morse code. Wouldn’t it be nice if we could read it and convert it back to text? This is what I intend to demonstrate in this blog post. We will see how to program the Arduino to read from a digital input pin and look at how to convert digital on/off information into Morse code sequences of dots and dashes, and then into text. The featured image at the beginning of this blog post is Copyright Museums Victoria / CC BY. The image was downloaded from Museums Victoria.

As mentioned in my previous post, Morse Code Generator, Morse code is a method of transmitting information as a series of on-off tones, lights, or clicks. Basically, letters, numbers, and punctuation marks are translated to a variable length collection of dots and dashes, of shorter and longer bursts of sound or light. The duration of the dot, the short burst, is the unit of time by which all other elements of Morse code are defined. A dash, the long burst, is a signal whose duration is three times that of the dot. The time between dots and dashes within an encoded character is one unit of time. the time between characters is three units of time and the time between words is seven units of time.

International Morse code is thus composed of five elements:

  • short mark, dot or ‘dit’: one time unit long
  • longer mark, dash or ‘dah’: three time units long
  • gap between the dots and dashes within a character: one time unit long
  • gap between letters of a word: three time units long
  • gap between words: seven time units long

Following, is the international Morse code equivalent, in dot (.) and dash (-) notation for each alphabetical and numerical character:

315px-International_Morse_Code.svg

A Question of Timing

Decoding Morse code is all about measuring time. Measuring the amount of time the tone, light or electrical signal is on and the amount of time the tone, light, or electrical signal is off. Take the following diagram depicting the Morse code signal for the text “A TEST”.

Morse Code Timing

It is the on signal time that determines whether a dot or dash was transmitted. As shown above, whenever the signal is on for one unit of time, a dot (or dit) was transmitted and whenever the signal is on for three units of time, a dash (or dah) was transmitted. The off signal time determines if we are within a character being transmitted, if we are between characters, if we are between words, or if we are at the end of a transmission. If the signal is off for one unit of time, we are within a character, if the signal is off for three units of time, we are between characters of a word, if the signal is off for seven units of time, we are between words, and if the signal is off for more than seven units of time, then the transmission is finished.

Measuring Time

In order to measure for how long the signal stays on or off, we must first determine when a signal starts to be on and when the signal starts to be off. In a previous blog post, LED Toggle with a Push-Button Switch, I introduced a piece of code that detects when a signal goes from the off to the on state or from the on to the off state. This piece of code is called an edge detector because it detects the moment the signal changes state, that is the moment an edge (vertical line) occurs in the digital signal. The piece of code introduced was the following:

// Wait for an edge and return state - LED Toggle post
bool waitForEdge() {
  bool startValue = digitalRead(INPORT);
  bool newValue = startValue;
  while (newValue == startValue) {
    newValue = digitalRead(INPORT);
    }
  delay(DEBOUNCE_DELAY);
  return newValue;
}

In the code, we first read the actual value of the signal, then read its value until it changes. Once the value changes, we wait a bit to let the signal settle. The function then returns the last value read. This works except for one thing: we need to detect end-of transmission, which occurs if a low signal lasts for significantly more than seven time units. We need to add a timeout to the logic and try to detect a state change, but only for a certain amount of time. Also, because we need to detect a state change from a previously known state, we want to pass the previous state to the function instead of doing an initial signal read in the function. This will allow for a state change occurring outside the function.

Following is the new detectEdge() function. The first modification to the edge detection function is that three parameters were added to the function: the Arduino digital input port to read from, a timeout value in milliseconds, and a Boolean value, startingState, containing the current state of the signal, on or off. At the beginning of the function body, we declare Boolean variable signalState that will be used to hold the current signal state. We then get the current process time in milliseconds.

Note that signalState is initialized to the startingState value, ensuring that signalState is always valid. It is a good practice to initialize variables upon their declaration as it prevents them from having unknown values. In the following code, if signalState was not initialized, it would be possible to exit the while loop upon a timeout, without ever assigning a value to signalState causing the function to return an unitialized value that may randomly take any value.

// Wait for an edge or timeout.
bool detectEdge(int port, int timeout, bool startingState) {
  unsigned long timerStart = millis();
  bool signalState = startingState;
  while (true) {
    if ((millis() - timerStart) > timeout) {
      break;
    }
    signalState = digitalRead(INPORT);
    if (signalState != startingState) {
      break;
    }
  }
  return signalState;
}

A while(true) infinite loop follows. Within the loop, we first check if we have timed-out and if so, exit the loop. If not, we read the signal value from the input port and if the signal value is different from the starting state, we also exit the loop. The loop continues indefinitely until either a timeout occurs or a signal value change is detected. The current signal state value is returned at the end of the function.

Evaluating Morse Code Time Units

Now that we are detecting edges, we can measure the time a signal remains on or off, true or false. We need to determine if a portion of the signal lasted for 1, 3, or 7 units of time or longer. We could simply take the elapsed time between edges and divide it by the time 1 unit of time takes. This would give us the exact number of time units a portion of the signal lasted, which could be, say, 0.9, 3.1, or 6.8. Intuitively, we can see that 0.9 time units is really 1 time unit, 3.1 is 3 time units, and 6.8 time units is 7 time units. When measuring signal time, we have to allow for a bit of error in the measurement of time or in the signal production. After all, Morse code can be manually produced by a key operator and speed will vary with time.

A solution to this problem could be to round the measured time unit to the nearest integer. Hence, 0.5 to 1.49 time units would be considered to be 1 time unit and 2.5 to 3.49 time units would be considered 3 time units. Now what would we do if we had some portions of signal that were measured to last 2, 4, 5, 6, 8, or 9 time units in this manner? Would we generate an error? Again, intuitively, 4 units of time is a lot closer to 3 units of time than it is to 7 units of time and 6 units of time is closer to 7 units of time. We are less interested in in-between values than we are in deciding if a portion of signal is closer to 1, 3, or 7 units of time, or if it is completely off the scale.

Since we want the number of units of time to be either 1, 3, or 7, we need to define ranges that translate into these values. Let’s use, as a first rule, that the time a portion of signal lasts must be within 50 percent of its nominal value in time units. That is a 1 time unit signal must be between 0.5 and 1.5 time units. For any portion of a measured signal, the measured time T, in time units, must then be within the ranges

1 time unit: 0.5 ≤ T < 1.5
3 time units: 1.5 ≤ T < 4.5
7 time units: 3.5 ≤ T < 10.5

Using this rule, all time measurements that are within 0.5 and 10.5 time units are covered. There is an overlap between 3.5 and 4.5 time units, however. This can be resolved by splitting the difference and specifying the following ranges

1 time unit: 0.5 ≤ T < 1.5
3 time units: 1.5 ≤ T < 4
7 time units: 4 ≤ T < 10.5

Anything below 0.5 time units is thus considered as if there was no signal, or as if there was noise on the line; anything above 10.5 time units is considered a time out, or an end of transmission. The following code translates an elapsed time in Morse code time units according to the rules just stated.

// Convert time in milliseconds to Morse code time units
int timeUnits(int elapsedTime, int timeUnit, int timeout) {
  int value = 15;
  if (elapsedTime < 0.5*timeUnit) {
    value = 0;
  }
  else if (elapsedTime < 1.5*timeUnit) {
    value = 1;
  }
  else if (elapsedTime < 4*timeUnit) {
    value = 3;
  }
  else if (elapsedTime < timeout) {
    value = 7;
  }
  return value;
}

The function timeUnits() takes three parameters: the elapsed time in milliseconds to be converted to Morse code time units; the number of milliseconds corresponding to one Morse code unit of time; and the number of milliseconds corresponding to a timeout. This last parameter could have been set to 10.5 time units in the code, but this timeout value is also used when detecting edges and in order to ensure that values are coherent throughout the code, it is set once and passed as a parameter.

The body of the function starts by assuming that there will be a timeout and sets the number of time units to 15, arbitrarily chosen to represent a timeout. Then the unit time value is set according to where the number of elapsed milliseconds fall within the range of acceptable values. Finally, the function returns the value of time units, either 0, 1, 3, 7, or 15.

Sending Text Back to the PC

Before continuing with Morse code signal analysis and decoding, let’s have a look at a very useful class in the Arduino library: the Serial class. It is used to allow the Arduino board to communicate with a computer or other devices. All Arduino boards have at least one serial port that communicate on digital pins 0 and 1 as well as with a computer through the board’s USB connector. You cannot use pins 0 and 1 for input/output and serial communication at the same time.

The Arduino Serial class supports several methods to configure, send and receive information from the serial port. In this project, I made use of three of these methods:

  • The begin() method is used to initialize the serial port with serial communication speed, called a baud rate, and communication options including the number of data bits, from 5 to 8; the parity type, odd, even, or none; and the number of stop bits.
  • The print() method sends ASCII characters corresponding to the method argument. If the argument is a String, the characters of the string are sent as is. If the argument is an integer or a floating-point number, the numerical value is first converted to a string and then sent as ASCII characters.
  • The println() method behaves the same as the print() method, but appends a carriage return character (ASCII 13 or ‘\r’) and a new line character (ASCII 10 or ‘\n’) to the information sent.

Converting Morse Code to Text

Now that we measure time and are evaluating the number of time units the signal is on or off, we can convert the signal to Morse code and then to text. First, we declare global information used throughout the code. In the following code snippet, we set the Morse code signal to be read from the Arduino’s board pin 8, INPORT. We set the unit of time, TIME_UNIT, to 50 milliseconds, corresponding to the CODEX speed standard of 20 Words Per Minute (WPM), and we set the timeout value, READ_TIMEOUT, at 10.5 times unit of time. There are three global variables used to hold information between calls to the loop() function: currentCode holds a Morse code character being read, currentLine holds the text string being decoded from Morse code, and currentState holds the current state of the signal being decoded.

/*
 Morse Code Reader
 Program that reads Morse code and outputs decoded information to the
 Serial interface. It is associated with the Morse Code decoder blog
 post on https://lagacemichel.com
 MIT License
 Copyright (c) 2019, Michel Lagace
 */

 define INPORT 8
 define TIME_UNIT 50
 define READ_TIMEOUT 10.5*TIME_UNIT
 String currentCode;
 String currentLine;
 bool currentState;

The next code snippet contains the setup() function that initializes the digital input pin to receive the Morse code signal, initializes global variables, and sets up the serial communication to communicate with the PC at 9600 baud, or approximately 10 characters per second.

// Setup the board.
 void setup() {
   pinMode(INPORT, INPUT);
   currentState = false;
   currentCode = "";
   currentLine = "";
   Serial.begin(9600);
 }

The decodeSequence() function converts a series of dots and dashes representing the signal sequence of a Morse code character into the equivalent ASCII character. In the code below, we first initialize a String, characters, that contains the collection of all supported characters, from ‘a’ to ‘z’ and from ‘0’ to ‘9’. We then have an array of String objects, codedCharacters, that contains the Morse code corresponding to each character in the first String. The decodeSequence() function takes one parameter, sequence, a String containing the Morse code to decode. The function returns a single ASCII character corresponding to the decoded Morse code signal. It starts by initializing character, the variable containing the decoded Morse code to an asterisk. This is the character returned if the Morse code cannot be found in codedCharacters. It loops through the list of Morse codes in codedCharacters comparing each one to the sequence to be decoded. If found, the index of the sequence found is used to identify the character in characters. The character found or an asterisk is returned at the end of the function.

// List of valid alphanumeric characters
static String characters = "abcdefghijklmnopqrstuvwxyz0123456789";

// Morse code sequences for each alphanumeric character
static String codedCharacters[] = { 
   ".-", "-…", "-.-.", "-..", ".", "..-.", "--.", "….",
   "..", ".---", "-.-", ".-..", "--", "-.", "---", ".--.",
   "--.-", ".-.", "…", "-", "..-", "…-", ".--", "-..-",
   "-.--", "--..", "-----", ".----", "..---", "…--",
   "….-", "…..", "-….", "--…", "---..", "----."};

 // Convert Morse code signal to a character
 char decodeSequence(String sequence) {
   int i;
   char character = '*';
   for (i = 0; i < sizeof(codedCharacters)/sizeof(String); i++) {
     if (codedCharacters[i] == sequence) {
       character = characters[i];
       break;
     }
   }
   return character;
 }

The Main Loop

The following piece of code contains the loop() function of the program. It waits for a signal’s edge, accumulates Morse code dashes and dots, decodes the dots and dashes into characters and assembles the characters into words to form a sentence. The loop() function is split into three sections: the edge detection section, the falling edge processing section and the rising edge processing section.

// This is the main loop. It monitors INPORT digital IO
// pin for Morse code, decodes the signal and sends decoded
// text to the attached PC.
void loop() {
  // Wait for a rising edge, falling edge, or time out
  unsigned long startTime = millis();
  bool previousState = currentState;
  currentState = detectEdge(INPORT, READ_TIMEOUT, previousState);
  int elapsedTimeUnits =
    timeUnits(millis() - startTime, TIME_UNIT, READ_TIMEOUT);

  // On a falling edge, append dot or dash to character
  if (previousState && !currentState) {
      if (elapsedTimeUnits == 1) {
        currentCode += ".";
      }
      else if (elapsedTimeUnits == 3) {
        currentCode += "-";
      }
  }

  // On a rising edge or time out, handle end of
  // character, word, or end of transmission. 
  else if (currentCode != "") {
    if (elapsedTimeUnits > 1) {
       currentLine += decodeSequence(currentCode);
      currentCode = "";
    }
    if (elapsedTimeUnits > 3) {
      currentLine += ' ';
    }
    if (elapsedTimeUnits > 7) {
      Serial.println(currentLine);
      currentLine = "";
    }
  }
}

In the edge detection section, we save the current time since the program started in milliseconds using the millis() function provided by the system. We then call the detectEdge() function which returns the current signal state after detecting an edge or a timeout. We measure the elapsed time in milliseconds by subtracting the current time since the program started from the time saved before detecting the edge. The time in milliseconds is converted to Morse code time units through a call to the timeUnits() function.

In the second section, if the previous signal state was on and the new state is off, we have a falling edge. Any other signal condition is either a rising edge or a timeout. In the case of a falling edge, if the on signal lasted for 1 time unit, add a ‘dot’ to currentCode. If the on signal lasted for 3 units of time, add a ‘dash’ to currentCode. Do not do anything if the signal lasted for any other amount of time.

In the third section, we process information only if some dots and dashes have been accumulated in the currentCode variable. If the off signal lasted 1 unit of time or less, do nothing and continue accumulating dots and dashes in the currentCode variable. If the off signal lasted for more than 1 time unit, decode the dots and dashes sequence into its corresponding character and add it to the sentence being formed; reset the sequence of dots and dashes. In addition, if the off signal lasted more than 3 time units, add a space character to the sentence. Finally, if the off signal lasted more than 7 time units, return the decoded sentence to the serial line through a call to Serial.println() and reset the current line.

Putting it All Together

Let’s use the same setup as was used in my previous post Raspberry Pi Speaks Arduino. Connect the different parts using a solderless breadboard, jump wires, a BC337-40 transistor, and a 10K, a 220K, and a 150K resistor as depicted. Through one of the breadboard’s ground rail, we connect the Raspberry Pi and Arduino’s ground pins. The Raspberry Pi’s 3.3-volt supply is connected to the transistor’s base through the 220K resistor. The Raspberry Pi’s GPIO4 output is connected directly to the transistor’s emitter. The Arduino’s 5-volt supply is connected to the transistor’s collector through a 10K resistor. The Arduino’s digital input 8 is connected directly to the transistor’s collector.

Get the Python and Arduino code for the Morse Code Reader demonstration by accessing the MorseCodeReader repository on Github at https://github.com/lagacemichel/MorseCodeReader and download a copy of MorseCodeTutorial.py on the Raspberry Pi and a copy of MorseDecodeTutorial.ino on your PC. On the PC, using the Arduino IDE, load the MorseDecodeTutorial.ino file and load it on the Arduino board. On the Raspberry Pi, run one of the Python 3 IDEs, load the MorseCodeTutorial.py file and run it.

On the PC, within the Arduino IDE, press the control, shift, and M keys simultaneously. This will make the Serial Monitor window appear. As the Python program on the Raspberry Pi sends Morse code signals, the Arduino board decodes it until an end of transmission is detected, then it sends the text, through the serial port, back to the PC. The text is displayed on the Serial Monitor window and the output should look like the following:

We have just demonstrated Morse code sent by a Raspberry Pi Python program, received by an Arduino digital input pin through a level shifting transistor, decoded back to text within the Arduino and sent to the PC for display through the serial communication port.

Morse Code Generator

Lets Beef-Up the ‘Blink’ Program

A blinking circuit may be useful as a signal to warn others of dangerous situations or to draw people’s attention. Another use for a blinking light is to communicate with someone over a distance. The following program makes use of the blinking light circuit to implement a Morse code generator.

Around 1837, the American Samuel F. B. Morse invented an early version of what was to become Morse code. Morse code is a method of transmitting information as a series of on-off tones, lights, or clicks. Basically, letters, numbers, and punctuation marks are translated to a variable length collection of dots and dashes, of shorter and longer bursts of sound or light. The duration of the dot, the short burst, is the unit of time by which all other elements of Morse code are defined. A dash, the long burst, is a signal whose duration is three times that of the dot. The time between dots and dashes within an encoded character is one unit of time. the time between characters is three units of time and the time between words is seven units of time.

International Morse code is thus composed of five elements:

  • short mark, dot or ‘dit’: one time unit long
  • longer mark, dash or ‘dah’: three time units long
  • gap between the dots and dashes within a character: one time unit long
  • gap between letters of a word: three time units long
  • gap between words: seven time units long

Following, is the international Morse code equivalent, in dot (.) and dash (-) notation for each alphabetical and numerical character:

315px-International_Morse_Code.svg

For instance, if we are to show the character sequence “PARIS” in Morse code, we can represent it in text as “.–.  .-  .-.  ..  …” and if we want to represent it as a series of on and off signals, arbitrarily represented by an equal sign “=” and an underscore “_” respectively, each one time unit in duration and we follow the rules stated above, we get the following signal:

___=_===_===_=___=_===___=_===_=___=_=___=_=_=___

The following program, written in “C” for the Arduino, converts a character string of alphabetical characters into a series of short and long blinking LED signals corresponding to their morse code equivalent. You can reconstruct the program in the Arduino IDE (Integrated Development Environment) by copying the code from this post and pasting it in sequence, as it appears in this text, in the text editing pane of the IDE.

The Program Header

The program header, a few paragraphs down, contains a comment describing the nature and purpose of the program as well as a copyright notice. I am the author of the code and I have put it in the public domain. Comments are character sequences ignored by the program interpreter or compiler. Comments can be formatted as any text between a slash-asterisk, ‘/*‘, and asterisk-slash, ‘*/‘, character sequences. This is the original way to enter comments in the ‘C’ language. Text enclosed between these two-character sequences can span several lines and are ignored. Comments can also be entered after a double-slash, ‘//‘, character sequence. All text after the double-slash character sequence, until the end of line, is considered a comment and is ignored by the program interpreter or compiler.

We define the digital output PORT as LED_BUILTIN, the digital output port corresponding to the Arduino’s built-in LED. The UNIT_TIME value corresponds to the time in milliseconds of the ‘on’ duration of the dot. The #define statement allows programmers to associate text to a name. When the program is compiled, the text replaces the name whenever it appears in the program. At compile time, PORT is replaced by the Arduino LED_BUILTIN  constant and UNIT_TIME is replaced by the integer value 100. Note that LED_BUILTIN is also a definition and it gets substituted by the appropriate integer value depending on the target Arduino board.

The characters String variable and codedCharacters String array variable work in tandem as the list of supported characters and their corresponding Morse code equivalent. The Morse code equivalent is encoded as a character string containing a series of period (‘.‘) and dashes (‘‘) representing the short and long bursts of Morse code. Each string in the codedCharacters array corresponds to the character at the same index in the characters string variable.

/* Morse Code Generator
   Blink an LED connected to pin LED_BUILTIN to display morse
   code corresponding to text entered. Repeats forever.
   This sketch was written by Michel Lagacé, 2018-09-02
   This code is in the public domain */

// Output port to display morse code
#define PORT LED_BUILTIN

// unit time length of the morse encoding
#define UNIT_TIME 100

// Characters to be encoded
static String characters = "abcdefghijklmnopqrstuvwxyz";

// Morse code sequences for each character
static String codedCharacters[] = { 
    ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....",
    "..", ".---", "-.-", ".-..", "--", "-.", "---", ".--.",
    "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-",
    "-.--", "--.." };

In ‘C++‘, an array is a collection of elements accessible through the use of an index. A String is a specialized array for collections of characters. Thus, each character in a String and each element of an array can be accessed through the use of an index. An index is an integer value enclosed in square brackets ([ ]) right after the variable name. The first character of a string or element in an array has index 0. The second character or element has index 1 and each successive character or element’s index is one more than the preceding index.

In the code above, character ‘c’ is at index 2 in the characters String. The corresponding Morse code sequence, “-.-.”, is a String at index 2 in the codeCharacters String array. The characters variable is declared as static meaning that the variable’s memory is allocated once at the start of the program and remains for the duration of the program. Non-static variables in functions are allocated in memory only for the duration of the execution of the function, then released back to the pool of memory a program can use. This is very useful for very large programs as only the necessary memory is used at any time. The characters String is initialized with the character string “abcdefghijklmnopqrstuvwxyz”. codedCharacters is also declared as a static String, but the variable name is followed by square brackets. These square brackets indicate that the variable is an array. The size of the array is set to the number of elements it is initialized with. In this case, 26 elements. If an uninitialized array is to be created, the size can be specified as a value within the square brackets.

Digital Output Setup

The setup of the digital output to blink an LED is identical to the setup in the ‘BLINK’ circuit demonstration. We set the pin mode to OUTPUT.

// Setup the board. Digital port LED_BUILTIN in output mode
void setup() {
    pinMode(PORT, OUTPUT);
}

Outputting Dots and Dashes

Code can be, and should be, segregated into functions that provide specific well-defined functionality to a program. Functions, also called subroutines, are sequences of code that can be called by name. In the ‘C’ language, functions must be defined before they are used and thus must appear in the code before any code that uses them. The simplest functions have the following structure:

void functionName() {
    // Sequence of code the function performs when called
}

The void reserved word specifies that the function does not return any value. The we have the function name, followed by a sequence of code enclosed in curly braces, ‘{‘ and ‘}‘. Elsewhere in the program, if we want to call up the sequence of code contained i the function, we simply use the function name, followed by parentheses and a semicolon:

    functionName();

In the Morse code program, we first define two functions, one to output a dot, put the LED on for one time unit, then off for one time unit; and the other to output a dash, put the LED on for three time units, then off for one time unit. In the following code, you will find both functions. They are very similar to the code found in the original ‘BLINK’ program: lights on, lights off.

/* Function to output a dot: one unit on, one unit off */
void outputDot() {
    digitalWrite(PORT,HIGH);
    delay(UNIT_TIME);
    digitalWrite(PORT,LOW);
    delay(UNIT_TIME);
}

/* Function to output a dash: three units on, one unit off */
void outputDash() {
    digitalWrite(PORT,HIGH);
    delay(UNIT_TIME*3);
    digitalWrite(PORT,LOW);
    delay(UNIT_TIME);
}

Encoding a Character in Morse Code

Next, we have a function that outputs a character as a series of dots and dashes. Before having a look at this function, let’s go through a few concepts that will help people unfamiliar with ‘C++’ understand the code better.

‘C++’ Classes

In ‘C++’, functions can be attached to data structures called classes. I will not delve on classes, but suffices to say that these functions are called methods. The String data type is actually a class called String. Variables created with a class are usually called objects. In the case of the String class, because it is used as a data type, I will still call them variables. The String class has several methods associated with it. in the following function, we use the indexOf() and length() methods. The indexOf() method returns the index of the first occurrence of the character passed as an argument in the String. The length() method returns the number of characters in the String. To use a method associated to a variable, use the variable name followed by a period, followed by the method name, parentheses, and any parameter required by the method. It is like calling a function, but preceded by the variable name. Thus, to get the index of the position of letter ‘f’ within the characters String and initialize the index variable with it, use the following code:

    int index = characters.indexOf('f');

index will be initialized with the value 5, the index of the letter ‘f’ in the String characters.

The outputCharacter function introduces us to two fundamental programming structures. The for-loop, and the if-then-else structures.

if-then-else Control Structure

The if-then-else control structure allows a program to conditionally execute a portion of a program. It has the following structure:

    if (condition) {
        // Code to execute if condition is true
    }
    else if (other condition) {
        // Code to execute if other condition is true
    }
    else {
        // Code to execute if all preceding conditions are false
    }

Only the first if block is mandatory; the else if and else blocks are optional. The execution flow is as follows: if the condition specified within parenthesis after the first if is true, then the code within the curly braces following the if statement is executed; if the condition within the first if statement is false then the condition in the else if statement is checked; if the condition in the else if statement is true, then the code within the curly braces following the else if statement is executed; if the condition in the else if statement is false, then the code contained within the curly braces after the else statement is executed. There can be several else if statements, each testing a different condition.

for-loop Control Structure

The for-loop control structure allows the program to iterate through code several times until a condition is met. It has the following structure:

    for (initialization; condition; post-processing {
        // Code to execute until condition is met
    }

where initialization is a statement executed just before entering the loop; condition is a test performed at the beginning of the loop, if true, code within the for-loop curly braces is executed, if false, execution continues after the loop; and post-processing, is a statement executed after each iteration through the code within the curly brackets. For instance, in the following for-loop control structure:

    for (int i = 0; i < 10; i++) {
        // Code executed 10 times
    }

Variable i is initialized to 0 before entering the loop; i is tested if smaller than 10, since it is, the code within the curly braces is executed and i is available for use within this code; after the code within the curly braces is executed, the variable i is incremented by 1. A second iteration of the loop then starts and i is tested if smaller than 10. The loop continues until the condition i < 10 is false. The loop executes 10 times with values of i from 0 to 9.

outputCharacter Function

Looking at the function header, we find that it is similar to the previous functions, but has a parameter within the parentheses: char c. This defines a single character parameter to the function. Parameters allows the calling program to pass values to the function. In this case, the character to be output as Morse code. Within the outputCharacter function, variable index is set to the index of character c in the characters String. If the character is found, that is if index is greater or equal to 0, then we output the Morse code. To do so, we get the Morse code associated with the character using index to retrieve the Morse code string from codedCharacters. We then iterate through each character of the Morse code and output a dash, if the character is a hyphen or a dot otherwise using the outputDash() and outputDot() functions respectively. At the end of the character output, we wait an extra 2 units of time, totaling the 3 units of time required at the end of a Morse code character. The wait totals 3 units of time since we already introduced a single unit of time delay at the end of the dash or dot.

/* Function to output a single character */
void outputCharacter(char c) {

    // Find index of character to encode
    int index = characters.indexOf(c);
 
    // Ignore unencodable characters
    if (index >= 0) {

        // Encode Morse code and output it
        String code = codedCharacters[index];
        for (int i = 0; i < code.length(); i++) {
            if (code[i] == '-') {
                outputDash();
            }
            else { // if not '-', must be '.'
                outputDot();
            }
        }

        // wait 3 units at the end of the letter
        // (2 units assuming previous dot or dash)
        delay(UNIT_TIME*2);
    }
}

Encoding Text into Morse Code

The sentence function’s purpose is to output text as Morse code. It accepts the single String parameter: text. First, the function gets the length of the text and stores it in the len variable. It then iterates through the whole text String using i as the index in a for-loop structure from 0 until len – 1. It gets the character indexed by i and turns it to lowercase using the tolower() built-in function. If the character is not a space, it is output using outputCharacter(). If the character is a space, we wait an extra 4 units of time, totaling the 7 units of time required at the end of a word. The wait amounts to 7 units of time since we already added 3 units of time at the end of the previous Morse code character output.

// Function to encode a whole string
void sentence (String text) {
    // Compute length of character string
    int len = text.length();

    // Output each character in turn
    for (int i = 0; i < len; i++) {

        // Only lower case characters are encoded
        char c = tolower(text[i]);
        if (c != ' ') {
            outputCharacter(c);
        }
 
        // Spaces are encoded as 7 units,
        // (4 units assuming a previous character)
        else {
            delay(UNIT_TIME*4);
        }
    }
}

The Main Loop

Finally, this is the main loop(). This code is repeated over and over. It outputs the text “Mikes Electro Shack” in Morse code, then waits 28 units of time corresponding to 4 spaces between words. The delay is actually specified as 25 units of time since the last character output already added a 3 units of time delay. You can of course replace the text by any text you see fit.

void loop() {
    sentence("Mikes Electro Shack");
    delay(UNIT_TIME*25); // Wait 4 spaces at the end
}

What Next?

Through the implementation of this fairly simple program, we have seen two extremely important control structures, the if-then-else control structure and the for-loop control structure. We have seen that the String data type is implemented as a class and that there are methods associated with it. We also have seen a few String methods such as indexOf() and length().

This program can be put to use to learn Morse code, which is required to get one’s ham radio (amateur radio) license. Complete the list of characters to include numbers and punctuation marks and their equivalent Morse code to complete the experience.