Arduino Stucks (Freezes) Randomly - Need a way to write robust code

Hi, Arduino Pro mini 3.3V stucks few hours after startup. After repowered, it begins to stuck within a few seconds. If I wait long enough before turning it on it stucks little later for example now it lasted 45 seconds before stuck. Whenever it is stuck SCL pin is high and SDA pin is low so I suspect somethin about I2C is wrong. There is an 60 seconds countdown at start and the stuck usually happens there, you will see it is an extremely simple for loop. There are SHT31 sensor, 128x64 SH1106 driven OLED screen, DS3231 clock module, hc-sr501PIR sensor, LDR, Encoder and a button on the project. Only first three works with I2C and the code for those devices are very short, they are in "start(), updatescreen(), getTime(), getDew()" functions in the long script below so I hope it is not hard to diagnose the problem. I have done some experiments. I isolated SHT31 and OLED, both caused freeze even though all other devices were disconnected (I added blink code to their example test sketch and the blinking stopped after few seconds which means stuck). OLED screen works when it is connected with jumpers outside the PCB so I even suspected the PCB but it is so simple that it shouldn't cause problems. The weird thing is that this device worked perfectly for 2 days before it crushed for the first time.

Schematic:

PCB (some mistakes were fixed later):

Real image:

Test script (the same freezing problem occurs but easier to read):

#include <U8g2lib.h>
#include <Wire.h>

U8G2_SH1106_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE);
void setup() {
  u8g2.begin();
  u8g2.setFont(u8g2_font_ncenB14_tr);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  u8g2.firstPage();
  do {
    u8g2.setCursor(0, 20);
    u8g2.print(F("Hello World!"));
  } while (u8g2.nextPage());
  delay(1000);
  digitalWrite(LED_BUILTIN, HIGH);  // turn the LED on (HIGH is the voltage level)
  delay(1000);                      // wait for a second
  digitalWrite(LED_BUILTIN, LOW);
}

Test script 2 (this time for sht31, the same freezing problem occurs but easier to read):

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

ClosedCube_SHT31D sht3xd;
void setup() {
  Wire.begin();
  sht3xd.begin(0x44);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);
  SHT31D result = sht3xd.readTempAndHumidity(SHT3XD_REPEATABILITY_LOW, SHT3XD_MODE_CLOCK_STRETCH, 50);
  delay(1000);
  digitalWrite(LED_BUILTIN, LOW);
  delay(1000);
}

Full Script:

#include "Wire.h"
#include "ClosedCube_SHT31D.h"
#include <DS3231.h>
#include "LowPower.h"
#include <EEPROM.h>
#include <U8g2lib.h>

#define PIR 2
#define ENCODER_BUTTON 3
#define ENC_A 4
#define ENC_B 5
#define FAN_MAIN_REL 8
#define FAN_SECOND_REL 9
#define BUZZER 10
#define LIGHT_REL 11
#define LDR_PWR 12
#define LDR_READ A0
#define FREE_RUN_BUTTON A2
#define CLOCK_INT A3

volatile bool PIR_flag, TIM1_flag, But_flag, FreeRun_flag, Settings_flag;
uint8_t Weather_flag;  // Weather_flag is special, it is set/reset by a regular function, 0: Dew is under threshold, 1: Dew is over DewThres but less than DewThresExtreme, 2: Dew is over DewThresExtreme
volatile int16_t encVal;

// Variables:
float Temp, Hum, Dew;
bool DewTrigEnabled;
uint8_t FanMotTrigEnabled;  // for fan relay
bool LightRelEnabled;       // for light relay
uint16_t waitDur;           // Setting 1
uint16_t Runtime;           // Setting 2
float DewThres;             // Setting 3
float DewThresExtreme;
uint16_t FreeRunDur;  // Setting 5
bool FreeRunStatus;
bool DNDenabled;
uint8_t DND_SH;  // DND start Hour
uint8_t DND_SM;  // DND start Minute
uint8_t DND_FH;  // DND finish Hour
uint8_t DND_FM;  // DND finish Minute
uint16_t counter;
uint8_t hour, minute;
bool LDRenabled;
uint16_t LDRThres;
bool screen_awake = true;  // screen starts "on"
uint8_t FanRelayStatus;
const uint8_t screenTime = 60;  // Screen On time without motion (seconds)
const float DewUSratio = 0.98;  // Determines the dew point which the FAN turns off if it was triggered by the Dew in the first place
const uint8_t phaseLim = 120;   // Determines LDR check rate if light is off but LDR is enabled, each phase corresponds to 5 seconds.

ClosedCube_SHT31D sht3xd;
RTClib myRTC_1;
DS3231 myRTC_2;
U8G2_SH1106_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE);

void swCounter(bool turn_on, uint32_t cnt = 0) {
  if (turn_on) {
    counter = cnt;
    TCNT1 = 0;                            // TIM1 counter value
    TCCR1B |= (1 << CS12) | (1 << CS10);  // Start timer by setting prescaler to 1024
  } else
    TCCR1B &= ~(1 << CS12) | ~(1 << CS10);  // Stop TIM1
}

void setup() {
  LoadPinsAndEEPROM();

  Wire.begin();

  // Temp&Hum Sensor:
  sht3xd.begin(0x44);

  // Clock Module:
  myRTC_2.setA2Time(
    0, 0, 0,
    0b01110000, false, false, false);
  myRTC_2.turnOnAlarm(2);

  myRTC_2.setA1Time(
    0, 0, 0xFF, 0,
    0b00001110, false, false, false);
  myRTC_2.turnOffAlarm(1);
  myRTC_2.checkIfAlarm(1);  // clear Alarm 1 flag

  sht3xd.heaterEnable();
  // OLED Screen:
  u8g2.begin();
  u8g2.setFont(u8g2_font_lubB12_tr);
  for (int i = 60; i > 0; i--) {
    u8g2.firstPage();
    do {
      u8g2.drawStr(6, 25, "by Prince");
      u8g2.setCursor(58, 55);
      u8g2.print(i);
      if (i == 30) sht3xd.heaterDisable();
    } while (u8g2.nextPage());
    delay(1000);
  }
  u8g2.setDrawColor(2);

  //TIM1 Configuration:
  TCCR1A = 0;
  TCCR1B = 0;
  TIMSK1 |= (1 << OCIE1A);  // Output Compare A Match Interrupt Enable
  OCR1A = 39061;            // Output Compare A
  TCCR1B |= (1 << WGM12);   // CRC (auto reset timer when it reaches OCR1A)
  TCNT1 = 0;                // TIM1 counter value

  // Attach interrupts (PinChanges and INTpin)
  attachInterrupt(digitalPinToInterrupt(PIR), ISR_PIR, RISING);
  attachInterrupt(digitalPinToInterrupt(ENCODER_BUTTON), ISR_ENCODER_BUTTON, FALLING);
  PCICR |= (1 << PCIE1);                      // A0-A6 group can create interrupt
  PCMSK1 |= (1 << PCINT10) | (1 << PCINT11);  // A2 and A3 pins can create interrupt
  PCMSK2 |= (1 << PCINT20) | (1 << PCINT21);  // D4 and D5 pins can create interrupt

  Beep(1, 100, 0);
  PIR_flag = false;
  But_flag = false;
}

void loop() {
  checkButtons();  // After execution it will return.
  if (!getDNDstatus()) {
    getDew();  // sets flag accordingly

    if (PIR_flag) {
      if (FanMotTrigEnabled == 1) {
        TIM1_flag = true;  // Update screen initially
        screenPwr(1);

        unsigned long lastMotion;
        uint8_t step = 1;
        if (waitDur <= 5) step = 2;  // PIR_flag is already true at this point
        else swCounter(1, waitDur);

        for (step = step; step <= 2; step++) {  // 1: waiting, 2: armed, 3: Running
          if (step == 2) {
            (waitDur >= 90) ? counter = 60 : counter = 30;  // Deciding how long ahead it will check for motion
            swCounter(1, counter);
            PIR_flag = false;
            updateScreen(step);
          }
          while (counter > 0) {  // Counter Loop
            if (checkButtons()) return;
            if (PIR_flag) {
              PIR_flag = false;
              lastMotion = millis();
              if (step >= 2) {    // if it's armed or Running and Motion is detected
                if (step == 2) {  // if it's armed and Motion is detected
                  if (FanRelayStatus != 2) swFanRelays(1);
                  step = 3;  // Motion..
                  updateScreen(step);
                }
                swCounter(1, Runtime);  // Restart interval
              }
            }
            if (TIM1_flag) {
              TIM1_flag = false;
              if (getDNDstatus()) return;
              if (step == 1 && (millis() - lastMotion) / 1000 >= screenTime) return;
              getDew();
              DewControl(step != 3);  // in case Dew increases while on wait
              updateScreen(step);
            }
          }
        }
      } else if (FanMotTrigEnabled != 1) {  // Mot trig is disabled but there is motion
        PIR_flag = false;
        if (FanMotTrigEnabled == 2) swFanRelays(0);
        RunScreen(false);
      }
    }

    DewControl(true);
    Sleep(true);

  } else {  // if it's DND times:
    if (PIR_flag) {
      PIR_flag = false;
      RunScreen(true);
    }
    if (getDNDstatus()) Sleep(false);  // DnD status is rechecked because it might have been stuck in RunScreen by the user when DND is over.
  }
}

void DewControl(bool cmd1) {  // 0: cannot turn the fan "off" but only "on" 1: can both turn "on" and "off"
  if (Weather_flag == 0 && cmd1) swFanRelays(0);
  else if (Weather_flag == 1) swFanRelays(1);
  else if (Weather_flag == 2) swFanRelays(2);
}

void RunScreen(bool cmd1) {  // 0: dont return based on DND, 1: return if not in DND times anymore (prevents being stuck in DND if screen is on)
  TIM1_flag = true;          // Update screen initially
  screenPwr(1);
  swCounter(1, screenTime);  // Fixed Run screen time
  while (counter > 0) {
    if (checkButtons()) return;
    if (TIM1_flag) {
      if (getDNDstatus()) {
        if (FanRelayStatus) return;
      } else {
        if (cmd1) return;
        DewControl(true);
      }
      getDew();
      updateScreen(0);  // Idle or Dew

      if (PIR_flag) {
        counter = screenTime;
        PIR_flag = false;
      }
      TIM1_flag = false;
    }
  }
  LightRel_IfNeeded(0);
}

void getDew() {
  SHT31D result = sht3xd.readTempAndHumidity(SHT3XD_REPEATABILITY_LOW, SHT3XD_MODE_CLOCK_STRETCH, 50);  // used to be a static var
  Temp = result.t;
  Hum = result.rh;

  float a_func = log(Hum / 100) + 17.625 * Temp / (243.04 + Temp);
  Dew = (243.04 * a_func) / (17.625 - a_func);

  if (!DewTrigEnabled || (Dew <= DewThres * DewUSratio) || (Dew < DewThres && FanRelayStatus == 0)) Weather_flag = 0;
  else if (Dew < DewThresExtreme) Weather_flag = 1;
  else Weather_flag = 2;
}

bool getDNDstatus() {  // check if hours and minutes are in the interval
  if (!DNDenabled) return false;

  updateTime();
  uint16_t currentmin = hour * 60 + minute;

  uint16_t localDNDstart = DND_SH * 60 + DND_SM;
  uint16_t localDNDfinish = DND_FH * 60 + DND_FM;

  if (localDNDstart < localDNDfinish) return (currentmin >= localDNDstart && currentmin < localDNDfinish);
  else return (currentmin >= localDNDstart || currentmin < localDNDfinish);
}

void updateTime() {
  if (myRTC_2.checkIfAlarm(2)) {
    DateTime now;  // used to be a static var
    now = myRTC_1.now();
    hour = now.hour();
    minute = now.minute();
  }
}

void swFanRelays(uint8_t cmd1) {  // 0: turn off, 1: turn on
  if (FanRelayStatus == cmd1) return;
  bool PIR_flag_original = PIR_flag;
  if (cmd1 == 0) {
    digitalWrite(FAN_MAIN_REL, LOW);
    digitalWrite(FAN_SECOND_REL, LOW);
    FanRelayStatus = 0;
  } else if (cmd1 == 1) {
    digitalWrite(FAN_MAIN_REL, HIGH);
    if (FanRelayStatus != 2) {
      digitalWrite(FAN_SECOND_REL, HIGH);
      delay(500);
    }
    digitalWrite(FAN_SECOND_REL, LOW);
    FanRelayStatus = 1;
  } else if (cmd1 == 2) {
    digitalWrite(FAN_MAIN_REL, HIGH);
    digitalWrite(FAN_SECOND_REL, HIGH);
    FanRelayStatus = 2;
  }
  delay(100);
  if (!PIR_flag_original) PIR_flag = false;
  But_flag = false;
  FreeRun_flag = false;
}

void LightRel_IfNeeded(bool cmd1) {  // 0: turn OFF Light Relay, 1: turn ON Light Relay
  static bool LightRelStatus;
  bool LDRresult;
  static uint8_t phase;  // regularly check light level in case brightness changes in the room over time.

  switch (cmd1) {
    case 0:
      if (!LightRelStatus) return;
      LightRelStatus = false;
      phase = 0;
      digitalWrite(LIGHT_REL, LOW);
      delay(100);
      PIR_flag = false;
      But_flag = false;
      FreeRun_flag = false;
      break;

    case 1:
      if (!LightRelStatus && phase == 0) {
        if (LDRenabled) {
          digitalWrite(LDR_PWR, HIGH);
          delay(10);
          LDRresult = (analogRead(LDR_READ) > LDRThres);  // Darkness --> High resistance --> High voltage on LDR --> TRUE              Lit --> Low resistance --> Low voltage on LDR --> FALSE
          digitalWrite(LDR_PWR, LOW);
        } else LDRresult = true;

        if (LDRresult) {
          LightRelStatus = true;
          digitalWrite(LIGHT_REL, HIGH);
          delay(100);
          PIR_flag = false;
          But_flag = false;
          FreeRun_flag = false;
        }
      }
      phase++;
      if (phase > phaseLim) phase = 0;  // default value is 120 which means 10 minutes
      break;
  }
}

void Sleep(bool cmd1) {  // 0: Sleep normal 1: Sleep without turning off the Fan
  if (cmd1 == 0) swFanRelays(0);
  LightRel_IfNeeded(0);
  screenPwr(0);
  updateTime();
  PIR_flag = false;
  But_flag = false;
  FreeRun_flag = false;

  LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
}

void updateScreen(uint8_t cmd1) {  // 0: idle or Dew Trig, 1: Detected, 2: Waiting, 3: Armed, 4: Mot Trig, 5: Free run dur
  String info;
  LightRel_IfNeeded(1);  // Regularly called because brigthness of the room info is updated every phase*5/60 minutes
  updateTime();

  switch (cmd1) {
    case 0:
      if (FanRelayStatus == 0) info = "Idle";
      else if (FanRelayStatus == 1) info = "Dew";
      else info = "OverDew";
      break;
    case 1:
      info = "Waiting";
      if (FanRelayStatus == 1) info += "&Dew";
      else if (FanRelayStatus == 2) info += "&OverDew";
      break;
    case 2:
      info = "Armed";
      if (FanRelayStatus == 1) info += "&Dew";
      else if (FanRelayStatus == 2) info += "&OverDew";
      break;
    case 3:
      info = "Motion";
      if (Weather_flag == 1) info += "&Dew";
      else if (Weather_flag == 2) info += "&OverDew";
      break;
    case 4:
      info = "<";
      info += String(counter / 60 + 1);
      info.remove(info.indexOf('.'));
      info += " mins";
      break;
  }

  u8g2.firstPage();
  do {
    u8g2.setFont(u8g2_font_6x10_mr);  // u8g2_font_haxrcorp4089_tn
    u8g2.setCursor(0, 7);
    u8g2.print(info);

    u8g2.setCursor(99, 7);
    if (hour < 10) u8g2.print("0");
    u8g2.print(hour);
    u8g2.print(":");
    if (minute < 10) u8g2.print("0");
    u8g2.print(minute);

    u8g2.drawLine(0, 10, 128, 10);

    u8g2.setFont(u8g2_font_fub20_tn);
    u8g2.setCursor(0, 38);
    if (Temp < 10 && Temp >= 0) u8g2.print("0");
    u8g2.print(Temp, 1);
    u8g2.setCursor(0, 64);
    u8g2.print(Hum, 1);
    u8g2.setCursor(71, 61);
    if (Dew < 10) u8g2.print("0");
    u8g2.print(Dew, 1);
    if (Dew < DewThres) u8g2.drawFrame(70, 38, 58, 26);
    else u8g2.drawBox(71, 39, 56, 24);


    u8g2.setFont(u8g2_font_lubB12_tr);
    u8g2.drawStr(55, 30, "*C");
    u8g2.drawStr(55, 64, "%");

    if (FreeRunStatus == true) u8g2.drawStr(81, 30, "FREE");
    else if (getDNDstatus()) u8g2.drawStr(82, 30, "DND");

  } while (u8g2.nextPage());
}

void screenPwr(bool cmd1) {
  if (cmd1 == screen_awake) return;

  switch (cmd1) {
    case 0:
      u8g2.setPowerSave(1);
      u8g2.clear();
      screen_awake = false;
      break;

    case 1:
      u8g2.setPowerSave(0);
      swCounter(0);
      screen_awake = true;
      break;
  }
}

void FreeRun() {
  Beep(1, 500, 0);
  FreeRun_flag = false;
  FreeRunStatus = true;
  getDew();
  swFanRelays(1);
  swCounter(1, FreeRunDur);
  updateScreen(4);
  screenPwr(1);
  while (counter > 0) {
    if (But_flag) {
      FreeRunStatus = false;
      Settings();
      return;
    }
    if (FreeRun_flag) {
      Beep(1, 100, 0);
      delay(1000);
      if (!digitalRead(FREE_RUN_BUTTON)) {  // if the user holds the button down
        swFanRelays(0);
        Beep(2, 100, 100);
        while (!digitalRead(FREE_RUN_BUTTON)) delay(100);  // For the user to remove hand
        delay(500);                                        // prevent debouncing
        FreeRun_flag = false;
        break;
      } else {
        FreeRun_flag = false;
        if (FanRelayStatus == 1) swFanRelays(2);
        else if (FanRelayStatus == 2) swFanRelays(1);
      }
    }
    if (TIM1_flag) {
      TIM1_flag = false;
      if (getDNDstatus()) break;
      getDew();
      updateScreen(4);
    }
  }
  DewControl(true);
  FreeRunStatus = false;
  PIR_flag = false;  // Ignore the motion while in "Free Run" mode
}

bool checkButtons() {
  if (FreeRun_flag) {
    if (!getDNDstatus()) {
      FreeRun();
      return 1;
    } else {  // if Free Run is pressed during DND, deny Free Run
      Beep(3, 50, 50);
      FreeRun_flag = false;
    }
  } else if (But_flag) {
    Beep(1, 100, 0);
    delay(1000);  // If button is held down
    if (!digitalRead(ENCODER_BUTTON)) {
      Settings();
      return 1;
    }
    But_flag = false;
  }
  return 0;
}

void Beep(uint8_t rep, uint8_t dur_act, uint8_t dur_pass) {  // repetition, duration of beeping, duration of pauses
  for (int i = 0; i < rep; i++) {
    digitalWrite(BUZZER, HIGH);
    delay(dur_act);
    digitalWrite(BUZZER, LOW);
    if (i < (rep - 1)) delay(dur_pass);
  }
}

void LoadPinsAndEEPROM() {

#define FanMotTrigEnabled_Address 0
#define waitDur_Address 1
#define Runtime_Address 3
#define DewTrigEnabled_Address 5
#define DewThres_Address 6
#define DewThresExtreme_Address 10
#define LightRelEnabled_Address 14
#define LDRenabled_Address 15
#define LDRThres_Address 16
#define DND_Enabled_Address 18  // DND enabled
#define DND_SH_Address 19       // DND start Hour
#define DND_SM_Address 20       // DND start Minute
#define DND_FH_Address 21       // DND finish Hour
#define DND_FM_Address 22       // DND finish Minute
#define FreeRunDur_Address 23

  EEPROM.get(FanMotTrigEnabled_Address, FanMotTrigEnabled);
  EEPROM.get(waitDur_Address, waitDur);
  EEPROM.get(Runtime_Address, Runtime);
  EEPROM.get(DewTrigEnabled_Address, DewTrigEnabled);
  EEPROM.get(DewThres_Address, DewThres);
  EEPROM.get(DewThresExtreme_Address, DewThresExtreme);
  EEPROM.get(LightRelEnabled_Address, LightRelEnabled);
  EEPROM.get(LDRenabled_Address, LDRenabled);
  EEPROM.get(LDRThres_Address, LDRThres);
  EEPROM.get(DND_Enabled_Address, DNDenabled);
  EEPROM.get(DND_SH_Address, DND_SH);
  EEPROM.get(DND_SM_Address, DND_SM);
  EEPROM.get(DND_FH_Address, DND_FH);
  EEPROM.get(DND_FM_Address, DND_FM);
  EEPROM.get(FreeRunDur_Address, FreeRunDur);

  // DEBUG:
  /*
  FanMotTrigEnabled = 1;
  waitDur = 10;
  Runtime = 10;
  DewTrigEnabled = true;
  DewThres = 18.0;
  DewThresExtreme = 19.0;
  LightRelEnabled = true;
  LDRenabled = true;
  LDRThres = 512;
  DNDenabled = false;
  DND_SH = 23;
  DND_SM = 45;
  DND_FH = 0;
  DND_FM = 2;
  FreeRunDur = 30;
*/
  pinMode(PIR, INPUT);
  pinMode(LDR_READ, INPUT);
  pinMode(ENC_A, INPUT);
  pinMode(ENC_B, INPUT);

  pinMode(CLOCK_INT, INPUT_PULLUP);  // Datasheet states pullup resistor is necessary
  pinMode(FREE_RUN_BUTTON, INPUT_PULLUP);
  pinMode(ENCODER_BUTTON, INPUT_PULLUP);

  pinMode(FAN_MAIN_REL, OUTPUT);
  pinMode(FAN_SECOND_REL, OUTPUT);
  pinMode(LIGHT_REL, OUTPUT);
  pinMode(BUZZER, OUTPUT);
  pinMode(LDR_PWR, OUTPUT);
}

void ISR_PIR() {
  PIR_flag = true;
}

void ISR_ENCODER_BUTTON() {
  But_flag = true;
}

ISR(PCINT1_vect) {
  if (!digitalRead(FREE_RUN_BUTTON)) FreeRun_flag = true;  // Button has pullup resistor thus "!" is used.
}

ISR(TIMER1_COMPA_vect) {
  TIM1_flag = true;
  counter -= 5;
  if (counter <= 0) swCounter(0);
}


// Horrible Settings Codes xd:
ISR(PCINT2_vect) {  // Rotary Encoder Interrupt
  static bool aLastState;
  bool aState = digitalRead(ENC_A);
  bool bState = digitalRead(ENC_B);

  if (aState != aLastState) {
    if (bState != aState) {
      encVal++;
    } else {
      encVal--;
    }
    aLastState = aState;
  }
}

void ButDebounce(bool cmd1) {  // 0: Encoder But, 1: Free Run
  if (cmd1) {
    while (!digitalRead(FREE_RUN_BUTTON)) delay(100);  // Wait until button is released
    delay(200);
    FreeRun_flag = false;
  } else {
    while (!digitalRead(ENCODER_BUTTON)) delay(100);  // Wait until button is released
    delay(200);
    But_flag = false;
  }
}

String formatTime(int input, bool type = true) {  // type = true means input is seconds, if false it means input is minutes
  int hours = input / 3600;                       // Get the number of hours
  int minutes = (input % 3600) / 60;              // Get the number of minutes
  int remainingSeconds = input % 60;              // Get the number of seconds

  if (!type) {
    hours = minutes;
    minutes = remainingSeconds;
    remainingSeconds = 0;
  }

  // Create a formatted string in the format "hour:minute:second"
  String formattedTime = "";

  if (hours < 10) {
    formattedTime += "0";  // Add leading zero for hours
  }
  formattedTime += String(hours) + ":";

  if (minutes < 10) {
    formattedTime += "0";  // Add leading zero for minutes
  }
  formattedTime += String(minutes) + ":";

  if (remainingSeconds < 10) {
    formattedTime += "0";  // Add leading zero for seconds
  }
  formattedTime += String(remainingSeconds);

  return formattedTime;
}

uint8_t setEncVal(uint16_t target, uint8_t inc1, uint8_t inc2, uint16_t inc3, uint8_t lim1, uint8_t lim2) {
  uint16_t a = 0;
  uint8_t b = 0;
  while (a < target) {
    if (b < lim1) a += inc1;
    else if (b < lim2) a += inc2;
    else a += inc3;
    b++;
  }
  return b;
}

void Settings() {
  Settings_flag = true;
  swFanRelays(0);
  Beep(1, 250, 0);
  pinMode(ENC_A, INPUT_PULLUP);
  pinMode(ENC_B, INPUT_PULLUP);
  PCICR |= (1 << PCIE2);  // D0-D7 group can create interrupt
  ButDebounce(0);
  screenPwr(1);

  int16_t newVal;
  u8g2.setFont(u8g2_font_6x10_mr);

MainMenu:
  uint8_t index;
  index = 0;
  encVal = 3;
  while (!But_flag) {  // Chose Main Category
    if (FreeRun_flag) {
      ButDebounce(1);
      goto endSetting;
    }
    if (encVal <= 6 && encVal > 0 && index != encVal) {
      index = encVal;
      u8g2.firstPage();
      do {
        u8g2.drawStr(0, 8, "1. Fan Motion");
        u8g2.drawStr(0, 19, "2. Fan Dew");
        u8g2.drawStr(0, 30, "3. Light");
        u8g2.drawStr(0, 41, "4. DND");
        u8g2.drawStr(0, 52, "5. Free Run");
        u8g2.drawStr(0, 63, "6. Clock");
        u8g2.drawBox(0, index * 11 - 12, 128, 11);

      } while (u8g2.nextPage());
    } else if (encVal > 6) encVal = 1;
    else if (encVal <= 0) encVal = 6;
  }

  uint8_t index2;
  encVal = 1;

SubMenu:
  ButDebounce(0);

  switch (index) {  // Choose sub category
    case 1:         // Fan Motion Trig. Settings
      while (!But_flag) {
        if (FreeRun_flag) {
          ButDebounce(1);
          goto MainMenu;
        }
        if (encVal <= 3 && encVal > 0) {
          index2 = encVal;
          u8g2.firstPage();
          do {
            u8g2.drawStr(0, 7, "Fan Motion");
            u8g2.drawLine(0, 10, 128, 10);
            u8g2.drawStr(18, 30, "Trig:");
            if (FanMotTrigEnabled) u8g2.drawStr(54, 30, "On");
            else u8g2.drawStr(54, 30, "Off");
            u8g2.drawStr(18, 43, "Wait:");
            u8g2.setCursor(54, 43);
            u8g2.print(formatTime(waitDur));
            u8g2.setCursor(0, 56);
            u8g2.print("Runtime: ");
            u8g2.print(formatTime(Runtime));
            u8g2.drawBox(0, 8 + index2 * 13, 128, 11);

          } while (u8g2.nextPage());
        } else if (encVal > 3) encVal = 1;
        else if (encVal <= 0) encVal = 3;
      }
      ButDebounce(0);

      switch (index2) {  // Change Settings for Fan Motion Trig On/Off
        case 1:
          encVal = FanMotTrigEnabled;
          while (!But_flag) {
            if (FreeRun_flag) {
              ButDebounce(1);
              goto SubMenu;
            }
            if (encVal == 1 || encVal == 0) {
              newVal = encVal;
              u8g2.firstPage();
              do {
                u8g2.drawStr(0, 7, "Fan Mot. Trig");
                u8g2.drawLine(0, 10, 128, 10);
                u8g2.setFont(u8g2_font_lubB12_tr);
                u8g2.setCursor(30, 40);
                if (newVal) u8g2.print("On");
                else u8g2.print("Off");
                u8g2.setFont(u8g2_font_6x10_mr);
              } while (u8g2.nextPage());
            } else if (encVal > 1) encVal = 0;
            else if (encVal < 0) encVal = 1;
          }
          FanMotTrigEnabled = newVal;
          EEPROM.update(FanMotTrigEnabled_Address, FanMotTrigEnabled);
          break;

        case 2:  // Change Settings for Delay Dur
          encVal = setEncVal(waitDur, 10, 30, 60, 90, 120);
          while (!But_flag) {
            if (FreeRun_flag) {
              ButDebounce(1);
              goto SubMenu;
            }
            if (encVal <= 210 && encVal >= 0) {
              if (encVal <= 90) newVal = 10 * encVal;                     // 10 sec increments until 15 mins
              else if (encVal <= 120) newVal = 900 + 30 * (encVal - 90);  // 30 sec increments until 30 mins
              else newVal = 1800 + 60 * (encVal - 120);                   // 1 min increments until 2 hours
              u8g2.firstPage();
              do {
                u8g2.drawStr(0, 7, "Fan Mot. Delay");
                u8g2.drawLine(0, 10, 128, 10);
                u8g2.setFont(u8g2_font_lubB12_tr);
                u8g2.setCursor(30, 40);
                u8g2.print(formatTime(newVal));
                u8g2.setFont(u8g2_font_6x10_mr);
              } while (u8g2.nextPage());
            } else if (encVal > 210) encVal = 0;
            else if (encVal < 0) encVal = 210;
          }
          if (waitDur != newVal) {
            waitDur = newVal;
            EEPROM.put(waitDur_Address, waitDur);
          }
          break;

        case 3:  // Change settings for Runtime Dur
          encVal = setEncVal(Runtime, 10, 30, 60, 90, 120);
          while (!But_flag) {
            if (FreeRun_flag) {
              ButDebounce(1);
              goto SubMenu;
            }
            if (encVal <= 210 && encVal >= 1) {
              if (encVal <= 90) newVal = 10 * encVal;                     // 10 sec increments until 15 mins
              else if (encVal <= 120) newVal = 900 + 30 * (encVal - 90);  // 30 sec increments until 30 mins
              else newVal = 1800 + 60 * (encVal - 120);                   // 1 min increments until 2 hours
              u8g2.firstPage();
              do {
                u8g2.drawStr(0, 7, "Fan Mot. Runtime");
                u8g2.drawLine(0, 10, 128, 10);
                u8g2.setFont(u8g2_font_lubB12_tr);
                u8g2.setCursor(30, 40);
                u8g2.print(formatTime(newVal));
                u8g2.setFont(u8g2_font_6x10_mr);
              } while (u8g2.nextPage());
            } else if (encVal > 210) encVal = 1;
            else if (encVal < 1) encVal = 210;
          }
          if (Runtime != newVal) {
            Runtime = newVal;
            EEPROM.put(Runtime_Address, Runtime);
          }
          break;
      }
      goto SubMenu;
      break;
    case 2:  // Fan Dew Trig. Settings
      while (!But_flag) {
        if (FreeRun_flag) {
          ButDebounce(1);
          goto MainMenu;
        }
        if (encVal <= 3 && encVal > 0) {
          index2 = encVal;
          u8g2.firstPage();
          do {
            u8g2.drawStr(0, 7, "Fan Dew");
            u8g2.drawLine(0, 10, 128, 10);
            u8g2.drawStr(42, 30, "Trig:");
            if (DewTrigEnabled) u8g2.drawStr(78, 30, "On");
            else u8g2.drawStr(78, 30, "Off");
            u8g2.drawStr(36, 43, "Thres:");
            u8g2.setCursor(78, 43);
            u8g2.print(DewThres, 1);
            u8g2.setCursor(0, 56);
            u8g2.print("Extr. Thres: ");
            u8g2.print(DewThresExtreme, 1);
            u8g2.drawBox(0, 8 + index2 * 13, 128, 11);

          } while (u8g2.nextPage());
        } else if (encVal > 3) encVal = 1;
        else if (encVal <= 0) encVal = 3;
      }

      ButDebounce(0);

      switch (index2) {  // Change Settings of Dew On/Off
        case 1:
          encVal = DewTrigEnabled;
          while (!But_flag) {
            if (FreeRun_flag) {
              ButDebounce(1);
              goto SubMenu;
            }
            if (encVal == 1 || encVal == 0) {
              newVal = encVal;
              u8g2.firstPage();
              do {
                u8g2.drawStr(0, 7, "Fan Dew Trig");
                u8g2.drawLine(0, 10, 128, 10);
                u8g2.setFont(u8g2_font_lubB12_tr);
                u8g2.setCursor(30, 40);
                if (newVal) u8g2.print("On");
                else u8g2.print("Off");
                u8g2.setFont(u8g2_font_6x10_mr);
              } while (u8g2.nextPage());
            } else if (encVal > 1) encVal = 0;
            else if (encVal < 0) encVal = 1;
          }
          DewTrigEnabled = newVal;
          EEPROM.update(DewTrigEnabled_Address, DewTrigEnabled);
          break;
        case 2:  // Change setings for DewThres
          encVal = 10 * DewThres;
          while (!But_flag) {
            if (FreeRun_flag) {
              ButDebounce(1);
              goto SubMenu;
            }
            if (encVal < DewThresExtreme * 10 && encVal >= 0) {
              newVal = encVal;
              u8g2.firstPage();
              do {
                u8g2.drawStr(0, 7, "Fan Dew Thres");
                u8g2.drawLine(0, 10, 128, 10);
                u8g2.setFont(u8g2_font_lubB12_tr);
                u8g2.setCursor(30, 40);
                u8g2.print(newVal / 10.0, 1);
                u8g2.setFont(u8g2_font_6x10_mr);
                if (encVal == 10 * DewThresExtreme - 1) u8g2.drawStr(0, 64, "Extr Dew Thres Limit!");
              } while (u8g2.nextPage());
            } else if (encVal >= DewThresExtreme * 10) encVal = 10 * DewThresExtreme - 1;
            else if (encVal < 0) encVal = 0;
          }
          if (DewThres != newVal / 10.0) {
            DewThres = newVal / 10.0;
            EEPROM.put(DewThres_Address, DewThres);
          }
          break;
        case 3:  // Change settings for Extr. Dew Thres
          encVal = 10 * DewThresExtreme;
          while (!But_flag) {
            if (FreeRun_flag) {
              ButDebounce(1);
              goto SubMenu;
            }
            if (encVal <= 300 && encVal > DewThres * 10) {
              newVal = encVal;
              u8g2.firstPage();
              do {
                u8g2.drawStr(0, 7, "Fan Dew Extr. Thres");
                u8g2.drawLine(0, 10, 128, 10);
                u8g2.setFont(u8g2_font_lubB12_tr);
                u8g2.setCursor(30, 40);
                u8g2.print(newVal / 10.0, 1);
                u8g2.setFont(u8g2_font_6x10_mr);
                if (encVal == 10 * DewThres + 1) u8g2.drawStr(0, 64, "Dew Thres Limit!");
              } while (u8g2.nextPage());
            } else if (encVal > 300) encVal = 0;
            else if (encVal <= DewThres * 10) encVal = DewThres * 10 + 1;
          }
          if (DewThresExtreme != newVal / 10.0) {
            DewThresExtreme = newVal / 10.0;
            EEPROM.put(DewThresExtreme_Address, DewThresExtreme);
          }
          break;
      }
      goto SubMenu;
      break;
    case 3:  // Light Settings
      while (!But_flag) {
        if (FreeRun_flag) {
          ButDebounce(1);
          goto MainMenu;
        }
        if (encVal <= 3 && encVal > 0) {
          index2 = encVal;
          u8g2.firstPage();
          do {
            u8g2.drawStr(0, 7, "Light");
            u8g2.drawLine(0, 10, 128, 10);
            u8g2.setCursor(30, 30);
            u8g2.print("Trig: ");
            if (LightRelEnabled) u8g2.print("On");
            else u8g2.print("Off");
            u8g2.setCursor(36, 43);
            u8g2.print("LDR: ");
            if (LDRenabled) u8g2.print("On");
            else u8g2.print("Off");
            u8g2.setCursor(0, 56);
            u8g2.print("LDR Thres: ");
            u8g2.print(LDRThres);
            u8g2.drawBox(0, 8 + index2 * 13, 128, 11);

          } while (u8g2.nextPage());
        } else if (encVal > 3) encVal = 1;
        else if (encVal <= 0) encVal = 3;
      }

      ButDebounce(0);

      switch (index2) {  // Change Settings of Light Enabled
        case 1:
          encVal = LightRelEnabled;
          while (!But_flag) {
            if (FreeRun_flag) {
              ButDebounce(1);
              goto SubMenu;
            }
            if (encVal == 1 || encVal == 0) {
              newVal = encVal;
              u8g2.firstPage();
              do {
                u8g2.drawStr(0, 7, "Light Mot Trig");
                u8g2.drawLine(0, 10, 128, 10);
                u8g2.setFont(u8g2_font_lubB12_tr);
                u8g2.setCursor(30, 40);
                if (newVal) u8g2.print("On");
                else u8g2.print("Off");
                u8g2.setFont(u8g2_font_6x10_mr);
              } while (u8g2.nextPage());
            } else if (encVal > 1) encVal = 0;
            else if (encVal < 0) encVal = 1;
          }
          LightRelEnabled = newVal;
          EEPROM.update(LightRelEnabled_Address, LightRelEnabled);
          break;
        case 2:  // Change settings for LDR enabled
          encVal = LDRenabled;
          while (!But_flag) {
            if (FreeRun_flag) {
              ButDebounce(1);
              goto SubMenu;
            }
            if (encVal == 1 || encVal == 0) {
              newVal = encVal;
              u8g2.firstPage();
              do {
                u8g2.drawStr(0, 7, "LDR");
                u8g2.drawLine(0, 10, 128, 10);
                u8g2.setFont(u8g2_font_lubB12_tr);
                u8g2.setCursor(30, 40);
                if (newVal) u8g2.print("On");
                else u8g2.print("Off");
                u8g2.setFont(u8g2_font_6x10_mr);
              } while (u8g2.nextPage());
            } else if (encVal > 1) encVal = 0;
            else if (encVal < 0) encVal = 1;
          }
          LDRenabled = newVal;
          EEPROM.update(LDRenabled_Address, LDRenabled);
          break;
        case 3:  // Change settings for LDR Thres
          encVal = LDRThres;
          uint16_t currentRead;
          digitalWrite(LDR_PWR, HIGH);
          delay(10);
          while (!But_flag) {
            if (FreeRun_flag) {
              ButDebounce(1);
              goto SubMenu;
            }
            currentRead = analogRead(LDR_READ);
            if (encVal <= 1024 && encVal >= 0) {
              newVal = encVal;
              u8g2.firstPage();
              do {
                u8g2.drawStr(0, 7, "LDR Thres");
                u8g2.drawLine(0, 10, 128, 10);
                u8g2.setFont(u8g2_font_lubB12_tr);
                u8g2.drawStr(12, 40, "Set:");
                u8g2.setCursor(50, 40);
                u8g2.print(newVal);
                u8g2.drawStr(0, 60, "Now:");
                u8g2.setCursor(50, 60);
                u8g2.print(currentRead);
                u8g2.setFont(u8g2_font_6x10_mr);
                if (currentRead > newVal) u8g2.drawStr(90, 60, "Dark");
                else u8g2.drawStr(90, 60, "Bright");
              } while (u8g2.nextPage());
            } else if (encVal > 1024) encVal = 0;
            else if (encVal < 0) encVal = 1024;
          }
          digitalWrite(LDR_PWR, LOW);
          if (LDRThres != newVal) {
            LDRThres = newVal;
            EEPROM.put(LDRThres_Address, LDRThres);
          }
          break;
      }
      goto SubMenu;
      break;
    case 4:  // Change Settings for Do Not Disturb
      encVal = DNDenabled;
      while (!But_flag) {  // change DNDenabled
        if (FreeRun_flag) {
          ButDebounce(1);
          goto MainMenu;
        }
        if (encVal == 1 || encVal == 0) {
          newVal = encVal;
          u8g2.firstPage();
          do {
            u8g2.drawStr(0, 7, "DND");
            u8g2.drawLine(0, 10, 128, 10);
            u8g2.setFont(u8g2_font_lubB12_tr);
            u8g2.setCursor(30, 40);
            if (newVal) u8g2.print("On");
            else u8g2.print("Off");
            u8g2.setFont(u8g2_font_6x10_mr);
          } while (u8g2.nextPage());
        } else if (encVal > 1) encVal = 0;
        else if (encVal < 0) encVal = 1;
      }
      DNDenabled = newVal;
      EEPROM.update(DND_Enabled_Address, DNDenabled);
      ButDebounce(0);
      encVal = (60 * DND_SH + DND_SM) / 15;
      while (!But_flag) {  // change DNDStart
        if (FreeRun_flag) {
          ButDebounce(1);
          goto MainMenu;
        }
        if (encVal < 96 && encVal >= 0) {
          newVal = 15 * encVal;
          u8g2.firstPage();
          do {
            u8g2.drawStr(0, 7, "DND Start");
            u8g2.drawLine(0, 10, 128, 10);
            u8g2.setFont(u8g2_font_lubB12_tr);
            u8g2.setCursor(30, 40);
            u8g2.print(formatTime(newVal, false));
            u8g2.setFont(u8g2_font_6x10_mr);
          } while (u8g2.nextPage());
        } else if (encVal >= 96) encVal = 0;
        else if (encVal < 0) encVal = 95;
      }
      DND_SH = newVal / 60;  // Get the number of hours
      DND_SM = (newVal % 60);
      EEPROM.update(DND_SH_Address, DND_SH);
      EEPROM.update(DND_SM_Address, DND_SM);
      ButDebounce(0);

      encVal = (60 * DND_FH + DND_FM) / 15;
      while (!But_flag) {  // // change DNDfinish
        if (FreeRun_flag) {
          ButDebounce(1);
          goto MainMenu;
        }
        if (encVal < 96 && encVal >= 0) {
          newVal = 15 * encVal;
          u8g2.firstPage();
          do {
            u8g2.drawStr(0, 7, "DND Finish");
            u8g2.drawLine(0, 10, 128, 10);
            u8g2.setFont(u8g2_font_lubB12_tr);
            u8g2.setCursor(30, 40);
            u8g2.print(formatTime(newVal, false));
            u8g2.setFont(u8g2_font_6x10_mr);
          } while (u8g2.nextPage());
        } else if (encVal >= 96) encVal = 0;
        else if (encVal < 0) encVal = 95;
      }
      DND_FH = newVal / 60;  // Get the number of hours
      DND_FM = (newVal % 60);
      EEPROM.update(DND_FH_Address, DND_FH);
      EEPROM.update(DND_FM_Address, DND_FM);
      break;
    case 5:  // Free Run Settings
      encVal = setEncVal(FreeRunDur, 30, 60, 300, 60, 90);
      while (!But_flag) {
        if (FreeRun_flag) {
          ButDebounce(1);
          goto MainMenu;
        }
        if (encVal <= 138 && encVal >= 0) {
          if (encVal <= 60) newVal = 30 * encVal;                     // 30 sec incriments until 30 mins
          else if (encVal <= 90) newVal = 1800 + 60 * (encVal - 60);  // 1 min incriments until 1 hour
          else newVal = 3600 + 300 * (encVal - 90);                   // 5 mins incriments until 5 hour
          u8g2.firstPage();
          do {
            u8g2.drawStr(0, 7, "Free Run Duration");
            u8g2.drawLine(0, 10, 128, 10);
            u8g2.setFont(u8g2_font_lubB12_tr);
            u8g2.setCursor(30, 40);
            u8g2.print(formatTime(newVal));
            u8g2.setFont(u8g2_font_6x10_mr);
          } while (u8g2.nextPage());
        } else if (encVal > 138) encVal = 0;
        else if (encVal < 0) encVal = 138;
      }
      if (FreeRunDur != newVal) {
        FreeRunDur = newVal;
        EEPROM.put(FreeRunDur_Address, FreeRunDur);
      }
      break;
    case 6:  // Clock Settings
      updateTime();
      encVal = 60 * hour + minute;
      while (!But_flag) {
        if (FreeRun_flag) {
          ButDebounce(1);
          goto MainMenu;
        }
        if (encVal < 1440 && encVal >= 0) {
          newVal = encVal;
          u8g2.firstPage();
          do {
            u8g2.drawStr(0, 7, "Set Clock");
            u8g2.drawLine(0, 10, 128, 10);
            u8g2.setFont(u8g2_font_lubB12_tr);
            u8g2.setCursor(30, 40);
            u8g2.print(formatTime(newVal, false));
            u8g2.setFont(u8g2_font_6x10_mr);
          } while (u8g2.nextPage());
        } else if (encVal >= 1440) encVal = 0;
        else if (encVal < 0) encVal = 1439;
      }
      hour = newVal / 60;  // Get the number of hours
      minute = (newVal % 60);
      myRTC_2.setHour(hour);
      myRTC_2.setMinute(minute);
      myRTC_2.setSecond(0);
      break;
  }
  ButDebounce(0);
  goto MainMenu;
endSetting:
  PCICR &= ~(1 << PCIE2);  // D0-D7 group canNOT create interrupt
  pinMode(ENC_A, INPUT);
  pinMode(ENC_B, INPUT);
  swCounter(0);
  Beep(2, 100, 100);
  PIR_flag = false;
  But_flag = false;
  FreeRun_flag = false;
  Settings_flag = false;
}

I2C pullup resistors ? Are these on the modules ?

Well, two possible problems.

  • Your soldering does not look all that good, there may be some COLD solder joints.

  • It's a clone, they do go bad.

1 Like

The modules I am using have the pull up resistors on board. I guess wire library turns internal Arduino pull-up resistors too. I've just measured when the power is off and it seems 2.2k each. I've measured pulldown too somehow which differes betweed 2.4k and 6.6k depending on how I hold the probes, probably not relevant right?

Are you sure my solder is bad? I hoped I just used too much of it that's why it looks bulky also there is copper layer underneath the solder. The clone might be the problem but first let's make sure the script isn't error prone.

No I'm not sure but big blobs is a good indication of a bad solder joint

Try a simple LED blinky program WITHOUT using any Wire function.
If it still gets stuck, then it's a hardware problem and not software.

U8g2lib.h can take up a lot of memory.
When compiling, do you get a 'warning' about being low on memory?

The only place that simple sketch can get stuck is accessing the display which means using wire. Eliminating those will mean the blinks program will probably work fine, and the hardware problem untested.

I'd use an I2C scanner and just repeat getting the device to respond at all until… it did not.

And try a different I2C device in the same experiment. It just sounds like a dodgy I2C implementation.

a7

Are the Mosfets drawn upside down ? The image quality is not very good (on my screen).

EDIT

These look OK with higher magnification.
I'd put a 100nF ceramic cap across the power supply output.

Static electricity, heat, just to name a few..
I'd change the board, get one made..
or try some conformal coating on the bottom of existing board..

as a test, breadboard the mpu and screen..

sorry.. ~q

Not true.

A bad solder joint can cause the same symtoms as well as a bad clone.

1 Like

Okay I will answer everyone now.

Delta, my power source is 3W HLK-PM01 (as in schematic)

jim, I tried to blink the led only, it works fine but whenever I2C is involved it fails.

runaway, no additional warning over this:
"Sketch uses 29638 bytes (96%) of program storage space. Maximum is 30720 bytes.
Global variables use 1145 bytes (55%) of dynamic memory, leaving 903 bytes for local variables. Maximum is 2048 bytes."

alto, I tried white version of the same OLED, same stuck occured.

6v6gt, mosfets are correct, I told you, the whole system behaved perfectly in the first 2 days (except the oled was making an annoying buzing sound that also occurs in some power adapters but I tired other OELD and it was fine, I will replace).

qubits, the board works fine on a breadboard test with the oled screen so not sure if the arduino is causing the problem. If my pcb is the problem, how it is so simple? I am really confused. I faced similar stuck problem with sht31 and oled on a different project, I tired again on that project and it worked, I don't understand why somethnig stops working and than starts working again without me changing anything.

I need to make long lasting breadboard tests to see if it will get stuck in hours. I need to try each module individually.

Or maybe it is the noise (how much of a noise that 3W power supply create meeh)
Well okay, I will first make a long lasting breadboard test if modules work individually and together I will retouch the solder connections with my 60W iron. If solder doesn't fix it maybe it is the socket I plug the arduino in, I will remove the back headers and solder it from the top in order to fully plug it in (because I cant put it fully in the socket right now). If it is a simple connectivity issue I will bang my had to the walls. I need time for to do all of it.

Not surprising.

Did you post the memory requirements for the tiny testing sketch? Or is what you listed for your "real" project?

I think there is an OLED library or means of using it that would use a smaller buffer.

a7

I've sent you the 'real' projects data, the other one would take only %10 of space or something. It can't be memory problem because those tiny sketches also cause freeze. One of which doesn't include screen at all.

Bad solder joints on the SCL and/or SDA lines.

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