FastLED 3.10.8
Loading...
Searching...
No Matches
multi_lane_device.cpp.hpp
Go to the documentation of this file.
3#include "fl/log/log.h"
4#include "fl/log/log.h"
5#include "fl/stl/string.h"
6// IWYU pragma: begin_keep
7#include "platforms/shared/spi_hw_1.h" // ok platform headers
8#include "platforms/shared/spi_hw_2.h" // ok platform headers
9#include "platforms/shared/spi_hw_4.h" // ok platform headers
10#include "platforms/shared/spi_hw_8.h" // ok platform headers
11#include "platforms/shared/spi_transposer.h" // ok platform headers
12#include "fl/stl/noexcept.h"
13// IWYU pragma: end_keep
14
15namespace fl {
16namespace spi {
17
18// ============================================================================
19// Implementation Details (pImpl pattern)
20// ============================================================================
21
25 u8 backend_type; // 1, 2, 4, or 8 (number of lanes supported by backend)
26
27 Impl(const Config& cfg)
29 , config(cfg)
30 , backend_type(0) {
31
32 // Create Lane objects
33 size_t num_lanes = config.data_pins.size();
34 for (size_t i = 0; i < num_lanes; i++) {
35 lanes.push_back(Lane(i, nullptr)); // Parent set later
36 }
37 }
38
40 if (initialized && backend) {
42 }
43 }
44
46 if (!backend) return;
47
48 // Use polymorphic interface - no casting needed!
49 backend->end();
50
51 clearBackend(); // Use base class method
52 }
53};
54
55// ============================================================================
56// MultiLaneDevice Implementation
57// ============================================================================
58
61
62 // Validate configuration
63 size_t num_lanes = config.data_pins.size();
64 if (num_lanes < 1 || num_lanes > 8) {
65 FL_WARN("MultiLaneDevice: Invalid number of data pins (" << num_lanes << "), must be 1-8");
66 }
67
68 FL_DBG("MultiLaneDevice: Created with " << num_lanes << " lane(s)");
69}
70
72 if (pImpl && pImpl->initialized) {
73 end();
74 }
75}
76
78 if (!pImpl) {
79 return fl::task::Error("Device not initialized");
80 }
81
82 if (pImpl->initialized) {
83 // Already initialized - idempotent
84 return fl::nullopt;
85 }
86
87 size_t num_lanes = pImpl->config.data_pins.size();
88
89 // Validate lane count
90 if (num_lanes < 1 || num_lanes > 8) {
91 return fl::task::Error("Invalid number of lanes (must be 1-8)");
92 }
93
94 // Auto-select appropriate hardware backend based on lane count
95 if (num_lanes == 1) {
96 // Try Single-SPI (SpiHw1)
97 const auto& controllers = SpiHw1::getAll();
98 if (controllers.empty()) {
99 FL_WARN("MultiLaneDevice: No Single-SPI hardware available");
100 return fl::task::Error("Single-SPI hardware not available");
101 }
102
103 // Find first available controller
105 for (const auto& ctrl : controllers) {
106 if (!ctrl->isInitialized()) {
107 hw = ctrl;
108 break;
109 }
110 }
111
112 if (!hw) {
113 FL_WARN("MultiLaneDevice: All Single-SPI controllers in use");
114 return fl::task::Error("All Single-SPI controllers already in use");
115 }
116
117 // Configure Single-SPI
118 SpiHw1::Config hw_config;
119 hw_config.bus_num = static_cast<u8>(hw->getBusId());
120 hw_config.clock_speed_hz = pImpl->config.clock_speed_hz;
121 hw_config.clock_pin = pImpl->config.clock_pin;
122 hw_config.data_pin = pImpl->config.data_pins[0];
123
124 if (!hw->begin(hw_config)) {
125 FL_WARN("MultiLaneDevice: Failed to initialize Single-SPI hardware");
126 return fl::task::Error("Failed to initialize Single-SPI hardware");
127 }
128
129 pImpl->backend = hw;
130 pImpl->backend_type = 1;
131 FL_DBG("MultiLaneDevice: Initialized Single-SPI (" << hw->getName() << ")");
132
133 } else if (num_lanes == 2) {
134 // Try Dual-SPI (SpiHw2)
135 const auto& controllers = SpiHw2::getAll();
136 if (controllers.empty()) {
137 FL_WARN("MultiLaneDevice: No Dual-SPI hardware available");
138 return fl::task::Error("Dual-SPI hardware not available");
139 }
140
141 // Find first available controller
143 for (const auto& ctrl : controllers) {
144 if (!ctrl->isInitialized()) {
145 hw = ctrl;
146 break;
147 }
148 }
149
150 if (!hw) {
151 FL_WARN("MultiLaneDevice: All Dual-SPI controllers in use");
152 return fl::task::Error("All Dual-SPI controllers already in use");
153 }
154
155 // Configure Dual-SPI
156 SpiHw2::Config hw_config;
157 hw_config.bus_num = static_cast<u8>(hw->getBusId());
158 hw_config.clock_speed_hz = pImpl->config.clock_speed_hz;
159 hw_config.clock_pin = pImpl->config.clock_pin;
160 hw_config.data0_pin = pImpl->config.data_pins[0];
161 hw_config.data1_pin = pImpl->config.data_pins[1];
162
163 if (!hw->begin(hw_config)) {
164 FL_WARN("MultiLaneDevice: Failed to initialize Dual-SPI hardware");
165 return fl::task::Error("Failed to initialize Dual-SPI hardware");
166 }
167
168 pImpl->backend = hw;
169 pImpl->backend_type = 2;
170 FL_DBG("MultiLaneDevice: Initialized Dual-SPI (" << hw->getName() << ")");
171
172 } else if (num_lanes >= 3 && num_lanes <= 4) {
173 // Try Quad-SPI (SpiHw4)
174 const auto& controllers = SpiHw4::getAll();
175 if (controllers.empty()) {
176 FL_WARN("MultiLaneDevice: No Quad-SPI hardware available");
177 return fl::task::Error("Quad-SPI hardware not available");
178 }
179
180 // Find first available controller
182 for (const auto& ctrl : controllers) {
183 if (!ctrl->isInitialized()) {
184 hw = ctrl;
185 break;
186 }
187 }
188
189 if (!hw) {
190 FL_WARN("MultiLaneDevice: All Quad-SPI controllers in use");
191 return fl::task::Error("All Quad-SPI controllers already in use");
192 }
193
194 // Configure Quad-SPI
195 SpiHw4::Config hw_config;
196 hw_config.bus_num = static_cast<u8>(hw->getBusId());
197 hw_config.clock_speed_hz = pImpl->config.clock_speed_hz;
198 hw_config.clock_pin = pImpl->config.clock_pin;
199 hw_config.data0_pin = pImpl->config.data_pins[0];
200 hw_config.data1_pin = (num_lanes > 1) ? pImpl->config.data_pins[1] : -1;
201 hw_config.data2_pin = (num_lanes > 2) ? pImpl->config.data_pins[2] : -1;
202 hw_config.data3_pin = (num_lanes > 3) ? pImpl->config.data_pins[3] : -1;
203
204 if (!hw->begin(hw_config)) {
205 FL_WARN("MultiLaneDevice: Failed to initialize Quad-SPI hardware");
206 return fl::task::Error("Failed to initialize Quad-SPI hardware");
207 }
208
209 pImpl->backend = hw;
210 pImpl->backend_type = 4;
211 FL_DBG("MultiLaneDevice: Initialized Quad-SPI (" << hw->getName() << ")");
212
213 } else if (num_lanes >= 5 && num_lanes <= 8) {
214 // Try Octal-SPI (SpiHw8)
215 const auto& controllers = SpiHw8::getAll();
216 if (controllers.empty()) {
217 FL_WARN("MultiLaneDevice: No Octal-SPI hardware available");
218 return fl::task::Error("Octal-SPI hardware not available");
219 }
220
221 // Find first available controller
223 for (const auto& ctrl : controllers) {
224 if (!ctrl->isInitialized()) {
225 hw = ctrl;
226 break;
227 }
228 }
229
230 if (!hw) {
231 FL_WARN("MultiLaneDevice: All Octal-SPI controllers in use");
232 return fl::task::Error("All Octal-SPI controllers already in use");
233 }
234
235 // Configure Octal-SPI
236 SpiHw8::Config hw_config;
237 hw_config.bus_num = static_cast<u8>(hw->getBusId());
238 hw_config.clock_speed_hz = pImpl->config.clock_speed_hz;
239 hw_config.clock_pin = pImpl->config.clock_pin;
240 hw_config.data0_pin = pImpl->config.data_pins[0];
241 hw_config.data1_pin = (num_lanes > 1) ? pImpl->config.data_pins[1] : -1;
242 hw_config.data2_pin = (num_lanes > 2) ? pImpl->config.data_pins[2] : -1;
243 hw_config.data3_pin = (num_lanes > 3) ? pImpl->config.data_pins[3] : -1;
244 hw_config.data4_pin = (num_lanes > 4) ? pImpl->config.data_pins[4] : -1;
245 hw_config.data5_pin = (num_lanes > 5) ? pImpl->config.data_pins[5] : -1;
246 hw_config.data6_pin = (num_lanes > 6) ? pImpl->config.data_pins[6] : -1;
247 hw_config.data7_pin = (num_lanes > 7) ? pImpl->config.data_pins[7] : -1;
248
249 if (!hw->begin(hw_config)) {
250 FL_WARN("MultiLaneDevice: Failed to initialize Octal-SPI hardware");
251 return fl::task::Error("Failed to initialize Octal-SPI hardware");
252 }
253
254 pImpl->backend = hw;
255 pImpl->backend_type = 8;
256 FL_DBG("MultiLaneDevice: Initialized Octal-SPI (" << hw->getName() << ")");
257 }
258
259 pImpl->initialized = true;
260 return fl::nullopt;
261}
262
264 if (!pImpl || !pImpl->initialized) {
265 return;
266 }
267
268 // Wait for pending operations
269 waitComplete();
270
271 // Release hardware backend
272 pImpl->releaseBackend();
273
274 // Clear lane buffers
275 for (auto& lane : pImpl->lanes) {
276 lane.clear();
277 }
278
279 FL_DBG("MultiLaneDevice: Shutdown complete");
280}
281
283 return pImpl && pImpl->isReady();
284}
285
287 if (!pImpl || lane_id >= pImpl->lanes.size()) {
288 FL_WARN("MultiLaneDevice: Invalid lane ID " << lane_id);
289 // Return first lane as fallback (avoid crash)
290 static Lane dummy_lane(0, nullptr); // okay static in header
291 return dummy_lane;
292 }
293 return pImpl->lanes[lane_id];
294}
295
297 return pImpl ? pImpl->lanes.size() : 0;
298}
299
301 if (!isReady()) {
302 return Result<void>::failure(SPIError::NOT_INITIALIZED,
303 "Device not initialized");
304 }
305
306 // Find lane sizes and validate all non-empty lanes have the same size
307 size_t expected_size = 0;
308 bool found_first = false;
309
310 for (size_t i = 0; i < pImpl->lanes.size(); i++) {
311 size_t lane_size = pImpl->lanes[i].bufferSize();
312
313 if (lane_size > 0) {
314 if (!found_first) {
315 // First non-empty lane sets the expected size
316 expected_size = lane_size;
317 found_first = true;
318 } else if (lane_size != expected_size) {
319 // Size mismatch detected
320 FL_WARN("MultiLaneDevice: Lane size mismatch - expected " << expected_size << " bytes (lane 0), but lane " << i << " has " << lane_size << " bytes");
321 return Result<void>::failure(SPIError::INVALID_PARAMETER,
322 "Lane size mismatch: all lanes must have identical sizes");
323 }
324 }
325 }
326
327 if (expected_size == 0) {
328 FL_WARN("MultiLaneDevice: No data to flush (all lanes empty)");
329 return Result<void>::failure(SPIError::ALLOCATION_FAILED,
330 "No data to transmit");
331 }
332
333 // Use expected_size for DMA buffer allocation (all lanes now guaranteed same size)
334 size_t max_size = expected_size;
335
336 // Acquire DMA buffer from hardware backend - use polymorphic interface
337 DMABuffer dma_buffer = pImpl->backend->acquireDMABuffer(max_size);
338
339 if (!dma_buffer.ok()) {
340 FL_WARN("MultiLaneDevice: Failed to acquire DMA buffer");
341 return Result<void>::failure(dma_buffer.error(),
342 "Failed to acquire DMA buffer");
343 }
344
345 // Transpose lanes into DMA buffer (or copy for single lane)
346 const char* error = nullptr;
347 bool transpose_ok = false;
348
349 if (pImpl->backend_type == 1) {
350 // Single lane - no transposition needed, just copy data directly
351 if (pImpl->lanes.size() > 0) {
352 fl::span<const u8> lane_data = pImpl->lanes[0].data();
353 fl::span<u8> dma_data = dma_buffer.data();
354
355 // Verify sizes match (DMA buffer should be exactly the size we requested)
356 if (lane_data.size() != dma_data.size()) {
357 FL_WARN("MultiLaneDevice: DMA buffer size mismatch - expected " << lane_data.size() << " bytes, got " << dma_data.size() << " bytes");
358 error = "DMA buffer size mismatch";
359 transpose_ok = false;
360 } else {
361 // Copy lane data to DMA buffer
362 for (size_t i = 0; i < lane_data.size(); i++) {
363 dma_data[i] = lane_data[i];
364 }
365 transpose_ok = true;
366 }
367 } else {
368 error = "No lanes configured";
369 transpose_ok = false;
370 }
371
372 } else if (pImpl->backend_type == 2) {
373 // Dual-SPI transposition
375 if (pImpl->lanes.size() > 0) {
377 pImpl->lanes[0].data(),
378 fl::span<const u8>() // No padding
379 };
380 }
381 if (pImpl->lanes.size() > 1) {
383 pImpl->lanes[1].data(),
384 fl::span<const u8>() // No padding
385 };
386 }
387
388 transpose_ok = SPITransposer::transpose2(lane0, lane1, dma_buffer.data(), &error);
389
390 } else if (pImpl->backend_type == 4) {
391 // Quad-SPI transposition
393 for (size_t i = 0; i < pImpl->lanes.size() && i < 4; i++) {
394 lanes[i] = SPITransposer::LaneData{
395 pImpl->lanes[i].data(),
396 fl::span<const u8>() // No padding
397 };
398 }
399
400 transpose_ok = SPITransposer::transpose4(lanes[0], lanes[1], lanes[2], lanes[3],
401 dma_buffer.data(), &error);
402
403 } else if (pImpl->backend_type == 8) {
404 // Octal-SPI transposition
406 for (size_t i = 0; i < pImpl->lanes.size() && i < 8; i++) {
407 lanes[i] = SPITransposer::LaneData{
408 pImpl->lanes[i].data(),
409 fl::span<const u8>() // No padding
410 };
411 }
412
413 transpose_ok = SPITransposer::transpose8(lanes, dma_buffer.data(), &error);
414 }
415
416 if (!transpose_ok) {
417 FL_WARN("MultiLaneDevice: Transposition failed - " << ((error ? error : "unknown error")));
418 return Result<void>::failure(SPIError::ALLOCATION_FAILED,
419 error ? error : "Transposition failed");
420 }
421
422 // Transmit via hardware backend - use polymorphic interface
423 bool transmit_ok = pImpl->backend->transmit(TransmitMode::ASYNC);
424
425 if (!transmit_ok) {
426 FL_WARN("MultiLaneDevice: Hardware transmit failed");
427 return Result<void>::failure(SPIError::BUSY,
428 "Hardware transmit failed");
429 }
430
431 // Clear lane buffers after starting transmission
432 for (auto& lane : pImpl->lanes) {
433 lane.clear();
434 }
435
436 FL_DBG("MultiLaneDevice: Flushed " << pImpl->lanes.size() << " lanes (" << max_size << " bytes per lane)");
437
438 // Success - transmission started asynchronously
439 // User must call waitComplete() manually to block until transmission completes
440 return Result<void>::success();
441}
442
444 if (!isReady()) {
445 return false;
446 }
447
448 // Use polymorphic interface - no casting needed!
449 return pImpl->backend->waitComplete(timeout_ms);
450}
451
453 if (!isReady()) {
454 return false;
455 }
456
457 // Use polymorphic interface - no casting needed!
458 return pImpl->backend->isBusy();
459}
460
462 if (!isReady()) {
463 FL_WARN("MultiLaneDevice: Not ready for write");
464 return WriteResult("Device not ready");
465 }
466
467 if (lane_data.size() > pImpl->lanes.size()) {
468 FL_WARN("MultiLaneDevice: Too many lanes provided (" << lane_data.size() << " > " << pImpl->lanes.size() << ")");
469 return WriteResult("Too many lanes provided");
470 }
471
472 // Validate that all lanes have the same size (required for reliable transposition)
473 if (lane_data.size() > 1) {
474 size_t first_size = lane_data[0].size();
475 for (size_t i = 1; i < lane_data.size(); i++) {
476 if (lane_data[i].size() != first_size) {
477 FL_WARN("MultiLaneDevice: Lane size mismatch - lane 0 has " << first_size << " bytes, lane " << i << " has " << lane_data[i].size() << " bytes");
478 return WriteResult("Lane size mismatch: all lanes must have identical sizes");
479 }
480 }
481 }
482
483 // Wait for previous transmission to complete
484 waitComplete();
485
486 // Write all lane data (can be optimized later with batch operations)
487 for (size_t i = 0; i < lane_data.size(); i++) {
488 pImpl->lanes[i].write(lane_data[i].data(), lane_data[i].size());
489 }
490
491 // Start hardware transmission (async)
492 auto flush_result = flush();
493 if (!flush_result.ok()) {
494 FL_WARN("MultiLaneDevice: Flush failed");
495 return WriteResult("Flush failed");
496 }
497
498 FL_DBG("MultiLaneDevice: Wrote " << lane_data.size() << " lanes atomically (async)");
499
500 // Return success - use device->wait() to block until complete
501 return WriteResult();
502}
503
505 static const Config empty_config;
506 return pImpl ? pImpl->config : empty_config;
507}
508
509} // namespace spi
510} // namespace fl
static bool transpose8(const fl::optional< LaneData > lanes[8], fl::span< u8 > output, const char **error=nullptr) FL_NO_EXCEPT
Transpose 8 lanes of data into interleaved octal-SPI format.
static bool transpose4(const fl::optional< LaneData > &lane0, const fl::optional< LaneData > &lane1, const fl::optional< LaneData > &lane2, const fl::optional< LaneData > &lane3, fl::span< u8 > output, const char **error=nullptr) FL_NO_EXCEPT
Transpose 4 lanes of data into interleaved quad-SPI format.
static bool transpose2(const fl::optional< LaneData > &lane0, const fl::optional< LaneData > &lane1, fl::span< u8 > output, const char **error=nullptr) FL_NO_EXCEPT
Transpose 2 lanes of data into interleaved dual-SPI format.
Lane data structure: payload + padding frame.
static expected failure(E err, const char *msg=nullptr) FL_NO_EXCEPT
Create error result.
Definition expected.h:115
static expected success(T value) FL_NO_EXCEPT
Create successful result.
Definition expected.h:108
const T * data() const FL_NO_EXCEPT
Definition span.h:486
constexpr fl::size size() const FL_NO_EXCEPT
Definition span.h:483
Single lane in a multi-lane SPI device.
Definition lane.h:25
bool waitComplete(u32 timeout_ms=(fl::numeric_limits< u32 >::max)()) FL_NO_EXCEPT
Wait for pending transmission to complete.
MultiLaneDevice(const Config &config) FL_NO_EXCEPT
Construct multi-lane device.
const Config & getConfig() const FL_NO_EXCEPT
Get current configuration.
Lane & lane(size_t lane_id) FL_NO_EXCEPT
Get access to a specific lane.
Result< void > flush() FL_NO_EXCEPT
Flush all lanes (transpose and transmit)
bool isBusy() const FL_NO_EXCEPT
Check if transmission is in progress.
fl::unique_ptr< Impl > pImpl
void end() FL_NO_EXCEPT
Shutdown hardware and release resources.
size_t numLanes() const FL_NO_EXCEPT
Get number of lanes.
WriteResult writeImpl(fl::span< const fl::span< const u8 > > lane_data) FL_NO_EXCEPT
Internal implementation - write all lanes atomically.
fl::optional< fl::task::Error > begin() FL_NO_EXCEPT
Initialize hardware.
~MultiLaneDevice() FL_NO_EXCEPT
Destructor - releases hardware resources.
bool isReady() const FL_NO_EXCEPT
Check if device is initialized.
Shared implementation helpers for SPI device classes.
#define FL_WARN(...)
Definition log.h:437
#define FL_DBG
Definition log.h:553
Centralized logging categories for FastLED hardware interfaces and subsystems.
Multi-lane SPI device for 2-8 independent LED strips.
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NO_EXCEPT
Definition unique_ptr.h:263
fl::result< T, SPIError > Result
Definition transaction.h:29
unsigned char u8
Definition stdint.h:131
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
Optional< T > optional
Definition optional.h:16
bool flush(u32 timeoutMs) FL_NO_EXCEPT
constexpr nullopt_t nullopt
Definition optional.h:13
Base definition for an LED controller.
Definition crgb.hpp:179
Result of a write operation.
void clearBackend() FL_NO_EXCEPT
Clear backend state.
bool initialized
Whether hardware is initialized.
DeviceImplBase() FL_NO_EXCEPT
Constructor.
fl::shared_ptr< SpiHwBase > backend
Polymorphic SPI hardware backend (SpiHw1/2/4/8)
Configuration for multi-lane SPI.
Impl(const Config &cfg) FL_NO_EXCEPT
Error type for promises.
Definition promise.h:39