Morse Code Translator unexpectedly printing new line to LCD, and other issues

Hi, I am trying to build a Morse Code translator at home that will print to an LCD screen the English translation of the Morse Code imputed through a pushbutton. The code I developed functions okay, but I have a few unexpected bugs. First, if there is a long duration between button inputs while the attached program is running, the LCD will appear to move the cursor down one row, despite there being nothing in the code intended to move the cursor outside of setup. Second, the program will print nothing to the LCD unless a character is printed to the LCD during setup. This issue does not occur when printing to the serial monitor. I am not sure why these issues are occuring and would like help resolving them. Thanks for your time, Eli.

The hardware I am using is a Chinese Arduino clone (the Elegoo Uno R3), a LCD1602 Module Hitachi-compatible LCD display (Sorry I can't get more specific, no information other than that was included in the starter kit I am using), and two-pin pushbutton.

Schematic is attached below

Code is included below

// true if the button was down when we last checked
boolean last_button_state = false;
// include the library code:
int count = 0;
unsigned long starttime = 0;
unsigned long endtime = 0;
float totaltime = 0;
const int dot = 1000;
const int dash = 3000;
const int lspace = dot * 2;
const int wspace = dot * 6;
const float tolerance = 0.2;
String letter = "";
//Keeping track of sequence of dots and dashes is being typed
const String morse_code_table[26] = {
  /* A */ ".-",
  /* B */ "-...",
  /* C */ "-.-.",
  /* D */ "-..",
  /* E */ ".",
  /* F */ "..-.",
  /* G */ "--.",
  /* H */ "....",
  /* I */ "..",
  /* J */ ".---",
  /* K */ "-.-",
  /* L */ ".-..",
  /* M */ "--",
  /* N */ "-.",
  /* O */ "---",
  /* P */ ".--.",
  /* Q */ "--.-",
  /* R */ ".-.",
  /* S */ "...",
  /* T */ "-",
  /* U */ "..-",
  /* V */ "...-",
  /* W */ ".--",
  /* X */ "-..-",
  /* Y */ "-.--",
  /* Z */ "--..",
};

#include <LiquidCrystal.h>

// initialize the library by associating any needed LCD interface pin
// with the arduino pin number it is connected to
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
// set up the LCD's number of columns and rows:

const int kButtonPin = 10;
// Pin that is being connected to the button for Morse imput.

char lettercheck(String letter) {
  for (int i = 0; i < 26; ++i) {
    if (letter == morse_code_table[i]) {
      return 'A' + i;
    }
  }
}
void setup() {
  // put your setup code here, to run once:
  pinMode(kButtonPin, INPUT_PULLUP);
  Serial.begin(9600);
  lcd.begin(16, 2);
  lcd.setCursor(15, 0);
  //I'm not sure why the lcdprint needs to be there, but without it nothing prints when the button is pressed.
  lcd.print(" ");
  lcd.autoscroll();
}
void loop() {
  bool new_button_state = (digitalRead(kButtonPin) == LOW);
  unsigned long now = millis();
  if ((last_button_state == false) && (new_button_state == true)) {
    // The button was not pressed the last time I iterated through this loop and it is now pressed.
    if ((now > (endtime + (lspace - (lspace * tolerance)))) && (now < (endtime + (wspace - (wspace * tolerance)))) && (endtime != 0)) {
      // If the current time is greater than the last time the button was pressed, plus the length of the space between letters
      // minus the space between letters times the tolerance variable, and less than the last time the button was pressed, plus the length of the space between words
      // minus the space between words times the tolerance variable and the button has been pressed more than once
      lcd.print(lettercheck(letter));
      // Compare the sequence of dashes and dots appended to letter with the morse code table, and print the result
      Serial.print(lettercheck(letter));
      // Print the result to the serial monitor for testing
      letter = "";
      // Reset the letter string keeping track of the dash-dot sequence
      count = 0;
    } else if ((now > (endtime + (wspace - (wspace * tolerance)))) && (endtime != 0)) {
      // If the current time is greater than the last time the button was pressed, plus the length of the space between words
      // minus the space between words times the tolerance variable and the button has been pressed more than once
      lcd.print(lettercheck(letter));
      Serial.print(lettercheck(letter));
      lcd.print(" ");
      Serial.print(" ");
      letter = "";
      count = 0;
    }
    last_button_state = true;
    starttime = now;
  } else if ((last_button_state == true) && (new_button_state == false)) {
    // The button was just released.
    last_button_state = false;
    endtime = now;
    totaltime = (endtime - starttime);
    if (totaltime <= (dot)) {
      //lcd.print("DOT");
      //lcd.println(count);
      letter += ".";
      count += 1;
      if (count >= 5) {
        count = 0;
        //lcd.print(letter);
        lcd.print(lettercheck(letter));
        Serial.print(lettercheck(letter));
        letter = "";
      }
    } else {
      //lcd.println("Dash");
      //lcd.println(count);
      letter += "-";
      count += 1;
      if (count >= 5) {
        count = 0;
        //lcd.print(letter);
        lcd.print(lettercheck(letter));
        Serial.print(lettercheck(letter));
        letter = "";
      }
    }
  }
  delay(15);
}```
1 Like

Well, you get points for that! Someone who respects the difference between a pin state and a boolean value...

    if ((now > (endtime + (lspace - (lspace * tolerance)))) && (now < (endtime + (wspace - (wspace * tolerance)))) && (endtime != 0)) {

Here you fell down. It's far too complex and makes the fatal mistake of using future time stamps.

Honestly, that was an edit suggested to me by a family member (and much more experienced programmer). I wouldn't have thought of that on my own. Thanks, though.

Before you do a single thing more, learn the correct way to use millis(). It's documented in the reference threads at the top of the forum.

You must store past value of millis() and subtract them from the current value of millis() to make it work without glitches...

So I should just be referencing milis() where I presently use the Now variable? That's originally what I was doing, but the same family member thought that might be causing problems in the code.

The endtime variable is just the past saved value of milis() when the button was last depressed. Also, I don't understand what you mean when you said the program makes the "fatal mistake of using future time stamps."

Also, can you link to where exactly in the documentation you're talking about Millis is referenced in several points up there.

There are several threads here about millis() timing and related subjects like state machines:
https://forum.arduino.cc/t/useful-links-check-here-for-reference-posts-tutorials/370268

No, it's a common practice to make a copy of millis() at the start of loop. You did that and called it Now. It's how you subsequently used it, that is incorrect.

What exactly is incorrect about my use of millis, and how could it potentially factor into the problems I experienced?

I'm looking at this fucntion

char lettercheck(String letter) {
  for (int i = 0; i < 26; ++i) {
    if (letter == morse_code_table[i]) {
      return 'A' + i;
    }
  }
}

and I wonder what gets returned if letter does not match any entry in the table.

a7

It prints a quadruple vertical bar in that case(I think that is because the function returns nothing). Although I should probably change it so it returns ?? instead. This might work

char lettercheck(String letter) {
  for (int i = 0; i < 26; ++i) {
     bool error = true;
    if (letter == morse_code_table[i]) {
      return 'A' + i;
    }
  }
  return '?';
}```

On most LCDs that is actually two characters, '||'. Denotes the value zero, as opposed to the character 0.


This is what it looks like, I meant to say horizontal bar. This is also an instance of the error I was talking about earlier.

Yep, that's an symbol for an otherwise unprintable character. Where are those coming from? Is it your keyswitch, or?
edit - sorry, dumb question, you're asking us! More shortly.

Thanks for taking on this project! I'm starting on a RTTY decoder.

Best 73 de KD0GY

You quoted the reference links that answer that question by explaining the correct way, in excruciating detail. I don't see how attempting to duplicate all that material here would be helpful.

I did already identify the problem of future time stamps... it would cause timing hiccups every time millis() rolls over from it's maximum value to zero again. That is also explained in the tutorials.

It would only happen every few weeks, but it's better to get things straight now, before your program grows bigger, and into more programs.

Also your program will morph to fit the incorrect design pattern, it will be difficult to correct later on...

These two statements require explanation, because it's not clear to me that the compounded if condition exactly matches the prose in the comment.

 if ((now > (endtime + (lspace - (lspace * tolerance)))) && (now < (endtime + (wspace - (wspace * tolerance)))) && (endtime != 0)) {
      // If the current time is greater than the last time the button was pressed, plus the length of the space between letters
      // minus the space between letters times the tolerance variable, and less than the last time the button was pressed, plus the length of the space between words
      // minus the space between words times the tolerance variable and the button has been pressed more than once
else if ((now > (endtime + (wspace - (wspace * tolerance)))) && (endtime != 0)) {
      // If the current time is greater than the last time the button was pressed, plus the length of the space between words
      // minus the space between words times the tolerance variable and the button has been pressed more than once

The concern with the complicated "forward looking" time calculation comes from the failure that results when an unsigned int, or unsigned long int, overflows, generally because they wrap around and become small in comparison to the expected value.
So if your code, when evaluated by the compiler, adds two unsigned or signed int values, and the result "doesn't fit", your calculation result is erroneous. For example, for an int, if 20000 is added to 12000, no problem, but if you instead add 13000, the end result will not be what you expect. Now, I'm not going to hand-work all the above calculations, because I'm not sure that you've got the expression correct to begin with, but you'd best work it through and ensure you're not encountering anything like what I've just described.
Just assigning the answer to a long unsigned int doesn't force the compiler to use long unsigned ints all the way through the calculation, so you really must check it over carefully. The time calculation concern expressed earlier is related to this, though it's more complicated.

Constructs like this are difficult to write, analyze and maintain, and often have hidden side effects:

    if ((now > (endtime + (lspace - (lspace * tolerance)))) && 
(now < (endtime + (wspace - (wspace * tolerance)))) && (endtime != 0)) {
...
    } else if ((now > (endtime + (wspace - (wspace * tolerance))))
 && (endtime != 0)) {

You should use code blocks to express some of those conditions. It is actually more intuitive because it reflects a "flow" rather than a set of states. In those statements above, the accumulation of conditions forces you to combine actions in the same execution blocks, which can easily obfuscate the semantics.

It enables accidental redundancy. For example did you notice that both the 'if' and the 'else' contain some of the same conditions? 'endtime !=0'. So you could enclose the if-else in one if statement that tests that.

Thanks so much for your help and time, everyone. Aarg, sorry I didn't do more research before commenting.