From c0e1b7e255a43e49517eb324e20a86d7efd48d7f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 22 Apr 2026 07:33:42 +0200 Subject: [PATCH] [libretiny] Use xTaskGetTickCount() directly for millis() on 1 kHz chips --- esphome/components/libretiny/core.cpp | 23 ++++++++++++++++++++++- esphome/core/millis_internal.h | 21 ++++++++++++++++++--- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/esphome/components/libretiny/core.cpp b/esphome/components/libretiny/core.cpp index 1cfe68e924..1d7cb6ff60 100644 --- a/esphome/components/libretiny/core.cpp +++ b/esphome/components/libretiny/core.cpp @@ -16,8 +16,29 @@ void loop(); namespace esphome { void HOT yield() { ::yield(); } +// Skip the Arduino core's ::millis() wrapper so the public esphome::millis() is +// inlinable at its call sites and matches MillisInternal::get()'s fast path. +// IRAM_ATTR is kept because components (e.g. rotary_encoder) call millis() from +// ISR handlers; xTaskGetTickCountFromISR() is used in ISR context to satisfy the +// FreeRTOS API contract. +// +// RTL87xx and LN882x run FreeRTOS at 1 kHz, so xTaskGetTickCount() is already in +// milliseconds. BK72xx runs at 500 Hz — multiply by portTICK_PERIOD_MS (== 2) to +// convert ticks to milliseconds, matching the Arduino core's wiring.c. +#if defined(USE_RTL87XX) || defined(USE_LN882X) +uint32_t IRAM_ATTR HOT millis() { + static_assert(configTICK_RATE_HZ == 1000, "millis() fast path requires 1 kHz FreeRTOS tick"); + return in_isr_context() ? xTaskGetTickCountFromISR() : xTaskGetTickCount(); +} +#elif defined(USE_BK72XX) +uint32_t IRAM_ATTR HOT millis() { + // BK72xx's Arduino millis() does not dispatch on ISR context; match that. + return xTaskGetTickCount() * portTICK_PERIOD_MS; +} +#else uint32_t IRAM_ATTR HOT millis() { return ::millis(); } -uint64_t millis_64() { return Millis64Impl::compute(::millis()); } +#endif +uint64_t millis_64() { return Millis64Impl::compute(millis()); } uint32_t IRAM_ATTR HOT micros() { return ::micros(); } void HOT delay(uint32_t ms) { ::delay(ms); } void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { ::delayMicroseconds(us); } diff --git a/esphome/core/millis_internal.h b/esphome/core/millis_internal.h index 6b73476680..ded49f9cd2 100644 --- a/esphome/core/millis_internal.h +++ b/esphome/core/millis_internal.h @@ -7,6 +7,9 @@ #include #include #include +#elif defined(USE_LIBRETINY) +#include +#include #endif namespace esphome { @@ -14,10 +17,11 @@ namespace esphome { // Friend-gated accessor for a fast millis() variant intended only for // known task-context callers on the main loop hot path (Application::loop() // and WarnIfComponentBlockingGuard::finish()). It skips the ISR-context -// dispatch that the public esphome::millis() pays on ESP32. +// dispatch that the public esphome::millis() pays on ESP32 and libretiny. // -// MUST NOT be called from ISR context: on ESP32 it calls the non-FromISR -// FreeRTOS API directly, which is undefined behavior in ISR context. +// MUST NOT be called from ISR context: on ESP32 and libretiny it calls the +// non-FromISR FreeRTOS API directly, which is undefined behavior in ISR +// context. // // Adding new callers requires adding a friend declaration here — that // is the review point. Do not relax the access (e.g. by making get() @@ -31,6 +35,17 @@ class MillisInternal { static ESPHOME_ALWAYS_INLINE uint32_t get() { #if defined(USE_ESP32) && CONFIG_FREERTOS_HZ == 1000 return xTaskGetTickCount(); +#elif defined(USE_LIBRETINY) && (defined(USE_RTL87XX) || defined(USE_LN882X)) + // RTL87xx and LN882x run FreeRTOS at 1 kHz, so xTaskGetTickCount() is + // already in milliseconds. + static_assert(configTICK_RATE_HZ == 1000, "MillisInternal fast path requires 1 kHz FreeRTOS tick"); + return xTaskGetTickCount(); +#elif defined(USE_BK72XX) + // BK72xx runs FreeRTOS at 500 Hz; scale ticks by portTICK_PERIOD_MS (== 2). + // Inlined here because esphome::millis() on BK72xx has no-op IRAM_ATTR but + // is still out-of-line, so calling it would cost a real function call. + static_assert(configTICK_RATE_HZ == 500, "BK72xx MillisInternal assumes 500 Hz FreeRTOS tick"); + return xTaskGetTickCount() * portTICK_PERIOD_MS; #else return millis(); #endif