feat(radio): Automatic clock drift compensation (#7763)

Co-authored-by: 3djc <3djc@gh.com>
Co-authored-by: Peter Feerick <5500713+pfeerick@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
3djc
2026-09-18 10:50:36 +10:00
committed by GitHub
co-authored by 3djc Peter Feerick Claude Sonnet 5
parent 6fe19b2bfd
commit ea44383da7
8 changed files with 565 additions and 11 deletions
+85 -4
View File
@@ -248,6 +248,40 @@ int toInt(const char ** argv, int index, int * val)
return result;
}
// "30" -> 30 s, "30.52" -> 30 s 520 ms
static int toSeconds(const char ** argv, int index, int * sec, int * ms)
{
const char * s = argv[index];
if (s == nullptr || *s == '\0') return 0;
char * endptr = nullptr;
long v = strtol(s, &endptr, 10);
if (endptr == s || v < 0 || v > 59) {
cliSerialPrint("%s: Invalid argument \"%s\"", argv[0], s);
return -1;
}
*sec = (int)v;
*ms = 0;
if (*endptr == '\0') return 1;
if (*endptr != '.') {
cliSerialPrint("%s: Invalid argument \"%s\"", argv[0], s);
return -1;
}
int scale = 100;
for (const char * p = endptr + 1; *p; p++) {
if (*p < '0' || *p > '9') {
cliSerialPrint("%s: Invalid argument \"%s\"", argv[0], s);
return -1;
}
if (scale) {
*ms += (*p - '0') * scale;
scale /= 10;
}
}
return 2;
}
int cliBeep(const char ** argv)
{
int freq = BEEP_DEFAULT_FREQ;
@@ -1083,10 +1117,11 @@ int cliSet(const char **argv)
{
if (!strcmp(argv[1], "rtc")) {
struct gtm t;
int year, month, day, hour, minute, second;
int year, month, day, hour, minute, second, ms = 0;
int secOk = toSeconds(argv, 7, &second, &ms);
if (toInt(argv, 2, &year) > 0 && toInt(argv, 3, &month) > 0 &&
toInt(argv, 4, &day) > 0 && toInt(argv, 5, &hour) > 0 &&
toInt(argv, 6, &minute) > 0 && toInt(argv, 7, &second) > 0) {
toInt(argv, 6, &minute) > 0 && secOk > 0) {
t.tm_year = year - TM_YEAR_BASE;
t.tm_mon = month - 1;
t.tm_mday = day;
@@ -1095,13 +1130,45 @@ int cliSet(const char **argv)
t.tm_sec = second;
// update local timestamp and get wday calculated
g_rtcTime = gmktime(&t);
rtcSetTime(&t);
// the CLI is driven by a host, the menu is where someone sets it by hand
rtcSetTimeAt(&t, (uint16_t)ms);
#if defined(DEBUG)
const struct RtcCalibReport * rep = rtcGetCalibrationReport();
if (rep->elapsed != 0) {
int32_t ppm10 = 0;
if (rep->elapsed > 0)
ppm10 = (int32_t)(((int64_t)rep->errorMs * 10000) / rep->elapsed);
cliSerialPrint("rtc drift = %d ms over %d s (%d ppm x10)", (int)rep->errorMs,
(int)rep->elapsed, (int)ppm10);
}
int32_t units = rtcGetCalibration();
cliSerialPrint("rtc calibration: %s (now %d units, %d ppm x10)",
rtcCalibrationResultText(rep->result), (int)units,
(int)rtcCalibrationPpm10(units));
#endif
} else {
cliSerialPrint("%s: Invalid arguments \"%s\" \"%s\"", argv[0], argv[1],
argv[2]);
return -1;
}
}
#if defined(DEBUG)
else if (!strcmp(argv[1], "rtccal")) {
int ppm = 0;
if (!strcmp(argv[2], "reset")) {
rtcResetCalibration();
} else if (toInt(argv, 2, &ppm) > 0) {
rtcSetCalibration(rtcCalibrationUnits(ppm));
} else {
cliSerialPrint("%s: expected \"reset\" or a ppm value", argv[0]);
return -1;
}
int32_t units = rtcGetCalibration();
cliSerialPrint("rtc calibration = %d units (%d ppm x10), reference = %u",
(int)units, (int)rtcCalibrationPpm10(units),
(unsigned)rtcGetCalibrationRef());
}
#endif
#if !defined(SOFTWARE_VOLUME) && defined(AUDIO)
else if (!strcmp(argv[1], "volume")) {
int level = 0;
@@ -1500,8 +1567,22 @@ int cliDisplay(const char ** argv)
}
else if (!strcmp(argv[1], "rtc")) {
struct gtm utm;
uint8_t sw100 = g_ms100;
gettime(&utm);
cliSerialPrint("rtc = %4d-%02d-%02d %02d:%02d:%02d.%02d0", utm.tm_year+TM_YEAR_BASE, utm.tm_mon+1, utm.tm_mday, utm.tm_hour, utm.tm_min, utm.tm_sec, g_ms100);
cliSerialPrint("rtc = %4d-%02d-%02d %02d:%02d:%02d.%02d0", utm.tm_year+TM_YEAR_BASE, utm.tm_mon+1, utm.tm_mday, utm.tm_hour, utm.tm_min, utm.tm_sec, sw100);
// gettime() reports the software clock, which free runs on the system tick
struct gtm htm = {};
uint16_t hwMs = rtcGetTimeMs(&htm);
cliSerialPrint("rtc hw = %4d-%02d-%02d %02d:%02d:%02d.%03d", htm.tm_year+TM_YEAR_BASE,
htm.tm_mon+1, htm.tm_mday, htm.tm_hour, htm.tm_min, htm.tm_sec, (int)hwMs);
int32_t skew = (int32_t)(((int64_t)gmktime(&utm) - gmktime(&htm)) * 1000
+ (int32_t)sw100 * 10 - hwMs);
cliSerialPrint("rtc software clock offset = %d ms", (int)skew);
int32_t units = rtcGetCalibration();
cliSerialPrint("rtc calibration = %d units (%d ppm x10), reference = %u",
(int)units, (int)rtcCalibrationPpm10(units),
(unsigned)rtcGetCalibrationRef());
}
#if defined(VOLUME_I2C_ADDRESS)
else if (!strcmp(argv[1], "volume")) {