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