Import ESP_UniClock (upstream 1189810)

Vendored from https://github.com/gpeter62/ESP_UniClock: the sketch
source, the web UI and the libraries the ESP8266 build needs, unpacked
from the bundled zips. Adds a PlatformIO project for the ESP-12. The
source is unchanged; local changes follow as separate commits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-25 20:48:04 +01:00
1 parent 8efa810e4e
commit b347f1c63f
843 files changed
+122061

No files matched your search

+534
View File
@@ -0,0 +1,534 @@
#ifdef USE_RTC
//====================================================================================================
// rtc.ino - DS3231 realtime clock driver, with optional manual clock-setup pushbuttons
//====================================================================================================
// Chip: Maxim/Analog DS3231 I2C RTC (temperature-compensated crystal oscillator, +-2ppm accuracy).
// Talked to via the Makuna "Rtc_by_Makuna" library (RtcDS3231<TwoWire>), see
// libraries/Rtc_by_Makuna.zip. Uses the Arduino `Wire` (I2C) object, so it shares the
// I2C bus with any other I2C sensors defined for the clock (see I2Csensors.ino).
// Wiring: I2C bus only - PIN_SDA / PIN_SCL, set per-CLOCK_NN in clocks.h (any GPIO pair works,
// it's not necessarily the chip's "default" I2C pins). Presence is auto-detected at
// startup by I2Csensors.ino (probes address 0x68) and reported in the RTCexist flag.
// Role in the system: the DS3231 is a battery-backed FALLBACK time source, not the primary one.
// NTP (WiFi) and/or GPS are preferred whenever available; the RTC is only read to set
// the running clock when neither NTP nor GPS refreshed the time on that update cycle,
// and it is written to (kept in sync) whenever NTP/GPS DID refresh the time - so after
// a power loss/reset with no network, the clock still starts from a decent time.
// See calcTime() in ESP32_UniClock2.ino:
// if (RTCexist) { if (refreshed || refreshed2) updateRTC(); else getRTC(); }
// Manual set: on clocks that define PIN_FLD_BUTTON/PIN_SET_BUTTON (and optionally
// PIN_MODE_SWITCH), a long-press on the FLD button enters/leaves a manual date/time
// editor (editor()/showValue()); the SET button changes the value of the currently
// selected field. See scanButFLD()/scanButSET() below for the button state machine.
// Used by: CLOCK_5 (IVL-2 VFD clock, MAX6921, has RTC+buttons)
// CLOCK_15a (IV-6 VFD clock RGB)
// CLOCK_17 (PT6311 IV-22 russian clock)
// CLOCK_35 (Z566M/IN-1 tubes, non-multiplex driver)
// CLOCK_52 (2xHV5122 IN18 clock, PROTOTYPE)
// CLOCK_71 (2x VQC10 clock - USE_RTC defined here only to also enable the pushbutton
// code below; this build has no physical DS3231, RTCexist stays false)
// CLOCK_80/81/82 (word clocks EN/DE/HU - RTC only, PIN_FLD_BUTTON/PIN_SET_BUTTON are
// commented out on these builds, so the manual editor is never entered)
// (CLOCK_50 and CLOCK_99 have USE_RTC available but commented out by default)
//====================================================================================================
#include <RtcDS3231.h> //Makuna/RTC
RtcDS3231<TwoWire> Rtc(Wire);
RtcDateTime rtcNow;
RtcDateTime compiled = RtcDateTime(__DATE__, __TIME__); //firmware build timestamp, used as a sane fallback date if the RTC ever loses confidence (e.g. dead backup battery)
#define EPOCH_OFFSET 946684800l //offset from 1970->2000 epoch start; the Makuna library's RtcDateTime counts seconds since 2000-01-01, while TimeLib's now() is a standard Unix epoch (1970) - subtract this constant to convert Unix-epoch -> DS3231 epoch
//------ Mode Switch and Push Buttons ---------------------
#ifndef PIN_MODE_SWITCH
#define PIN_MODE_SWITCH -1 //-1 = "not wired on this clock", all buttoncode below is compiled out via the #if PIN_xxx>=0 guards
#endif
#ifndef PIN_FLD_BUTTON
#define PIN_FLD_BUTTON -1
#endif
#ifndef PIN_SET_BUTTON
#define PIN_SET_BUTTON -1
#endif
boolean pushedFldButtonValue = LOW; //which logic level means "button pushed"
boolean pushedSetButtonValue = LOW; //which logic level means "button pushed"
//------- I2C bus definition Any pins are usable Please, SET in clocks.h --------
//#define PIN_SDA 4 //D2 original general setup for 8266
//#define PIN_SCL 5 //D1
#define MENU_UNSELECT_TIMEOUT 30000 //NOTE: defined but not actually read/used anywhere in this file (LastModify is set on every keypress but never compared against this timeout) - the editor only exits via a long FLD press, not by an idle timeout. Left in place for a possible future auto-exit-on-idle feature.
int monthdays[13] = {0,31,29,31,30,31,30,31,31,30,31,30,31}; //days-per-month lookup (index 1-12, Feb hardcoded to 29/leap); NOTE: not currently referenced anywhere in this file - no leap-year/day-overflow validation is actually performed when editing the Day field (see scanButSET())
struct Menu {
short lowlimit; // lower limit for this field's value
short uplimit; // upper limit for this field's value
char name[5]; // short label shown on the display while editing this field
};
// Editable fields for manual time setting, indexed by `fld` (1..MAXFLD). Index 0 is a dummy/unused
// "no field selected" placeholder so `fld==0` can mean "editor not active" without needing -1 checks.
const struct Menu m1[]= {
{0,0," "},
{2022,2099,"Year"},
{1,12,"Mon "},
{1,31,"Day "}, //NOTE: fixed 1..31 range regardless of month/leap-year - editing Feb 30 or Apr 31 is not prevented, see scanButSET()
{0,23,"Hour"},
{0,59,"Min "},
};
#define MAXFLD 5
int mvar[MAXFLD+1]; //working copy of the field being edited: mvar[1]=year, [2]=month, [3]=day, [4]=hour, [5]=minute (seconds are always reset to 0 on save, see saveRTC())
// Pushbutton debounce/press-length state machine (used identically by both FLD and SET buttons):
// IS_OPEN idle, button released
// IS_PUSHING/PUSHED button just went down (one extra state to timestamp the press)
// IS_LONGPRESSING/-ED button has been held past the long-press threshold (~2s) - action fires once, on entry
// IS_OPENING button just released - if this happened quickly (<500ms since push) it's a "short press"
// IS_LONGOPENING button released after a long press - swallow the release so it isn't also seen as a short press
enum switchStates {IS_OPEN, IS_OPENING, IS_PUSHED, IS_LONGPRESSING, IS_LONGPRESSED, IS_PUSHING, IS_LONGOPENING};
int fld = 0; //0 = manual editor not active; 1..MAXFLD = index into m1[]/mvar[] of the field currently being edited
unsigned long LastModify = 0; //timestamp of the last field change while editing; set by scanButFLD/scanButSET, only used as a "was anything changed?" flag by editor() to decide whether saveRTC() is needed on exit (NOT used for the MENU_UNSELECT_TIMEOUT idle-exit that its name might suggest)
void setupRTC() {
#if PIN_MODE_SWITCH>=0
pinMode(PIN_MODE_SWITCH,INPUT_PULLUP);
regPin(PIN_MODE_SWITCH,"PIN_MODE_SWITCH");
#endif
#if PIN_FLD_BUTTON>=0
pinMode(PIN_FLD_BUTTON,INPUT_PULLUP);
regPin(PIN_FLD_BUTTON,"PIN_FLD_BUTTON");
// Auto-detect button polarity at boot: with INPUT_PULLUP the idle (unpressed) line should read
// HIGH. If it reads LOW here instead, the wiring/hardware inverts the logic (e.g. external
// pull-down, or the button is wired to the pin differently than the internal-pullup default),
// so remember that for this board "pressed" actually reads HIGH, not LOW.
// NOTE: this assumes the button is NOT physically held down at power-on - if it were, polarity
// detection here would be wrong for the rest of the session.
if (digitalRead(PIN_FLD_BUTTON) == LOW) //inverted button?
pushedFldButtonValue = HIGH;
#endif
#if PIN_SET_BUTTON>=0
pinMode(PIN_SET_BUTTON,INPUT_PULLUP);
regPin(PIN_SET_BUTTON,"PIN_SET_BUTTON");
if (digitalRead(PIN_SET_BUTTON) == LOW) //inverted button?
pushedSetButtonValue = HIGH;
#endif
if (RTCexist) {
DPRINTLN("Connecting to RTC clock...");
Rtc.Begin();
if (!Rtc.IsDateTimeValid()) {
// IsDateTimeValid() also surfaces DS3231-specific fault conditions (oscillator stop flag,
// invalid register values) via LastError(), distinct from a plain I2C communication failure.
if (Rtc.LastError() != 0) {
// A nonzero LastError() here means the I2C transaction itself failed (bus error / chip
// not responding) - do NOT touch the RTC's stored time in that case, since we can't
// trust what we'd be reading or writing over a broken bus.
DPRINT("RTC communications error = "); DPRINTLN(Rtc.LastError());
}
else {
// I2C is fine, but the chip reports its own datetime as invalid - typically means the
// DS3231's Oscillator Stop Flag (OSF) is set, i.e. the coin-cell backup battery died (or
// is missing/depleted) at some point and the chip lost power, so its date/time can no
// longer be trusted. Reseed it with the firmware's own compile timestamp as a "better
// than nothing" starting point until NTP/GPS can correct it.
DPRINTLN("RTC lost confidence in the DateTime!");
Rtc.SetDateTime(compiled); //Set RTC time to firmware compile time
}
}
if (!Rtc.GetIsRunning()) {
// The DS3231 has an Enable Oscillator (EOSC) bit that can leave the clock halted even
// though the chip itself answers on I2C - make sure the oscillator/counting is actually on.
DPRINTLN("RTC was not actively running, starting now");
Rtc.SetIsRunning(true);
}
// Disable the 32kHz output and the SQW/INT pin's square-wave function - this firmware never
// uses the DS3231 as an interrupt/alarm source, so both are turned off to save a little power
// and avoid an unused, possibly-floating/toggling pin on the board.
Rtc.Enable32kHzPin(false);
Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeNone);
}
/*
while (true) {
DPRINT(digitalRead(PIN_MODE_SWITCH)); DPRINT(" / ");
DPRINT(digitalRead(PIN_FLD_BUTTON)); DPRINT(" / ");
DPRINTLN(digitalRead(PIN_SET_BUTTON));
delay(1000);
}
*/
}
// Called once per main loop() iteration (unconditionally, whenever USE_RTC is defined). Most of the
// time this is a cheap no-op: it just polls the FLD button once (scanButFLD) and returns immediately
// because fld==0 (editor not active). Only when a long FLD-press flips fld to nonzero does it drop
// into the inner while(true) loop below - and while that loop runs, it BLOCKS the rest of loop() (no
// WiFi/animation/sensor/etc. processing happens) until the user long-presses FLD again to save & exit.
void editor() {
static unsigned long lastDisplay = 0;
if (RTCexist) {rtcNow = Rtc.GetDateTime();} //note: read but not otherwise used here - editor() re-reads current time into mvar[] via getRTC() only at the moment edit mode is entered (see scanButFLD's IS_LONGPRESSING case)
LastModify = 0;
#if PIN_FLD_BUTTON >= 0 && PIN_SET_BUTTON >= 0 //Are buttons installed?
editorRunning = true; //flag read elsewhere (e.g. wordclock.ino) to show an "editing" indicator on the display
while (true) {
scanButFLD(100); //100ms poll interval for both buttons - also doubles as the blocking loop's own pacing/refresh rate
if (fld == 0) {
editorRunning = false;
break;
}
scanButSET(100);
showValue();
#if defined(WORDCLOCK)
if ((millis()-lastDisplay)>100) {
lastDisplay = millis();
writeDisplay2();
}
#else
writeDisplaySingle();
#endif
printDigits(1000);
Fdelay(100);
}
#endif
if (LastModify != 0) saveRTC(); //only persist if at least one field was actually changed during this edit session
}
// Renders the value of the field currently being edited (mvar[fld]) into the shared `digit[]`
// display buffer (digit 10 = blank, used by memset to clear unused positions first).
void showValue() {
memset(digit,10,sizeof(digit)); //clear array
memset(digitDP,0,sizeof(digitDP));
if(fld==1) {
//Year is the only 4-digit field (e.g. 2026) - split into thousands/hundreds/tens/units across digit[0..3]
digit[3] = mvar[1] / 1000;
digit[2] = (mvar[1] % 1000) / 100;
digit[1] = (mvar[1] % 100) / 10;
digit[0] = mvar[1] % 10;
}
else {
//Month/Day/Hour/Minute are all 1-2 digit values - tens/units into digit[0..1]
digit[1] = mvar[fld] / 10;
digit[0] = mvar[fld] % 10;
}
#if defined(MAX6921) || defined(MAX6921_ESP32)
//character set not available yet (MAX6921-driven displays can't render the alphabetic field-name label below, only digits)
#else
// On displays with more than 4 digit positions, show the field's short text label (e.g. "Year",
// "Hour") in the upper digit positions alongside the numeric value, so the user knows which field
// they're editing. Needs >=6 digits to fit 3 label characters + a ':' separator, or >=8 to fit the
// full 4-character label without the ':' filler.
if (!digitsOnly && (maxDigits>4)) {
if (maxDigits>=8) digit[7] = m1[fld].name[0];
if (maxDigits>=8) digit[6] = m1[fld].name[1];
if (maxDigits>=6) digit[5] = m1[fld].name[2];
else digit[5] = ':'; //not enough digit positions for the 3rd label character - show a separator instead
digit[4] = m1[fld].name[3];
}
#endif
}
//------------------------------------- RTC Clock functions --------------------------------------------------------------------
// Called from calcTime() (ESP32_UniClock2.ino) ONLY on cycles where the system time (TimeLib's
// now()) was just refreshed from NTP and/or GPS this same cycle - i.e. "we just got a trustworthy
// time from the network/satellites, so push it into the battery-backed RTC to keep it in sync as
// the fallback source for the next time we DON'T have network/GPS (e.g. after a reboot)."
void updateRTC() {
DPRINTLN("Update RTC?");
if (!RTCexist) return;
if (year()<2020) return; //invalid system date?? sanity guard so a not-yet-synced/garbage system clock can never be written into the RTC
rtcNow = Rtc.GetDateTime();
// Only actually write the RTC if it disagrees with the system time by more than the noise you'd
// expect from normal read/processing latency (>2s on seconds, or any mismatch on minute/hour/
// day/month/year) - avoids needless I2C writes to the RTC on every single refresh cycle.
if (abs(rtcNow.Second() - second())>2 || (rtcNow.Minute() != minute()) || (rtcNow.Hour() != hour())
|| (rtcNow.Day() != day()) || (rtcNow.Month() != month()) || (rtcNow.Year() != year()) ) {
DPRINTLN("Updating RTC from TimeServer...");
DPRINT("- DS3231 date:"); DPRINT(rtcNow.Year()); DPRINT("/"); DPRINT(rtcNow.Month()); DPRINT("/"); DPRINT(rtcNow.Day());
DPRINT(" time:"); DPRINT(rtcNow.Hour()); DPRINT(":"); DPRINT(rtcNow.Minute()); DPRINT(":"); DPRINTLN(rtcNow.Second());
DPRINT("- Tserver date:"); DPRINT(year()); DPRINT("/"); DPRINT(month()); DPRINT("/"); DPRINT(day());
DPRINT(" time:"); DPRINT(hour()); DPRINT(":"); DPRINT(minute()); DPRINT(":"); DPRINTLN(second());
Rtc.SetDateTime(now()-EPOCH_OFFSET); //update RTC with UNIX epoch timestamp to 2000year epoch
} //endif
}
// Called from calcTime() on cycles where NTP/GPS did NOT refresh the time this cycle - the DS3231
// is the fallback: read its current date/time and push it into TimeLib via setTime() so the rest
// of the firmware (display, animations, etc., all of which read time via now()/hour()/etc.) sees a
// continuously-updated clock even with no network/GPS. Also refreshes mvar[] with the (now current)
// date/time fields, which is what the manual editor uses as its starting point when entered next
// (see scanButFLD's IS_LONGPRESSING case calling getRTC() to seed mvar[] with "now").
void getRTC() {
if (RTCexist) {
if (!Rtc.IsDateTimeValid()) {
if (Rtc.LastError() != 0) {
// I2C communication problem - do not touch system time this cycle; whatever TimeLib
// already has (from the last successful update) is left running/ticking on its own.
DPRINT("RTC communications error = "); DPRINTLN(Rtc.LastError());
}
else {
// Oscillator Stop Flag set (dead/missing backup battery, or never-yet-set RTC) - the
// chip's stored date/time can't be trusted, so it is deliberately NOT read into
// setTime() here; system time is simply left as-is for this cycle.
DPRINTLN("RTC lost confidence in the DateTime!");
}
}
else {
rtcNow = Rtc.GetDateTime();
//DPRINT("\nGet DS3231 data&time: "); DPRINT(rtcNow.Year()); DPRINT("/"); DPRINT(rtcNow.Month()); DPRINT("/"); DPRINT(rtcNow.Day());
//DPRINT(" "); DPRINT(rtcNow.Hour()); DPRINT(":"); DPRINT(rtcNow.Minute()); DPRINT(":"); DPRINTLN(rtcNow.Second());
setTime(rtcNow.Hour(),rtcNow.Minute(),rtcNow.Second(),rtcNow.Day(),rtcNow.Month(),rtcNow.Year()); //set the time (hr,min,sec,day,mnth,yr)
}
}
// NOTE: mvar[] is refreshed from now()/year()/month()/etc. below unconditionally, even when
// RTCexist is false (or the RTC read above was skipped due to an invalid/error state) - in that
// case it simply reflects whatever TimeLib's current system time already is (e.g. last NTP time,
// or millis()-derived free-running time), which is a reasonable "best current guess" starting
// point for the manual editor on hardware with no RTC chip (see CLOCK_71 in clocks.h).
mvar[1] = year(); mvar[2] = month(); mvar[3] = day();
mvar[4] = hour(); mvar[5] = minute();
}
// Commits the values edited via the manual FLD/SET buttons (mvar[1..5]) back into the running
// system clock, and - if present - into the DS3231 as well. Called once, when the editor is exited
// (long FLD press while fld>0), only if at least one field was actually changed (see editor()).
void saveRTC() {
setTime(mvar[4],mvar[5],0,mvar[3],mvar[2],mvar[1]); //set the time (hr,min,sec,day,mnth,yr) - seconds are always reset to :00 since there's no seconds field in the editor
if (RTCexist) {
DPRINTLN("Updating RTC from Manual settings...");
Rtc.SetDateTime(now()-EPOCH_OFFSET);
}
DPRINT("New Date & Time:"); DPRINT(year()); DPRINT("/"); DPRINT(month()); DPRINT("/"); DPRINT(day());
DPRINT(" time:"); DPRINT(hour()); DPRINT(":"); DPRINT(minute()); DPRINT(":"); DPRINTLN(second());
}
//-------------------------- check buttons ------------------------------------------------
// FLD ("field") button:
// - Long press (>2s) toggles the editor: OFF->ON (loads current time into mvar[] via getRTC(),
// fld becomes 1 = "Year"); ON->OFF (saves mvar[] back via saveRTC(), fld becomes 0).
// - Short press (<500ms) while already editing (fld>0) advances to the next field, wrapping
// Year->Month->Day->Hour->Minute->Year... Has no effect while the editor is closed (fld==0),
// since a short press there does nothing but a long press is needed to open it.
// Debounce: state changes are only evaluated once per `mill` ms (called with 100ms from editor()),
// so a button transition is only "seen" after at least one 100ms tick, which is enough to reject
// mechanical contact bounce without any extra filtering.
void scanButFLD(unsigned long mill) {
static unsigned long lastRun = millis();
static unsigned long lastPush = millis();;
static switchStates butState;
byte sw;
if (PIN_FLD_BUTTON<0) return;
if ((millis() - lastRun) < mill) return; //refresh rate
lastRun = millis();
sw = digitalRead(PIN_FLD_BUTTON);
if (pushedFldButtonValue == HIGH) sw = !sw; //inverted button logic - normalize so downstream code can always treat LOW as "pressed"
switch (butState) {
case IS_OPEN:
if(sw == LOW) butState = IS_PUSHING;
break;
case IS_PUSHING:
butState = IS_PUSHED;
lastPush = millis(); //timestamp the press; used both for the 2s long-press threshold and the 500ms short-press classification on release
DPRINTLN(" PUSHED");
break;
case IS_PUSHED:
if(sw == HIGH) butState = IS_OPENING;
if((sw == LOW) && ((millis()-lastPush)>2000)) butState = IS_LONGPRESSING;
break;
case IS_LONGPRESSING:
// Entered exactly once per long press (state immediately advances to IS_LONGPRESSED below),
// so this toggle fires a single time per press-and-hold, not repeatedly while held.
butState = IS_LONGPRESSED;
DPRINTLN(" IS_LONGPRESSED");
if (fld>0) {
fld = 0;
saveRTC(); //leaving edit mode: commit mvar[] to system clock + RTC chip
}
else {
fld = 1;
getRTC(); //entering edit mode: seed mvar[] with the current date/time (field 1 = Year)
}
break;
case IS_LONGPRESSED:
if (sw == HIGH) butState = IS_LONGOPENING;
break;
case IS_OPENING:
butState = IS_OPEN;
if ((millis()-lastPush)<500) {
//short press (press+release completed within 500ms of the initial push) - only meaningful while editing (fld>0), since fld is unaffected when already 0
LastModify = millis();
fld++; if (fld>MAXFLD) fld = 1; //wraps back to field 1 (Year), never to 0 - the editor can only be exited via a long press, not by cycling fields
DPRINT(m1[fld].name); DPRINTLN(fld);
} //endif millis()
break;
case IS_LONGOPENING:
// Swallow the release-after-long-press here so it isn't misread as an additional short press
// once control returns to IS_OPEN.
butState = IS_OPEN;
break;
} //end switch
}
// SET button: changes the value of whichever field FLD currently has selected (mvar[fld]).
// - Short press (<500ms): increments the value by 1 (with wrap-around at m1[fld].lowlimit/uplimit).
// - Held past the long-press threshold (2s): auto-repeats the +1 increment on every subsequent
// 100ms poll tick for as long as the button stays down (see IS_LONGPRESSED case below) - lets
// the user "run" a field up quickly (e.g. scrolling through years) instead of clicking 60 times
// to advance the minute field.
void scanButSET(unsigned long mill) {
static unsigned long lastRun = millis();
static unsigned long lastPush = millis();
static switchStates butState;
byte sw;
if (PIN_SET_BUTTON<0) return;
if ((millis() - lastRun) < mill) return; //refresh rate
lastRun = millis();
sw = digitalRead(PIN_SET_BUTTON);
if (pushedSetButtonValue == HIGH) sw = !sw; //inverted button logic
switch (butState) {
case IS_OPEN:
if(sw == LOW) butState = IS_PUSHING;
break;
case IS_PUSHING:
butState = IS_PUSHED;
lastPush = millis();
DPRINTLN(" PUSHED");
break;
case IS_PUSHED:
if(sw == HIGH) butState = IS_OPENING;
if((sw == LOW) && ((millis()-lastPush)>2000)) butState = IS_LONGPRESSING;
break;
case IS_LONGPRESSING:
butState = IS_LONGPRESSED;
DPRINTLN(" IS_LONGPRESSED");
//if (noZero !=0) noZero =0; else noZero = 1; //dead code (leftover from another clock variant?) - no noZero/EEPROM_NOZERO symbol exists in this codebase
//EEPROM.update(EEPROM_NOZERO, noZero);
break;
case IS_LONGPRESSED:
// Stays in this state (re-entered every poll) for as long as the button remains held, which
// is what produces the auto-repeat effect: mvar[fld] is bumped by 1 on every ~100ms tick.
if (sw == HIGH) butState = IS_LONGOPENING;
// if ((millis()-lastPush)>10000) {factoryReset(); resetFunc();} //reset to factory settings (dead code - factory-reset-via-long-hold was never wired up here)
LastModify = millis();
mvar[fld]+=1;
if (mvar[fld] < m1[fld].lowlimit) mvar[fld] = m1[fld].uplimit;
if (mvar[fld] > m1[fld].uplimit) mvar[fld] = m1[fld].lowlimit;
DPRINT(fld); DPRINT(" : "); DPRINTLN(mvar[fld]);
break;
case IS_OPENING:
butState = IS_OPEN;
if ((millis()-lastPush)<500) {
LastModify = millis();
mvar[fld]++;
if (mvar[fld] < m1[fld].lowlimit) mvar[fld] = m1[fld].uplimit;
if (mvar[fld] > m1[fld].uplimit) mvar[fld] = m1[fld].lowlimit;
//if (fld <= 2) mvar[2] = monthday_check(mvar[RTC_Ev], mvar[RTC_Ho], mvar[RTC_Nap]); //leap-year check (szökőév ellenőrzés) -- dead code: monthday_check()/RTC_Ev/RTC_Ho/RTC_Nap are not defined anywhere in this codebase, so a Day value like 30 in February can currently be set and saved without any leap-year/month-length validation
DPRINT(fld); DPRINT(" : "); DPRINTLN(mvar[fld]);
if (fld>MAXFLD) fld = 0; // POSSIBLE BUG: this resets fld (the FIELD SELECTOR), not mvar[fld] (the field's VALUE). fld is only ever changed by scanButFLD(), which already clamps it to 1..MAXFLD, so this condition should never be true in practice - looks like leftover/copy-pasted logic (or was meant to operate on something else) rather than an intentional part of the SET-button behavior.
//flash(fld);
} //endif millis()
break;
case IS_LONGOPENING:
butState = IS_OPEN;
break;
} //end switch
}
// Manual I2C bus-recovery routine: bit-bangs SCL/SDA as plain GPIOs (bypassing the Wire/TWI
// peripheral) to unstick an I2C bus where a slave device is stuck holding a line low (e.g. a
// device that was reset/powered mid-transaction and is waiting for clocks it will never see from
// a normal Wire.begin()). Follows the standard "clock out up to 9 pulses, then issue a STOP"
// recovery procedure from the I2C spec / Adafruit/NXP application notes.
// NOTE: currently DEAD CODE from this file's own call graph - not invoked anywhere in rtc.ino; the
// only call site in the whole project is commented out (I2Csensors.ino, near where RTCexist gets
// probed on the bus) rather than being wired into the normal startup/error-recovery path.
#if defined(PIN_SDA) && defined(PIN_SCL)
int I2C_ClearBus() {
#if defined(TWCR) && defined(TWEN)
TWCR &= ~(_BV(TWEN)); //Disable the Atmel 2-Wire interface so we can control the SDA and SCL pins directly
#endif
DPRINTLN("I2C_ClearBus() started.");
pinMode(PIN_SDA, INPUT_PULLUP); // Make SDA (data) and SCL (clock) pins Inputs with pullup.
pinMode(PIN_SCL, INPUT_PULLUP);
Fdelay(2500); // Wait 2.5 secs. This is strictly only necessary on the first power
// up of the DS3231 module to allow it to initialize properly,
// but is also assists in reliable programming of FioV3 boards as it gives the
// IDE a chance to start uploaded the program
// before existing sketch confuses the IDE by sending Serial data.
boolean SCL_LOW = (digitalRead(PIN_SCL) == LOW); // Check if SCL is Low.
if (SCL_LOW) { //If it is held low, Arduino cannot become the I2C master.
return 1; //I2C bus error. Could not clear SCL clock line held low
}
boolean SDA_LOW = (digitalRead(PIN_SDA) == LOW); // vi. Check SDA input.
int clockCount = 20; // > 2x9 clock
while (SDA_LOW && (clockCount > 0)) { // vii. If SDA is Low,
clockCount--;
// Note: I2C bus is open collector so do NOT drive SCL or SDA high.
pinMode(PIN_SCL, INPUT); // release SCL pullup so that when made output it will be LOW
pinMode(PIN_SCL, OUTPUT); // then clock SCL Low
delayMicroseconds(10); // for >5uS
pinMode(PIN_SCL, INPUT); // release SCL LOW
pinMode(PIN_SCL, INPUT_PULLUP); // turn on pullup resistors again
// do not force high as slave may be holding it low for clock stretching.
delayMicroseconds(10); // for >5uS
// The >5uS is so that even the slowest I2C devices are handled.
SCL_LOW = (digitalRead(PIN_SCL) == LOW); // Check if SCL is Low.
int counter = 20;
while (SCL_LOW && (counter > 0)) { // loop waiting for SCL to become High only wait 2sec.
counter--;
delay(100);
SCL_LOW = (digitalRead(PIN_SCL) == LOW);
}
if (SCL_LOW) { // still low after 2 sec error
return 2; // I2C bus error. Could not clear. SCL clock line held low by slave clock stretch for >2sec
}
SDA_LOW = (digitalRead(PIN_SDA) == LOW); // and check SDA input again and loop
}
if (SDA_LOW) { // still low
return 3; // I2C bus error. Could not clear. SDA data line held low
}
// else pull SDA line low for Start or Repeated Start
pinMode(PIN_SDA, INPUT); // remove pullup.
pinMode(PIN_SDA, OUTPUT); // and then make it LOW i.e. send an I2C Start or Repeated start control.
// When there is only one I2C master a Start or Repeat Start has the same function as a Stop and clears the bus.
/// A Repeat Start is a Start occurring after a Start with no intervening Stop.
delayMicroseconds(10); // wait >5uS
pinMode(PIN_SDA, INPUT); // remove output low
pinMode(PIN_SDA, INPUT_PULLUP); // and make SDA high i.e. send I2C STOP control.
delayMicroseconds(10); // x. wait >5uS
pinMode(PIN_SDA, INPUT); // and reset pins as tri-state inputs which is the default state on reset
pinMode(PIN_SCL, INPUT);
return 0; // all ok
}
#else
int I2C_ClearBus() {return 1;} //PIN_SDA/PIN_SCL not defined for this build - can't bit-bang pins that don't exist, report a generic "bus error" instead
#endif
//------------------------------------------------------------------------------------------
#else
// USE_RTC not defined for this clock build: provide empty stand-in implementations so the rest
// of the codebase (ESP32_UniClock2.ino's setup()/calcTime()/loop()) can call these unconditionally
// without needing #ifdef USE_RTC guards at every call site.
void updateRTC() {}
void setupRTC() {}
void getRTC() {}
void editor() {}
#endif