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.
This commit is contained in:
parent
c743bdac88
commit
1af6384349
2 changed files with 76 additions and 9 deletions
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue