Introduction: Practicing Morse Code With Arduino: a Rainbow Bridge of Communication

About: Hello! I'm a freshman student studying Electrical Engineering. I love tinkering and making gadgets as a hobby. I really enjoy learning from the projects here on Instructables.

When I first saw the theme "Rainbow", what came to my mind was not only the vivid seven colors, but also the symbolic meaning behind it. Rainbow is frequently assciated with the idea of hope and optimism, as well as the "beauty after the storm". Its physical shape also resembles the arch of a bridge. Thus, these symbolisms rose to my mind when I connected the rainbow with the bridge of communication and message of hope throughout history: morse code.

I first heard of morse code several years back, in a news article where the phrase "SOS" was used in an emergency communication. Several weeks ago, I found a course in Udemy called "Learn Morse Code with Stories" taught by Alex Deva, and started learning it out of curiosity. After hearing the various stories that Mr. Deva integrated into the end of each unit, however, I gradually saw the significance of morse code in some of the most critical times in history. Morse code helped save lives of soldiers who were prisoners of war and conduct important messages in creative, subtle and sometimes extraordinary ways. Indeed, I found that the symbolism of the rainbow combined the ideas of hope and a bridge of communication.

After some research, I found that morse code, although it has declined in the digital world, is still sometimes used in radio, aviation, and serve as backup communication in the modern world.

As I am also learning the basics of morse code with the Udemy course and don't have a physical telegraph, I decided to program my Arduino to provide a way (tactually, audibly and visually) to simulate "sending" and "receiving" morse code. This could provide two ways to practice morse code for those who want to learn it, or even to record and decode something quicker with computer (when and if other forms of communication fail). The first program allows the user to input a message and output it as morse code through the buzzer and two LEDs. The second allows the user to enter morse code through two pushbuttons and also outputs the code through the buzzer and LEDs.

Supplies

  • Arduino UNO
  • Breadboard
  • Two 10kΩ Resistors
  • Two 300Ω Resistors
  • Two Pushbuttons
  • Two LEDs (Red and Blue, or any colors)
  • Passive Buzzer
  • Jumper wires (16 total for me)

Software

  • Arduino IDE
  • Tinkercad (for brainstorming, designing, and testing)

Step 1: Research

Electricity and magnetism has always been a fascinating topic to me, so the idea to do a project related to morse code drove me to do some research on how Samuel Morse's electric telegraph worked.

How Does Morse's Electric Telegraph Work?

The telegraph key is supported by a spring which allows the key to be pressed down and bounce back up in a sequence of short and long intervals. When pressed down, the telegraph key closes the circuit between the battery and ground. In this way, the current flows through the cables, and electricity passes through the coils of wire in the electromagnet. Thus, the electromagnet creates a magnetic field that attracts the iron on the armature, which is held up by a spring. The right end of the armature has a stylus, or pen-like instrument, attached. The longer intervals the stylus has in contact with the paper tap, the code is a dash, or dah. The shorter interval is a dot, or dit. With morse code, the receiver can decode the message sent.

How Does the Electromagnet Work?

The electric telegraph utilizes the property of the pulses of electric current that creates an electric field. This was first discovered by Hans Christian Oersted in 1820 (Britannica). As shown in the second image, whenever the current passes through the coils around the iron core, a magnetic field forms, allowing it the temporary ability to attract metals ("Electromagnetism"). Then, Michael Faraday conducted an experiment to demonstrate electromagnetic induction with a galvanometer.

Working of the Galvanometer

As shown in the third image above, the positive terminal of the battery is connected to a resistor, the galvanometer and then to ground. As the current flows in the clockwise direction, the needle fluxes to the right. Switching the terminals of the battery would flip the direction of current flow. Inside the galvanometer, there is an iron cylinder with two conducting rods on both faces. These are electrically separate from the iron core, but connected to the coils that wraps around the cylinder. The rods are also connected to torsional springs on either side, which tighten and relax to indicate the direction of the needle ("Moving-Coil").

In the fourth image, the current passes through the positive to the negative through the coils. According to the motor effect, a wire carrying a current will experience a force pushing the wire at right angles in another magnetic field. The magnetic field of the two outer magnets passes from the north into the south. The iron core affects the magnetic field of the north and south poles also, bending it so that it bends radially around the core. Thus, the longer sides of the coil experience a uniform magnetic field as they rotate ("Moving-Coil"). The force acting on the coil can be calculated with the equation F=BIl, where B=magnetic field, I=current, and l=length of wire. The fifth image is another illustration of Faraday's Law of Induction (BYJU's).


Britannica, The Editors of Encyclopaedia. "Hans Christian Ørsted". Encyclopedia Britannica, 5 Mar. 2023, https://www.britannica.com/biography/Hans-Christian-Orsted. Accessed 29 July 2023.

"Electromagnetism." BYJU's, https://byjus.com/physics/electromagnetism/. Accessed 29 July 2023.

"Electromagnetism." matc.edu, https://ecampus.matc.edu/mihalj/scitech/unit3/electromagnets/electromagnetism.htm. Accessed 29 July 2023.

"Lesson Video: The Moving-Coil Galvanometer." Nagwa, https://www.nagwa.com/en/videos/658138915780/. Accessed 29 July 2023.

Step 2: Symbolism of Rainbow: Morse Code in History

There were several stories about morse code from history which were particularly intriguing to me, after listening to the stories from Mr. Deva's course, but one in particular strikes me as a bridge of communication to bring hope in the storm:

The story was related to the Colombian Colonel Jose Espejo Muñoz and an advertizing executive Juan Carlos Ortiz. Col. Muñoz asked Ortiz to come up with a way to communicate with his soldiers who were captured about the plan of escape, which was essential to the rescue mission's success. Among the other ideas such as disguising messages in aid packages, Ortiz came up with an idea to communicate with commercial radio. This can work as the soldiers were allowed to listen to radio and they understood morse code. Thus, they asked Carlos Portela to write a pop song with a hidden morse code message. The lyrics was about hope and freedom, and even had a phrase "escucha este mensaje, hermano!" or "listen to this message, brother!". The message in morse code was "19 libertado, siguen ustedes. Animo.", or "19 freed, you are next. Don't Lose Hope."

Step 3: Design With Tinkercad and Build

My first step was to create a design of the project in Tinkercad. Next, I built it on the Arduino as shown in the video in the Introduction. Both Code Part 1 and 2 uses the same circuit.

Pin Numbers:

  • Dot (dit) pushbutton: pin 11
  • Dash (dah) pushbutton: pin 10
  • Blue LED: pin 9
  • Red LED: pin 8
  • Passive Buzzer: pin 7

Pushbuttons:

With a pullup resistor as used here, the input into pin 11 is HIGH when unpressed and LOW when the button is pressed. The left leg of the button is connected to the right leg of a 10kΩ resistor, the same leg of button is also connected to pin 11 or 10. The left leg of the 10kΩ resistor is connected to 5 volts. The right leg of the button is connected to ground.

LEDs:

The left leg of the LED is connected to right leg of 330Ω resistor, left leg of resistor is connected to pin 8 or 9. The right leg goes to ground.

Passive Buzzer:

The left leg goes to pin 7 and right leg to ground.

Morse_Code_Two_Way_Design

Step 4: Code Part 1: User Input Speed

There are two parts to this project. One is the receiving of messages as morse code (with user input), while the other is transmitting morse code to print it on screen.

In the next steps I will break down and attempt to explain the two parts step by step and provide a final version at the end, if you would like to see the explanations. The first part is for practicing receiving message (only outputting a phrase in morse code):

int RedLED=8;
int BlueLED=9;
int buzzPin=7;
int toneVal=1;
int ditLength;
int dahLength;
int spaceLength;


void setup() {
  Serial.begin(9600);
  pinMode(buzzPin,OUTPUT);
  pinMode(RedLED,OUTPUT);
  pinMode(BlueLED,OUTPUT);
}


void loop() {
  Serial.println("Choose a speed: ");
  char speed_choice = getMenuChoice();
  Serial.println(speed_choice);

  setSpeed(speed_choice);
}


char getMenuChoice(){
  Serial.println("A. 20 WPM  B. 35 WPM  C. 50 WPM");
  while(Serial.available()==0){
    
  }
  char choice=Serial.read();


  return choice;
}


void setSpeed(char c){
  switch(c){
    case 'A':
      ditLength=60;
      break;
    case 'B':
      ditLength=34;
      break;
    case 'C':
      ditLength=24;
      break;
  }
  dahLength=ditLength*3;
  spaceLength=ditLength*7;
}
  • The first thing in the void loop is the to prompt the user to choose a speed in words per minute for the morse code with println. The program then calls up a function getMenuChoice(), which waits using the while loop until the user types in the choice and stores the choice in speed_choice.
  • The second thing is to set the speed for the morse code. This treats the length of each dit as a unit, so the duration of a dah = dit*3 and a space = dit*7 according to the conventions.


Calculating Speed in WPM in Morse Code:

  • The word "PARIS " (50 characters of dots and dashes) in morse code with a space on the end is used to determine a standard "word per minute". 20 wpm is equivalent to 20 "PARIS " is send in a minute.
  • To calculate the speed in wpm: if 10 wpm, 10 times of "PARIS " is sent in 60 senconds, or 500 characters sent.
  • 10 wpm: 60 s/ 500 character = .12 s/character = 120 ms/character (in dit)
  • 20 wpm: 60 s/ 1000 character = .06 s/character = 60 ms/dit
  • 35 wpm: 60 s/ 1750 character = .034 s/character = 34 ms/dit
  • 50 wpm: 60 s/ 2500 character = .024 s/character = 24 ms/dit


"Morse Code Timing | Morse Code World." MorseCode.World,https://morsecode.world/international/timing.html. Accessed 29 July 2023.

Step 5: Code Part 1: User Input Word or Phrase

//same code as before

void loop() {

//after setSpeed(speed_choice);

String input_string;
  Serial.println("Enter a Word or Phrase: "); 
  while (Serial.available()==0){
  }
  
  input_string=Serial.readString(); 
  
  int str_len=input_string.length()+1; 
  
  char buf[30];

  input_string.toCharArray(buf, str_len); 
  Serial.println(buf);
}

//same code as before

  • Next is prompting the user to enter a phrase to translate into morse. Again the serial monitor prints the question, waits, and stores the user input string into the variable input_string.
  • input_string is then converted to a char array with the toCharArray() function.
  • The buf array, where the input is stored, is declared with a size of 30. The str_length variable is input_string.length()+1 as one null char need to be added to the end of a char array.
  • The program prints out the user input phrase.

Step 6: Code Part 1: Translate

//same code as before

void loop(){

//same code as before

for (int x=0; x<str_len-1; x++){
      char letter = tolower(buf[x]);
      Serial.println(letter);
      translateLetter(letter); 
      
      delay(800);
  }
}

//same code as before

void translateLetter(char l){
  switch(l){
        case 'a':
          ditSound(1);  //The dit sound last for a set number of milliseconds,
          dahSound(1);  //the dah sound lasts for 3 times as long as a dit
          break;        //LED blinks are of same duration and simultaneous as the dit and dah sounds
        case 'b':
          dahSound(1);
          ditSound(3);
          break;
        case 'c':
          dahSound(1);
          ditSound(1);
          dahSound(1);
          ditSound(1);
          break;
        case 'd':
          dahSound(1);
          ditSound(2);
          break;
        case 'e':
          ditSound(1);
          break;
        case 'f':
          ditSound(2);
          dahSound(1);
          ditSound(1);
          break;
        case 'g':
          dahSound(2);
          ditSound(1);
          break;
        case 'h':
          ditSound(4);
          break;
        case 'i':
          ditSound(2);
          break;
        case 'j':
          ditSound(1);
          dahSound(3);
          break;
        case 'k':
          dahSound(1);
          ditSound(1);
          dahSound(1);
          break;
        case 'l':
          ditSound(1);
          dahSound(1);
          ditSound(2);
          break;
        case 'm':
          dahSound(2);
          break;
        case 'n':
          dahSound(1);
          ditSound(1);
          break;
        case 'o':
          dahSound(3);
          break;
        case 'p':
          ditSound(1);
          dahSound(2);
          ditSound(1);
          break;
        case 'q':
          dahSound(2);
          ditSound(1);
          dahSound(1);
          break;
        case 'r':
          ditSound(1);
          dahSound(1);
          ditSound(1);
          break;
        case 's':
          ditSound(3);
          break;
        case 't':
          dahSound(1);
          break;
        case 'u':
          ditSound(2);
          dahSound(1);
          break;
        case 'v':
          ditSound(3);
          dahSound(1);
          break;
        case 'w':
          ditSound(1);
          dahSound(2);
          break;
        case 'x':
          dahSound(1);
          ditSound(2);
          dahSound(1);
          break;
        case 'y':
          dahSound(1);
          ditSound(1);
          dahSound(2);
          break;
        case 'z':
          dahSound(2);
          ditSound(2);
          break;
        case '1':
          ditSound(1);
          dahSound(4);
          break;
        case '2':
          ditSound(2);
          dahSound(3);
          break;
        case '3':
          ditSound(3);
          dahSound(2);
          break;
        case '4':
          ditSound(4);
          dahSound(1);
          break;
        case '5':
          ditSound(5);
          break;
        case '6':
          dahSound(1);
          ditSound(4);
          break;
        case '7':
          dahSound(2);
          ditSound(3);
          break;
        case '8':
          dahSound(3);
          ditSound(2);
          break;
        case '9':
          dahSound(4);
          ditSound(1);
          break;
        case '0':
          dahSound(5);
          break;
        case ' ':
          delay(spaceLength);
          break;
        default:
          Serial.println("Invalid Letter");
          break;
      }
}
 
void ditSound(int times){
  for(int k=0; k<times; k++){
    digitalWrite(RedLED,HIGH);
    delay(ditLength);

    for(int x=0; x<ditLength; x++){
      digitalWrite(buzzPin,HIGH);
      delay(toneVal);
      digitalWrite(buzzPin,LOW);
      delay(toneVal);
      }

    digitalWrite(RedLED,LOW);
  delay(dahLength);
  }
}


//a dah sound should be 3 times longer than the duration of a dit
void dahSound(int times){
  for(int k=0; k<times; k++){
    digitalWrite(BlueLED,HIGH);
    delay(dahLength);

    for(int x=0; x<dahLength; x++){
      digitalWrite(buzzPin,HIGH);
      delay(toneVal);
      digitalWrite(buzzPin,LOW);
      delay(toneVal);
      }

    digitalWrite(BlueLED,LOW);
  delay(dahLength);
  }
}


The final step is to actually translate the user input string into morse code, with the chosen speed.
A for loop is used to cycle through all the letters in the char array buf[]. Each letter in the char array is called with buf[x] as x increases from 0 to str_len-1 (so subtracting the null char), and stored into letter.
The function translateLetter(char l) is called. It passes the argument letter in the loop into a switch statement, which determines how many dits and dahs are to be played according to morse code.
The dits and dahs each have a void function, which takes in an argument times for the number of times a dit or dah is to be played. The ditLength is set originally by the user in a previous function setSpeed(). At the same time, the red LED is turned on for the same duration as a dit when ditSound() is called, and the blue LED when the dahSound() is called. You can also adjust the toneVal to change the tone of the buzzer.

Above is an image of where I found the morse code for the alphabet and letters, you can also add on punctuations if you would like.


"Morse Code and Phonetic Alphabet Page." sckans.edu, http://www.sckans.edu/~sireland/radio/code.html. Accessed 29 July 2023.

Step 7: Code Part 2: Check for Gap Between Words

int dotPin = 11; 
int dashPin = 10; 
int buzzPin=7;
int redLED=8;
int blueLED=9;

String morseInput = "";   
String message = "";     


int letterGap = 500;     
int wordGap = 1000;  

long lastInputTime = 0;  

int ditLength=40;
int dahLength=120;
int toneVal=1;

void setup() {
  Serial.begin(9600);
  pinMode(dotPin, INPUT_PULLUP);
  pinMode(dashPin, INPUT_PULLUP);
  pinMode(redLED,OUTPUT);
  pinMode(blueLED,OUTPUT);
  pinMode(buzzPin,OUTPUT);
}


void loop() {
  int dotButtonState = digitalRead(dotPin);
  int dashButtonState = digitalRead(dashPin);
  long currentTime = millis();


  if (dotButtonState == LOW || dashButtonState == LOW) {
  
    if (currentTime - lastInputTime > wordGap) {
      
      message += " ";
    }
    lastInputTime = currentTime;
}


  • Before the void loop, set up a lastInputTime variable, where the updated currentTime is stored whenever a button is pushed.
  • In the void loop, set up a currentTime variable to store the time of initially starting the program. The first if statement tests if either of the buttons are pressed. If one is pressed, the program calculates for the time elapsed since the program starts by subtracting the lastInputTime from currentTime. If this time interval is greater than the wordGap, the program adds a space to the input message. After the second if statement, the lastInputTime is updated to the currentTime.

Step 8: Code Part 2: Setting Buttons

//same code as before

void loop() {
  //if (dotButtonState == LOW || dashButtonState == LOW) {
  
   //same code as before

    if (dotButtonState == LOW) {
      digitalWrite(redLED,HIGH);
      delay(ditLength);
      digitalWrite(redLED,LOW);

      buzzerSound(ditLength);
      while (digitalRead(dotPin) == LOW) {}
        morseInput += ".";
    }

    if (dashButtonState == LOW) {
      digitalWrite(blueLED,HIGH);
      delay(dahLength);
      digitalWrite(blueLED,LOW);

      buzzerSound(dahLength);
      while (digitalRead(dashPin) == LOW) {}
      morseInput += "-";
    }
  }
}

void buzzerSound(int buzzT){
  for (int x=0; x<buzzT; x++){
    digitalWrite(buzzPin,HIGH);
    delay(toneVal);
    digitalWrite(buzzPin,LOW);
    delay(toneVal);
  }
}

  • Inside the same if statement that tests for whether a button is pushed, add two if statements to individually test whether the dot or dash button was pushed.
  • For instance, if the dot button was pushed, the red LED is turned on for a length ditLength, which can be adjusted. The buzzSound() calls a function that turns on for a length buzzT, which in this case is the same as ditLength. Also, when the button becomes HIGH (not pressed), a dot is added to the string of morseInput that will be translated in another function.
  • Inside the same if statement that tests for whether a button is pushed, add two if statements to individually test whether the dot or dash button was pushed.
  • The same is done for the dash button, except the pause after the light is turned on and the buzz time is dahLength.

Step 9: Code Part 2: Translate Message and Print

//same code as before

void loop() {

//if (dotButtonState == LOW || dashButtonState == LOW) {
//same code as before
//}

if (currentTime - lastInputTime > letterGap && morseInput != "") {
    char letter = translateCode(morseInput);

    message += letter;
    morseInput = "";
  }

  if (message != "") {
    Serial.print(message);

    message = ""; 
  }
}

char translateCode(String code) {
  
  const char* morseCode[] = {
    ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
    "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..",
    "-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----.",
    ".-.-.-"};
  
  
  const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.";
  
  for (int i = 0; i < 37; i++) { 
    if (code == morseCode[i]) {
      return alphabet[i];
    }
  }
}

//void buzzerSound(int buzzT){
//same code as before
}

  • The last step is to recognize whether a letter is finished and print out the letter according to its code.
  • An if statement is used to test if the time elapsed between two successive presses of a button is longer than the set letterGap AND if the morseInput is not empty. If these conditions are both true, the morseInput code for a letter is now passed as an argument to the translateCode() function. This function utilizes an if statement to search through the entire morseCode[] char array to see whether any matches the code. When it does find the matching code, the function returns the corresponding letter from the same index in the alphabet[] array.
  • The message adds the letter translated from the function above, and is printed on the serial monitor.

Step 10: Code Part 1 and 2 Complete

Part 1:

int RedLED=8;
int BlueLED=9;
int buzzPin=7;
int toneVal=1;
int ditLength;
int dahLength;
int spaceLength;

void setup() {
  Serial.begin(9600);
  pinMode(buzzPin,OUTPUT);
  pinMode(RedLED,OUTPUT);
  pinMode(BlueLED,OUTPUT);
}

void loop() {
  Serial.println("Choose a speed: ");
  char speed_choice = getMenuChoice();
  Serial.println(speed_choice);

  setSpeed(speed_choice);
  
  String input_string;
  Serial.println("Enter a Word or Phrase: ");  //Prompt User for Word to Translate into Morse Code
  while (Serial.available()==0){
  }
  
  input_string=Serial.readString();  //Store it in a String variable
  
  int str_len=input_string.length()+1;  //Calculate number of letter = length of array
  
  char buf[30];


  input_string.toCharArray(buf, str_len);   //Converts string input into a char array
  Serial.println(buf);


  for (int x=0; x<str_len-1; x++){
      char letter = tolower(buf[x]);
      Serial.println(letter);
      translateLetter(letter);   //translates each letter in the char array into buzzer sound and LED blinks
      
      delay(800);
  }
}


char getMenuChoice(){
  Serial.println("A. 20 WPM  B. 35 WPM  C. 50 WPM");
  while(Serial.available()==0){
    
  }
  char choice=Serial.read();


  return choice;
}


void setSpeed(char c){
  switch(c){
    case 'A':
      ditLength=60;
      break;
    case 'B':
      ditLength=34;
      break;
    case 'C':
      ditLength=24;
      break;
  }
  dahLength=ditLength*3;
  spaceLength=ditLength*7;
}


void translateLetter(char l){
  switch(l){
        case 'a':
          ditSound(1);  //The dit sound last for a set number of milliseconds,
          dahSound(1);  //the dah sound lasts for 3 times as long as a dit
          break;        //LED blinks are of same duration and simultaneous as the dit and dah sounds
        case 'b':
          dahSound(1);
          ditSound(3);
          break;
        case 'c':
          dahSound(1);
          ditSound(1);
          dahSound(1);
          ditSound(1);
          break;
        case 'd':
          dahSound(1);
          ditSound(2);
          break;
        case 'e':
          ditSound(1);
          break;
        case 'f':
          ditSound(2);
          dahSound(1);
          ditSound(1);
          break;
        case 'g':
          dahSound(2);
          ditSound(1);
          break;
        case 'h':
          ditSound(4);
          break;
        case 'i':
          ditSound(2);
          break;
        case 'j':
          ditSound(1);
          dahSound(3);
          break;
        case 'k':
          dahSound(1);
          ditSound(1);
          dahSound(1);
          break;
        case 'l':
          ditSound(1);
          dahSound(1);
          ditSound(2);
          break;
        case 'm':
          dahSound(2);
          break;
        case 'n':
          dahSound(1);
          ditSound(1);
          break;
        case 'o':
          dahSound(3);
          break;
        case 'p':
          ditSound(1);
          dahSound(2);
          ditSound(1);
          break;
        case 'q':
          dahSound(2);
          ditSound(1);
          dahSound(1);
          break;
        case 'r':
          ditSound(1);
          dahSound(1);
          ditSound(1);
          break;
        case 's':
          ditSound(3);
          break;
        case 't':
          dahSound(1);
          break;
        case 'u':
          ditSound(2);
          dahSound(1);
          break;
        case 'v':
          ditSound(3);
          dahSound(1);
          break;
        case 'w':
          ditSound(1);
          dahSound(2);
          break;
        case 'x':
          dahSound(1);
          ditSound(2);
          dahSound(1);
          break;
        case 'y':
          dahSound(1);
          ditSound(1);
          dahSound(2);
          break;
        case 'z':
          dahSound(2);
          ditSound(2);
          break;
        case '1':
          ditSound(1);
          dahSound(4);
          break;
        case '2':
          ditSound(2);
          dahSound(3);
          break;
        case '3':
          ditSound(3);
          dahSound(2);
          break;
        case '4':
          ditSound(4);
          dahSound(1);
          break;
        case '5':
          ditSound(5);
          break;
        case '6':
          dahSound(1);
          ditSound(4);
          break;
        case '7':
          dahSound(2);
          ditSound(3);
          break;
        case '8':
          dahSound(3);
          ditSound(2);
          break;
        case '9':
          dahSound(4);
          ditSound(1);
          break;
        case '0':
          dahSound(5);
          break;
        case ' ':
          delay(spaceLength);
          break;
        default:
          Serial.println("Invalid Letter");
          break;
      }
}
 
void ditSound(int times){
  for(int k=0; k<times; k++){
    digitalWrite(RedLED,HIGH);
    delay(ditLength);
    for(int x=0; x<ditLength; x++){
      digitalWrite(buzzPin,HIGH);
      delay(toneVal);
      digitalWrite(buzzPin,LOW);
      delay(toneVal);
      }
    digitalWrite(RedLED,LOW);
  delay(dahLength);
  }
}


//a dah sound should be 3 times longer than the duration of a dit
void dahSound(int times){
  for(int k=0; k<times; k++){
    digitalWrite(BlueLED,HIGH);
    delay(dahLength);
    for(int x=0; x<dahLength; x++){
      digitalWrite(buzzPin,HIGH);
      delay(toneVal);
      digitalWrite(buzzPin,LOW);
      delay(toneVal);
      }
    digitalWrite(BlueLED,LOW);
  delay(dahLength);
  }
}

Part 2:

int dotPin = 11; 
int dashPin = 10; 
int buzzPin=7;
int redLED=8;
int blueLED=9;


String morseInput = "";   
String message = "";     


int letterGap = 500;     
int wordGap = 1000;  


long lastInputTime = 0;  


int ditLength=40;
int dahLength=120;
int toneVal=1;


void setup() {
  Serial.begin(9600);
  pinMode(dotPin, INPUT_PULLUP);
  pinMode(dashPin, INPUT_PULLUP);
  pinMode(redLED,OUTPUT);
  pinMode(blueLED,OUTPUT);
  pinMode(buzzPin,OUTPUT);
}


void loop() {
  int dotButtonState = digitalRead(dotPin);
  int dashButtonState = digitalRead(dashPin);
  long currentTime = millis();


  if (dotButtonState == LOW || dashButtonState == LOW) {
  
    if (currentTime - lastInputTime > wordGap) {
      
      message += " ";
    }
    lastInputTime = currentTime;


    if (dotButtonState == LOW) {
      digitalWrite(redLED,HIGH);
      delay(ditLength);
      digitalWrite(redLED,LOW);


      buzzerSound(ditLength);
      while (digitalRead(dotPin) == LOW) {}
        morseInput += ".";
    }


    if (dashButtonState == LOW) {
      digitalWrite(blueLED,HIGH);
      delay(dahLength);
      digitalWrite(blueLED,LOW);


      buzzerSound(dahLength);
      while (digitalRead(dashPin) == LOW) {}
      morseInput += "-";
    }
  }


  if (currentTime - lastInputTime > letterGap && morseInput != "") {
    char letter = translateCode(morseInput);


    message += letter;
    morseInput = "";
  }


  if (message != "") {
    Serial.print(message);


    message = ""; 
  }
}


char translateCode(String code) {
  
  const char* morseCode[] = {
    ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
    "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..",
    "-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----.",
    ".-.-.-"};
  
  
  const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.";
  
  for (int i = 0; i < 37; i++) { 
    if (code == morseCode[i]) {
      return alphabet[i];
    }
  }
}


void buzzerSound(int buzzT){
  for (int x=0; x<buzzT; x++){
    digitalWrite(buzzPin,HIGH);
    delay(toneVal);
    digitalWrite(buzzPin,LOW);
    delay(toneVal);
  }
}

In the second part of the program, I initially had trouble keeping the correct pace with transmitting the dots and dashes on the two pushbuttons. Nevertheless, with practice and repetition of some frequently used letters and phrases, I began to get better at it. With practice, I can now transmit two words with the correct spacing. Not only has this project helped me learn more about Arduino programming, it also helped me memorize some morse code that I've been learning through the course in Udemy.

Thank you for reading my Instructable!