Hello,
I discovered that my new Nicla Vision does not keep time with the RTC as it should. As I want to log things with this board an accurate time keeping is required, but after some testing I found that the RTC clock runs a lot slower than the ntp time I am synching it to.
Firmware version: 5.0.0 and version 4.5.9
I tested the timing with deepsleep as well as with the board awake for a period of a few hours and found the following:
Deep-Sleep Test
From start to ntp resynch after 120 wakes:
- NTP elapsed: 4,331 s (72 min 11 s)
- RTC elapsed: 4,171 s (69 min 31 s)
- RTC loss: 160 s
That is about:
or roughly 3.69% slow.
Awake Test
Two awake runs gave nearly identical results:
| Run | NTP elapsed | RTC elapsed | RTC loss |
|---|---|---|---|
| 12:27:27 to 14:27:34 | 7,206 s | 7,045 s | 161 s |
| 14:27:39 to 16:27:46 | 7,207 s | 7,045 s | 162 s |
That is approximately:
or 2.23% slow.
Combined Conclusion
- The RTC slow even while the Nicla awake.
- Deep sleep makes the observed error worse: about 53 min/day versus 32 min/day awake.
RTC.info()is consistently0x180300: documentedlse_failedandnewly_initializedflags are both clear, so this is not the documented LSE-failed fallback path.rtc.calibration()is0; normal RTC calibration cannot correct a 2-4% frequency error.
Has someone run into the same problem or is my Nicla Vision faulty?
My code:
# Writes append-only CSV records to rtc_drift.csv.
#specify wifi and ntp server detail below
import machine
import os
import time
import ntptime
import network
SLEEP_MS = 30_000
LOG_FILE = "rtc_drift.csv"
NTP_SYNC_EVERY_WAKES = 120 # 120 * 30 s = 1 hour
STATE_FILE = "rtc_drift_state.txt"
AWAKE_TEST_SECONDS = 2 * 60 * 60 # 2 hours
AWAKE_LOG_INTERVAL_SECONDS = 5 * 60 # 5 seconds
#wifi_country =
#wifi_ssid =
#wifi_password =
#ntp_server =
rtc = machine.RTC()
def datetime_text(dt):
return "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ" % (
dt[0], dt[1], dt[2], dt[4], dt[5], dt[6], dt[7] * 1000 // 256
)
def datetime_seconds(dt):
return time.mktime((
dt[0], dt[1], dt[2], dt[4], dt[5], dt[6], 0, 0
))
def rtc_info_text():
info = rtc.info()
startup_ms = info & 0xFFFF
lse_failed = bool(info & 0x10000)
newly_initialized = bool(info & 0x20000)
return info, startup_ms, lse_failed, newly_initialized
def connect_wifi():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
network.country(wifi_country)
wlan.connect(wifi_ssid, wifi_password)
for _ in range(30):
if wlan.status() == 3:
return wlan
if wlan.status() < 0:
raise RuntimeError("Wi-Fi failed: %d" % wlan.status())
time.sleep(1)
raise RuntimeError("Wi-Fi connection timed out")
def ntp_datetime():
ntptime.host = ntp_server
timestamp = time.gmtime(ntptime.time())
return (
timestamp[0], timestamp[1], timestamp[2], timestamp[6],
timestamp[3], timestamp[4], timestamp[5], timestamp[7]
)
def append_record(event, rtc_dt, ntp_dt, info):
info_value, startup_ms, lse_failed, newly_initialized = info
rtc_epoch = datetime_seconds(rtc_dt)
ntp_epoch = datetime_seconds(ntp_dt) if ntp_dt else 0
offset_seconds = rtc_epoch - ntp_epoch if ntp_dt else ""
new_file = not file_exists(LOG_FILE)
with open(LOG_FILE, "a") as log_file:
if new_file:
log_file.write(
"event,rtc_time,ntp_time,rtc_minus_ntp_seconds,"
"rtc_info,startup_ms,lse_failed,newly_initialized,"
"calibration\n"
)
log_file.write(
"%s,%s,%s,%s,%d,%d,%d,%d,%d\n"
% (
event,
datetime_text(rtc_dt),
datetime_text(ntp_dt) if ntp_dt else "",
offset_seconds,
info_value,
startup_ms,
lse_failed,
newly_initialized,
rtc.calibration(),
)
)
print(
"RTC_TEST event=%s rtc=%s ntp=%s offset_s=%s "
"info=0x%08X startup_ms=%d lse_failed=%s newly_initialized=%s "
"calibration=%d"
% (
event,
datetime_text(rtc_dt),
datetime_text(ntp_dt) if ntp_dt else "",
offset_seconds,
info_value,
startup_ms,
lse_failed,
newly_initialized,
rtc.calibration(),
)
)
def file_exists(filename):
try:
os.stat(filename)
return True
except OSError:
return False
def read_wake_count():
try:
with open(STATE_FILE, "r") as state_file:
return int(state_file.read().strip())
except (OSError, ValueError):
return 0
def write_wake_count(wake_count):
with open(STATE_FILE, "w") as state_file:
state_file.write(str(wake_count))
def disconnect_wifi(wlan):
if wlan is not None:
wlan.disconnect()
wlan.active(False)
# def main():
# print("RTC test starting; press Stop within 5 seconds to abort.")
# time.sleep(5)
# wake_count = read_wake_count() + 1
# write_wake_count(wake_count)
# rtc_dt = rtc.datetime()
# info = rtc_info_text()
# # This line occurs on every deep-sleep reboot and is the evidence for
# # LSE versus LSI fallback and unexpected RTC initialization.
# append_record("wake_%d" % wake_count, rtc_dt, None, info)
# if wake_count == 1 or wake_count % NTP_SYNC_EVERY_WAKES == 0:
# wlan = None
# try:
# wlan = connect_wifi()
# # Measure error BEFORE correcting the device calendar.
# ntp_dt = ntp_datetime()
# append_record(
# "ntp_check_before_set_%d" % wake_count,
# rtc.datetime(),
# ntp_dt,
# rtc_info_text(),
# )
# rtc.datetime(ntp_dt)
# # This confirms that setting the calendar worked.
# append_record(
# "ntp_set_%d" % wake_count,
# rtc.datetime(),
# ntp_dt,
# rtc_info_text(),
# )
# except Exception as error:
# print("RTC_TEST_NTP_ERROR:", error)
# append_record(
# "ntp_failed_%d" % wake_count,
# rtc.datetime(),
# None,
# rtc_info_text(),
# )
# finally:
# disconnect_wifi(wlan)
# rtc.wakeup(SLEEP_MS)
# machine.deepsleep()
def main():
wlan = None
try:
wlan = connect_wifi()
# Establish RTC and NTP reference at the same time.
ntp_dt = ntp_datetime()
rtc.datetime(ntp_dt)
append_record(
"awake_ntp_set",
rtc.datetime(),
ntp_dt,
rtc_info_text(),
)
disconnect_wifi(wlan)
wlan = None
print("RTC_TEST staying awake for %d seconds" % AWAKE_TEST_SECONDS)
# Keep the interpreter active without calling lightsleep/deepsleep.
started_ms = time.ticks_ms()
next_log_ms = 0
while time.ticks_diff(time.ticks_ms(), started_ms) < (
AWAKE_TEST_SECONDS * 1000
):
elapsed_ms = time.ticks_diff(time.ticks_ms(), started_ms)
if elapsed_ms >= next_log_ms:
append_record(
"awake_elapsed_%ds" % (elapsed_ms // 1000),
rtc.datetime(),
None,
rtc_info_text(),
)
next_log_ms += AWAKE_LOG_INTERVAL_SECONDS * 1000
time.sleep(1)
wlan = connect_wifi()
# Measure the offset before modifying RTC time.
ntp_dt = ntp_datetime()
append_record(
"awake_ntp_check_before_set",
rtc.datetime(),
ntp_dt,
rtc_info_text(),
)
except Exception as error:
print("RTC_TEST_ERROR:", error)
append_record(
"awake_test_failed",
rtc.datetime(),
None,
rtc_info_text(),
)
finally:
disconnect_wifi(wlan)
main()