Просмотр исходного кода

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.
Ivan Grokhotkov 8 лет назад
Родитель
Сommit
1af6384349
2 измененных файлов с 76 добавлено и 9 удалено
  1. 29 9
      components/esp32/esp_timer_esp32.c
  2. 47 0
      components/esp32/test/test_esp_timer.c

+ 29 - 9
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)

+ 47 - 0
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);
+}