From 6ac705cd66519d2a2d1b4440b591b5b995d38916 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 11 Apr 2026 22:13:33 -1000 Subject: [PATCH] [esp32] Use xTaskGetTickCount() for millis() when tick rate is 1kHz MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ESPHome sets CONFIG_FREERTOS_HZ=1000, so one FreeRTOS tick equals one millisecond and xTaskGetTickCount() returns ms directly. This replaces esp_timer_get_time() + micros_to_millis() (a hardware timer read plus a 64-bit multiply-shift conversion) with a single volatile memory read. Benchmarked on real ESP32 hardware: Before: 686 ns/call (esp_timer_get_time + micros_to_millis) After: ~20 ns/call (volatile read of xTickCount) millis() is called 1+N times per main loop iteration (once at the top and once per component via WarnIfComponentBlockingGuard::finish()), so on a 5-component device this saves ~3.4 μs per loop iteration. millis_64() is left on esp_timer_get_time() for full 64-bit μs precision — it is only called once per loop by the Scheduler. micros() is also unchanged (runtime_stats needs μs precision). Falls back to the original implementation if CONFIG_FREERTOS_HZ != 1000 (non-standard user override). --- esphome/components/esp32/core.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index 313818e601..eaa8bf2d17 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -23,7 +23,19 @@ extern "C" __attribute__((weak)) void initArduino() {} namespace esphome { void HOT yield() { vPortYield(); } -uint32_t IRAM_ATTR HOT millis() { return micros_to_millis(static_cast(esp_timer_get_time())); } +uint32_t IRAM_ATTR HOT millis() { + // ESPHome sets CONFIG_FREERTOS_HZ=1000 (see esp32/__init__.py), so one tick = one millisecond + // and xTaskGetTickCount() returns ms directly. This is a single volatile memory read (~20ns) + // vs esp_timer_get_time() + micros_to_millis() which does a hardware timer read + 64-bit + // multiply-shift conversion (~686ns measured). millis() is called 1+N times per main loop + // iteration (once at top + once per component for warn_blocking), so this saves ~3.4μs/loop + // on a 5-component device. micros() still uses esp_timer_get_time() for μs precision. +#if CONFIG_FREERTOS_HZ == 1000 + return xTaskGetTickCount(); +#else + return micros_to_millis(static_cast(esp_timer_get_time())); +#endif +} uint64_t HOT millis_64() { return micros_to_millis(static_cast(esp_timer_get_time())); } void HOT delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); } uint32_t IRAM_ATTR HOT micros() { return (uint32_t) esp_timer_get_time(); }