FastLED 3.10.9
Loading...
Searching...
No Matches
AutoResearchEdgeProbe.cpp
Go to the documentation of this file.
1// AutoResearch edge probe implementation (FastLED#3586 tooling).
2// See AutoResearchEdgeProbe.h.
3
5
6#if SKETCH_HAS_LOTS_OF_MEMORY
7
9#include "fl/stl/singleton.h"
10
11#include "platforms/is_platform.h"
12
13#ifdef FL_IS_ESP32
14
15#include <Arduino.h>
16#include "freertos/FreeRTOS.h"
17#include "freertos/task.h"
18#include "soc/io_mux_reg.h"
19#include "soc/gpio_periph.h"
20#include "soc/gpio_reg.h"
21
22namespace {
23
24constexpr fl::u32 kMaxStamps = 200;
25volatile fl::u32 g_stamps[kMaxStamps];
26volatile fl::u32 g_count = 0;
27volatile fl::u32 g_selftest_edges = 0;
28volatile fl::u32 g_probe_state = 0; // 1=task started 2=triggered 3=sample done
29volatile fl::u32 g_rmt_live[8] = {0};
30int g_pin = -1;
31
32// Arduino's wrapper selects the SDK-compatible cycle counter, including
33// Espressif's custom counter on RISC-V and the older IDF 4.x HAL API.
34static inline fl::u32 probeCycles() {
35 return static_cast<fl::u32>(ESP.getCycleCount()); // ok platform specific - this ESP-only edge probe needs a cycle counter; FastLED has no public equivalent
36}
37
38void ARDUINO_ISR_ATTR edgeIsr() {
39 const fl::u32 i = g_count;
40 if (i < kMaxStamps) {
41 // Level BEFORE this edge = inverse of the level now.
42 const fl::u32 lvl_now = static_cast<fl::u32>(digitalRead(g_pin));
43 g_stamps[i] = ((lvl_now ^ 1u) << 31) | (probeCycles() & 0x7FFFFFFFu);
44 g_count = i + 1;
45 }
46}
47
48} // namespace
49
50void edgeProbeArm(int pin) { edgeProbeArmMode(pin, false); }
51
52void edgeProbeArmMode(int pin, bool passive) {
53 if (g_pin >= 0 && !passive) {
54 detachInterrupt(digitalPinToInterrupt(g_pin));
55 }
56 g_count = 0;
57 g_pin = pin;
58 if (!passive) {
59 // Active mode reconfigures the pad; passive mode leaves the pad
60 // completely untouched (for probing pins owned by a receiver).
61 pinMode(pin, INPUT);
62 attachInterrupt(digitalPinToInterrupt(pin), edgeIsr, CHANGE);
63 }
64 if (passive) {
65 g_selftest_edges = 0;
66 } else {
67 // Self-test: flip the pad's pull resistors to generate edges the
68 // ISR must record. Proves the interrupt path end-to-end without
69 // touching the output driver (jumpered TX pins stay unharmed).
70 for (int i = 0; i < 4; ++i) {
71 pinMode(pin, INPUT_PULLUP);
73 pinMode(pin, INPUT_PULLDOWN);
75 }
76 g_selftest_edges = g_count;
77 g_count = 0; // restart clean for the real capture
78 }
79
80 // TX drivers reconfigure the probed pad when the test starts,
81 // clearing the input-enable and the interrupt binding. Spawn a
82 // one-shot task that re-enables the INPUT stage mid-test (pure
83 // IO_MUX IE bit — the output routing is untouched) and re-attaches
84 // the interrupt, so patterns after the first get recorded.
85 xTaskCreate([](void *arg) {
86 const int pin = static_cast<int>(reinterpret_cast<intptr_t>(arg));
87 g_probe_state = 1;
88 vTaskDelay(pdMS_TO_TICKS(150));
89 PIN_INPUT_ENABLE(GPIO_PIN_MUX_REG[pin]);
90 // Triggered tight-loop run-length sampler: per-edge interrupts
91 // can't resolve sub-µs LED pulses (~4 µs ISR latency), but a
92 // spin loop reading GPIO_IN can. TX is DMA/ISR-driven, so
93 // starving lower-priority tasks doesn't stop the waveform.
94 fl::u32 mask;
95 volatile fl::u32 *in_reg;
96 if (pin < 32) {
97 mask = 1u << pin;
98 in_reg = reinterpret_cast<volatile fl::u32 *>(GPIO_IN_REG); // ok reinterpret cast - SoC GPIO input register
99 } else {
100#ifdef GPIO_IN1_REG
101 mask = 1u << (pin - 32);
102 in_reg = reinterpret_cast<volatile fl::u32 *>(GPIO_IN1_REG); // ok reinterpret cast - SoC GPIO input register (pins 32+)
103#else
104 mask = 1u << (pin & 31);
105 in_reg = reinterpret_cast<volatile fl::u32 *>(GPIO_IN_REG); // ok reinterpret cast - SoC GPIO input register
106#endif
107 }
108 const fl::u32 idle = *in_reg & mask;
109 // Wait up to ~4 s for the line to move. LED frames can be
110 // shorter than a tick, so poll in tight ~900 µs bursts with a
111 // 1-tick yield between them (a lone vTaskDelay(1) poll misses
112 // whole frames and only sees the settled DC level).
113 bool triggered = false;
114 const fl::u32 burst_cycles = edgeProbeCpuMhz() * 900u;
115 for (int t = 0; t < 2000 && !triggered; ++t) {
116 // Drivers may clear the pad's input-enable on every
117 // pattern re-init — keep re-asserting it.
118 PIN_INPUT_ENABLE(GPIO_PIN_MUX_REG[pin]);
119 const fl::u32 b0 = probeCycles();
120 const fl::u32 lvl0 = *in_reg & mask;
121 while (probeCycles() - b0 < burst_cycles) {
122 if ((*in_reg & mask) != lvl0) { triggered = true; break; }
123 }
124 if (!triggered) {
125 vTaskDelay(1);
126 }
127 }
128 if (triggered) {
129 g_probe_state = 2;
130#if defined(CONFIG_IDF_TARGET_ESP32C6)
131 // Live RMT-RX registers at the instant the waveform is on
132 // the wire (FastLED#3586).
133 g_rmt_live[0] = *reinterpret_cast<volatile fl::u32 *>(0x60006038); // ok reinterpret cast - RMT_INT_RAW
134 g_rmt_live[1] = *reinterpret_cast<volatile fl::u32 *>(0x60006030); // ok reinterpret cast - CH2STATUS
135 g_rmt_live[2] = *reinterpret_cast<volatile fl::u32 *>(0x60006034); // ok reinterpret cast - CH3STATUS
136 g_rmt_live[3] = *reinterpret_cast<volatile fl::u32 *>(0x6000601C); // ok reinterpret cast - CH2CONF1 (real RX ch)
137 g_rmt_live[6] = *reinterpret_cast<volatile fl::u32 *>(0x60091000 + 0x154 + 71 * 4); // ok reinterpret cast - FUNC71_IN_SEL (RMT_SIG_IN0)
138 g_rmt_live[7] = *reinterpret_cast<volatile fl::u32 *>(0x60091000 + 0x154 + 72 * 4); // ok reinterpret cast - FUNC72_IN_SEL (RMT_SIG_IN1)
139#endif
140 fl::u32 idx = 0;
141 fl::u32 prev = *in_reg & mask;
142 fl::u32 last_edge = probeCycles();
143 const fl::u32 start = last_edge;
144 const fl::u32 budget = edgeProbeCpuMhz() * 1000u * 8u; // 8 ms
145 for (;;) {
146 const fl::u32 now = probeCycles();
147 if (now - start > budget) break;
148 const fl::u32 cur = *in_reg & mask;
149 if (cur != prev) {
150 if (idx < kMaxStamps) {
151 g_stamps[idx] = ((prev ? 1u : 0u) << 31) | (now & 0x7FFFFFFFu);
152 ++idx;
153 }
154 last_edge = now;
155 prev = cur;
156 }
157 }
158 g_count = idx;
159#if defined(CONFIG_IDF_TARGET_ESP32C6)
160 // Second snapshot ~8 ms later (post-frame): did the RMT
161 // receiver's interrupt lines fire at all during the frame?
162 g_rmt_live[4] = *reinterpret_cast<volatile fl::u32 *>(0x60006038); // ok reinterpret cast - RMT_INT_RAW post
163 g_rmt_live[5] = *reinterpret_cast<volatile fl::u32 *>(0x60006030); // ok reinterpret cast - CH2STATUS post
164#endif
165 g_probe_state = 3;
166 }
167 vTaskDelete(nullptr);
168 }, "edgeprobe_rearm", 3072, reinterpret_cast<void *>(static_cast<intptr_t>(pin)), 20, nullptr);
169}
170
171const fl::u32 *edgeProbeStamps(fl::u32 &count) {
172 count = g_count;
173 return const_cast<const fl::u32 *>(g_stamps);
174}
175
176fl::u32 edgeProbeSelfTestEdges() { return g_selftest_edges; }
177
178fl::u32 edgeProbeState() { return g_probe_state; }
179
180const fl::u32 *edgeProbeRmtLive() { return const_cast<const fl::u32 *>(g_rmt_live); }
181
182fl::u32 edgeProbeCpuMhz() {
183 return static_cast<fl::u32>(getCpuFrequencyMhz());
184}
185
186#else // !FL_IS_ESP32
187
188void edgeProbeArm(int) {}
189void edgeProbeArmMode(int, bool) {}
190const fl::u32 *edgeProbeStamps(fl::u32 &count) {
191 count = 0;
192 return nullptr;
193}
194fl::u32 edgeProbeCpuMhz() { return 1; }
195fl::u32 edgeProbeSelfTestEdges() { return 0; }
196fl::u32 edgeProbeState() { return 0; }
197struct EdgeProbeNoopState {
198 fl::u32 rmt_live[4];
199};
200const fl::u32 *edgeProbeRmtLive() {
202}
203
204#endif // FL_IS_ESP32
205
206#endif // SKETCH_HAS_LOTS_OF_MEMORY
bool triggered
Definition simple.h:94
const fl::u32 * edgeProbeStamps(fl::u32 &count)
Timestamp buffer: entry = (level_before_edge << 31) | cycle_count.
fl::u32 edgeProbeState()
Probe task progress: 0=not started 1=started 2=triggered 3=sampled.
const fl::u32 * edgeProbeRmtLive()
RMT registers snapshotted at trigger time (INT_RAW, CH2STATUS, CH3STATUS, CH2_RX_CONF1).
fl::u32 edgeProbeSelfTestEdges()
Edges recorded by the arm-time pull-toggle self-test.
void edgeProbeArmMode(int pin, bool passive)
Passive variant: no pinMode/attachInterrupt/pull toggles — only the triggered tight-loop sampler task...
fl::u32 edgeProbeCpuMhz()
CPU frequency in MHz for cycle→ns conversion.
void edgeProbeArm(int pin)
Arm the probe on a pin (re-arming resets the buffer).
static T & instance() FL_NO_EXCEPT
Definition singleton.h:41
static uint32_t t
Definition Luminova.h:55
fl::iptr intptr_t
Definition s16x16x4.h:225
PinValue digitalRead(int pin) FL_NO_EXCEPT
Read digital value from pin.
Definition pin.cpp.hpp:55
void pinMode(int pin, PinMode mode) FL_NO_EXCEPT
Set pin mode (input, output, pull-up, pull-down)
Definition pin.cpp.hpp:398
void delayMicroseconds(u32 us) FL_NO_EXCEPT
Delay for a given number of microseconds.