// Copyright (C) 2026, GPS/RTC support contributed by GlassOnTin
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
#ifndef RTC_H
#define RTC_H
#if HAS_RTC == true
#include
// RTC I2C address (shared by PCF8563 and PCF85063A)
#define RTC_I2C_ADDR 0x51
#if BOARD_MODEL == BOARD_TWATCH_ULT
// PCF85063A registers (T-Watch Ultra)
#define RTC_REG_CTRL1 0x00
#define RTC_REG_CTRL2 0x01
#define RTC_REG_SEC 0x04
#define RTC_REG_MIN 0x05
#define RTC_REG_HOUR 0x06
#define RTC_REG_DAY 0x07
#define RTC_REG_WDAY 0x08
#define RTC_REG_MON 0x09
#define RTC_REG_YEAR 0x0A
#else
// PCF8563 registers (T-Beam Supreme, etc.)
#define RTC_REG_CTRL1 0x00
#define RTC_REG_CTRL2 0x01
#define RTC_REG_SEC 0x02
#define RTC_REG_MIN 0x03
#define RTC_REG_HOUR 0x04
#define RTC_REG_DAY 0x05
#define RTC_REG_WDAY 0x06
#define RTC_REG_MON 0x07
#define RTC_REG_YEAR 0x08
#endif
bool rtc_ready = false;
bool rtc_synced = false; // true once GPS time has been written to RTC
uint32_t rtc_last_sync = 0;
#define RTC_SYNC_INTERVAL 3600000 // re-sync from GPS every hour
// Cached time from last RTC read
uint8_t rtc_hour = 0;
uint8_t rtc_minute = 0;
uint8_t rtc_second = 0;
uint8_t rtc_day = 0;
uint8_t rtc_month = 0;
uint16_t rtc_year = 0;
static uint8_t bcd_to_dec(uint8_t bcd) { return (bcd >> 4) * 10 + (bcd & 0x0F); }
static uint8_t dec_to_bcd(uint8_t dec) { return ((dec / 10) << 4) | (dec % 10); }
void rtc_setup() {
// Wire is already initialised by Power.h (PMU init) or Display.h.
Wire.beginTransmission(RTC_I2C_ADDR);
if (Wire.endTransmission() == 0) {
rtc_ready = true;
// Clear control registers (normal mode, no alarms)
Wire.beginTransmission(RTC_I2C_ADDR);
Wire.write(RTC_REG_CTRL1);
Wire.write(0x00); // normal mode
Wire.write(0x00); // control/status 2: no alarms/timer
Wire.endTransmission();
}
}
bool rtc_read_time() {
if (!rtc_ready) return false;
Wire.beginTransmission(RTC_I2C_ADDR);
Wire.write(RTC_REG_SEC);
if (Wire.endTransmission() != 0) return false;
Wire.requestFrom((uint8_t)RTC_I2C_ADDR, (uint8_t)7);
if (Wire.available() < 7) return false;
uint8_t sec = Wire.read();
uint8_t min = Wire.read();
uint8_t hour = Wire.read();
uint8_t day = Wire.read();
Wire.read(); // weekday — skip
uint8_t mon = Wire.read();
uint8_t year = Wire.read();
// Check oscillator stop bit (sec register bit 7)
// Set on both PCF8563 and PCF85063A when clock integrity is lost
if (sec & 0x80) return false;
rtc_second = bcd_to_dec(sec & 0x7F);
rtc_minute = bcd_to_dec(min & 0x7F);
rtc_hour = bcd_to_dec(hour & 0x3F);
rtc_day = bcd_to_dec(day & 0x3F);
rtc_month = bcd_to_dec(mon & 0x1F);
rtc_year = 2000 + bcd_to_dec(year);
return true;
}
bool rtc_write_time(uint16_t year, uint8_t month, uint8_t day,
uint8_t hour, uint8_t minute, uint8_t second) {
if (!rtc_ready) return false;
Wire.beginTransmission(RTC_I2C_ADDR);
Wire.write(RTC_REG_SEC);
Wire.write(dec_to_bcd(second));
Wire.write(dec_to_bcd(minute));
Wire.write(dec_to_bcd(hour));
Wire.write(dec_to_bcd(day));
Wire.write(0x00); // weekday (not used)
Wire.write(dec_to_bcd(month));
Wire.write(dec_to_bcd(year - 2000));
return Wire.endTransmission() == 0;
}
// Called from gps_update() when GPS has a valid time fix.
// Syncs RTC from GPS time at most once per RTC_SYNC_INTERVAL.
void rtc_sync_from_gps(TinyGPSPlus &gps) {
if (!rtc_ready) return;
if (!gps.date.isValid() || !gps.time.isValid()) return;
if (gps.date.year() < 2024) return; // sanity check
uint32_t now = millis();
if (rtc_synced && (now - rtc_last_sync < RTC_SYNC_INTERVAL)) return;
if (rtc_write_time(gps.date.year(), gps.date.month(), gps.date.day(),
gps.time.hour(), gps.time.minute(), gps.time.second())) {
rtc_synced = true;
rtc_last_sync = now;
}
}
#endif
#endif