Arduino Nano crashes for unknown reasons

I´m using an Arduino Nano to count parts at a vibrating conveyer. The parts fall through a light bridge which is connected to the arduino via an octocoupler(The pin on the arduino is in input_Pullup mode). The counter is displayed over a lcd display with I2C backpack. If no part was registered within a certain time, a lamp,also controlled via an octocoupler, turns on. When a new part is registered or the counter is resetted(by pressing the “reset counter” button) the lamp turns off.

My problem is, that the arduino chrashes after a, seemingly, random time(Sometimes it runs for hours). When this happens, the display shows weird characters and the lamp won´t shut off neither when a part drops through the light barrier nor when the “reset counter” button is pressed. The only way to “free” the arduino, is disconnecting the power supply.

I´m thankful for any help, even if it is just a suggestion how to improve the code.

The code is originaly written in german, but I added english comments and most of the variable names should be understandable.

/*
  Programm Zähleranlage
  V 2.1.0

  Program Counting System 
  V2.1.0
  
  Projektbeschreibung:
  Dieses Programm steuert den Micro-Controller einer Zählanlage, die an einer Rüttelstrecke montiert ist. Die Zählung erfolgt
  über eine Lichtschranke, die Anzeige über ein LCD-Display. Das Rücksetzen des Zählers erfolgt über einen, am Gehäuse verbauten Taster. 
  Kommt nach der eingestellten Zeit(s. Nutzerhinweise) kein Zählsignal, wird ein Relais angesteuert, welches eine Warnleuchte aufleuchten
  lässt.

  Project description:
  This program controls the micro-controller of a counting system which is mounted on a vibrating conveyor. The counting is done by a 
  light barrier, which connected to the arduino via an optocoupler, the indication by a LCD display. The counter is reset via a button 
  installed on the housing. If no counting signal is received after the set time, a relay is activated which lights up a warning light.
*/

#include <Arduino.h>
#include <LiquidCrystal_I2C.h>

#if defined(ARDUINO) && ARDUINO >= 100
#define printByte(args) write(args);
#else
#define printByte(args) print(args, BYTE);
#endif

/* 
  Pin-Belegung/Used Pins:
    Variablenname/Variable name : Pin   : Funktion/Function                                                           : Pin mode
    Lichtschranke/Optokoppler   : D3    : Lichtschranke zur Teileerkennung; Optokoppler zur Umwandlung von 24 V zu 5V/Light barrier;octocoupler : INPUT_Pullup
    CounterResetButton          : D2    : Taster zum Zurücksetzen des Zählers /button for resetting the counter : INPUT_Pullup
    Leuchte                     : D8    : Optokoppler zur Ansteuerung der Alarmleuchte/ lamp : OUTPUT
    lcd                         : A4 A5 : LCD-Display zur Anzeige von Informationen(Zähler, Zeit) /LCD :(Output
*/

//Input Variablen
//Input Variables
const byte Lichtschranke = 3;
const byte CounterResetButton = 2;
//Output Variablen
//Output Variables
const byte Leuchte = 8;
LiquidCrystal_I2C lcd(0x27, 20, 4);

//Globale Variablen
//Global Variables
unsigned long counter = 0;     //Zähler //Counter
bool prevSensor = 1;           //Speicherung des vorherigen Sensorwertes   //Stores the sensor value for the next cycle
bool prevReset = 1;            //Speicherung des vorherige Wertes des Reset-Tasters    //Stores the value of the "reset"-button for the next cycle
unsigned long timeAtLastPart;  //Zeit beim letzten Teil  //Time at the last part
unsigned long prevTime;        //Die Zeit beim letzten Programmzyklus  //Stores the time at the last cycle
const int timeToError = 20000; //Zeit in ms bevor der Alarm auslöst; Zahl bearbeiten um die Zeit bis Alarm zu ändern (Standard = 20 s = 20000 ms)  //The time in ms until the warning light lights up. Change the number (default = 20 s = 20000 ms)

//Deklarierung der Funktionen
void count();
void setupDisplay();
void displayCounter();

//Setup-Funktion (Läuft einmal bei Einschalten)
void setup()
{
  //Einschalten des LCD
  //Turning on the LCD
  lcd.init();
  lcd.backlight();
  //Display in Ausgangszustand
  //Set LCD to Initial-State
  setupDisplay();
  //Definieren der Pin-Modi
  //Set the pinModes
  pinMode(Lichtschranke, INPUT_PULLUP);
  pinMode(CounterResetButton, INPUT_PULLUP);
  pinMode(Leuchte, OUTPUT);
}

//Loop-Funktion (Wiederholt sich immer wieder)
void loop()
{
  unsigned long time = millis();

  //Auswerten des Sensorwertes
  //Ist der Sensorwert anders als beim letzten Durchgang und ist aktiv (-> positive Flanke), wird die Zählfunktion aufgerufen.
  //If the sensor state is different than in the last cycle and HIGH the counting function is called
  bool sensor = digitalRead(Lichtschranke);
  if ((sensor != prevSensor) && sensor)
  {
    count();
  }
  prevSensor = sensor; //Der aktuelle Sensorwert wird als vorhergehender Sensorwert für den nächsten Durchlauf gespeichert  //The value of the sensor is stored for the next cycle

  //Auswerten der Zeit
  //Error-Check
  bool error = false;
  unsigned long timeSinceLastPart = time - timeAtLastPart; //Die Zeit seit dem letzten Teil wird errechnet  //The time since the last part is calculated
  if (timeSinceLastPart >= timeToError)                    //Wenn die Zeit größer als die Vorgabezeit ist, wird die Error-Variable gesetzt  //If the time since the last part is greater than the configured time, the "Error"-variable is set to true.
  {
    error = true;
  }

  digitalWrite(Leuchte, error); //Die Leuchte wird auf den Fehlerwert gesetzt (error = 1 > Lampe an) //The warning light is set to the value of "Error" (error = true -> light on)

  //Rücksetzen des Zählers bei Betätigung des Reset-Tasters
  //Resetting of the counter if the "Reset"-button is pressed
  bool reset = !digitalRead(CounterResetButton);
  if (reset && (reset = !prevReset))
  {
    counter = 0;           //Zähler wird auf 0 gesetzt //Counter is set to 0
    setupDisplay();        //Die Anzeige wird in den Ausgangszustand versetzt //The Display is set to the initial state
    timeAtLastPart = time; //Die Zeit bei dem letzten Teil wird auf die aktuelle Zeit gesetzt //The time a the last part is set to the new time
  }
  prevReset = reset; //Der aktuelle Wert des Reset-Tasters wird als vorhergehender Wert für den nächsten Durchlauf gespeichert. //The value of the "Reset"-button is stored for the next cycle

  //Falls ein Fehler vorlieggt, wird die Stillstandzeit angezeigt, ansonsten die Zeit seit dem letzten Teil
  //If "Error" = true, the time, how long error is true is displayed, else the time since the last part is Displayed
  if (time / 1000 > prevTime / 1000)
  {
    if (error)
    {
      lcd.setCursor(0, 2);
      delay(3);
      lcd.print("Sillstandzeit:      ");
      delay(3);
      lcd.setCursor(0, 3);
      delay(3);
      lcd.print((timeSinceLastPart - time) / 1000);
      delay(3);
      lcd.print(" s          ");
    }
    else
    {
      lcd.setCursor(0, 2);
      delay(3);
      lcd.print("Zeit seit Teil:     ");
      delay(3);
      lcd.setCursor(0, 3);
      delay(3);
      lcd.print((timeSinceLastPart) / 1000);
      delay(3);
      lcd.print(" s          ");
    }
  }
  prevTime = time;
}

//Funktion die aufgerufen wird wenn ein neues Teil erkannt wird.
//Function, which gets called if a part is detected
void count()
{
  counter++;             //Zähler wird um eins erhöht. //Counter + 1
  timeAtLastPart = time; // Die Zeit beim letzten Teil wird mit der aktuellen Zeit ersetzt. //The time a the last part is set to the new time
  displayCounter();      //Die Anzeige wird akualisiert //The counter is displayed
}

//Funktion die das Display in den Ausgangszustand versetzt:
//Function for the initial state of the display
/*
      Anzahl Teile:
      <Zähler>
      

*/
void setupDisplay()
{
  lcd.clear(); //Die Anzeige wird geleert //The Display is cleared
  delay(3);
  lcd.print("Anzahl Teile:    "); //Das Display wir beschrieben (s.o.)
  delay(3);
  lcd.setCursor(0, 1);
  delay(3);
  lcd.print(counter);
}

//Funktion die den aktuellen Zählerwert auf das Display schreibt.
//writing the counter value to the display
void displayCounter()
{
  lcd.setCursor(0, 1);
  delay(3);
  lcd.print(counter);
  delay(3);
  lcd.print("            ");
}

I don't see anything obviously wrong, although I can't compile it, I get:

'time' was not declared in this scope

If you're getting garbage on the LCD, I suspect you're getting electrical interference, which wouldn't be a surprise as it sounds like you're running it in some kind of production facility. Opto-couplers are a good idea, but it seems that it isn't enough.

Looks like some kind of power supply glitch to me as well as well.
Try a better power supply, more decoupling, or implement a watchdog.
Or maybe there is a bug in the library....

Can you run the code outside the factory environment to test?

Not your problem, but this code...

  if (time / 1000 > prevTime / 1000)
  {

..looks rather iffy to me.

Looks like you are trying to only update the LCD ever 1000ms (1s)?

Use the standard millis() timing (like you using are elsewhere)...

if (time - lastUpdateTime > 1000)
{
   // Blah, blah....
   lastUpdateTime += 1000;
}

At least that way you are avoiding two long divisions.

I suspect you're getting electrical interference

Could this chrash/freeze the whole arduino?

wildbill:
I don't see anything obviously wrong, although I can't compile it, I get:

'time' was not declared in this scope

I fixed this error. "time" was just declared locally in loop() but I used it in a function.

pcbbc:
Not your problem, but this code...

  if (time / 1000 > prevTime / 1000)

{



..looks rather iffy to me.

Looks like you are trying to only update the LCD ever 1000ms (1s)?

Use the standard millis() timing (like you using are elsewhere)...


if (time - lastUpdateTime > 1000)
{
  // Blah, blah....
  lastUpdateTime += 1000;
}



At least that way you are avoiding two long divisions.

Thanks for the suggestion, I already implemented it in my code.