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