Add tracking and control of the rate error in the clock crystal. (#1697)
* Improve RTC timekeeping -- includes clock rate tracking * Improved division by 1M * Fix crash in sntp * Disable RTC debug * Get the offset correct * Add comments on where the mysterious numbers came from * Fix a crash with auto repeat mode and errors on repeat
This commit is contained in:
parent
864bcdbd89
commit
d93465cd86
|
@ -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);
|
||||
|
|
|
@ -32,6 +32,11 @@
|
|||
* @author Johny Mattsson <jmattsson@dius.com.au>
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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<previous)
|
||||
{ // We had a rollover.
|
||||
uint64_t to_add=(1ULL<<32)*multiplier;
|
||||
uint64_t base=rtc_mem_read64(RTC_CYCLEOFFSETL_POS);
|
||||
uint64_t base = rtc_cycleoffset;
|
||||
if (base)
|
||||
rtc_mem_write64(RTC_CYCLEOFFSETL_POS,base+to_add);
|
||||
rtc_cycleoffset = base + to_add;
|
||||
}
|
||||
rtc_mem_write(RTC_LASTSOURCEVAL_POS,raw);
|
||||
rtc_lastsourceval = raw;
|
||||
return ((uint64_t)raw)*multiplier;
|
||||
}
|
||||
|
||||
|
@ -298,7 +396,7 @@ static inline uint64_t rtc_time_unix_unitcycles(void)
|
|||
{
|
||||
// Note: The order of these two must be maintained, as the first call might change the outcome of the second
|
||||
uint64_t offset=rtc_time_source_offset();
|
||||
uint64_t base=rtc_mem_read64(RTC_CYCLEOFFSETL_POS);
|
||||
uint64_t base = rtc_cycleoffset;
|
||||
|
||||
if (!base)
|
||||
return 0; // No known time
|
||||
|
@ -308,17 +406,22 @@ static inline uint64_t rtc_time_unix_unitcycles(void)
|
|||
|
||||
static inline uint64_t rtc_time_unix_us(void)
|
||||
{
|
||||
#if UNITCYCLE_MHZ == 2080
|
||||
return div2080(rtc_time_unix_unitcycles());
|
||||
#else
|
||||
#error UNITCYCLE_MHZ has an odd value
|
||||
return rtc_time_unix_unitcycles()/UNITCYCLE_MHZ;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void rtc_time_register_time_reached(uint32_t s, uint32_t us)
|
||||
{
|
||||
rtc_mem_write(RTC_LASTTODUS_POS,us);
|
||||
rtc_lasttodus = us;
|
||||
}
|
||||
|
||||
static inline uint32_t rtc_time_us_since_time_reached(uint32_t s, uint32_t us)
|
||||
{
|
||||
uint32_t lastus=rtc_mem_read(RTC_LASTTODUS_POS);
|
||||
uint32_t lastus=rtc_lasttodus;
|
||||
if (us<lastus)
|
||||
us+=1000000;
|
||||
return us-lastus;
|
||||
|
@ -334,14 +437,14 @@ static inline bool rtc_time_calibration_is_sane(uint32_t cali)
|
|||
|
||||
static inline uint32_t rtc_time_get_calibration(void)
|
||||
{
|
||||
uint32_t cal=rtc_time_check_magic()?rtc_mem_read(RTC_CALIBRATION_POS):0;
|
||||
uint32_t cal=rtc_time_check_magic()?rtc_calibration:0;
|
||||
if (!cal)
|
||||
{
|
||||
// Make a first guess, most likely to be rather bad, but better then nothing.
|
||||
#ifndef BOOTLOADER_CODE // This will pull in way too much of the system for the bootloader to handle.
|
||||
ets_delay_us(200);
|
||||
cal=system_rtc_clock_cali_proc();
|
||||
rtc_mem_write(RTC_CALIBRATION_POS,cal);
|
||||
rtc_calibration = cal;
|
||||
#else
|
||||
cal=6<<12;
|
||||
#endif
|
||||
|
@ -351,7 +454,7 @@ static inline uint32_t rtc_time_get_calibration(void)
|
|||
|
||||
static inline void rtc_time_invalidate_calibration(void)
|
||||
{
|
||||
rtc_mem_write(RTC_CALIBRATION_POS,0);
|
||||
rtc_calibration = 0;
|
||||
}
|
||||
|
||||
static inline uint64_t rtc_time_us_to_ticks(uint64_t us)
|
||||
|
@ -374,7 +477,7 @@ static inline uint64_t rtc_time_get_now_us_adjusted(void)
|
|||
uint64_t raw=rtc_time_get_now_us_raw();
|
||||
if (!raw)
|
||||
return 0;
|
||||
return raw+rtc_mem_read(RTC_TODOFFSETUS_POS);
|
||||
return raw+rtc_todoffsetus;
|
||||
}
|
||||
|
||||
|
||||
|
@ -383,16 +486,16 @@ static inline void rtc_time_add_sleep_tracking(uint32_t us, uint32_t cycles)
|
|||
if (rtc_time_check_magic())
|
||||
{
|
||||
// us is the one that will grow faster...
|
||||
uint32_t us_before=rtc_mem_read(RTC_SLEEPTOTALUS_POS);
|
||||
uint32_t us_before=rtc_sleeptotalus;
|
||||
uint32_t us_after=us_before+us;
|
||||
uint32_t cycles_after=rtc_mem_read(RTC_SLEEPTOTALCYCLES_POS)+cycles;
|
||||
uint32_t cycles_after=rtc_sleeptotalcycles+cycles;
|
||||
|
||||
if (us_after<us_before) // Give up if it would cause an overflow
|
||||
{
|
||||
us_after=cycles_after=0xffffffff;
|
||||
}
|
||||
rtc_mem_write(RTC_SLEEPTOTALUS_POS, us_after);
|
||||
rtc_mem_write(RTC_SLEEPTOTALCYCLES_POS,cycles_after);
|
||||
rtc_sleeptotalus = us_after;
|
||||
rtc_sleeptotalcycles = cycles_after;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -402,6 +505,8 @@ static void rtc_time_enter_deep_sleep_us(uint32_t us)
|
|||
{
|
||||
if (rtc_time_check_wake_magic())
|
||||
rtc_time_set_sleep_magic();
|
||||
else
|
||||
bbram_save();
|
||||
|
||||
rtc_reg_write(0,0);
|
||||
rtc_reg_write(0,rtc_reg_read(0)&0xffffbfff);
|
||||
|
@ -442,22 +547,23 @@ static void rtc_time_enter_deep_sleep_us(uint32_t us)
|
|||
rtc_time_enter_deep_sleep_final();
|
||||
}
|
||||
|
||||
static inline void rtc_time_deep_sleep_us(uint32_t us)
|
||||
static void rtc_time_deep_sleep_us(uint32_t us)
|
||||
{
|
||||
if (rtc_time_check_magic())
|
||||
{
|
||||
uint32_t to_adjust=rtc_mem_read(RTC_TODOFFSETUS_POS);
|
||||
uint32_t to_adjust=rtc_todoffsetus;
|
||||
if (to_adjust)
|
||||
{
|
||||
us+=to_adjust;
|
||||
rtc_mem_write(RTC_TODOFFSETUS_POS,0);
|
||||
rtc_todoffsetus = 0;
|
||||
}
|
||||
uint64_t now=rtc_time_get_now_us_raw(); // Now the same as _adjusted()
|
||||
if (now)
|
||||
{ // Need to maintain the clock first. When we wake up, counter will be 0
|
||||
uint64_t wakeup=now+us;
|
||||
uint64_t wakeup_cycles=wakeup*UNITCYCLE_MHZ;
|
||||
rtc_mem_write64(RTC_CYCLEOFFSETL_POS,wakeup_cycles);
|
||||
// Probly should factor in the rate here TODO
|
||||
rtc_cycleoffset = wakeup_cycles;
|
||||
}
|
||||
}
|
||||
rtc_time_enter_deep_sleep_us(us);
|
||||
|
@ -476,27 +582,32 @@ static inline void rtc_time_deep_sleep_until_aligned(uint32_t align, uint32_t mi
|
|||
rtc_time_deep_sleep_us(then-now);
|
||||
}
|
||||
|
||||
static inline void rtc_time_reset(bool clear_cali)
|
||||
static void rtc_time_reset(bool clear_cali)
|
||||
{
|
||||
rtc_mem_write64(RTC_CYCLEOFFSETL_POS,0);
|
||||
rtc_mem_write(RTC_SLEEPTOTALUS_POS,0);
|
||||
rtc_mem_write(RTC_SLEEPTOTALCYCLES_POS,0);
|
||||
rtc_mem_write(RTC_TODOFFSETUS_POS,0);
|
||||
rtc_mem_write(RTC_LASTTODUS_POS,0);
|
||||
rtc_mem_write(RTC_SOURCECYCLEUNITS_POS,0);
|
||||
rtc_mem_write(RTC_LASTSOURCEVAL_POS,0);
|
||||
rtc_cycleoffset = 0;
|
||||
rtc_sleeptotalus = 0;
|
||||
rtc_sleeptotalcycles = 0;
|
||||
rtc_todoffsetus = 0;
|
||||
rtc_lasttodus = 0;
|
||||
rtc_sourcecycleunits = 0;
|
||||
rtc_lastsourceval = 0;
|
||||
rtc_usatlastrate = 0;
|
||||
rtc_rateadjustedus = 0;
|
||||
rtc_usrate = 0;
|
||||
|
||||
if (clear_cali)
|
||||
rtc_mem_write(RTC_CALIBRATION_POS,0);
|
||||
rtc_calibration = 0;
|
||||
|
||||
bbram_save();
|
||||
}
|
||||
|
||||
static inline bool rtc_time_have_time(void)
|
||||
{
|
||||
return (rtc_time_check_magic() && rtc_mem_read64(RTC_CYCLEOFFSETL_POS)!=0);
|
||||
return (rtc_time_check_magic() && rtc_cycleoffset!=0);
|
||||
}
|
||||
|
||||
|
||||
static inline void rtc_time_select_frc2_source()
|
||||
static void rtc_time_select_frc2_source()
|
||||
{
|
||||
// FRC2 always runs at 1/256th of the default 80MHz clock, even if the actual clock is different
|
||||
uint32_t new_multiplier=(256*UNITCYCLE_MHZ+CPU_DEFAULT_MHZ/2)/CPU_DEFAULT_MHZ;
|
||||
|
@ -515,14 +626,14 @@ static inline void rtc_time_select_frc2_source()
|
|||
if (rtc_time_have_time())
|
||||
{
|
||||
uint64_t offset=(uint64_t)after*new_multiplier;
|
||||
rtc_mem_write64(RTC_CYCLEOFFSETL_POS,now-offset);
|
||||
rtc_mem_write(RTC_LASTSOURCEVAL_POS,after);
|
||||
rtc_cycleoffset = now-offset;
|
||||
rtc_lastsourceval = after;
|
||||
}
|
||||
rtc_mem_write(RTC_SOURCECYCLEUNITS_POS,new_multiplier);
|
||||
rtc_mem_write(RTC_TIME_MAGIC_POS,RTC_TIME_MAGIC_FRC2);
|
||||
rtc_sourcecycleunits = new_multiplier;
|
||||
rtc_time_set_magic(RTC_TIME_MAGIC_FRC2);
|
||||
}
|
||||
|
||||
static inline void rtc_time_select_ccount_source(uint32_t mhz, bool first)
|
||||
static void rtc_time_select_ccount_source(uint32_t mhz, bool first)
|
||||
{
|
||||
uint32_t new_multiplier=(UNITCYCLE_MHZ+mhz/2)/mhz;
|
||||
|
||||
|
@ -532,9 +643,9 @@ static inline void rtc_time_select_ccount_source(uint32_t mhz, bool first)
|
|||
|
||||
if (first)
|
||||
{ // The ccounter has been running at this rate since startup, and the offset is set accordingly
|
||||
rtc_mem_write(RTC_LASTSOURCEVAL_POS,0);
|
||||
rtc_mem_write(RTC_SOURCECYCLEUNITS_POS,new_multiplier);
|
||||
rtc_mem_write(RTC_TIME_MAGIC_POS,RTC_TIME_MAGIC_CCOUNT);
|
||||
rtc_lastsourceval = 0;
|
||||
rtc_sourcecycleunits = new_multiplier;
|
||||
rtc_time_set_magic(RTC_TIME_MAGIC_CCOUNT);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -552,11 +663,11 @@ static inline void rtc_time_select_ccount_source(uint32_t mhz, bool first)
|
|||
if (rtc_time_have_time())
|
||||
{
|
||||
uint64_t offset=(uint64_t)after*new_multiplier;
|
||||
rtc_mem_write64(RTC_CYCLEOFFSETL_POS,now-offset);
|
||||
rtc_mem_write(RTC_LASTSOURCEVAL_POS,after);
|
||||
rtc_cycleoffset = now-offset;
|
||||
rtc_lastsourceval = after;
|
||||
}
|
||||
rtc_mem_write(RTC_SOURCECYCLEUNITS_POS,new_multiplier);
|
||||
rtc_mem_write(RTC_TIME_MAGIC_POS,RTC_TIME_MAGIC_CCOUNT);
|
||||
rtc_sourcecycleunits = new_multiplier;
|
||||
rtc_time_set_magic(RTC_TIME_MAGIC_CCOUNT);
|
||||
}
|
||||
|
||||
|
||||
|
@ -573,12 +684,9 @@ static inline void rtc_time_switch_to_system_clock(void)
|
|||
rtc_time_select_frc2_source();
|
||||
}
|
||||
|
||||
static inline void rtc_time_tmrfn(void* arg)
|
||||
{
|
||||
rtc_time_source_offset();
|
||||
}
|
||||
static inline void rtc_time_tmrfn(void* arg);
|
||||
|
||||
static inline void rtc_time_install_timer(void)
|
||||
static void rtc_time_install_timer(void)
|
||||
{
|
||||
static ETSTimer tmr;
|
||||
|
||||
|
@ -618,8 +726,10 @@ static inline void rtc_time_install_wrap_handler(void)
|
|||
|
||||
// This switches from MAGIC_SLEEP to MAGIC_CCOUNT, with ccount running at bootup frequency (i.e. 52MHz).
|
||||
// To be called as early as possible, potententially as the first thing in an overridden entry point.
|
||||
static inline void rtc_time_register_bootup(void)
|
||||
static void rtc_time_register_bootup(void)
|
||||
{
|
||||
RTC_DBG_NOT_ENABLED();
|
||||
bbram_load();
|
||||
uint32_t reset_reason=rtc_get_reset_reason();
|
||||
#ifndef BOOTLOADER_CODE
|
||||
static const bool erase_calibration=true;
|
||||
|
@ -634,6 +744,9 @@ static inline void rtc_time_register_bootup(void)
|
|||
if (reset_reason!=2) // This was *not* a proper wakeup from a deep sleep. All our time keeping is f*cked!
|
||||
rtc_time_reset(erase_calibration); // Possibly keep the calibration, it should still be good
|
||||
rtc_time_select_ccount_source(CPU_BOOTUP_MHZ,true);
|
||||
rtc_rateadjustedus = rtc_usatlastrate = rtc_time_get_now_us_adjusted();
|
||||
rtc_todoffsetus = 0;
|
||||
RTC_DBG_ENABLED();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -642,6 +755,7 @@ static inline void rtc_time_register_bootup(void)
|
|||
// We did not go to sleep properly. All our time keeping is f*cked!
|
||||
rtc_time_reset(erase_calibration); // Possibly keep the calibration, it should still be good
|
||||
}
|
||||
RTC_DBG_ENABLED();
|
||||
}
|
||||
|
||||
// Call this from the nodemcu entry point, i.e. just before we switch from 52MHz to 80MHz
|
||||
|
@ -664,12 +778,44 @@ static inline void rtc_time_prepare(void)
|
|||
rtc_time_select_frc2_source();
|
||||
}
|
||||
|
||||
static uint64_t rtc_time_adjust_us_by_rate(uint64_t us, int force) {
|
||||
uint64_t usoff = us - rtc_usatlastrate;
|
||||
uint64_t usadj = (usoff * ((1ull << 32) + (int) rtc_usrate)) >> 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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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`
|
||||
|
|
Loading…
Reference in New Issue