Files
esp32-Serial-Logger/rtc_task.cpp
2026-02-18 15:49:30 +00:00

273 lines
7.8 KiB
C++

#include "rtc_task.h"
#include <sys/time.h>
#include <time.h>
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)");
}
}