esp_timer: Add ESP_TIMER_ISR dispatch method

Using own spinlock for each list (TASK and ISR disp method)
Reduced the dependency of ISR on the TASK dispatch method
This commit is contained in:
KonstantinKondrashov
2020-02-10 21:45:09 +08:00
parent bcbee89030
commit 63d4911cdb
11 changed files with 551 additions and 159 deletions

View File

@@ -56,7 +56,7 @@
#define EVENT_ID_DELETE_TIMER 0xF0DE1E1E
typedef enum {
FL_DISPATCH_METHOD = (1 << 0), //!< 0=Callback is called from timer task, 1=Callback is called from timer ISR
FL_ISR_DISPATCH_METHOD = (1 << 0), //!< 0=Callback is called from timer task, 1=Callback is called from timer ISR
FL_SKIP_UNHANDLED_EVENTS = (1 << 1), //!< 0=NOT skip unhandled events for periodic timers, 1=Skip unhandled events for periodic timers
} flags_t;
@@ -80,11 +80,11 @@ struct esp_timer {
};
static inline bool is_initialized(void);
static esp_err_t timer_insert(esp_timer_handle_t timer);
static esp_err_t timer_insert(esp_timer_handle_t timer, bool without_update_alarm);
static esp_err_t timer_remove(esp_timer_handle_t timer);
static bool timer_armed(esp_timer_handle_t timer);
static void timer_list_lock(void);
static void timer_list_unlock(void);
static void timer_list_lock(esp_timer_dispatch_t timer_type);
static void timer_list_unlock(esp_timer_dispatch_t timer_type);
#if WITH_PROFILING
static void timer_insert_inactive(esp_timer_handle_t timer);
@@ -93,21 +93,29 @@ static void timer_remove_inactive(esp_timer_handle_t timer);
__attribute__((unused)) static const char* TAG = "esp_timer";
// list of currently armed timers
static LIST_HEAD(esp_timer_list, esp_timer) s_timers =
LIST_HEAD_INITIALIZER(s_timers);
// lists of currently armed timers for two dispatch methods: ISR and TASK
static LIST_HEAD(esp_timer_list, esp_timer) s_timers[ESP_TIMER_MAX] = {
[0 ... (ESP_TIMER_MAX - 1)] = LIST_HEAD_INITIALIZER(s_timers)
};
#if WITH_PROFILING
// list of unarmed timers, used only to be able to dump statistics about
// all the timers
static LIST_HEAD(esp_inactive_timer_list, esp_timer) s_inactive_timers =
LIST_HEAD_INITIALIZER(s_timers);
// lists of unarmed timers for two dispatch methods: ISR and TASK,
// used only to be able to dump statistics about all the timers
static LIST_HEAD(esp_inactive_timer_list, esp_timer) s_inactive_timers[ESP_TIMER_MAX] = {
[0 ... (ESP_TIMER_MAX - 1)] = LIST_HEAD_INITIALIZER(s_timers)
};
#endif
// task used to dispatch timer callbacks
static TaskHandle_t s_timer_task;
// lock protecting s_timers, s_inactive_timers
static portMUX_TYPE s_timer_lock = portMUX_INITIALIZER_UNLOCKED;
static portMUX_TYPE s_timer_lock[ESP_TIMER_MAX] = {
[0 ... (ESP_TIMER_MAX - 1)] = portMUX_INITIALIZER_UNLOCKED
};
#ifdef CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD
// For ISR dispatch method, a callback function of the timer may require a context switch
static volatile BaseType_t s_isr_dispatch_need_yield = pdFALSE;
#endif // CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD
esp_err_t esp_timer_create(const esp_timer_create_args_t* args,
esp_timer_handle_t* out_handle)
@@ -115,7 +123,8 @@ esp_err_t esp_timer_create(const esp_timer_create_args_t* args,
if (!is_initialized()) {
return ESP_ERR_INVALID_STATE;
}
if (args == NULL || args->callback == NULL || out_handle == NULL) {
if (args == NULL || args->callback == NULL || out_handle == NULL ||
args->dispatch_method < 0 || args->dispatch_method >= ESP_TIMER_MAX) {
return ESP_ERR_INVALID_ARG;
}
esp_timer_handle_t result = (esp_timer_handle_t) calloc(1, sizeof(*result));
@@ -124,11 +133,14 @@ esp_err_t esp_timer_create(const esp_timer_create_args_t* args,
}
result->callback = args->callback;
result->arg = args->arg;
result->flags = (args->dispatch_method ? FL_DISPATCH_METHOD : 0) |
result->flags = (args->dispatch_method ? FL_ISR_DISPATCH_METHOD : 0) |
(args->skip_unhandled_events ? FL_SKIP_UNHANDLED_EVENTS : 0);
#if WITH_PROFILING
result->name = args->name;
esp_timer_dispatch_t dispatch_method = result->flags & FL_ISR_DISPATCH_METHOD;
timer_list_lock(dispatch_method);
timer_insert_inactive(result);
timer_list_unlock(dispatch_method);
#endif
*out_handle = result;
return ESP_OK;
@@ -142,14 +154,16 @@ esp_err_t IRAM_ATTR esp_timer_start_once(esp_timer_handle_t timer, uint64_t time
if (!is_initialized() || timer_armed(timer)) {
return ESP_ERR_INVALID_STATE;
}
timer_list_lock();
timer->alarm = esp_timer_get_time() + timeout_us;
int64_t alarm = esp_timer_get_time() + timeout_us;
esp_timer_dispatch_t dispatch_method = timer->flags & FL_ISR_DISPATCH_METHOD;
timer_list_lock(dispatch_method);
timer->alarm = alarm;
timer->period = 0;
#if WITH_PROFILING
timer->times_armed++;
#endif
esp_err_t err = timer_insert(timer);
timer_list_unlock();
esp_err_t err = timer_insert(timer, false);
timer_list_unlock(dispatch_method);
return err;
}
@@ -161,16 +175,18 @@ esp_err_t IRAM_ATTR esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t
if (!is_initialized() || timer_armed(timer)) {
return ESP_ERR_INVALID_STATE;
}
timer_list_lock();
period_us = MAX(period_us, esp_timer_impl_get_min_period_us());
timer->alarm = esp_timer_get_time() + period_us;
int64_t alarm = esp_timer_get_time() + period_us;
esp_timer_dispatch_t dispatch_method = timer->flags & FL_ISR_DISPATCH_METHOD;
timer_list_lock(dispatch_method);
timer->alarm = alarm;
timer->period = period_us;
#if WITH_PROFILING
timer->times_armed++;
timer->times_skipped = 0;
#endif
esp_err_t err = timer_insert(timer);
timer_list_unlock();
esp_err_t err = timer_insert(timer, false);
timer_list_unlock(dispatch_method);
return err;
}
@@ -193,25 +209,32 @@ esp_err_t esp_timer_delete(esp_timer_handle_t timer)
if (timer_armed(timer)) {
return ESP_ERR_INVALID_STATE;
}
timer_list_lock();
// A case for the timer with ESP_TIMER_ISR:
// This ISR timer was removed from the ISR list in esp_timer_stop() or in timer_process_alarm() -> LIST_REMOVE(it, list_entry)
// and here this timer will be added to another the TASK list, see below.
// We do this because we want to free memory of the timer in a task context instead of an isr context.
int64_t alarm = esp_timer_get_time();
timer_list_lock(ESP_TIMER_TASK);
timer->flags &= ~FL_ISR_DISPATCH_METHOD;
timer->event_id = EVENT_ID_DELETE_TIMER;
timer->alarm = esp_timer_get_time();
timer->alarm = alarm;
timer->period = 0;
timer_insert(timer);
timer_list_unlock();
timer_insert(timer, false);
timer_list_unlock(ESP_TIMER_TASK);
return ESP_OK;
}
static IRAM_ATTR esp_err_t timer_insert(esp_timer_handle_t timer)
static IRAM_ATTR esp_err_t timer_insert(esp_timer_handle_t timer, bool without_update_alarm)
{
#if WITH_PROFILING
timer_remove_inactive(timer);
#endif
esp_timer_handle_t it, last = NULL;
if (LIST_FIRST(&s_timers) == NULL) {
LIST_INSERT_HEAD(&s_timers, timer, list_entry);
esp_timer_dispatch_t dispatch_method = timer->flags & FL_ISR_DISPATCH_METHOD;
if (LIST_FIRST(&s_timers[dispatch_method]) == NULL) {
LIST_INSERT_HEAD(&s_timers[dispatch_method], timer, list_entry);
} else {
LIST_FOREACH(it, &s_timers, list_entry) {
LIST_FOREACH(it, &s_timers[dispatch_method], list_entry) {
if (timer->alarm < it->alarm) {
LIST_INSERT_BEFORE(it, timer, list_entry);
break;
@@ -223,22 +246,32 @@ static IRAM_ATTR esp_err_t timer_insert(esp_timer_handle_t timer)
LIST_INSERT_AFTER(last, timer, list_entry);
}
}
if (timer == LIST_FIRST(&s_timers)) {
esp_timer_impl_set_alarm(timer->alarm);
if (without_update_alarm == false && timer == LIST_FIRST(&s_timers[dispatch_method])) {
esp_timer_impl_set_alarm_id(timer->alarm, dispatch_method);
}
return ESP_OK;
}
static IRAM_ATTR esp_err_t timer_remove(esp_timer_handle_t timer)
{
timer_list_lock();
esp_timer_dispatch_t dispatch_method = timer->flags & FL_ISR_DISPATCH_METHOD;
timer_list_lock(dispatch_method);
esp_timer_handle_t first_timer = LIST_FIRST(&s_timers[dispatch_method]);
LIST_REMOVE(timer, list_entry);
timer->alarm = 0;
timer->period = 0;
if (timer == first_timer) { // if this timer was the first in the list.
uint64_t next_timestamp = UINT64_MAX;
first_timer = LIST_FIRST(&s_timers[dispatch_method]);
if (first_timer) { // if after removing the timer from the list, this list is not empty.
next_timestamp = first_timer->alarm;
}
esp_timer_impl_set_alarm_id(next_timestamp, dispatch_method);
}
#if WITH_PROFILING
timer_insert_inactive(timer);
#endif
timer_list_unlock();
timer_list_unlock(dispatch_method);
return ESP_OK;
}
@@ -249,24 +282,21 @@ static IRAM_ATTR void timer_insert_inactive(esp_timer_handle_t timer)
/* May be locked or not, depending on where this is called from.
* Lock recursively.
*/
timer_list_lock();
esp_timer_handle_t head = LIST_FIRST(&s_inactive_timers);
esp_timer_dispatch_t dispatch_method = timer->flags & FL_ISR_DISPATCH_METHOD;
esp_timer_handle_t head = LIST_FIRST(&s_inactive_timers[dispatch_method]);
if (head == NULL) {
LIST_INSERT_HEAD(&s_inactive_timers, timer, list_entry);
LIST_INSERT_HEAD(&s_inactive_timers[dispatch_method], timer, list_entry);
} else {
/* Insert as head element as this is the fastest thing to do.
* Removal is O(1) anyway.
*/
LIST_INSERT_BEFORE(head, timer, list_entry);
}
timer_list_unlock();
}
static IRAM_ATTR void timer_remove_inactive(esp_timer_handle_t timer)
{
timer_list_lock();
LIST_REMOVE(timer, list_entry);
timer_list_unlock();
}
#endif // WITH_PROFILING
@@ -276,31 +306,37 @@ static IRAM_ATTR bool timer_armed(esp_timer_handle_t timer)
return timer->alarm > 0;
}
static IRAM_ATTR void timer_list_lock(void)
static IRAM_ATTR void timer_list_lock(esp_timer_dispatch_t timer_type)
{
portENTER_CRITICAL_SAFE(&s_timer_lock);
portENTER_CRITICAL_SAFE(&s_timer_lock[timer_type]);
}
static IRAM_ATTR void timer_list_unlock(void)
static IRAM_ATTR void timer_list_unlock(esp_timer_dispatch_t timer_type)
{
portEXIT_CRITICAL_SAFE(&s_timer_lock);
portEXIT_CRITICAL_SAFE(&s_timer_lock[timer_type]);
}
static void timer_process_alarm(esp_timer_dispatch_t dispatch_method)
#ifdef CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD
static IRAM_ATTR bool timer_process_alarm(esp_timer_dispatch_t dispatch_method)
#else
static bool timer_process_alarm(esp_timer_dispatch_t dispatch_method)
#endif
{
/* unused, provision to allow running callbacks from ISR */
(void) dispatch_method;
timer_list_lock();
timer_list_lock(dispatch_method);
bool processed = false;
esp_timer_handle_t it;
while (1) {
it = LIST_FIRST(&s_timers);
it = LIST_FIRST(&s_timers[dispatch_method]);
int64_t now = esp_timer_impl_get_time();
if (it == NULL || it->alarm > now) {
break;
}
processed = true;
LIST_REMOVE(it, list_entry);
if (it->event_id == EVENT_ID_DELETE_TIMER) {
// It is handled only by ESP_TIMER_TASK (see esp_timer_delete()).
// All the ESP_TIMER_ISR timers which should be deleted are moved by esp_timer_delete() to the ESP_TIMER_TASK list.
// We want to free memory of the timer in a task context instead of an isr context.
free(it);
it = NULL;
} else {
@@ -314,7 +350,7 @@ static void timer_process_alarm(esp_timer_dispatch_t dispatch_method)
} else {
it->alarm += it->period;
}
timer_insert(it);
timer_insert(it, true);
} else {
it->alarm = 0;
#if WITH_PROFILING
@@ -326,19 +362,26 @@ static void timer_process_alarm(esp_timer_dispatch_t dispatch_method)
#endif
esp_timer_cb_t callback = it->callback;
void* arg = it->arg;
timer_list_unlock();
timer_list_unlock(dispatch_method);
(*callback)(arg);
timer_list_lock();
timer_list_lock(dispatch_method);
#if WITH_PROFILING
it->times_triggered++;
it->total_callback_run_time += esp_timer_impl_get_time() - callback_start;
#endif
}
}
} // while(1)
if (it) {
esp_timer_impl_set_alarm(it->alarm);
if (dispatch_method == ESP_TIMER_TASK || (dispatch_method != ESP_TIMER_TASK && processed == true)) {
esp_timer_impl_set_alarm_id(it->alarm, dispatch_method);
}
} else {
if (processed) {
esp_timer_impl_set_alarm_id(UINT64_MAX, dispatch_method);
}
}
timer_list_unlock();
timer_list_unlock(dispatch_method);
return processed;
}
static void timer_task(void* arg)
@@ -350,10 +393,29 @@ static void timer_task(void* arg)
}
}
#ifdef CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD
IRAM_ATTR void esp_timer_isr_dispatch_need_yield(void)
{
assert(xPortInIsrContext());
s_isr_dispatch_need_yield = pdTRUE;
}
#endif
static void IRAM_ATTR timer_alarm_handler(void* arg)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(s_timer_task, &xHigherPriorityTaskWoken);
bool isr_timers_processed = false;
#ifdef CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD
// process timers with ISR dispatch method
isr_timers_processed = timer_process_alarm(ESP_TIMER_ISR);
xHigherPriorityTaskWoken = s_isr_dispatch_need_yield;
s_isr_dispatch_need_yield = pdFALSE;
#endif
if (isr_timers_processed == false) {
vTaskNotifyGiveFromISR(s_timer_task, &xHigherPriorityTaskWoken);
}
if (xHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR();
}
@@ -407,16 +469,20 @@ esp_err_t esp_timer_deinit(void)
}
/* Check if there are any active timers */
if (!LIST_EMPTY(&s_timers)) {
return ESP_ERR_INVALID_STATE;
for (esp_timer_dispatch_t dispatch_method = ESP_TIMER_TASK; dispatch_method < ESP_TIMER_MAX; ++dispatch_method) {
if (!LIST_EMPTY(&s_timers[dispatch_method])) {
return ESP_ERR_INVALID_STATE;
}
}
/* We can only check if there are any timers which are not deleted if
* profiling is enabled.
*/
#if WITH_PROFILING
if (!LIST_EMPTY(&s_inactive_timers)) {
return ESP_ERR_INVALID_STATE;
for (esp_timer_dispatch_t dispatch_method = ESP_TIMER_TASK; dispatch_method < ESP_TIMER_MAX; ++dispatch_method) {
if (!LIST_EMPTY(&s_inactive_timers[dispatch_method])) {
return ESP_ERR_INVALID_STATE;
}
}
#endif
@@ -463,16 +529,18 @@ esp_err_t esp_timer_dump(FILE* stream)
/* First count the number of timers */
size_t timer_count = 0;
timer_list_lock();
LIST_FOREACH(it, &s_timers, list_entry) {
++timer_count;
}
for (esp_timer_dispatch_t dispatch_method = ESP_TIMER_TASK; dispatch_method < ESP_TIMER_MAX; ++dispatch_method) {
timer_list_lock(dispatch_method);
LIST_FOREACH(it, &s_timers[dispatch_method], list_entry) {
++timer_count;
}
#if WITH_PROFILING
LIST_FOREACH(it, &s_inactive_timers, list_entry) {
++timer_count;
}
LIST_FOREACH(it, &s_inactive_timers[dispatch_method], list_entry) {
++timer_count;
}
#endif
timer_list_unlock();
timer_list_unlock(dispatch_method);
}
/* Allocate the memory for this number of timers. Since we have unlocked,
* we may find that there are more timers. There's no bulletproof solution
@@ -486,20 +554,24 @@ esp_err_t esp_timer_dump(FILE* stream)
}
/* Print to the buffer */
timer_list_lock();
char* pos = print_buf;
LIST_FOREACH(it, &s_timers, list_entry) {
print_timer_info(it, &pos, &buf_size);
}
for (esp_timer_dispatch_t dispatch_method = ESP_TIMER_TASK; dispatch_method < ESP_TIMER_MAX; ++dispatch_method) {
timer_list_lock(dispatch_method);
LIST_FOREACH(it, &s_timers[dispatch_method], list_entry) {
print_timer_info(it, &pos, &buf_size);
}
#if WITH_PROFILING
LIST_FOREACH(it, &s_inactive_timers, list_entry) {
print_timer_info(it, &pos, &buf_size);
}
LIST_FOREACH(it, &s_inactive_timers[dispatch_method], list_entry) {
print_timer_info(it, &pos, &buf_size);
}
#endif
timer_list_unlock();
timer_list_unlock(dispatch_method);
}
/* Print the buffer */
fputs(print_buf, stream);
if (stream != NULL) {
fputs(print_buf, stream);
}
free(print_buf);
return ESP_OK;
@@ -508,12 +580,16 @@ esp_err_t esp_timer_dump(FILE* stream)
int64_t IRAM_ATTR esp_timer_get_next_alarm(void)
{
int64_t next_alarm = INT64_MAX;
timer_list_lock();
esp_timer_handle_t it = LIST_FIRST(&s_timers);
if (it) {
next_alarm = it->alarm;
for (esp_timer_dispatch_t dispatch_method = ESP_TIMER_TASK; dispatch_method < ESP_TIMER_MAX; ++dispatch_method) {
timer_list_lock(dispatch_method);
esp_timer_handle_t it = LIST_FIRST(&s_timers[dispatch_method]);
if (it) {
if (next_alarm > it->alarm) {
next_alarm = it->alarm;
}
}
timer_list_unlock(dispatch_method);
}
timer_list_unlock();
return next_alarm;
}

View File

@@ -214,51 +214,61 @@ int64_t IRAM_ATTR esp_timer_impl_get_time(void)
int64_t esp_timer_get_time(void) __attribute__((alias("esp_timer_impl_get_time")));
void IRAM_ATTR esp_timer_impl_set_alarm_id(uint64_t timestamp, unsigned alarm_id)
{
static uint64_t timestamp_id[2] = { UINT64_MAX, UINT64_MAX };
portENTER_CRITICAL_SAFE(&s_time_update_lock);
timestamp_id[alarm_id] = timestamp;
timestamp = MIN(timestamp_id[0], timestamp_id[1]);
if (timestamp != UINT64_MAX) {
// Use calculated alarm value if it is less than ALARM_OVERFLOW_VAL.
// Note that if by the time we update ALARM_REG, COUNT_REG value is higher,
// interrupt will not happen for another ALARM_OVERFLOW_VAL timer ticks,
// so need to check if alarm value is too close in the future (e.g. <2 us away).
int32_t offset = s_timer_ticks_per_us * 2;
do {
// Adjust current time if overflow has happened
if (timer_overflow_happened() ||
((REG_READ(FRC_TIMER_COUNT_REG(1)) > ALARM_OVERFLOW_VAL) &&
((REG_READ(FRC_TIMER_CTRL_REG(1)) & FRC_TIMER_INT_STATUS) == 0))) {
// 1. timer_overflow_happened() checks overflow with the interrupt flag.
// 2. During several loops, the counter can be higher than the alarm and even step over ALARM_OVERFLOW_VAL boundary (the interrupt flag is not set).
timer_count_reload();
s_time_base_us += s_timer_us_per_overflow;
}
s_mask_overflow = false;
int64_t cur_count = REG_READ(FRC_TIMER_COUNT_REG(1));
// Alarm time relative to the moment when counter was 0
int64_t time_after_timebase_us = (int64_t)timestamp - s_time_base_us;
// Calculate desired timer compare value (may exceed 2^32-1)
int64_t compare_val = time_after_timebase_us * s_timer_ticks_per_us;
compare_val = MAX(compare_val, cur_count + offset);
uint32_t alarm_reg_val = ALARM_OVERFLOW_VAL;
if (compare_val < ALARM_OVERFLOW_VAL) {
alarm_reg_val = (uint32_t) compare_val;
}
REG_WRITE(FRC_TIMER_ALARM_REG(1), alarm_reg_val);
int64_t delta = (int64_t)alarm_reg_val - (int64_t)REG_READ(FRC_TIMER_COUNT_REG(1));
if (delta <= 0) {
/*
When the timestamp is a bit less than the current counter then the alarm = current_counter + offset.
But due to CPU_freq in some case can be equal APB_freq the offset time can not exceed the overhead
(the alarm will be less than the counter) and it leads to the infinity loop.
To exclude this behavior to the offset was added the delta to have the opportunity to go through it.
*/
offset += abs((int)delta) + s_timer_ticks_per_us * 2;
} else {
break;
}
} while (1);
}
portEXIT_CRITICAL_SAFE(&s_time_update_lock);
}
void IRAM_ATTR esp_timer_impl_set_alarm(uint64_t timestamp)
{
portENTER_CRITICAL_SAFE(&s_time_update_lock);
// Use calculated alarm value if it is less than ALARM_OVERFLOW_VAL.
// Note that if by the time we update ALARM_REG, COUNT_REG value is higher,
// interrupt will not happen for another ALARM_OVERFLOW_VAL timer ticks,
// so need to check if alarm value is too close in the future (e.g. <2 us away).
int32_t offset = s_timer_ticks_per_us * 2;
do {
// Adjust current time if overflow has happened
if (timer_overflow_happened() ||
((REG_READ(FRC_TIMER_COUNT_REG(1)) > ALARM_OVERFLOW_VAL) &&
((REG_READ(FRC_TIMER_CTRL_REG(1)) & FRC_TIMER_INT_STATUS) == 0))) {
// 1. timer_overflow_happened() checks overflow with the interrupt flag.
// 2. During several loops, the counter can be higher than the alarm and even step over ALARM_OVERFLOW_VAL boundary (the interrupt flag is not set).
timer_count_reload();
s_time_base_us += s_timer_us_per_overflow;
}
s_mask_overflow = false;
int64_t cur_count = REG_READ(FRC_TIMER_COUNT_REG(1));
// Alarm time relative to the moment when counter was 0
int64_t time_after_timebase_us = (int64_t)timestamp - s_time_base_us;
// Calculate desired timer compare value (may exceed 2^32-1)
int64_t compare_val = time_after_timebase_us * s_timer_ticks_per_us;
compare_val = MAX(compare_val, cur_count + offset);
uint32_t alarm_reg_val = ALARM_OVERFLOW_VAL;
if (compare_val < ALARM_OVERFLOW_VAL) {
alarm_reg_val = (uint32_t) compare_val;
}
REG_WRITE(FRC_TIMER_ALARM_REG(1), alarm_reg_val);
int64_t delta = (int64_t)alarm_reg_val - (int64_t)REG_READ(FRC_TIMER_COUNT_REG(1));
if (delta <= 0) {
/*
When the timestamp is a bit less than the current counter then the alarm = current_counter + offset.
But due to CPU_freq in some case can be equal APB_freq the offset time can not exceed the overhead
(the alarm will be less than the counter) and it leads to the infinity loop.
To exclude this behavior to the offset was added the delta to have the opportunity to go through it.
*/
offset += abs((int)delta) + s_timer_ticks_per_us * 2;
} else {
break;
}
} while (1);
portEXIT_CRITICAL_SAFE(&s_time_update_lock);
esp_timer_impl_set_alarm_id(timestamp, 0);
}
static void IRAM_ATTR timer_alarm_isr(void *arg)

View File

@@ -154,29 +154,39 @@ int64_t IRAM_ATTR esp_timer_impl_get_time(void)
int64_t esp_timer_get_time(void) __attribute__((alias("esp_timer_impl_get_time")));
void IRAM_ATTR esp_timer_impl_set_alarm_id(uint64_t timestamp, unsigned alarm_id)
{
static uint64_t timestamp_id[2] = { UINT64_MAX, UINT64_MAX };
portENTER_CRITICAL_SAFE(&s_time_update_lock);
timestamp_id[alarm_id] = timestamp;
timestamp = MIN(timestamp_id[0], timestamp_id[1]);
if (timestamp != UINT64_MAX) {
int64_t offset = TICKS_PER_US * 2;
uint64_t now_time = esp_timer_impl_get_counter_reg();
timer_64b_reg_t alarm = { .val = MAX(timestamp * TICKS_PER_US, now_time + offset) };
do {
REG_CLR_BIT(CONFIG_REG, TIMG_LACT_ALARM_EN);
REG_WRITE(ALARM_LO_REG, alarm.lo);
REG_WRITE(ALARM_HI_REG, alarm.hi);
REG_SET_BIT(CONFIG_REG, TIMG_LACT_ALARM_EN);
now_time = esp_timer_impl_get_counter_reg();
int64_t delta = (int64_t)alarm.val - (int64_t)now_time;
if (delta <= 0 && REG_GET_FIELD(INT_ST_REG, TIMG_LACT_INT_ST) == 0) {
// new alarm is less than the counter and the interrupt flag is not set
offset += abs((int)delta) + TICKS_PER_US * 2;
alarm.val = now_time + offset;
} else {
// finish if either (alarm > counter) or the interrupt flag is already set.
break;
}
} while(1);
}
portEXIT_CRITICAL_SAFE(&s_time_update_lock);
}
void IRAM_ATTR esp_timer_impl_set_alarm(uint64_t timestamp)
{
portENTER_CRITICAL_SAFE(&s_time_update_lock);
int64_t offset = TICKS_PER_US * 2;
uint64_t now_time = esp_timer_impl_get_counter_reg();
timer_64b_reg_t alarm = { .val = MAX(timestamp * TICKS_PER_US, now_time + offset) };
do {
REG_CLR_BIT(CONFIG_REG, TIMG_LACT_ALARM_EN);
REG_WRITE(ALARM_LO_REG, alarm.lo);
REG_WRITE(ALARM_HI_REG, alarm.hi);
REG_SET_BIT(CONFIG_REG, TIMG_LACT_ALARM_EN);
now_time = esp_timer_impl_get_counter_reg();
int64_t delta = (int64_t)alarm.val - (int64_t)now_time;
if (delta <= 0 && REG_GET_FIELD(INT_ST_REG, TIMG_LACT_INT_ST) == 0) {
// new alarm is less than the counter and the interrupt flag is not set
offset += abs((int)delta) + TICKS_PER_US * 2;
alarm.val = now_time + offset;
} else {
// finish if either (alarm > counter) or the interrupt flag is already set.
break;
}
} while(1);
portEXIT_CRITICAL_SAFE(&s_time_update_lock);
esp_timer_impl_set_alarm_id(timestamp, 0);
}
static void IRAM_ATTR timer_alarm_isr(void *arg)

View File

@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "sys/param.h"
#include "esp_timer_impl.h"
#include "esp_err.h"
#include "esp_timer.h"
@@ -73,11 +74,21 @@ int64_t IRAM_ATTR esp_timer_impl_get_time(void)
int64_t esp_timer_get_time(void) __attribute__((alias("esp_timer_impl_get_time")));
void IRAM_ATTR esp_timer_impl_set_alarm_id(uint64_t timestamp, unsigned alarm_id)
{
static uint64_t timestamp_id[2] = { UINT64_MAX, UINT64_MAX };
portENTER_CRITICAL_SAFE(&s_time_update_lock);
timestamp_id[alarm_id] = timestamp;
timestamp = MIN(timestamp_id[0], timestamp_id[1]);
if (timestamp != UINT64_MAX) {
systimer_hal_set_alarm_target(SYSTIMER_ALARM_2, timestamp);
}
portEXIT_CRITICAL_SAFE(&s_time_update_lock);
}
void IRAM_ATTR esp_timer_impl_set_alarm(uint64_t timestamp)
{
portENTER_CRITICAL_SAFE(&s_time_update_lock);
systimer_hal_set_alarm_target(SYSTIMER_ALARM_2, timestamp);
portEXIT_CRITICAL_SAFE(&s_time_update_lock);
esp_timer_impl_set_alarm_id(timestamp, 0);
}
static void IRAM_ATTR timer_alarm_isr(void *arg)