From af01537a95de4ce84da8957b910fdbd8f67c6a79 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Wed, 26 Jan 2022 16:21:44 +0100 Subject: [PATCH] improve RTC accuracy --- include/globals.h | 1 - include/rtctime.h | 2 +- include/timekeeper.h | 6 +++--- src/main.cpp | 35 +++++++++++++++----------------- src/rtctime.cpp | 20 ++++++++++++------ src/timekeeper.cpp | 48 +++++++++++++++++++++++--------------------- 6 files changed, 59 insertions(+), 53 deletions(-) diff --git a/include/globals.h b/include/globals.h index a3a60341..fb2e773e 100644 --- a/include/globals.h +++ b/include/globals.h @@ -111,6 +111,5 @@ typedef struct { } sdsStatus_t; extern char clientId[20]; // unique clientID -extern time_t _COMPILETIME; // epoch build time #endif \ No newline at end of file diff --git a/include/rtctime.h b/include/rtctime.h index d4d64935..3f75eb37 100644 --- a/include/rtctime.h +++ b/include/rtctime.h @@ -12,7 +12,7 @@ extern RtcDS3231 Rtc; // make RTC instance globally available uint8_t rtc_init(void); uint8_t set_rtctime(time_t t); void sync_rtctime(void); -time_t get_rtctime(void); +time_t get_rtctime(uint16_t *msec); float get_rtctemp(void); #endif // _RTCTIME_H \ No newline at end of file diff --git a/include/timekeeper.h b/include/timekeeper.h index 8eb184b4..956595df 100644 --- a/include/timekeeper.h +++ b/include/timekeeper.h @@ -10,8 +10,8 @@ #include "dcf77.h" #include "esp_sntp.h" -#define SECS_YR_2000 (946684800UL) // the time at the start of y2k -#define GPS_UTC_DIFF 315964800UL // seconds diff between gps and utc epoch +#define SECS_YR_2000 (946684800UL) // the time at the start of y2k +#define GPS_UTC_DIFF 315964800UL // seconds diff between gps and utc epoch #define LEAP_SECS_SINCE_GPSEPOCH 18UL // state of 2021 enum timesource_t { _gps, _rtc, _lora, _unsynced, _set }; @@ -33,7 +33,7 @@ bool timeIsValid(time_t const t); void calibrateTime(void); void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec, timesource_t mytimesource); -time_t compileTime(const String compile_date); +time_t compileTime(void); time_t mkgmtime(const struct tm *ptm); TickType_t tx_Ticks(uint32_t framesize, unsigned long baud, uint32_t config, int8_t rxPin, int8_t txPins); diff --git a/src/main.cpp b/src/main.cpp index fda59d8e..a2b2cc1a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -25,26 +25,24 @@ licenses. Refer to LICENSE.txt file in repository for more details. // Tasks and timers: -Task Core Prio Purpose +Task Core Prio Purpose ------------------------------------------------------------------------------- -ledloop 0 3 blinks LEDs -spiloop 0 2 reads/writes data on spi interface -IDLE 0 0 ESP32 arduino scheduler -> runs wifi sniffer +ledloop* 0 3 blinks LEDs +spiloop# 0 2 reads/writes data on spi interface +lmictask* 1 2 MCCI LMiC LORAWAN stack +clockloop# 1 4 generates realtime telegrams for external clock +mqttloop# 1 2 reads/writes data on ETH interface +timesync_proc# 1 3 processes realtime time sync requests +irqhandler# 1 2 cyclic tasks (i.e. displayrefresh) triggered by +timers gpsloop* 1 1 reads data from GPS via serial or i2c +lorasendtask# 1 1 feeds data from lora sendqueue to lmcic +rmcd_process# 1 1 Remote command interpreter loop -lmictask 1 2 MCCI LMiC LORAWAN stack -clockloop 1 4 generates realtime telegrams for external clock -mqttloop 1 2 reads/writes data on ETH interface -timesync_proc 1 3 processes realtime time sync requests -irqhandler 1 2 cyclic tasks (i.e. displayrefresh) triggered by timers -gpsloop 1 1 reads data from GPS via serial or i2c -lorasendtask 1 1 feeds data from lora sendqueue to lmcic -rmcd_process 1 1 Remote command interpreter loop -IDLE 1 0 ESP32 arduino scheduler -> runs wifi channel rotator +* spinning task +# blocked/waiting task Low priority numbers denote low priority tasks. - -NOTE: Changing any timings will have impact on time accuracy of whole code. -So don't do it if you do not own a digital oscilloscope. +------------------------------------------------------------------------------- // ESP32 hardware timers ------------------------------------------------------------------------------- @@ -126,7 +124,7 @@ void setup() { snprintf(clientId, 20, "paxcounter_%08x", hashedmac); ESP_LOGI(TAG, "Starting %s v%s (runmode=%d / restarts=%d)", clientId, PROGVERSION, RTC_runmode, RTC_restarts); - ESP_LOGI(TAG, "code build date: %d", _COMPILETIME); + ESP_LOGI(TAG, "code build date: %d", compileTime()); // print chip information on startup if in verbose mode after coldstart #if (VERBOSE) @@ -494,8 +492,7 @@ void setup() { cyclicTimer.attach(HOMECYCLE, setCyclicIRQ); // only if we have a timesource we do timesync -#if ((TIME_SYNC_LORAWAN) || (TIME_SYNC_LORASERVER) || (HAS_GPS) || \ - defined HAS_RTC) +#if ((TIME_SYNC_LORAWAN) || (TIME_SYNC_LORASERVER) || (HAS_GPS) || (HAS_RTC)) #if (defined HAS_IF482 || defined HAS_DCF77) ESP_LOGI(TAG, "Starting Clock Controller..."); diff --git a/src/rtctime.cpp b/src/rtctime.cpp index cb87c9de..eb25698d 100644 --- a/src/rtctime.cpp +++ b/src/rtctime.cpp @@ -25,14 +25,14 @@ uint8_t rtc_init(void) { } #if (TIME_SYNC_COMPILEDATE) - // initialize a blank RTC without battery backup with compiled time + // initialize a blank RTC without battery backup with build time RtcDateTime tt = Rtc.GetDateTime(); time_t t = tt.Epoch32Time(); // sec2000 -> epoch if (!Rtc.IsDateTimeValid() || !timeIsValid(t)) { - ESP_LOGW(TAG, "RTC has no recent time, setting to compiled time"); - Rtc.SetDateTime( - RtcDateTime(_COMPILETIME - SECS_YR_2000)); // epoch -> sec2000 + ESP_LOGW(TAG, "RTC has no recent time, setting to compiletime"); + Rtc.SetDateTime(RtcDateTime(mkgmtime(compileTime()) - + SECS_YR_2000)); // epoch -> sec2000 } #endif @@ -62,15 +62,23 @@ uint8_t set_rtctime(time_t t) { // t is sec epoch time } } // set_rtctime() -time_t get_rtctime(void) { +time_t get_rtctime(uint16_t *msec) { time_t t = 0; + *msec = 0; if (I2C_MUTEX_LOCK()) { if (Rtc.IsDateTimeValid() && Rtc.GetIsRunning()) { RtcDateTime tt = Rtc.GetDateTime(); t = tt.Epoch32Time(); // sec2000 -> epoch } I2C_MUTEX_UNLOCK(); - return timeIsValid(t); +#ifdef RTC_INT + // adjust time to top of next second by waiting TimePulseTick to flip + bool lastTick = TimePulseTick; + while (TimePulseTick == lastTick) { + }; + t++; +#endif + return t; } else { ESP_LOGE(TAG, "RTC get time failure"); return 0; // failure diff --git a/src/timekeeper.cpp b/src/timekeeper.cpp index 876bff85..ab686650 100644 --- a/src/timekeeper.cpp +++ b/src/timekeeper.cpp @@ -21,7 +21,6 @@ const char timeSetSymbols[] = {'G', 'R', 'L', '*', '?'}; bool volatile TimePulseTick = false; timesource_t timeSource = _unsynced; -time_t _COMPILETIME = compileTime(__DATE__); TaskHandle_t ClockTask = NULL; hw_timer_t *ppsIRQ = NULL; @@ -40,8 +39,7 @@ Ticker timesyncer; void setTimeSyncIRQ() { xTaskNotify(irqHandlerTask, TIMESYNC_IRQ, eSetBits); } void calibrateTime(void) { - ESP_LOGD(TAG, "[%0.3f] calibrateTime, timeSource == %d", _seconds(), - timeSource); + time_t t = 0; uint16_t t_msec = 0; @@ -57,7 +55,7 @@ void calibrateTime(void) { // has RTC -> fallback to RTC time #ifdef HAS_RTC - t = get_rtctime(); + t = get_rtctime(&t_msec); // set time from RTC - method will check if time is valid setMyTime((uint32_t)t, t_msec, _rtc); #endif @@ -101,26 +99,27 @@ void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec, vTaskDelay(pdMS_TO_TICKS(1000 - t_msec % 1000)); } + // from here on we are on top of next second + tv.tv_sec = time_to_set; tv.tv_usec = 0; sntp_sync_time(&tv); ESP_LOGI(TAG, "[%0.3f] UTC time: %d.000 sec", _seconds(), time_to_set); - // if we have a software pps timer, shift it to top of second - if (ppsIRQ != NULL) { - - timerWrite(ppsIRQ, 0); // reset pps timer - CLOCKIRQ(); // fire clock pps, this advances time 1 sec - } - -// if we have got an external timesource, set RTC time and shift RTC_INT pulse -// to top of second + // if we have a precise time timesource, set RTC time and shift RTC_INT + // pulse to top of second #ifdef HAS_RTC if ((mytimesource == _gps) || (mytimesource == _lora)) set_rtctime(time_to_set); #endif + // if we have a software pps timer, shift it to top of second + if (ppsIRQ != NULL) { + timerWrite(ppsIRQ, 0); // reset pps timer + CLOCKIRQ(); // fire clock pps to advance wall clock by 1 sec + } + timeSource = mytimesource; // set global variable timesyncer.attach(TIME_SYNC_INTERVAL * 60, setTimeSyncIRQ); @@ -132,7 +131,7 @@ void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec, "[%0.3f] Failed to synchronise time from source %c | unix sec " "obtained from source: %d | unix sec at program compilation: %d", _seconds(), timeSetSymbols[mytimesource], time_to_set, - _COMPILETIME); + compileTime()); } } @@ -140,7 +139,7 @@ void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec, uint8_t timepulse_init() { // set esp-idf API sntp sync mode - //sntp_init(); + // sntp_init(); sntp_set_sync_mode(SNTP_SYNC_MODE_IMMED); // use time pulse from GPS as time base with fixed 1Hz frequency @@ -224,7 +223,9 @@ void IRAM_ATTR CLOCKIRQ(void) { // helper function to check plausibility of a given epoch time bool timeIsValid(time_t const t) { // is t a time in the past? we use compile time to guess - return (t > _COMPILETIME); + // compile time is some local time, but we do not know it's time zone + // thus, we go 1 full day back to be sure to catch a time in the past + return (t > (compileTime() - 86400)); } // helper function to calculate serial transmit time @@ -283,7 +284,7 @@ void clock_loop(void *taskparameter) { // ClockTask // set calendar time for next second of clock output tt = (time_t)(current_time + 1); localtime_r(&tt, &t); - mktime(&t); + tt = mktime(&t); #if defined HAS_IF482 @@ -293,13 +294,13 @@ void clock_loop(void *taskparameter) { // ClockTask if (xTaskNotifyWait(0x00, ULONG_MAX, ¤t_time, txDelay) == pdTRUE) { tt = (time_t)(current_time + 1); localtime_r(&tt, &t); - mktime(&t); + tt = mktime(&t); } // send IF482 telegram - IF482.print(IF482_Frame(t)); // note: telegram is for *next* second + IF482.print(IF482_Frame(tt)); // note: telegram is for *next* second - ESP_LOGD(TAG, "[%0.3f] IF482: %s", _seconds(), IF482_Frame(t)); + ESP_LOGD(TAG, "[%0.3f] IF482: %s", _seconds(), IF482_Frame(tt).c_str()); #elif defined HAS_DCF77 @@ -340,7 +341,7 @@ void clock_loop(void *taskparameter) { // ClockTask } // clock_loop() // we use compile date to create a time_t reference "in the past" -time_t compileTime(const String compile_date) { +time_t compileTime(void) { char s_month[5]; int year; @@ -353,10 +354,11 @@ time_t compileTime(const String compile_date) { if (secs == -1) { // determine date - // we go one day back to bypass unknown timezone of local time - sscanf(compile_date.c_str(), "%s %d %d", s_month, &t.tm_mday - 1, &year); + sscanf(__DATE__, "%s %d %d", s_month, &t.tm_mday, &year); t.tm_mon = (strstr(month_names, s_month) - month_names) / 3; t.tm_year = year - 1900; + // determine time + sscanf(__TIME__, "%d:%d:%d", &t.tm_hour, &t.tm_min, &t.tm_sec); // convert to secs local time secs = mktime(&t);