ESPHome 2026.5.0b1
Loading...
Searching...
No Matches
hal.cpp
Go to the documentation of this file.
1#ifdef USE_ESP8266
2
3#include "esphome/core/hal.h"
5
6#include <Arduino.h>
7#include <core_esp8266_features.h>
8
9extern "C" {
10#include <user_interface.h>
11}
12
13// Empty esp8266 namespace block to satisfy ci-custom's lint_namespace check.
14// HAL functions live in namespace esphome (root) — they are not part of the
15// esp8266 component's API.
16namespace esphome::esp8266 {} // namespace esphome::esp8266
17
18namespace esphome {
19
20// yield(), micros(), millis_64(), delayMicroseconds(), arch_feed_wdt(),
21// progmem_read_*() are inlined in components/esp8266/hal.h.
22//
23// Fast accumulator replacement for Arduino's millis() (~3.3 μs via 4× 64-bit
24// multiplies on the LX106). Tracks a running ms counter from 32-bit
25// system_get_time() deltas using pure 32-bit ops. Installed as __wrap_millis
26// (via -Wl,--wrap=millis) so Arduino libs and IRAM_ATTR ISR handlers (e.g.
27// Wiegand, ZyAura) also get the fast version. xt_rsil(15) guards the static
28// state against ISR re-entry; the critical section is bounded (≤10 while-loop
29// iterations, ~100 ns on the common path, or a constant-time /1000 ~2.5 μs on
30// the rare path — well under WiFi's ~10 μs ISR latency budget). NMIs (level
31// >15) are not masked, but the ESP8266 SDK's NMI handlers don't call millis().
32//
33// system_get_time() wraps every ~71.6 min; unsigned (now_us - last_us) handles
34// one wrap. The main loop calls millis() at 60+ Hz, so delta stays tiny — a
35// >71 min block would trip the watchdog long before it could matter here.
36static constexpr uint32_t MILLIS_RARE_PATH_THRESHOLD_US = 10000;
37static constexpr uint32_t US_PER_MS = 1000;
38
39uint32_t IRAM_ATTR HOT millis() {
40 // Struct packs the three statics so the compiler loads one base address
41 // instead of three separate literal pool entries (saves ~8 bytes IRAM).
42 static struct {
43 uint32_t cache;
44 uint32_t remainder;
45 uint32_t last_us;
46 } state = {0, 0, 0};
47 uint32_t ps = xt_rsil(15);
48 uint32_t now_us = system_get_time();
49 uint32_t delta = now_us - state.last_us;
50 state.last_us = now_us;
51 state.remainder += delta;
52 if (state.remainder >= MILLIS_RARE_PATH_THRESHOLD_US) {
53 // Rare path: large gap (WiFi scan, boot, long block). Constant-time
54 // conversion keeps the critical section bounded.
55 uint32_t ms = state.remainder / US_PER_MS;
56 state.cache += ms;
57 // Reuse ms instead of `remainder %= US_PER_MS` — `%` would compile to a
58 // second __umodsi3 call on the LX106 (no hardware divide).
59 state.remainder -= ms * US_PER_MS;
60 } else {
61 // Common path: small gap. At most ~10 iterations since remainder was
62 // < threshold (10 ms) on entry and delta adds at most one more threshold
63 // before exiting this branch.
64 while (state.remainder >= US_PER_MS) {
65 state.cache++;
66 state.remainder -= US_PER_MS;
67 }
68 }
69 uint32_t result = state.cache;
70 xt_wsr_ps(ps);
71 return result;
72}
73
74// Poll-based delay that avoids ::delay() — Arduino's __delay has an intra-object
75// call to the original millis() that --wrap can't intercept, so calling ::delay()
76// would keep the slow Arduino millis body alive in IRAM. optimistic_yield still
77// enters esp_schedule()/esp_suspend_within_cont() via yield(), so SDK tasks and
78// WiFi run correctly. Theoretically less power-efficient than Arduino's
79// os_timer-based delay() for long waits, but nearly all ESPHome delays are short
80// (sensor/I²C/SPI settling in the 1–100 ms range) where the difference is
81// negligible.
82void HOT delay(uint32_t ms) {
83 if (ms == 0) {
84 optimistic_yield(1000);
85 return;
86 }
87 uint32_t start = millis();
88 while (millis() - start < ms) {
89 optimistic_yield(1000);
90 }
91}
92
93void arch_restart() {
94 system_restart();
95 // restart() doesn't always end execution
96 while (true) { // NOLINT(clang-diagnostic-unreachable-code)
97 yield();
98 }
99}
100
101} // namespace esphome
102
103// Linker wrap: redirect all ::millis() calls (Arduino libs, ISRs) to our accumulator.
104// Requires -Wl,--wrap=millis in build flags (added by __init__.py).
105// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
106extern "C" uint32_t IRAM_ATTR __wrap_millis() { return esphome::millis(); }
107// Note: Arduino's init() registers a 60-second overflow timer for micros64().
108// We leave it running — wrapping init() as a no-op would break micros64()'s
109// overflow tracking, and the timer's cost is negligible (~3 μs per 60 s).
110
111#endif // USE_ESP8266
void yield(void)
uint32_t IRAM_ATTR __wrap_millis()
Definition hal.cpp:106
bool state
Definition fan.h:2
void HOT delay(uint32_t ms)
Definition hal.cpp:82
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:28
void arch_restart()
Definition hal.cpp:39
static void uint32_t