[esp32] Keep millis() out-of-line with IRAM_ATTR, add ISR context check

Revert the inline hal.h approach — millis() must remain IRAM_ATTR
because Wiegand and ZyAura call it from IRAM_ATTR ISR handlers on
all platforms including ESP32.

Use xPortInIsrContext() to dispatch to xTaskGetTickCountFromISR()
from ISR or xTaskGetTickCount() from task context, satisfying the
FreeRTOS API contract. ISR check is [[unlikely]] so the hot path
stays fast.

Benchmarked: 686 ns -> 361 ns (1.9x faster). Correctness verified.
This commit is contained in:
J. Nick Koston
2026-04-12 09:07:08 -10:00
parent c155f41110
commit b5a2d58b80
2 changed files with 13 additions and 19 deletions
+13 -6
View File
@@ -23,13 +23,20 @@ extern "C" __attribute__((weak)) void initArduino() {}
namespace esphome {
void HOT yield() { vPortYield(); }
// millis() is inlined in hal.h when CONFIG_FREERTOS_HZ == 1000 (just xTaskGetTickCount()).
// Fallback for non-standard tick rates. No IRAM_ATTR — the original IRAM placement was
// inherited from ESP8266 where Arduino's millis() is called from ISR handlers (Wiegand,
// ZyAura). ESPHome doesn't call millis() from ISR on ESP32.
#if CONFIG_FREERTOS_HZ != 1000
uint32_t HOT millis() { return micros_to_millis(static_cast<uint64_t>(esp_timer_get_time())); }
// 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.
// xTaskGetTickCountFromISR() is used in ISR context to satisfy the FreeRTOS API contract.
uint32_t IRAM_ATTR HOT millis() {
#if CONFIG_FREERTOS_HZ == 1000
if (xPortInIsrContext()) [[unlikely]] {
return xTaskGetTickCountFromISR();
}
return xTaskGetTickCount();
#else
return micros_to_millis(static_cast<uint64_t>(esp_timer_get_time()));
#endif
}
uint64_t HOT millis_64() { return micros_to_millis<uint64_t>(static_cast<uint64_t>(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(); }
-13
View File
@@ -28,23 +28,10 @@
#endif
// On ESP32 with 1 kHz FreeRTOS tick rate, millis() is just xTaskGetTickCount() —
// a single volatile read of a DRAM global. Inlining it here eliminates the
// function call entirely at every call site. No IRAM needed (no flash code
// executed), no 64-bit math, no HAL call.
#if defined(USE_ESP32) && CONFIG_FREERTOS_HZ == 1000
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#endif
namespace esphome {
void yield();
#if defined(USE_ESP32) && CONFIG_FREERTOS_HZ == 1000
inline uint32_t millis() { return xTaskGetTickCount(); }
#else
uint32_t millis();
#endif
uint64_t millis_64();
uint32_t micros();
void delay(uint32_t ms);