FastLED 3.10.6
Loading...
Searching...
No Matches
AutoResearchTimingDrift.h
Go to the documentation of this file.
1
27
28#pragma once
29
30#include "FastLED.h"
31#include "fl/stl/int.h"
32#include "fl/stl/singleton.h"
34
35#include <Arduino.h> // millis(), micros(), delay()
36
37namespace autoresearch {
38namespace timing_drift {
39
40constexpr int kMaxIterations = 64;
41constexpr int kMaxLeds = 256;
42
46
50
57 bool mRunning = false;
58
59 void start(uint32_t delay_ms) {
60 mDelay = delay_ms;
61 mStart = millis();
62 mRunning = true;
63 }
64 void stop() { mRunning = false; }
65 bool justFinished() {
66 if (mRunning && (millis() - mStart) >= mDelay) {
67 mRunning = false;
68 return true;
69 }
70 return false;
71 }
72};
73
75 bool valid_pin; // false if pin outside LegacyClocklessProxy range
76 int pin;
78 int iterations; // completed sequence count
79 uint32_t cpu_mhz; // CPU frequency during the run (0 if unknown)
80 uint32_t iter_ms[kMaxIterations]; // per-sequence wall time; index 0 is partial
81 uint32_t show_count; // FastLED.show() calls inside the fade loop
84 uint64_t show_total_us;
85};
86
87inline DriftResult run(int pin, int num_leds, int iterations) {
88 DriftResult r{};
89 r.pin = pin;
90
91 if (iterations < 1) iterations = 1;
92 if (iterations > kMaxIterations) iterations = kMaxIterations;
93 if (num_leds < 2) num_leds = 2;
94 if (num_leds > kMaxLeds) num_leds = kMaxLeds;
95 r.num_leds = num_leds;
96
98 for (int i = 0; i < num_leds; ++i) {
99 leds[i] = CRGB::Black;
100 }
101
102 // Probe whether the requested pin is dispatchable BEFORE mutating any
103 // global state — invalid pins must not clear other RPC tests' channels.
104 {
105 LegacyClocklessProxy probe(pin, leds, num_leds);
106 if (!probe.valid()) {
107 return r; // valid_pin stays false; no side effects
108 }
109 }
110
111 // Clean slate: drop any channels left over from earlier RPC tests so
112 // show() drives only the legacy controller under test.
114
115 LegacyClocklessProxy proxy(pin, leds, num_leds);
116 r.valid_pin = true;
117
118#if defined(ARDUINO_ARCH_ESP32)
119 r.cpu_mhz = getCpuFrequencyMhz();
120#endif
121
122 // sketch setup() — addLeds happened in the proxy above.
123 FastLED.setMaxRefreshRate(800);
124 FastLED.show();
125
126 constexpr uint32_t kFastLedUpdateMs = 5;
127 MillisDelay ms_fast_led;
128 MillisDelay ms_delay_post;
129
130 ms_delay_post.start(0);
131 ms_fast_led.start(kFastLedUpdateMs);
132
133 int i_post_fade = 255;
134 uint32_t i_iteration_time = millis();
135 int completed = 0;
136
137 r.show_min_us = 0xFFFFFFFFu;
138
139 auto timedShow = [&]() {
140 const uint32_t t0 = micros();
141 FastLED.show();
142 const uint32_t dt = micros() - t0;
143 r.show_count++;
144 r.show_total_us += dt;
145 if (dt < r.show_min_us) r.show_min_us = dt;
146 if (dt > r.show_max_us) r.show_max_us = dt;
147 };
148
149 auto updateLEDs = [&]() {
150 if (ms_fast_led.justFinished()) {
151 timedShow();
152 ms_fast_led.start(kFastLedUpdateMs);
153 }
154 };
155
156 const int i_ring_start = 0;
157 const int i_ring_end = num_leds - 1;
158 const int i_ring_divisor = 7;
159
160 // Each sequence runs ~2.5 s and we may queue up to kMaxIterations of
161 // them, so feed the AutoResearch watchdog (5 s timeout) on a coarse
162 // cadence to keep the device alive through long runs.
163 uint32_t last_wdt_feed_ms = millis();
164 constexpr uint32_t kWdtFeedIntervalMs = 1000;
165 auto maybeFeedWatchdog = [&]() {
166 const uint32_t now = millis();
167 if (now - last_wdt_feed_ms >= kWdtFeedIntervalMs) {
168 FastLED.watchdog().feed();
169 last_wdt_feed_ms = now;
170 }
171 };
172
173 // Transliteration of the sketch's loop(); `continue` stands in for the
174 // sketch's early `return` (Arduino re-enters loop()).
175 while (completed < iterations) {
176 if (i_post_fade > 0 && ms_delay_post.justFinished()) {
177 const int i_ring_counter =
178 ((255 - i_post_fade) / i_ring_divisor) + i_ring_start;
179
180 if (i_ring_counter <= i_ring_end) {
181 leds[i_ring_counter] = CRGB::Red;
182 }
183 if (i_ring_counter - 1 >= i_ring_start &&
184 i_ring_counter - 1 <= i_ring_end) {
185 leds[i_ring_counter - 1] = CRGB::Black;
186 }
187
188 i_post_fade--;
189
190 if (i_post_fade == 0) {
191 const uint32_t now = millis();
192 r.iter_ms[completed] = now - i_iteration_time;
193 completed++;
194 i_iteration_time = now;
195 ms_delay_post.stop();
196
197 for (int i = i_ring_start; i <= i_ring_end; i++) {
198 leds[i] = CRGB::Black;
199 }
200
201 updateLEDs();
202 FastLED.watchdog().feed(); // before the 1 s sleep
203 delay(1000);
204 FastLED.watchdog().feed(); // after the 1 s sleep
205 last_wdt_feed_ms = millis();
206 i_post_fade = 255;
207 ms_delay_post.start(225);
208 continue;
209 } else {
210 ms_delay_post.start(5);
211 }
212 }
213 updateLEDs();
214 maybeFeedWatchdog();
215 }
216
217 r.iterations = completed;
218 if (r.show_count == 0) {
219 r.show_min_us = 0;
220 }
221
222 // Teardown: restore unthrottled refresh (boot default). The proxy
223 // destructor removes the legacy controller from the draw list.
224 FastLED.setMaxRefreshRate(0);
225 return r;
226}
227
228} // namespace timing_drift
229} // namespace autoresearch
CRGB leds[1]
void updateLEDs()
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
bool valid() const
Check if the proxy was successfully created.
Proxy that creates a legacy clockless controller from runtime inputs.
static T & instance() FL_NO_EXCEPT
Definition singleton.h:81
@ CHANNELS
Remove all channels from controller list.
Definition FastLED.h:668
fl::CRGB CRGB
Definition crgb.h:25
DriftResult run(int pin, int num_leds, int iterations)
TimingDriftState & timingDriftState()
fl::u32 uint32_t
Definition stdint.h:218
Faithful emulation of the millisDelay library used by the issue sketch: start(t) arms a deadline; jus...
@ Red
<div style='background:#FF0000;width:4em;height:4em;'></div>
Definition crgb.h:629
@ Black
<div style='background:#000000;width:4em;height:4em;'></div>
Definition crgb.h:517