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