esp_timer: add support for frequency scaling

This commit is contained in:
Ivan Grokhotkov 2017-10-09 15:24:51 +08:00
parent 3f818f4862
commit eb0c34e5c9
3 changed files with 80 additions and 0 deletions

View file

@ -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

View file

@ -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)
{

View file

@ -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