From f7d970fe44b6f3709c1ba8c9ee53d8c07bacedaa Mon Sep 17 00:00:00 2001 From: jpirnay Date: Fri, 17 Apr 2026 22:47:08 +0200 Subject: [PATCH 01/10] implement DS3231 clock support --- lib/hal/HalClock.cpp | 112 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/lib/hal/HalClock.cpp b/lib/hal/HalClock.cpp index 324eb58c..e1eede02 100644 --- a/lib/hal/HalClock.cpp +++ b/lib/hal/HalClock.cpp @@ -4,6 +4,7 @@ #include #include #include +#include // Needed for I2C communication with the RTC #include #include #include @@ -12,6 +13,14 @@ #include #include +// ---- RTC / I2C configuration ---------------------------------------------- +// Pins for ESP32-C3 (according to https://gist.github.com/CrazyCoder/1c5f846adee18e21f91e264601a6ddce) +static constexpr uint8_t DS3231_ADDRESS = 0x68; +static constexpr int I2C_SDA = 8; +static constexpr int I2C_SCL = 9; +static uint8_t bin2bcd(uint8_t val) { return val + 6 * (val / 10); } +static uint8_t bcd2bin(uint8_t val) { return val - 6 * (val >> 4); } + // ---- RTC-memory state (survives deep sleep, not cold boot) ---------------- static constexpr uint32_t CLOCK_RTC_MAGIC = 0xC10C4B1D; @@ -174,6 +183,78 @@ static float readChipTemperatureC() { return (float)temperatureRead(); } +// ---- New internal helpers for DS3231 --------------------------------------- + +static bool initExternalRTC() { + static bool initialized = false; + static bool exists = false; + if (initialized) return exists; + + Wire.begin(I2C_SDA, I2C_SCL); + Wire.beginTransmission(DS3231_ADDRESS); + if (Wire.endTransmission() == 0) { + exists = true; + LOG_INF("CLK", "DS3231 Hardware via I2C found."); + } else { + LOG_INF("CLK", "No DS3231 found."); + } + initialized = true; + return exists; +} + +// Write time to DS3231 +static void writeExternalRTC(time_t t) { + struct tm timeinfo; + gmtime_r(&t, &timeinfo); // DS3231 wird meist in UTC betrieben + + Wire.beginTransmission(DS3231_ADDRESS); + Wire.write(0x00); // Start-Register (Sekunden) + Wire.write(bin2bcd(timeinfo.tm_sec)); + Wire.write(bin2bcd(timeinfo.tm_min)); + Wire.write(bin2bcd(timeinfo.tm_hour)); + Wire.write(bin2bcd(0)); // Wochentag (hier ignoriert) + Wire.write(bin2bcd(timeinfo.tm_mday)); + Wire.write(bin2bcd(timeinfo.tm_mon + 1)); + Wire.write(bin2bcd(timeinfo.tm_year - 100)); // DS3231 speichert Jahre seit 2000 + Wire.endTransmission(); +} + +// Liest die Zeit vom DS3231 +static time_t readExternalRTC() { + Wire.beginTransmission(DS3231_ADDRESS); + Wire.write(0x00); + if (Wire.endTransmission() != 0) return 0; + + Wire.requestFrom(DS3231_ADDRESS, (uint8_t)7); + if (Wire.available() < 7) return 0; + + struct tm timeinfo = {}; + timeinfo.tm_sec = bcd2bin(Wire.read() & 0x7F); + timeinfo.tm_min = bcd2bin(Wire.read()); + timeinfo.tm_hour = bcd2bin(Wire.read() & 0x3F); + Wire.read(); // Wochentag überspringen + timeinfo.tm_mday = bcd2bin(Wire.read()); + timeinfo.tm_mon = bcd2bin(Wire.read()) - 1; + timeinfo.tm_year = bcd2bin(Wire.read()) + 100; + timeinfo.tm_isdst = 0; + + return mktime(&timeinfo); +} + +// Read temperature (Register 0x11) +static float readExternalTemp() { + Wire.beginTransmission(DS3231_ADDRESS); + Wire.write(0x11); + Wire.endTransmission(); + Wire.requestFrom(DS3231_ADDRESS, (uint8_t)2); + + int8_t msb = Wire.read(); + uint8_t lsb = Wire.read(); + return (float)msb + (lsb >> 6) * 0.25f; +} + +// ---- + static void setSystemClock(time_t epoch) { struct timeval tv = {}; tv.tv_sec = epoch; @@ -231,6 +312,10 @@ static double computeCorrectedElapsedSec(uint64_t lpNow, float tempNow) { /// Capture current time + LP timer into RTC memory, and epoch into NVS. static void capture(bool lpValid) { rtcEpoch = time(nullptr); + // Update DS3231 + if (initExternalRTC()) { + writeExternalRTC(rtcEpoch); + } rtcLpTimeUs = esp_clk_rtc_time(); rtcSlowCal = esp_clk_slowclk_cal_get(); rtcTemperatureC = readChipTemperatureC(); @@ -337,8 +422,19 @@ void saveBeforeSleep(bool keepLpAlive) { } void restore() { - rtcDriftScale = nvsReadDriftScale(); + // PRIORITY 1: DS3231 (Hardware-RTC) + if (initExternalRTC()) { + time_t rtcTime = readExternalRTC(); + if (rtcTime > 1577836800) { // Check if time is after 2020 (plausible timestamp) + setSystemClock(rtcTime); + rtcEpoch = rtcTime; + clockApproximate = false; + LOG_INF("CLK", "Got time from DS3231."); + return; + } + } + rtcDriftScale = nvsReadDriftScale(); const bool lpValid = (rtcClockFlags & CLOCK_RTC_FLAG_LP_VALID) != 0; if (rtcValid() && lpValid) { // RTC memory survived — we woke from deep sleep. @@ -411,6 +507,20 @@ time_t now() { } void updatePeriodic() { + // DS3231 (if present) has priority, synchronize the system time + // every 10 minutes directly against the RTC, instead of calculating. + if (initExternalRTC()) { + time_t rtcTime = readExternalRTC(); + unsigned long nowMs = millis(); + if ((nowMs - lastPeriodicUpdateMs >= PERIODIC_UPDATE_INTERVAL_MS) && + (rtcTime > 1577836800)) { // Check if time is after 2020 (plausible timestamp) + lastPeriodicUpdateMs = nowMs; + setSystemClock(rtcTime); + LOG_DBG("CLK", "Systemtime has been taken from DS3231"); + } + return; + } + if (!isSynced()) { return; } From 4d0af7331c93e2ca0e5296cea802cfc4f5757dd6 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Fri, 17 Apr 2026 22:57:56 +0200 Subject: [PATCH 02/10] X4 quick exit --- lib/hal/HalClock.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/hal/HalClock.cpp b/lib/hal/HalClock.cpp index e1eede02..2d0c2905 100644 --- a/lib/hal/HalClock.cpp +++ b/lib/hal/HalClock.cpp @@ -1,6 +1,7 @@ #include "HalClock.h" #include +#include #include #include #include @@ -189,6 +190,12 @@ static bool initExternalRTC() { static bool initialized = false; static bool exists = false; if (initialized) return exists; + initialized = true; + + if (!gpio.deviceIsX3()) { + LOG_DBG("CLK", "Skipping DS3231 init on non-X3 board"); + return false; + } Wire.begin(I2C_SDA, I2C_SCL); Wire.beginTransmission(DS3231_ADDRESS); @@ -198,7 +205,6 @@ static bool initExternalRTC() { } else { LOG_INF("CLK", "No DS3231 found."); } - initialized = true; return exists; } From 45dbd2f54ac3e1b2dee30fb1fc13909e46cd1e92 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Fri, 17 Apr 2026 23:01:52 +0200 Subject: [PATCH 03/10] Version bump --- platformio.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index e352775e..c8498aed 100644 --- a/platformio.ini +++ b/platformio.ini @@ -3,7 +3,7 @@ default_envs = default extra_configs = platformio.local.ini [crosspoint] -version = 1.35 +version = 1.36 [base] platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.37/platform-espressif32.zip @@ -26,7 +26,6 @@ build_flags = -DEINK_DISPLAY_SINGLE_BUFFER_MODE=1 -DDISABLE_FS_H_WARNING=1 -DDESTRUCTOR_CLOSES_FILE=1 - ;-DENABLE_IMAGE_DITHERING_EXTENSION ; dont enable by default, as it increases code size and may not be needed for all images # https://libexpat.github.io/doc/api/latest/#XML_GE -DXML_GE=0 -DXML_CONTEXT_BYTES=1024 From b6c3774de999721a8457d956425fac86be86d45d Mon Sep 17 00:00:00 2001 From: jpirnay Date: Fri, 17 Apr 2026 23:14:40 +0200 Subject: [PATCH 04/10] Proper timezone conversion --- lib/hal/HalClock.cpp | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/hal/HalClock.cpp b/lib/hal/HalClock.cpp index 2d0c2905..c20008b0 100644 --- a/lib/hal/HalClock.cpp +++ b/lib/hal/HalClock.cpp @@ -22,6 +22,30 @@ static constexpr int I2C_SCL = 9; static uint8_t bin2bcd(uint8_t val) { return val + 6 * (val / 10); } static uint8_t bcd2bin(uint8_t val) { return val - 6 * (val >> 4); } +/** + * Convert struct tm (interpreted as UTC) to Unix epoch seconds. + * Replaces mktime(), as mktime considers the local timezone (TZ). + */ +static time_t timegm_compat(const struct tm* tm) { + int32_t year = tm->tm_year + 1900; + int32_t month = tm->tm_mon; // 0-11 + + // Helper calculation: days since the beginning of the year + static const uint16_t days_before_month[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; + + // Days since 1970 (considering leap years) + time_t days = (year - 1970) * 365 + (year - 1969) / 4; + days += days_before_month[month]; + + // Leap year correction for the current year (no extra day before March) + if (month > 1 && (year % 4 == 0)) { + days++; + } + days += tm->tm_mday - 1; + + return days * 86400 + tm->tm_hour * 3600 + tm->tm_min * 60 + tm->tm_sec; +} + // ---- RTC-memory state (survives deep sleep, not cold boot) ---------------- static constexpr uint32_t CLOCK_RTC_MAGIC = 0xC10C4B1D; @@ -244,7 +268,7 @@ static time_t readExternalRTC() { timeinfo.tm_year = bcd2bin(Wire.read()) + 100; timeinfo.tm_isdst = 0; - return mktime(&timeinfo); + return timegm_compat(&timeinfo); } // Read temperature (Register 0x11) From 141ec0274613eccb4a51a652c7a67ae49a81a2e2 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Fri, 17 Apr 2026 23:17:42 +0200 Subject: [PATCH 05/10] Fix comments --- lib/hal/HalClock.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/hal/HalClock.cpp b/lib/hal/HalClock.cpp index c20008b0..1055bd30 100644 --- a/lib/hal/HalClock.cpp +++ b/lib/hal/HalClock.cpp @@ -205,6 +205,9 @@ static time_t nvsReadSyncTime() { static float readChipTemperatureC() { // ESP32 and ESP32-C3 use the internal ADC temperature sensor. + if (initExternalRTC()) { + return readExternalTemp(); + } return (float)temperatureRead(); } @@ -235,21 +238,21 @@ static bool initExternalRTC() { // Write time to DS3231 static void writeExternalRTC(time_t t) { struct tm timeinfo; - gmtime_r(&t, &timeinfo); // DS3231 wird meist in UTC betrieben + gmtime_r(&t, &timeinfo); // DS3231 gets usually operated in UTC Wire.beginTransmission(DS3231_ADDRESS); - Wire.write(0x00); // Start-Register (Sekunden) + Wire.write(0x00); // start-register (seconds) Wire.write(bin2bcd(timeinfo.tm_sec)); Wire.write(bin2bcd(timeinfo.tm_min)); Wire.write(bin2bcd(timeinfo.tm_hour)); - Wire.write(bin2bcd(0)); // Wochentag (hier ignoriert) + Wire.write(bin2bcd(0)); // weekday (ignored here) Wire.write(bin2bcd(timeinfo.tm_mday)); Wire.write(bin2bcd(timeinfo.tm_mon + 1)); - Wire.write(bin2bcd(timeinfo.tm_year - 100)); // DS3231 speichert Jahre seit 2000 + Wire.write(bin2bcd(timeinfo.tm_year - 100)); // DS3231 stores years since 2000 Wire.endTransmission(); } -// Liest die Zeit vom DS3231 +// Read time from DS3231 static time_t readExternalRTC() { Wire.beginTransmission(DS3231_ADDRESS); Wire.write(0x00); From f68fbc39e4403b57b28b1fba1f69b34fd7019ec3 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Fri, 17 Apr 2026 23:19:42 +0200 Subject: [PATCH 06/10] Dont write guesstimates into RTC --- lib/hal/HalClock.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/hal/HalClock.cpp b/lib/hal/HalClock.cpp index 1055bd30..81cf472a 100644 --- a/lib/hal/HalClock.cpp +++ b/lib/hal/HalClock.cpp @@ -345,8 +345,8 @@ static double computeCorrectedElapsedSec(uint64_t lpNow, float tempNow) { /// Capture current time + LP timer into RTC memory, and epoch into NVS. static void capture(bool lpValid) { rtcEpoch = time(nullptr); - // Update DS3231 - if (initExternalRTC()) { + // Update DS3231 only when the current time is authoritative. + if (initExternalRTC() && !clockApproximate) { writeExternalRTC(rtcEpoch); } rtcLpTimeUs = esp_clk_rtc_time(); @@ -393,6 +393,8 @@ bool syncNtp() { return false; } + // NTP sync yields authoritative time; allow DS3231 to be updated. + clockApproximate = false; capture(false); nvsWriteSyncTime(rtcEpoch); From 134527de5f30d061d43ae06c59225bee7a503aea Mon Sep 17 00:00:00 2001 From: jpirnay Date: Fri, 17 Apr 2026 23:21:03 +0200 Subject: [PATCH 07/10] Dont hammer the bus --- lib/hal/HalClock.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/hal/HalClock.cpp b/lib/hal/HalClock.cpp index 81cf472a..e7d83ed9 100644 --- a/lib/hal/HalClock.cpp +++ b/lib/hal/HalClock.cpp @@ -545,13 +545,14 @@ void updatePeriodic() { // DS3231 (if present) has priority, synchronize the system time // every 10 minutes directly against the RTC, instead of calculating. if (initExternalRTC()) { - time_t rtcTime = readExternalRTC(); unsigned long nowMs = millis(); - if ((nowMs - lastPeriodicUpdateMs >= PERIODIC_UPDATE_INTERVAL_MS) && - (rtcTime > 1577836800)) { // Check if time is after 2020 (plausible timestamp) - lastPeriodicUpdateMs = nowMs; - setSystemClock(rtcTime); - LOG_DBG("CLK", "Systemtime has been taken from DS3231"); + if (nowMs - lastPeriodicUpdateMs >= PERIODIC_UPDATE_INTERVAL_MS) { + time_t rtcTime = readExternalRTC(); + if (rtcTime > 1577836800) { // Check if time is after 2020 (plausible timestamp) + lastPeriodicUpdateMs = nowMs; + setSystemClock(rtcTime); + LOG_DBG("CLK", "Systemtime has been taken from DS3231"); + } } return; } From 718ebaaed61058c0a95d526993006caf780db185 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Fri, 17 Apr 2026 23:21:27 +0200 Subject: [PATCH 08/10] Revert --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index c8498aed..28ced26c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -3,7 +3,7 @@ default_envs = default extra_configs = platformio.local.ini [crosspoint] -version = 1.36 +version = 1.35 [base] platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.37/platform-espressif32.zip From 94929848505e322adb6696622ee90413da698019 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Fri, 17 Apr 2026 23:27:38 +0200 Subject: [PATCH 09/10] Remove wire init --- lib/hal/HalClock.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/hal/HalClock.cpp b/lib/hal/HalClock.cpp index e7d83ed9..30c7bd8c 100644 --- a/lib/hal/HalClock.cpp +++ b/lib/hal/HalClock.cpp @@ -17,8 +17,8 @@ // ---- RTC / I2C configuration ---------------------------------------------- // Pins for ESP32-C3 (according to https://gist.github.com/CrazyCoder/1c5f846adee18e21f91e264601a6ddce) static constexpr uint8_t DS3231_ADDRESS = 0x68; -static constexpr int I2C_SDA = 8; -static constexpr int I2C_SCL = 9; +// static constexpr int I2C_SDA = 8; +// static constexpr int I2C_SCL = 9; static uint8_t bin2bcd(uint8_t val) { return val + 6 * (val / 10); } static uint8_t bcd2bin(uint8_t val) { return val - 6 * (val >> 4); } @@ -203,6 +203,9 @@ static time_t nvsReadSyncTime() { // ---- internal helpers ----------------------------------------------------- +static bool initExternalRTC(); +static float readExternalTemp(); + static float readChipTemperatureC() { // ESP32 and ESP32-C3 use the internal ADC temperature sensor. if (initExternalRTC()) { @@ -224,7 +227,7 @@ static bool initExternalRTC() { return false; } - Wire.begin(I2C_SDA, I2C_SCL); + // Wire has already been initialized; no need to call Wire.begin(I2C_SDA, I2C_SCL); Wire.beginTransmission(DS3231_ADDRESS); if (Wire.endTransmission() == 0) { exists = true; From f31159b430998a5bed4e7a65e7bcdcff29cdc196 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Fri, 17 Apr 2026 23:32:26 +0200 Subject: [PATCH 10/10] Temperature sanity check --- lib/hal/HalClock.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/hal/HalClock.cpp b/lib/hal/HalClock.cpp index 30c7bd8c..fdd1ca4a 100644 --- a/lib/hal/HalClock.cpp +++ b/lib/hal/HalClock.cpp @@ -281,8 +281,14 @@ static time_t readExternalRTC() { static float readExternalTemp() { Wire.beginTransmission(DS3231_ADDRESS); Wire.write(0x11); - Wire.endTransmission(); - Wire.requestFrom(DS3231_ADDRESS, (uint8_t)2); + if (Wire.endTransmission() != 0) { + return 0.0f; + } + + int count = Wire.requestFrom(DS3231_ADDRESS, (uint8_t)2); + if (count < 2) { + return 0.0f; + } int8_t msb = Wire.read(); uint8_t lsb = Wire.read();