Arduino Nano freezing randomly after a few minutes

Looking for help with a bit of my code that's really got me puzzled!

I've got a Nano running an SSD1309 128 x 64 OLED using SPI. It's job is to count up in 0.1 seconds to a certain value whilst an input (pumpPin, D7) is true, then to hold that displayed value for 15 seconds if it hasn't changed, before resetting the counter to zero. Typically it's expected to only count for 30 seconds or so, then rest at 0 for minutes at a time.
Upon the input being active for 5 seconds or more an output (pumpActivePin, D6) gets pulled low for an external interface to log the event. -not currently connected.

Testing this program everything seems to work just fine! It's repeatable and works just as intended. But when left powered on for typically 10 to 30 minutes, a different and seemingly random failure will occur:

  1. The LED on the Nano from pin 13 will stop flashing and extinguish. The display will then freeze, displaying whatever it last displayed.

  2. The LED on the Nano from pin 13 will stop flashing and extinguish. The display will then go blank.

  3. The LED on the Nano from pin 13 will keep flashing, but the display goes blank. Can confirm 5V is still present on the VCC pin of the display at this time.

A press of the reset button or reset of power to the unit rectifies any of these issues.
The failures will occur regardless of whether or not the input (pumpPin) is being triggered.

My suspicions are that I've either made a complete goof of the use of millis(), or something in the U8G2 library is doing something funky.

I've tried different Nano boards and displays in case there's a hardware glitch but no change in performance..

Code for reference. Hopefully it's not too horrendous to look at!

#include <Arduino.h>
#include <U8g2lib.h>

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif

U8G2_SSD1309_128X64_NONAME0_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);  

int pumpPin = 7;
int pumpActivePin = 6;
unsigned long prevMillis;
unsigned long curMillis = 0;
unsigned long lastMillis;
float count;
float initialCount = 0.0;
boolean pumpActive;
boolean hasTriggered;

void setup() {
  pinMode(pumpPin, INPUT);
  pinMode(pumpActivePin, OUTPUT);
  digitalWrite(pumpActivePin, HIGH);
  count = initialCount;
  u8g2.begin();
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_fub42_tn);
  u8g2.setCursor(5,62);
  u8g2.print(count,1);
  u8g2.sendBuffer(); 
}

void loop() {
  
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_9x15_tf);
  u8g2.drawStr(19,12, "PUMP TIMER"); 
  
  if(digitalRead(pumpPin) == LOW){
  u8g2.setFont(u8g2_font_fub42_tn);
  u8g2.setCursor(5,62);
  u8g2.print(count,1);
  u8g2.sendBuffer(); 
  }

curMillis = millis();

  // if one tenth of a second has passed
  if(((curMillis - prevMillis) >= 100) && (digitalRead(pumpPin) == HIGH)){
    curMillis = millis();
    prevMillis = (millis() - 4); // Adjust this as required to get time correct (speeds up by 4ms per tick)
    count += 0.1;
    u8g2.setFont(u8g2_font_fub42_tn);
    u8g2.setCursor(5,62);
    u8g2.print(count,1);
    u8g2.sendBuffer();
  }

  // If the pump has been running for 5 seconds or more, trigger the output to go LOW
  if(count >= 5 && hasTriggered == 0){
    digitalWrite(pumpActivePin, LOW);
    hasTriggered = 1; // Flag operation so this only has one falling edge per timer cycle
  }

  // Hold value on screen for 15 sec if it hasn't changed, isn't zero and the pump is not running
if(((curMillis - prevMillis) >= 15000) && (digitalRead(pumpPin) == LOW) && count != 0){
    resetCounter();
}
}

 // Reset the counter, output and flag
void resetCounter() {
  count = initialCount; 
  digitalWrite(pumpActivePin, HIGH);
  hasTriggered = 0; // Reset flag so it is ready to trigger again during following pump
}

how are you driving the pump? a circuit would help

if you suspect the OLED you could remove that part from the code and just use the Serial monitor and see if this is still crashing

You can draw on a piece of paper a schematic/circuit and make of photo of it.

Your use of millis() is not okay. Can you describe what it should do ?

The U8G2 library is too big for the Arduino Nano and you use two fonts. There are other (smaller) libraries, or you could use the U8X8 library (part of U8G2).

In this testing phase the input (pumpPin, D7) is just being pulled high to the 5v rail to simulate the yet to be connected external pump logic when I want the counter to run. D7 also has a 10k pull down resistor installed.

Good thinking on the OLED! I'll disconnect that now and run the unit, see if it still falls over.

remove the OLED code so that the library does not generate any issue (if any)

Currently aside from a 10k pull-down resistor on D7, there's no external circuitry other than an LM7808 powering the VIN, with it's suggested 300nF and 100nF capacitors on the input and output respectively.

Ah, yeah I was worried about my use of millis(). Basically I'm trying to count time since the input has been triggered, display said time in 0.1s increments on the display, hold that count once the input is inactive, then reset and wait.
If the count has reached 5s, then I trigger an output to an external interface which resets once the count, and therefore input, has been inactive for 15 seconds.

Power is never expected to be applied for longer than a few hours to the unit, so it was my understanding that millis() in an unsigned long shouldn't run out of counts.

I was concerned by that library, particularly since it takes so long to compile! Though a false sense of security may be that the sketch suggests it fits:
Sketch uses 13252 bytes (43%) of program storage space. Maximum is 30720 bytes. Global variables use 1464 bytes (71%) of dynamic memory, leaving 584 bytes for local variables. Maximum is 2048 bytes.

Cheers for the library suggestion! I'll take a look at changing it across

A few tips:

  1. That 71% is from the compiler. At runtime, the U8G2 library has also allocated memory from the heap. Can you run a freeMem() at the end of setup() ? https://learn.adafruit.com/memories-of-an-arduino/measuring-free-memory#sram-370031
  2. You don't need the #include <Arduino.h>, you may remove that.
  3. Is it possible to update the display every 100ms all the time ?
  4. Every millis-timer needs its own 'previousMillis'. So you get 'previousMillisDisplay', 'previousMillis5seconds', 'previousMillis15seconds'.
  5. A millis-timer usually runs on its own in the main section of the loop(). For a single-shot-timer, you need code to start it and the code of the millis-timer itself. See my millis_single_delay.ino
  6. A continuous millis-timer can be accurate (for a clock, millis_clock.ino) or can be safe (for leds and display updates).

Safe:

if( currentMillis - previousMillis >= interval)
{
  previousMillis = currentMillis;
  ...
}

Accurate

if( currentMillis - previousMillis >= interval)
{
  previousMillis += interval;
  ...
}

The "accurate" version is not unsafe. Only with bad code it might be unsafe in a very rare situation. But the "safe" version is the common millis-timer.

By the way, I think that you don't need a accurate timer. Use the unsigned long value from millis() for the timing, that is always accurate, don't use a millis-timer to count a time.

There is a other option: a Finite State Machine. If you want a longer sequence of things to do or make your sketch ten times more complex, then your sketch is holding you back and a Finite State Machine does not. A Finite State Machine executes code according to the state it is in. The 'state' is a variable that tells what to do. Tutorial: The Finite State Machine | Majenko Technologies

Without the Finite State Machine, you still need a variable that keeps track whether the timer is running or not.

Your sketch in a Wokwi simulation:

There are still 2299 Free Memory. So it might be something else.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.