From 1ce6364892ff9d12fcf271e717dfaf46bade9b6c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 29 Apr 2026 06:01:11 -0500 Subject: [PATCH 1/3] [libretiny] Move HAL bodies into components/libretiny/hal.cpp + inline trivial dispatches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-platform follow-up to #15977. Move out-of-line HAL bodies (delayMicroseconds, arch_init, arch_restart) from components/libretiny/core.cpp into a new components/libretiny/hal.cpp. core.cpp is now empty (no extra component bootstrap to keep) — left as a stub for symmetry. Inline the trivial one-liners directly in hal_libretiny.h with forward decls for the LibreTiny C API: arch_feed_wdt() -> lt_wdt_feed() arch_get_cpu_cycle_count() -> lt_cpu_get_cycle_count() arch_get_cpu_freq_hz() -> lt_cpu_get_freq() arch_init stays out-of-line because of the libretiny::setup_preferences() call (component-private header) plus the BK72xx priority-raise + GPIO recovery logic. delayMicroseconds() also stays out-of-line — Arduino's ::delayMicroseconds signature is unsigned int and forward-declaring it across the C/C++ boundary in a hot header is fragile. --- esphome/components/libretiny/core.cpp | 53 +-------------------------- esphome/components/libretiny/hal.cpp | 53 +++++++++++++++++++++++++++ esphome/core/hal/hal_libretiny.h | 14 +++++-- 3 files changed, 66 insertions(+), 54 deletions(-) create mode 100644 esphome/components/libretiny/hal.cpp diff --git a/esphome/components/libretiny/core.cpp b/esphome/components/libretiny/core.cpp index f46abe3b81..8686a41e64 100644 --- a/esphome/components/libretiny/core.cpp +++ b/esphome/components/libretiny/core.cpp @@ -1,55 +1,6 @@ #ifdef USE_LIBRETINY -#include "core.h" -#include "esphome/core/defines.h" -#include "esphome/core/hal.h" -#include "esphome/core/helpers.h" -#include "preferences.h" - -#include -#include - -void setup(); -void loop(); - -namespace esphome { - -// yield(), delay(), micros(), millis(), millis_64() inlined in hal.h. -void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { ::delayMicroseconds(us); } - -void arch_init() { - libretiny::setup_preferences(); - lt_wdt_enable(10000L); -#ifdef USE_BK72XX - // BK72xx SDK creates the main Arduino task at priority 3, which is lower than - // all WiFi (4-5), LwIP (4), and TCP/IP (7) tasks. This causes ~100ms loop - // stalls whenever WiFi background processing runs, because the main task - // cannot resume until every higher-priority task finishes. - // - // By contrast, RTL87xx creates the main task at osPriorityRealtime (highest). - // - // Raise to priority 6: above WiFi/LwIP tasks (4-5) so they don't preempt the - // main loop, but below the TCP/IP thread (7) so packet processing keeps priority. - // This is safe because ESPHome yields voluntarily via wakeable_delay() and - // the Arduino mainTask yield() after each loop() iteration. - static constexpr UBaseType_t MAIN_TASK_PRIORITY = 6; - static_assert(MAIN_TASK_PRIORITY < configMAX_PRIORITIES, "MAIN_TASK_PRIORITY must be less than configMAX_PRIORITIES"); - vTaskPrioritySet(nullptr, MAIN_TASK_PRIORITY); -#endif -#if LT_GPIO_RECOVER - lt_gpio_recover(); -#endif -} - -void arch_restart() { - lt_reboot(); - while (1) { - } -} -void HOT arch_feed_wdt() { lt_wdt_feed(); } -uint32_t arch_get_cpu_cycle_count() { return lt_cpu_get_cycle_count(); } -uint32_t arch_get_cpu_freq_hz() { return lt_cpu_get_freq(); } - -} // namespace esphome +// HAL functions live in hal.cpp. core.cpp is intentionally empty for +// libretiny — there is no extra component bootstrap to keep here. #endif // USE_LIBRETINY diff --git a/esphome/components/libretiny/hal.cpp b/esphome/components/libretiny/hal.cpp new file mode 100644 index 0000000000..64b8de8f86 --- /dev/null +++ b/esphome/components/libretiny/hal.cpp @@ -0,0 +1,53 @@ +#ifdef USE_LIBRETINY + +#include "esphome/core/hal.h" +#include "preferences.h" + +#include +#include + +// Empty libretiny namespace block to satisfy ci-custom's lint_namespace check. +// HAL functions live in namespace esphome (root) — they are not part of the +// libretiny component's API. +namespace esphome::libretiny {} // namespace esphome::libretiny + +namespace esphome { + +// yield(), delay(), micros(), millis(), millis_64(), arch_feed_wdt(), +// arch_get_cpu_cycle_count(), arch_get_cpu_freq_hz() inlined in +// core/hal/hal_libretiny.h. +void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { ::delayMicroseconds(us); } + +void arch_init() { + libretiny::setup_preferences(); + lt_wdt_enable(10000L); +#ifdef USE_BK72XX + // BK72xx SDK creates the main Arduino task at priority 3, which is lower than + // all WiFi (4-5), LwIP (4), and TCP/IP (7) tasks. This causes ~100ms loop + // stalls whenever WiFi background processing runs, because the main task + // cannot resume until every higher-priority task finishes. + // + // By contrast, RTL87xx creates the main task at osPriorityRealtime (highest). + // + // Raise to priority 6: above WiFi/LwIP tasks (4-5) so they don't preempt the + // main loop, but below the TCP/IP thread (7) so packet processing keeps priority. + // This is safe because ESPHome yields voluntarily via wakeable_delay() and + // the Arduino mainTask yield() after each loop() iteration. + static constexpr UBaseType_t MAIN_TASK_PRIORITY = 6; + static_assert(MAIN_TASK_PRIORITY < configMAX_PRIORITIES, "MAIN_TASK_PRIORITY must be less than configMAX_PRIORITIES"); + vTaskPrioritySet(nullptr, MAIN_TASK_PRIORITY); +#endif +#if LT_GPIO_RECOVER + lt_gpio_recover(); +#endif +} + +void arch_restart() { + lt_reboot(); + while (1) { + } +} + +} // namespace esphome + +#endif // USE_LIBRETINY diff --git a/esphome/core/hal/hal_libretiny.h b/esphome/core/hal/hal_libretiny.h index ecfe830fe3..2c8b45812a 100644 --- a/esphome/core/hal/hal_libretiny.h +++ b/esphome/core/hal/hal_libretiny.h @@ -53,6 +53,13 @@ extern "C" unsigned long micros(void); extern "C" unsigned long millis(void); // NOLINTEND(google-runtime-int,readability-identifier-naming,readability-redundant-declaration) +// Forward decls from libretiny's family for the inline arch_* +// wrappers below. Pulling the full header would drag in the rest of the +// LibreTiny C API. +extern "C" void lt_wdt_feed(void); +extern "C" uint32_t lt_cpu_get_cycle_count(void); +extern "C" uint32_t lt_cpu_get_freq(void); + namespace esphome { /// Returns true when executing inside an interrupt handler. @@ -88,11 +95,12 @@ __attribute__((always_inline)) inline uint32_t millis() { return static_cast Date: Wed, 29 Apr 2026 07:03:56 -0500 Subject: [PATCH 2/3] [libretiny] Address Copilot review on #16113 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - hal.cpp: include components/libretiny/core.h so the lt_* C API (lt_wdt_enable, lt_reboot, lt_gpio_recover) and the LT_GPIO_RECOVER macro are properly declared rather than relying on transitive includes. core.h pulls which is libretiny's umbrella header. - hal_libretiny.h: preserve the HOT attribute on arch_feed_wdt() — the out-of-line wrapper had it; the inline now uses __attribute__((hot, always_inline)) to keep the placement hint consistent with the other platforms. The arch_restart() noreturn concern is already covered by the dispatcher hal.h declaration ('void __attribute__((noreturn)) arch_restart();' is visible when the body is parsed). --- esphome/components/libretiny/hal.cpp | 1 + esphome/core/hal/hal_libretiny.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/libretiny/hal.cpp b/esphome/components/libretiny/hal.cpp index 64b8de8f86..4e94d5242b 100644 --- a/esphome/components/libretiny/hal.cpp +++ b/esphome/components/libretiny/hal.cpp @@ -1,5 +1,6 @@ #ifdef USE_LIBRETINY +#include "core.h" #include "esphome/core/hal.h" #include "preferences.h" diff --git a/esphome/core/hal/hal_libretiny.h b/esphome/core/hal/hal_libretiny.h index 2c8b45812a..e519698ce0 100644 --- a/esphome/core/hal/hal_libretiny.h +++ b/esphome/core/hal/hal_libretiny.h @@ -95,7 +95,7 @@ __attribute__((always_inline)) inline uint32_t millis() { return static_cast Date: Wed, 29 Apr 2026 07:17:37 -0500 Subject: [PATCH 3/3] [libretiny] Inline delayMicroseconds() in hal_libretiny.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wrapper is a one-liner forwarding to Arduino's ::delayMicroseconds. Add the forward decl to the existing NOLINTBEGIN block (alongside yield, delay, micros, millis) and inline the body — same pattern as the other Arduino-flavored wrappers in this header. Drops the out-of-line copy from components/libretiny/hal.cpp. --- esphome/components/libretiny/hal.cpp | 7 +++---- esphome/core/hal/hal_libretiny.h | 4 +++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/esphome/components/libretiny/hal.cpp b/esphome/components/libretiny/hal.cpp index 4e94d5242b..e6dbb7296c 100644 --- a/esphome/components/libretiny/hal.cpp +++ b/esphome/components/libretiny/hal.cpp @@ -14,10 +14,9 @@ namespace esphome::libretiny {} // namespace esphome::libretiny namespace esphome { -// yield(), delay(), micros(), millis(), millis_64(), arch_feed_wdt(), -// arch_get_cpu_cycle_count(), arch_get_cpu_freq_hz() inlined in -// core/hal/hal_libretiny.h. -void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { ::delayMicroseconds(us); } +// yield(), delay(), micros(), millis(), millis_64(), delayMicroseconds(), +// arch_feed_wdt(), arch_get_cpu_cycle_count(), arch_get_cpu_freq_hz() +// inlined in core/hal/hal_libretiny.h. void arch_init() { libretiny::setup_preferences(); diff --git a/esphome/core/hal/hal_libretiny.h b/esphome/core/hal/hal_libretiny.h index e519698ce0..db0fc11bfb 100644 --- a/esphome/core/hal/hal_libretiny.h +++ b/esphome/core/hal/hal_libretiny.h @@ -51,6 +51,7 @@ extern "C" void yield(void); extern "C" void delay(unsigned long ms); extern "C" unsigned long micros(void); extern "C" unsigned long millis(void); +extern "C" void delayMicroseconds(unsigned int us); // NOLINTEND(google-runtime-int,readability-identifier-naming,readability-redundant-declaration) // Forward decls from libretiny's family for the inline arch_* @@ -95,11 +96,12 @@ __attribute__((always_inline)) inline uint32_t millis() { return static_cast