FastLED 3.9.15
Loading...
Searching...
No Matches
AutoResearchLowMemory.h
Go to the documentation of this file.
1#pragma once
2//
3// examples/AutoResearch/AutoResearchLowMemory.h
4//
5// Low-memory AutoResearch bring-up surface for parts whose flash budget
6// cannot fit the full ESP32 / Teensy LED-protocol matrix. Originally lived
7// as the standalone `examples/AutoResearchLpc/AutoResearchLpc.ino` sketch;
8// retired and folded in here per FastLED #3030 once the soft-FP cascade
9// from #3022 phase 2 freed up the budget on LPC8xx.
10//
11// Included from the main `AutoResearch.ino` when
12// `FASTLED_AUTORESEARCH_LOW_MEMORY` is defined (auto-defined when
13// `FL_PLATFORM_HAS_LARGE_MEMORY == 0`, i.e. Low + Tiny tiers per the
14// FastLED #3000 memory classification).
15//
16// Same JSON-RPC contract as the retired LPC sketch:
17// echo: [int] -> int
18// pinToggleRx: [tx_pin, rx_pin, freq_hz, duration_ms] -> CSV
19// ws2812SctTest: [test_case, tx_pin, rx_pin, capture_ms] -> CSV
20// See FastLED #3021 for the SCT-RX bring-up flags.
21//
22
23// Gate FL_DBG to no-op. Without `RELEASE`, fl/log/log.h auto-sets
24// FASTLED_FORCE_DBG=1, which expands every FL_DBG call site (inside
25// fl::Remote etc.) through the fl::println formatting machinery and
26// blows ~4 KB of flash budget. fbuild's nxplpc orchestrator does not
27// yet propagate platformio.ini's `build_flags`, so it's defined here.
28#ifndef RELEASE
29#define RELEASE 1
30#endif
31
32// Opt in to the SCT input-capture RX backend (FastLED #3021). With this
33// flag set, `LpcSctRxChannel::begin()` programs the SCT for hardware
34// edge-capture; without it the driver is a no-op stub (host tests still
35// work via `injectEdges()`).
36#ifndef FASTLED_LPC_RX_SCT
37#define FASTLED_LPC_RX_SCT 1
38#endif
39
40// Opt in to the SCT->DMA RX capture path. Required for WS2812 hardware
41// loopback. Auto-enabled when WS2812 mode is on.
42#if defined(FASTLED_LPC_RX_SCT_WS2812) && !defined(FASTLED_LPC_RX_SCT_DMA)
43#define FASTLED_LPC_RX_SCT_DMA 1
44#endif
45
46#include <Arduino.h>
47#include "fl/remote/remote.h"
49#include "fl/wdt/watchdog.h"
50#include "fl/log/log.h"
51#include "fl/stl/cstdio.h" // fl::serial_begin -- HWCDC-safe Serial.begin wrapper
52
53// The host-stub example DLL build (`tests/shared/example_dll_wrapper_template.cpp`)
54// compiles this sketch on Linux to surface ABI breaks. The RX SCT driver
55// types are platform-gated behind `FL_IS_ARM_LPC` -- they only exist on
56// the real LPC build. So the include + every RX usage below must be gated
57// the same way.
58#include "platforms/arm/lpc/is_lpc.h" // ok platform headers - LPC RX driver gate
59#if defined(FL_IS_ARM_LPC)
61#include "fl/stl/strstream.h"
62#endif
63
64#if defined(FASTLED_LPC_RX_SCT_WS2812)
65#include "FastLED.h"
67#endif
68
69namespace {
71
72#if defined(FL_IS_ARM_LPC)
73// Period-stat helpers for `pinToggleRx`. Mirrors the FlexIO RX benchmark
74// logic in `examples/AutoResearch/AutoResearchRemote.cpp::flexioRxBenchmark`
75// but inline so we don't pull in the AutoResearch ObjectFLED bus.
76struct LowMemPinTogglePeriodStats {
77 fl::u32 periods;
78 fl::u32 mean_ns;
79 fl::u32 sigma_ns;
80 fl::u32 min_ns;
81 fl::u32 max_ns;
82};
83
84inline LowMemPinTogglePeriodStats computeLowMemPeriodStats(
85 const fl::EdgeTime* edges, fl::size edge_count) FL_NOEXCEPT {
86 LowMemPinTogglePeriodStats s{0, 0, 0, 0, 0};
87 fl::u64 sum = 0;
88 fl::u64 sum_sq = 0;
89 fl::u32 min_ns = 0xFFFFFFFFu;
90 fl::u32 max_ns = 0;
91 fl::u32 count = 0;
92 for (fl::size i = 0; i + 1 < edge_count; i += 2) {
93 const fl::u32 period_ns =
94 static_cast<fl::u32>(edges[i].ns) +
95 static_cast<fl::u32>(edges[i + 1].ns);
96 if (period_ns == 0) continue;
97 sum += period_ns;
98 sum_sq += static_cast<fl::u64>(period_ns) * static_cast<fl::u64>(period_ns);
99 if (period_ns < min_ns) min_ns = period_ns;
100 if (period_ns > max_ns) max_ns = period_ns;
101 ++count;
102 }
103 if (count == 0) return s;
104 const fl::u32 mean = static_cast<fl::u32>(sum / static_cast<fl::u64>(count));
105 const fl::u64 mean64 = static_cast<fl::u64>(mean);
106 const fl::u64 var = (sum_sq / static_cast<fl::u64>(count)) - (mean64 * mean64);
107 fl::u32 sig = 0;
108 while (static_cast<fl::u64>(sig + 1) * static_cast<fl::u64>(sig + 1) <= var) ++sig;
109 s.periods = count;
110 s.mean_ns = mean;
111 s.sigma_ns = sig;
112 s.min_ns = (min_ns == 0xFFFFFFFFu) ? 0u : min_ns;
113 s.max_ns = max_ns;
114 return s;
115}
116#endif // FL_IS_ARM_LPC
117
118} // namespace
119
121 fl::serial_begin(115200);
122
123 // Arm the watchdog so a hung sketch unbricks itself on crash.
125
126 // Emit a literal-only warning so the autoresearch harness can verify
127 // the FL_WARN log pipeline reaches the host.
128 FL_WARN_LIT("FL_WARN: low-memory bring-up OK");
129
130 // The C++ inline-ODR rule guarantees a single shared instance across
131 // every translation unit that calls this inline function, which is
132 // exactly what we want (one Remote bound to the singleton Serial
133 // transport). The included-once-per-sketch model of `.ino` builds
134 // also reduces to a single TU in practice.
135 static fl::Remote remote( // okay static in header
137 fl::createSerialResponseSink("REMOTE: "));
139 remote.bind("echo", [](int v) -> int {
140 FL_WARN_LIT("FL_WARN: echo invoked");
141 return v;
142 });
143
144#if defined(FL_IS_ARM_LPC)
145 // pinToggleRx (FastLED #3021 Phase 1) — bit-bang square wave on tx_pin
146 // and capture SCT edges on rx_pin. CSV stats out.
147 remote.bind("pinToggleRx",
148 [](int tx_pin, int rx_pin, int freq_hz, int duration_ms) -> fl::string {
149 if (tx_pin < 0 || rx_pin < 0 || freq_hz <= 0 || duration_ms <= 0) {
150 return fl::string("0,0,0,0,0,0,0");
151 }
152 const fl::u32 expected_edges =
153 2u * static_cast<fl::u32>(freq_hz) *
154 static_cast<fl::u32>(duration_ms) / 1000u;
155 fl::u32 cap = expected_edges + (expected_edges / 2u);
156 if (cap < 256u) cap = 256u;
157 if (cap > 2048u) cap = 2048u;
158
159 auto rx = fl::LpcSctRxChannel::create(rx_pin);
160 if (!rx) return fl::string("0,0,0,0,0,0,0");
161
162 fl::RxConfig cfg;
163 cfg.buffer_size = cap;
164 cfg.start_low = true;
165 if (!rx->begin(cfg)) return fl::string("0,0,0,0,0,0,0");
166
167 pinMode(tx_pin, OUTPUT);
168 digitalWrite(tx_pin, LOW);
169
170 const fl::u32 half_us = 500000u / static_cast<fl::u32>(freq_hz);
171 const fl::u32 cycles = static_cast<fl::u32>(freq_hz) *
172 static_cast<fl::u32>(duration_ms) / 1000u;
173
174 for (fl::u32 i = 0; i < cycles; ++i) {
175 digitalWrite(tx_pin, HIGH);
176 delayMicroseconds(half_us);
177 rx->pollOnce();
178 digitalWrite(tx_pin, LOW);
179 delayMicroseconds(half_us);
180 rx->pollOnce();
181 if ((i & 0x7Fu) == 0u) {
183 }
184 }
185 rx->wait(1);
186
187 fl::EdgeTime edges_buf[2048];
188 const fl::size n_read = rx->getRawEdgeTimes(
189 fl::span<fl::EdgeTime>(edges_buf, sizeof(edges_buf) / sizeof(edges_buf[0])));
190
191 LowMemPinTogglePeriodStats stats = computeLowMemPeriodStats(edges_buf, n_read);
192
193 fl::sstream s;
194 const bool success = (stats.periods > 0);
195 s << (success ? 1 : 0) << ',' << static_cast<fl::u32>(n_read) << ','
196 << stats.periods << ',' << stats.mean_ns << ',' << stats.sigma_ns << ','
197 << stats.min_ns << ',' << stats.max_ns;
198 return s.str();
199 });
200
201#if defined(FASTLED_LPC_RX_SCT_WS2812)
202 // ws2812SctTest (FastLED #3021 Phase 2) -- WS2812 byte-match loopback.
203 remote.bind("ws2812SctTest",
204 [](int test_case, int tx_pin, int rx_pin, int capture_ms) -> fl::string {
205 if (test_case < 0 || test_case > 4 ||
206 tx_pin < 0 || rx_pin < 0 || capture_ms <= 0) {
207 return fl::string("0,0,0,0,0,0,0,0");
208 }
209 int num_leds = 1;
210 switch (test_case) {
211 case 1: num_leds = 3; break;
212 case 4: num_leds = 100; break;
213 default: num_leds = 1; break;
214 }
215 static CRGB leds_buf[100];
216 for (int i = 0; i < num_leds; ++i) {
217 switch (test_case) {
218 case 0: leds_buf[i] = CRGB(0xFF, 0x00, 0x00); break;
219 case 1: {
220 if (i == 0) leds_buf[i] = CRGB(0xFF, 0x00, 0x00);
221 else if (i == 1) leds_buf[i] = CRGB(0x00, 0xFF, 0x00);
222 else leds_buf[i] = CRGB(0x00, 0x00, 0xFF);
223 break;
224 }
225 case 2: leds_buf[i] = CRGB(0x00, 0x00, 0x00); break;
226 case 3: leds_buf[i] = CRGB(0xFF, 0xFF, 0xFF); break;
227 case 4: {
228 const uint8_t r = (i % 3 == 0) ? 0xFFu : 0u;
229 const uint8_t g = (i % 3 == 1) ? 0xFFu : 0u;
230 const uint8_t b = (i % 3 == 2) ? 0xFFu : 0u;
231 leds_buf[i] = CRGB(r, g, b);
232 break;
233 }
234 }
235 }
236 const int expected_bytes = num_leds * 3;
237 static uint8_t expected_buf[300];
238 for (int i = 0; i < num_leds; ++i) {
239 expected_buf[i * 3 + 0] = leds_buf[i].g;
240 expected_buf[i * 3 + 1] = leds_buf[i].r;
241 expected_buf[i * 3 + 2] = leds_buf[i].b;
242 }
243 auto rx = fl::LpcSctRxChannel::create(rx_pin);
244 if (!rx) return fl::string("0,0,0,0,0,0,0,0");
245 fl::RxConfig cfg;
246 const fl::u32 expected_edges = (fl::u32)num_leds * 24u * 2u;
247 fl::u32 cap = expected_edges + (expected_edges / 2u);
248 if (cap < 256u) cap = 256u;
249 if (cap > 2048u) cap = 2048u;
250 cfg.buffer_size = cap;
251 cfg.start_low = true;
252 if (!rx->begin(cfg)) return fl::string("0,0,0,0,0,0,0,0");
253
254 constexpr int kFixedTxPin = 10; // P0_10
255 if (tx_pin != kFixedTxPin) {
256 return fl::string("0,0,0,0,0,0,0,0");
257 }
258 static bool fastled_inited = false;
259 if (!fastled_inited) {
260 FastLED.addLeds<WS2812, kFixedTxPin, GRB>(leds_buf, 100);
261 fastled_inited = true;
262 }
263 FastLED.show();
264 rx->wait(capture_ms);
265
266 static uint8_t decoded_buf[300];
267 for (int i = 0; i < expected_bytes; ++i) decoded_buf[i] = 0;
268
272 /*reset_us=*/0u);
273
274 auto dec = rx->decode(timing, fl::span<u8>(decoded_buf, expected_bytes));
275 const fl::u32 decoded_bytes = dec.ok() ? dec.value() : 0u;
276
277 fl::u32 matched = 0;
278 fl::u32 mismatched = 0;
279 for (fl::u32 i = 0; i < decoded_bytes && i < (fl::u32)expected_bytes; ++i) {
280 if (decoded_buf[i] == expected_buf[i]) ++matched;
281 else ++mismatched;
282 }
283 const fl::u32 missing = (fl::u32)expected_bytes - decoded_bytes;
284 mismatched += missing;
285
286 fl::EdgeTime probe[1024];
287 const fl::size edges_captured = rx->getRawEdgeTimes(
288 fl::span<fl::EdgeTime>(probe, 1024));
289
290 const bool success = (mismatched == 0 && decoded_bytes == (fl::u32)expected_bytes);
291
292 fl::sstream s;
293 s << (success ? 1 : 0) << ',' << test_case << ',' << num_leds << ','
294 << expected_bytes << ',' << decoded_bytes << ','
295 << matched << ',' << mismatched << ','
296 << static_cast<fl::u32>(edges_captured);
297 return s.str();
298 });
299#endif // FASTLED_LPC_RX_SCT_WS2812
300#endif // FL_IS_ARM_LPC
301}
302
306 g_low_memory_remote->update(millis());
307 }
308}
void autoResearchLowMemoryLoop()
void autoResearchLowMemorySetup()
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
fl::unique_ptr< fl::Remote > remote
Definition RpcClient.ino:43
size_t update(u32 currentTimeMs)
Main update: pull + tick + push (overrides Server::update)
JSON-RPC server with scheduling support.
Definition remote.h:40
static Watchdog & instance() FL_NOEXCEPT
void begin(fl::u32 timeout_ms) FL_NOEXCEPT
void feed() FL_NOEXCEPT
string str() const FL_NOEXCEPT
Definition strstream.h:43
constexpr EOrder GRB
Definition eorder.h:19
fl::CRGB CRGB
Definition crgb.h:25
#define FL_WARN_LIT(LITERAL)
Definition log.h:294
Centralized logging categories for FastLED hardware interfaces and subsystems.
constexpr ChipsetTiming to_runtime_timing() FL_NOEXCEPT
Convert enum-based timing type to runtime ChipsetTiming struct.
Definition led_timing.h:521
void serial_begin(u32 baudRate)
fl::function< fl::optional< fl::json >()> createSerialRequestSource(const char *prefix="")
Create a JSON-RPC RequestSource that reads from fl:: serial input.
Definition serial.h:109
ChipsetTiming4Phase make4PhaseTiming(const ChipsetTiming &timing_3phase, u32 tolerance_ns) FL_NOEXCEPT
Create 4-phase RX timing from 3-phase chipset timing with tolerance.
Definition rx.cpp.hpp:51
fl::function< void(const fl::json &)> createSerialResponseSink(const char *prefix="REMOTE: ")
Create a JSON-RPC ResponseSink that writes to fl:: serial output.
Definition serial.h:162
fl::u64 u64
Definition s16x16x4.h:221
4-phase RX timing thresholds for chipset detection
Definition rx.h:87
Public re-export of the LPC SCT-capture RX driver (FastLED#3015).
#define FL_NOEXCEPT
Universal edge timing representation (platform-agnostic)
Definition rx.h:34
size_t buffer_size
Buffer size in symbols/edges (default: 512)
Definition rx.h:218
bool start_low
Pin idle state: true=LOW (WS2812B), false=HIGH (inverted)
Definition rx.h:225
Configuration for RX device initialization.
Definition rx.h:216
Compile-time timing extraction from ChipsetTiming structs.
Unified cross-platform Watchdog Timer API for FastLED.