2

I am new to Arduino and I am trying to create a Morse code based "chat".

I can submit a message in Morse code using the send button (the one on the right,) and when I press the button on the left and the whole decoded message should appear on the screen. However, this is not happening unless I press the send button immediately after introducing the Morse code. Can anyone help me out? I tried changing the delay period, but nothing seems to make it send how it should.

As I said, I am a beginner, so please go easy on me.

This is my code:

/* String tone = "-... ..- -. .- ";
    

  Morse code - Receiver

  modified on 14 Apr 2019
  by Saeed Hosseini @ Electropeak
  https://electropeak.com/learn

*/
#include <LiquidCrystal.h>
// Create an LCD object. Parameters: (RS, E, D4, D5, D6, D7):
LiquidCrystal lcd = LiquidCrystal(2, 3, 4, 5, 6, 7);
String code = "";
int len = 0;
char ch;
int ok;
char new_char;
int i1=0;
int i2=1;
const int but_send=8;
const int but = 12;
const int led = 13;
String a="";
unsigned long pres_len = 0, rel_time, pres_time = 0, old_time_len = 0, old_pres = 0, space = 0;
int state = 0;
int unit_delay = 250;
int min_delay = 10;
int buttonState = 0;

char MakeString()
{
  if (pres_len < (unit_delay*3) && pres_len > 50)
  {
    return '.';                        //if button press less than 0.6sec, it is a dot
  }
  else if (pres_len > (unit_delay*3))
  {
    return '-';                        //if button press more than 0.6sec, it is a dash
  }
}

void Morse_decod()
{
  static String morse[] = {".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....",
                             "..", ".---", "-.-", ".-..", "--", "-.", "---", ".--.", "--.-",
                             ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..", "!"
                            };
  int i = 0;
  while (morse[i] != "!")  
  {
    if (morse[i] == code)
    {
      Serial.print(char('A' + i));
      a=a+char('A' + i);
      //lcd.print(char('A' + i));
      Serial.print(" ");
      if(i2>=2)
      {
        i1++;
        i2=0;
      }
      else 
      {i2++;
      }
      break;
    }
    i++;
  }
  if (morse[i] == "!")
  {
    Serial.println("");
    Serial.println("This code is not exist!");
  }

  code = "";
}
void sendText()
{
  Serial.println(digitalRead(but_send));
  // check if the pushbutton is pressed.
  // if it is, the buttonState is HIGH:
  //if (digitalRead(but_send) == HIGH) {
    // turn LED on:
     //gitalWrite(led, HIGH);
  if(ok==1){
    Serial.println(a);}
  ok=0;
   
    //}
  //else {
    // turn LED off:
    //Serial.println("NIMIC");
  //}
 delay(200);
}
void setup() {
  Serial.begin(9600);
  pinMode(but, INPUT_PULLUP);
  pinMode(but_send, INPUT_PULLUP);
  pinMode(led, OUTPUT);
  lcd.setCursor(i1, 12);
  lcd.begin(16, 2);
  

}
void loop() {  
label:  
  while (digitalRead(but) == HIGH) {}
  old_pres = rel_time;
  pres_time = millis();
  digitalWrite(led, HIGH);
  while (digitalRead(but) == LOW) {}
  rel_time = millis();
  digitalWrite(led, LOW);
  pres_len = rel_time - pres_time;
  space = pres_time - old_pres;
  if (pres_len > min_delay)
  {
    code += MakeString();
  }
  while ((millis() - rel_time) < (unit_delay * 3))
  {
    
    if (digitalRead(but) == LOW)
    {
      goto label;
      
    }
    
  }

  Morse_decod();
    delay(1000);
  if (digitalRead(but_send) == LOW)
  {
    ok=1;
    //Serial.println("A intrat aici");
    sendText();
    //delay(100);

  }
 
  
 //Serial.println(digitalRead(but_send));


 
 
  
}

enter image description here

This is what should happen all the time, not only after pressing the send button very fast: enter image description here

If needed, I can send a link from my tinkercad simulation.

3 Answers 3

3

tavis’ answer nailed down your problem: your program is completely unresponsive when it is blocked in the first while loop of the loop() function. Here I will try to give you some hints on how to avoid this situation, but first some general comments:

if (pres_len < (unit_delay*3) && pres_len > 50)

The threshold should be unit_delay*2, maybe with a longer unit_delay. In Morse, a dot is one unit, and a dash is three units. If you have to set a threshold, it should be somewhere in the middle. Otherwise, a perfectly valid dash of 2.99 units would be mistaken for a dot.

Also, a function returning anything but void should always return a value. What is this function supposed to return if press_len is smaller or equal than 50? And if it is exactly equal to unit_delay*3? The best way to make sure you did not forget any case is to have an else at the end that returns a default value in case no condition matches:

else
    return '?';  // we could not understand

In this particular case, it seems to me like you already did the debounce prior to calling the function, so it could be simplified to

char getSymbol()
{
    if (pres_len < 2 * unit_delay)
        return '.';
    else
        return '-';
}

Note that giving functions meaningful names is an important step in making the code easy to understand and reason about.

if(i2>=2)
{
  i1++;
  i2=0;
}
else
{i2++;
}

All this serves no useful purpose, as you do not use i1 or i2 after setup(). Removing useless stuff is also important for the clarity of the code.

Serial.println("This code is not exist!");

It should say “I cannot understand this code!”, but it may well exist.

Now, to the core of the issue. If you want the program to be responsive, you should embrace non-blocking programming. This means getting rid of delay() (see Blink Without Delay) and getting rid of blocking loops, like the two while loops at the top of loop(). A very useful approach is to program your system as a finite state machine. Please, take the time to read and digest these two resources.

Next, as an example, I can point you to a Morse decoder of mine that is based on the finite state machine concept. You do not have have to look at my code, which may be too low level for your taste, but look at the documentation of the program internals. It explains how the state machines are built and interact with one another. Below is a state diagram taken from that documentation. It shows the state machine that takes the debounced “rise” and “fall” events as inputs, and generates “dot”, “dash”, “end-of-char” and “end-of-word” symbols as outputs:

state diagram of Morse decoder

2

It looks like the line : while (digitalRead(but) == HIGH) {} will capture the processor in a loop until "but" is pressed. So if you press the send button immediately after using "but" it might see that you pressed "send", but if you let the program loop get back to while (digitalRead(but) == HIGH) {}, then nothing can happen until "but" is pressed again.

1

Both tavis and Edgar Bonet have hit the nail on the head, and Edgar has provided you with an excellent tutorial.

You are unlikely to get this to work properly in the Tinkercad browser simulator because it runs at different speeds depending on the complexity of the code it is running. And the complexity varies when you press buttons, which throws the timing out as the Morse button is pressed.

However, I managed to get it roughly working in Tinkercad as follows.

Tinkercad Morse code

#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

const byte MORSE_BUTTON_PIN = 6;
const byte SEND_BUTTON_PIN = 7;
const byte MORSE_LED_PIN = 8;

const unsigned long DEBOUNCE_DELAY_ms = 50;
const unsigned long MORSE_UNIT_1 = 500;  // milliseconds.
const unsigned long MORSE_UNIT_2 = 2 * MORSE_UNIT_1;  // milliseconds.
const unsigned long MORSE_UNIT_5 = 5 * MORSE_UNIT_1;  // milliseconds.

const String MORSE_CODES[] =
{
  ".-", "-...", "-.-.", "-..", ".",
  "..-.", "--.", "....", "..", ".---",
  "-.-", ".-..", "--", "-.", "---",
  ".--.", "--.-", ".-.", "...", "-",
  "..-", "...-", ".--", "-..-", "-.--",
  "--.."
};

String symbol;
String message;

Debouncer morse_button(MORSE_BUTTON_PIN, DEBOUNCE_DELAY_ms);
Debouncer send_button(SEND_BUTTON_PIN, DEBOUNCE_DELAY_ms);

void setup()
{
  Serial.begin(115200);
  pinMode(MORSE_BUTTON_PIN, INPUT);
  pinMode(SEND_BUTTON_PIN, INPUT);
  pinMode(MORSE_LED_PIN, OUTPUT);
  // Set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  // Print a message to the LCD.
  lcd.print("Hello, world!");
}

void loop()
{
  static unsigned long press_begin_time = 0;
  static unsigned long release_begin_time = 0;
  static bool measure_the_gap = false;

  morse_button.Update();
  send_button.Update();

  if (morse_button.Fall())
  {
    // Remember when the morse button was pressed and
    // switch on the LED.
    int timestamp = millis();
    digitalWrite(MORSE_LED_PIN, HIGH);
    press_begin_time = timestamp;
  }

  if (morse_button.Rise())
  {
    // Remember when the morse button was released and
    // switch off the LED.
    int timestamp = millis();
    digitalWrite(MORSE_LED_PIN, LOW);
    release_begin_time = timestamp;
    int pressed_time = timestamp - press_begin_time;
    if (pressed_time > MORSE_UNIT_2)
    {
      // Dash.
      //Serial.print("-");
      symbol += "-";
    }
    else
    {
      // Dot.
      //Serial.print(".");
      symbol += ".";
    }
    measure_the_gap = symbol.length() > 0;
  }

  if (measure_the_gap)
  {
    unsigned long timestamp = millis();
    //Serial.println("Measuring the gap.");
    if (((timestamp - release_begin_time) > MORSE_UNIT_2))
    {
      // A long gap between presses signifies the end of a symbol.
      //Serial.println("Long gap");
      //Serial.println(symbol);
      measure_the_gap = false;
      message += DecodeSymbol(symbol);
      symbol = "";
    }
  }

  if (send_button.Fall())
  {
    // Print the Morse string.
    //Serial.println(message);
    lcd.clear();
    lcd.setCursor(0, 1);
    lcd.print(message);
    message = "";
  }

  // Delay for Tinkercad simulator.
  // Not needed for real hardware.
  delay(10);
}

char DecodeSymbol(String& symbol)
{
  //Serial.print(symbol);
  //Serial.print(": ");
  char ch = '?';
  for (int i = 0; i < 26; i++)
  {
    if (symbol == MORSE_CODES[i])
    {
      ch = 'A' + i;
      break;
    }
  }
  //Serial.println(ch);
  return ch;
}

For debouncing the buttons, I used this debouncer that I've put on GitHub. For Tinkercad, you will need to copy and paste that debouncer code (the debouncer.h file followed by the debouncer.cpp file) into the top of the Tnkercad code file because Tinkercad doesn't allow you to include your own code files.

1
  • +1, but note that: 1. timestamp and pressed_time should be unsigned long in order to avoid rollover issues, but then these variables are not really needed. 2. symbol.length() > 0 is always evaluated right after adding a character to symbol, and in this context it is always true. Commented May 13, 2021 at 12:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.