FastLED 3.10.6
Loading...
Searching...
No Matches
AutoResearchPwmDmaClockless.h
Go to the documentation of this file.
1// AutoResearchPwmDmaClockless.h — LPC845 SCT+DMA channels-API engine
2// AutoResearch validation harness (FastLED #3468).
3//
4// Device-side RPC handlers that exercise the `ChannelEngineLpcSctDma`
5// engine (from #3459 / #3460 / #3466) on real LPC845-BRK silicon and
6// self-loopback the output through `LpcSctRxChannel` for byte-level
7// verification.
8//
9// Gated on `FL_IS_ARM_LPC_845 && FASTLED_LPC_PWM_DMA`. The whole file
10// compiles out on any other build target.
11//
12// **Sibling of `AutoResearchSpiDma.h`** (#3456) — same architectural
13// shape, different peripheral.
14//
15// Handlers registered (call `autoresearch::pwm_dma_cl::bind(remote)` from
16// AutoResearch.ino when `FASTLED_LPC_PWM_DMA` is defined):
17//
18// `pwmDmaClFrameOnce(led_count, color_rgb, data_pin)`
19// Allocate a `CRGB[led_count]` filled with `color_rgb`, drive one
20// frame through the channels-API engine, return `{started_us,
21// done_us, cpu_busy_during_tx}`.
22//
23// `pwmDmaClFrameBurst(led_count, color_rgb, n_frames, data_pin)`
24// Drive `n_frames` back-to-back and report measured FPS + inter-
25// frame guard-band violations.
26//
27// `pwmDmaClCaptureSelf(led_count, color_rgb, data_pin, rx_pin,
28// capture_ms)`
29// Drive one frame AND capture the wire via `LpcSctRxChannel` on
30// `rx_pin`. Returns the decoded byte sequence for host-side
31// byte-for-byte comparison against the input color pattern. This
32// is the on-silicon equivalent of the host TX→RX readback test in
33// `tests/fl/channels/lpc_sct_dma_engine.cpp`.
34//
35// Loopback wiring (see #3468 for the sketch): single jumper from
36// `data_pin` to `rx_pin`. Any two PIO0_* pins that the SWM allows to
37// route to the SCT input mux.
38
39#pragma once
40
41#include <FastLED.h>
42
43#if defined(FL_IS_ARM_LPC_845) && defined(FASTLED_LPC_PWM_DMA)
44
45#include "fl/channels/data.h"
46#include "fl/channels/bus.h"
48#include "fl/channels/rx.h"
53#include "fl/remote/remote.h"
54#include "fl/stl/cstring.h"
55#include "fl/stl/noexcept.h"
56#include "fl/stl/singleton.h"
57#include "fl/stl/sstream.h"
58#include "fl/stl/vector.h"
59#include "platforms/arm/lpc/drivers/sct_dma/channel_engine_lpc_sct_dma.h" // ok platform headers — AutoResearch driver-specific test needs the concrete engine type
60// The LPC BusTraits<Bus::BIT_BANG> specialization this file names. It used to
61// arrive only via clockless_channel_lpc.h, which is skipped when another
62// ClocklessController was already picked, leaving the type incomplete.
63#include "platforms/arm/lpc/drivers/sct_dma/bus_traits.h" // ok platform headers — same reason
64
65namespace autoresearch {
66namespace pwm_dma_cl {
67
68struct PwmDmaClocklessState {
69 fl::u8 decoded[128];
70};
71
72inline PwmDmaClocklessState& pwmDmaClocklessState() FL_NO_EXCEPT {
74}
75
76// Standard WS2812B timing. Callers can override per-frame if the engine
77// gains multi-chipset support later; today the engine's `canHandle()`
78// filter accepts anything in [1000, 2500] ns total period, so any
79// WS2812-class chipset works.
80inline fl::ChipsetTimingConfig ws2812b_timing() FL_NO_EXCEPT {
81 return fl::ChipsetTimingConfig(/*T1=*/350, /*T2=*/450, /*T3=*/450,
82 /*reset_us=*/50, "WS2812B");
83}
84
85// Decoder timings for the self-loopback capture path. Matches the
86// tolerance window used by `rx_sct_capture.cpp`.
87inline fl::ChipsetTiming4Phase ws2812b_decoder_timing() FL_NO_EXCEPT {
88 fl::ChipsetTiming4Phase t{};
89 t.t0h_min_ns = 250; t.t0h_max_ns = 550;
90 t.t0l_min_ns = 700; t.t0l_max_ns = 1000;
91 t.t1h_min_ns = 650; t.t1h_max_ns = 950;
92 t.t1l_min_ns = 300; t.t1l_max_ns = 600;
93 t.reset_min_us = 50;
94 t.gap_tolerance_ns = 0;
95 return t;
96}
97
98// Build a `ChannelData` filled with N LEDs of a single GRB color.
99// Colors passed from the host as 24-bit RGB; we swap to GRB (WS2812
100// wire order) inside so the LED count matches the raw byte layout.
101inline fl::ChannelDataPtr makeSolidChannelData(
102 int pin, int num_leds, fl::u32 rgb) FL_NO_EXCEPT {
103 fl::ChipsetTimingConfig t = ws2812b_timing();
104 fl::vector_psram<fl::u8> encoded(static_cast<fl::size>(num_leds * 3));
105 const fl::u8 r = static_cast<fl::u8>((rgb >> 16) & 0xFF);
106 const fl::u8 g = static_cast<fl::u8>((rgb >> 8) & 0xFF);
107 const fl::u8 b = static_cast<fl::u8>((rgb >> 0) & 0xFF);
108 for (int i = 0; i < num_leds; ++i) {
109 encoded[i * 3 + 0] = g; // WS2812 GRB
110 encoded[i * 3 + 1] = r;
111 encoded[i * 3 + 2] = b;
112 }
113 return fl::ChannelData::create(pin, t, fl::move(encoded));
114}
115
116// Drive one frame through the channels-API engine. Returns
117// "success,started_us,done_us,duration_us" as a CSV string.
118inline fl::string frameOnceHandler(int led_count, fl::u32 rgb, int data_pin) FL_NO_EXCEPT {
119 if (led_count <= 0 || led_count > 300 || data_pin < 0 || data_pin > 31) {
120 return fl::string("0,0,0,0");
121 }
122 auto& engine = fl::BusTraits<fl::Bus::BIT_BANG>::instance();
123 auto ch = makeSolidChannelData(data_pin, led_count, rgb);
124 if (!engine.canHandle(ch)) {
125 return fl::string("0,0,0,0");
126 }
127
128 const fl::u32 t_start = micros();
129 engine.enqueue(ch);
130 engine.show();
131 // Spin poll() until READY. show() blocks in transmit() today
132 // (busy-wait per chunk), so this is really a state-machine settle
133 // check rather than a wait. When the WFI TODO(2842) lands this
134 // becomes the actual completion wait.
135 while (engine.poll() != fl::IChannelDriver::DriverState::READY) {
136 // spin
137 }
138 const fl::u32 t_end = micros();
139 const fl::u32 dur_us = t_end - t_start;
140
141 fl::sstream s;
142 s << 1 << ',' << t_start << ',' << t_end << ',' << dur_us;
143 return s.str();
144}
145
146// Drive `n_frames` back-to-back. Returns "success,total_us,fps_x100" as CSV.
147inline fl::string frameBurstHandler(int led_count, fl::u32 rgb,
148 int n_frames, int data_pin) FL_NO_EXCEPT {
149 if (led_count <= 0 || led_count > 300 || n_frames <= 0 ||
150 n_frames > 1000 || data_pin < 0 || data_pin > 31) {
151 return fl::string("0,0,0");
152 }
153 auto& engine = fl::BusTraits<fl::Bus::BIT_BANG>::instance();
154 auto ch = makeSolidChannelData(data_pin, led_count, rgb);
155 if (!engine.canHandle(ch)) {
156 return fl::string("0,0,0");
157 }
158
159 const fl::u32 t_start = micros();
160 for (int i = 0; i < n_frames; ++i) {
161 engine.enqueue(ch);
162 engine.show();
163 while (engine.poll() != fl::IChannelDriver::DriverState::READY) {
164 // spin
165 }
166 // Feed the watchdog every N frames so long bursts don't reset us.
167 if ((i & 0x3F) == 0) {
169 }
170 }
171 const fl::u32 t_end = micros();
172 const fl::u32 total_us = t_end - t_start;
173 // fps × 100 to keep two decimals of precision in an integer CSV.
174 const fl::u32 fps_x100 = total_us > 0
175 ? (static_cast<fl::u32>(n_frames) * 100u * 1000000u / total_us)
176 : 0u;
177
178 fl::sstream s;
179 s << 1 << ',' << total_us << ',' << fps_x100;
180 return s.str();
181}
182
183// Drive one frame AND capture via `LpcSctRxChannel`. Returns the
184// decoded bytes as a comma-separated hex string prefixed with the
185// success flag: "1,ff,00,00,ff,00,00,..." (or "0,..." on failure).
186//
187// This is the on-silicon TX→RX readback that closes the #3468 goal
188// on hardware. The host tests in `tests/fl/channels/lpc_sct_dma_engine.cpp`
189// prove the byte-flow at the mock-transmitter layer; this handler
190// proves it on real silicon end-to-end.
191inline fl::string captureSelfHandler(int led_count, fl::u32 rgb,
192 int data_pin, int rx_pin,
193 int capture_ms) FL_NO_EXCEPT {
194 if (led_count <= 0 || led_count > 32 || data_pin < 0 || data_pin > 31 ||
195 rx_pin < 0 || rx_pin > 31 || capture_ms <= 0 || capture_ms > 200) {
196 return fl::string("0");
197 }
198
199 // Set up the RX device first so the SCT input capture is armed
200 // before the TX kicks. buffer_size sized for LED count × 3 bytes ×
201 // 16 edges per byte + reset margin.
202 auto rx = fl::LpcSctRxChannel::create(rx_pin);
203 if (!rx) return fl::string("0");
204 fl::RxConfig cfg;
205 cfg.buffer_size = static_cast<fl::u32>(led_count * 3 * 16 + 32);
206 cfg.start_low = true;
207 if (!rx->begin(cfg)) return fl::string("0");
208
209 // Drive the frame.
210 auto& engine = fl::BusTraits<fl::Bus::BIT_BANG>::instance();
211 auto ch = makeSolidChannelData(data_pin, led_count, rgb);
212 if (!engine.canHandle(ch)) return fl::string("0");
213 engine.enqueue(ch);
214 engine.show();
215 while (engine.poll() != fl::IChannelDriver::DriverState::READY) {
216 // spin
217 }
218
219 // Wait for capture to settle. The SCT DMA path fills its ring
220 // autonomously; wait() drains + halts the SCT.
221 (void)rx->wait(static_cast<fl::u32>(capture_ms));
222
223 // Decode captured edges → GRB bytes.
224 const fl::size decoded_cap = static_cast<fl::size>(led_count * 3);
225 fl::u8* decoded = pwmDmaClocklessState().decoded;
226 if (decoded_cap > sizeof(decoded)) return fl::string("0");
227 auto result = rx->decode(ws2812b_decoder_timing(),
228 fl::span<fl::u8>(decoded, decoded_cap));
229 if (!result.ok()) return fl::string("0");
230 const fl::u32 n_decoded = result.value();
231
232 // Emit "1,<n>,<hex bytes>". The host phase parses and byte-compares
233 // against the driven `rgb` (mapped to GRB per LED).
234 fl::sstream s;
235 s << 1 << ',' << n_decoded;
236 for (fl::u32 i = 0; i < n_decoded; ++i) {
237 s << ',' << static_cast<fl::u32>(decoded[i]);
238 }
239 return s.str();
240}
241
242// Bind the three handlers on the passed-in `Remote`. Call from
243// AutoResearch.ino under `#if defined(FASTLED_LPC_PWM_DMA)`.
244inline void bind(fl::Remote& remote) FL_NO_EXCEPT {
245 remote.bind("pwmDmaClFrameOnce",
246 [](int led_count, fl::u32 rgb, int data_pin) -> fl::string {
247 return frameOnceHandler(led_count, rgb, data_pin);
248 });
249 remote.bind("pwmDmaClFrameBurst",
250 [](int led_count, fl::u32 rgb, int n_frames, int data_pin) -> fl::string {
251 return frameBurstHandler(led_count, rgb, n_frames, data_pin);
252 });
253 remote.bind("pwmDmaClCaptureSelf",
254 [](int led_count, fl::u32 rgb, int data_pin, int rx_pin,
255 int capture_ms) -> fl::string {
256 return captureSelfHandler(led_count, rgb, data_pin, rx_pin,
257 capture_ms);
258 });
259}
260
261} // namespace pwm_dma_cl
262} // namespace autoresearch
263
264#endif // FL_IS_ARM_LPC_845 && FASTLED_LPC_PWM_DMA
fl::unique_ptr< fl::Remote > remote
Definition RpcClient.ino:43
Portable compile-time identifiers for LED channel buses.
Per-driver traits keyed on fl::Bus.
Runtime chipset timing configuration for clockless LED drivers.
static ChannelDataPtr create(const ChipsetVariant &chipset, fl::vector_psram< u8 > &&encodedData=fl::vector_psram< u8 >(), ChannelPixelFormat pixelFormat=ChannelPixelFormat::RGB) FL_NO_EXCEPT
Create channel transmission data (modern variant-based API)
Definition data.cpp.hpp:12
static T & instance() FL_NO_EXCEPT
Definition singleton.h:81
static Watchdog & instance() FL_NO_EXCEPT
void feed() FL_NO_EXCEPT
string str() const FL_NO_EXCEPT
Definition strstream.h:43
Channel transmission data - lightweight DTO for driver transmission.
static uint32_t t
Definition Luminova.h:55
unsigned char u8
Definition stdint.h:131
constexpr remove_reference< T >::type && move(T &&t) FL_NO_EXCEPT
Definition move.h:28
fl::u32 micros()
Universal microsecond timer - returns microseconds since system startup.
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
InputGamut g
Definition rgbw.h:124
Common RX interfaces and shared types.
#define FL_NO_EXCEPT
@ READY
Hardware idle; ready to accept new transmissions.
Definition driver.h:170
size_t buffer_size
Buffer size in symbols/edges (default: 512)
Definition rx.h:224
bool start_low
Pin idle state: true=LOW (WS2812B), false=HIGH (inverted)
Definition rx.h:231
Compile-time timing extraction from ChipsetTiming structs.