mirror of
https://github.com/esphome/esphome.git
synced 2026-09-02 02:56:01 +00:00
[core] Extend HAL inlining to yield/delay/millis_64 + libretiny + rp2040
Extends the prior commit to cover more wrappers and platforms: - ESP32: also inline yield() and delay() - ESP8266: also inline yield(), delay(), millis(), millis_64() - LibreTiny: inline yield(), delay(), micros(), per-variant millis() fast paths, and millis_64() (via Millis64Impl::compute, now reachable from hal.h since time_64.h dropped its helpers.h dep) - RP2040: also inline yield(), delay(), micros() Consolidates the ESP8266/LibreTiny/RP2040 Arduino-flavored ::yield / ::delay / ::micros wrappers into a single shared block in hal.h. LibreTiny note: the prior IRAM_ATTR on the wrapper was decorative — ::micros(), ::yield(), ::delay() and xTaskGetTickCount all live in flash on every libretiny family (realtek-amb, beken-72xx, lightning-ln882h all checked), so an IRAM ISR call would have crashed the same way an inlined direct call does. Also drops the helpers.h include from time_64.h (only used for the ESPHOME_ALWAYS_INLINE macro, replaced with the raw attribute) so time_64.h is light enough for hal.h to include.
This commit is contained in:
@@ -22,7 +22,7 @@ extern "C" __attribute__((weak)) void initArduino() {}
|
||||
|
||||
namespace esphome {
|
||||
|
||||
void HOT yield() { vPortYield(); }
|
||||
// yield(), delay(), micros(), millis_64() inlined in hal.h.
|
||||
// Use xTaskGetTickCount() when tick rate is 1 kHz (ESPHome's default via sdkconfig),
|
||||
// falling back to esp_timer for non-standard rates. IRAM_ATTR is required because
|
||||
// Wiegand and ZyAura call millis() from IRAM_ATTR ISR handlers on ESP32.
|
||||
@@ -37,16 +37,6 @@ uint32_t IRAM_ATTR HOT millis() {
|
||||
return micros_to_millis(static_cast<uint64_t>(esp_timer_get_time()));
|
||||
#endif
|
||||
}
|
||||
// millis_64() and micros() are defined inline in hal.h on ESP32. Both sit on
|
||||
// esp_timer — a different clock from xTaskGetTickCount(). That is safe because
|
||||
// the two are never cross-compared: millis() values are only used for
|
||||
// millis()-vs-millis() deltas (feed_wdt, warn_blocking, component start time),
|
||||
// while millis_64() is used by the Scheduler and uptime sensors. On ESP32
|
||||
// (USE_NATIVE_64BIT_TIME), Scheduler::millis_64_from_(now) discards the 32-bit
|
||||
// now and calls millis_64() directly, so the Scheduler is internally consistent
|
||||
// on the esp_timer clock. Inlining both collapses the wrapper call/return that
|
||||
// runtime_stats and the main-loop hot path would otherwise incur every iteration.
|
||||
void HOT delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); }
|
||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
|
||||
void arch_restart() {
|
||||
esp_restart();
|
||||
|
||||
@@ -15,12 +15,7 @@ extern "C" {
|
||||
|
||||
namespace esphome {
|
||||
|
||||
void HOT yield() { ::yield(); }
|
||||
uint32_t IRAM_ATTR HOT millis() { return ::millis(); }
|
||||
uint64_t millis_64() { return Millis64Impl::compute(::millis()); }
|
||||
void HOT delay(uint32_t ms) { ::delay(ms); }
|
||||
// micros() is defined inline in hal.h on ESP8266 so callers collapse the
|
||||
// wrapper down to a direct ::micros() call.
|
||||
// yield(), delay(), micros(), millis(), millis_64() inlined in hal.h.
|
||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
|
||||
void arch_restart() {
|
||||
system_restart();
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "core.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/time_64.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "preferences.h"
|
||||
|
||||
@@ -15,32 +14,7 @@ void loop();
|
||||
|
||||
namespace esphome {
|
||||
|
||||
void HOT yield() { ::yield(); }
|
||||
// Inline the tick read so esphome::millis() matches MillisInternal::get()'s fast
|
||||
// path instead of going through the Arduino core's out-of-line ::millis() wrapper.
|
||||
//
|
||||
// RTL87xx / LN882x (1 kHz): xTaskGetTickCount() is already ms. IRAM_ATTR + ISR
|
||||
// dispatch are needed because ISR handlers (e.g. rotary_encoder) call millis().
|
||||
//
|
||||
// BK72xx (500 Hz): ticks * portTICK_PERIOD_MS (== 2). IRAM_ATTR and ISR dispatch
|
||||
// are both unnecessary — the SDK masks FIQ + IRQ during flash writes (see hal.h),
|
||||
// so no ISR runs while flash is stalled.
|
||||
#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 HOT millis() {
|
||||
static_assert(configTICK_RATE_HZ == 500, "BK72xx millis() fast path assumes 500 Hz FreeRTOS tick");
|
||||
return xTaskGetTickCount() * portTICK_PERIOD_MS;
|
||||
}
|
||||
#else
|
||||
uint32_t IRAM_ATTR HOT millis() { return ::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); }
|
||||
// yield(), delay(), micros(), millis(), millis_64() inlined in hal.h.
|
||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { ::delayMicroseconds(us); }
|
||||
|
||||
void arch_init() {
|
||||
|
||||
@@ -13,11 +13,7 @@
|
||||
|
||||
namespace esphome {
|
||||
|
||||
void HOT yield() { ::yield(); }
|
||||
// millis() and millis_64() are defined inline in hal.h on RP2040 so callers
|
||||
// collapse the wrapper down to a direct time_us_64() call.
|
||||
void HOT delay(uint32_t ms) { ::delay(ms); }
|
||||
uint32_t HOT micros() { return ::micros(); }
|
||||
// yield(), delay(), micros(), millis(), millis_64() inlined in hal.h.
|
||||
void HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
|
||||
void arch_restart() {
|
||||
watchdog_reboot(0, 0, 10);
|
||||
|
||||
+50
-29
@@ -2,6 +2,8 @@
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include "gpio.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/time_64.h"
|
||||
#include "esphome/core/time_conversion.h"
|
||||
|
||||
#if defined(USE_ESP32)
|
||||
@@ -63,6 +65,12 @@
|
||||
#include <freertos/task.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
// For the inline millis() fast paths (xTaskGetTickCount, portTICK_PERIOD_MS).
|
||||
#include <FreeRTOS.h>
|
||||
#include <task.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_BK72XX
|
||||
// Declared in the Beken FreeRTOS port (portmacro.h) and built in ARM mode so
|
||||
// it is callable from Thumb code via interworking. The MRS CPSR instruction
|
||||
@@ -71,17 +79,19 @@
|
||||
extern "C" uint32_t platform_is_in_interrupt_context(void);
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
// Forward-declared from <Arduino.h> (esp8266 core) so the inline esphome::micros()
|
||||
// wrapper below can call it without pulling Arduino.h into every TU. The
|
||||
// redundant-declaration NOLINT covers TUs that also include Arduino.h directly.
|
||||
// NOLINTNEXTLINE(google-runtime-int,readability-identifier-naming,readability-redundant-declaration)
|
||||
// Forward decls from Arduino's <Arduino.h> for the inline wrappers below.
|
||||
// NOLINT covers TUs that also include Arduino.h.
|
||||
#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2040)
|
||||
// NOLINTBEGIN(google-runtime-int,readability-identifier-naming,readability-redundant-declaration)
|
||||
extern "C" void yield(void);
|
||||
extern "C" void delay(unsigned long ms);
|
||||
extern "C" unsigned long micros(void);
|
||||
extern "C" unsigned long millis(void);
|
||||
// NOLINTEND(google-runtime-int,readability-identifier-naming,readability-redundant-declaration)
|
||||
#endif
|
||||
|
||||
#ifdef USE_RP2040
|
||||
// Forward-declared from <pico/time.h> so the inline esphome::millis()/millis_64()
|
||||
// wrappers below can call it without pulling pico/time.h into every TU.
|
||||
// Forward decl from <pico/time.h>.
|
||||
extern "C" uint64_t time_us_64(void);
|
||||
#endif
|
||||
|
||||
@@ -117,41 +127,52 @@ __attribute__((always_inline)) inline bool in_isr_context() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void yield();
|
||||
// yield()/delay()/micros()/millis()/millis_64() are inlined per platform to
|
||||
// drop the wrapper call/return — most relevant to runtime_stats and the main
|
||||
// loop. ESP8266/LibreTiny/RP2040 share Arduino's ::yield/::delay/::micros.
|
||||
#if defined(USE_ESP32)
|
||||
// Forward-declared from <esp_timer.h> to avoid pulling the full header (and its
|
||||
// transitive IDF deps) into every translation unit that includes hal.h. Signature
|
||||
// is stable IDF public API.
|
||||
// Forward decl from <esp_timer.h>.
|
||||
extern "C" int64_t esp_timer_get_time(void);
|
||||
|
||||
uint32_t millis();
|
||||
// Inlined so callers (especially the main loop via runtime_stats) collapse the
|
||||
// wrapper: ``call micros → call esp_timer_get_time`` becomes a single
|
||||
// ``call esp_timer_get_time``. Inlining into an IRAM_ATTR ISR is safe because
|
||||
// ``esp_timer_get_time`` itself lives in IRAM (IDF marks it so).
|
||||
__attribute__((always_inline)) inline void yield() { vPortYield(); }
|
||||
__attribute__((always_inline)) inline void delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); }
|
||||
__attribute__((always_inline)) inline uint32_t micros() { return static_cast<uint32_t>(esp_timer_get_time()); }
|
||||
uint32_t millis();
|
||||
__attribute__((always_inline)) inline uint64_t millis_64() {
|
||||
return micros_to_millis<uint64_t>(static_cast<uint64_t>(esp_timer_get_time()));
|
||||
}
|
||||
#elif defined(USE_ESP8266)
|
||||
// Arduino's ::micros() (esp8266 core) is itself in IRAM, so inlining our wrapper
|
||||
// into IRAM_ATTR ISRs is safe. Forward-declared at global scope below to avoid
|
||||
// pulling Arduino.h into every TU that includes hal.h.
|
||||
#elif defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2040)
|
||||
__attribute__((always_inline)) inline void yield() { ::yield(); }
|
||||
__attribute__((always_inline)) inline void delay(uint32_t ms) { ::delay(ms); }
|
||||
__attribute__((always_inline)) inline uint32_t micros() { return static_cast<uint32_t>(::micros()); }
|
||||
uint32_t millis();
|
||||
uint64_t millis_64();
|
||||
#elif defined(USE_RP2040)
|
||||
uint32_t micros();
|
||||
// Pico SDK clock used by ::millis()/time_us_64() under the hood; forward-declared
|
||||
// at global scope below.
|
||||
#if defined(USE_ESP8266)
|
||||
__attribute__((always_inline)) inline uint32_t millis() { return static_cast<uint32_t>(::millis()); }
|
||||
__attribute__((always_inline)) inline uint64_t millis_64() { return Millis64Impl::compute(millis()); }
|
||||
#elif defined(USE_LIBRETINY)
|
||||
// Per-variant millis() fast path — matches MillisInternal::get().
|
||||
#if defined(USE_RTL87XX) || defined(USE_LN882X)
|
||||
static_assert(configTICK_RATE_HZ == 1000, "millis() fast path requires 1 kHz FreeRTOS tick");
|
||||
__attribute__((always_inline)) inline uint32_t millis() {
|
||||
// xTaskGetTickCountFromISR is mandatory in interrupt context per the FreeRTOS API contract.
|
||||
return in_isr_context() ? xTaskGetTickCountFromISR() : xTaskGetTickCount();
|
||||
}
|
||||
#elif defined(USE_BK72XX)
|
||||
static_assert(configTICK_RATE_HZ == 500, "BK72xx millis() fast path assumes 500 Hz FreeRTOS tick");
|
||||
__attribute__((always_inline)) inline uint32_t millis() { return xTaskGetTickCount() * portTICK_PERIOD_MS; }
|
||||
#else
|
||||
__attribute__((always_inline)) inline uint32_t millis() { return static_cast<uint32_t>(::millis()); }
|
||||
#endif
|
||||
__attribute__((always_inline)) inline uint64_t millis_64() { return Millis64Impl::compute(millis()); }
|
||||
#else // USE_RP2040
|
||||
__attribute__((always_inline)) inline uint32_t millis() { return micros_to_millis(::time_us_64()); }
|
||||
__attribute__((always_inline)) inline uint64_t millis_64() { return micros_to_millis<uint64_t>(::time_us_64()); }
|
||||
#endif
|
||||
#else
|
||||
uint32_t millis();
|
||||
void yield();
|
||||
void delay(uint32_t ms);
|
||||
uint32_t micros();
|
||||
uint32_t millis();
|
||||
uint64_t millis_64();
|
||||
#endif
|
||||
void delay(uint32_t ms);
|
||||
void delayMicroseconds(uint32_t us); // NOLINT(readability-identifier-naming)
|
||||
void __attribute__((noreturn)) arch_restart();
|
||||
void arch_init();
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
class Scheduler;
|
||||
@@ -24,7 +22,9 @@ class Millis64Impl {
|
||||
static uint32_t last_millis_;
|
||||
static uint16_t millis_major_;
|
||||
|
||||
static inline uint64_t ESPHOME_ALWAYS_INLINE compute(uint32_t now) {
|
||||
// Raw __attribute__((always_inline)) (not ESPHOME_ALWAYS_INLINE) so this
|
||||
// header does not need to pull helpers.h.
|
||||
static inline uint64_t __attribute__((always_inline)) compute(uint32_t now) {
|
||||
// Half the 32-bit range - used to detect rollovers vs normal time progression
|
||||
static constexpr uint32_t HALF_MAX_UINT32 = std::numeric_limits<uint32_t>::max() / 2;
|
||||
|
||||
|
||||
@@ -25,10 +25,6 @@ namespace esphome {
|
||||
///
|
||||
/// Safe for us up to ~3.2e18 (~101,700 years of microseconds).
|
||||
///
|
||||
/// Lives in its own header (rather than helpers.h) so hal.h can include it
|
||||
/// for inline millis_64() definitions without dragging the rest of helpers.h
|
||||
/// into every translation unit.
|
||||
///
|
||||
/// See: https://en.wikipedia.org/wiki/Euclidean_division
|
||||
/// See: https://ridiculousfish.com/blog/posts/labor-of-division-episode-iii.html
|
||||
template<typename ReturnT = uint32_t>
|
||||
|
||||
Reference in New Issue
Block a user