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