فهرست منبع

esp_timer: add support for frequency scaling

Ivan Grokhotkov 8 سال پیش
والد
کامیت
eb0c34e5c9
3فایلهای تغییر یافته به همراه80 افزوده شده و 0 حذف شده
  1. 13 0
      components/esp32/Kconfig
  2. 58 0
      components/esp32/esp_timer_esp32.c
  3. 9 0
      components/esp32/esp_timer_impl.h

+ 13 - 0
components/esp32/Kconfig

@@ -970,6 +970,19 @@ config PM_DFS_INIT_AUTO
 		If disabled, DFS will not be active until the application
 		configures it using esp_pm_configure function.
 
+config PM_USE_RTC_TIMER_REF
+	bool "Use RTC timer to prevent time drift (EXPERIMENTAL)"
+	depends on PM_ENABLE && (ESP32_TIME_SYSCALL_USE_RTC || ESP32_TIME_SYSCALL_USE_RTC_FRC1)
+	default n
+	help
+		When APB clock frequency changes, high-resolution timer (esp_timer)
+		scale and base value need to be adjusted. Each adjustment may cause
+		small error, and over time such small errors may cause time drift.
+		If this option is enabled, RTC timer will be used as a reference to
+		compensate for the drift.
+		It is recommended that this option is only used if 32k XTAL is selected
+		as RTC clock source.
+
 config PM_PROFILING
 	bool "Enable profiling counters for PM locks"
 	depends on PM_ENABLE

+ 58 - 0
components/esp32/esp_timer_esp32.c

@@ -19,6 +19,7 @@
 #include "esp_attr.h"
 #include "esp_intr_alloc.h"
 #include "esp_log.h"
+#include "esp_clk.h"
 #include "esp_timer_impl.h"
 #include "soc/frc_timer_reg.h"
 #include "soc/rtc.h"
@@ -112,6 +113,14 @@ static uint32_t s_timer_us_per_overflow;
 // will not increment s_time_base_us if this flag is set.
 static bool s_mask_overflow;
 
+#ifdef CONFIG_PM_DFS_USE_RTC_TIMER_REF
+// If DFS is enabled, upon the first frequency change this value is set to the
+// difference between esp_timer value and RTC timer value. On every subsequent
+// frequency change, s_time_base_us is adjusted to maintain the same difference
+// between esp_timer and RTC timer. (All mentioned values are in microseconds.)
+static uint64_t s_rtc_time_diff = 0;
+#endif
+
 // Spinlock used to protect access to static variables above and to the hardware
 // registers.
 portMUX_TYPE s_time_update_lock = portMUX_INITIALIZER_UNLOCKED;
@@ -208,6 +217,55 @@ static void IRAM_ATTR timer_alarm_isr(void *arg)
     (*s_alarm_handler)(arg);
 }
 
+void IRAM_ATTR esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us)
+{
+    portENTER_CRITICAL(&s_time_update_lock);
+    /* Bail out if the timer is not initialized yet */
+    if (s_timer_interrupt_handle == NULL) {
+        portEXIT_CRITICAL(&s_time_update_lock);
+        return;
+    }
+
+    uint32_t new_ticks_per_us = apb_ticks_per_us / TIMER_DIV;
+    uint32_t alarm = REG_READ(FRC_TIMER_ALARM_REG(1));
+    uint32_t count = REG_READ(FRC_TIMER_COUNT_REG(1));
+    uint64_t ticks_to_alarm = alarm - count;
+    uint64_t new_ticks = (ticks_to_alarm * new_ticks_per_us) / s_timer_ticks_per_us;
+    uint32_t new_alarm_val;
+    if (alarm > count && new_ticks <= FRC_TIMER_LOAD_VALUE(1)) {
+        new_alarm_val = new_ticks;
+    } else {
+        new_alarm_val = ALARM_OVERFLOW_VAL;
+        if (alarm != ALARM_OVERFLOW_VAL) {
+            s_mask_overflow = true;
+        }
+    }
+    REG_WRITE(FRC_TIMER_ALARM_REG(1), new_alarm_val);
+    REG_WRITE(FRC_TIMER_LOAD_REG(1), 0);
+
+    s_time_base_us += count / s_timer_ticks_per_us;
+
+#ifdef CONFIG_PM_DFS_USE_RTC_TIMER_REF
+    // Due to the extra time required to read RTC time, don't attempt this
+    // adjustment when switching to a higher frequency (which usually
+    // happens in an interrupt).
+    if (new_ticks_per_us < s_timer_ticks_per_us) {
+        uint64_t rtc_time = esp_clk_rtc_time();
+        uint64_t new_rtc_time_diff = s_time_base_us - rtc_time;
+        if (s_rtc_time_diff != 0) {
+            uint64_t correction = new_rtc_time_diff - s_rtc_time_diff;
+            s_time_base_us -= correction;
+        } else {
+            s_rtc_time_diff = new_rtc_time_diff;
+        }
+    }
+#endif // CONFIG_PM_DFS_USE_RTC_TIMER_REF
+
+    s_timer_ticks_per_us = new_ticks_per_us;
+    s_timer_us_per_overflow = FRC_TIMER_LOAD_VALUE(1) / new_ticks_per_us;
+
+    portEXIT_CRITICAL(&s_time_update_lock);
+}
 
 esp_err_t esp_timer_impl_init(intr_handler_t alarm_handler)
 {

+ 9 - 0
components/esp32/esp_timer_impl.h

@@ -51,6 +51,15 @@ void esp_timer_impl_deinit();
  */
 void esp_timer_impl_set_alarm(uint64_t timestamp);
 
+/**
+ * @brief Notify esp_timer implementation that APB frequency has changed
+ *
+ * Called by the frequency switching code.
+ *
+ * @param apb_ticks_per_us new number of APB clock ticks per microsecond
+ */
+void esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us);
+
 /**
  * @brief Get time, in microseconds, since esp_timer_impl_init was called
  * @return timestamp in microseconds