From 1af63843490f273e1702ebca48e50f975853e7c5 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 30 Aug 2017 08:43:02 +0800 Subject: [PATCH] esp_timer: lock-free implementation of esp_timer_get_time The implementation of esp_timer_get_time used a critical section, which resulted in a call time of ~1.8us. To make esp_timer_get_time more useable as a high-resolution time source, this change replaces the lock with polling. Call time is reduced to ~0.7us. --- components/esp32/esp_timer_esp32.c | 38 ++++++++++++++++----- components/esp32/test/test_esp_timer.c | 47 ++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 9 deletions(-) diff --git a/components/esp32/esp_timer_esp32.c b/components/esp32/esp_timer_esp32.c index 864167b2b..3e08338a5 100644 --- a/components/esp32/esp_timer_esp32.c +++ b/components/esp32/esp_timer_esp32.c @@ -126,15 +126,35 @@ static inline bool IRAM_ATTR timer_overflow_happened() uint64_t IRAM_ATTR esp_timer_impl_get_time() { - portENTER_CRITICAL(&s_time_update_lock); - uint32_t timer_val = REG_READ(FRC_TIMER_COUNT_REG(1)); - uint64_t result = s_time_base_us; - if (timer_overflow_happened()) { - result += s_timer_us_per_overflow; - } - uint32_t ticks_per_us = s_timer_ticks_per_us; - portEXIT_CRITICAL(&s_time_update_lock); - return result + timer_val / ticks_per_us; + uint32_t timer_val; + uint64_t time_base; + uint32_t ticks_per_us; + bool overflow; + uint64_t us_per_overflow; + + do { + /* Read all values needed to calculate current time */ + timer_val = REG_READ(FRC_TIMER_COUNT_REG(1)); + time_base = s_time_base_us; + overflow = timer_overflow_happened(); + ticks_per_us = s_timer_ticks_per_us; + us_per_overflow = s_timer_us_per_overflow; + + /* Read them again and compare */ + if (REG_READ(FRC_TIMER_COUNT_REG(1)) > timer_val && + time_base == *((volatile uint64_t*) &s_time_base_us) && + ticks_per_us == *((volatile uint32_t*) &s_timer_ticks_per_us) && + overflow == timer_overflow_happened()) { + break; + } + + /* If any value has changed (other than the counter increasing), read again */ + } while(true); + + uint64_t result = time_base + + (overflow ? us_per_overflow : 0) + + timer_val / ticks_per_us; + return result; } void IRAM_ATTR esp_timer_impl_set_alarm(uint64_t timestamp) diff --git a/components/esp32/test/test_esp_timer.c b/components/esp32/test/test_esp_timer.c index a5b23a51f..a24cbfe5a 100644 --- a/components/esp32/test/test_esp_timer.c +++ b/components/esp32/test/test_esp_timer.c @@ -322,3 +322,50 @@ TEST_CASE("esp_timer for very short intervals", "[esp_timer]") vSemaphoreDelete(semaphore); } + + +TEST_CASE("esp_timer_get_time call takes less than 1us", "[esp_timer]") +{ + uint64_t begin = esp_timer_get_time(); + volatile uint64_t end; + const int iter_count = 10000; + for (int i = 0; i < iter_count; ++i) { + end = esp_timer_get_time(); + } + int ns_per_call = (int) ((end - begin) * 1000 / iter_count); + printf("esp_timer_get_time: %dns per call\n", ns_per_call); + TEST_ASSERT(ns_per_call < 1000); +} + +/* This test runs for about 10 minutes and is disabled in CI */ +TEST_CASE("esp_timer_get_time returns monotonic values", "[esp_timer][ignore]") +{ + void timer_test_task(void* arg) { + uint64_t last = esp_timer_get_time(); + + const int iter_count = 1000000000; + for (int i = 0; i < iter_count; ++i) { + uint64_t now = esp_timer_get_time(); + if (now < last || now - last > 100) { + printf("core_id:%d now: %lld last:%lld\n", xPortGetCoreID(), now, last); + fflush(stdout); + abort(); + } + last = now; + } + + xSemaphoreGive((SemaphoreHandle_t) arg); + vTaskDelete(NULL); + } + + SemaphoreHandle_t done_1 = xSemaphoreCreateBinary(); + SemaphoreHandle_t done_2 = xSemaphoreCreateBinary(); + + xTaskCreatePinnedToCore(&timer_test_task, "t1", 4096, (void*) done_1, 6, NULL, 0); + xTaskCreatePinnedToCore(&timer_test_task, "t2", 4096, (void*) done_2, 6, NULL, 1); + + TEST_ASSERT_TRUE( xSemaphoreTake(done_1, portMAX_DELAY) ); + TEST_ASSERT_TRUE( xSemaphoreTake(done_2, portMAX_DELAY) ); + vSemaphoreDelete(done_1); + vSemaphoreDelete(done_2); +}