FastLED 3.10.8
Loading...
Searching...
No Matches
device.cpp.hpp
Go to the documentation of this file.
1#include "fl/channels/spi.h"
3#include "fl/log/log.h"
4#include "fl/log/log.h"
5#include "fl/stl/chrono.h"
6// IWYU pragma: begin_keep
7#include "platforms/shared/spi_manager.h" // ok platform headers
8#include "platforms/shared/spi_hw_1.h" // ok platform headers
9#include "platforms/shared/spi_hw_2.h" // ok platform headers
10#include "platforms/shared/spi_hw_4.h" // ok platform headers
11#include "fl/stl/noexcept.h"
12
13// IWYU pragma: end_keep
14namespace fl {
15namespace spi {
16
17// ============================================================================
18// Device Implementation
19// ============================================================================
20
21Device::Device(const Config& config)
23 FL_LOG_SPI("SPI Device: Created with clock=" << config.clock_pin << " data_pins.size()=" << config.data_pins.size());
24}
25
27 FL_LOG_SPI("SPI Device: Destructor called");
28 if (pImpl && pImpl->initialized) {
29 FL_LOG_SPI("SPI Device: Calling end() from destructor");
30 end();
31 }
32
33 FL_LOG_SPI("SPI Device: Checking owned hardware backend");
34 // Clean up owned hardware backend (for SINGLE_SPI mode)
35 // Note: hw_backend is now a shared_ptr, so cleanup is automatic
36 if (pImpl && pImpl->owns_backend && pImpl->hw_backend) {
37 FL_LOG_SPI("SPI Device: Cleaning up owned hardware backend");
38 pImpl->hw_backend->end();
39 pImpl->hw_backend = nullptr;
40 }
41 FL_LOG_SPI("SPI Device: Destructor complete");
42}
43
45 if (!pImpl) {
46 return fl::task::Error("Device not initialized");
47 }
48
49 if (pImpl->initialized) {
50 // Already initialized - idempotent
51 return fl::nullopt;
52 }
53
54 // Validate SPI mode (0-3 for CPOL/CPHA combinations)
55 if (pImpl->config.spi_mode > 3) {
56 FL_WARN("SPI Device: Invalid SPI mode " << pImpl->config.spi_mode << " (must be 0-3)");
57 return fl::task::Error("Invalid SPI mode");
58 }
59
60 // Note: SPI mode configuration is not yet supported by the hardware layer
61 // All devices currently operate in mode 0 (CPOL=0, CPHA=0)
62 if (pImpl->config.spi_mode != 0) {
63 FL_WARN("SPI Device: SPI mode " << pImpl->config.spi_mode << " requested but hardware layer only supports mode 0 - ignoring");
64 }
65
66 // Register with SPIBusManager
67 SPIBusManager& mgr = getSPIBusManager();
68 int data_pin = pImpl->config.data_pins.empty() ? -1 : pImpl->config.data_pins[0];
69 pImpl->bus_handle = mgr.registerDevice(
70 pImpl->config.clock_pin,
71 data_pin,
72 pImpl->config.clock_speed_hz,
73 this // controller pointer
74 );
75
76 if (!pImpl->bus_handle.is_valid) {
77 FL_WARN("SPI Device: Failed to register with bus manager");
78 return fl::task::Error("Failed to register with bus manager");
79 }
80
81 // Initialize the bus
82 if (!mgr.initialize()) {
83 FL_WARN("SPI Device: Bus initialization failed");
84 return fl::task::Error("Bus initialization failed");
85 }
86
87 // Check if we need to create our own SpiHw1 controller (SINGLE_SPI/passthrough mode)
88 const SPIBusInfo* bus_info = mgr.getBusInfo(pImpl->bus_handle.bus_id);
89 if (bus_info && bus_info->bus_type == SPIBusType::SINGLE_SPI && !bus_info->hw_controller) {
90 // Create and initialize a SpiHw1 controller
91 const fl::vector<fl::shared_ptr<SpiHw1>>& controllers = SpiHw1::getAll();
92 if (controllers.empty()) {
93 FL_WARN("SPI Device: No SpiHw1 controllers available on this platform");
94 return fl::task::Error("No SpiHw1 controllers available");
95 }
96
97 // Use the first available controller (could be improved with bus number selection)
98 fl::shared_ptr<SpiHw1> hw = controllers[0];
99
100 SpiHw1::Config hw_config;
101 hw_config.clock_pin = pImpl->config.clock_pin;
102 hw_config.data_pin = pImpl->config.data_pins.empty() ? -1 : pImpl->config.data_pins[0];
103 hw_config.clock_speed_hz = pImpl->config.clock_speed_hz;
104 hw_config.bus_num = 0; // Default to bus 0
105
106 if (!hw->begin(hw_config)) {
107 FL_WARN("SPI Device: Failed to initialize SpiHw1 controller");
108 return fl::task::Error("Failed to initialize SpiHw1");
109 }
110
111 pImpl->hw_backend = hw;
112 pImpl->owns_backend = false; // We don't own it (it's from the static pool)
113 FL_LOG_SPI("SPI Device: Created SpiHw1 controller for SINGLE_SPI mode");
114 } else {
115 // Multi-lane or hardware controller already exists
116 pImpl->hw_backend = bus_info ? bus_info->hw_controller : nullptr;
117 pImpl->owns_backend = false;
118 }
119
120 pImpl->initialized = true;
121 FL_LOG_SPI("SPI Device: Initialized successfully");
122 return fl::nullopt;
123}
124
126 if (!pImpl || !pImpl->initialized) {
127 return;
128 }
129
130 // Wait for any pending operations
131 waitComplete();
132
133 // Note: Do NOT call hw->end() here!
134 // The SPIBusManager will call releaseBusHardware() (which calls hw->end())
135 // when the last device on this bus is unregistered below.
136 // Calling hw->end() here would result in calling end() twice on the same hardware.
137 pImpl->hw_backend = nullptr;
138
139 // Unregister from bus manager
140 if (pImpl->bus_handle.is_valid) {
141 SPIBusManager& mgr = getSPIBusManager();
142 mgr.unregisterDevice(pImpl->bus_handle);
143 pImpl->bus_handle = SPIBusHandle();
144 }
145
146 pImpl->initialized = false;
147 FL_LOG_SPI("SPI Device: Shutdown complete");
148}
149
151 return pImpl && pImpl->initialized;
152}
153
154// ============================================================================
155// Unsupported RX Operations (TX-Only SPI Design)
156// ============================================================================
157
158// Commented out - methods not declared in header
159// fl::optional<fl::task::Error> Device::read(uint8_t* buffer, size_t size) {
160// FL_UNUSED(buffer);
161// FL_UNUSED(size);
162//
163// FL_WARN("SPI Device: read() not supported - FastLED uses TX-only SPI for LED strips");
164// return fl::task::Error(SPIError::NOT_SUPPORTED,
165// "Read operations not supported - LED strips are TX-only (no MISO/readback)");
166// }
167
168// fl::optional<fl::task::Error> Device::transfer(const uint8_t* tx_data, uint8_t* rx_buffer, size_t size) {
169// FL_UNUSED(tx_data);
170// FL_UNUSED(rx_buffer);
171// FL_UNUSED(size);
172//
173// FL_WARN("SPI Device: transfer() not supported - FastLED uses TX-only SPI. Use writeAsync() instead.");
174// return fl::task::Error(SPIError::NOT_SUPPORTED,
175// "Full-duplex transfer not supported - LED strips are TX-only. Use writeAsync() for transmission.");
176// }
177
178// ============================================================================
179// Transaction API (Primary Interface)
180// ============================================================================
181
183 if (!isReady()) {
184 return Result<Transaction>::failure(SPIError::NOT_INITIALIZED, "Device not initialized");
185 }
186
187 if (!data || size == 0) {
188 return Result<Transaction>::failure(SPIError::ALLOCATION_FAILED, "Invalid data or size");
189 }
190
191 // Check if an async operation is already in progress
192 if (pImpl->async_state.active) {
193 return Result<Transaction>::failure(SPIError::BUSY, "Another async operation is in progress");
194 }
195
196 // Acquire DMA buffer
197 DMABuffer buffer = acquireBuffer(size);
198 if (!buffer.ok()) {
199 FL_WARN("SPI Device: Failed to acquire DMA buffer for async write");
200 return Result<Transaction>::failure(buffer.error(), "Failed to acquire DMA buffer");
201 }
202
203 // Copy data to DMA buffer
204 fl::span<u8> buf_span = buffer.data();
205 if (buf_span.size() < size) {
206 FL_WARN("SPI Device: Buffer size mismatch");
207 return Result<Transaction>::failure(SPIError::BUFFER_TOO_LARGE, "Buffer size mismatch");
208 }
209
210 for (size_t i = 0; i < size; i++) {
211 buf_span[i] = data[i];
212 }
213
214 // Start async transmission
215 fl::optional<fl::task::Error> tx_result = transmit(buffer, true); // true = async
216 if (tx_result) { // If error is present
217 FL_WARN("SPI Device: Failed to start async transmission");
218 return Result<Transaction>::failure(SPIError::NOT_SUPPORTED, tx_result->message.c_str());
219 }
220
221 // Store async state
222 pImpl->async_state.active = true;
223 pImpl->async_state.tx_buffer = data;
224 pImpl->async_state.rx_buffer = nullptr;
225 pImpl->async_state.size = size;
226 pImpl->async_state.start_time = fl::millis();
227
228 // Create Transaction object with proper initialization
229 Transaction txn;
231 txn.pImpl->completed = false; // Will complete when hardware finishes
232
233 FL_LOG_SPI("SPI Device: Async write started (" << size << " bytes)");
235}
236
237// Commented out - methods not declared in header
238// Result<Transaction> Device::readAsync(uint8_t* buffer, size_t size) {
239// FL_UNUSED(buffer);
240// FL_UNUSED(size);
241//
242// FL_WARN("SPI Device: readAsync() not supported - FastLED uses TX-only SPI for LED strips");
243// return Result<Transaction>::failure(SPIError::NOT_SUPPORTED,
244// "Async read operations not supported - LED strips are TX-only (no MISO/readback)");
245// }
246//
247// Result<Transaction> Device::transferAsync(const uint8_t* tx_data, uint8_t* rx_buffer, size_t size) {
248// FL_UNUSED(tx_data);
249// FL_UNUSED(rx_buffer);
250// FL_UNUSED(size);
251//
252// FL_WARN("SPI Device: transferAsync() not supported - FastLED uses TX-only SPI. Use writeAsync() instead.");
253// return Result<Transaction>::failure(SPIError::NOT_SUPPORTED,
254// "Async full-duplex transfer not supported - LED strips are TX-only. Use writeAsync() for transmission.");
255// }
256
257DMABuffer Device::acquireBuffer(size_t size) FL_NO_EXCEPT {
258 if (!isReady()) {
259 return DMABuffer(SPIError::NOT_INITIALIZED);
260 }
261
262 // Get hardware controller
263 if (!pImpl->hw_backend) {
264 FL_WARN("SPI Device: No hardware controller available");
265 return DMABuffer(SPIError::NOT_INITIALIZED);
266 }
267
268 // Use polymorphic interface (works for SpiHw1/2/4/8)
269 SpiHwBase* hw = pImpl->hw_backend.get();
270
271 // Always acquire a fresh buffer from hardware
272 // Note: The hardware controller may internally cache/reuse buffers
273 DMABuffer buffer = hw->acquireDMABuffer(size);
274
275 if (!buffer.ok()) {
276 FL_WARN("SPI Device: Failed to acquire DMA buffer from hardware");
277 } else {
278 FL_LOG_SPI("SPI Device: Acquired DMA buffer (" << size << " bytes)");
279 }
280
281 return buffer;
282}
283
285 if (!isReady()) {
286 return fl::task::Error("Device not initialized");
287 }
288
289 if (!buffer.ok()) {
290 return fl::task::Error("Invalid buffer");
291 }
292
293 // Get hardware controller
294 if (!pImpl->hw_backend) {
295 FL_WARN("SPI Device: No hardware controller available");
296 return fl::task::Error("No hardware controller");
297 }
298
299 // Use polymorphic interface (works for SpiHw1/2/4/8)
300 SpiHwBase* hw = pImpl->hw_backend.get();
301
302 // Start transmission
303 TransmitMode mode = async ? TransmitMode::ASYNC : TransmitMode::SYNC;
304 bool success = hw->transmit(mode);
305
306 if (!success) {
307 FL_WARN("SPI Device: Transmission failed");
308 return fl::task::Error("Transmission failed");
309 }
310
311 // If blocking mode, wait for completion
312 if (!async) {
313 if (!hw->waitComplete()) {
314 FL_WARN("SPI Device: Wait for completion failed");
315 return fl::task::Error("Wait for completion failed");
316 }
317 }
318
319 FL_LOG_SPI("SPI Device: Transmission started (" << ((async ? "async" : "blocking")) << ")");
320 return fl::nullopt;
321}
322
323bool Device::waitComplete(u32 timeout_ms) FL_NO_EXCEPT {
324 if (!isReady()) {
325 return false;
326 }
327
328 // Get hardware controller
329 if (!pImpl->hw_backend) {
330 FL_WARN("SPI Device: No hardware controller available");
331 return false;
332 }
333
334 // Use polymorphic interface (works for SpiHw1/2/4/8)
335 SpiHwBase* hw = pImpl->hw_backend.get();
336
337 return hw->waitComplete(timeout_ms);
338}
339
341 if (!isReady()) {
342 return false;
343 }
344
345 // Get hardware controller
346 if (!pImpl->hw_backend) {
347 return false;
348 }
349
350 // Use polymorphic interface (works for SpiHw1/2/4/8)
351 SpiHwBase* hw = pImpl->hw_backend.get();
352
353 return hw->isBusy();
354}
355
357 if (!pImpl) {
358 return fl::task::Error("Device not initialized");
359 }
360
361 // Update the configuration
362 pImpl->config.clock_speed_hz = speed_hz;
363
364 // Note: Runtime clock speed updates are not currently supported by the hardware layer.
365 // The new speed will take effect on the next begin() call.
366 // To apply immediately, call end() followed by begin().
367
368 if (pImpl->initialized) {
369 FL_LOG_SPI("SPI Device: Clock speed updated to " << speed_hz << " Hz (will take effect on next begin())");
370 } else {
371 FL_LOG_SPI("SPI Device: Clock speed set to " << speed_hz << " Hz");
372 }
373
374 return fl::nullopt;
375}
376
378 // Note: Caller must ensure Device is valid
379 return pImpl->config;
380}
381
382// ============================================================================
383// Transaction Implementation (Stub)
384// ============================================================================
385
387
391
393 if (this != &other) {
394 pImpl = fl::move(other.pImpl);
395 }
396 return *this;
397}
398
400 if (pImpl) {
401 // Auto-wait for completion if not already done
402 if (!pImpl->completed) {
403 wait();
404 }
405 }
406}
407
408bool Transaction::wait(u32 timeout_ms) FL_NO_EXCEPT {
409 if (!pImpl) {
410 return true; // Already completed (or invalid)
411 }
412
413 if (pImpl->completed) {
414 return true; // Already completed
415 }
416
417 if (pImpl->cancelled) {
418 pImpl->completed = true;
419 return false; // Was cancelled
420 }
421
422 // Check if device is still valid
423 if (!pImpl->device) {
424 pImpl->completed = true;
425 pImpl->result = fl::task::Error("Device pointer is null");
426 return false;
427 }
428
429 if (!pImpl->device->isReady()) {
430 pImpl->completed = true;
431 pImpl->result = fl::task::Error("Device not ready");
432 return false;
433 }
434
435#ifdef FASTLED_LOG_SPI_ENABLED
436 u32 start_time = fl::millis();
437#endif
438 // Wait for the hardware to complete
439 bool success = pImpl->device->waitComplete(timeout_ms);
440
441 if (success) {
442 // Clear async state in device
443 if (pImpl->device->pImpl) {
444 pImpl->device->pImpl->async_state.active = false;
445 }
446 pImpl->completed = true;
447 pImpl->result = fl::nullopt;
448
449#ifdef FASTLED_LOG_SPI_ENABLED
450 FL_LOG_SPI("Transaction: Completed successfully (waited " << (fl::millis() - start_time) << "ms)");
451#endif
452 return true;
453 } else {
454 // Timeout occurred
455 pImpl->completed = true;
456 pImpl->result = fl::task::Error("Transaction timeout");
457 FL_WARN("Transaction: Timeout after " << timeout_ms << "ms");
458 return false;
459 }
460}
461
463 return pImpl ? pImpl->completed : true;
464}
465
467 return pImpl ? !pImpl->completed : false;
468}
469
471 if (!pImpl || pImpl->completed) {
472 return false; // Already completed or invalid
473 }
474
475 // Note: Cancellation of in-progress DMA transfers is platform-specific
476 // and not always supported. For now, we mark it as cancelled and
477 // the next wait() call will handle it.
478
479 pImpl->cancelled = true;
480 pImpl->completed = true;
481 pImpl->result = fl::task::Error("Transaction cancelled");
482
483 // Clear async state in device
484 if (pImpl->device && pImpl->device->pImpl) {
485 pImpl->device->pImpl->async_state.active = false;
486 }
487
488 FL_LOG_SPI("Transaction: Cancelled");
489 return true;
490}
491
493 if (!pImpl) {
494 return fl::task::Error("Invalid transaction");
495 }
496 return pImpl->result;
497}
498
499} // namespace spi
500} // namespace fl
Multi-lane SPI interface for LED output Hardware (SPI_HW): 1-8 parallel data lanes Software (SPI_BITB...
FastLED chrono implementation - duration types for time measurements.
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
fl::optional< fl::task::Error > setClockSpeed(u32 speed_hz) FL_NO_EXCEPT
Update clock speed.
bool isBusy() const FL_NO_EXCEPT
Check if async operation is in progress.
const Config & getConfig() const FL_NO_EXCEPT
Get current configuration.
bool waitComplete(u32 timeout_ms=(fl::numeric_limits< u32 >::max)()) FL_NO_EXCEPT
Wait for pending async operation to complete.
bool isReady() const FL_NO_EXCEPT
Check if device is initialized and ready for use.
friend class Transaction
Definition device.h:131
Device(const Config &config) FL_NO_EXCEPT
Construct SPI device with configuration.
~Device() FL_NO_EXCEPT
Destructor - releases hardware resources.
fl::unique_ptr< Impl > pImpl
Definition device.h:134
Result< Transaction > writeAsync(const u8 *data, size_t size) FL_NO_EXCEPT
Begin asynchronous write operation (returns immediately)
void end() FL_NO_EXCEPT
Shutdown the SPI hardware and release resources.
DMABuffer acquireBuffer(size_t size) FL_NO_EXCEPT
Acquire DMA-capable buffer for zero-copy transmission.
fl::optional< fl::task::Error > begin() FL_NO_EXCEPT
Initialize the SPI hardware.
fl::optional< fl::task::Error > transmit(DMABuffer &buffer, bool async=true) FL_NO_EXCEPT
Transmit from previously acquired DMA buffer.
fl::unique_ptr< Impl > pImpl
Definition transaction.h:78
bool isDone() const FL_NO_EXCEPT
Check if transaction is complete.
~Transaction()
Destructor - automatically waits for completion.
Transaction(Transaction &&other) FL_NO_EXCEPT
bool isPending() const FL_NO_EXCEPT
Check if transaction is still in progress.
fl::optional< fl::task::Error > getResult() const FL_NO_EXCEPT
Get result of completed transaction.
Transaction() FL_NO_EXCEPT
bool wait(u32 timeout_ms=(fl::numeric_limits< u32 >::max)()) FL_NO_EXCEPT
Wait for transaction to complete.
bool cancel() FL_NO_EXCEPT
Cancel pending transaction (if supported by platform)
Transaction & operator=(Transaction &&other) FL_NO_EXCEPT
bool empty() const FL_NO_EXCEPT
#define FL_LOG_SPI(...)
Serial Peripheral Interface (SPI) logging Logs SPI configuration, initialization, and transfers.
Definition log.h:646
Private implementation details for fl::spi::Device.
#define FL_WARN(...)
Definition log.h:437
Centralized logging categories for FastLED hardware interfaces and subsystems.
constexpr remove_reference< T >::type && move(T &&t) FL_NO_EXCEPT
Definition move.h:30
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::SpiConfig Config
Definition config.h:82
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
fl::u32 millis() FL_NO_EXCEPT
Universal millisecond timer - returns milliseconds since system startup.
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
Error type for promises.
Definition promise.h:39