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