Timer Program Freezes Intermittently

Hello everyone.

I wrote a program a couple years ago that counts time and changes the state of some grove relays at specified times. It also sends some data to a grove LCD screen to monitor everything.

The program works fine except that it intermittently freezes and I have to reset the board. I would like the program to run indefinitely, as it is controlling fans and humidifiers to grow Oyster mushrooms, so when it goes down and I don't notice it right away things can dry out and I lose potential yields.

My current theory is that after a certain point the variable I have holding the millis() time gets maxed out somehow after the program runs for a while, but I don't really think that's true because it is controlled by a modulo which should keep the number low. If that's not the problem I wonder if it is something like that.

Board is Arduino UNO.

Sorry if I am missing any relevant technical details, I wrote the program a couple years ago and haven't done any work with Arudino since. I really hope there is an easy answer to run a program in this fashion.

I really appreciate any help anyone can offer. Thank you!

Here is the code:

// Relay 1 is controlling fae, currently will be right side of double gang box
// Relay 2 is controlling humidifier, which I took out of the code on 2021-01-21

// transfered to internal hard drive on HP laptop new linux install February 14, 2022
// board is Arduino Uno
// if using on fresh install, add user to dialout group

// last updated 03/06/22

const int relayOne = 3; // base shield pin number for Relay 1 // Connect the LED to Arduino digital pin 4, Grove socket D4
const int relayTwo = 4; // base shield pin number for Relay 2

int changeRelay = 0;

//timer for fae

const long faeOnInterval = 60000; // 60 sec fae
const long humOnInterval = 90000; // 90 sec hum
const long allOffInterval = 1050000; // 17.5 minutes all off, 20 minutes total cycle 

unsigned long timeToNextEvent = 0;
unsigned long totalSequenceRunTime = 0;

unsigned long previousDisplayMillis = 0;
unsigned long currentDisplayInterval = 1000;

unsigned long currentInterval = 0; //current time, could delay start time
unsigned long previousMillis = 0; // will store last time LED was updated
int faeRelayOneState = LOW;

int currentState = 2; //immediately advances to 0

// rgb screen

#include <Wire.h>
#include "rgb_lcd.h"

rgb_lcd lcd;

int colorR = 255;
int colorG = 255;
int colorB = 0;

void setup() {

  pinMode(relayOne, OUTPUT); // setup for Relay 1 "initialize ledPin as an output."
  pinMode(relayTwo, OUTPUT);

  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  // set screen background color! fun.
  lcd.setRGB(colorR, colorG, colorB);


  totalSequenceRunTime = faeOnInterval + humOnInterval + allOffInterval;


}

void loop() {

  timerSequencerFunctionality();

}

void timerSequencerFunctionality() {

  unsigned long currentMillis = millis() % totalSequenceRunTime;

  //display timer
  if ((unsigned long)(currentMillis - previousDisplayMillis) >= currentDisplayInterval) {

    lcd.setCursor(0, 0);
    lcd.print(currentMillis);

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

    lcd.setCursor(8, 1);
    lcd.print(totalSequenceRunTime);

    previousDisplayMillis = currentMillis;

    if (currentState == 2)
    {
      lcd.setRGB(random(0, 255), random(0, 255), random(0, 255)); // set screen to random color;
    }

  }

  if ((unsigned long)(currentMillis - previousMillis) >= currentInterval) {

    currentState = (currentState + 1) % 3;

    lcd.clear();

    switch (currentState) {
      case 0: // fae

        digitalWrite(relayTwo, LOW); // turn off hum

        faeRelayOneState = HIGH; // turns relay to ON
        currentInterval = faeOnInterval;
        timeToNextEvent = (currentMillis + faeOnInterval) % totalSequenceRunTime;

        lcd.setCursor(8, 0); // set text print position
        lcd.print(" fae ON "); // display text

        lcd.setRGB(random(0, 255), random(0, 255), random(0, 255)); // set screen to random color;

        //}

        break;
      case 1: //run hum

        digitalWrite(relayTwo, HIGH); // turn on hum

        faeRelayOneState = LOW; // turns relay to OFF
        currentInterval = humOnInterval;
        timeToNextEvent = (currentMillis + humOnInterval) % totalSequenceRunTime;

        lcd.setCursor(8, 0); // set text print position
        lcd.print(" hum On "); // display text

        lcd.setRGB(random(0, 255), random(0, 255), random(0, 255)); // set screen to random color;

        break;

      case 2: //all off

        digitalWrite(relayTwo, LOW); //shut off hum relay

        currentInterval = allOffInterval;
        timeToNextEvent = (currentMillis + allOffInterval) % totalSequenceRunTime;

        lcd.setCursor(8, 0); // set text print position
        lcd.print(" all OFF"); // display text

        break;
    }

    previousMillis = currentMillis;

    digitalWrite(relayOne, faeRelayOneState);
  }

}

I hope someone has some ideas.

Thank you!

After 57 days, or thereabouts, millis() wraps around to zero, so I expect you're not using it correctly.
Check the examples of using millis() instead of delay().

Welcome to the forum

After what sort of period of time does the freeze occur ?

Hours, days, weeks ?

Written properly using the right techniques a sketch will survive the millis() rollover with no problem. One of those techniques is to always use subtraction when manipulating timing variables

I have not looked in detail at your code but I see statements like this

        timeToNextEvent = (currentMillis + faeOnInterval) % totalSequenceRunTime;

What will the result of the addition be if the currentMiilis value is close to the maximum value of millis() ?

why do you takes the modulo of millis()? what happens when previousMillis is greater than currentMillis?

if the values were allowed to be as large as the variable type 2^64 -1, the math works out. in other words an unsigned long value of 1 minus an unsigned long value of (2^64) --2 is 3 because of the wraparound.

Close, but no cigar !

The figure is 49, and a bit days but you are right about correctly written code not having a problem with rollover to zero

WIth my memory these days, it's a lucky thing that I was remembering days, not weeks!

It seems to freeze after a day or two. I have been managing the issue by manually resetting the board whenever I check on everything, but if I don't do it for a few days then I come back and it is often frozen.

Let me put that out there and then look at the other responses more carefully.

Also I am wondering about this entry from the posting guidelines:
8. Memory usage

Microcontrollers like the Arduino have much less memory than you would be used to on a Mac, Windows or Linux. For example the Atmega328 processor (used on the Uno, Fio, Nano, Duemilanove boards, and others) has 32 Kb of Program Memory (for your code), 2 Kb of SRAM* (for your variables), and 1 Kb of EEPROM (for saving data when powered off).

Typically programs that reset unexpectedly, or hang, have run out of SRAM. Note that the number reported by the compiler when you compile your program is program memory, not SRAM.

Even a simple array like the one below uses 2000 bytes of memory. This line alone would almost certainly make your program crash:

int myData [20] [50];

Hint: The String class tends to gobble up memory. Try to avoid using that, especially in larger programs.

** SRAM = Static Random Access Memory*

Warning: In versions of the Arduino IDE up to 1.0.1 (at least) there is a bug in dynamic memory allocation. This affects both malloc/free and new/delete. In particular it also affects the String class, which uses dynamic memory allocation. This is discussed in this forum thread 3. A work-around is provided in this thread 2 (a replacement malloc.c file) until the inbuilt libraries are updated.

Hint: Static strings can quickly gobble up SRAM. A simple technique to avoid that is to use the F( ) macro, as shown here:

Instead of:

Serial.println ("Welcome to my program!");

Use:

Serial.println (F("Welcome to my program!"));

it's a math problem ... fix it

I am not using delay() in my code

I believe it would be ok as long as the maximum value of the unsigned long currentMillis is greater than the maximum value of millis(). I see the max value of an unsigned long is 4,294,967,295, but I don't know the maximum value of millis().

But without knowing that value, maybe subtraction is safer? I could certainly explore a rewrite based around subtraction.

seems like another OP more interested in posting comments than simply fixing the code

You misunderstood; the reference was to an example of using millis() correctly. I still suspect a math/logical error somewhere, but...meh.

Belief systems are funny things.

That's my point. millis() wraps every 49 days; wrapping means it goes from max to min value. It's a u32, so of course it's as big as the biggest value in currentMillis, whatever that is, right before it wraps to zero.

I believe the reason I take the modulo of millis() is this, the numbers in parenthesis are hypothetical values for the variables:
currentMillis (500) = millis() (1500) % totalSequenceRunTime (1000)

So this way the currentMillis never goes beyond the maximum sequence run time of 1000, even as millis() continues to count beyond 1000.

I'm not sure. I can't see easily where that would happen in the code. Also, if there was a logical error like that, I would anticipate that the code wouldn't make it through more than one sequence before freezing or crashing.

Thanks for your continued input! I've never posted here before, and didn't know what to anticipate as far as response times to my questions. I don't have the board with me to try new code ideas, so I'm exploring what everyone is saying and collecting ideas to bring into my next coding session. I also don't code much anymore, so I am brushing the dust off a little bit and trying to work through the different ideas you're presenting. I was hoping there would be a glaring error that would be easy to fix, but I guess the fact that it's not is a testament to the code being decent to begin with.

Thanks for the clarification! I assumed I was using millis() somewhat correctly because I probably drew from examples to create the code I did.

Well... it could also be thick as soup, and as hard to read as sanskrit. Not sayin' it is, but alternate viewpoints might exist!

The problem is, it's hard to see where, but your code is probably running afoul of the millis() wrap around; it's just difficult to quantify because the approach is, well, unusual.
If your failure is around the 49 day mark, that's a smoking gun.

Oh ok! So is your idea basically that as millis() is getting close to the max, that on this line

I try to add humOnInterval to currentMillis and this addition pushes the value of currentMillis higher than its maximum value, and that could freeze the program?

Which belief systems are you referring to specifically?

ha ha ha now that I could certainly understand. That's probably why I'm here in the first place. I like to be creative, and to be technical, but clearly I'm not a great software writer.

I guess my point was that, by and large the program works and accomplishes what it's supposed to, but that I'm not familiar enough with the intricacies of debugging to figure out a runtime error.

It's definitely before that.

The statement "I believe" with respect to software has a way of leading to grief, is all I'm saying.

ha ha ha let me revise that to "My current belief is..."

I'm not doing the analysis of your code for you, I'm pointing at something I think you should be looking at. However, looking back at your comments, it may not be related because you're looking at 1-2 day intervals, whereas I got the impression early in the thread that the survival time was much longer.
Best of luck, I'm off to supper. I gather you'll not be hands-on for a while, I look forward to hearing your next update.

Thank you! That's generally what I was thinking it was too. Now, thankfully, I have a few specific places I'll be trying to adjust to make the program run smoothly.

Also I disagree with your equating the use of the statement "I believe" to a belief system.