FastLED 3.10.8
Loading...
Searching...
No Matches
apa102.h
Go to the documentation of this file.
1#pragma once
2
5#include "crgb.h"
6#include "eorder.h"
8#include "fastspi.h"
9// IWYU pragma: begin_keep
10#include "platforms/shared/spi_pixel_writer.h" // ok platform headers
11#include "fl/stl/noexcept.h"
12// IWYU pragma: end_keep
13
15//
16// APA102 definition - takes data/clock/select pin values (N.B. should take an SPI definition?)
17//
19
25template <
28 // APA102 has a bug where long strip can't handle full speed due to clock degredation.
29 // This only affects long strips, but then again if you have a short strip does 6 mhz actually slow
30 // you down? Probably not. And you can always bump it up for speed. Therefore we are prioritizing
31 // "just works" over "fastest possible" here.
32 // https://www.pjrc.com/why-apa102-leds-have-trouble-at-24-mhz/
33 fl::u32 SPI_SPEED = DATA_RATE_MHZ(6),
35 fl::u32 START_FRAME = 0x00000000,
36 // The vendor datasheet requires all ones for the end clocks. These clocks
37 // shift the final LED frame through a long cascade; a zero (or partial)
38 // end frame is not a substitute because it can be misread as a start
39 // frame. Matches encodeAPA102()/encodeSK9822() in fl/chipsets/encoders/.
40 fl::u32 END_FRAME = 0xFFFFFFFF
41>
42class APA102Controller : public CPixelLEDController<RGB_ORDER> {
43 typedef fl::SPIOutput<DATA_PIN, CLOCK_PIN, SPI_SPEED> SPI;
45
47 mSPI.writeWord(START_FRAME >> 16);
48 mSPI.writeWord(START_FRAME & 0xFFFF);
49 }
50 void endBoundary(int nLeds) FL_NO_EXCEPT {
51 int nDWords = (nLeds/32);
52 const fl::u8 b0 = fl::u8(END_FRAME >> 24 & 0x000000ff);
53 const fl::u8 b1 = fl::u8(END_FRAME >> 16 & 0x000000ff);
54 const fl::u8 b2 = fl::u8(END_FRAME >> 8 & 0x000000ff);
55 const fl::u8 b3 = fl::u8(END_FRAME >> 0 & 0x000000ff);
56 do {
57 mSPI.writeByte(b0);
58 mSPI.writeByte(b1);
59 mSPI.writeByte(b2);
60 mSPI.writeByte(b3);
61 } while(nDWords--);
62 }
63
65#ifdef FASTLED_SPI_BYTE_ONLY
66 mSPI.writeByte(0xE0 | brightness);
67 mSPI.writeByte(b0);
68 mSPI.writeByte(b1);
69 mSPI.writeByte(b2);
70#else
71 fl::u16 b = 0xE000 | (brightness << 8) | (fl::u16)b0;
72 mSPI.writeWord(b);
73 fl::u16 w = b1 << 8;
74 w |= b2;
75 mSPI.writeWord(w);
76#endif
77 }
78
80#ifdef FASTLED_SPI_BYTE_ONLY
81 mSPI.writeByte(b1);
82 mSPI.writeByte(b2);
83#else
84 mSPI.writeWord(fl::u16(b1) << 8 | b2);
85#endif
86 }
87
88public:
90 #if defined(FL_IS_ESP32)
92 mSPI.setBus(bus);
93 return *this;
94 }
95 #endif
96
97 virtual void init() FL_NO_EXCEPT override {
98 mSPI.init();
99 }
100
101protected:
103 virtual void showPixels(PixelController<RGB_ORDER> & pixels) FL_NO_EXCEPT override {
104 switch (GAMMA_CORRECTION_MODE) {
106 showPixelsDefault(pixels);
107 break;
108 }
111 break;
112 }
113 }
114 }
115
116private:
117
120 fl::u8* out_s0, fl::u8* out_s1, fl::u8* out_s2, fl::u8* out_brightness) FL_NO_EXCEPT {
121#if FASTLED_HD_COLOR_MIXING
123 pixels.loadRGBScaleAndBrightness(out_s0, out_s1, out_s2, &brightness);
124 struct Math {
125 static fl::u16 map(fl::u16 x, fl::u16 in_min, fl::u16 in_max, fl::u16 out_min, fl::u16 out_max) FL_NO_EXCEPT { // okay static in header
126 const fl::u16 run = in_max - in_min;
127 const fl::u16 rise = out_max - out_min;
128 const fl::u16 delta = x - in_min;
129 return (delta * rise) / run + out_min;
130 }
131 };
132 // *out_brightness = Math::map(brightness, 0, 255, 0, 31);
133 fl::u16 bri = Math::map(brightness, 0, 255, 0, 31);
134 if (bri == 0 && brightness != 0) {
135 // Fixes https://github.com/FastLED/FastLED/issues/1908
136 bri = 1;
137 }
138 *out_brightness = static_cast<fl::u8>(bri);
139 return;
140#else
141 fl::u8 s0, s1, s2;
142 pixels.loadAndScaleRGB(&s0, &s1, &s2);
143#if FASTLED_USE_GLOBAL_BRIGHTNESS == 1
144 // This function is pure magic.
145 const fl::u16 maxBrightness = 0x1F;
146 // Use sequential comparisons to avoid nested fl::max (helps AVR register allocation)
147 fl::u8 max_s01 = (s0 > s1) ? s0 : s1;
148 fl::u8 max_component = (max_s01 > s2) ? max_s01 : s2;
149 fl::u16 brightness = ((((fl::u16)max_component + 1) * maxBrightness - 1) >> 8) + 1;
150 s0 = (maxBrightness * s0 + (brightness >> 1)) / brightness;
151 s1 = (maxBrightness * s1 + (brightness >> 1)) / brightness;
152 s2 = (maxBrightness * s2 + (brightness >> 1)) / brightness;
153#else
154 const fl::u8 brightness = 0x1F;
155#endif // FASTLED_USE_GLOBAL_BRIGHTNESS
156 *out_s0 = s0;
157 *out_s1 = s1;
158 *out_s2 = s2;
159 *out_brightness = static_cast<fl::u8>(brightness);
160#endif // FASTLED_HD_COLOR_MIXING
161 }
162
163 // Legacy showPixels implementation.
165 mSPI.select();
166 fl::u8 s0, s1, s2, global_brightness;
167 getGlobalBrightnessAndScalingFactors(pixels, &s0, &s1, &s2, &global_brightness);
169 while (pixels.has(1)) {
170 fl::u8 c0, c1, c2;
171 pixels.loadAndScaleRGB(&c0, &c1, &c2);
172 writeLed(global_brightness, c0, c1, c2);
173 pixels.stepDithering();
174 pixels.advanceData();
175 }
176 endBoundary(pixels.size());
177
178 mSPI.endTransaction();
179
180 // Finalize transmission (no-op on non-ESP32, flushes Quad-SPI on ESP32)
181 mSPI.finalizeTransmission();
182 }
183
185 static constexpr fl::u16 kBatchSize = 8;
186 const fl::u16 n = static_cast<fl::u16>(pixels.size());
187
188 // Extract uniform color scale and brightness (constant across all pixels)
189 fl::u8 scale_r, scale_g, scale_b, global_brightness;
190 #if FASTLED_HD_COLOR_MIXING
191 pixels.loadRGBScaleAndBrightness(&scale_r, &scale_g, &scale_b, &global_brightness);
192 #else
193 pixels.loadAndScaleRGB(&scale_r, &scale_g, &scale_b);
194 global_brightness = 255;
195 #endif
196 const CRGB colors_scale(scale_r, scale_g, scale_b);
197
198 // RGB reorder indices (compile-time constant from RGB_ORDER template param)
199 const fl::u8 b0_index = (static_cast<int>(RGB_ORDER) >> 6) & 0x3;
200 const fl::u8 b1_index = (static_cast<int>(RGB_ORDER) >> 3) & 0x3;
201 const fl::u8 b2_index = static_cast<int>(RGB_ORDER) & 0x3;
202
203 mSPI.select();
205
206 // Process pixels in batches of 8 using stack-allocated buffers
207 fl::u16 remaining = n;
208 while (remaining > 0) {
209 const fl::u16 batch = (remaining < kBatchSize) ? remaining : kBatchSize;
210 CRGB input_buf[kBatchSize];
211 fl::CRGBA5 gamma_buf[kBatchSize];
212
213 // Copy raw pixel bytes into CRGB input buffer
214 for (fl::u16 i = 0; i < batch; ++i) {
215 const fl::u8* raw = pixels.getRawPixelData();
216 input_buf[i] = CRGB(raw[0], raw[1], raw[2]);
217 pixels.advanceData();
218 }
219
221 fl::span<const CRGB>(input_buf, batch), colors_scale, global_brightness,
222 fl::span<fl::CRGBA5>(gamma_buf, batch));
223
224 for (fl::u16 i = 0; i < batch; ++i) {
225 const CRGB& rgb = gamma_buf[i].color;
226 writeLed(gamma_buf[i].brightness_5bit,
227 rgb.raw[b0_index], rgb.raw[b1_index], rgb.raw[b2_index]);
228 }
229
230 remaining -= batch;
231 }
232
233 endBoundary(n);
234 mSPI.endTransaction();
235 mSPI.finalizeTransmission();
236 }
237
238public:
243 static constexpr fl::u8 getPaddingByte() FL_NO_EXCEPT { return 0xFF; }
244
249 static fl::span<const fl::u8> getPaddingLEDFrame() FL_NO_EXCEPT { // okay static in header
250 // APA102 LED frame format: [111BBBBB][B][G][R]
251 // Black LED: 0xE0 (brightness=0), RGB=0,0,0
252 static const fl::u8 frame[] = { // okay static in header
253 0xE0, // Brightness byte (111 00000 = brightness 0)
254 0x00, // Blue = 0
255 0x00, // Green = 0
256 0x00 // Red = 0
257 };
258 return fl::span<const fl::u8>(frame, 4);
259 }
260
263 static constexpr size_t getPaddingLEDFrameSize() FL_NO_EXCEPT {
264 return 4;
265 }
266
271 static constexpr size_t calculateBytes(size_t num_leds) FL_NO_EXCEPT {
272 // APA102 protocol:
273 // - Start frame: 4 bytes (0x00000000)
274 // - LED data: 4 bytes per LED (brightness + RGB)
275 // - End frame: (num_leds / 32) + 1 DWords = 4 * ((num_leds / 32) + 1) bytes
276 return 4 + (num_leds * 4) + (4 * ((num_leds / 32) + 1));
277 }
278
281 static constexpr fl::u32 getStartFrame() FL_NO_EXCEPT { return START_FRAME; }
282
285 static constexpr fl::u32 getEndFrame() FL_NO_EXCEPT { return END_FRAME; }
286};
287
293template <
297 // APA102 has a bug where long strip can't handle full speed due to clock degredation.
298 // This only affects long strips, but then again if you have a short strip does 6 mhz actually slow
299 // you down? Probably not. And you can always bump it up for speed. Therefore we are prioritizing
300 // "just works" over "fastest possible" here.
301 // https://www.pjrc.com/why-apa102-leds-have-trouble-at-24-mhz/
302 fl::u32 SPI_SPEED = DATA_RATE_MHZ(6)
303>
305 DATA_PIN,
306 CLOCK_PIN,
307 RGB_ORDER,
308 SPI_SPEED,
309 fl::FiveBitGammaCorrectionMode::kFiveBitGammaCorrectionMode_BitShift,
310 fl::u32(0x00000000),
311 fl::u32(0xFFFFFFFF)> {
312public:
315};
316
322template <
323 fl::u8 DATA_PIN,
324 fl::u8 CLOCK_PIN,
326 fl::u32 SPI_SPEED = DATA_RATE_MHZ(12)
327>
329 DATA_PIN,
330 CLOCK_PIN,
331 RGB_ORDER,
332 SPI_SPEED,
333 fl::FiveBitGammaCorrectionMode::kFiveBitGammaCorrectionMode_Null,
334 0x00000000,
335 0xFFFFFFFF
336> {
337};
338
344template <
348 fl::u32 SPI_SPEED = DATA_RATE_MHZ(12)
349>
351 DATA_PIN,
352 CLOCK_PIN,
353 RGB_ORDER,
354 SPI_SPEED,
355 fl::FiveBitGammaCorrectionMode::kFiveBitGammaCorrectionMode_BitShift,
356 0x00000000,
357 0xFFFFFFFF
358> {
359};
360
362template <
366 fl::u32 SPI_SPEED = DATA_RATE_MHZ(40)
367>
369 DATA_PIN,
370 CLOCK_PIN,
371 RGB_ORDER,
372 SPI_SPEED,
373 fl::FiveBitGammaCorrectionMode::kFiveBitGammaCorrectionMode_Null,
374 0x00000000,
375 0xFFFFFFFF
376> {};
377
379template <
383 fl::u32 SPI_SPEED = DATA_RATE_MHZ(40)
384>
386 DATA_PIN,
387 CLOCK_PIN,
388 RGB_ORDER,
389 SPI_SPEED> {
390};
fl::UISlider brightness("Brightness", BRIGHTNESS, 0, 255)
int x
Definition simple.h:92
#define DATA_PIN
Definition ClientReal.h:82
#define RGB_ORDER
void showPixelsDefault(PixelController< RGB_ORDER > &pixels) FL_NO_EXCEPT
Definition apa102.h:164
void endBoundary(int nLeds) FL_NO_EXCEPT
Definition apa102.h:50
static void getGlobalBrightnessAndScalingFactors(PixelController< RGB_ORDER > &pixels, fl::u8 *out_s0, fl::u8 *out_s1, fl::u8 *out_s2, fl::u8 *out_brightness) FL_NO_EXCEPT
Definition apa102.h:118
static fl::span< const fl::u8 > getPaddingLEDFrame() FL_NO_EXCEPT
Get padding LED frame for synchronized latching in quad-SPI Returns a black LED frame to prepend to s...
Definition apa102.h:249
FASTLED_FORCE_INLINE void writeLed(fl::u8 brightness, fl::u8 b0, fl::u8 b1, fl::u8 b2) FL_NO_EXCEPT
Definition apa102.h:64
APA102Controller() FL_NO_EXCEPT
Definition apa102.h:89
void startBoundary() FL_NO_EXCEPT
Definition apa102.h:46
virtual void init() FL_NO_EXCEPT override
Initialize the LED controller.
Definition apa102.h:97
static constexpr fl::u8 getPaddingByte() FL_NO_EXCEPT
Get the protocol-safe padding byte for APA102 Used for quad-SPI lane padding when strips have differe...
Definition apa102.h:243
void showPixelsGammaBitShift(PixelController< RGB_ORDER > &pixels) FL_NO_EXCEPT
Definition apa102.h:184
static constexpr fl::u32 getStartFrame() FL_NO_EXCEPT
Get the configured start frame DWord.
Definition apa102.h:281
fl::SPIOutput< DATA_PIN, CLOCK_PIN, SPI_SPEED > SPI
Definition apa102.h:43
static constexpr size_t calculateBytes(size_t num_leds) FL_NO_EXCEPT
Calculate total byte count for APA102 protocol Used for quad-SPI buffer pre-allocation.
Definition apa102.h:271
FASTLED_FORCE_INLINE void write2Bytes(fl::u8 b1, fl::u8 b2) FL_NO_EXCEPT
Definition apa102.h:79
virtual void showPixels(PixelController< RGB_ORDER > &pixels) FL_NO_EXCEPT override
Send the LED data to the strip.
Definition apa102.h:103
static constexpr size_t getPaddingLEDFrameSize() FL_NO_EXCEPT
Get size of padding LED frame in bytes.
Definition apa102.h:263
static constexpr fl::u32 getEndFrame() FL_NO_EXCEPT
Get the configured end frame DWord.
Definition apa102.h:285
APA102ControllerHD() FL_NO_EXCEPT=default
CPixelLEDController(RegistrationMode mode) FL_NO_EXCEPT
HD107 is just the APA102 with a default 40Mhz clock rate.
Definition apa102.h:376
HD107HD is just the APA102HD with a default 40Mhz clock rate.
Definition apa102.h:389
SK9822 controller class.
Definition apa102.h:358
SK9822 controller class.
Definition apa102.h:336
CLEDController & setSpiBus(Esp32SpiBus bus) FL_NO_EXCEPT
Select an ESP-IDF SPI host for this clocked controller before init().
fl::CLEDController CLEDController
#define FASTLED_FORCE_INLINE
defines the templated version of the CLEDController class
fl::EOrder EOrder
Definition eorder.h:12
constexpr EOrder RGB
Definition eorder.h:17
#define DATA_RATE_MHZ
Definition fastspi.h:95
Serial peripheral interface (SPI) definitions per platform.
Declares functions for five-bit gamma correction.
fl::CRGB CRGB
Definition crgb.h:25
unsigned char u8
Definition stdint.h:131
Esp32SpiBus
ESP-IDF SPI host selection for an individual clocked LED controller.
Definition spi_bus.h:18
FiveBitGammaCorrectionMode
FL_OPTIMIZE_FUNCTION void five_bit_hd_gamma_bitshift(fl::span< const CRGB > colors, CRGB colors_scale, u8 global_brightness, fl::span< CRGB > out_colors, fl::span< u8 > out_power_5bit) FL_NO_EXCEPT
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NO_EXCEPT
Pixel controller class.