Thanks @Liam120347. Capacitors have been periodically charged. Yesterday test ran for 6hrs, powered throughout, with no significant improvement in performance.
"""
RTC Drift Tester - SIMPLE VERSION
Uses Pico's ticks_ms() as the timebase reference instead of repeated NTP queries
"""
import time
import network
import ntptime
from machine import Pin, I2C
# ============================================================================
# CONFIGURATION
# ============================================================================
STATIC_IP = "192.168.1.120"
SUBNET_MASK = "255.255.255.0"
GATEWAY = "192.168.1.1"
DNS_SERVER = "192.168.1.1"
NTP_SERVER = "au.pool.ntp.org"
TIMEZONE_OFFSET = 11 * 3600 # UTC+11 for AEDT
CHECK_INTERVAL = 60 # seconds between drift checks
# ============================================================================
# --- I2C setup for RV3028 ---
i2c = I2C(0, scl=Pin(1), sda=Pin(0), freq=400000)
RV3028_ADDR = 0x52
# --- BCD conversion helpers ---
def bcd_to_int(b):
return (b >> 4) * 10 + (b & 0x0F)
def int_to_bcd(i):
return ((i // 10) << 4) | (i % 10)
def write_regs(start_addr, data):
i2c.writeto_mem(RV3028_ADDR, start_addr, bytes(data))
def read_regs(start_addr, nbytes):
return i2c.readfrom_mem(RV3028_ADDR, start_addr, nbytes)
# --- Ethernet connection ---
def ethernet_connect():
try:
nic = network.WIZNET5K()
nic.active(True)
nic.ifconfig((STATIC_IP, SUBNET_MASK, GATEWAY, DNS_SERVER))
print(f"Ethernet configured: {STATIC_IP}")
timeout = 10
while not nic.isconnected() and timeout > 0:
time.sleep(1)
timeout -= 1
print(".", end="")
if nic.isconnected():
print(f"\nEthernet connected: {nic.ifconfig()}")
return True
else:
print("\nEthernet connection failed")
return False
except Exception as e:
print(f"Ethernet error: {e}")
return False
# --- Read RV3028 as seconds since epoch ---
def read_rtc_datetime():
"""Read RV3028 and return (year, month, day, hour, minute, second)"""
data = read_regs(0x00, 7)
second = bcd_to_int(data[0] & 0x7F)
minute = bcd_to_int(data[1] & 0x7F)
hour = bcd_to_int(data[2] & 0x3F)
day = bcd_to_int(data[4] & 0x3F)
month = bcd_to_int(data[5] & 0x1F)
year = 2000 + bcd_to_int(data[6])
return (year, month, day, hour, minute, second)
# --- Sync RTC from NTP ---
def sync_rtc_from_ntp():
"""Sync RV3028 from NTP"""
try:
ntptime.host = NTP_SERVER
print(f"Syncing with NTP server: {NTP_SERVER}")
# Get NTP time
ntptime.settime()
# Get UTC time and convert to local
utc_timestamp = time.time()
local_timestamp = utc_timestamp + TIMEZONE_OFFSET
t = time.localtime(local_timestamp)
year = t[0] % 100
month, day, wday, hour, minute, second = (
t[1], t[2], (t[6] + 1) % 7, t[3], t[4], t[5]
)
# Write to RV3028
data = [
int_to_bcd(second),
int_to_bcd(minute),
int_to_bcd(hour),
int_to_bcd(wday),
int_to_bcd(day),
int_to_bcd(month),
int_to_bcd(year),
]
write_regs(0x00, data)
print(f"RTC synced: 20{year:02d}-{month:02d}-{day:02d} {hour:02d}:{minute:02d}:{second:02d} (local)")
return True
except Exception as e:
print(f"NTP sync failed: {e}")
return False
# --- Format seconds as readable time ---
def format_drift_time(seconds):
"""Format drift in seconds as a readable string"""
abs_sec = abs(seconds)
if abs_sec < 1:
return f"{seconds*1000:.1f} ms"
elif abs_sec < 60:
return f"{seconds:.3f} s"
else:
minutes = abs_sec / 60
return f"{minutes:.2f} min ({seconds:.1f} s)"
# --- Main program ---
def main():
print("="*60)
print("RV3028 RTC Drift Tester (Simple Version)")
print("="*60)
if not ethernet_connect():
print("Cannot continue without network")
return
# Initial sync
print("\n--- Initial Sync ---")
if not sync_rtc_from_ntp():
print("Initial sync failed, cannot continue")
return
print("\nWaiting for stable second boundary...")
# Wait until we're at the start of a second for clean reference
last_sec = -1
while True:
dt = read_rtc_datetime()
if dt[5] != last_sec: # Second changed
last_sec = dt[5]
# Wait for next second boundary
while read_rtc_datetime()[5] == last_sec:
time.sleep_ms(10)
# Now we're at a clean second boundary
break
# Capture reference point
ref_rtc_time = read_rtc_datetime()
ref_pico_ticks = time.ticks_ms()
print(f"\nReference captured at: {ref_rtc_time[0]}-{ref_rtc_time[1]:02d}-{ref_rtc_time[2]:02d} "
f"{ref_rtc_time[3]:02d}:{ref_rtc_time[4]:02d}:{ref_rtc_time[5]:02d}")
print("\n--- Starting Drift Monitoring ---")
print("Checking drift every", CHECK_INTERVAL, "seconds")
print("Using Pico's ticks_ms() as reference timebase")
print("Press Ctrl+C to stop\n")
print(f"{'Elapsed':<12} {'RTC Drift':<15} {'Drift Rate':<20}")
print("-" * 60)
try:
while True:
time.sleep(CHECK_INTERVAL)
# Read current RTC time
current_rtc = read_rtc_datetime()
current_pico_ticks = time.ticks_ms()
# Calculate elapsed time according to Pico (in seconds)
pico_elapsed_ms = time.ticks_diff(current_pico_ticks, ref_pico_ticks)
pico_elapsed_sec = pico_elapsed_ms / 1000.0
# Calculate elapsed time according to RTC (in seconds)
# Simple calculation: convert both times to total seconds since midnight
ref_total_sec = ref_rtc_time[3] * 3600 + ref_rtc_time[4] * 60 + ref_rtc_time[5]
current_total_sec = current_rtc[3] * 3600 + current_rtc[4] * 60 + current_rtc[5]
# Handle day rollover
if current_rtc[2] > ref_rtc_time[2]:
current_total_sec += 86400 * (current_rtc[2] - ref_rtc_time[2])
rtc_elapsed_sec = current_total_sec - ref_total_sec
# Calculate drift (RTC elapsed - Pico elapsed)
drift = rtc_elapsed_sec - pico_elapsed_sec
# Calculate drift rate (seconds per hour)
drift_rate = (drift / pico_elapsed_sec) * 3600 if pico_elapsed_sec > 0 else 0
# Format output
elapsed_str = f"{pico_elapsed_sec/60:.1f} min"
drift_str = format_drift_time(drift)
rate_str = f"{drift_rate:+.4f} s/hour"
print(f"{elapsed_str:<12} {drift_str:<15} {rate_str:<20}")
except KeyboardInterrupt:
print("\n\n--- Test Summary ---")
final_rtc = read_rtc_datetime()
final_pico_ticks = time.ticks_ms()
pico_elapsed_ms = time.ticks_diff(final_pico_ticks, ref_pico_ticks)
pico_elapsed_sec = pico_elapsed_ms / 1000.0
ref_total_sec = ref_rtc_time[3] * 3600 + ref_rtc_time[4] * 60 + ref_rtc_time[5]
final_total_sec = final_rtc[3] * 3600 + final_rtc[4] * 60 + final_rtc[5]
if final_rtc[2] > ref_rtc_time[2]:
final_total_sec += 86400 * (final_rtc[2] - ref_rtc_time[2])
rtc_elapsed_sec = final_total_sec - ref_total_sec
total_drift = rtc_elapsed_sec - pico_elapsed_sec
avg_drift_rate = (total_drift / pico_elapsed_sec) * 3600 if pico_elapsed_sec > 0 else 0
print(f"Total test duration: {pico_elapsed_sec/60:.1f} minutes ({pico_elapsed_sec/3600:.2f} hours)")
print(f"Total drift: {format_drift_time(total_drift)}")
print(f"Average drift rate: {avg_drift_rate:+.4f} seconds/hour")
print(f"Projected drift per day: {format_drift_time(avg_drift_rate * 24)}")
print(f"Projected drift per week: {format_drift_time(avg_drift_rate * 24 * 7)}")
print("\nTest stopped.")
# Run the test
main()