FastLED 3.10.9
Loading...
Searching...
No Matches
spi_channel_adapter.cpp.hpp
Go to the documentation of this file.
1
3
5#include "fl/log/log.h"
6#include "fl/log/log.h"
7#include "fl/stl/cstring.h" // For memcpy
8#include "fl/channels/config.h" // For SpiChipsetConfig
9// IWYU pragma: begin_keep
10#include "platforms/shared/spi_hw_1.h" // For SpiHw1::Config // ok platform headers
11#include "fl/stl/noexcept.h"
12// IWYU pragma: end_keep
13
14namespace fl {
15
16// Static factory method (multi-controller version)
19 fl::vector<int> priorities,
21 const char* adapterName
23 // Validation
24 if (hwControllers.empty()) {
25 FL_WARN("SpiChannelEngineAdapter::create: No controllers provided");
26 return nullptr;
27 }
28
29 if (hwControllers.size() != priorities.size() || hwControllers.size() != names.size()) {
30 FL_WARN("SpiChannelEngineAdapter::create: Size mismatch in arguments");
31 return nullptr;
32 }
33
34 if (!adapterName || adapterName[0] == '\0') {
35 FL_WARN("SpiChannelEngineAdapter::create: Empty adapter name");
36 return nullptr;
37 }
38
39 // Create adapter
40 auto adapter = fl::make_shared<SpiChannelEngineAdapter>(adapterName);
41
42 // Register all controllers
43 for (size_t i = 0; i < hwControllers.size(); i++) {
44 if (!hwControllers[i]) {
45 FL_WARN("SpiChannelEngineAdapter: Null controller at index " << i);
46 continue;
47 }
48
49 adapter->mControllers.emplace_back(hwControllers[i], priorities[i], names[i]);
50
51 FL_DBG("SpiChannelEngineAdapter: Registered controller '" << names[i] << "' (priority " << priorities[i] << ", lanes: " << (static_cast<int>(hwControllers[i]->getLaneCount())) << ")");
52 }
53
54 if (adapter->mControllers.empty()) {
55 FL_WARN("SpiChannelEngineAdapter: No valid controllers registered");
56 return nullptr;
57 }
58
59 return adapter;
60}
61
63 FL_NO_EXCEPT : mName(name)
64{
65 // Controllers added via create() factory method
66}
67
69 FL_DBG("SpiChannelEngineAdapter: Destructor for '" << mName << "'");
70
71 // Clear any enqueued channels that were never transmitted
72 // This prevents infinite loop in poll() which returns DRAINING if enqueued channels exist
73 mEnqueuedChannels.clear();
74
75 // Poll until READY to ensure cleanup of any in-flight transmissions
76 while (poll() != DriverState::READY) {
77 // Busy-wait for transmission completion
78 // This is acceptable in destructor context
79 }
80
81 // Shutdown all initialized controllers
82 for (auto& ctrl : mControllers) {
83 if (ctrl.controller && ctrl.controller->isInitialized()) {
84 ctrl.controller->end();
85 }
86 }
87}
88
90 int maxPriority = -1;
91 for (const auto& ctrl : mControllers) {
92 if (ctrl.priority > maxPriority) {
93 maxPriority = ctrl.priority;
94 }
95 }
96 return maxPriority;
97}
98
100 // Check if clock pin already assigned
101 for (const auto& assignment : mClockPinAssignments) {
102 if (assignment.clockPin == clockPin && assignment.requestedBus == requestedBus) {
103 return assignment.controllerIndex;
104 }
105 }
106
107 // Find highest priority available controller
108 int bestIndex = -1;
109 int bestPriority = -1;
110
111 for (size_t i = 0; i < mControllers.size(); i++) {
112 if (requestedBus != Esp32SpiBus::AUTO &&
113 mControllers[i].controller->getBusId() != static_cast<int>(requestedBus)) {
114 continue;
115 }
116 if (requestedBus == Esp32SpiBus::AUTO &&
118 continue;
119 }
120
121 if (mControllers[i].priority > bestPriority) {
122 bestIndex = i;
123 bestPriority = mControllers[i].priority;
124 }
125 }
126
127 // Record assignment
128 if (bestIndex >= 0) {
129 mClockPinAssignments.push_back({clockPin, requestedBus, static_cast<size_t>(bestIndex)});
130 }
131
132 return bestIndex;
133}
134
136 const ControllerInfo& ctrl, int clockPin) const FL_NO_EXCEPT {
137
138 // Uninitialized controllers can handle any pin
139 if (!ctrl.isInitialized) {
140 return true;
141 }
142
143 // Check if already handling this pin
144 for (int assignedPin : ctrl.assignedClockPins) {
145 if (assignedPin == clockPin) {
146 return true;
147 }
148 }
149
150 // ESP32 constraint: cannot re-initialize with different pins
151 return false;
152}
153
155 ControllerInfo& ctrl, int clockPin, int dataPin, u32 clockSpeedHz) FL_NO_EXCEPT {
156
157 if (ctrl.isInitialized) {
158 if (!ctrl.assignedClockPins.empty() &&
159 ctrl.assignedClockPins[0] == clockPin &&
160 ctrl.configuredDataPin == dataPin &&
161 ctrl.configuredClockSpeedHz == clockSpeedHz) {
162 return true;
163 }
164 ctrl.controller->end();
165 ctrl.isInitialized = false;
166 ctrl.assignedClockPins.clear();
167 }
168
169 // Initialize controller based on lane count
170 u8 lanes = ctrl.controller->getLaneCount();
171
172 if (lanes == 1) {
173 SpiHw1::Config config;
174 config.bus_num = static_cast<fl::u8>(ctrl.controller->getBusId());
175 config.clock_pin = clockPin;
176 config.data_pin = dataPin;
177 config.clock_speed_hz = clockSpeedHz;
178 config.max_transfer_sz = 65536;
179
180 if (!ctrl.controller->begin(&config)) {
181 FL_WARN("SpiChannelEngineAdapter: Failed to initialize " << ctrl.name);
182 return false;
183 }
184 } else {
185 // TODO: Multi-lane initialization (SpiHw2/4/8/16)
186 FL_WARN("SpiChannelEngineAdapter: Multi-lane init not yet implemented");
187 return false;
188 }
189
190 ctrl.isInitialized = true;
191 ctrl.configuredDataPin = dataPin;
192 ctrl.configuredClockSpeedHz = clockSpeedHz;
193 ctrl.assignedClockPins.push_back(clockPin);
194
195 FL_DBG("SpiChannelEngineAdapter: Initialized " << ctrl.name << " with clock pin " << clockPin);
196
197 return true;
198}
199
200bool SpiChannelEngineAdapter::canHandle(const ChannelDataPtr& data) const FL_NO_EXCEPT {
201 if (!data) {
202 return false;
203 }
204
205 // ⚠️ CRITICAL: Accept ONLY true SPI chipsets (APA102, SK9822, HD108)
206 // Reject clockless chipsets (WS2812, SK6812) - those use ChannelEngineSpi or RMT
207 //
208 // This is the opposite of ChannelEngineSpi::canHandle(), which should be:
209 // return !data->isSpi(); // Accept clockless, reject true SPI
210 //
211 // Correct routing:
212 // APA102 → SpiChannelEngineAdapter (this adapter)
213 // WS2812 → ChannelEngineSpi (clockless-over-SPI) OR RMT/PARLIO
214 return data->isSpi();
215}
216
217void SpiChannelEngineAdapter::enqueue(ChannelDataPtr channelData) FL_NO_EXCEPT {
218 if (!channelData) {
219 FL_WARN("SpiChannelEngineAdapter: Null channel data passed to enqueue()");
220 return;
221 }
222
223 // Validate that we can handle this data
224 if (!canHandle(channelData)) {
225 FL_WARN("SpiChannelEngineAdapter: Cannot handle non-SPI channel data (chipset mismatch)");
226 return;
227 }
228
229 mEnqueuedChannels.push_back(channelData);
230 FL_DBG("SpiChannelEngineAdapter: Enqueued channel (total: " << mEnqueuedChannels.size() << ")");
231}
232
234 if (mEnqueuedChannels.empty()) {
235 return;
236 }
237
238 FL_DBG("SpiChannelEngineAdapter: show() called with " << mEnqueuedChannels.size() << " channels");
239
240 // Move enqueued channels to transmitting list
242 mEnqueuedChannels.clear();
243
244 // Group adjacent channels with identical SPI bus configuration.
246
247 FL_DBG("SpiChannelEngineAdapter: Grouped into " << groups.size() << " SPI configuration groups");
248
249 // Transmit each group sequentially
250 for (size_t i = 0; i < groups.size(); i++) {
251 const ClockPinGroup& group = groups[i];
252
253 FL_DBG("SpiChannelEngineAdapter: Transmitting group with clock pin " << group.clockPin << " (" << group.channels.size() << " channels)");
254
255 if (!transmitBatch(group.channels)) {
256 FL_WARN("SpiChannelEngineAdapter: Failed to transmit batch for clock pin " << group.clockPin);
257 // Continue with other groups rather than aborting entirely
258 }
259 }
260
261 FL_DBG("SpiChannelEngineAdapter: show() complete");
262}
263
265 // Check if ANY controller is busy
266 bool anyBusy = false;
267 for (const auto& ctrl : mControllers) {
268 if (ctrl.controller && ctrl.controller->isBusy()) {
269 anyBusy = true;
270 break;
271 }
272 }
273
274 if (anyBusy) {
275 return DriverState::BUSY;
276 }
277
278 // All controllers idle - release transmitting channels
279 if (!mTransmittingChannels.empty()) {
280 FL_DBG("SpiChannelEngineAdapter: Releasing " << mTransmittingChannels.size() << " completed channels");
281
282 for (const auto& channel : mTransmittingChannels) {
283 if (channel) {
284 channel->setInUse(false);
285 }
286 }
287 mTransmittingChannels.clear();
288 }
289
290 // Check for draining state
291 if (!mEnqueuedChannels.empty()) {
292 return DriverState::DRAINING;
293 }
294
295 return DriverState::READY;
296}
297
300) FL_NO_EXCEPT {
302
303 for (const auto& channel : channels) {
304 if (!channel) {
305 continue;
306 }
307
308 // Extract clock pin from chipset configuration
309 const auto& chipset = channel->getChipset();
310 if (!chipset.is<SpiChipsetConfig>()) {
311 FL_WARN("SpiChannelEngineAdapter: Non-SPI chipset in groupByClockPin");
312 continue;
313 }
314
315 const SpiChipsetConfig& spiConfig = chipset.get<SpiChipsetConfig>();
316 int clockPin = spiConfig.clockPin;
317 int dataPin = channel->getPin();
318 u32 clockSpeedHz = spiConfig.timing.clock_hz;
319
320 // Preserve enqueue order when the same host alternates configurations.
321 if (!groups.empty()) {
322 ClockPinGroup& last = groups.back();
323 if (last.clockPin == clockPin && last.dataPin == dataPin &&
324 last.clockSpeedHz == clockSpeedHz &&
325 last.requestedBus == spiConfig.spiBus) {
326 last.channels.push_back(channel);
327 continue;
328 }
329 }
330
331 ClockPinGroup newGroup;
332 newGroup.clockPin = clockPin;
333 newGroup.dataPin = dataPin;
334 newGroup.clockSpeedHz = clockSpeedHz;
335 newGroup.requestedBus = spiConfig.spiBus;
336 newGroup.channels.push_back(channel);
337 groups.push_back(newGroup);
338 }
339
340 return groups;
341}
342
344 if (channels.empty()) {
345 return true;
346 }
347
348 // Extract clock pin and data pin from first channel (all same in batch)
349 const auto& chipset = channels[0]->getChipset();
350 if (!chipset.is<SpiChipsetConfig>()) {
351 FL_WARN("SpiChannelEngineAdapter: Non-SPI chipset in transmitBatch");
352 return false;
353 }
354
355 const SpiChipsetConfig& spiConfig = chipset.get<SpiChipsetConfig>();
356 int clockPin = spiConfig.clockPin;
357 int dataPin = channels[0]->getPin(); // MOSI pin from channel config
358
359 // Select controller for this clock pin
360 int controllerIndex = selectControllerForClockPin(clockPin, spiConfig.spiBus);
361 if (controllerIndex < 0) {
362 FL_WARN("SpiChannelEngineAdapter: No available controller for clock pin " << clockPin);
363 return false;
364 }
365
366 ControllerInfo& ctrl = mControllers[controllerIndex];
367
368 // Initialize controller if needed
369 if (!initializeControllerIfNeeded(ctrl, clockPin, dataPin,
370 spiConfig.timing.clock_hz)) {
371 return false;
372 }
373
374 // Transmit using selected controller
375 for (const auto& channel : channels) {
376 if (!channel) {
377 continue;
378 }
379
380 // Mark channel as in use
381 channel->setInUse(true);
382
383 // Get encoded data
384 const auto& data = channel->getData();
385 if (data.empty()) {
386 FL_WARN("SpiChannelEngineAdapter: Empty channel data");
387 channel->setInUse(false);
388 continue;
389 }
390
391 FL_DBG("SpiChannelEngineAdapter: Transmitting channel via " << ctrl.name << " (pin " << channel->getPin() << ", " << data.size() << " bytes)");
392
393 // Acquire DMA buffer
394 DMABuffer dmaBuffer = ctrl.controller->acquireDMABuffer(data.size());
395 if (!dmaBuffer.ok()) {
396 FL_WARN("SpiChannelEngineAdapter: Failed to acquire DMA buffer (error " << (static_cast<int>(dmaBuffer.error())) << ")");
397 channel->setInUse(false);
398 return false;
399 }
400
401 // Copy data to DMA buffer
402 fl::span<u8> buffer = dmaBuffer.data();
403 if (buffer.size() < data.size()) {
404 FL_WARN("SpiChannelEngineAdapter: DMA buffer too small (" << buffer.size() << " < " << data.size() << ")");
405 channel->setInUse(false);
406 return false;
407 }
408
409 fl::memcpy(buffer.data(), data.data(), data.size());
410
411 // Transmit (async mode for non-blocking operation)
412 if (!ctrl.controller->transmit(TransmitMode::ASYNC)) {
413 FL_WARN("SpiChannelEngineAdapter: Transmission failed");
414 channel->setInUse(false);
415 return false;
416 }
417
418 FL_DBG("SpiChannelEngineAdapter: Transmission queued successfully");
419 }
420
421 // Wait for transmission to complete (synchronous for now)
422 // TODO: Make fully async by returning BUSY state and polling in poll()
423 if (!ctrl.controller->waitComplete(1000)) { // 1 second timeout
424 FL_WARN("SpiChannelEngineAdapter: Transmission timeout");
425 return false;
426 }
427
428 FL_DBG("SpiChannelEngineAdapter: Batch transmission complete");
429 return true;
430}
431
432} // namespace fl
CLEDController * controller
bool transmitBatch(fl::span< const ChannelDataPtr > channels) FL_NO_EXCEPT
Transmit a batch of channels sharing one SPI configuration.
fl::vector< ClockPinGroup > groupByClockPin(fl::span< const ChannelDataPtr > channels) FL_NO_EXCEPT
Group adjacent channels with matching pins, host, and clock rate.
void enqueue(ChannelDataPtr channelData) FL_NO_EXCEPT override
Enqueue channel data for transmission.
fl::string mName
Engine name for debugging.
int getPriority() const FL_NO_EXCEPT
Get maximum priority among all controllers.
void show() FL_NO_EXCEPT override
Trigger transmission of enqueued data.
bool canHandle(const ChannelDataPtr &data) const FL_NO_EXCEPT override
Check if adapter can handle channel data.
fl::vector< ControllerInfo > mControllers
All managed controllers.
static fl::shared_ptr< SpiChannelEngineAdapter > create(fl::vector< fl::shared_ptr< SpiHwBase > > hwControllers, fl::vector< int > priorities, fl::vector< const char * > names, const char *adapterName) FL_NO_EXCEPT
Create unified adapter managing multiple controllers.
bool initializeControllerIfNeeded(ControllerInfo &ctrl, int clockPin, int dataPin, u32 clockSpeedHz) FL_NO_EXCEPT
Initialize controller if needed for this clock pin.
int selectControllerForClockPin(int clockPin, Esp32SpiBus requestedBus) FL_NO_EXCEPT
Select best controller for a given clock pin.
SpiChannelEngineAdapter(const char *name) FL_NO_EXCEPT
Private constructor - use create() factory method.
bool canControllerHandleClockPin(const ControllerInfo &ctrl, int clockPin) const FL_NO_EXCEPT
Check if controller can handle this clock pin.
DriverState poll() FL_NO_EXCEPT override
Query driver state and perform maintenance.
~SpiChannelEngineAdapter() FL_NO_EXCEPT override
Destructor.
fl::vector< ChannelDataPtr > mEnqueuedChannels
Channels waiting for show()
fl::vector< ClockPinAssignment > mClockPinAssignments
Clock pin → controller mapping.
fl::vector< ChannelDataPtr > mTransmittingChannels
Channels currently transmitting.
Channels sharing one SPI bus configuration.
fl::size size() const FL_NO_EXCEPT
const T * data() const FL_NO_EXCEPT
Definition span.h:486
constexpr fl::size size() const FL_NO_EXCEPT
Definition span.h:483
fl::size size() const FL_NO_EXCEPT
bool empty() const FL_NO_EXCEPT
void push_back(const T &value) FL_NO_EXCEPT
Definition vector.h:624
T & back() FL_NO_EXCEPT
Definition vector.h:616
#define FL_WARN(...)
Definition log.h:437
#define FL_DBG
Definition log.h:553
Centralized logging categories for FastLED hardware interfaces and subsystems.
shared_ptr< T > make_shared(Args &&... args) FL_NO_EXCEPT
Definition shared_ptr.h:423
constexpr remove_reference< T >::type && move(T &&t) FL_NO_EXCEPT
Definition s16x16x4.h:30
unsigned char u8
Definition s16x16x4.h:132
unsigned char u8
Definition stdint.h:131
Esp32SpiBus
ESP-IDF SPI host selection for an individual clocked LED controller.
Definition spi_bus.h:18
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
FASTLED_FORCE_INLINE void * memcpy(void *dest, const void *src, size_t n) FL_NO_EXCEPT
Definition cstring.h:136
Base definition for an LED controller.
Adapter that wraps HW SPI controllers for ChannelManager.
Driver state with optional error message.
Definition driver.h:168
fl::shared_ptr< SpiHwBase > controller
Hardware instance.
fl::string name
Name (e.g., "SPI2", "SPI3", "I2S0")
Information about a registered SPI hardware controller.
SpiEncoder timing
SPI encoder configuration.
Definition config.h:106
Esp32SpiBus spiBus
ESP-IDF host override, or AUTO.
Definition config.h:107
int clockPin
GPIO clock pin (SCK)
Definition config.h:105
SPI chipset configuration (data + clock pins)
Definition config.h:103
u32 clock_hz
SPI clock frequency in Hz (e.g., 6000000 for 6MHz)
Definition spi.h:40