]> granicus.if.org Git - esp-idf/commitdiff
esp32: make time monotonic across resets
authorIvan Grokhotkov <ivan@espressif.com>
Mon, 12 Jun 2017 11:51:17 +0000 (19:51 +0800)
committerIvan Grokhotkov <ivan@espressif.com>
Fri, 16 Jun 2017 04:06:04 +0000 (12:06 +0800)
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
components/esp32/include/esp_clk.h
components/esp32/include/rom/rtc.h
components/newlib/time.c

index ce7b580eb76cc9cabda507474fede712c2967278..ab589fcdcfa65573540dd2f53f685ba868a3b8cd 100644 (file)
 // limitations under the License.
 
 #include <stdint.h>
+#include <sys/cdefs.h>
+#include <sys/time.h>
 #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);
 }
index 0cedf0373e6350b4bc31380524e6ebd29f4dcfec..7b9c64c69280d4f56051ca52961f10f767031b72 100644 (file)
@@ -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);
+
index 9ea3126a9cafdf29f17957d99f97433a7193e794..3161fb2748442146e0d4c5bfb87ef89a3062371a 100644 (file)
@@ -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
index 51d0894da552b072d9431b968d22384c91fdf8f5..8764c40766a5177180d832f6076c12da8e7c5d97 100644 (file)
@@ -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 )