mirror of
https://github.com/esphome/esphome.git
synced 2026-06-01 01:19:45 +08:00
Merge remote-tracking branch 'upstream/followup/hal-esp8266' into integration
This commit is contained in:
@@ -3,98 +3,12 @@
|
|||||||
#include "core.h"
|
#include "core.h"
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/time_64.h"
|
|
||||||
#include "esphome/core/helpers.h"
|
|
||||||
#include "preferences.h"
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <core_esp8266_features.h>
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
#include <user_interface.h>
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|
||||||
// yield(), micros(), millis_64() inlined in hal.h.
|
// HAL functions live in hal.cpp. This file keeps only the ESP8266-specific
|
||||||
// Fast accumulator replacement for Arduino's millis() (~3.3 μs via 4× 64-bit
|
// firmware bootstrap (Tasmota OTA magic bytes, optional GPIO pre-init).
|
||||||
// multiplies on the LX106). Tracks a running ms counter from 32-bit
|
|
||||||
// system_get_time() deltas using pure 32-bit ops. Installed as __wrap_millis
|
|
||||||
// (via -Wl,--wrap=millis) so Arduino libs and IRAM_ATTR ISR handlers (e.g.
|
|
||||||
// Wiegand, ZyAura) also get the fast version. xt_rsil(15) guards the static
|
|
||||||
// state against ISR re-entry; the critical section is bounded (≤10 while-loop
|
|
||||||
// iterations, ~100 ns on the common path, or a constant-time /1000 ~2.5 μs on
|
|
||||||
// the rare path — well under WiFi's ~10 μs ISR latency budget). NMIs (level
|
|
||||||
// >15) are not masked, but the ESP8266 SDK's NMI handlers don't call millis().
|
|
||||||
//
|
|
||||||
// system_get_time() wraps every ~71.6 min; unsigned (now_us - last_us) handles
|
|
||||||
// one wrap. The main loop calls millis() at 60+ Hz, so delta stays tiny — a
|
|
||||||
// >71 min block would trip the watchdog long before it could matter here.
|
|
||||||
static constexpr uint32_t MILLIS_RARE_PATH_THRESHOLD_US = 10000;
|
|
||||||
static constexpr uint32_t US_PER_MS = 1000;
|
|
||||||
|
|
||||||
uint32_t IRAM_ATTR HOT millis() {
|
|
||||||
// Struct packs the three statics so the compiler loads one base address
|
|
||||||
// instead of three separate literal pool entries (saves ~8 bytes IRAM).
|
|
||||||
static struct {
|
|
||||||
uint32_t cache;
|
|
||||||
uint32_t remainder;
|
|
||||||
uint32_t last_us;
|
|
||||||
} state = {0, 0, 0};
|
|
||||||
uint32_t ps = xt_rsil(15);
|
|
||||||
uint32_t now_us = system_get_time();
|
|
||||||
uint32_t delta = now_us - state.last_us;
|
|
||||||
state.last_us = now_us;
|
|
||||||
state.remainder += delta;
|
|
||||||
if (state.remainder >= MILLIS_RARE_PATH_THRESHOLD_US) {
|
|
||||||
// Rare path: large gap (WiFi scan, boot, long block). Constant-time
|
|
||||||
// conversion keeps the critical section bounded.
|
|
||||||
uint32_t ms = state.remainder / US_PER_MS;
|
|
||||||
state.cache += ms;
|
|
||||||
// Reuse ms instead of `remainder %= US_PER_MS` — `%` would compile to a
|
|
||||||
// second __umodsi3 call on the LX106 (no hardware divide).
|
|
||||||
state.remainder -= ms * US_PER_MS;
|
|
||||||
} else {
|
|
||||||
// Common path: small gap. At most ~10 iterations since remainder was
|
|
||||||
// < threshold (10 ms) on entry and delta adds at most one more threshold
|
|
||||||
// before exiting this branch.
|
|
||||||
while (state.remainder >= US_PER_MS) {
|
|
||||||
state.cache++;
|
|
||||||
state.remainder -= US_PER_MS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uint32_t result = state.cache;
|
|
||||||
xt_wsr_ps(ps);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
// Poll-based delay that avoids ::delay() — Arduino's __delay has an intra-object
|
|
||||||
// call to the original millis() that --wrap can't intercept, so calling ::delay()
|
|
||||||
// would keep the slow Arduino millis body alive in IRAM. optimistic_yield still
|
|
||||||
// enters esp_schedule()/esp_suspend_within_cont() via yield(), so SDK tasks and
|
|
||||||
// WiFi run correctly. Theoretically less power-efficient than Arduino's
|
|
||||||
// os_timer-based delay() for long waits, but nearly all ESPHome delays are short
|
|
||||||
// (sensor/I²C/SPI settling in the 1–100 ms range) where the difference is
|
|
||||||
// negligible.
|
|
||||||
void HOT delay(uint32_t ms) {
|
|
||||||
if (ms == 0) {
|
|
||||||
optimistic_yield(1000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint32_t start = millis();
|
|
||||||
while (millis() - start < ms) {
|
|
||||||
optimistic_yield(1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// delayMicroseconds(), arch_feed_wdt(), and progmem_read_*() are inlined in hal/hal_esp8266.h.
|
|
||||||
void arch_restart() {
|
|
||||||
system_restart();
|
|
||||||
// restart() doesn't always end execution
|
|
||||||
while (true) { // NOLINT(clang-diagnostic-unreachable-code)
|
|
||||||
yield();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void arch_init() {}
|
|
||||||
uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() { return esp_get_cycle_count(); }
|
|
||||||
uint32_t arch_get_cpu_freq_hz() { return F_CPU; }
|
|
||||||
|
|
||||||
void force_link_symbols() {
|
void force_link_symbols() {
|
||||||
// Tasmota uses magic bytes in the binary to check if an OTA firmware is compatible
|
// Tasmota uses magic bytes in the binary to check if an OTA firmware is compatible
|
||||||
@@ -131,12 +45,4 @@ extern "C" void resetPins() { // NOLINT
|
|||||||
|
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
// Linker wrap: redirect all ::millis() calls (Arduino libs, ISRs) to our accumulator.
|
|
||||||
// Requires -Wl,--wrap=millis in build flags (added by __init__.py).
|
|
||||||
// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
|
|
||||||
extern "C" uint32_t IRAM_ATTR __wrap_millis() { return esphome::millis(); }
|
|
||||||
// Note: Arduino's init() registers a 60-second overflow timer for micros64().
|
|
||||||
// We leave it running — wrapping init() as a no-op would break micros64()'s
|
|
||||||
// overflow tracking, and the timer's cost is negligible (~3 μs per 60 s).
|
|
||||||
|
|
||||||
#endif // USE_ESP8266
|
#endif // USE_ESP8266
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
#ifdef USE_ESP8266
|
||||||
|
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <core_esp8266_features.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <user_interface.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty esp8266 namespace block to satisfy ci-custom's lint_namespace check.
|
||||||
|
// HAL functions live in namespace esphome (root) — they are not part of the
|
||||||
|
// esp8266 component's API.
|
||||||
|
namespace esphome::esp8266 {} // namespace esphome::esp8266
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
|
||||||
|
// yield(), micros(), millis_64(), delayMicroseconds(), arch_feed_wdt(),
|
||||||
|
// progmem_read_*() are inlined in core/hal/hal_esp8266.h.
|
||||||
|
//
|
||||||
|
// Fast accumulator replacement for Arduino's millis() (~3.3 μs via 4× 64-bit
|
||||||
|
// multiplies on the LX106). Tracks a running ms counter from 32-bit
|
||||||
|
// system_get_time() deltas using pure 32-bit ops. Installed as __wrap_millis
|
||||||
|
// (via -Wl,--wrap=millis) so Arduino libs and IRAM_ATTR ISR handlers (e.g.
|
||||||
|
// Wiegand, ZyAura) also get the fast version. xt_rsil(15) guards the static
|
||||||
|
// state against ISR re-entry; the critical section is bounded (≤10 while-loop
|
||||||
|
// iterations, ~100 ns on the common path, or a constant-time /1000 ~2.5 μs on
|
||||||
|
// the rare path — well under WiFi's ~10 μs ISR latency budget). NMIs (level
|
||||||
|
// >15) are not masked, but the ESP8266 SDK's NMI handlers don't call millis().
|
||||||
|
//
|
||||||
|
// system_get_time() wraps every ~71.6 min; unsigned (now_us - last_us) handles
|
||||||
|
// one wrap. The main loop calls millis() at 60+ Hz, so delta stays tiny — a
|
||||||
|
// >71 min block would trip the watchdog long before it could matter here.
|
||||||
|
static constexpr uint32_t MILLIS_RARE_PATH_THRESHOLD_US = 10000;
|
||||||
|
static constexpr uint32_t US_PER_MS = 1000;
|
||||||
|
|
||||||
|
uint32_t IRAM_ATTR HOT millis() {
|
||||||
|
// Struct packs the three statics so the compiler loads one base address
|
||||||
|
// instead of three separate literal pool entries (saves ~8 bytes IRAM).
|
||||||
|
static struct {
|
||||||
|
uint32_t cache;
|
||||||
|
uint32_t remainder;
|
||||||
|
uint32_t last_us;
|
||||||
|
} state = {0, 0, 0};
|
||||||
|
uint32_t ps = xt_rsil(15);
|
||||||
|
uint32_t now_us = system_get_time();
|
||||||
|
uint32_t delta = now_us - state.last_us;
|
||||||
|
state.last_us = now_us;
|
||||||
|
state.remainder += delta;
|
||||||
|
if (state.remainder >= MILLIS_RARE_PATH_THRESHOLD_US) {
|
||||||
|
// Rare path: large gap (WiFi scan, boot, long block). Constant-time
|
||||||
|
// conversion keeps the critical section bounded.
|
||||||
|
uint32_t ms = state.remainder / US_PER_MS;
|
||||||
|
state.cache += ms;
|
||||||
|
// Reuse ms instead of `remainder %= US_PER_MS` — `%` would compile to a
|
||||||
|
// second __umodsi3 call on the LX106 (no hardware divide).
|
||||||
|
state.remainder -= ms * US_PER_MS;
|
||||||
|
} else {
|
||||||
|
// Common path: small gap. At most ~10 iterations since remainder was
|
||||||
|
// < threshold (10 ms) on entry and delta adds at most one more threshold
|
||||||
|
// before exiting this branch.
|
||||||
|
while (state.remainder >= US_PER_MS) {
|
||||||
|
state.cache++;
|
||||||
|
state.remainder -= US_PER_MS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint32_t result = state.cache;
|
||||||
|
xt_wsr_ps(ps);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll-based delay that avoids ::delay() — Arduino's __delay has an intra-object
|
||||||
|
// call to the original millis() that --wrap can't intercept, so calling ::delay()
|
||||||
|
// would keep the slow Arduino millis body alive in IRAM. optimistic_yield still
|
||||||
|
// enters esp_schedule()/esp_suspend_within_cont() via yield(), so SDK tasks and
|
||||||
|
// WiFi run correctly. Theoretically less power-efficient than Arduino's
|
||||||
|
// os_timer-based delay() for long waits, but nearly all ESPHome delays are short
|
||||||
|
// (sensor/I²C/SPI settling in the 1–100 ms range) where the difference is
|
||||||
|
// negligible.
|
||||||
|
void HOT delay(uint32_t ms) {
|
||||||
|
if (ms == 0) {
|
||||||
|
optimistic_yield(1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint32_t start = millis();
|
||||||
|
while (millis() - start < ms) {
|
||||||
|
optimistic_yield(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void arch_restart() {
|
||||||
|
system_restart();
|
||||||
|
// restart() doesn't always end execution
|
||||||
|
while (true) { // NOLINT(clang-diagnostic-unreachable-code)
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
// Linker wrap: redirect all ::millis() calls (Arduino libs, ISRs) to our accumulator.
|
||||||
|
// Requires -Wl,--wrap=millis in build flags (added by __init__.py).
|
||||||
|
// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
|
||||||
|
extern "C" uint32_t IRAM_ATTR __wrap_millis() { return esphome::millis(); }
|
||||||
|
// Note: Arduino's init() registers a 60-second overflow timer for micros64().
|
||||||
|
// We leave it running — wrapping init() as a no-op would break micros64()'s
|
||||||
|
// overflow tracking, and the timer's cost is negligible (~3 μs per 60 s).
|
||||||
|
|
||||||
|
#endif // USE_ESP8266
|
||||||
+3
-4
@@ -31,11 +31,10 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|
||||||
// Cross-platform declarations. delayMicroseconds(), arch_feed_wdt(),
|
// Cross-platform declarations. delayMicroseconds(), arch_feed_wdt(),
|
||||||
// arch_get_cpu_cycle_count() vary per platform (some inline, some
|
// arch_get_cpu_cycle_count(), arch_init(), arch_get_cpu_freq_hz() vary
|
||||||
// out-of-line) so they live in hal/hal_<platform>.h.
|
// per platform (some inline, some out-of-line) so they live in
|
||||||
|
// hal/hal_<platform>.h.
|
||||||
void __attribute__((noreturn)) arch_restart();
|
void __attribute__((noreturn)) arch_restart();
|
||||||
void arch_init();
|
|
||||||
uint32_t arch_get_cpu_freq_hz();
|
|
||||||
|
|
||||||
#ifndef USE_ESP8266
|
#ifndef USE_ESP8266
|
||||||
// All non-ESP8266 platforms: PROGMEM is a no-op, so these are direct dereferences.
|
// All non-ESP8266 platforms: PROGMEM is a no-op, so these are direct dereferences.
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ __attribute__((always_inline)) inline void delayMicroseconds(uint32_t us) { dela
|
|||||||
__attribute__((always_inline)) inline void arch_feed_wdt() { esp_task_wdt_reset(); }
|
__attribute__((always_inline)) inline void arch_feed_wdt() { esp_task_wdt_reset(); }
|
||||||
__attribute__((always_inline)) inline uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); }
|
__attribute__((always_inline)) inline uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); }
|
||||||
|
|
||||||
|
void arch_init();
|
||||||
|
uint32_t arch_get_cpu_freq_hz();
|
||||||
|
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
|
|
||||||
#include <c_types.h>
|
#include <c_types.h>
|
||||||
|
#include <core_esp8266_features.h>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <pgmspace.h>
|
#include <pgmspace.h>
|
||||||
|
|
||||||
@@ -59,8 +60,11 @@ __attribute__((always_inline)) inline uint16_t progmem_read_uint16(const uint16_
|
|||||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||||
__attribute__((always_inline)) inline void delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
|
__attribute__((always_inline)) inline void delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
|
||||||
__attribute__((always_inline)) inline void arch_feed_wdt() { system_soft_wdt_feed(); }
|
__attribute__((always_inline)) inline void arch_feed_wdt() { system_soft_wdt_feed(); }
|
||||||
|
__attribute__((always_inline)) inline void arch_init() {}
|
||||||
uint32_t arch_get_cpu_cycle_count();
|
// esp_get_cycle_count() declared in <core_esp8266_features.h>; F_CPU is a
|
||||||
|
// compiler-driven macro from the ESP8266 Arduino board defs (-DF_CPU=...).
|
||||||
|
__attribute__((always_inline)) inline uint32_t arch_get_cpu_cycle_count() { return esp_get_cycle_count(); }
|
||||||
|
__attribute__((always_inline)) inline uint32_t arch_get_cpu_freq_hz() { return F_CPU; }
|
||||||
|
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ uint64_t millis_64();
|
|||||||
void delayMicroseconds(uint32_t us); // NOLINT(readability-identifier-naming)
|
void delayMicroseconds(uint32_t us); // NOLINT(readability-identifier-naming)
|
||||||
void arch_feed_wdt();
|
void arch_feed_wdt();
|
||||||
uint32_t arch_get_cpu_cycle_count();
|
uint32_t arch_get_cpu_cycle_count();
|
||||||
|
void arch_init();
|
||||||
|
uint32_t arch_get_cpu_freq_hz();
|
||||||
|
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ __attribute__((always_inline)) inline uint64_t millis_64() { return Millis64Impl
|
|||||||
void delayMicroseconds(uint32_t us); // NOLINT(readability-identifier-naming)
|
void delayMicroseconds(uint32_t us); // NOLINT(readability-identifier-naming)
|
||||||
void arch_feed_wdt();
|
void arch_feed_wdt();
|
||||||
uint32_t arch_get_cpu_cycle_count();
|
uint32_t arch_get_cpu_cycle_count();
|
||||||
|
void arch_init();
|
||||||
|
uint32_t arch_get_cpu_freq_hz();
|
||||||
|
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ __attribute__((always_inline)) inline uint64_t millis_64() { return micros_to_mi
|
|||||||
void delayMicroseconds(uint32_t us); // NOLINT(readability-identifier-naming)
|
void delayMicroseconds(uint32_t us); // NOLINT(readability-identifier-naming)
|
||||||
void arch_feed_wdt();
|
void arch_feed_wdt();
|
||||||
uint32_t arch_get_cpu_cycle_count();
|
uint32_t arch_get_cpu_cycle_count();
|
||||||
|
void arch_init();
|
||||||
|
uint32_t arch_get_cpu_freq_hz();
|
||||||
|
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ uint64_t millis_64();
|
|||||||
void delayMicroseconds(uint32_t us); // NOLINT(readability-identifier-naming)
|
void delayMicroseconds(uint32_t us); // NOLINT(readability-identifier-naming)
|
||||||
void arch_feed_wdt();
|
void arch_feed_wdt();
|
||||||
uint32_t arch_get_cpu_cycle_count();
|
uint32_t arch_get_cpu_cycle_count();
|
||||||
|
void arch_init();
|
||||||
|
uint32_t arch_get_cpu_freq_hz();
|
||||||
|
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user