Arduino UNO Freezes after a few hours

I am having an issue where my project freezes after a few hours, roughly 6-8. I just changed how it gathers and sends data using the millis function. Before adding the millis function i could run the sketch for over 24 hours without a problem.

#include <LiquidCrystal.h>  // includes the LiquidCrystal Library

LiquidCrystal lcd(7, 8, 9, 10, 11, 12);  // Creates an LCD object. Parameters: (rs, enable, d4, d5, d6, d7)

#include "Adafruit_CCS811.h"

Adafruit_CCS811 ccs;

bool firstLoop = true;
bool fanOveride = false;
bool switched = false;
bool timerOn = false;
bool setupMode = false;
bool setupDone = false;
bool timerSetup = false;
bool setPointSetup = false;
double setPoint = 400;
int TVOC;
int CO2;
int highTvocCount = 0;
int setupSelect = 1;
int buttonCount = 0;
int multiply = 1;
int timerHrs = 0;
int dataTimerMin = 0;
int dataTimerHr = 0;
int dataTimerDay = 0;
int remainingTime;
unsigned long startTime;
unsigned long elapsedTime;
unsigned long dataTimerEnd;
unsigned long lcdDataTimer;
unsigned long timerDuration = 600;
unsigned long timerDurationMinusHrs = 0;



void setup() {
  lcd.begin(16, 2);  // Initializes the interface to the LCD screen, and specifies the dimensions (width and height) of the display }
  Serial.begin(9600);
  Serial.println("CCS811 test");

  if (!ccs.begin()) {

    Serial.println("Failed to start sensor! Please check your wiring.");
    lcd.setCursor(0, 0);
    lcd.print("check sensor");
    while (1)
      ;
  }

  // Wait for the sensor to be ready
  while (!ccs.available())
    ;

  //Start Up LCD Display
  lcd.setCursor(1, 0);
  lcd.print("Air Nobel Pro");
  delay(1000);  // 1 second delay
  lcd.setCursor(1, 1);
  lcd.print("VOC Controller");
  delay(2000);
  lcd.clear();  // Clears the display

  //Pin Mode Setup
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(4, INPUT_PULLUP);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);

  //CCS118 SETUP
  ccs.setDriveMode(1);
  ccs.setBaseline(54206);

  //Data Timer Setup
  dataTimerEnd = 0;
  lcdDataTimer = 0;
}


void loop() {
  // Read the button state
  int buttonState = digitalRead(2);

  if (buttonState == LOW) {  // Button is pressed
    if (!setupMode) {
      // Enter setup mode
      setupMode = true;
      Serial.println("Entering setup mode...");
      lcd.clear();
      lcd.print("Entering");
      lcd.setCursor(0, 1);
      lcd.print("Setup");
      delay(500);
      lcd.clear();

      // Perform setup mode actions here
    } else {
      // Exit setup mode
      setupMode = false;
      Serial.println("Exiting setup mode...");
      lcd.clear();
      setupDone = false;
    }

    // Add a delay or other debounce mechanism to prevent rapid toggling
    delay(500);  // Adjust as needed
  }

  // ========================================================================================
  //                           SETUP MODE START
  //=========================================================================================
  if (setupMode) {
    if (setupDone == true) {
    }
    if (setupDone == false) {
      while (setupSelect == 1 and setPointSetup == false and timerSetup == false) {
        lcd.setCursor(0, 0);
        lcd.print("Setpoint Setup");
        if (digitalRead(3) == LOW or digitalRead(4) == LOW) {
          setupSelect = 2;
          Serial.println("Mode Switched");
          lcd.clear();
          delay(500);
        }
        if (digitalRead(2) == LOW or buttonCount > 0) {
          setPointSetup = true;
          delay(500);
        }
      }

      while (setupSelect == 2 and setPointSetup == false and timerSetup == false) {
        lcd.setCursor(0, 0);
        lcd.print("Timer Setup");
        if (digitalRead(3) == LOW or digitalRead(4) == LOW) {
          Serial.println("Mode Switched");
          setupSelect = 1;
          lcd.clear();
          delay(500);
        }
        if (digitalRead(2) == LOW or buttonCount > 0) {
          timerSetup = true;
          Serial.println("Enter Timer Setup");
          delay(500);
        }
      }
    }

    //===========Set Point Setup START============================================
    while (setPointSetup == true) {



      //Print Set Point setup title
      lcd.setCursor(0, 0);
      lcd.print("Choose Set Point");

      //Multiply setup for holding button making indexing faster
      if (digitalRead(3) == HIGH and digitalRead(4) == HIGH) {
        buttonCount = 1;
        multiply = 1;
      }
      if (buttonCount > 7) {
        multiply = 5;
      }
      if (buttonCount > 15) {
        multiply = 10;
      }
      if (buttonCount > 25) {
        multiply = 50;
      }

      if (setPoint < 50) {
        multiply = 5;
        buttonCount = 1;
      }
      if (setPoint < 10) {
        multiply = 1;
        buttonCount = 1;
      }

      //Add to Set Point with "UP" button
      if (digitalRead(3) == LOW) {

        setPoint = setPoint + multiply;
        buttonCount = buttonCount + 1;
        delay(100);
        Serial.print("button Count: ");
        Serial.println(buttonCount);
      }

      //Remove from Set Point with "DOWN" button
      if (digitalRead(4) == LOW and setPoint > 0) {
        setPoint = setPoint - multiply;
        buttonCount = buttonCount + 1;
        delay(100);
        Serial.print("button Count: ");
        Serial.println(buttonCount);
      }

      //Print current Set Point
      lcd.setCursor(0, 1);

      if (setPoint < 1000) {
        lcd.print(" ");
        if (setPoint < 100) {
          lcd.print(" ");
          if (setPoint < 10) {
            lcd.print(" ");
          }
        }
      }
      lcd.print(setPoint, 0);
      delay(25);

      //Set Value by pressing set button
      if (digitalRead(2) == LOW) {
        setPointSetup = false;
        lcd.clear();
        lcd.print("Set Point Saved");
        lcd.setCursor(0, 1);
        lcd.print(setPoint, 0);
        delay(1500);
        lcd.clear();
        setupMode = false;
      }
    }
    //----------------Set Point Setup END---------------------------------



    //===============Timer Setup START====================================

    while (timerSetup == true) {
      lcd.setCursor(0, 0);
      lcd.print("Run Time");

      //Multiply count for holding button
      if (buttonCount > 5) {
        multiply = 5;
      }
      if (buttonCount > 10) {
        multiply = 30;
      }
      if (buttonCount > 15) {
        multiply = 60;
      }
      if (buttonCount > 20) {
        multiply = 300;
      }
      if (buttonCount > 25) {
        multiply = 600;
      }

      //Increase Timer Duration when "UP" button pushed
      if (digitalRead(3) == LOW) {

        timerDuration = (timerDuration + multiply);
        buttonCount = buttonCount + 1;
        Serial.print("button Count: ");
        Serial.println(buttonCount);
        Serial.print("multiply: ");
        Serial.println(multiply);
        delay(150);
      }

      //Decrease Timer Duration when "DOWN" button pushed
      if (digitalRead(4) == LOW and timerDuration > 0) {
        if (timerDuration < 1201 and buttonCount > 25) {
          multiply = 600;
        }
        if (timerDuration < 901 and buttonCount > 20) {
          multiply = 300;
        }
        if (timerDuration < 301 and buttonCount > 15) {
          multiply = 60;
        }
        if (timerDuration < 61 and buttonCount > 10) {
          multiply = 10;
        }
        if (timerDuration < 30 and buttonCount > 5) {
          multiply = 5;
        }
        if (timerDuration < 10) {
          multiply = 1;
        }
        timerDuration = (timerDuration - multiply);
        buttonCount = buttonCount + 1;
        Serial.print("button Count: ");
        Serial.println(buttonCount);
        Serial.print("multiply: ");
        Serial.println(multiply);
        delay(150);
      }

      //......Print Current Timer Duration............
      lcd.setCursor(0, 1);

      //Calcualte minutes
      double timerDurationMin = timerDuration / 60;


      //Calculate Seconds
      int timerDurationSec = timerDuration % 60;


      //Print Minutes
      if (timerDurationMin < 100) {
        lcd.print(" ");
        if (timerDurationMin < 10) {
          lcd.print(" ");
        }
      }
      lcd.print(timerDurationMin, 0);
      lcd.print("m ");

      //Print Seconds
      if (timerDurationSec < 10) {
        lcd.print(" ");
      }
      lcd.print(timerDurationSec);
      lcd.print("s");

      // Reset Button Count
      if (digitalRead(3) == HIGH and digitalRead(4) == HIGH) {
        buttonCount = 1;
        multiply = 1;
      }

      //Set Timer when "SET" button Pushed
      if (digitalRead(2) == LOW) {
        lcd.clear();
        lcd.print("Timer Set For");
        lcd.setCursor(0, 1);
        lcd.print(timerDuration / 60);
        lcd.print("m ");
        lcd.print(timerDurationSec);
        lcd.print("s");
        delay(1500);
        timerSetup = false;
        setupMode = false;
        lcd.clear();
      }
    }
    //----------------Timer Setup END--------------------------------------------

    delay(250);

    //----------------------------------------------------------------------------------------------
    //                       SETUP MODE END
    //----------------------------------------------------------------------------------------------


    //================================================================================================
    //                                          RUN MODE
    //================================================================================================
  } else {

    //============================Manual Fan START==================================================
    while (digitalRead(3) == LOW and buttonCount < 12) {
      buttonCount = buttonCount + 1;
      Serial.println(buttonCount);
      delay(100);
      while (buttonCount > 10 and digitalRead(3) == LOW) {
        if (switched == false) {
          switched = true;
          timerOn = !timerOn;

          if (timerOn == false) {
            lcd.clear();
            lcd.print("Timer OFF");
          }
          if (timerOn == true) {

            lcd.clear();
            lcd.print("Timer ON");
            startTime = millis();
            Serial.print("Start Time: ");
            Serial.println(startTime);
          }
        }
      }
    }


    //-----------------------Manual Fan END------------------------------------------------------------

    //============================ Fan Overide START==================================================
    while (digitalRead(4) == LOW and buttonCount < 12) {
      buttonCount = buttonCount + 1;
      Serial.println(buttonCount);
      delay(100);
      while (buttonCount > 10 and digitalRead(4) == LOW) {
        if (switched == false) {
          switched = true;
          fanOveride = !fanOveride;
          Serial.println("Overide");

          if (fanOveride == false) {
            lcd.clear();
            lcd.print("Overide OFF");
          }
          if (fanOveride == true) {

            lcd.clear();
            lcd.print("Overide ON");
            if (digitalRead(4) == HIGH) {
            }
          }
        }
      }
    }


    //----------------------- Fan Overide END------------------------------------------------------------

    if (digitalRead(3) == HIGH and digitalRead(4) == HIGH) {
      buttonCount = 0;
      switched = false;
    }
    //====================================================================================================================================================
    //===========PRINT MEASUREMENTS START=========================================================================================================================
    //====================================================================================================================================================

    if (ccs.available()) {
      if (!ccs.readData()) {

        if (lcdDataTimer < millis()) {
          lcdDataTimer = millis() + 1500;
          TVOC = ccs.getTVOC();
          CO2 = ccs.geteCO2();

          //Print TVOC-------------------------------------------------------------------------------
          lcd.setCursor(0, 0);
          lcd.print("TVOC:");
          if (TVOC < 1000) {
            lcd.print(" ");
            if (TVOC < 100) {
              lcd.print(" ");
              if (TVOC < 10) {
                lcd.print(" ");
              }
            }
          }
          lcd.print(TVOC);
          lcd.print("ppb");

          //Print CO2--------------------------------------------------------------------------------
          lcd.setCursor(0, 1);
          lcd.print("CO2: ");
          if (CO2 < 1000) {
            lcd.print(" ");
            if (CO2 < 100) {
              lcd.print(" ");
              if (CO2 < 10)
                lcd.print(" ");
            }
          }
          lcd.print(CO2);
          lcd.print("ppm");
        }

        //Serial Data------------------------------------------------------------------------------
        if (dataTimerEnd < millis()) {
          dataTimerEnd = millis() + 300000;

          if (firstLoop == false) {
            //Data Timer day:hour:minute SETUP
            dataTimerMin = dataTimerMin + 5;
            if (dataTimerMin == 60) {
              dataTimerMin = 0;
              dataTimerHr = dataTimerHr + 1;
              if (dataTimerHr == 24) {
                dataTimerHr = 0;
                dataTimerDay = dataTimerDay + 1;
              }
            }
          }

          //Print Recording Time
          if (dataTimerDay < 10) {
            Serial.print("0");
          }
          Serial.print(dataTimerDay);  //Print Day

          Serial.print(":");
          if (dataTimerHr < 10) {
            Serial.print("0");
          }
          Serial.print(dataTimerHr);  //Print Hour

          Serial.print(":");
          if (dataTimerMin < 10) {
            Serial.print("0");
          }
          Serial.print(dataTimerMin);  //Print Min


          //TVOC to Serial
          Serial.print("  TVOC: ");
          if (TVOC < 1000) {
            Serial.print(" ");
            if (TVOC < 100) {
              Serial.print(" ");
              if (TVOC < 10) {
                Serial.print(" ");
              }
            }
          }
          Serial.print(TVOC);  //Print TVOC

          //CO2 to Serial
          Serial.print("  CO2: ");
          if (CO2 < 1000) {
            Serial.print(" ");
            if (CO2 < 100) {
              Serial.print(" ");
              if (CO2 < 10) {
                Serial.print(" ");
              }
            }
          }
          Serial.print(CO2);  //Print CO2

          //Baseline to Serial
          Serial.print("  Baseline: ");
          Serial.println(ccs.getBaseline());  //Print Baseline
        }
        //-------------------------------------------------------------------------------------------------------------------------------------------------
        //---------PRINT MEASUREMENTS END--------------------------------------------------
        //-------------------------------------------------------------------------------------------------------------------------------------------------


        //====================Timer START====================================
        if (timerOn == true and fanOveride == false) {
          Serial.print("Start Time: ");
          Serial.print(startTime);

          //Calculate Minutes and Seconds
          elapsedTime = millis() - startTime;

          remainingTime = (timerDuration * 1000 - elapsedTime) / 1000;

          int minutes = remainingTime / 60;
          int seconds = remainingTime % 60;
          lcd.setCursor(0, 1);
          lcd.print("Shut Off ");

          //Print Timer Minutes
          if (minutes < 10) {
            lcd.print("0");
          }
          lcd.print(minutes);
          lcd.print(":");

          //Print Timer Seconds
          if (seconds < 10) {
            lcd.print("0");
          }
          lcd.print(seconds);
          if (remainingTime < 0) {
            lcd.clear();
            timerOn = false;
          }
        }
        //-------------------------Timer END-----------------------------------------------------------


        delay(500);


        //===================Fan Control START=========================================================

        //Fan Overide to turn fan off
        if (fanOveride == true) {
          digitalWrite(5, LOW);
        }

        //Turn fan on if reading high or timer is on
        if ((ccs.getTVOC() > setPoint || timerOn == true) and digitalRead(5) != HIGH and fanOveride == false) {
          digitalWrite(5, HIGH);
          startTime = millis();
          timerOn = true;
        }

        // Reset Timer if Reading is still high
        if (ccs.getTVOC() > setPoint and timerOn == false) {
          startTime = millis();
          timerOn = true;
        }

        //Turn fan off when timer is done and reading is safe
        if (ccs.getTVOC() < setPoint and timerOn == false and digitalRead(5) != LOW) {
          digitalWrite(5, LOW);
        }

        //Print messages
        if (highTvocCount > 9) {
          highTvocCount = 0;
        }
        highTvocCount = highTvocCount + 1;

        if (fanOveride == true) {
          if (ccs.getTVOC() < setPoint) {
            if (highTvocCount > 7 and highTvocCount < 9) {
              lcd.setCursor(0, 1);
              lcd.print("Overide On    ");
              delay(1000);
              lcd.clear();
            }
          }
        }

        if (ccs.getTVOC() > setPoint and highTvocCount > 6 and highTvocCount < 8) {

          lcd.setCursor(0, 1);
          lcd.print("TVOC TOO HIGH   ");
          delay(1000);
          lcd.clear();
        }

        digitalWrite(6, digitalRead(5));
        //------------------Fan Control END--------------------------------------------------------------

      } else {
        lcd.clear();
        lcd.print("ERROR!");
        lcd.setCursor(0, 1);
        lcd.print("Sensor Connection");

        while (1)
          ;
      }
    }
    firstLoop = false;
  }
  //-----------------------------------------------------------------------------------------------------
  //                                 **RUN MODE END**
  //-----------------------------------------------------------------------------------------------------
}

At least, give us a hint! What is the last entry on your serial.Print()? Have you added more serial.Print() to the program to narrow down the problem?

Your code is a bit monolithic. And therefore hard to check.
You might want to read about functions...
Why is there delay() if your code uses millis() timers?

2 Likes

What else is connected to the Arduino. If there is post an annotated schematic showing exactly how it is wired. We also need to see how you are applying power to your Arduino.

Maybe this is the problem. If millis is near the end and you add 300000, then you overflow dataTimerEnd variable.
BTW, the code is not well structured, IMO, there are lot of while and if statements, and its very hard to follow.

You are not saying what kind of Arduino you are working with, but I may say that if timerDuration is greater than 32 this operation will fail.

I saw some other strange things regarding data types, as using double

Well found!

Hint to OP:
Do a subtraction.

if (millis() - lastMillis > somePeriod) {
   Blablabla;
}

That way you will not have rollover problems (assuming that you use unsigned long integer types).

No. millis() will not get "near the end" for over 40 days!

No. timerDuration is unsigned long, which is 32-bits. Plenty of headrooom.

That's correct, my bad.

What is while(1); for at the end of the code?
Very hard to follow with so much nested if statements.

read what gets printed to the serial monitor

image

I wish i would have saved it but is said something like 00:08:45 TVOC: 52 CO2: 742 Baseline: 26557

I found one delay that wasn't needed, as far as i know. The other delays are to keep the buttons from going crazy when pressed or to display a message long enough to be read.

Ok, that is a beginning. Add a serial.Print() message after whatever you do after that message and see if the new debug message is displayed. Advance one step at a time!


Right now I am running it without the fan hooked up and so far the measurements haven't gone high enough to trigger the relay. This drawing is a bit old I don't know what i did with the recent one without resistors on the buttons.

Great tip thank you, does the rollover set millis to 0? If that is the case when it rolls over wouldn't this stay false until near the end of the rollover again since the answer would be a negative number?

Those are all unsigned long values. They CANNOT be negative. That is precisely why it is critical to use millis() in that way, subtracting the saved value from the current value, so the result is aways correct even when millis() rolls over to zero every 40-some days.

Why is multiply added?
The name suggests multiplication...
In that case a better name would be factor.
Multiple occasions...

because that number gets starts as 1 and goes up. I guess increment would be the better. Though I don't think this is the cause of my sketch freezing. Thanks for pointing it out though.

It will indeed not solve your problem.
But naming of variables is important to produce understandable code.
If your code points in two directions (adding multiply), the reader of your code has to guess what you really meant.
So indeed rename that variable to 'increment'.

You should not need any delay() if your sketch is controlled by mills().
In this case serious reconstruction would be needed to get rid of all delay() calls.
You would need to use a state machine (google). @StefanL38 has some good tutorials on this subject (find it somewhere on this forum).
But I strongly recommend to first move bits of your program into functions...
That will also make easier to see how the state machine needs to be done...