#include "rtc_task.h" #include #include RTCStatus rtcStatus = { false, false, 0, 0.0 }; SemaphoreHandle_t rtcMutex = NULL; // SoftWire instance + required buffers static SoftWire rtcWire(RTC_SDA_PIN, RTC_SCL_PIN); static char swTxBuffer[16]; static char swRxBuffer[16]; // BCD conversion static uint8_t bcd2dec(uint8_t bcd) { return (bcd >> 4) * 10 + (bcd & 0x0F); } static uint8_t dec2bcd(uint8_t dec) { return ((dec / 10) << 4) | (dec % 10); } // ============================================================ // Initialize SoftWire I2C and probe DS3231 // ============================================================ bool rtcInit() { rtcMutex = xSemaphoreCreateMutex(); rtcWire.setTxBuffer(swTxBuffer, sizeof(swTxBuffer)); rtcWire.setRxBuffer(swRxBuffer, sizeof(swRxBuffer)); rtcWire.setDelay_us(5); rtcWire.begin(); // Probe DS3231 at address 0x68 rtcWire.beginTransmission(DS3231_ADDRESS); uint8_t err = rtcWire.endTransmission(); if (err == 0) { rtcStatus.available = true; Serial.printf("[RTC] DS3231 found at 0x%02X (SDA=%d, SCL=%d) [SoftWire]\n", DS3231_ADDRESS, RTC_SDA_PIN, RTC_SCL_PIN); // Read temperature to verify communication rtcStatus.temperature = rtcReadTemperature(); Serial.printf("[RTC] Temperature: %.2f C\n", rtcStatus.temperature); // Check if RTC has valid time (year >= 2024) time_t t = rtcReadTime(); struct tm tm; localtime_r(&t, &tm); if (tm.tm_year + 1900 >= 2024) { rtcStatus.timeSynced = true; Serial.printf("[RTC] Valid time: %04d-%02d-%02d %02d:%02d:%02d\n", tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); // Boot with RTC time immediately rtcSyncToSystem(); Serial.println("[RTC] System clock set from RTC on boot"); } else { Serial.println("[RTC] No valid time stored - waiting for browser sync"); } // Disable DS3231 square wave, enable battery backup xSemaphoreTake(rtcMutex, portMAX_DELAY); rtcWire.beginTransmission(DS3231_ADDRESS); rtcWire.write(0x0E); // Control register rtcWire.write(0x04); // INTCN=1, no SQW rtcWire.endTransmission(); xSemaphoreGive(rtcMutex); return true; } else { rtcStatus.available = false; Serial.printf("[RTC] DS3231 NOT found (err=%d). Running without RTC.\n", err); return false; } } // ============================================================ // Read time from DS3231 → epoch // ============================================================ time_t rtcReadTime() { if (!rtcStatus.available) return 0; xSemaphoreTake(rtcMutex, portMAX_DELAY); rtcWire.beginTransmission(DS3231_ADDRESS); rtcWire.write(0x00); rtcWire.endTransmission(); rtcWire.requestFrom((uint8_t)DS3231_ADDRESS, (uint8_t)7); if (rtcWire.available() < 7) { xSemaphoreGive(rtcMutex); return 0; } uint8_t sec = bcd2dec(rtcWire.read() & 0x7F); uint8_t min = bcd2dec(rtcWire.read()); uint8_t hour = bcd2dec(rtcWire.read() & 0x3F); rtcWire.read(); // day of week (skip) uint8_t date = bcd2dec(rtcWire.read()); uint8_t raw5 = rtcWire.read(); uint8_t month = bcd2dec(raw5 & 0x1F); uint8_t year = bcd2dec(rtcWire.read()); xSemaphoreGive(rtcMutex); uint16_t fullYear = 2000 + year; if (raw5 & 0x80) fullYear += 100; struct tm tm; tm.tm_sec = sec; tm.tm_min = min; tm.tm_hour = hour; tm.tm_mday = date; tm.tm_mon = month - 1; tm.tm_year = fullYear - 1900; tm.tm_isdst = 0; time_t epoch = mktime(&tm); return epoch; } // ============================================================ // Write time to DS3231 from epoch // ============================================================ bool rtcWriteTime(time_t epoch) { if (!rtcStatus.available) return false; struct tm tm; localtime_r(&epoch, &tm); xSemaphoreTake(rtcMutex, portMAX_DELAY); rtcWire.beginTransmission(DS3231_ADDRESS); rtcWire.write(0x00); rtcWire.write(dec2bcd(tm.tm_sec)); rtcWire.write(dec2bcd(tm.tm_min)); rtcWire.write(dec2bcd(tm.tm_hour)); rtcWire.write(dec2bcd(tm.tm_wday + 1)); rtcWire.write(dec2bcd(tm.tm_mday)); rtcWire.write(dec2bcd(tm.tm_mon + 1)); rtcWire.write(dec2bcd((tm.tm_year + 1900) % 100)); uint8_t err = rtcWire.endTransmission(); xSemaphoreGive(rtcMutex); if (err == 0) { rtcStatus.timeSynced = true; Serial.printf("[RTC] Written: %04d-%02d-%02d %02d:%02d:%02d\n", tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); return true; } Serial.printf("[RTC] Write FAILED (err=%d)\n", err); return false; } // ============================================================ // Sync: RTC → ESP32 System Clock // ============================================================ bool rtcSyncToSystem() { if (!rtcStatus.available || !rtcStatus.timeSynced) return false; time_t rtcEpoch = rtcReadTime(); if (rtcEpoch == 0) return false; struct timeval tv; tv.tv_sec = rtcEpoch; tv.tv_usec = 0; settimeofday(&tv, NULL); rtcStatus.syncCount++; struct tm tm; localtime_r(&rtcEpoch, &tm); Serial.printf("[RTC->SYS] Sync #%u: %04d-%02d-%02d %02d:%02d:%02d\n", rtcStatus.syncCount, tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); return true; } // ============================================================ // Sync: ESP32 System Clock → RTC // ============================================================ bool rtcSyncFromSystem() { struct timeval tv; gettimeofday(&tv, NULL); if (tv.tv_sec < 1700000000) { Serial.println("[RTC] System time not valid, skipping write to RTC"); return false; } bool ok = rtcWriteTime(tv.tv_sec); if (ok) { Serial.println("[RTC] System time -> RTC written OK"); } return ok; } // ============================================================ // Read DS3231 internal temperature sensor // ============================================================ float rtcReadTemperature() { if (!rtcStatus.available) return -999.0; xSemaphoreTake(rtcMutex, portMAX_DELAY); rtcWire.beginTransmission(DS3231_ADDRESS); rtcWire.write(0x11); rtcWire.endTransmission(); rtcWire.requestFrom((uint8_t)DS3231_ADDRESS, (uint8_t)2); if (rtcWire.available() < 2) { xSemaphoreGive(rtcMutex); return -999.0; } int8_t msb = (int8_t)rtcWire.read(); uint8_t lsb = rtcWire.read(); xSemaphoreGive(rtcMutex); float temp = (float)msb + ((lsb >> 6) * 0.25f); return temp; } // ============================================================ // RTC Periodic Sync Task // ============================================================ void rtcSyncTask(void *param) { Serial.println("[Task] RTC Sync started on core " + String(xPortGetCoreID())); vTaskDelay(pdMS_TO_TICKS(5000)); while (true) { vTaskDelay(pdMS_TO_TICKS(RTC_SYNC_INTERVAL_MS)); if (!rtcStatus.available || !rtcStatus.timeSynced) continue; struct timeval sysTv; gettimeofday(&sysTv, NULL); time_t rtcEpoch = rtcReadTime(); if (rtcEpoch == 0) continue; int32_t drift = (int32_t)(sysTv.tv_sec - rtcEpoch); if (abs(drift) > 2) { Serial.printf("[RTC] Drift detected: %+d sec -> correcting\n", drift); rtcSyncToSystem(); } else { static uint32_t logCounter = 0; if (++logCounter % 10 == 0) { rtcStatus.temperature = rtcReadTemperature(); Serial.printf("[RTC] OK (drift=%+ds, temp=%.1fC, syncs=%u)\n", drift, rtcStatus.temperature, rtcStatus.syncCount); } } } } // ============================================================ // Start RTC Task // ============================================================ void rtcTaskInit() { if (rtcStatus.available) { xTaskCreatePinnedToCore(rtcSyncTask, "RTC", TASK_STACK_RTC, NULL, TASK_PRIORITY_RTC, NULL, 0); Serial.println("[RTC] Sync task started (interval: " + String(RTC_SYNC_INTERVAL_MS / 1000) + "s)"); } }