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;
}