diff --git a/app/include/rtc/rtctime.h b/app/include/rtc/rtctime.h index 3677b949..81bf8f67 100644 --- a/app/include/rtc/rtctime.h +++ b/app/include/rtc/rtctime.h @@ -62,6 +62,7 @@ struct rtc_tm{ void TEXT_SECTION_ATTR rtctime_early_startup (void); void rtctime_late_startup (void); +void rtctime_adjust_rate (int rate); void rtctime_gettimeofday (struct rtc_timeval *tv); void rtctime_settimeofday (const struct rtc_timeval *tv); bool rtctime_have_time (void); diff --git a/app/include/rtc/rtctime_internal.h b/app/include/rtc/rtctime_internal.h index d69fab40..b9a4068d 100644 --- a/app/include/rtc/rtctime_internal.h +++ b/app/include/rtc/rtctime_internal.h @@ -32,6 +32,11 @@ * @author Johny Mattsson */ +/* + * It is vital that this file is only included once in the entire + * system. + */ + #ifndef _RTCTIME_INTERNAL_H_ #define _RTCTIME_INTERNAL_H_ @@ -181,6 +186,17 @@ #define CPU_DEFAULT_MHZ 80 #define CPU_BOOTUP_MHZ 52 +#ifdef RTC_DEBUG_ENABLED +#define RTC_DBG(...) do { if (rtc_dbg_enabled == 'R') { dbg_printf(__VA_ARGS__); } } while (0) +static bool rtc_dbg_enabled; +#define RTC_DBG_ENABLED() rtc_dbg_enabled = 'R' +#define RTC_DBG_NOT_ENABLED() rtc_dbg_enabled = 0 +#else +#define RTC_DBG(...) +#define RTC_DBG_ENABLED() +#define RTC_DBG_NOT_ENABLED() +#endif + // RTCTIME storage #define RTC_TIME_MAGIC_POS (RTC_TIME_BASE+0) #define RTC_CYCLEOFFSETL_POS (RTC_TIME_BASE+1) @@ -190,8 +206,22 @@ #define RTC_CALIBRATION_POS (RTC_TIME_BASE+5) #define RTC_SLEEPTOTALUS_POS (RTC_TIME_BASE+6) #define RTC_SLEEPTOTALCYCLES_POS (RTC_TIME_BASE+7) -#define RTC_TODOFFSETUS_POS (RTC_TIME_BASE+8) -#define RTC_LASTTODUS_POS (RTC_TIME_BASE+9) +//#define RTC_TODOFFSETUS_POS (RTC_TIME_BASE+8) +//#define RTC_LASTTODUS_POS (RTC_TIME_BASE+9) +#define RTC_USRATE_POS (RTC_TIME_BASE+8) + +static uint32_t rtc_time_magic; +static uint64_t rtc_cycleoffset; +static uint32_t rtc_lastsourceval; +static uint32_t rtc_sourcecycleunits; +static uint32_t rtc_calibration; +static uint32_t rtc_sleeptotalus; +static uint32_t rtc_sleeptotalcycles; +static uint64_t rtc_usatlastrate; +static uint64_t rtc_rateadjustedus; +static uint32_t rtc_todoffsetus; +static uint32_t rtc_lasttodus; +static uint32_t rtc_usrate; struct rtc_timeval @@ -200,12 +230,78 @@ struct rtc_timeval uint32_t tv_usec; }; +static void bbram_load() { + rtc_time_magic = rtc_mem_read(RTC_TIME_MAGIC_POS); + rtc_cycleoffset = rtc_mem_read64(RTC_CYCLEOFFSETL_POS); + rtc_lastsourceval = rtc_mem_read(RTC_LASTSOURCEVAL_POS); + rtc_sourcecycleunits = rtc_mem_read(RTC_SOURCECYCLEUNITS_POS); + rtc_calibration = rtc_mem_read(RTC_CALIBRATION_POS); + rtc_sleeptotalus = rtc_mem_read(RTC_SLEEPTOTALUS_POS); + rtc_sleeptotalcycles = rtc_mem_read(RTC_SLEEPTOTALCYCLES_POS); + rtc_usrate = rtc_mem_read(RTC_USRATE_POS); +} + +static void bbram_save() { + RTC_DBG("bbram_save\n"); + rtc_mem_write(RTC_TIME_MAGIC_POS , rtc_time_magic); + rtc_mem_write64(RTC_CYCLEOFFSETL_POS , rtc_cycleoffset); + rtc_mem_write(RTC_LASTSOURCEVAL_POS , rtc_lastsourceval); + rtc_mem_write(RTC_SOURCECYCLEUNITS_POS , rtc_sourcecycleunits); + rtc_mem_write(RTC_CALIBRATION_POS , rtc_calibration); + rtc_mem_write(RTC_SLEEPTOTALUS_POS , rtc_sleeptotalus); + rtc_mem_write(RTC_SLEEPTOTALCYCLES_POS , rtc_sleeptotalcycles); + rtc_mem_write(RTC_USRATE_POS , rtc_usrate); +} + +static inline uint64_t div2080(uint64_t n) { + n = n >> 5; + uint64_t t = n >> 7; + + uint64_t q = t + (t >> 1) + (t >> 2); + + q += q >> 3; + q += q >> 12; + q += q >> 24; + q += q >> 48; + + uint32_t r = (uint32_t) n - (uint32_t) q * 65; + + uint32_t off = (r - (r >> 6)) >> 6; + + q = q + off; + + return q; +} + +static uint32_t div1m(uint32_t *rem, unsigned long long n) { + // 0 -> 0002000000000000 sub + // 0 >> 5 - 0 >> 19 + [-1] -> 00020fffc0000000 add + // 0 >> 9 - 0 >> 6 + [-1] -> 000208ffc0000000 add + // 2 >> 12 - 2 >> 23 -> 000000208bea0080 sub + + uint64_t q1 = (n >> 5) - (n >> 19) + n; + uint64_t q2 = q1 + (n >> 9) - (n >> 6); + uint64_t q3 = (q2 >> 12) - (q2 >> 23); + + uint64_t q = q1 - n + q2 - q3; + + q = q >> 20; + + uint32_t r = (uint32_t) n - (uint32_t) q * 1000000; + + if (r >= 1000000) { + r -= 1000000; + q++; + } + + *rem = r; + + return q; +} + static inline uint64_t rtc_time_get_now_us_adjusted(); -static inline uint32_t rtc_time_get_magic(void) -{ - return rtc_mem_read(RTC_TIME_MAGIC_POS); -} +#define rtc_time_get_magic() rtc_time_magic static inline bool rtc_time_check_sleep_magic(void) { @@ -225,9 +321,11 @@ static inline bool rtc_time_check_magic(void) return (magic==RTC_TIME_MAGIC_FRC2 || magic==RTC_TIME_MAGIC_CCOUNT || magic==RTC_TIME_MAGIC_SLEEP); } -static inline void rtc_time_set_magic(uint32_t new_magic) +static void rtc_time_set_magic(uint32_t new_magic) { - rtc_mem_write(RTC_TIME_MAGIC_POS,new_magic); + RTC_DBG("Set magic to %08x\n", new_magic); + rtc_time_magic = new_magic; + bbram_save(); } static inline void rtc_time_set_sleep_magic(void) @@ -249,7 +347,7 @@ static inline void rtc_time_set_frc2_magic(void) static inline void rtc_time_unset_magic(void) { - rtc_mem_write(RTC_TIME_MAGIC_POS,0); + rtc_time_set_magic(0); } static inline uint32_t rtc_time_read_raw(void) @@ -281,16 +379,16 @@ static inline uint64_t rtc_time_source_offset(void) case RTC_TIME_MAGIC_FRC2: raw=rtc_time_read_raw_frc2(); break; default: return 0; // We are not in a position to offer time } - uint32_t multiplier=rtc_mem_read(RTC_SOURCECYCLEUNITS_POS); - uint32_t previous=rtc_mem_read(RTC_LASTSOURCEVAL_POS); + uint32_t multiplier = rtc_sourcecycleunits; + uint32_t previous = rtc_lastsourceval; if (raw> 32; + usadj = usadj + rtc_rateadjustedus; + + if (usoff > 1000000000 || force) { + rtc_usatlastrate = us; + rtc_rateadjustedus = usadj; + } + + return usadj; +} + +static inline void rtc_time_set_rate(int32_t rate) { + uint64_t now=rtc_time_get_now_us_adjusted(); + rtc_time_adjust_us_by_rate(now, 1); + rtc_usrate = rate; +} + +static inline int32_t rtc_time_get_rate() { + return rtc_usrate; +} + +static inline void rtc_time_tmrfn(void* arg) +{ + uint64_t now=rtc_time_get_now_us_adjusted(); + rtc_time_adjust_us_by_rate(now, 0); + rtc_time_source_offset(); +} + + static inline void rtc_time_gettimeofday(struct rtc_timeval* tv) { uint64_t now=rtc_time_get_now_us_adjusted(); - uint32_t sec=now/1000000; - uint32_t usec=now%1000000; - uint32_t to_adjust=rtc_mem_read(RTC_TODOFFSETUS_POS); + now = rtc_time_adjust_us_by_rate(now, 0); + uint32_t usec; + uint32_t sec = div1m(&usec, now); + uint32_t to_adjust=rtc_todoffsetus; if (to_adjust) { uint32_t us_passed=rtc_time_us_since_time_reached(sec,usec); @@ -680,9 +826,8 @@ static inline void rtc_time_gettimeofday(struct rtc_timeval* tv) adjust=to_adjust; to_adjust-=adjust; now-=adjust; - now/1000000; - now%1000000; - rtc_mem_write(RTC_TODOFFSETUS_POS,to_adjust); + sec = div1m(&usec, now); + rtc_todoffsetus = to_adjust; } } tv->tv_sec=sec; @@ -690,23 +835,24 @@ static inline void rtc_time_gettimeofday(struct rtc_timeval* tv) rtc_time_register_time_reached(sec,usec); } -static inline void rtc_time_settimeofday(const struct rtc_timeval* tv) +static void rtc_time_settimeofday(const struct rtc_timeval* tv) { if (!rtc_time_check_magic()) return; - uint32_t sleep_us=rtc_mem_read(RTC_SLEEPTOTALUS_POS); - uint32_t sleep_cycles=rtc_mem_read(RTC_SLEEPTOTALCYCLES_POS); + uint32_t sleep_us=rtc_sleeptotalus; + uint32_t sleep_cycles=rtc_sleeptotalcycles; // At this point, the CPU clock will definitely be at the default rate (nodemcu fully booted) uint64_t now_esp_us=rtc_time_get_now_us_adjusted(); + now_esp_us = rtc_time_adjust_us_by_rate(now_esp_us, 1); uint64_t now_ntp_us=((uint64_t)tv->tv_sec)*1000000+tv->tv_usec; int64_t diff_us=now_esp_us-now_ntp_us; // Store the *actual* time. uint64_t target_unitcycles=now_ntp_us*UNITCYCLE_MHZ; uint64_t sourcecycles=rtc_time_source_offset(); - rtc_mem_write64(RTC_CYCLEOFFSETL_POS,target_unitcycles-sourcecycles); + rtc_cycleoffset = target_unitcycles-sourcecycles; // calibrate sleep period based on difference between expected time and actual time if (sleep_us>0 && sleep_us<0xffffffff && @@ -715,11 +861,15 @@ static inline void rtc_time_settimeofday(const struct rtc_timeval* tv) uint64_t actual_sleep_us=sleep_us-diff_us; uint32_t cali=(actual_sleep_us<<12)/sleep_cycles; if (rtc_time_calibration_is_sane(cali)) - rtc_mem_write(RTC_CALIBRATION_POS,cali); + rtc_calibration = cali; } - rtc_mem_write(RTC_SLEEPTOTALUS_POS,0); - rtc_mem_write(RTC_SLEEPTOTALCYCLES_POS,0); + rtc_sleeptotalus = 0; + rtc_sleeptotalcycles = 0; + + rtc_usatlastrate = now_ntp_us; + rtc_rateadjustedus = now_ntp_us; + rtc_usrate = 0; // Deal with time adjustment if necessary if (diff_us>0) // Time went backwards. Avoid that.... @@ -730,7 +880,7 @@ static inline void rtc_time_settimeofday(const struct rtc_timeval* tv) } else diff_us=0; - rtc_mem_write(RTC_TODOFFSETUS_POS,diff_us); + rtc_todoffsetus = diff_us; uint32_t now_s=now_ntp_us/1000000; uint32_t now_us=now_ntp_us%1000000; diff --git a/app/modules/rtctime.c b/app/modules/rtctime.c index b6558e5f..f8656a6e 100644 --- a/app/modules/rtctime.c +++ b/app/modules/rtctime.c @@ -57,6 +57,11 @@ void rtctime_late_startup (void) rtc_time_switch_system (); } +void rtctime_adjust_rate (int rate) +{ + rtc_time_set_rate (rate); +} + void rtctime_gettimeofday (struct rtc_timeval *tv) { rtc_time_gettimeofday (tv); @@ -66,7 +71,9 @@ void rtctime_settimeofday (const struct rtc_timeval *tv) { if (!rtc_time_check_magic ()) rtc_time_prepare (); + int32_t rate = rtc_time_get_rate(); rtc_time_settimeofday (tv); + rtc_time_set_rate(rate); } bool rtctime_have_time (void) @@ -131,6 +138,9 @@ static int rtctime_set (lua_State *L) struct rtc_timeval tv = { sec, usec }; rtctime_settimeofday (&tv); + + if (lua_isnumber(L, 3)) + rtc_time_set_rate(lua_tonumber(L, 3)); return 0; } @@ -142,7 +152,8 @@ static int rtctime_get (lua_State *L) rtctime_gettimeofday (&tv); lua_pushnumber (L, tv.tv_sec); lua_pushnumber (L, tv.tv_usec); - return 2; + lua_pushnumber (L, rtc_time_get_rate()); + return 3; } static void do_sleep_opt (lua_State *L, int idx) diff --git a/app/modules/sntp.c b/app/modules/sntp.c index e1a06a16..f783eaa1 100644 --- a/app/modules/sntp.c +++ b/app/modules/sntp.c @@ -254,7 +254,7 @@ static void sntp_handle_result(lua_State *L) { int64_t f = ((state->best.delta * PLL_A) >> 32) + pll_increment; pll_increment += (state->best.delta * PLL_B) >> 32; sntp_dbg("f=%d, increment=%d\n", (int32_t) f, (int32_t) pll_increment); - //rtctime_adjust_rate((int32_t) f); + rtctime_adjust_rate((int32_t) f); } else { rtctime_settimeofday (&tv); } diff --git a/docs/en/modules/rtctime.md b/docs/en/modules/rtctime.md index 860e1138..3b04ede2 100644 --- a/docs/en/modules/rtctime.md +++ b/docs/en/modules/rtctime.md @@ -5,19 +5,28 @@ The rtctime module provides advanced timekeeping support for NodeMCU, including keeping time across deep sleep cycles (provided [`rtctime.dsleep()`](#rtctimedsleep) is used instead of [`node.dsleep()`](node.md#nodedsleep)). This can be used to significantly extend battery life on battery powered sensor nodes, as it is no longer necessary to fire up the RF module each wake-up in order to obtain an accurate timestamp. -This module is intended for use together with [NTP](https://en.wikipedia.org/wiki/Network_Time_Protocol) (Network Time Protocol) for keeping highly accurate real time at all times. Timestamps are available with microsecond precision, based on the Unix Epoch (1970/01/01 00:00:00). +This module is intended for use together with [NTP](https://en.wikipedia.org/wiki/Network_Time_Protocol) (Network Time Protocol) for keeping highly accurate real time at all times. Timestamps are available with microsecond precision, based on the Unix Epoch (1970/01/01 00:00:00). However, the accuracy is (in practice) no better then 1ms, and often worse than that. Time keeping on the ESP8266 is technically quite challenging. Despite being named [RTC](https://en.wikipedia.org/wiki/Real-time_clock), the RTC is not really a Real Time Clock in the normal sense of the word. While it does keep a counter ticking while the module is sleeping, the accuracy with which it does so is *highly* dependent on the temperature of the chip. Said temperature changes significantly between when the chip is running and when it is sleeping, meaning that any calibration performed while the chip is active becomes useless mere moments after the chip has gone to sleep. As such, calibration values need to be deduced across sleep cycles in order to enable accurate time keeping. This is one of the things this module does. Further complicating the matter of time keeping is that the ESP8266 operates on three different clock frequencies - 52MHz right at boot, 80MHz during regular operation, and 160MHz if boosted. This module goes to considerable length to take all of this into account to properly keep the time. -To enable this module, it needs to be given a reference time at least once (via [`rtctime.set()`](#rtctimeset)). For best accuracy it is recommended to provide a reference time twice, with the second time being after a deep sleep. +To enable this module, it needs to be given a reference time at least once (via [`rtctime.set()`](#rtctimeset)). For best accuracy it is recommended to provide reference +times at regular intervals. The [`sntp.sync()`](sntp.md#sntpsync) function has an easy way to do this. It is important that a reference time is provided at least twice, with the second time being after a deep sleep. Note that while the rtctime module can keep time across deep sleeps, it *will* lose the time if the module is unexpectedly reset. +This module can compensate for the underlying clock not running at exactly the required rate. The adjustment is in steps of 1 part in 2^32 (i.e. around 0.25 ppb). This adjustment +is done automatically if the [`sntp.sync()`](sntp.md#sntpsync) is called with the `autorepeat` flag set. The rate is settable using the [`set()`](#rtctimeset) function below. When the platform +is booted, it defaults to 0 (i.e. nominal). A sample of modules shows that the actual clock rate is temperature dependant, but is normally within 5ppm of the nominal rate. This translates to around 15 seconds per month. + +In the automatic update mode it can take a couple of hours for the clock rate to settle down to the correct value. After that, how well it tracks will depend on the amount +of variation in timestamps from the NTP servers. If they are close, then the time will track to within a millisecond or so. If they are further away (say 100ms round trip), then +time tracking is somewhat worse, but normally within 10ms. + !!! important - This module uses RTC memory slots 0-9, inclusive. As soon as [`rtctime.set()`](#rtctimeset) (or [`sntp.sync()`](sntp.md#sntpsync)) has been called these RTC memory slots will be used. +This module uses RTC memory slots 0-9, inclusive. As soon as [`rtctime.set()`](#rtctimeset) (or [`sntp.sync()`](sntp.md#sntpsync)) has been called these RTC memory slots will be used. This is a companion module to the [rtcmem](rtcmem.md) and [SNTP](sntp.md) modules. @@ -30,6 +39,8 @@ Puts the ESP8266 into deep sleep mode, like [`node.dsleep()`](node.md#nodedsleep - The time slept will generally be considerably more accurate than with [`node.dsleep()`](node.md#nodedsleep). - A sleep time of zero does not mean indefinite sleep, it is interpreted as a zero length sleep instead. +When the sleep timer expires, the platform is rebooted and the lua code is started with the `init.lua` file. The clock is set reasonably accurately. + #### Syntax `rtctime.dsleep(microseconds [, option])` @@ -107,14 +118,15 @@ Returns the current time. If current time is not available, zero is returned. none #### Returns -A two-value timestamp containing: +A three-value timestamp containing: - `sec` seconds since the Unix epoch - `usec` the microseconds part +- `rate` the current clock rate offset. This is an offset of `rate / 2^32` (where the nominal rate is 1). For example, a value of 4295 corresponds to 1 part per million. #### Example ```lua -sec, usec = rtctime.get() +sec, usec, rate = rtctime.get() ``` #### See also [`rtctime.set()`](#rtctimeset) @@ -128,11 +140,12 @@ It is highly recommended that the timestamp is obtained via NTP (see [SNTP modul Values very close to the epoch are not supported. This is a side effect of keeping the memory requirements as low as possible. Considering that it's no longer 1970, this is not considered a problem. #### Syntax -`rtctime.set(seconds, microseconds)` +`rtctime.set(seconds, microseconds, [rate])` #### Parameters - `seconds` the seconds part, counted from the Unix epoch - `microseconds` the microseconds part +- `rate` the rate in the same units as for `rtctime.get()`. The stored rate is not modified if not specified. #### Returns `nil`