FastLED 3.10.8
Loading...
Searching...
No Matches
ObjectFLED.cpp.hpp
Go to the documentation of this file.
1/* ObjectFLED - Teensy 4.x DMA to all pins for independent control of large and
2 multiple LED display objects
3
4 Copyright (c) 2024 Kurt Funderburg
5
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12
13 The above copyright notice and this permission notice shall be included in
14 all copies or substantial portions of the Software.
15
16 OctoWS2811 library code was well-studied and substantial portions of it used
17 to implement high-speed, non-blocking DMA for LED signal output in this library.
18 See below for a summary of changes made to the original OctoWS2811.
19
20 OctoWS2811 - High Performance WS2811 LED Display Library
21 Copyright (c) 2013 Paul Stoffregen, PJRC.COM, LLC
22 http://www.pjrc.com/teensy/td_libs_OctoWS2811.html
23
24 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
30 THE SOFTWARE.
31*/
32/*
33Teensy 4.0 pin - port assignments
34GPIO6List = { 0, 1, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27 } //12 top, 4 bottom
35GPIO7List = { 6, 7, 8, 9, 10, 11, 12, 13, 32 } //8 top, 1 bottom
36GPIO8List = { 28, 30, 31, 34, 35, 36, 37, 38, 39 } //0 top, 9 bottom
37GOIO9List = { 2, 3, 4, 5, 29, 33 } //4 top, 2 bottom
38Teensy 4.1 pin - port assignments
39GPIO6List = { 0, 1, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 38, 39, 40, 41 } //20 top, 0 bottom
40GPIO7List = { 6, 7, 8, 9, 10, 11, 12, 13, 32, 34, 35, 36, 37 } //13 top, 0 bottom
41GPIO8List = { 28, 30, 31, 42, 43, 44, 45, 46, 47 } //3 top, 6 bottom
42GOIO9List = { 2, 3, 4, 5, 29, 33, 48, 49, 50, 51, 52, 53, 54 } //6 top, 7 bottom
43* 4 pin groups, 4 timer groups, !4 dma channel groups: only 1 DMA group (4 ch) maps to GPIO (DMAMUX
44* mapping) Also, only DMA ch0-3 have periodic mode for timer trigger (p77 manual). Separate objects
45* cannot DMA at once.
46*
47* CHANGES:
48* Moved some variables so class supports multiple instances with separate LED config params
49* Implemented context switching so multiple instances can show() independently
50* Re-parameterized TH_TL, T0H, T1H, OC_FACTOR; recalc time for latch at end of show()
51* Added genFrameBuffer() to implement RGB order, brightness, color balance, and serpentine
52* Added setBrightness(), setBalance()
53* FrameBuffer no longer passed in, constructor now creates buffer; destructor added
54* Added support for per-object setting of OC factor, TH+TL, T0H, T1H, and LATCH_DELAY in begin function
55* Set DSE=3, SPEED=0, SRE=0 on output pins per experiment & PJRC forum guidance
56* New default values for TH_TL, T0H, T1H, LATCH_DELAY to work with Audio lib and more LED types
57* Added wait for prior xmission to complete in destructor
58*/
59
60#include "platforms/arm/teensy/is_teensy.h"
61
62#ifndef FL_IS_TEENSY_4X
63// Do nothing for other platforms.
64#else
65#include "ObjectFLED.h"
68#include "fl/log/log.h"
69
70#ifndef MIN
71#define MIN(a,b) ((a)<(b)?(a):(b))
72#endif
73
74#ifndef MAX
75#define MAX(a,b) ((a)>(b)?(a):(b))
76#endif
77
78namespace fl {
79
80volatile bool dma_first;
81
82
83ObjectFLED::ObjectFLED(uint16_t numLEDs, void *drawBuf, uint8_t config, uint8_t numPins, \
84 const uint8_t *pinList, uint8_t serpentine) {
85 serpNumber = serpentine;
86 drawBuffer = drawBuf;
87 params = config;
88 if (numPins > NUM_DIGITAL_PINS) {
89 // #3416 OF-LOW-2: warn on silent truncation so users debugging
90 // "why is my multi-strip setup short on pins" see the cause.
91 // Serial.print, not Serial.printf: printf links ~16 KB of newlib
92 // formatting into ITCM (RAM1) for every Teensy 4 sketch (#4709).
93 Serial.print("ObjectFLED: numPins=");
94 Serial.print((unsigned)numPins);
95 Serial.print(" exceeds NUM_DIGITAL_PINS=");
96 Serial.print((unsigned)NUM_DIGITAL_PINS);
97 Serial.print("; truncating\r\n");
98 numPins = NUM_DIGITAL_PINS;
99 }
100 // CodeRabbit-flagged on PR #3419: guard against numPins=0 which
101 // would otherwise divide by zero in stripLen = numLEDs / numpinsLocal.
102 if (numPins == 0) {
103 Serial.print("ObjectFLED: numPins=0 is invalid; abandoning init\r\n");
104 numpinsLocal = 0;
105 stripLen = 0;
106 initialized = false;
107 return;
108 }
109 numpinsLocal = numPins;
110 stripLen = numLEDs / numpinsLocal;
111 // #3416 OF-LOW-8: warn if numLEDs doesn't divide evenly across pins
112 // -- tail LEDs are silently dropped from the per-strip stride.
113 if (numpinsLocal > 0 && numLEDs % numpinsLocal != 0) {
114 Serial.print("ObjectFLED: numLEDs=");
115 Serial.print((unsigned)numLEDs);
116 Serial.print(" not divisible by numpins=");
117 Serial.print((unsigned)numpinsLocal);
118 Serial.print("; last ");
119 Serial.print((unsigned)(numLEDs % numpinsLocal));
120 Serial.print(" LED(s) ignored\r\n");
121 }
122 memcpy(pinlist, pinList, numpinsLocal);
123 if ((params & 0x3F) < 6) {
124 frameBufferLocal = new uint8_t[numLEDs * 3];
125 numbytesLocal = stripLen * 3; // RGB formats
126 }
127 else {
128 frameBufferLocal = new uint8_t[numLEDs * 4];
129 numbytesLocal = stripLen * 4; // RGBW formats
130 }
131} // ObjectFLED constructor
132
133
134extern "C" void xbar_connect(unsigned int input, unsigned int output); // in pwm.c
135static volatile uint32_t *standard_gpio_addr(volatile uint32_t *fastgpio) {
136 return (volatile uint32_t *)((uint32_t)fastgpio - 0x01E48000);
137}
138
139static void force_pin_to_gpio_mux(uint8_t pin) {
140 // Teensy 4.x GPIO pads use MUX_MODE=5. Keep SION set so diagnostics can
141 // observe the pad input path while DMA drives the standard GPIO alias.
142 // #3416 OF-LOW-5: SION=1 enables the pad input keeper even on a
143 // pure-output pin and can leak ~1-2 mA per pin plus capacitively
144 // couple noise back into the input keeper on long traces. Gate
145 // behind a define so production users can drop the keeper if not
146 // using the loopback RX path for diagnostics.
147#ifdef FASTLED_OBJECTFLED_PROBE_INPUT
148 *portConfigRegister(pin) = 5 | 0x10; // ALT5 (GPIO) + SION
149#else
150 *portConfigRegister(pin) = 5; // ALT5 (GPIO) only
151#endif
152}
153
154static void configure_objectfled_xbar_dma_edges() {
155 // XBAR CTRL registers hold two request-control slots each. STS bits are
156 // write-one-to-clear, so keep non-owned STS bits at zero while preserving
157 // their edge/interrupt/DMA enable settings.
158 constexpr uint16_t ctrl0ConfigMask =
159 XBARA_CTRL_EDGE0(3) | XBARA_CTRL_DEN0 |
160 XBARA_CTRL_EDGE1(3) | XBARA_CTRL_DEN1;
161 constexpr uint16_t ctrl1ConfigMask =
162 XBARA_CTRL_EDGE0(3) | XBARA_CTRL_DEN0;
163
164 uint16_t ctrl0 = XBARA1_CTRL0;
165 ctrl0 &= ~(ctrl0ConfigMask | XBARA_CTRL_STS0 | XBARA_CTRL_STS1);
166 ctrl0 |= XBARA_CTRL_STS1 | XBARA_CTRL_EDGE1(3) | XBARA_CTRL_DEN1 |
167 XBARA_CTRL_STS0 | XBARA_CTRL_EDGE0(3) | XBARA_CTRL_DEN0;
168 XBARA1_CTRL0 = ctrl0;
169
170 uint16_t ctrl1 = XBARA1_CTRL1;
171 ctrl1 &= ~(ctrl1ConfigMask | XBARA_CTRL_STS0 | XBARA_CTRL_STS1);
172 ctrl1 |= XBARA_CTRL_STS0 | XBARA_CTRL_EDGE0(3) | XBARA_CTRL_DEN0;
173 XBARA1_CTRL1 = ctrl1;
174}
175
176static void clear_objectfled_xbar_dma_status() {
177 uint16_t ctrl0 = XBARA1_CTRL0;
178 ctrl0 &= ~(XBARA_CTRL_STS0 | XBARA_CTRL_STS1);
179 XBARA1_CTRL0 = ctrl0 | XBARA_CTRL_STS1 | XBARA_CTRL_STS0;
180
181 uint16_t ctrl1 = XBARA1_CTRL1;
182 ctrl1 &= ~(XBARA_CTRL_STS0 | XBARA_CTRL_STS1);
183 XBARA1_CTRL1 = ctrl1 | XBARA_CTRL_STS0;
184}
185
186static bool objectfled_has_dma_tcds(const ObjectFLEDDmaManager& dma) {
187 return dma.dma1.TCD != nullptr &&
188 dma.dma2.TCD != nullptr &&
189 dma.dma3.TCD != nullptr &&
190 dma.dma2next.TCD != nullptr;
191}
192
193
194void ObjectFLED::begin(uint16_t latchDelay) {
195 LATCH_DELAY = latchDelay;
196 begin();
197}
198
199
200void ObjectFLED::begin(uint16_t t1, uint16_t t2, uint16_t t3, uint16_t latchDelay) {
201 // Convert 3-phase timing (T1, T2, T3) to ObjectFLED's internal format
202 // See fl/clockless/timing_conversion.h for details
203 // T1 = T0H (high time for '0' bit)
204 // T1 + T2 = T1H (high time for '1' bit)
205 // T1 + T2 + T3 = total period
206 uint16_t clk_ns = t1 + t2 + t3;
207 uint16_t t0h_ns = t1;
208 uint16_t t1h_ns = t1 + t2;
209 beginInternal(clk_ns, t0h_ns, t1h_ns, latchDelay);
210}
211
212void ObjectFLED::beginInternal(uint16_t period, uint16_t t0h, uint16_t t1h, uint16_t latchDelay) {
213 TH_TL = period;
214 T0H = t0h;
215 T1H = t1h;
216 LATCH_DELAY = latchDelay;
217 begin();
218}
219
220
221// INPUT stripLen, frameBuffer, params, numPins, pinList
222// GPIOR bits set for pins[i] -> bitmask, pin_bitnum[i], pin_offset[i]
223// init timers, xbar to DMA, DMA bitdata -> GPIOR; clears frameBuffer (total LEDs * 3 bytes)
224void ObjectFLED::begin(void) {
225 auto& dma = ObjectFLEDDmaManager::getInstance();
226 initialized = false;
227
228 // Set each pin's bitmask bit, store offset & bit# for pin
229 uint32_t tempBitmask[4] = {0};
230 uint8_t validPinCount = 0;
231 for (uint32_t i=0; i < numpinsLocal; i++) {
232 uint8_t pin = pinlist[i];
233
234 // Validate pin using validation helper
235 auto validation = objectfled::validate_teensy4_pin(pin);
236 if (!validation.valid) {
237 FL_WARN("================================================================================");
238 FL_WARN("FASTLED ERROR: Pin " << (int)pin << " is INVALID and has been disabled");
239 FL_WARN(validation.error_message);
240 FL_WARN("================================================================================");
241 continue;
242 }
243
244 // Check for warnings (pin is valid but may have issues)
245 if (validation.error_message != nullptr) {
246 FL_WARN("================================================================================");
247 FL_WARN("FASTLED WARNING: Pin " << (int)pin << " may have issues");
248 FL_WARN(validation.error_message);
249 FL_WARN("================================================================================");
250 }
251
252 uint8_t bit = digitalPinToBit(pin); // pin's bit index in word port DR
253 // which GPIO R controls this pin: 0-3 map to GPIO6-9 then map to DMA compat GPIO1-4
254 uint8_t offset = ((uint32_t)portOutputRegister(pin) - (uint32_t)&GPIO6_DR) >> 14;
255 if (offset > 3) {
256 FL_WARN("================================================================================");
257 FL_WARN("FASTLED ERROR: Pin " << (int)pin << " does not map to GPIO6-9 (offset=" << (int)offset << ")");
258 FL_WARN("This pin may be a ground/power/read-only pin - strip disabled");
259 FL_WARN("================================================================================");
260 continue;
261 }
262
263 validPinCount++;
264 pin_bitnumLocal[i] = bit; //local copy for context switch
265 pin_offsetLocal[i] = offset; //local copy for context switch
266 uint32_t mask = 1 << bit; //mask32 = bit set @position in GPIO DR
267 tempBitmask[offset] |= mask; //bitmask32[0..3] = collective pin bit masks for each GPIO DR
268 force_pin_to_gpio_mux(pin); // reclaim pin after PWM/Serial/FlexIO mux use
269 //bit7:6 SPEED; bit 5:3 DSE; bit0 SRE (default SPEED = 0b10; def. DSE = 0b110)
270 *portControlRegister(pin) &= ~0xF9; //clear SPEED, DSE, SRE
271 *portControlRegister(pin) |= ((OUTPUT_PAD_SPEED & 0x3) << 6) | \
272 ((OUTPUT_PAD_DSE & 0x7) << 3); //DSE = 0b011 for LED overclock
273 //clear pin bit in IOMUX_GPR26 to map GPIO6-9 to GPIO1-4 for DMA
274 *(&IOMUXC_GPR_GPR26 + offset) &= ~mask;
275 *standard_gpio_addr(portModeRegister(pin)) |= mask; //GDIR? bit flag set output mode
276 }
277
278 // Check if any valid pins were configured
279 if (validPinCount == 0) {
280 FL_WARN("================================================================================");
281 FL_WARN("FASTLED CRITICAL ERROR: No valid pins configured!");
282 FL_WARN("All " << (int)numpinsLocal << " pins failed validation.");
283 FL_WARN("ObjectFLED driver is disabled - no LEDs will be updated.");
284 FL_WARN("================================================================================");
285 numpinsLocal = 0;
286 return;
287 }
288
289 if (validPinCount < numpinsLocal) {
290 FL_WARN("================================================================================");
291 FL_WARN("FASTLED WARNING: Only " << (int)validPinCount << " of " << (int)numpinsLocal << " pins are valid");
292 FL_WARN("Strips on invalid pins will not function.");
293 FL_WARN("================================================================================");
294 }
295
296 //stash context for multi-show
297 memcpy(bitmaskLocal, tempBitmask, 16);
298
299 arm_dcache_flush_delete(bitmaskLocal, sizeof(bitmaskLocal)); //can't DMA from cached memory
300
301 // Set up 3 timers to create waveform timing events.
302 // Add 0.5f to the float result before casting so the integer conversion
303 // rounds to nearest tick rather than truncating downward. At F_BUS=150 MHz
304 // and T0H=225 ns, 225e-9 * 150e6 = 33.75 ticks -> truncated to 33 (=220 ns,
305 // 5 ns short of spec). +0.5f rounds to 34 (=226.67 ns, well inside the
306 // WS2812B-V5 215-235 ns window). #3406 audit Agent 3.
307 // #3416 OF-MED-2: comp1load[] is computed from F_BUS_ACTUAL at
308 // begin() time. If a user changes the bus clock at runtime (e.g.
309 // via set_arm_clock(816000000) which raises IPG -> 198 MHz), these
310 // values become stale and the WS2812 timing slips outside spec.
311 // If you need runtime clock changes, call begin() again first.
312 comp1load[0] = (uint16_t)((float)F_BUS_ACTUAL / 1000000000.0f * (float)TH_TL + 0.5f);
313 comp1load[1] = (uint16_t)((float)F_BUS_ACTUAL / 1000000000.0f * (float)T0H + 0.5f);
314 comp1load[2] = (uint16_t)((float)F_BUS_ACTUAL / 1000000000.0f * (float)T1H + 0.5f);
315 TMR4_ENBL &= ~7;
316 TMR4_SCTRL0 = TMR_SCTRL_OEN | TMR_SCTRL_FORCE | TMR_SCTRL_MSTR;
317 TMR4_CSCTRL0 = TMR_CSCTRL_CL1(1) | TMR_CSCTRL_TCF1EN;
318 TMR4_CNTR0 = 0;
319 TMR4_LOAD0 = 0;
320 TMR4_COMP10 = comp1load[0];
321 TMR4_CMPLD10 = comp1load[0];
322 TMR4_CTRL0 = TMR_CTRL_CM(1) | TMR_CTRL_PCS(8) | TMR_CTRL_LENGTH | TMR_CTRL_OUTMODE(3);
323 TMR4_SCTRL1 = TMR_SCTRL_OEN | TMR_SCTRL_FORCE;
324 TMR4_CNTR1 = 0;
325 TMR4_LOAD1 = 0;
326 TMR4_COMP11 = comp1load[1]; // T0H
327 TMR4_CMPLD11 = comp1load[1];
328 TMR4_CTRL1 = TMR_CTRL_CM(1) | TMR_CTRL_PCS(8) | TMR_CTRL_COINIT | TMR_CTRL_OUTMODE(3);
329 TMR4_SCTRL2 = TMR_SCTRL_OEN | TMR_SCTRL_FORCE;
330 TMR4_CNTR2 = 0;
331 TMR4_LOAD2 = 0;
332 TMR4_COMP12 = comp1load[2]; // T1H
333 TMR4_CMPLD12 = comp1load[2];
334 TMR4_CTRL2 = TMR_CTRL_CM(1) | TMR_CTRL_PCS(8) | TMR_CTRL_COINIT | TMR_CTRL_OUTMODE(3);
335
336 // route the timer outputs through XBAR to edge trigger DMA request: only 4 mappings avail.
337 CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON);
338 // #3416 OF-LOW-4: xbar_connect() (in Teensyduino's pwm.c) only writes
339 // the XBAR SEL[] register; it does NOT touch CTRL. So calling it
340 // after configure_objectfled_xbar_dma_edges() (which configures
341 // CTRL bits below) is safe. Future contributors: if you ever
342 // change xbar_connect to also clear CTRL, this sequence breaks.
343 xbar_connect(XBARA1_IN_QTIMER4_TIMER0, XBARA1_OUT_DMA_CH_MUX_REQ30);
344 xbar_connect(XBARA1_IN_QTIMER4_TIMER1, XBARA1_OUT_DMA_CH_MUX_REQ31);
345 xbar_connect(XBARA1_IN_QTIMER4_TIMER2, XBARA1_OUT_DMA_CH_MUX_REQ94);
346
347 // configure DMA channels
348 // #3416 OF-LOW-7: dma{1,2,3}.begin() returns void; if channel
349 // allocation fails (all 32 eDMA channels taken) we'd silently
350 // proceed and the show() path would write to TCD->* on a null
351 // or stale TCD pointer. The `initialized` flag at the end of
352 // this function gates show(); add explicit null checks below.
353 dma.dma1.begin();
354 dma.dma2.begin();
355 dma.dma3.begin();
356 if (!dma.dma1.TCD || !dma.dma2.TCD || !dma.dma3.TCD) {
357 // All channels needed for ObjectFLED; abandon init.
358 initialized = false;
359 numpinsLocal = 0;
360 return;
361 }
362 if (!objectfled_has_dma_tcds(dma)) {
363 numpinsLocal = 0;
364 return;
365 }
366
367 configure_objectfled_xbar_dma_edges();
368
369 dma.dma1.TCD->SADDR = dma.bitmask; // source 4*32b GPIO pin mask
370 dma.dma1.TCD->SOFF = 8; // bytes offset added to SADDR after each transfer
371 // SMOD(4) low bits of SADDR to update with adds of SOFF
372 // SSIZE(3) code for 64 bit transfer size DSIZE(2) code for 32 bit transfer size
373 dma.dma1.TCD->ATTR = DMA_TCD_ATTR_SSIZE(3) | DMA_TCD_ATTR_SMOD(4) | DMA_TCD_ATTR_DSIZE(2);
374 dma.dma1.TCD->NBYTES_MLOFFYES = DMA_TCD_NBYTES_DMLOE | // Dest minor loop offsetting enable
375 DMA_TCD_NBYTES_MLOFFYES_MLOFF(-65536) |
376 DMA_TCD_NBYTES_MLOFFYES_NBYTES(16); // #bytes to tansfer, offsetting enabled
377 dma.dma1.TCD->SLAST = 0; // add to SADDR after xfer
378 dma.dma1.TCD->DADDR = &GPIO1_DR_SET;
379 dma.dma1.TCD->DOFF = 16384; //&GPIO1_DR_SET + DOFF = next &GPIO2_DR_SET
380 dma.dma1.TCD->CITER_ELINKNO = numbytesLocal * 8; // CITER outer loop count (linking disabled) = # LED bits to write
381 dma.dma1.TCD->DLASTSGA = -65536; // add to DADDR after xfer
382 dma.dma1.TCD->BITER_ELINKNO = numbytesLocal * 8; // Beginning CITER (not decremented by transfer)
383 dma.dma1.TCD->CSR = DMA_TCD_CSR_DREQ; // channel ERQ field cleared when minor loop completed
384 dma.dma1.triggerAtHardwareEvent(DMAMUX_SOURCE_XBAR1_0); // only 4 XBAR1 triggers (DMA MUX mapping)
385
386 dma.dma2next.TCD->SADDR = dma.bitdata; // may be null; showInternal() sets SADDR before arming
387 dma.dma2next.TCD->SOFF = 8;
388 dma.dma2next.TCD->ATTR = DMA_TCD_ATTR_SSIZE(3) | DMA_TCD_ATTR_DSIZE(2);
389 dma.dma2next.TCD->NBYTES_MLOFFYES = DMA_TCD_NBYTES_DMLOE |
390 DMA_TCD_NBYTES_MLOFFYES_MLOFF(-65536) |
391 DMA_TCD_NBYTES_MLOFFYES_NBYTES(16);
392 dma.dma2next.TCD->SLAST = 0;
393 dma.dma2next.TCD->DADDR = &GPIO1_DR_CLEAR;
394 dma.dma2next.TCD->DOFF = 16384;
395 dma.dma2next.TCD->CITER_ELINKNO = BYTES_PER_DMA * 8;
396 dma.dma2next.TCD->DLASTSGA = (int32_t)(dma.dma2next.TCD);
397 dma.dma2next.TCD->BITER_ELINKNO = BYTES_PER_DMA * 8;
398 dma.dma2next.TCD->CSR = 0;
399
400 dma.dma2 = dma.dma2next; // copies TCD
401 // #3416 OF-LOW-3: dma2 uses default eDMA channel priority. During a
402 // 100-LED frame (~1.5 ms) the ObjectFLED DMA channels can starve
403 // equal-or-lower-priority channels held by other subsystems
404 // (Audio I2S typically at ch3). If the user reports audio underruns
405 // during LED updates, raise the offending subsystem's priority via
406 // DCHPRIn or migrate this DMA to a higher channel number.
407 dma.dma2.triggerAtHardwareEvent(DMAMUX_SOURCE_XBAR1_1);
408 // #3416 OF-CRIT-2: detach any prior interrupt before re-attaching so
409 // a re-init after pin change doesn't leave an attachInterrupt() routed
410 // to the OLD vector slot from the previous channel allocation.
411 dma.dma2.detachInterrupt();
412 dma.dma2.attachInterrupt(isr);
413
414 dma.dma3.TCD->SADDR = dma.bitmask;
415 dma.dma3.TCD->SOFF = 8;
416 dma.dma3.TCD->ATTR = DMA_TCD_ATTR_SSIZE(3) | DMA_TCD_ATTR_SMOD(4) | DMA_TCD_ATTR_DSIZE(2);
417 dma.dma3.TCD->NBYTES_MLOFFYES = DMA_TCD_NBYTES_DMLOE |
418 DMA_TCD_NBYTES_MLOFFYES_MLOFF(-65536) |
419 DMA_TCD_NBYTES_MLOFFYES_NBYTES(16);
420 dma.dma3.TCD->SLAST = 0;
421 dma.dma3.TCD->DADDR = &GPIO1_DR_CLEAR;
422 dma.dma3.TCD->DOFF = 16384;
423 dma.dma3.TCD->CITER_ELINKNO = numbytesLocal * 8;
424 dma.dma3.TCD->DLASTSGA = -65536;
425 dma.dma3.TCD->BITER_ELINKNO = numbytesLocal * 8;
426 // Note: DMA_TCD_CSR_DONE pre-set is INTENTIONAL -- it lets
427 // waitForCompletion() short-circuit on the very first acquire()
428 // after begin() (before any transfer has actually run). Audit
429 // #3416 OF-CRIT-1 suggested removing this; verified empirically
430 // that doing so hangs the first show() indefinitely (the spin
431 // loop never sees DONE=1 because the channel hasn't started yet).
432 dma.dma3.TCD->CSR = DMA_TCD_CSR_DREQ | DMA_TCD_CSR_DONE;
433 dma.dma3.triggerAtHardwareEvent(DMAMUX_SOURCE_XBAR1_2);
434 initialized = true;
435} // begin()
436
437
438//*dest = *bitdata + pin offset
439//*pixels = pin's block in frameBuffer
440//mask = pin's bit position in GPIOR
441//set a pin's mask32 for each color bit=0 at every 4*words32 in bitdata+offset
442void fillbits(uint32_t *dest, const uint8_t *pixels, int n, uint32_t mask) {
443 do {
444 uint8_t pix = *pixels++;
445 if (!(pix & 0x80)) *dest |= mask;
446 dest += 4;
447 if (!(pix & 0x40)) *dest |= mask;
448 dest += 4;
449 if (!(pix & 0x20)) *dest |= mask;
450 dest += 4;
451 if (!(pix & 0x10)) *dest |= mask;
452 dest += 4;
453 if (!(pix & 0x08)) *dest |= mask;
454 dest += 4;
455 if (!(pix & 0x04)) *dest |= mask;
456 dest += 4;
457 if (!(pix & 0x02)) *dest |= mask;
458 dest += 4;
459 if (!(pix & 0x01)) *dest |= mask;
460 dest += 4;
461 } while (--n > 0);
462}
463
464
466 uint32_t j = 0;
467 int jChange = -3;
468 if (serp == 0) { // use faster loops if no serp
469 switch (params & 0x3F) {
470 case CORDER_RGBW: // R,G,B = R,G,B - MIN(R,G,B); W = MIN(R,G,B)
471 for (uint16_t i = 0; i < (numbytesLocal * numpinsLocal); i += 4) {
472 uint8_t minRGB = MIN(*((uint8_t*)drawBuffer + j) * rLevel / 65025, \
473 *((uint8_t*)drawBuffer + j + 1) * rLevel / 65025);
474 minRGB = MIN(minRGB, *((uint8_t*)drawBuffer + j + 2) * rLevel / 65025);
475 *(frameBufferLocal + i) = *((uint8_t*)drawBuffer + j) * rLevel / 65025 - minRGB;
476 *(frameBufferLocal + i + 1) = *((uint8_t*)drawBuffer + j + 1) * gLevel / 65025 - minRGB;
477 *(frameBufferLocal + i + 2) = *((uint8_t*)drawBuffer + j + 2) * bLevel / 65025 - minRGB;
478 *(frameBufferLocal + i + 3) = minRGB;
479 j += 3;
480 } //for(leds in drawbuffer)
481 break;
482 case CORDER_GBR:
483 for (uint16_t i = 0; i < (numbytesLocal * numpinsLocal); i += 3) {
484 *(frameBufferLocal + i + 2) = *((uint8_t*)drawBuffer + j) * rLevel / 65025;
485 *(frameBufferLocal + i) = *((uint8_t*)drawBuffer + j + 1) * gLevel / 65025;
486 *(frameBufferLocal + i + 1) = *((uint8_t*)drawBuffer + j + 2) * bLevel / 65025;
487 j += 3;
488 } //for(leds in drawbuffer)
489 break;
490 case CORDER_BGR:
491 for (uint16_t i = 0; i < (numbytesLocal * numpinsLocal); i += 3) {
492 *(frameBufferLocal + i + 2) = *((uint8_t*)drawBuffer + j) * rLevel / 65025;
493 *(frameBufferLocal + i + 1) = *((uint8_t*)drawBuffer + j + 1) * gLevel / 65025;
494 *(frameBufferLocal + i) = *((uint8_t*)drawBuffer + j + 2) * bLevel / 65025;
495 j += 3;
496 } //for(leds in drawbuffer)
497 break;
498 case CORDER_BRG:
499 for (uint16_t i = 0; i < (numbytesLocal * numpinsLocal); i += 3) {
500 *(frameBufferLocal + i + 1) = *((uint8_t*)drawBuffer + j) * rLevel / 65025;
501 *(frameBufferLocal + i + 2) = *((uint8_t*)drawBuffer + j + 1) * gLevel / 65025;
502 *(frameBufferLocal + i) = *((uint8_t*)drawBuffer + j + 2) * bLevel / 65025;
503 j += 3;
504 } //for(leds in drawbuffer)
505 break;
506 case CORDER_GRB:
507 for (uint16_t i = 0; i < (numbytesLocal * numpinsLocal); i += 3) {
508 *(frameBufferLocal + i + 1) = *((uint8_t*)drawBuffer + j) * rLevel / 65025;
509 *(frameBufferLocal + i) = *((uint8_t*)drawBuffer + j + 1) * gLevel / 65025;
510 *(frameBufferLocal + i + 2) = *((uint8_t*)drawBuffer + j + 2) * bLevel / 65025;
511 j += 3;
512 } //for(leds in drawbuffer)
513 break;
514 case CORDER_RGB:
515 default:
516 for (uint16_t i = 0; i < (numbytesLocal * numpinsLocal); i += 3) {
517 *(frameBufferLocal + i) = *((uint8_t*)drawBuffer + j) * rLevel / 65025;
518 *(frameBufferLocal + i + 1) = *((uint8_t*)drawBuffer + j + 1) * gLevel / 65025;
519 *(frameBufferLocal + i + 2) = *((uint8_t*)drawBuffer + j + 2) * bLevel / 65025;
520 j += 3;
521 } //for(leds in drawbuffer)
522 } // switch()
523 } else { //serpentine
524 switch (params & 0x3F) {
525 case CORDER_RGBW: // R,G,B = R,G,B - MIN(R,G,B); W = MIN(R,G,B)
526 for (uint16_t i = 0; i < (numbytesLocal * numpinsLocal); i += 4) {
527 uint8_t minRGB = MIN(*((uint8_t*)drawBuffer + j) * rLevel / 65025, \
528 * ((uint8_t*)drawBuffer + j + 1) * rLevel / 65025);
529 minRGB = MIN(minRGB, *((uint8_t*)drawBuffer + j + 2) * rLevel / 65025);
530 if (i % (serp * 4) == 0) {
531 if (jChange < 0) { j = i / 4 * 3; jChange = 3; }
532 else { j = (i / 4 + serp - 1) * 3; jChange = -3; }
533 }
534 *(frameBufferLocal + i) = *((uint8_t*)drawBuffer + j) * rLevel / 65025 - minRGB;
535 *(frameBufferLocal + i + 1) = *((uint8_t*)drawBuffer + j + 1) * gLevel / 65025 - minRGB;
536 *(frameBufferLocal + i + 2) = *((uint8_t*)drawBuffer + j + 2) * bLevel / 65025 - minRGB;
537 *(frameBufferLocal + i + 3) = minRGB;
538 j += jChange;
539 } //for(leds in drawbuffer)
540 break;
541 case CORDER_GBR:
542 for (uint16_t i = 0; i < (numbytesLocal * numpinsLocal); i += 3) {
543 if (i % (serp * 3) == 0) {
544 if (jChange < 0) { j = i; jChange = 3; }
545 else { j = i + (serp - 1) * 3; jChange = -3; }
546 }
547 *(frameBufferLocal + i + 2) = *((uint8_t*)drawBuffer + j) * rLevel / 65025;
548 *(frameBufferLocal + i) = *((uint8_t*)drawBuffer + j + 1) * gLevel / 65025;
549 *(frameBufferLocal + i + 1) = *((uint8_t*)drawBuffer + j + 2) * bLevel / 65025;
550 j += 3;
551 } //for(leds in drawbuffer)
552 break;
553 case CORDER_BGR:
554 for (uint16_t i = 0; i < (numbytesLocal * numpinsLocal); i += 3) {
555 if (i % (serp * 3) == 0) {
556 if (jChange < 0) { j = i; jChange = 3; }
557 else { j = i + (serp - 1) * 3; jChange = -3; }
558 }
559 *(frameBufferLocal + i + 2) = *((uint8_t*)drawBuffer + j) * rLevel / 65025;
560 *(frameBufferLocal + i + 1) = *((uint8_t*)drawBuffer + j + 1) * gLevel / 65025;
561 *(frameBufferLocal + i) = *((uint8_t*)drawBuffer + j + 2) * bLevel / 65025;
562 j += 3;
563 } //for(leds in drawbuffer)
564 break;
565 case CORDER_BRG:
566 for (uint16_t i = 0; i < (numbytesLocal * numpinsLocal); i += 3) {
567 if (i % (serp * 3) == 0) {
568 if (jChange < 0) { j = i; jChange = 3; }
569 else { j = i + (serp - 1) * 3; jChange = -3; }
570 }
571 *(frameBufferLocal + i + 1) = *((uint8_t*)drawBuffer + j) * rLevel / 65025;
572 *(frameBufferLocal + i + 2) = *((uint8_t*)drawBuffer + j + 1) * gLevel / 65025;
573 *(frameBufferLocal + i) = *((uint8_t*)drawBuffer + j + 2) * bLevel / 65025;
574 j += jChange;
575 } //for(leds in drawbuffer)
576 break;
577 case CORDER_GRB:
578 for (uint16_t i = 0; i < (numbytesLocal * numpinsLocal); i += 3) {
579 if (i % (serp * 3) == 0) {
580 if (jChange < 0) { j = i; jChange = 3; }
581 else { j = i + (serp - 1) * 3; jChange = -3; }
582 }
583 *(frameBufferLocal + i + 1) = *((uint8_t*)drawBuffer + j) * rLevel / 65025;
584 *(frameBufferLocal + i) = *((uint8_t*)drawBuffer + j + 1) * gLevel / 65025;
585 *(frameBufferLocal + i + 2) = *((uint8_t*)drawBuffer + j + 2) * bLevel / 65025;
586 j += jChange;
587 } //for(leds in drawbuffer)
588 break;
589 case CORDER_RGB:
590 default:
591 for (uint16_t i = 0; i < (numbytesLocal * numpinsLocal); i += 3) {
592 if (i % (serp * 3) == 0) {
593 if (jChange < 0) { j = i; jChange = 3; }
594 else { j = i + (serp - 1) * 3; jChange = -3; }
595 }
596 *(frameBufferLocal + i) = *((uint8_t*)drawBuffer + j) * rLevel / 65025;
597 *(frameBufferLocal + i + 1) = *((uint8_t*)drawBuffer + j + 1) * gLevel / 65025;
598 *(frameBufferLocal + i + 2) = *((uint8_t*)drawBuffer + j + 2) * bLevel / 65025;
599 j += jChange;
600 } //for(leds in drawbuffer)
601 } // switch()
602 } // else serpentine
603} //genFrameBuffer()
604
605
606// pre-show prior transfer wait, copies drawBuffer -> frameBuffer
607// resets timers, clears pending DMA reqs
608// fills bitdata[BYTES_PER_DMA * 64 * 4 bytes] from frameBuffer with 4-block bitmasks for 0's in led data
609// 4 word32s for each bit in (led data)/pin = 16 * 8 = 96 bitdata bytes for each LED byte: 288 bytes / LED
610// launches DMA with IRQ activation to reload bitdata from frameBuffer
611void ObjectFLED::show(void) {
612 showInternal(true);
613}
614
616 showInternal(false);
617}
618
619void ObjectFLED::showInternal(bool regenerateFrameBuffer) {
620 if (!initialized || numpinsLocal == 0 || frameBufferLocal == nullptr) {
621 return;
622 }
623
624 auto& dma = ObjectFLEDDmaManager::getInstance();
625
626 // Acquire DMA (blocks if another instance transmitting)
627 dma.acquire(this);
628
629 //Restore context if needed
630 if (dma.frameBuffer != frameBufferLocal) {
631 dma.numpins = numpinsLocal;
632 dma.frameBuffer = frameBufferLocal;
633 dma.numbytes = numbytesLocal;
634 memcpy(dma.bitmask, bitmaskLocal, 16);
635 memcpy(dma.pin_bitnum, pin_bitnumLocal, numpinsLocal);
636 memcpy(dma.pin_offset, pin_offsetLocal, numpinsLocal);
637 arm_dcache_flush_delete(dma.bitmask, sizeof(dma.bitmask)); //can't DMA from cached memory
638 // Restore 3 timers to create waveform timing events
639 TMR4_COMP10 = comp1load[0];
640 TMR4_CMPLD10 = comp1load[0];
641 TMR4_COMP11 = comp1load[1]; // T0H
642 TMR4_CMPLD11 = comp1load[1];
643 TMR4_COMP12 = comp1load[2]; // T1H
644 TMR4_CMPLD12 = comp1load[2];
645 //restore DMA loop control
646 dma.dma1.TCD->CITER_ELINKNO = dma.numbytes * 8; // CITER outer loop count (linking disabled) = # LED bits to write
647 dma.dma1.TCD->BITER_ELINKNO = dma.numbytes * 8; // Beginning CITER (not decremented by transfer)
648 dma.dma3.TCD->CITER_ELINKNO = dma.numbytes * 8;
649 dma.dma3.TCD->BITER_ELINKNO = dma.numbytes * 8;
650 } //done restoring context
651
652 // #4711: size bitdata to this frame while the DMA is idle (after
653 // acquire()). On allocation failure drop the frame rather than arm the
654 // DMA on a null or too-small buffer.
655 if (!dma.ensureBitdata(dma.numbytes)) {
656 static bool warned = false; // warn once; don't flood serial per frame
657 if (!warned) {
658 warned = true;
659 FL_WARN("ObjectFLED: out of memory for the DMA bit buffer ("
660 << dma.numbytes << " bytes/strip); frames dropped");
661 }
662 dma.release(this);
663 return;
664 }
665
666 if (regenerateFrameBuffer) {
668 }
669
670 // disable timers
671 uint16_t enable = TMR4_ENBL;
672 TMR4_ENBL = enable & ~7;
673
674 // force all timer outputs to logic low
675 TMR4_SCTRL0 = TMR_SCTRL_OEN | TMR_SCTRL_FORCE | TMR_SCTRL_MSTR;
676 TMR4_SCTRL1 = TMR_SCTRL_OEN | TMR_SCTRL_FORCE;
677 TMR4_SCTRL2 = TMR_SCTRL_OEN | TMR_SCTRL_FORCE;
678
679 // clear any prior pending DMA requests
680 clear_objectfled_xbar_dma_status();
681
682 // fill the DMA transmit buffer
683 uint32_t count = dma.numbytes; //bytes per strip
684 if (count > BYTES_PER_DMA*2) count = BYTES_PER_DMA*2;
685 // Clear only what this frame fills (count*32 words, at least one byte's
686 // worth for fillbits' do/while); ensureBitdata() guaranteed the capacity.
687 memset(dma.bitdata, 0, objectfled::bitdataWordsFor(count, BYTES_PER_DMA) * sizeof(uint32_t));
688 dma.framebuffer_index = count; //ptr to framebuffer last byte output
689
690 //Sets each pin mask in bitdata32[BYTES_PER_DMA*64] for every 0 bit of pin's frameBuffer block bytes
691 for (uint32_t i=0; i < dma.numpins; i++) { //for each pin
692 fillbits(dma.bitdata + dma.pin_offset[i], (uint8_t *)dma.frameBuffer + i*dma.numbytes,
693 count, 1<<dma.pin_bitnum[i]);
694 }
695 arm_dcache_flush_delete(dma.bitdata, count * 128); // don't need bitdata in cache for DMA
696
697 // set up DMA transfers
698 if (dma.numbytes <= BYTES_PER_DMA*2) {
699 dma.dma2.TCD->SADDR = dma.bitdata;
700 dma.dma2.TCD->DADDR = &GPIO1_DR_CLEAR;
701 dma.dma2.TCD->CITER_ELINKNO = count * 8;
702 dma.dma2.TCD->CSR = DMA_TCD_CSR_DREQ;
703 } else {
704 dma.dma2.TCD->SADDR = dma.bitdata;
705 dma.dma2.TCD->DADDR = &GPIO1_DR_CLEAR;
706 dma.dma2.TCD->CITER_ELINKNO = BYTES_PER_DMA * 8;
707 // #3416 OF-HIGH-2: drop the dual-write CSR=0;CSR=ESG|INTMAJOR
708 // pattern. The intermediate CSR=0 cleared DREQ which can re-arm
709 // the channel mid-window if ERQ is set, leaving a brief gap
710 // where the channel is armed without ESG. Just write the final
711 // value directly.
712 //
713 // But clear DONE first: the previous frame leaves dma2 with
714 // CSR.DONE set, and the eDMA does not honour ESG written while
715 // DONE is set. At the end of chunk 1 the channel then applied
716 // DLASTSGA (the dma2next TCD address) as a DADDR offset instead of
717 // loading dma2next, took a destination bus error (DMA_ES = DBE on
718 // channel 2) and stopped clearing pins, so every bit from byte
719 // BYTES_PER_DMA onward went out as '1' (101 LEDs: LED 50+ = 0xFF).
720 dma.dma2.clearComplete();
721 dma.dma2.TCD->CSR = DMA_TCD_CSR_INTMAJOR | DMA_TCD_CSR_ESG;
722 dma.dma2next.TCD->SADDR = dma.bitdata + BYTES_PER_DMA*32;
723 dma.dma2next.TCD->CITER_ELINKNO = BYTES_PER_DMA * 8;
724 if (dma.numbytes <= BYTES_PER_DMA*3) {
725 dma.dma2next.TCD->CSR = DMA_TCD_CSR_ESG;
726 } else {
727 dma.dma2next.TCD->CSR = DMA_TCD_CSR_ESG | DMA_TCD_CSR_INTMAJOR;
728 }
729 dma_first = true;
730 }
731 // #3416 OF-MED-4: barriers so all memcpy + TCD writes commit before
732 // the eDMA channels are armed. Without these the compiler is free
733 // to reorder the memcpy of dma.pin_bitnum / pin_offset / bitmask
734 // past the dma*.enable() calls, and the ISR (or DMA engine itself)
735 // could observe stale shared-state values. Strongly-ordered peripheral
736 // region writes drain on DSB; DMB ensures the memcpy stores are
737 // visible to the eDMA engine via the AXIM port before ERQ goes hot.
738 asm volatile("dmb" ::: "memory");
739 dma.dma3.clearComplete();
740 dma.dma1.enable();
741 dma.dma2.enable();
742 dma.dma3.enable();
743 asm volatile("dsb" ::: "memory");
744
745 // initialize timers
746 TMR4_CNTR0 = 0;
747 TMR4_CNTR1 = comp1load[0] + 1;
748 TMR4_CNTR2 = comp1load[0] + 1;
749
750 // wait for last LED reset to finish
751 while (micros() - update_begin_micros < dma.numbytes * 8 * TH_TL / 1000 + LATCH_DELAY);
752
753 // start everything running!
754 TMR4_ENBL = enable | 7;
756
757 // Release DMA (transmission continues asynchronously)
758 dma.release(this);
759} // showInternal()
760
761
762//INPUT: dma2, dma2next, bitdata, framebuffer_inedex, numpins, numbytes, pin_offset[], pin_bitnum[]
763//Reads next block of framebuffer -> fillbits() -> bitdata
764//Checks for last block to transfer, next to last, or not to update dma2next major loop
765void ObjectFLED::isr(void)
766{
767 auto& dma = ObjectFLEDDmaManager::getInstance();
768
769 // first ack the interrupt
770 dma.dma2.clearInterrupt();
771 asm volatile("dsb" ::: "memory"); // #3416 OF mirror of FX-HIGH-6
772
773 // Determine remaining payload first so we can short-circuit the
774 // expensive memset+fillbits+dcache_flush when there's nothing left
775 // to send (#3416 OF-HIGH-4).
776 uint32_t index_check = dma.framebuffer_index;
777 uint32_t remain_check = dma.numbytes - index_check;
778
779 uint32_t *dest;
780 if (dma_first) {
781 dma_first = false;
782 dest = dma.bitdata;
783 } else {
784 dma_first = true;
785 dest = dma.bitdata + BYTES_PER_DMA*32;
786 }
787
788 if (remain_check == 0) {
789 // #3416 OF-HIGH-4: nothing more to send. The next-iteration
790 // CSR is already set to DREQ from the previous ISR or
791 // showInternal, so no further DMA fires. Skipping ~10us of
792 // memset + fillbits + cache flush trims ISR length and the
793 // next show() does a full memset(dma.bitdata) anyway.
794 dma.dma2next.TCD->CSR = DMA_TCD_CSR_DREQ;
795 return;
796 }
797
798 // #3416 OF-MED-5: memset zeroes the half-buffer we're about to fill.
799 // fillbits() below does *dest |= mask (read-modify-write); the OR
800 // only works correctly when the destination starts at 0. The full-
801 // buffer memset in showInternal at frame start initialises only the
802 // half DMA reads first; this per-ISR memset initialises the OTHER
803 // half before each refill so the |= invariant holds.
804 // Only the >2*BPD path reaches here, where bitdataWords == 2*BPD*32
805 // (static_assert in ObjectFLEDDmaManager.h).
806 memset(dest, 0, BYTES_PER_DMA * 32 * sizeof(uint32_t));
807 uint32_t index = dma.framebuffer_index;
808 uint32_t count = dma.numbytes - dma.framebuffer_index;
809 if (count > BYTES_PER_DMA) count = BYTES_PER_DMA;
810 dma.framebuffer_index = index + count;
811 for (int i=0; i < dma.numpins; i++) {
812 fillbits(dest + dma.pin_offset[i], (uint8_t *)dma.frameBuffer + index + i*dma.numbytes,
813 count, 1<<dma.pin_bitnum[i]);
814 }
815 arm_dcache_flush_delete(dest, count * 128);
816 //digitalWriteFast(12, LOW);
817
818 // queue it for the next DMA transfer
819 dma.dma2next.TCD->SADDR = dest;
820 dma.dma2next.TCD->CITER_ELINKNO = count * 8;
821 uint32_t remain = dma.numbytes - (index + count);
822 if (remain == 0) {
823 dma.dma2next.TCD->CSR = DMA_TCD_CSR_DREQ;
824 } else if (remain <= BYTES_PER_DMA) {
825 dma.dma2next.TCD->CSR = DMA_TCD_CSR_ESG;
826 } else {
827 dma.dma2next.TCD->CSR = DMA_TCD_CSR_ESG | DMA_TCD_CSR_INTMAJOR;
828 }
829} // isr()
830
831
832int ObjectFLED::busy(void)
833{
834 if (!initialized) {
835 return 0;
836 }
837 auto& dma = ObjectFLEDDmaManager::getInstance();
838 if (micros() - update_begin_micros < dma.numbytes * TH_TL / 1000 * 8 + LATCH_DELAY) {
839 return 1;
840 }
841 return 0;
842}
843
844
845void ObjectFLED::setBrightness(uint8_t brightLevel) {
846 brightness = brightLevel;
847 rLevel = brightness * (colorBalance >> 16);
848 gLevel = brightness * ((colorBalance >> 8) & 0xFF);
849 bLevel = brightness * (colorBalance & 0xFF);
850 }
851
852
853void ObjectFLED::setBalance(uint32_t balMask) {
854 colorBalance = balMask & 0xFFFFFF;
855 rLevel = brightness * (colorBalance >> 16);
856 gLevel = brightness * ((colorBalance >> 8) & 0xFF);
857 bLevel = brightness * (colorBalance & 0xFF);
858}
859
860
861//Fades CRGB array towards the background color by amount.
862void fadeToColorBy(void* leds, uint16_t count, uint32_t color, uint8_t fadeAmt) {
863 for (uint32_t x = 0; x < count * 3; x += 3) {
864 //fade red
865 *((uint8_t*)leds + x) = (( *((uint8_t*)leds + x) * (1 + (255 - fadeAmt))) >> 8) + \
866 (( ((color >> 16) & 0xFF) * (1 + fadeAmt)) >> 8);
867 //fade green
868 *((uint8_t*)leds + x + 1) = (( *((uint8_t*)leds + x + 1) * (1 + (255 - fadeAmt))) >> 8) + \
869 (( ((color >> 8) & 0xFF) * (1 + fadeAmt)) >> 8);
870 //fade blue
871 *((uint8_t*)leds + x + 2) = (( *((uint8_t*)leds + x + 2) * (1 + (255 - fadeAmt))) >> 8) + \
872 (( (color & 0xFF) * (1 + fadeAmt)) >> 8);
873 }
874} //fadeToColorBy()
875
876
877// Safely draws box even if partially offscreen on 2D CRGB array
878void drawSquare(void* leds, uint16_t planeY, uint16_t planeX, int yCorner, int xCorner, uint32_t size, uint32_t color) {
879 if (size != 0) { size--; }
880 else { return; }
881 for (int x = xCorner; x <= xCorner + (int)size; x++) {
882 // if validX { if validY+S {draw Y+S,X}; if validY {draw Y, X} }
883 if ((x >= 0) && (x < planeX)) { //valid X
884 if ((yCorner >= 0) && (yCorner < planeY)) {
885 *((uint8_t*)leds + (yCorner * planeX + x) * 3) = ((color >> 16) & 0xFF);
886 *((uint8_t*)leds + (yCorner * planeX + x) * 3 + 1) = ((color >> 8) & 0xFF);
887 *((uint8_t*)leds + (yCorner * planeX + x) * 3 + 2) = (color & 0xFF);
888 }
889 // FastLED #2726: `size` is uint32_t so `yCorner + size` promotes
890 // to unsigned, making the `>= 0` clause always true (-Wtype-limits)
891 // and breaking the bounds check when yCorner is negative. Cast
892 // `size` to int to keep the arithmetic signed (matches the
893 // for-loop bound `x <= xCorner + (int)size` above).
894 if ((yCorner + (int)size >= 0) && (yCorner + (int)size < planeY)) {
895 *((uint8_t*)leds + ((yCorner + (int)size) * planeX + x) * 3) = ((color >> 16) & 0xFF);
896 *((uint8_t*)leds + ((yCorner + (int)size) * planeX + x) * 3 + 1) = ((color >> 8) & 0xFF);
897 *((uint8_t*)leds + ((yCorner + (int)size) * planeX + x) * 3 + 2) = (color & 0xFF);
898 }
899 } //if valid x
900 } //for x
901 for (int y = yCorner; y <= yCorner + (int)size; y++) {
902 if ((y >= 0) && (y < planeY)) { //valid y
903 if ((xCorner >= 0) && (xCorner < planeX)) {
904 *((uint8_t*)leds + (xCorner + y * planeX) * 3) = ((color >> 16) & 0xFF);
905 *((uint8_t*)leds + (xCorner + y * planeX) * 3 + 1) = ((color >> 8) & 0xFF);
906 *((uint8_t*)leds + (xCorner + y * planeX) * 3 + 2) = (color & 0xFF);
907 }
908 // FastLED #2726: same -Wtype-limits fix as the yCorner branch above.
909 if ((xCorner + (int)size >= 0) && (xCorner + (int)size < planeX)) {
910 *((uint8_t*)leds + (xCorner + (int)size + y * planeX) * 3) = ((color >> 16) & 0xFF);
911 *((uint8_t*)leds + (xCorner + (int)size + y * planeX) * 3 + 1) = ((color >> 8) & 0xFF);
912 *((uint8_t*)leds + (xCorner + (int)size + y * planeX) * 3 + 2) = (color & 0xFF);
913 }
914 } //if valid y
915 } //for y
916} // drawSquare()
917
918
919// ============================================================================
920// ObjectFLED Member Functions (moved from header)
921// ============================================================================
922
924 // Wait for prior xmission to end, don't need to wait for latch time before deleting buffer
925 if (initialized) {
926 while (micros() - update_begin_micros < numbytesLocal * 8 * TH_TL / 1000 + 5);
928 }
929 // The DMA manager caches the last instance's context (numbytes, pins,
930 // bitmask) keyed by its frame buffer address; showInternal() restores
931 // context only when that address differs. The next instance's buffer
932 // can be allocated at the same address, so it would run with this
933 // instance's byte count: a 101-LED frame after a 100-LED one sent 300
934 // data bytes while dma1/dma3 clocked 303, and the last LED went out as
935 // all ones. Drop the key so the next show() always restores.
936 auto& dma = ObjectFLEDDmaManager::getInstance();
937 if (dma.frameBuffer == frameBufferLocal) {
938 dma.frameBuffer = nullptr;
939 }
940 delete[] frameBufferLocal;
941}
942
944 ObjectFLEDDmaManager::getInstance().waitForCompletion();
945}
946
948 return brightness;
949}
950
952 return colorBalance;
953}
954
955} // namespace fl
956
957
958#endif // FL_IS_TEENSY_4X
CRGB leds[1]
#define CORDER_GRB
Definition ObjectFLED.h:83
#define CORDER_BGR
Definition ObjectFLED.h:86
#define CORDER_RGBW
Definition ObjectFLED.h:87
#define OUTPUT_PAD_DSE
Definition ObjectFLED.h:51
#define CORDER_BRG
Definition ObjectFLED.h:85
#define OUTPUT_PAD_SPEED
Definition ObjectFLED.h:52
#define CORDER_GBR
Definition ObjectFLED.h:84
#define CORDER_RGB
Definition ObjectFLED.h:81
void * drawBuffer
Definition ObjectFLED.h:209
uint8_t * frameBufferLocal
Definition ObjectFLED.h:194
uint8_t brightness
Definition ObjectFLED.h:203
uint8_t pin_bitnumLocal[NUM_DIGITAL_PINS]
Definition ObjectFLED.h:233
uint8_t pinlist[NUM_DIGITAL_PINS]
Definition ObjectFLED.h:213
uint32_t update_begin_micros
Definition ObjectFLED.h:202
uint32_t colorBalance
Definition ObjectFLED.h:204
int busy(void)
uint32_t getBalance()
void setBalance(uint32_t)
uint16_t LATCH_DELAY
Definition ObjectFLED.h:219
void genFrameBuffer(uint32_t)
ObjectFLED(uint16_t numLEDs, void *drawBuf, uint8_t config, uint8_t numPins, const uint8_t *pinList, uint8_t serpentine=0)
uint32_t gLevel
Definition ObjectFLED.h:206
uint8_t getBrightness()
void waitForDmaToFinish()
static void isr(void)
uint16_t stripLen
Definition ObjectFLED.h:211
uint8_t serpNumber
Definition ObjectFLED.h:215
void setBrightness(uint8_t)
void showRawFrameBuffer(void)
uint16_t TH_TL
Definition ObjectFLED.h:216
uint32_t rLevel
Definition ObjectFLED.h:205
uint32_t bitmaskLocal[8]
Definition ObjectFLED.h:230
void begin(void)
void show(void)
uint32_t numbytesLocal
Definition ObjectFLED.h:232
uint16_t comp1load[3]
Definition ObjectFLED.h:214
uint8_t numpinsLocal
Definition ObjectFLED.h:231
void beginInternal(uint16_t, uint16_t, uint16_t, uint16_t=300)
uint8_t pin_offsetLocal[NUM_DIGITAL_PINS]
Definition ObjectFLED.h:234
void showInternal(bool regenerateFrameBuffer)
uint32_t bLevel
Definition ObjectFLED.h:207
fl::UISlider offset("Offset", 0.0f, 0.0f, 1.0f, 0.01f)
#define FL_WARN(...)
Definition log.h:437
Centralized logging categories for FastLED hardware interfaces and subsystems.
fl::u32 uint32_t
Definition s16x16x4.h:219
fl::u16 uint16_t
Definition s16x16x4.h:214
fl::u32 micros() FL_NO_EXCEPT
Universal microsecond timer - returns microseconds since system startup.
FASTLED_FORCE_INLINE void * memset(void *s, int c, size_t n) FL_NO_EXCEPT
Definition cstring.h:148
void drawSquare(void *, uint16_t, uint16_t, int, int, uint32_t, uint32_t)
FASTLED_FORCE_INLINE void * memcpy(void *dest, const void *src, size_t n) FL_NO_EXCEPT
Definition cstring.h:136
fl::i32 int32_t
Definition s16x16x4.h:220
void fadeToColorBy(void *, uint16_t, uint32_t, uint8_t)
unsigned char uint8_t
Definition s16x16x4.h:209
Base definition for an LED controller.
PinValidationResult validate_teensy4_pin(uint8_t pin)
constexpr uint32_t bitdataWordsFor(uint32_t numbytes, uint32_t bytesPerDma)
Words of bitdata a frame of numbytes bytes per strip needs.
#define Serial
Definition serial.h:309