From cc8af682444469cf15bdb988ea092c11dc213b14 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 19 May 2017 11:37:16 +0800 Subject: [PATCH 1/2] syscalls: fix wraparound of RTC time This change removes the erroneous cast to uint32_t (which caused time to wrap around after 1 hour) and splits the multiplication into two terms to remove the wraparound after 13 days. Ref. https://esp32.com/viewtopic.php?f=13&t=1908 --- components/newlib/time.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/components/newlib/time.c b/components/newlib/time.c index d763f8390..51d0894da 100644 --- a/components/newlib/time.c +++ b/components/newlib/time.c @@ -46,8 +46,23 @@ #ifdef WITH_RTC static uint64_t get_rtc_time_us() { - uint64_t ticks = rtc_time_get(); - return (uint32_t) ((ticks * esp_clk_slowclk_cal_get()) >> RTC_CLK_CAL_FRACT); + const uint64_t ticks = rtc_time_get(); + const uint32_t cal = esp_clk_slowclk_cal_get(); + /* RTC counter result is up to 2^48, calibration factor is up to 2^24, + * for a 32kHz clock. We need to calculate (assuming no overflow): + * (ticks * cal) >> RTC_CLK_CAL_FRACT + * + * An overflow in the (ticks * cal) multiplication would cause time to + * wrap around after approximately 13 days, which is probably not enough + * for some applications. + * Therefore multiplication is split into two terms, for the lower 32-bit + * and the upper 16-bit parts of "ticks", i.e.: + * ((ticks_low + 2^32 * ticks_high) * cal) >> RTC_CLK_CAL_FRACT + */ + const uint64_t ticks_low = ticks & UINT32_MAX; + const uint64_t ticks_high = ticks >> 32; + return ((ticks_low * cal) >> RTC_CLK_CAL_FRACT) + + ((ticks_high * cal) << (32 - RTC_CLK_CAL_FRACT)); } #endif // WITH_RTC From 8ccb2a499064bcd1c651265c7d9d41a60db115c6 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 12 Jun 2017 19:51:17 +0800 Subject: [PATCH 2/2] esp32: make time monotonic across resets Small changes to clock calibration value will cause increasing errors the longer the device runs. Consider the case of deep sleep, assuming that RTC counter is used for timekeeping: - before sleep: time_before = rtc_counter * calibration_val - after sleep: time_after = (rtc_counter + sleep_count) * (calibration_val + epsilon) where 'epsilon' is a small estimation error of 'calibration_val'. The apparent sleep duration thus will be: time_after - time_before = sleep_count * (calibration_val + epsilon) + rtc_counter * epsilon Second term on the right hand side is the error in time difference estimation, it is proportional to the total system runtime (rtc_counter). To avoid this issue, this change makes RTC_SLOW_CLK calibration value persistent across restarts. This allows the calibration value update to be preformed, while keeping time after update same as before the update. --- components/esp32/clk.c | 23 +++++++++-------------- components/esp32/include/esp_clk.h | 14 +++++++++++++- components/esp32/include/rom/rtc.h | 7 ++++--- components/newlib/time.c | 26 ++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 18 deletions(-) diff --git a/components/esp32/clk.c b/components/esp32/clk.c index ce7b580eb..ab589fcdc 100644 --- a/components/esp32/clk.c +++ b/components/esp32/clk.c @@ -13,11 +13,15 @@ // limitations under the License. #include +#include +#include #include "sdkconfig.h" #include "esp_attr.h" #include "esp_log.h" +#include "esp_clk.h" #include "rom/ets_sys.h" #include "rom/uart.h" +#include "rom/rtc.h" #include "soc/soc.h" #include "soc/rtc.h" #include "soc/rtc_cntl_reg.h" @@ -82,12 +86,6 @@ void IRAM_ATTR ets_update_cpu_frequency(uint32_t ticks_per_us) g_ticks_per_us_app = ticks_per_us; } -/* This is a cached value of RTC slow clock period; it is updated by - * the select_rtc_slow_clk function at start up. This cached value is used in - * other places, like time syscalls and deep sleep. - */ -static uint32_t s_rtc_slow_clk_cal = 0; - static void select_rtc_slow_clk(rtc_slow_freq_t slow_clk) { if (slow_clk == RTC_SLOW_FREQ_32K_XTAL) { @@ -114,19 +112,16 @@ static void select_rtc_slow_clk(rtc_slow_freq_t slow_clk) ESP_EARLY_LOGD(TAG, "32k oscillator ready, wait=%d", wait); } rtc_clk_slow_freq_set(slow_clk); + uint32_t cal_val; if (SLOW_CLK_CAL_CYCLES > 0) { /* TODO: 32k XTAL oscillator has some frequency drift at startup. * Improve calibration routine to wait until the frequency is stable. */ - s_rtc_slow_clk_cal = rtc_clk_cal(RTC_CAL_RTC_MUX, SLOW_CLK_CAL_CYCLES); + cal_val = rtc_clk_cal(RTC_CAL_RTC_MUX, SLOW_CLK_CAL_CYCLES); } else { const uint64_t cal_dividend = (1ULL << RTC_CLK_CAL_FRACT) * 1000000ULL; - s_rtc_slow_clk_cal = (uint32_t) (cal_dividend / rtc_clk_slow_freq_get_hz()); + cal_val = (uint32_t) (cal_dividend / rtc_clk_slow_freq_get_hz()); } - ESP_EARLY_LOGD(TAG, "RTC_SLOW_CLK calibration value: %d", s_rtc_slow_clk_cal); -} - -uint32_t esp_clk_slowclk_cal_get() -{ - return s_rtc_slow_clk_cal; + ESP_EARLY_LOGD(TAG, "RTC_SLOW_CLK calibration value: %d", cal_val); + esp_clk_slowclk_cal_set(cal_val); } diff --git a/components/esp32/include/esp_clk.h b/components/esp32/include/esp_clk.h index 0cedf0373..7b9c64c69 100644 --- a/components/esp32/include/esp_clk.h +++ b/components/esp32/include/esp_clk.h @@ -33,7 +33,7 @@ void esp_clk_init(void); /** - * @brief Get the cached calibration value of RTC slow clock + * @brief Get the calibration value of RTC slow clock * * The value is in the same format as returned by rtc_clk_cal (microseconds, * in Q13.19 fixed-point format). @@ -42,3 +42,15 @@ void esp_clk_init(void); */ uint32_t esp_clk_slowclk_cal_get(); + +/** + * @brief Update the calibration value of RTC slow clock + * + * The value has to be in the same format as returned by rtc_clk_cal (microseconds, + * in Q13.19 fixed-point format). + * This value is used by timekeeping functions (such as gettimeofday) to + * calculate current time based on RTC counter value. + * @param value calibration value obtained using rtc_clk_cal + */ +void esp_clk_slowclk_cal_set(uint32_t value); + diff --git a/components/esp32/include/rom/rtc.h b/components/esp32/include/rom/rtc.h index 9ea3126a9..3161fb274 100644 --- a/components/esp32/include/rom/rtc.h +++ b/components/esp32/include/rom/rtc.h @@ -50,9 +50,9 @@ extern "C" { * 0x3ff80000(0x400c0000) Fast 8192 deep sleep entry code * ************************************************************************************* - * Rtc store registers usage - * RTC_CNTL_STORE0_REG - * RTC_CNTL_STORE1_REG + * RTC store registers usage + * RTC_CNTL_STORE0_REG Reserved + * RTC_CNTL_STORE1_REG RTC_SLOW_CLK calibration value * RTC_CNTL_STORE2_REG Boot time, low word * RTC_CNTL_STORE3_REG Boot time, high word * RTC_CNTL_STORE4_REG External XTAL frequency @@ -62,6 +62,7 @@ extern "C" { ************************************************************************************* */ +#define RTC_SLOW_CLK_CAL_REG RTC_CNTL_STORE1_REG #define RTC_BOOT_TIME_LOW_REG RTC_CNTL_STORE2_REG #define RTC_BOOT_TIME_HIGH_REG RTC_CNTL_STORE3_REG #define RTC_XTAL_FREQ_REG RTC_CNTL_STORE4_REG diff --git a/components/newlib/time.c b/components/newlib/time.c index 51d0894da..8764c4076 100644 --- a/components/newlib/time.c +++ b/components/newlib/time.c @@ -130,6 +130,32 @@ static uint64_t get_boot_time() } #endif //defined(WITH_RTC) || defined(WITH_FRC1) + +void esp_clk_slowclk_cal_set(uint32_t new_cal) +{ +#if defined(WITH_RTC) + /* To force monotonic time values even when clock calibration value changes, + * we adjust boot time, given current time and the new calibration value: + * T = boot_time_old + cur_cal * ticks / 2^19 + * T = boot_time_adj + new_cal * ticks / 2^19 + * which results in: + * boot_time_adj = boot_time_old + ticks * (cur_cal - new_cal) / 2^19 + */ + const int64_t ticks = (int64_t) rtc_time_get(); + const uint32_t cur_cal = REG_READ(RTC_SLOW_CLK_CAL_REG); + int32_t cal_diff = (int32_t) (cur_cal - new_cal); + int64_t boot_time_diff = ticks * cal_diff / (1LL << RTC_CLK_CAL_FRACT); + uint64_t boot_time_adj = get_boot_time() + boot_time_diff; + set_boot_time(boot_time_adj); +#endif // WITH_RTC + REG_WRITE(RTC_SLOW_CLK_CAL_REG, new_cal); +} + +uint32_t esp_clk_slowclk_cal_get() +{ + return REG_READ(RTC_SLOW_CLK_CAL_REG); +} + void esp_setup_time_syscalls() { #if defined( WITH_FRC1 )