FastLED 3.10.8
Loading...
Searching...
No Matches
channel.cpp.hpp
Go to the documentation of this file.
1
3
4#include "platforms/is_platform.h"
8#include "fl/channels/data.h"
10#include "fl/channels/manager.h"
11#include "fl/stl/atomic.h"
12#include "fl/log/log.h"
13#include "fl/channels/options.h"
16#include "pixel_controller.h"
17#include "fl/system/trace.h"
18
19#include "fl/system/pin.h"
21#include "fl/chipsets/ucs7604.h"
22#include "fl/math/ease.h"
23#include "fl/stl/iterator.h"
25#include "fl/math/xymap.h"
26#include "fl/stl/singleton.h"
27#include "fl/stl/noexcept.h"
28#include "fl/stl/compiler_control.h" // IWYU pragma: keep - FL_UNUSED
29
30namespace fl {
31
32namespace {
33
36 private:
44#if FL_COLOR_PROFILE_RUNTIME
45 // Present only when a colour profile is bound. The managed source keeps
46 // a reference to the controller it wraps, so it has to outlive the
47 // iterator built over it -- both live here, for the frame.
48 //
49 // Raw storage rather than the types themselves, because naming them here
50 // is what made the whole colour pipeline reachable from `show()` and cost
51 // every sketch ~3.5 KB of flash. `colorPipelineHooks()` builds into this,
52 // and only `setColorProfile` installs those hooks -- so a program that
53 // never binds a profile never references the pipeline and the linker
54 // drops it.
55 FL_ALIGNAS(8) unsigned char mManagedSourceStorage[kColorPipelineSourceStorage];
56 FL_ALIGNAS(8) unsigned char mManagedIteratorStorage[kColorPipelineIteratorStorage];
57 PixelIterator* mManagedIterator = nullptr;
58#endif
59
60 public:
69 const XYMap* addressing,
70 EOrder rgbOrder,
71 Rgbw rgbw,
72 Rgbww rgbww,
73 const fl::string& channelName,
75 const StreamingPipelineQ16* pipeline,
76#else
77 StreamingPipelineQ16* pipeline,
78#endif
79 u8 ditherPhase, u8 brightness) FL_NO_EXCEPT
80 : mPixelIterator(pixels, rgbOrder, rgbw, rgbww) {
81 FL_UNUSED(pipeline);
82 FL_UNUSED(ditherPhase); // read only by the colour-managed source
83 FL_UNUSED(brightness); // read only by the colour-managed source
84 FL_UNUSED(channelName); // only consumed by FL_ERROR, a no-op on small platforms
85
86 // Apply addressing transformation if configured
87 if (addressing) {
88 u16 numLeds = pixels.size();
89 u16 width = addressing->getWidth();
90 u16 height = addressing->getHeight();
91 u16 expectedLeds = width * height;
92
93 // Validate that XYMap dimensions match channel LED count
94 if (expectedLeds != numLeds) {
95 FL_ERROR("Channel '" << channelName << "': XYMap dimensions (" << width << "x" << height << "=" << expectedLeds << ") don't match LED count (" << numLeds << "). Addressing transformation may produce unexpected results.");
96 }
97
98 // Cast mData to CRGB array
99 const CRGB* pixelArray = (const CRGB*)pixels.mData;
100
101 // Get thread-local buffer (reuses allocation if size unchanged)
103 buffer.clear();
104 buffer.resize(numLeds);
105
106 // Fill buffer by mapping each physical index to its source
107 for (u16 physicalIdx = 0; physicalIdx < numLeds; physicalIdx++) {
108 u16 x = physicalIdx % width;
109 u16 y = physicalIdx / width;
110 u16 sourceIdx = addressing->mapToIndex(x, y);
111 buffer[physicalIdx] = (sourceIdx < numLeds) ? pixelArray[sourceIdx] : CRGB::Black;
112 }
113
114 // Construct PixelController with reordered buffer
115 mAddressedController.emplace(
116 buffer.data(), buffer.size(),
117 pixels.mColorAdjustment, DISABLE_DITHER);
118 mPixelIterator = PixelIteratorAny(mAddressedController.value(), rgbOrder, rgbw, rgbww);
119 }
120
121#if FL_COLOR_PROFILE_RUNTIME
122 const ColorPipelineHooks& hooks = colorPipelineHooks();
123 if (pipeline != nullptr && hooks.makeIterator != nullptr) {
124 // Colour-managed: build the iterator over a streaming source
125 // instead of over the order-variant controller. The source
126 // applies the colour order itself, so it wraps the RGB-ordered
127 // controller -- whichever of the two that is after addressing.
130 mManagedIterator = hooks.makeIterator(
131 mManagedSourceStorage, mManagedIteratorStorage, base, rgbOrder,
132 *pipeline, rgbw, rgbww, ditherPhase, brightness);
133 }
134#endif
135 }
136
138#if FL_COLOR_PROFILE_RUNTIME
139 // Placement-new'd into our own storage, so the teardown goes back
140 // through the hooks too -- this file must not name the types.
141 if (mManagedIterator != nullptr) {
142 const ColorPipelineHooks& hooks = colorPipelineHooks();
143 if (hooks.destroyIterator != nullptr) {
144 hooks.destroyIterator(mManagedSourceStorage,
145 mManagedIteratorStorage);
146 }
147 mManagedIterator = nullptr;
148 }
149#endif
150 }
151
158#if FL_COLOR_PROFILE_RUNTIME
159 if (mManagedIterator != nullptr) {
160 return *mManagedIterator;
161 }
162#endif
163 return mPixelIterator.get();
164 }
166 return const_cast<ReorderingPixelIteratorAny*>(this)->get();
167 }
168
177 bool isManaged() const FL_NO_EXCEPT {
178#if FL_COLOR_PROFILE_RUNTIME
179 return mManagedIterator != nullptr;
180#else
181 return false;
182#endif
183 }
184};
185
200static void emitDisabledDriverError(const fl::string& channelName,
201 const fl::string& driverName,
202 const fl::string& exclusive) FL_NO_EXCEPT {
203 FL_UNUSED(channelName); // only consumed by FL_ERROR, a no-op on small platforms
204 FL_UNUSED(driverName);
205 if (!exclusive.empty()) {
206 FL_ERROR("Channel '" << channelName << "': bound driver '" << driverName << "' is currently DISABLED by exclusive-driver selection '" << exclusive << "'. Frame will be silently dropped. Resolve with: FastLED.enableDrivers<fl::Bus::" << driverName << ">() or FastLED.enableAllDrivers().");
207 } else {
208 FL_ERROR("Channel '" << channelName << "': bound driver '" << driverName << "' is currently DISABLED. Frame will be silently dropped. Resolve with: FastLED.enableDrivers<fl::Bus::" << driverName << ">() or FastLED.enableAllDrivers().");
209 }
210}
211
212} // anonymous namespace
213
214Channel::PixelEncoder
215Channel::selectPixelEncoder(const ChipsetVariant& chipset) FL_NO_EXCEPT {
216 if (const ClocklessChipset* clockless = chipset.ptr<ClocklessChipset>()) {
217 switch (clockless->encoder) {
219 return &Channel::encodeWS2812;
221 return &Channel::encodeTM1812RGBWW;
223 return &Channel::encodeTM1908;
224#if !defined(FASTLED_DISABLE_UCS7604) || !FASTLED_DISABLE_UCS7604
228 return &Channel::encodeUCS7604;
229#endif
230 }
231 } else if (const SpiChipsetConfig* spi = chipset.ptr<SpiChipsetConfig>()) {
232#if !defined(FASTLED_DISABLE_SPI_CHIPSETS) || !FASTLED_DISABLE_SPI_CHIPSETS
233 switch (spi->timing.chipset) {
240 return &Channel::encodeAPA102;
243 return &Channel::encodeSK9822;
245 return &Channel::encodeWS2801;
247 return &Channel::encodeWS2803;
249 return &Channel::encodeP9813;
251 return &Channel::encodeLPD8806;
253 return &Channel::encodeLPD6803;
255 return &Channel::encodeSM16716;
257 return &Channel::encodeMY9221;
259 return &Channel::encodeHD108;
260 }
261#endif // !FASTLED_DISABLE_SPI_CHIPSETS
262 }
263 return nullptr;
264}
265
266i32 Channel::nextId() FL_NO_EXCEPT {
267 static fl::atomic<i32> gNextChannelId(0); // okay static in header
268 return gNextChannelId.fetch_add(1);
269}
270
271fl::string Channel::makeName(i32 id, const fl::optional<fl::string>& configName) FL_NO_EXCEPT {
272 if (configName.has_value()) {
273 return configName.value();
274 }
275 // Auto-naming `"Channel_<id>"` only matters for diagnostics — in release
276 // builds (NDEBUG → FASTLED_LOG_VERBOSITY=0 per Stage 1) every FL_WARN /
277 // FL_ERROR site that reads mName collapses to `do { } while(0)`, so the
278 // string and the supporting `fl::to_string` + `operator+` chain go
279 // unused. Empty in release saves the `fl::to_string<i64>` instantiation
280 // plus the heap allocation per Channel ctor. See #2942 / #2886.
281#if FASTLED_LOG_RUNTIME_ENABLED
282 return "Channel_" + fl::to_string(static_cast<i64>(id));
283#else
284 (void)id;
285 return {};
286#endif
287}
288
289ChannelPtr Channel::create(const ChannelConfig &config) FL_NO_EXCEPT {
290 // Late binding strategy: Always create with empty driver
291 // Engine binding happens on first showPixels() call:
292 // - Affinity channels: Look up by name and cache
293 // - Non-affinity channels: Select dynamically each frame
294
295 auto channel = fl::make_shared<Channel>(config.chipset, config.mLeds,
296 config.rgb_order, config.options);
297 auto& events = ChannelEvents::instance();
298#if FL_COLOR_PROFILE_RUNTIME
299 if (channel->reconcileColorProfile(config.options)) {
300 events.onColorProfileFallback(
301 ColorProfileEvent{channel->id(), channel->colorProfileStatus(), {}});
302 }
303#endif
304 channel->mName = makeName(channel->mId, config.mName);
305 events.onChannelCreated(*channel);
306 return channel;
307}
308
309int Channel::getPin() const FL_NO_EXCEPT {
310 if (const ClocklessChipset* cs = mChipset.ptr<ClocklessChipset>()) {
311 return cs->pin;
312 }
313 if (const SpiChipsetConfig* spi = mChipset.ptr<SpiChipsetConfig>()) {
314 return spi->dataPin;
315 }
316 return -1;
317}
318
319const ChipsetTimingConfig& Channel::getTiming() const FL_NO_EXCEPT {
320 if (const ClocklessChipset* cs = mChipset.ptr<ClocklessChipset>()) {
321 return cs->timing;
322 }
323 static const ChipsetTimingConfig sEmpty(0, 0, 0, 0);
324 return sEmpty;
325}
326
327Channel::Channel(const ChipsetVariant& chipset, fl::span<CRGB> leds,
328 EOrder rgbOrder, const ChannelOptions& options) FL_NO_EXCEPT
329 : CPixelLEDController<RGB>(RegistrationMode::DeferRegister) // Defer registration until FastLED.add()
330 , mChipset(chipset)
332 , mRgbOrder(rgbOrder)
333 , mDriver() // Empty weak_ptr - late binding on first showPixels()
334 , mBus(options.mBus) // Bus selection (#2459)
335 , mBusWhich(options.mBusWhich)
336 , mId(nextId())
337 , mName(makeName(mId)) {
338 // NOTE: Do NOT call fl::pinMode() here. The pin may already be
339 // configured for SPI output by a persistent driver (ChannelEngineSpi).
340 // Calling pinMode(InputPulldown) would disconnect the SPI MOSI from the
341 // GPIO matrix, causing subsequent transmissions to produce no output.
342 // The driver will configure the pin when it first uses it.
343
344 // Set the LED data array
345 setLeds(leds);
346
347 // Channels owns its options; copy only the legacy rendering fields into
348 // the base controller.
349 mSettings = options;
350
351 // Set color correction/temperature/dither/rgbw from ChannelOptions
353
354 // Create ChannelData during construction with chipset variant
356}
357
358// Backwards-compatible constructor (deprecated)
359Channel::Channel(int pin, const ChipsetTimingConfig& timing, fl::span<CRGB> leds,
360 EOrder rgbOrder, const ChannelOptions& options) FL_NO_EXCEPT
361 : CPixelLEDController<RGB>(RegistrationMode::DeferRegister) // Defer registration until FastLED.add()
362 , mChipset(ClocklessChipset(pin, timing)) // Convert to variant
364 , mRgbOrder(rgbOrder)
365 , mDriver() // Empty weak_ptr - late binding on first showPixels()
366 , mBus(options.mBus) // Bus selection (#2459)
367 , mBusWhich(options.mBusWhich)
368 , mId(nextId())
369 , mName(makeName(mId)) {
370 // NOTE: Do NOT call fl::pinMode() here — see comment in the
371 // Channel(ChipsetVariant, span, EOrder, ChannelOptions) constructor.
372
373 // Set the LED data array
374 setLeds(leds);
375
376 // Channels owns its options; synchronize the base rendering fields.
377 mSettings = options;
378
379 // Set color correction/temperature/dither/rgbw from ChannelOptions
381
382 // Create ChannelData during construction
384}
385
386Channel::~Channel() FL_NO_EXCEPT {
387 auto& events = ChannelEvents::instance();
388 events.onChannelBeginDestroy(*this);
389}
390
391void Channel::syncLegacySettings() FL_NO_EXCEPT {
392 mLegacySettings.mCorrection = mSettings.mCorrection;
393 mLegacySettings.mTemperature = mSettings.mTemperature;
394 mLegacySettings.mDitherMode = mSettings.mDitherMode;
395 mLegacySettings.mWhiteCfg = mSettings.mWhiteCfg;
396 // Active white config assigned straight into ChannelOptions.mWhiteCfg
397 // never passed through setRgbw()/setRgbww(): install its conversion here
398 // (#4795).
399 if (const Rgbw* rgbw = mLegacySettings.mWhiteCfg.ptr<Rgbw>()) {
400 if (rgbw->active()) {
402 }
403 } else if (const Rgbww* rgbww = mLegacySettings.mWhiteCfg.ptr<Rgbww>()) {
404 if (rgbww->active()) {
406 }
407 }
408 if (const auto* rgbw = mSettings.mWhiteCfg.ptr<Rgbw>()) {
411 }
412}
413
414#ifndef FL_IS_AVR
415void Channel::onSettingsChanged(SettingsChange change) FL_NO_EXCEPT {
416 // Frame completion restores dithering through the base setter. Dithering
417 // does not change pipeline coefficients and must not rebuild the pipeline.
418 mSettings.mDitherMode = mLegacySettings.mDitherMode;
419 if (change == SettingsChange::Dither) return;
420 mSettings.mWhiteCfg = mLegacySettings.mWhiteCfg;
421 if (change == SettingsChange::Correction || change == SettingsChange::Temperature) {
423 if (change == SettingsChange::Correction) {
424 mSettings.setLegacyCorrection(mLegacySettings.mCorrection);
425 } else {
426 mSettings.setLegacyTemperature(mLegacySettings.mTemperature);
427 }
428 }
429#if FL_COLOR_PROFILE_RUNTIME
430 reconcileColorProfile(mSettings);
431#endif
432}
433#endif
434
435#if FL_COLOR_PROFILE_RUNTIME
436void Channel::raiseColorProfileFallback(const char* reason) FL_NO_EXCEPT {
437 mColorProfileFallback = true;
438 mProfileBindingAccepted = !detail::colorProfileStrictMode();
439 // C5's one-time warning. The event fired by the callers is the
440 // machine-readable half; this is the half a sketch sees without
441 // subscribing to anything (#4333). Strict mode turns the same condition
442 // into a disabled channel, so say which happened.
443 if (mWarnedColorProfileFallback) {
444 return;
445 }
446 if (mProfileBindingAccepted) {
447 FL_WARN("Channel " << id() << ": color management requested but " << reason << "; falling back to the legacy path");
448 } else {
449 FL_WARN("Channel " << id() << ": color management requested but " << reason << "; strict mode disables this channel");
450 }
451 mWarnedColorProfileFallback = true;
452}
453
454bool Channel::reconcileColorProfile(const ChannelOptions& options) FL_NO_EXCEPT {
455 // A strict-mode rejection disables the channel. Remember whether the
456 // current disabled state is ours, so that withdrawing the rejection
457 // re-enables the channel without also overriding a caller's own
458 // setEnabled(false).
459 const bool wasRejectedByUs = mColorProfileFallback && !mProfileBindingAccepted;
460
461 // Recompute from scratch. Reconfiguration must not inherit the verdict of
462 // the configuration it replaces: a valid profile applied over a rejected
463 // one has to clear the fallback, not keep reporting it.
464 mColorProfileFallback = false;
465 mProfileBindingAccepted = true;
466
467 // C5's fallback, raised by the two conditions that mean the same thing to
468 // a caller: management was asked for and is not happening. The second one
469 // -- a profile that binds but builds no pipeline -- is raised further
470 // down, once the build has been attempted.
471 if (options.mColorProfile.mRequested && !options.hasColorProfile()) {
472 raiseColorProfileFallback("no profile bound");
473 }
474 if (options.mColorProfile.mUseGlobalSourceDefault) {
475 mSettings.mColorProfile.mSource = detail::defaultSourceProfile();
476 }
477 // Guards the build below: a binding already rejected by strict mode must
478 // not be built. It is deliberately *not* reused for the enablement
479 // branch -- the build can raise the fallback itself (#4345), and reading
480 // this stale value there left a strict-mode channel reporting Rejected
481 // while still enabled and rendering through the legacy path, which is the
482 // one thing strict mode exists to prevent.
483 const bool rejectedBeforeBuild = mColorProfileFallback && !mProfileBindingAccepted;
484
485 // Derive the pipeline once, here, rather than per frame: it inverts
486 // matrices and bisects a lightness bound. A binding that does not
487 // describe a usable pipeline leaves the channel on the legacy path
488 // rather than failing -- an unbound channel is the ordinary case, not an
489 // error.
490 mPipeline.reset();
491 const ColorPipelineHooks& hooks = colorPipelineHooks();
492 if (!rejectedBeforeBuild && hooks.build != nullptr) {
493 // Through the hook, not by name. Calling `buildPipelineForBinding`
494 // directly from here is what kept the entire pipeline alive in every
495 // build; the pointer is null until `setColorProfile` installs it.
496 // Built on the stack first, so a binding that does not describe a
497 // usable pipeline costs no allocation -- that is the failing half of
498 // an ordinary unbound channel, not an exceptional path.
499 StreamingPipelineQ16 pipeline;
500 if (hooks.build(mSettings.mColorProfile, mSettings, mChipset,
501 &pipeline)) {
502#if FL_COLOR_PIPELINE_SHARED
503 mPipeline = fl::make_shared<StreamingPipelineQ16>(pipeline);
504#else
505 mPipeline = fl::make_unique<StreamingPipelineQ16>(pipeline);
506#endif
507 } else if (options.hasColorProfile()) {
508 // R6: a profile outside the numerical bounds must "fail
509 // explicitly" and "not become native-drive input". A binding that
510 // cannot produce a pipeline used to leave every reporting surface
511 // saying the channel was fine -- Configured, no fallback flag, no
512 // warning -- while it rendered through the legacy path (#4345).
513 //
514 // This reports; it does not reject. Refusing the binding at
515 // setColorProfile() time is R6's fuller reading and needs the
516 // published coefficient bounds it also asks for, which do not
517 // exist yet.
518 raiseColorProfileFallback("bound profile builds no usable pipeline");
519 }
520 }
521
522 // Recomputed after the build, so a rejection raised by a failed build
523 // reaches this branch.
524 const bool rejectedNow = mColorProfileFallback && !mProfileBindingAccepted;
525 if (rejectedNow) {
526 setEnabled(false);
527 } else if (wasRejectedByUs) {
528 setEnabled(true);
529 }
530 return mColorProfileFallback;
531}
532#endif
533
534void Channel::applyConfig(const ChannelConfig& config) FL_NO_EXCEPT {
535 mRgbOrder = config.rgb_order;
536 if (config.mName.has_value()) {
537 mName = config.mName.value();
538 }
539 setLeds(config.mLeds);
540 // Replace Channel-owned options and synchronize the base rendering fields.
541 mSettings = config.options;
542#if !FL_COLOR_PROFILE_RUNTIME && !defined(FL_IS_AVR)
543 // Tiny options cannot carry a replacement profile; applying them is a
544 // legacy transition, just like the base correction/temperature setters.
546#endif
547 mBus = config.options.mBus;
548 mBusWhich = config.options.mBusWhich;
550 auto& events = ChannelEvents::instance();
551#if FL_COLOR_PROFILE_RUNTIME
552 if (reconcileColorProfile(config.options)) {
553 events.onColorProfileFallback(ColorProfileEvent{id(), colorProfileStatus(), {}});
554 }
555#endif
556 events.onChannelConfigured(*this, config);
557}
558
559int Channel::getClockPin() const FL_NO_EXCEPT {
560 if (const SpiChipsetConfig* spi = mChipset.ptr<SpiChipsetConfig>()) {
561 return spi->clockPin;
562 }
563 return -1; // Clockless chipsets don't have a clock pin
564}
565
566
567namespace {
568
576 ClocklessEncoder encoder, const ChannelOptions& settings,
577 EOrder rgbOrder, bool managed) FL_NO_EXCEPT {
578 // Map encoder enum to UCS7604Mode
579 UCS7604Mode mode;
580 switch (encoder) {
583 break;
586 break;
589 break;
590 default:
591 // Should never happen — caller already checked
593 break;
594 }
595
596 // Get current control from global UCS7604 brightness
598
599 // Reorder current control values to match wire order (same logic as UCS7604ControllerT)
600 u8 rgb_currents[3] = {current.r, current.g, current.b};
601 u8 pos0 = (static_cast<int>(rgbOrder) >> 6) & 0x3;
602 u8 pos1 = (static_cast<int>(rgbOrder) >> 3) & 0x3;
603 u8 pos2 = (static_cast<int>(rgbOrder) >> 0) & 0x3;
604 UCS7604CurrentControl wire_current(
605 rgb_currents[pos0], rgb_currents[pos1], rgb_currents[pos2], current.w);
606
607 // Get gamma LUT.
608 //
609 // Only the unbound path uses it. With a profile bound the pixel source
610 // has already produced the device drive and quantized it once, to 16
611 // bits, so widening it again through a curve is the second shaping stage
612 // B1 and section 6 forbid after the device solve -- and the 2.8 here is
613 // `value_or`'s default rather than anything the caller asked for, since
614 // `setColorProfile` clears `mGamma`. #4326.
615 // The iterator's own state, not the options'. `hasColorProfile()` says a
616 // profile is configured; it does not say the managed iterator was built.
617 // A rejected binding leaves the legacy controller in place, and taking
618 // the no-gamma path over its 8-bit pixels would drop a stage that path
619 // still needs.
620 const bool wide_source = managed;
621 float gamma = settings.mGamma.value_or(2.8f);
623
624 bool is_rgbw = pixelIterator.get_rgbw().active();
625 size_t num_leds = pixelIterator.size();
626
627 // (#2558) UCS7604 is a 4-channel chipset (always RGBW). If the user
628 // configured this channel for 5-channel RGBWW (warm-W + cool-W), the
629 // chipset can't carry the second W byte. Warn loudly — silently dropping
630 // the white channels would be very hard to diagnose. The pixel iterator
631 // continues to emit RGB-only bytes (is_rgbw=false) so the strip still
632 // lights up, just without the W diode.
633 if (pixelIterator.get_rgbww().active() && !is_rgbw) {
634 FL_WARN_ONCE("UCS7604 cannot carry 5-channel RGBWW — this chipset "
635 "always emits 4-channel RGBW. The warm/cool white "
636 "channels in your Rgbww config will be dropped. "
637 "Set ChannelOptions.mWhiteCfg = Rgbw{...} instead to "
638 "silence this warning.");
639 }
640
641 // Encode into the data buffer
642 encodeUCS7604(pixelIterator, num_leds, fl::back_inserter(*data),
643 mode, wire_current, is_rgbw, gamma8.get(), wide_source);
644}
645
646} // anonymous namespace
647
648namespace {
649
654 bool managed, fl::vector_psram<u8>& output,
655 SpiChipset chipset)
657#if FL_COLOR_PROFILE_RUNTIME
658 if (!managed) {
659 return false;
660 }
661 const ColorPipelineHooks& hooks = colorPipelineHooks();
662 return hooks.encodeManagedSpi != nullptr &&
663 hooks.encodeManagedSpi(pixels, &output, chipset, channel);
664#else
665 FL_UNUSED(channel);
666 FL_UNUSED(pixels);
667 FL_UNUSED(managed);
668 FL_UNUSED(output);
669 FL_UNUSED(chipset);
670 return false;
671#endif
672}
673
675 const SpiChipsetConfig* spi =
676 channel.getChipset().ptr<SpiChipsetConfig>();
677 return spi->timing.chipset;
678}
679
680} // anonymous namespace
681
683void Channel::encodeWS2812(Channel&, PixelIterator& pixels,
684 bool, fl::vector_psram<u8>& output) FL_NO_EXCEPT {
685 pixels.writeWS2812(&output);
686}
687
689void Channel::encodeTM1812RGBWW(Channel&, PixelIterator& pixels,
690 bool, fl::vector_psram<u8>& output) FL_NO_EXCEPT {
691 pixels.writeTM1812RGBWW(&output);
692}
693
695void Channel::encodeTM1908(Channel&, PixelIterator& pixels,
696 bool, fl::vector_psram<u8>& output) FL_NO_EXCEPT {
697 pixels.writeTM1908(&output);
698}
699
701void Channel::encodeUCS7604(Channel& channel, PixelIterator& pixels,
702 bool managed,
704 const ClocklessChipset* clockless =
705 channel.mChipset.ptr<ClocklessChipset>();
706 writeUCS7604(&output, pixels, clockless->encoder, channel.mSettings,
707 channel.mRgbOrder, managed);
708}
709
711void Channel::encodeAPA102(Channel& channel, PixelIterator& pixels,
712 bool managed, fl::vector_psram<u8>& output)
714 const SpiChipset chipset = spiChipsetFor(channel);
715 if (tryEncodeManagedSpi(channel, pixels, managed, output, chipset)) {
716 return;
717 }
718 const bool hdGamma = chipset == SpiChipset::APA102HD ||
719 chipset == SpiChipset::DOTSTARHD ||
720 chipset == SpiChipset::HD107HD;
721 pixels.writeAPA102(&output, hdGamma);
722}
723
725void Channel::encodeSK9822(Channel& channel, PixelIterator& pixels,
726 bool managed, fl::vector_psram<u8>& output)
728 const SpiChipset chipset = spiChipsetFor(channel);
729 if (tryEncodeManagedSpi(channel, pixels, managed, output, chipset)) {
730 return;
731 }
732 pixels.writeSK9822(&output, chipset == SpiChipset::SK9822HD);
733}
734
736void Channel::encodeWS2801(Channel& channel, PixelIterator& pixels,
737 bool managed, fl::vector_psram<u8>& output)
739 if (tryEncodeManagedSpi(channel, pixels, managed, output,
740 spiChipsetFor(channel))) {
741 return;
742 }
743 pixels.writeWS2801(&output);
744}
745
747void Channel::encodeWS2803(Channel& channel, PixelIterator& pixels,
748 bool managed, fl::vector_psram<u8>& output)
750 if (tryEncodeManagedSpi(channel, pixels, managed, output,
751 spiChipsetFor(channel))) {
752 return;
753 }
754 pixels.writeWS2803(&output);
755}
756
758void Channel::encodeP9813(Channel& channel, PixelIterator& pixels,
759 bool managed, fl::vector_psram<u8>& output)
761 if (tryEncodeManagedSpi(channel, pixels, managed, output,
762 spiChipsetFor(channel))) {
763 return;
764 }
765 pixels.writeP9813(&output);
766}
767
769void Channel::encodeLPD8806(Channel& channel, PixelIterator& pixels,
770 bool managed, fl::vector_psram<u8>& output)
772 if (tryEncodeManagedSpi(channel, pixels, managed, output,
773 spiChipsetFor(channel))) {
774 return;
775 }
776 pixels.writeLPD8806(&output);
777}
778
780void Channel::encodeLPD6803(Channel& channel, PixelIterator& pixels,
781 bool managed, fl::vector_psram<u8>& output)
783 if (tryEncodeManagedSpi(channel, pixels, managed, output,
784 spiChipsetFor(channel))) {
785 return;
786 }
787 pixels.writeLPD6803(&output);
788}
789
791void Channel::encodeSM16716(Channel& channel, PixelIterator& pixels,
792 bool managed, fl::vector_psram<u8>& output)
794 if (tryEncodeManagedSpi(channel, pixels, managed, output,
795 spiChipsetFor(channel))) {
796 return;
797 }
798 pixels.writeSM16716(&output);
799}
800
802void Channel::encodeHD108(Channel& channel, PixelIterator& pixels,
803 bool managed, fl::vector_psram<u8>& output)
805 if (tryEncodeManagedSpi(channel, pixels, managed, output,
806 spiChipsetFor(channel))) {
807 return;
808 }
809 pixels.writeHD108(&output, managed);
810}
811
813void Channel::encodeMY9221(Channel& channel, PixelIterator& pixels,
814 bool managed, fl::vector_psram<u8>& output)
816 (void)tryEncodeManagedSpi(channel, pixels, managed, output,
817 spiChipsetFor(channel));
818}
819
829fl::shared_ptr<IChannelDriver> Channel::resolveDynamicDriver() FL_NO_EXCEPT {
830#if defined(FASTLED_DISABLE_DYNAMIC_DRIVER) && FASTLED_DISABLE_DYNAMIC_DRIVER
831 // Body excluded via FASTLED_DISABLE_DYNAMIC_DRIVER (#2926). The
832 // showPixels call site is also gated so this is unreachable; the
833 // empty body lets the linker dead-strip ChannelManager::findDriverByName
834 // and selectDriverForChannel along with this symbol.
835 return {};
836#else
837 // Build busKey only when we actually need it (mBus != AUTO).
838 fl::string busKey;
839 if (mBus != Bus::AUTO) {
841 }
842
845 mDriver = driver;
846
847 // #2455 / #2459: one-shot diagnostic when a typed-Bus miss happens.
848 // Probe via `findDriverByName` (silent) to distinguish "driver wasn't
849 // instantiated" from "driver exists but canHandle() rejected this
850 // chipset" — resolution paths differ. The mBusWarned guard suppresses
851 // duplicate logs on subsequent shows of the same channel.
852#if FASTLED_LOG_RUNTIME_ENABLED
853 if (mBus != Bus::AUTO && !mBusWarned &&
854 (!driver || driver->getName() != busKey)) {
855 auto busDriver = ChannelManager::instance().findDriverByName(busKey);
856 if (!busDriver) {
857 // Typed Bus miss — emit the actionable hint with the three
858 // currently-shipping remediations (option 3 added in #2460).
859 FL_ERROR("Channel '" << mName << "': Driver '" << busKey << "' wasn't instantiated. Resolve with: (1) fl::enableDrivers<fl::Bus::" << busKey << ">() (links only this driver), (2) FastLED.enableAllDrivers() (links every driver), or (3) FastLED.addLeds<..., fl::Bus::" << busKey << ">(...) (legacy API; pins Bus + triggers linker keep-alive). Defaulting to AUTO/priority dispatch.");
860 } else {
861 // Registered, but canHandle() said no — bus/chipset mismatch.
862 FL_ERROR("Channel '" << mName << "': Driver '" << busKey << "' is registered but cannot handle this channel's chipset (bus/chipset mismatch). Defaulting to AUTO/priority dispatch.");
863 }
864 mBusWarned = true;
865 }
866 if (!driver) {
867 FL_ERROR("Channel '" << mName << "': No compatible driver found - cannot transmit");
868 }
869#endif // FASTLED_LOG_RUNTIME_ENABLED — release skips the per-frame
870 // driver->getName() != busKey compare + the silent
871 // findDriverByName probe (used only to differentiate the
872 // FL_ERROR message). The functional return value below is
873 // preserved in both builds. See #2952.
874 return driver;
875#endif // !FASTLED_DISABLE_DYNAMIC_DRIVER
876}
877
878// Cold path of showPixels(): the previous frame's buffer is still owned by the
879// driver. Returns false when showPixels() must drop this frame (#4566).
880bool Channel::waitForInUseBuffer() FL_NO_EXCEPT {
881 FL_WARN("Channel '" << mName << "': showPixels() called while mChannelData is in use by driver, attempting to wait");
882 auto driver = mDriver.lock();
883 if (!driver) {
884 FL_ERROR("Channel '" << mName << "': No driver bound yet the mChannelData is in use - cannot transmit");
885 return false;
886 }
887 // wait until the driver is in a READY state.
888 bool ok = driver->waitForReady();
889 if (!ok) {
890 FL_ERROR("Channel '" << mName << "': Timeout occurred while waiting for driver to become READY");
891 return false;
892 }
893 FL_WARN("Channel '" << mName << "': Engine became READY after waiting");
894 return true;
895}
896
897// showPixels() is only a dispatcher (#4566): in-use wait, driver resolution,
898// encodeFrame(), submitFrame(). Each helper is FL_NO_INLINE so the entry
899// point stays small and the bodies exist exactly once.
900void Channel::showPixels(PixelController<RGB, 1, 0xFFFFFFFF> &pixels) FL_NO_EXCEPT {
902
903 // Safety check: don't modify buffer if driver is currently transmitting it
904 if (mChannelData->isInUse() && !waitForInUseBuffer()) {
905 return;
906 }
907
908 // Channels created via the manager-based API (Channel::create(cfg))
909 // always resolve their driver dynamically through resolveDynamicDriver(),
910 // which handles the busKey construction, ChannelManager lookup, and
911 // diagnostics. Legacy addLeds<> controllers no longer go through Channel
912 // at all -- they route through fl::SlimBridgeController.
914#if !defined(FASTLED_DISABLE_DYNAMIC_DRIVER) || !FASTLED_DISABLE_DYNAMIC_DRIVER
915 driver = resolveDynamicDriver();
916 if (!driver) {
917 return;
918 }
919#else
920 // Dynamic-driver lookup gated out via FASTLED_DISABLE_DYNAMIC_DRIVER
921 // (#2926). The gate lets `--gc-sections` drop the resolveDynamicDriver
922 // body plus the ChannelManager::findDriverByName /
923 // selectDriverForChannel chain (~400-900 B). Under this flag, runtime
924 // Channels emit nothing -- user accepts the constraint.
925 return;
926#endif
927
928 encodeFrame(pixels);
929 submitFrame(driver);
930}
931
932// Encode `pixels` into mChannelData with the construction-bound encoder and
933// fire onChannelDataEncoded (#4566: out of line from showPixels()).
934void Channel::encodeFrame(PixelController<RGB, 1, 0xFFFFFFFF> &pixels) FL_NO_EXCEPT {
935 // Build pixel iterator with optional addressing transformation
936 // (#2558) Pass both Rgbw and Rgbww from the current rendering settings; the iterator
937 // carries both, and the encoder dispatch below picks the right path based
938 // on which white-channel variant the controller currently holds.
939#if FL_COLOR_PROFILE_RUNTIME
940 // Brightness reaches the pipeline as C4's flux scalar and nowhere else.
941 // `premixed` is the brightness on this path: binding a profile sets
942 // correction and temperature to identity, so nothing else is folded into
943 // it -- and reading it rather than `ColorAdjustment::brightness` keeps
944 // this working when FASTLED_HD_COLOR_MIXING is off, where that field
945 // does not exist.
946 //
947 // The shared pipeline is never written after it is built (#4440): this
948 // managed iterator takes a reference, copies it, and sets flux on the copy, so a
949 // concurrent reader -- the power estimate copies the same pipeline --
950 // never sees a half-written flux, and a reconfiguration replaces
951 // `mPipeline` without touching what this frame encodes through. Small
952 // tiers encode synchronously and keep single ownership, mutated in place.
953#if FL_COLOR_PIPELINE_SHARED
954 const ColorPipelineStorage pipeline_ref = mPipeline;
955 const StreamingPipelineQ16* pipeline = pipeline_ref.get();
956#else
957 StreamingPipelineQ16* pipeline = mPipeline.get();
958#endif
959#else
960 StreamingPipelineQ16* pipeline = nullptr;
961#endif
962 // Dither advances on presentation, not on attempt (#4347, R8): this
963 // channel's phase moves only when its driver accepts a frame (submitFrame()), so
964 // a dropped submission -- no driver, a disabled one, a busy buffer --
965 // does not consume a phase and the cycle's average stays unbiased. Both
966 // the legacy offsets and the colour-managed temporal dither read it.
967 if (pixels.ditherActive()) {
968 pixels.reseed_binary_dithering(mDitherPhase);
969 }
970#ifdef FL_IS_AVR
971 // AVR omits base settings notifications, so base setters are authoritative.
972 const auto& whiteSettings = mLegacySettings;
973#else
974 const auto& whiteSettings = mSettings;
975#endif
976 ReorderingPixelIteratorAny iterator(pixels, mScreenMap.getXYMap(), mRgbOrder,
977 whiteSettings.rgbw(), whiteSettings.rgbww(),
978 mName, pipeline, mDitherPhase,
979 pixels.mColorAdjustment.premixed.r);
980 PixelIterator& pixelIterator = iterator.get();
981
982 // Encode pixels with the writer selected once from the immutable chipset.
983 auto& data = mChannelData->getData();
984 data.clear();
985 const auto* rgbww = whiteSettings.mWhiteCfg.ptr<Rgbww>();
986 const auto* rgbw = whiteSettings.mWhiteCfg.ptr<Rgbw>();
987 if (rgbww != nullptr && rgbww->active()) {
989 } else if (rgbw != nullptr && rgbw->active()) {
990 mChannelData->setPixelFormat(ChannelPixelFormat::RGBW);
991 } else {
992 mChannelData->setPixelFormat(ChannelPixelFormat::RGB);
993 }
994
995 if (mPixelEncoder != nullptr) {
996 mPixelEncoder(*this, pixelIterator, iterator.isManaged(), data);
997 }
998
999 // Fire event after encoding completes
1000 {
1001 auto& events = ChannelEvents::instance();
1002 events.onChannelDataEncoded(*this, *mChannelData);
1003 }
1004}
1005
1006// Submit the encoded frame to `driver` (#4566: out of line from showPixels()).
1007void Channel::submitFrame(const fl::shared_ptr<IChannelDriver>& driver) FL_NO_EXCEPT {
1008 // #2517: detect the silent-drop scenario before enqueuing — if the
1009 // resolved driver is registered with ChannelManager but currently
1010 // disabled (typically by `FastLED.setExclusiveDriver<OtherBus>()`),
1011 // `ChannelManager::onEndFrame()` will skip its `show()` call and the
1012 // frame is silently dropped on the floor. Emit an actionable
1013 // FL_ERROR so users can diagnose the missing output.
1014 //
1015 // One-shot via `mDisabledDriverWarned` to avoid per-frame spam.
1016 // Reset the latch whenever the driver returns to ENABLED so a later
1017 // disable re-emits the diagnostic.
1018 fl::string driverName = driver->getName();
1019 auto status = ChannelManager::instance().driverStatus(driverName);
1021#if FASTLED_LOG_RUNTIME_ENABLED
1022 if (!mDisabledDriverWarned) {
1023 emitDisabledDriverError(
1024 mName, driverName,
1025 ChannelManager::instance().exclusiveDriverName());
1026 mDisabledDriverWarned = true;
1027 }
1028#endif
1029 // Skip the enqueue — the data wouldn't be sent anyway, and dropping
1030 // it here matches the existing behaviour (rather than leaving stale
1031 // bytes in the driver's pending queue across an enable/disable flip).
1032 // The drop happens in both debug and release; only the diagnostic
1033 // and the one-shot latch are gated. In release, the empty
1034 // emitDisabledDriverError + the exclusiveDriverName() call + the
1035 // 3-arg fl::string chain dead-strip (see #2950).
1036 return;
1037 } else if (status == ChannelManager::DriverStatus::STATUS_ENABLED) {
1038#if FASTLED_LOG_RUNTIME_ENABLED
1039 // Reset the one-shot guard so a future disable re-emits the diagnostic.
1040 mDisabledDriverWarned = false;
1041#endif
1042 }
1043 // NOT_REGISTERED: driver is not managed by ChannelManager (e.g. a custom
1044 // test driver, or one that was removed). Fall through to enqueue and let
1045 // the driver decide what to do — this is the historic behaviour.
1046
1047 // Enqueue for transmission (will be sent when driver->show() is called)
1048 driver->enqueue(mChannelData);
1049 // Presented, per the driver contract: an accepted enqueue is the frame.
1050 ++mDitherPhase;
1051 auto& events = ChannelEvents::instance();
1052 // Reuse the name fetched for driverStatus() instead of a second getName().
1053 events.onChannelEnqueued(*this, driverName);
1054}
1055
1056void Channel::init() FL_NO_EXCEPT {
1057 // TODO: Implement initialization
1058}
1059
1060// Stub driver - provides no-op implementation for testing or unsupported platforms
1061namespace {
1063public:
1065
1066 virtual bool canHandle(const ChannelDataPtr& data) const FL_NO_EXCEPT override {
1067 (void)data;
1068 return true; // Test driver accepts all channel types
1069 }
1070
1071 virtual void enqueue(ChannelDataPtr /*channelData*/) FL_NO_EXCEPT override {
1072 // No-op: stub driver does nothing
1073 static bool warned = false;
1074 if (!warned) {
1075 FL_DBG("StubChannelEngine: No-op enqueue (use for testing or unsupported platforms)");
1076 warned = true;
1077 }
1078 }
1079
1080 virtual void show() FL_NO_EXCEPT override {
1081 // No-op: no hardware to drive
1082 }
1083
1084 virtual DriverState poll() FL_NO_EXCEPT override {
1085 return DriverState(DriverState::READY); // Always "ready" (does nothing)
1086 }
1087
1088 virtual fl::string getName() const FL_NO_EXCEPT override {
1089 return fl::string::from_literal("STUB");
1090 }
1091
1092 virtual Capabilities getCapabilities() const FL_NO_EXCEPT override {
1093 return Capabilities(true, true); // Stub accepts both clockless and SPI
1094 }
1095};
1096
1097} // anonymous namespace
1098
1100 static StubChannelEngine instance;
1101 return &instance;
1102}
1103
1104// Re-exposed protected base class methods
1105void Channel::addToDrawList() FL_NO_EXCEPT {
1106 if (isInList()) {
1107 FL_WARN("Channel '" << mName << "': Skipping addToDrawList() - already in draw list");
1108 return;
1109 }
1111 // Fire event after adding to draw list (detectable even if user bypasses FastLED.add())
1112 auto& events = ChannelEvents::instance();
1113 events.onChannelAdded(*this);
1114}
1115
1116void Channel::removeFromDrawList() FL_NO_EXCEPT {
1117 if (!isInList()) {
1118 FL_WARN("Channel '" << mName << "': Skipping removeFromDrawList() - not in draw list");
1119 return;
1120 }
1122 // Fire event after removing from draw list (detectable even if user bypasses FastLED.remove())
1123 auto& events = ChannelEvents::instance();
1124 events.onChannelRemoved(*this);
1125
1126 // Clear driver weak_ptr when removed from draw list
1127 mDriver.reset();
1128}
1129
1130int Channel::size() const FL_NO_EXCEPT {
1132}
1133
1137
1138u8 Channel::ditherPhase() const FL_NO_EXCEPT {
1139 return mDitherPhase;
1140}
1141
1142bool Channel::isInDrawList() const FL_NO_EXCEPT {
1144}
1145
1149
1153
1154CRGB Channel::getCorrection() FL_NO_EXCEPT {
1156}
1157
1158CRGB Channel::getTemperature() FL_NO_EXCEPT {
1160}
1161
1162u8 Channel::getDither() FL_NO_EXCEPT {
1164}
1165
1166#if FL_COLOR_PIPELINE_SHARED
1167fl::shared_ptr<StreamingPipelineQ16> Channel::colorPipeline() const FL_NO_EXCEPT {
1168 return mPipeline;
1169}
1170#endif
1171
1172Rgbw Channel::getRgbw() const FL_NO_EXCEPT {
1174}
1175
1176Channel& Channel::setGamma(float gamma) FL_NO_EXCEPT {
1177 mSettings.mGamma = gamma;
1178 return *this;
1179}
1180
1181fl::optional<float> Channel::getGamma() const FL_NO_EXCEPT {
1182 return mSettings.mGamma;
1183}
1184
1185fl::string Channel::getEngineName() const FL_NO_EXCEPT {
1186 // Lock the weak_ptr to get a shared_ptr
1187 auto driver = mDriver.lock();
1188 if (driver) {
1189 return driver->getName();
1190 }
1191 return fl::string(); // Return empty string if no driver bound
1192}
1193
1194Channel& Channel::setScreenMap(const fl::XYMap& map, float diameter) FL_NO_EXCEPT {
1195 fl::ScreenMap screenmap = map.toScreenMap();
1196 if (diameter <= 0.0f) {
1197 screenmap.setDiameter(.15f); // Default diameter for small matrices
1198 } else {
1199 screenmap.setDiameter(diameter);
1200 }
1203 return *this;
1204}
1205
1206Channel& Channel::setScreenMap(const fl::ScreenMap& map) FL_NO_EXCEPT {
1207 mScreenMap = map;
1209 return *this;
1210}
1211
1212Channel& Channel::setScreenMap(fl::u16 width, fl::u16 height, float diameter) FL_NO_EXCEPT {
1214 return setScreenMap(xymap, diameter);
1215}
1216
1217Channel& Channel::setScreenMap(const fl::XMap& map) FL_NO_EXCEPT {
1218 // Convert 1D XMap to 2D XYMap (width=length, height=1) and reuse existing logic
1220 return setScreenMap(xymap, .15f); // Use default diameter for 1D strips
1221}
1222
1223const fl::ScreenMap& Channel::getScreenMap() const FL_NO_EXCEPT {
1224 return mScreenMap;
1225}
1226
1227bool Channel::hasScreenMap() const FL_NO_EXCEPT {
1228 return mScreenMap.getLength() > 0;
1229}
1230
1231} // namespace fl
fl::UISlider brightness("Brightness", BRIGHTNESS, 0, 255)
XYMap xymap
fl::ScreenMap screenmap
Rgbw rgbw
ESP32-P4 Parallel IO (PARLIO) LED channel.
CPixelLEDController(RegistrationMode mode) FL_NO_EXCEPT
T fetch_add(T value) FL_NO_EXCEPT
Definition atomic.h:159
bool isInList() const FL_NO_EXCEPT
Check if this controller is in the linked list.
@ DeferRegister
Defer registration until addToList() is called.
LegacySettings mLegacySettings
VIRTUAL_IF_NOT_AVR int size() const FL_NO_EXCEPT
How many LEDs does this controller manage?
CLEDController & setLeds(CRGB *data, int nLeds) FL_NO_EXCEPT
Set the default array of LEDs to be used by this controller.
CRGB getCorrection() FL_NO_EXCEPT
Get the correction value used by this controller.
fl::u8 getDither() const FL_NO_EXCEPT
Get the dithering option currently set for this controller.
CRGB getTemperature() FL_NO_EXCEPT
Get the color temperature, aka white point, for this controller.
void removeFromDrawList() FL_NO_EXCEPT
Remove this controller from the draw list.
CRGB * leds() FL_NO_EXCEPT
Pointer to the CRGB array for this controller.
Rgbw getRgbw() const FL_NO_EXCEPT
VIRTUAL_IF_NOT_AVR void showLeds(fl::u8 brightness) FL_NO_EXCEPT
void addToList() FL_NO_EXCEPT
Add this controller to the linked list.
EOrder mRgbOrder
Definition channel.h:415
static i32 nextId() FL_NO_EXCEPT
fl::u8 mBusWhich
Definition channel.h:439
i32 id() const FL_NO_EXCEPT override
Get the channel ID.
Definition channel.h:106
ChipsetVariant mChipset
Definition channel.h:413
bool isSpi() const FL_NO_EXCEPT
Check if this is an SPI chipset.
Definition channel.h:146
PixelEncoder mPixelEncoder
Definition channel.h:414
u8 mDitherPhase
This channel's temporal-dither phase.
Definition channel.h:448
static fl::string makeName(i32 id, const fl::optional< fl::string > &configName=fl::optional< fl::string >()) FL_NO_EXCEPT
bool mBusWarned
Definition channel.h:440
fl::span< CRGB > leds() FL_NO_EXCEPT
Get the LED array as a span (non-const)
FL_NO_INLINE fl::shared_ptr< IChannelDriver > resolveDynamicDriver() FL_NO_EXCEPT
Cold slow-path helper for showPixels().
fl::ScreenMap mScreenMap
Definition channel.h:458
ChannelDataPtr mChannelData
Definition channel.h:457
static PixelEncoder selectPixelEncoder(const ChipsetVariant &) FL_NO_EXCEPT
The chipset is fixed when a Channel is constructed, so bind its pixel encoder once instead of switchi...
Channel & setScreenMap(const fl::XYMap &map, float diameter=-1.f) FL_NO_EXCEPT
Set screen map for JS canvas visualization from XYMap.
FL_NO_INLINE bool waitForInUseBuffer() FL_NO_EXCEPT
Cold helper for showPixels() when mChannelData is still in use by the driver: warns and waits for REA...
Channel(const ChipsetVariant &chipset, fl::span< CRGB > leds, EOrder rgbOrder, const ChannelOptions &options) FL_NO_EXCEPT
Private constructor (use create() factory method)
ColorProfileStatus colorProfileStatus() const FL_NO_EXCEPT
Definition channel.h:275
CLEDController * asController() FL_NO_EXCEPT
Get pointer to base CLEDController for linked list traversal.
Definition channel.h:192
void syncLegacySettings() FL_NO_EXCEPT
FL_NO_INLINE void encodeFrame(PixelController< RGB, 1, 0xFFFFFFFF > &pixels) FL_NO_EXCEPT
Encode pixels into mChannelData and fire onChannelDataEncoded (#4566).
bool mDisabledDriverWarned
Definition channel.h:449
fl::weak_ptr< IChannelDriver > mDriver
Definition channel.h:433
bool mStaticProfileCleared
Definition channel.h:461
const i32 mId
Definition channel.h:455
FL_NO_INLINE void submitFrame(const fl::shared_ptr< IChannelDriver > &driver) FL_NO_EXCEPT
Submit the encoded frame to driver: disabled-driver drop + one-shot diagnostic, enqueue,...
fl::string mName
Definition channel.h:456
ChannelOptions mSettings
Definition channel.h:459
static ChannelDataPtr create(const ChipsetVariant &chipset, fl::vector_psram< u8 > &&encodedData=fl::vector_psram< u8 >(), ChannelPixelFormat pixelFormat=ChannelPixelFormat::RGB) FL_NO_EXCEPT
Create channel transmission data (modern variant-based API)
Definition data.cpp.hpp:12
LED channel for parallel output, pretty much a CPixelLEDController but with timing and pin informatio...
Definition channel.h:60
fl::shared_ptr< IChannelDriver > findDriverByName(const fl::string &name) const FL_NO_EXCEPT
Silent counterpart to getDriverByName().
static ChannelManager & instance() FL_NO_EXCEPT
Get the global singleton instance.
DriverStatus driverStatus(const fl::string &name) const FL_NO_EXCEPT
Look up a driver's registration status without logging on miss.
@ STATUS_ENABLED
Driver is registered and enabled.
Definition manager.h:199
@ STATUS_DISABLED
Driver is registered but disabled.
Definition manager.h:198
fl::shared_ptr< IChannelDriver > selectDriverForChannel(const ChannelDataPtr &data, const fl::string &affinity) FL_NO_EXCEPT
Select best driver for channel data (used by Channel::showPixels)
static void onCanvasUiSet(CLEDController *strip, const ScreenMap &xymap) FL_NO_EXCEPT
static fl::shared_ptr< const Gamma8 > getOrCreate(float gamma) FL_NO_EXCEPT
Definition ease.cpp.hpp:460
IChannelDriver() FL_NO_EXCEPT=default
Minimal interface for LED channel transmission drivers.
Definition driver.h:52
Adapter class that creates a PixelIterator from any color order.
static XYMap constructRectangularGrid(u16 width, u16 height, u16 offset=0) FL_NO_EXCEPT
Definition xymap.cpp.hpp:36
static XYMap fromXMap(const XMap &xmap) FL_NO_EXCEPT
Create an XYMap from an XMap (treats 1D as 2D with height=1)
Definition xymap.cpp.hpp:68
bool isManaged() const FL_NO_EXCEPT
True when get() hands back the colour-managed iterator.
ReorderingPixelIteratorAny(PixelController< RGB, 1, 0xFFFFFFFF > &pixels, const XYMap *addressing, EOrder rgbOrder, Rgbw rgbw, Rgbww rgbww, const fl::string &channelName, StreamingPipelineQ16 *pipeline, u8 ditherPhase, u8 brightness) FL_NO_EXCEPT
Construct pixel iterator with optional addressing transformation.
PixelIterator & get() FL_NO_EXCEPT
Get the constructed pixel iterator.
static fl::vector< CRGB > & getReorderBufferTLS() FL_NO_EXCEPT
Get thread-local buffer for addressing transformation.
fl::optional< PixelController< RGB, 1, 0xFFFFFFFF > > mAddressedController
virtual void enqueue(ChannelDataPtr) FL_NO_EXCEPT override
Enqueue channel data for transmission.
virtual void show() FL_NO_EXCEPT override
Trigger transmission of enqueued data.
virtual fl::string getName() const FL_NO_EXCEPT override
Get the driver name for affinity binding.
virtual bool canHandle(const ChannelDataPtr &data) const FL_NO_EXCEPT override
Check if this driver can handle the given channel data.
virtual DriverState poll() FL_NO_EXCEPT override
Query driver state and perform maintenance.
virtual Capabilities getCapabilities() const FL_NO_EXCEPT override
Get driver capabilities (clockless, SPI, or both)
T * get() const FL_NO_EXCEPT
Definition shared_ptr.h:342
static string from_literal(const char *literal) FL_NO_EXCEPT
fl::size size() const FL_NO_EXCEPT
void resize(fl::size n) FL_NO_EXCEPT
Definition vector.h:593
void clear() FL_NO_EXCEPT
Definition vector.h:634
T * data() FL_NO_EXCEPT
Definition vector.h:619
#define FL_UNUSED(x)
#define FL_NO_INLINE
Channel transmission data - lightweight DTO for driver transmission.
#define DISABLE_DITHER
Disable dithering.
Definition dither_mode.h:14
UCS7604 LED chipset encoder implementation.
#define FL_WARN_ONCE(...)
Definition log.h:439
#define FL_WARN(...)
Definition log.h:437
#define FL_DBG
Definition log.h:553
#define FL_ERROR(...)
Definition log.h:373
Centralized logging categories for FastLED hardware interfaces and subsystems.
Unified manager for channel drivers with priority-based fallback.
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NO_EXCEPT
Definition unique_ptr.h:263
shared_ptr< T > make_shared(Args &&... args) FL_NO_EXCEPT
Definition shared_ptr.h:423
FL_NO_INLINE bool tryEncodeManagedSpi(Channel &channel, PixelIterator &pixels, bool managed, fl::vector_psram< u8 > &output, SpiChipset chipset) FL_NO_EXCEPT
Shared color-managed SPI override check.
void writeUCS7604(fl::vector_psram< u8 > *data, PixelIterator &pixelIterator, ClocklessEncoder encoder, const ChannelOptions &settings, EOrder rgbOrder, bool managed) FL_NO_EXCEPT
Encode UCS7604 pixel data into the channel data buffer.
static FL_NO_INLINE void emitDisabledDriverError(const fl::string &channelName, const fl::string &driverName, const fl::string &exclusive) FL_NO_EXCEPT
Out-of-line cold-path emitter for the #2517 silent-drop DISABLED-driver diagnostic in Channel::showPi...
SpiChipset spiChipsetFor(const Channel &channel) FL_NO_EXCEPT
void enable_rgbww_conversion() FL_NO_EXCEPT
Installs the RGBWW pixel conversion (see enable_rgbw_conversion, #4795).
bool & colorProfileStrictMode() FL_NO_EXCEPT
void enable_rgbw_conversion() FL_NO_EXCEPT
Installs the RGBW pixel conversion used by PixelIterator.
Definition rgbw.cpp.hpp:732
void enable_rgbw_power_estimate() FL_NO_EXCEPT
Lets the power limiter charge a controller's RGBW conversion.
SourceProfile & defaultSourceProfile() FL_NO_EXCEPT
CurrentControl brightness() FL_NO_EXCEPT
Get current global UCS7604 brightness value.
back_insert_iterator< Container > back_inserter(Container &c) FL_NO_EXCEPT
Helper function to create a back_insert_iterator.
Definition iterator.h:139
unsigned char u8
Definition stdint.h:131
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
ClocklessEncoder
Identifies which encoder to use for clockless chipsets in the Channel API.
@ CLOCKLESS_ENCODER_UCS7604_8BIT
UCS7604 8-bit 800KHz.
@ CLOCKLESS_ENCODER_TM1812_RGBWW
TM1812: two 5-byte RGBWW pixels in each 12-byte IC frame.
@ CLOCKLESS_ENCODER_UCS7604_16BIT
UCS7604 16-bit 800KHz.
@ CLOCKLESS_ENCODER_UCS7604_16BIT_1600
UCS7604 16-bit 1600KHz.
@ CLOCKLESS_ENCODER_WS2812
Default, no preamble (WS2812 and compatible)
@ CLOCKLESS_ENCODER_TM1908
TM1908: command prefix followed by RGB pixels.
AtomicFake< T > atomic
Definition atomic.h:26
IChannelDriver * getStubChannelEngine() FL_NO_EXCEPT
Get stub channel driver for testing or unsupported platforms.
MapRedBlackTree< Key, T, Compare, fl::allocator_slab< char > > map
Definition map.h:283
constexpr u32 gamma(float g) FL_NO_EXCEPT
Definition gamma_lut.h:36
u8 u8 height
Definition blur.h:186
Optional< T > optional
Definition optional.h:16
void prepare_rgbw_colorimetric(const Rgbw &) FL_NO_EXCEPT
Definition rgbw.cpp.hpp:626
const char * busDriverName(Bus b, fl::u8 which=0, bool spi=false) FL_NO_EXCEPT
Concrete ChannelManager driver name for a portable bus.
Definition bus.h:170
constexpr fl::size kColorPipelineSourceStorage
The two things Channel needs from the colour pipeline, reached through pointers rather than by name.
fl::variant< ClocklessChipset, SpiChipsetConfig > ChipsetVariant
Definition config.h:157
ColorPipelineHooks & colorPipelineHooks() FL_NO_EXCEPT
The installed hooks.
@ AUTO
Definition bus.h:28
u8 width
Definition blur.h:186
struct FL_ALIGNAS(4) Wave3BitExpansionLut
Lookup table for nibble-to-waveform expansion in wave3 format (32 bytes)
Definition wave3.h:32
constexpr fl::size kColorPipelineIteratorStorage
UCS7604Mode
UCS7604 protocol configuration modes.
Definition ucs7604.h:30
@ UCS7604_MODE_8BIT_800KHZ
Definition ucs7604.h:31
@ UCS7604_MODE_16BIT_1600KHZ
Definition ucs7604.h:33
@ UCS7604_MODE_16BIT_800KHZ
Definition ucs7604.h:32
string to_string(T value) FL_NO_EXCEPT
Definition string.h:459
EOrder
RGB color channel orderings, used when instantiating controllers to determine what order the controll...
Definition eorder.h:15
void encodeUCS7604(PixelIterator &pixel_iter, size_t num_leds, OutputIterator out, UCS7604Mode mode, const UCS7604CurrentControl &current, bool is_rgbw, const Gamma8 *gamma=nullptr, bool wide_source=false) FL_NO_EXCEPT
Encode complete UCS7604 frame (preamble + padding + pixel data)
Definition ucs7604.h:273
SpiChipset
LED chipsets with SPI interface (clocked protocols) Modern type-safe enum class - prefer this for new...
@ SM16716
SM16716 LED chipset.
@ LPD6803
LPD6803 LED chipset.
@ APA102HD
APA102 LED chipset with 5-bit gamma correction.
@ HD107
Same as APA102, but in turbo 40-mhz mode.
@ DOTSTARHD
APA102HD LED chipset alias.
@ SK9822HD
SK9822 LED chipset with 5-bit gamma correction.
@ SK9822
SK9822 LED chipset.
@ DOTSTAR
APA102 LED chipset alias.
@ P9813
P9813 LED chipset.
@ APA102
APA102 LED chipset.
@ HD108
16-bit variant of HD107, always gamma corrected. No SD (standard definition) option available - all H...
@ LPD8806
LPD8806 LED chipset.
@ WS2803
WS2803 LED chipset.
@ MY9221
MY9221 12-channel LED driver (dual-edge clocked; 4 RGB LEDs per chip)
@ WS2801
WS2801 LED chipset.
@ HD107HD
Same as APA102HD, but in turbo 40-mhz mode.
Base definition for an LED controller.
Definition crgb.hpp:179
void(* destroyIterator)(void *source_storage, void *iterator_storage)
Destroys what makeIterator built. Null until installed.
bool(* encodeManagedSpi)(PixelIterator &pixels, vector_psram< u8 > *out, SpiChipset chip, const Channel &controller)
Unscaled (full-brightness) power demand of leds as the pipeline will drive them, in mW,...
PixelIterator *(* makeIterator)(void *source_storage, void *iterator_storage, PixelController< RGB, 1, 0xFFFFFFFF > &controller, EOrder order, ColorPipelineFrameRef pipeline, const Rgbw &rgbw, Rgbww rgbww, u8 dither_phase, u8 brightness)
Constructs a colour-managed PixelIterator in caller-provided storage.
Payload shared by every color-profile notification.
Everything the per-pixel path needs, derived once when a profile binds.
Definition pipeline.h:73
#define FL_COLOR_PIPELINE_SHARED
Shared ownership of a channel's colour pipeline, the controller accessor that hands it out,...
Definition options.h:60
Low level pixel data writing class.
#define FL_ALIGNAS(N)
fl::i64 i64
Definition stdint.h:221
Pixel controller class.
@ Black
<div style='background:#000000;width:4em;height:4em;'></div>
Definition crgb.h:517
Representation of an 8-bit RGB pixel (Red, Green, Blue)
Definition crgb.h:38
Configuration for a single LED channel.
Definition config.h:167
static ChannelEvents & instance() FL_NO_EXCEPT
Optional channel configuration parameters All fields have sensible defaults and can be overridden as ...
Definition options.h:92
Runtime bit-period timing for a clockless chipset.
ClocklessEncoder encoder
Byte-level encoding pipeline (default: WS2812)
Definition config.h:36
Clockless chipset configuration (single data pin)
Definition config.h:33
Driver capabilities.
Definition driver.h:154
Driver state with optional error message.
Definition driver.h:168
SPI chipset configuration (data + clock pins)
Definition config.h:103
u8 g
Green channel current (0x0-0xF)
Definition ucs7604.h:39
u8 b
Blue channel current (0x0-0xF)
Definition ucs7604.h:40
u8 w
White channel current (0x0-0xF)
Definition ucs7604.h:41
u8 r
Red channel current (0x0-0xF)
Definition ucs7604.h:38
UCS7604 current control structure with 4-bit fields for each channel.
Definition ucs7604.h:37
#define FL_SCOPED_TRACE
Definition trace.h:128
UCS7604 LED chipset controller implementation for FastLED.