LCD freezes but Program Continues after some hours of running

Hello, I am using LCD, Relay, Stepper motors and AC motora for my automation project. Everything runs fine but after 2 hours of running LCD freezes But Program Continues.

#include <Wire.h>
#include <Keypad.h>
#include <hd44780.h>
#include <hd44780ioClass/hd44780_I2Cexp.h>

hd44780_I2Cexp lcd;

//LiquidCrystal_I2C lcd(0x27,16,2);

// Keypad layout
char keys[5][4] = {
  {'F','f','#','*'},
  {'1','2','3','U'},
  {'4','5','6','D'},
  {'7','8','9','C'},
  {'L','0','R','N'}
};
byte rowPins[5] = {22,24,26,28,30};
byte colPins[4] = {38,36,34,32};
Keypad keypad(makeKeymap(keys), rowPins, colPins, 5, 4);

// Motor pins
const int m2P=A0, m2D=A1, m2E=A2;
const int m1P=11, m1D=12, m1E=13;
const int relayPin = 8;


// Stepper settings
const long stepsPerRev = 200;
const unsigned long stepDelayM1 = (60*1000000/(stepsPerRev*4*200));
const unsigned long stepDelayM2 = (60*1000000/(stepsPerRev*4*600));

// For F1 flow
char lastKey = 0;

void setup(){
    Wire.begin();
    Wire.setClock(50000);
    lcd.begin(16,2);
lcd.backlight();
  //lcd.init(); lcd.backlight();

  pinMode(m2P,OUTPUT); pinMode(m2D,OUTPUT); pinMode(m2E,OUTPUT);
  pinMode(m1P,OUTPUT); pinMode(m1D,OUTPUT); pinMode(m1E,OUTPUT);
  pinMode(A3, OUTPUT);
    pinMode(relayPin, OUTPUT);
    

  digitalWrite(m2E,HIGH);
  digitalWrite(m1E,HIGH);
    digitalWrite(relayPin,HIGH);

  showMainMenu();
}

void loop(){
  char k = keypad.getKey();
  if(!k) return;

  if(k == 'F') f1Flow();
  else if(k == 'f') manualMode();
}

void showMainMenu(){
  lcd.clear();
  lcd.setCursor(0,0); lcd.print("F1 : New Bamboo");
  lcd.setCursor(0,1); lcd.print("F2 : Manual");
}

// ---------------- MANUAL MODE ----------------
void manualMode() {
    bool relayState = false;
  lcd.clear();
lcd.setCursor(0,0);
lcd.print("Manual Mode");
lcd.setCursor(0,1);
lcd.print("Belt OFF");
        

  while (true) {
    keypad.getKeys();

    for (int i = 0; i < LIST_MAX; i++) {
      if (keypad.key[i].kchar != NO_KEY) {
        char k = keypad.key[i].kchar;
        KeyState state = keypad.key[i].kstate;

        if (state == PRESSED) {

 
  // Toggle relay with *
if (k == '*') {

    relayState = !relayState;

    digitalWrite(relayPin, relayState ? LOW : HIGH);

    // Update only second line
    lcd.setCursor(0,1);

    if(relayState){
      lcd.print("Belt ON ");
    }
   else{
      lcd.print("Belt OFF");
    }
  delay(300);
 }

  // Motor controls
  else {
    runStepFor(k);
  }
}

else if (state == HOLD) {
  runStepFor(k);
}
          else if (state == RELEASED) {
          digitalWrite(m1E, HIGH);
          digitalWrite(m2E, HIGH);

          if (k == 'N' || k == 'C') {

  // Relay OFF when exiting manual mode
  digitalWrite(relayPin, HIGH);
  relayState = false;

  showMainMenu();
  return;
          }
        }
      }
    }
  }
}

void runStepFor(char key) {
  if (key == 'U') {
    digitalWrite(m2E, LOW);
    runMotor(m2P, m2D, 1, true, stepDelayM2);
  } 
  else if (key == 'D') {
    digitalWrite(m2E, LOW);
    runMotor(m2P, m2D, 1, false, stepDelayM2);
  } 
  else if (key == 'L') {
    digitalWrite(m1E, LOW);
    runMotor(m1P, m1D, 1, true, stepDelayM2);
  } 
  else if (key == 'R') {
    digitalWrite(m1E, LOW);
    runMotor(m1P, m1D, 1, false, stepDelayM2);
  }
}

// ---------------- F1 FLOW ----------------
void f1Flow(){
  long length;
  int stage;

  // Step 1: Length
  lcd.clear(); lcd.print("Enter length:");
  length = promptNumber();
  if(lastKey=='C'){ showMainMenu(); return; }

  // Step 2: Stage
  while(true){
    lcd.clear(); lcd.print("Wear stage 1-10:");
    stage = promptNumber();

    if(lastKey=='C'){
      lcd.clear(); lcd.print("Enter length:");
      length = promptNumber();
      if(lastKey=='C'){ showMainMenu(); return; }
      continue;
    }

    if(stage>=1 && stage<=10) break;

    lcd.clear(); lcd.print("Invalid stage");
    delay(1000);
  }

  // Confirmation
  lcd.clear();
  lcd.print("Len:"); lcd.print(length);

  while(true){
    lcd.setCursor(0,1);
    lcd.print("Stg:"); lcd.print(stage);

    char k = keypad.getKey();
    if(!k) continue;

    if(k=='C'){
      stage = promptNumber();
      if(lastKey=='C'){ showMainMenu(); return; }
      continue;
    }
    if(k=='N') break;
  }

  digitalWrite(m2E,LOW);
  digitalWrite(m1E,LOW);

  lcd.clear(); lcd.print("In Progress");

  int m2r1 = getStageRevs(stage);
  int m2r2 = getStage2Revs(stage);
  int m1r  = getM1Revs(stage, length);

    digitalWrite(relayPin, LOW);
     runMotor(m2P, m2D, m2r1, false, stepDelayM2);
  runMotor(m1P, m1D, m1r, true, stepDelayM1);

  delay(5000);

  runMotor(m1P, m1D, m1r, false, stepDelayM1);
    delay(2000);
    digitalWrite(relayPin, HIGH);
  runMotor(m2P, m2D, m2r2, true, stepDelayM2);

  lcd.clear(); lcd.print("Done.");
  delay(1000);

  digitalWrite(m2E,HIGH);
  digitalWrite(m1E,HIGH);

  // buzzer
  digitalWrite(A3,HIGH); delay(50);
  digitalWrite(A3,LOW);  delay(50);
  digitalWrite(A3,HIGH); delay(50);
  digitalWrite(A3,LOW);  delay(50);
  digitalWrite(A3,HIGH); delay(500);
  digitalWrite(A3,LOW);

  showMainMenu();
}

// ---------------- INPUT WITH BACKSPACE ----------------
long promptNumber(){
  String input = "";

  lcd.setCursor(0,1);
  lcd.print("                ");

  while(true){
    char k = keypad.getKey();
    if(!k) continue;

    lastKey = k;

    if(isdigit(k)){
      input += k;
    }
    else if(k == 'L'){  // ✅ Backspace
      if(input.length() > 0){
        input.remove(input.length() - 1);
      }
    }
    else if(k == 'N'){  // Confirm
      if(input.length() > 0){
        return input.toInt();
      }
    }
    else if(k == 'C'){  // Cancel
      return 0;
    }

    // Refresh display
    lcd.setCursor(0,1);
    lcd.print("                ");
    lcd.setCursor(0,1);
    lcd.print(input);
  }
}

// ---------------- STAGE LOGIC ----------------
int getStageRevs(int s){
  if(s==1) return (4*4);
  if(s==2) return (5*4);
  if(s==3) return (7*4);
  if(s==4) return (9*4);
  if(s>=5 && s<=7) return (11*4);
  return (12*4);
}

int getStage2Revs(int s){
  if(s==1) return (24*4);
  if(s==2) return (25*4);
  if(s==3) return (27*4);
  if(s==4) return (29*4);
  if(s>=5 && s<=7) return (31*4);
  return (32*4);
}

int getM1Revs(int stage, long length){
  if(stage >= 1 && stage <= 3){
    return (928 - length);
  }
  else if(stage >= 4 && stage <= 10){
    return (926 - length);
  }
  return (923 - length);
}

// ---------------- MOTOR RUN ----------------
void runMotor(int P, int D, long revs, bool fwd, unsigned long stepDelay) {
  digitalWrite(D, fwd ? HIGH : LOW);

  long steps = revs * stepsPerRev;

  for (long i = 0; i < steps; i++) {
    digitalWrite(P, HIGH);
    delayMicroseconds(stepDelay);
    digitalWrite(P, LOW);
    delayMicroseconds(stepDelay);
  }
}

This problem appears to be caused by electrical noise.
Post the schematic of your project so we can analyze it more clearly.

When using large, literal, constants, add the "UL" suffix (for unsigned long) so the compiler uses UL math and not int math... like this...

const unsigned long stepDelayM1 = (60*1000000UL/(stepsPerRev*4*200));
const unsigned long stepDelayM2 = (60*1000000UL/(stepsPerRev*4*600));

But... with all those energy-consuming devices... Post #2 sounds right.

Welcome
I have seen this many times, the internal logic of the PCF8574 gets trashed and it does not recover. This would be indicated by SDA being held low. Power cycling or reset only the ESP32 won't fix it. The only sure way to fix it is to power cycle the display PCF8574.

If you have something like this in one of the libraries it could be part of your problem, it will never return if SDA gets stuck low.
Wire.requestFrom(addr, 1);
while(Wire.available() == 0) { }
It is important you have stiff pull ups especially with motors etc around. Additional things to check for:

  • wires are long
  • pullups are weak
  • noise spikes occur
  • LCD backpacks are poor quality

When it gets stuck you can try calling this:

#include <Wire.h>
#define SDA_PIN 21
#define SCL_PIN 22

void recoverI2CBus()
{
    Wire.end();

    pinMode(SDA_PIN, INPUT_PULLUP);
    pinMode(SCL_PIN, INPUT_PULLUP);

    delay(10);

    // If SDA LOW, slave may be stuck
    if (digitalRead(SDA_PIN) == LOW)
    {
        pinMode(SCL_PIN, OUTPUT);

        // Clock out 16 pulses
        for (int i = 0; i < 16; i++)
        {
            digitalWrite(SCL_PIN, HIGH);
            delayMicroseconds(10);

            digitalWrite(SCL_PIN, LOW);
            delayMicroseconds(10);
        }

        // Generate STOP condition
        pinMode(SDA_PIN, OUTPUT);

        digitalWrite(SDA_PIN, LOW);
        delayMicroseconds(10);

        digitalWrite(SCL_PIN, HIGH);
        delayMicroseconds(10);

        digitalWrite(SDA_PIN, HIGH);
        delayMicroseconds(10);
    }

    // Restart I2C
    Wire.begin(SDA_PIN, SCL_PIN);

    Wire.setClock(100000);

    Wire.setTimeOut(50);

    Serial.println("I2C recovery complete");
}

You can change your setup and put this in:

Wire.begin(21, 22);
Wire.setClock(50000);
Wire.setTimeOut(50);

Let us know how you do:

How do we know a ESP32 is being used?
Did I miss something?

In the many years I have been playing with Arduino I2C and the PCF8574 based backpacks, I have not seen the stuck signal you are reporting happen.
Maybe just luck?

What I have seen on AVR systems is the Wire library hanging down in the low level twi code when it gets confused from noise on the i2c signals and waits for an event that isn't going to happen.
This is on the older AVR Wire library and on the newer AVR Wire library when i2c timeouts are not explicitly enabled by the sketch.

Another very common issue I see quite often is that power issues or noise on bus signals causes an i2c transaction to the PCF8574 to get lost or the data gets corrupted and which often corrupts the hd44780 LCD E signal handling which causes the Host and the hd44780 LCD to get out of nibble sync. The only recovery from this is to re run LCD initialization code.
(i.e. call lcd.begin() again)

@jaydip111
What version of IDE are you using, and what core are you using.
i.e. AVR, ESP32, ARM, chipkit, etc...

It would be interesting to run the I2CexpDiag sketch to see if catches any issues.

Also, I see you calling Wire.setClock(50000);
which looks like an attempt to slow down the clock.
Perhaps as an attempt to work around some noise issues?

Keep in mind that the hd44780_I2Cexp i/o class calls Wire.begin() in its begin() function. So calling lcd.begin() after Wire.SetClock() may undo the setClock() affect depending on which core you are using.
For example, on the AVR platform, the call to Wire.begin() down in lcd.begin() will set the i2c clock back to the default rate.

--- bill

Yes that was tried by the OP:

void setup(){
    Wire.begin();
    Wire.setClock(50000);
    lcd.begin(16,2);

It is a good try.

I mistyped ESP32, I meant processor, sorry about that. I have seen the hangups without any display on the system. It usually can be cleared up by having a very clean power supply and environment.

I have had the PCF8574 hang up on systems without a display. For me, it has been noise getting into the internal logic of the PCF8574 via the I2C bus, as you indicated. On my system there was nothing on the other side to initialize. I had one that only hung in the summer; it turned out it only happened during bad lightning storms.

But like I said, that won't work on an AVR and some other platforms because the call to lcd.begin() in most lcd libraries (for sure in the hd44780_I2Cexp i/o class) will reset the clock rate back to its default. This is because lcd.begin() calls Wire.begin() and on the AVR platform (and some others) the call to Wire.begin() resets the clock rate back to its default. So that is why if you want to set the i2c clock, you have to call setClock() after you call lcd.begin().

+----------------+
| Arduino Mega |
+-------+--------+
|
+---------------+------------------+
| | |
| | |
Keypad LCD I2C Buzzer
| | |
| | |
v v v

TB6600 #1 ------------------> X-Axis Stepper Motor
(M1)

TB6600 #2 ------------------> Y-Axis Stepper Motor
(M2)

Relay Module ---------------> Belt Grinder and Synchronous AC Motor

For Noise i have added Snubber Circuit at AC Load Side and I2C module also have pull up resistors

I also tried UL suffix, Not working

Well it's quite noisy bcs im using Relay to On/Off Angle Grinder and one other AC synchronous motor.
AVR board is Arduino Mega CH340
IDE version - Arduino Droid (App on android)

Only LCD freezes But program still runs. It will be fine if turn it off for 5 minutes or so.

Did you try to put a small delay after lcd.clear() and lcd.setCursor() ?
50mS or so.

If it really needs 5 minutes, it sounds like heat related problem. Overheating, loose wire connector, bad soldering...

I would like to see a picture, you are using a lot of noise generating devices and have an apparent heat problem. If you can post links to the various items you are controlling. Along with that post an annotated schematic, frizies do not count. Be sure to show power sources.