IS31FL3733 Async Driver 1.0.0
Asynchronous DMA-driven IS31FL3733 LED driver for Arduino SAMD
Loading...
Searching...
No Matches
is31fl3733.cpp
1/*---------------------------------------------------------------------------------------------
2 * Copyright (c) Cal Abel. All rights reserved.
3 * Author: Cal Abel (async rewrite and ongoing maintenance)
4 * Author: Neil Enns (original C++ library)
5 * Licensed under the MIT License. See LICENSE in the project root for license information.
6 *
7 * Based on the original IS31FL3733 C++ library by Neil Enns
8 * (https://github.com/neilenns/is31fl3733).
9 * This is a nearly complete rewrite for asynchronous DMA-driven operation on
10 * Arduino SAMD.
11 *
12 * Asynchronous DMA-driven IS31FL3733 driver implementation.
13 *
14 * See is31fl3733.hpp and /docs/ASYNC_DMA_ARCHITECTURE.md for details.
15 *--------------------------------------------------------------------------------------------*/
16
17#include "is31fl3733.hpp"
18
19#include <utility>
20
21namespace IS31FL3733 {
22
23// =========================================================================================
24// Static Members
25// =========================================================================================
26
27IS31FL3733 *IS31FL3733::_instance = nullptr;
28
29// =========================================================================================
30// Constructor
31// =========================================================================================
32
33IS31FL3733::IS31FL3733(TwoWire *wire, uint8_t addr, uint8_t sdbPin, uint8_t irqPin)
34 : _hw(wire ? wire->getSercom() : nullptr), _addr(addr), _sdbPin(sdbPin), _irqPin(irqPin),
35 _currentPage(0xFF), _pwmEnqueued(0), _pwmLocked(false), _pwmBatchActive(false),
36 _abmEnqueued(0), _abmLocked(false), _cmdReturn(0), _cmdError(0), _syncComplete(false),
37 _syncStatus(0), _syncTargetCmd(-1), _begun(false), _lastISR(0), _crValue(CR_SSD),
38 _colorOrder(ColorOrder::GRB), _maskShortFaults(false) {
39
40 // Pre-stage unlock transaction and page buffers
41 _crwlTx[0] = PSWL;
42 _crwlTx[1] = PSWL_ENABLE;
43 _pgSelTx[0] = PSR;
44 // _pgSelTx[1] will be set dynamically based on target register page during transactions
45
46 // CRWL Txn (unlock)
47 _cmdTxn[0].config = I2C_CFG_STOP;
48 _cmdTxn[0].address = _addr;
49 _cmdTxn[0].txPtr = _crwlTx;
50 _cmdTxn[0].length = 2;
51 _cmdCtx[0] = {this, 0, nullptr, nullptr};
52 _cmdTxn[0].onComplete = _cmdCallback;
53 _cmdTxn[0].user = &_cmdCtx[0];
54 _cmdTxn[0].chainNext = false;
55
56 // Page Selection Txn
57 _cmdTxn[1].config = I2C_CFG_STOP;
58 _cmdTxn[1].address = _addr;
59 _cmdTxn[1].txPtr = _pgSelTx;
60 _cmdTxn[1].length = 2;
61 _cmdCtx[1] = {this, 1, nullptr, nullptr};
62 _cmdTxn[1].onComplete = _cmdCallback;
63 _cmdTxn[1].user = &_cmdCtx[1];
64 _cmdTxn[1].chainNext = false;
65
66 // Command Write Txn
67 _cmdTxn[2].config = I2C_CFG_STOP;
68 _cmdTxn[2].address = _addr;
69 _cmdTxn[2].txPtr = _cmdTx;
70 _cmdCtx[2] = {this, 2, nullptr, nullptr};
71 _cmdTxn[2].onComplete = _cmdCallback;
72 _cmdTxn[2].user = &_cmdCtx[2];
73 _cmdTxn[2].chainNext = false;
74
75 // Command Read Txn
76 _cmdTxn[3].config = I2C_CFG_READ | I2C_CFG_STOP;
77 _cmdTxn[3].address = _addr;
78 _cmdTxn[3].rxPtr = _cmdRx;
79 _cmdCtx[3] = {this, 3, nullptr, nullptr};
80 _cmdTxn[3].onComplete = _cmdCallback;
81 _cmdTxn[3].user = &_cmdCtx[3];
82 _cmdTxn[3].chainNext = false;
83
84 // PWM Txn
85 _pwmTxn.config = I2C_CFG_STOP;
86 _pwmTxn.address = _addr;
87 _pwmTxn.length = 17;
88 _pwmTxn.txPtr = nullptr;
89 _pwmTxn.onComplete = _txnCallback;
90 _pwmTxn.user = this;
91 _pwmTxn.chainNext = false;
92
93 // ABM Txn
94 _abmTxn.config = I2C_CFG_STOP;
95 _abmTxn.address = _addr;
96 _abmTxn.length = 17;
97 _abmTxn.txPtr = nullptr;
98 _abmTxn.onComplete = _txnModeCallback;
99 _abmTxn.user = this;
100 _abmTxn.chainNext = false;
101
102 // set all LED's on by default (will be updated in begin()) after OSD reads if enabled
103 memset(_ledOn, 0xFF, sizeof(_ledOn));
104
105 // Initialize row start-register addresses with 16-byte row stride:
106 // row0=0x00, row1=0x10, ... row11=0xB0
107 for (uint8_t row = 0; row < kHardwareRows; row++)
108 _pwm_matrix[row][0] = static_cast<uint8_t>(row * kHardwareCols);
109
110 for (uint8_t row = 0; row < kHardwareRows; row++)
111 _abm_matrix[row][0] = static_cast<uint8_t>(row * kHardwareCols);
112}
113
115 if (_begun)
116 end();
117}
118
119// =========================================================================================
120// Initialization (Blocking)
121// =========================================================================================
122
123bool IS31FL3733::begin(uint8_t pfs, uint8_t pur, uint8_t pdr) {
124 // ---------------------------------------------------------------------------------
125 // STEP 1: Configure pins
126 // ---------------------------------------------------------------------------------
127 // SDB pin (active high)
128 if (_sdbPin != 0xFF) {
129 pinMode(_sdbPin, OUTPUT);
130 digitalWrite(_sdbPin, LOW); // Start disabled
131 delay(1);
132 digitalWrite(_sdbPin, HIGH); // Enable device
133 delay(1);
134 }
135
136 // IRQ pin (if provided). Defer ISR attachment until initialization completes.
137 if (_irqPin != 0xFF)
138 pinMode(_irqPin, INPUT_PULLUP);
139
140 // ---------------------------------------------------------------------------------
141 // Force software RESET
142 // ---------------------------------------------------------------------------------
143
144 // Reading RESET triggers the IS31FL3733 software reset.
145 uint8_t dummy;
146 if (!_syncRead(RESET, &dummy, 1))
147 return false;
148 delay(1);
149
150 // ---------------------------------------------------------------------------------
151 // Configure Page 3 registers
152 // ---------------------------------------------------------------------------------
153
154 // CR: Normal operation (without OSD initially)
155 _crValue = static_cast<uint8_t>(CR_SSD | CR_PFS(pfs & 0x03));
156 if (!_syncWrite(CR, &_crValue, 1))
157 return false;
158
159 // ---------------------------------------------------------------------------------
160 // Configure Page 0: LED On/Off and Open/Short Detection
161 // ---------------------------------------------------------------------------------
162 // Step 1: Enable all LEDs in LEDONOFF
163 if (!_syncWrite(LEDONOFF, _ledOn, 24))
164 return false;
165
166 // Step 2: Set GCC to 0x01 for OSD
167 uint8_t gcc_osd = 0x01;
168 if (!_syncWrite(GCC, &gcc_osd, 1))
169 return false;
170
171 // Step 3: Trigger OSD strobe (CR with OSD bit set)
172 uint8_t cr_osd = static_cast<uint8_t>(_crValue | CR_OSD);
173 if (!_syncWrite(CR, &cr_osd, 1))
174 return false;
175
176 // Step 4: Wait for OSD to complete
177 delay(10); // 10ms to ensure completion
178
179 // Step 5: Read LEDOPEN and LEDSHORT registers
180 if (!_syncRead(LEDOPEN, _ledOpen, 24))
181 return false;
182 if (!_syncRead(LEDSHORT, _ledShort, 24))
183 return false;
184
185 // Store ISR status read after OSD.
186 uint8_t isr_status = 0;
187 if (_syncRead((uint16_t)ISR << 8, &isr_status, 1))
188 _lastISR = isr_status;
189
190 // Compute LED On/Off mask based on configured fault policy.
191 // NOTE: Short circuit detection disabled due to spurious shorts detected on
192 // red LED rows during OSD sequence. Only OPEN detection is used to disable LEDs.
193 for (size_t i = 0; i < 24; i++) {
194 // Only disable LEDs if they are detected as open (not shorted)
195 _ledOn[i] = static_cast<uint8_t>(_ledOn[i] & ~_ledOpen[i]);
196 }
197
198 // Write updated LEDONOFF mask (if faults detected)
199 if (!_syncWrite(LEDONOFF, _ledOn, 24))
200 return false;
201
202 // Step 6: Restore GCC to normal operating value (0xFF)
203 uint8_t gcc_normal = 0xFF;
204 if (!_syncWrite(GCC, &gcc_normal, 1))
205 return false;
206
207 // Step 7: Clear CR OSD bit for normal operation
208 if (!_syncWrite(CR, &_crValue, 1))
209 return false;
210
211 // Step 8: Force all LED mode registers to normal PWM mode. RESET should leave
212 // Page 2 at 0x00, but write it explicitly during begin so no previous ABM state
213 // can survive a partial init or bus fault.
214 uint8_t pwmMode[kHardwareCols];
215 memset(pwmMode, static_cast<uint8_t>(ABMMode::PWM_MODE), sizeof(pwmMode));
216 for (uint8_t row = 0u; row < kHardwareRows; ++row) {
217 if (!_syncWrite(static_cast<uint16_t>(LEDABM + (row * kHardwareCols)), pwmMode,
218 sizeof(pwmMode)))
219 return false;
220 memset(_abm_matrix[row] + 1, static_cast<uint8_t>(ABMMode::PWM_MODE), kHardwareCols);
221 }
222
223 // Step 9: Minimal OSD sequence (if IRQ pin provided)
224 if (_irqPin != 0xFF) {
225 // Unmask interrupts for runtime fault detection.
226 uint8_t imr_value = IMR_IO;
227 if (_maskShortFaults)
228 imr_value = static_cast<uint8_t>(imr_value | IMR_IS);
229 if (!_syncWrite((uint16_t)IMR << 8, &imr_value, 1))
230 return false;
231 }
232
233 // Step 10: Set pull-up/down for de-ghosting (default to all enabled)
234 uint8_t purValue = pur & 0b111; // Mask to 3 bits
235 uint8_t pdrValue = pdr & 0b111; // Mask to 3 bits
236 if (!_syncWrite(SWPUR, &purValue, 1))
237 return false;
238 if (!_syncWrite(CSPDR, &pdrValue, 1))
239 return false;
240
241 // ---------------------------------------------------------------------------------
242 // Clear command transaction pointers and set default page to Page 1 (PWM)
243 // ---------------------------------------------------------------------------------
244
245 _cmdCtx[2].userCallback = nullptr;
246 _cmdCtx[2].user = nullptr;
247 _cmdCtx[3].userCallback = nullptr;
248 _cmdCtx[3].user = nullptr;
249
250 _ensurePage(1);
251
252 if (_irqPin != 0xFF) {
253 _instance = this; // Set static instance for ISR access
254 attachInterrupt(digitalPinToInterrupt(_irqPin), _irqCallback, FALLING);
255 }
256
257 _begun = true;
258
259 return true;
260}
261
263 if (!_begun)
264 return;
265
266 _syncRead(CR, _cmdTx, 1); // RESET the device state
267
268 DeviceOff();
269
270 // Detach IRQ if configured
271 if (_irqPin != 0xFF) {
272 detachInterrupt(digitalPinToInterrupt(_irqPin));
273 _instance = nullptr;
274 }
275
276 _begun = false;
277}
278
279// =========================================================================================
280// Device Control
281// =========================================================================================
282
284 if (_sdbPin != 0xFF)
285 digitalWrite(_sdbPin, HIGH);
286
287 // Write cached CR register (SSD + runtime config bits)
288 _asyncWrite(CR, &_crValue, 1, nullptr, nullptr);
289
290 _resumeDataTransfers();
291}
292
294 const uint8_t cr_off = 0x00;
295 _syncWrite(CR, &cr_off, 1); // Ensure CR write completes before cutting power
296
297 if (_sdbPin != 0xFF)
298 digitalWrite(_sdbPin, LOW);
299}
300
301void IS31FL3733::SetGCC(uint8_t gcc) {
302 _asyncWrite(GCC, &gcc, 1, nullptr, nullptr);
303}
304
305void IS31FL3733::SetSWPUR(uint8_t pur) {
306 uint8_t purValue = static_cast<uint8_t>(pur & 0x07);
307 _asyncWrite(SWPUR, &purValue, 1, nullptr, nullptr);
308}
309
310void IS31FL3733::SetCSPDR(uint8_t pdr) {
311 uint8_t pdrValue = static_cast<uint8_t>(pdr & 0x07);
312 _asyncWrite(CSPDR, &pdrValue, 1, nullptr, nullptr);
313}
314
315void IS31FL3733::SetPFS(uint8_t pfs) {
316 _crValue = static_cast<uint8_t>((_crValue & ~0x60u) | CR_PFS(pfs & 0x03));
317 _asyncWrite(CR, &_crValue, 1, nullptr, nullptr);
318}
319
320void IS31FL3733::SetIMR(uint8_t imrMask) {
321 uint8_t mask = static_cast<uint8_t>(imrMask & 0x0F);
322 _asyncWrite((uint16_t)IMR << 8, &mask, 1, nullptr, nullptr);
323}
324
325// =========================================================================================
326// PWM Control (Raw Hardware Interface)
327// =========================================================================================
328
329void IS31FL3733::SetPixelPWM(uint8_t row, uint8_t col, uint8_t pwm) {
330 // guard against out-of-bounds and 0 (1-based indexing)
331 if (row > kHardwareRows || col > kHardwareCols || !row || !col)
332 return;
333
334 // Convert 1-based to 0-based index
335 uint8_t idx = row - 1;
336
337 // [idx][0] is reserved for row address, so column data starts at offset 1
338 _pwm_matrix[idx][col] = pwm;
339
340 // Enqueue row for transmission (if not already enqueued)
341 uint16_t rowBit = 1 << idx;
342 if (_pwmEnqueued & rowBit)
343 return; // Already enqueued
344
345 _pwmEnqueued |= rowBit;
346 _pwmPendingRows.store(idx);
347
348 // Kick transmission if not already in-flight
349 if (!_pwmTxn.txPtr && !_pwmLocked)
350 _sendRowPWM();
351}
352
353void IS31FL3733::SetRowPWM(uint8_t row, const uint8_t *pwmValues) {
354 // guard against out-of-bounds and 0 (1-based indexing)
355 if (row > kHardwareRows || !row)
356 return;
357
358 // Convert 1-based to 0-based index
359 uint8_t idx = (row - 1) & 0x0F;
360
361 // Update pwm buffer for the row (starting at offset 1 since [idx][0] is reserved for row
362 // address)
363 memcpy(_pwm_matrix[idx] + 1, pwmValues, kHardwareCols);
364
365 // Enqueue row for transmission (if not already enqueued)
366 uint16_t rowBit = 1 << idx;
367 if (_pwmEnqueued & rowBit)
368 return; // Already enqueued
369
370 _pwmEnqueued |= rowBit;
371 _pwmPendingRows.store(idx);
372
373 // Kick transmission if not already in-flight
374 if (!_pwmTxn.txPtr && !_pwmLocked)
375 _sendRowPWM();
376}
377
379 _pwmBatchActive = true;
380}
381
382void IS31FL3733::EndPwmBatch(bool flush) {
383 _pwmBatchActive = false;
384
385 if (flush && !_pwmTxn.txPtr && !_pwmLocked)
386 _sendRowPWM();
387}
388
389// =========================================================================================
390// RGB Pixel Control (Logical Coordinates with Color Order)
391// =========================================================================================
392
393void IS31FL3733::SetPixelColor(uint8_t row, uint8_t col, uint8_t r, uint8_t g, uint8_t b) {
394 if (row > kLogicalRows || col > kHardwareCols || !row || !col)
395 return;
396
397 // Convert 1-based to 0-based index
398 uint8_t idx = row - 1;
399
400 // Map logical row to hardware rows based on color order
401 // baseRow will be 0, 3, 6, or 9 for logical rows 0-3
402 // Add 1 to convert to 1-based indexing for SetPixelPWM
403 uint8_t baseRow = idx * 3 + 1;
404
405 // Set PWM for each channel based on color order
406 switch (_colorOrder) {
407 case ColorOrder::RGB:
408 SetPixelPWM(baseRow + 0, col, r); // R on first row
409 SetPixelPWM(baseRow + 1, col, g); // G on second row
410 SetPixelPWM(baseRow + 2, col, b); // B on third row
411 break;
412 case ColorOrder::GRB:
413 SetPixelPWM(baseRow + 0, col, g); // G on first row
414 SetPixelPWM(baseRow + 1, col, r); // R on second row
415 SetPixelPWM(baseRow + 2, col, b); // B on third row
416 break;
417 case ColorOrder::BRG:
418 SetPixelPWM(baseRow + 0, col, b); // B on first row
419 SetPixelPWM(baseRow + 1, col, r); // R on second row
420 SetPixelPWM(baseRow + 2, col, g); // G on third row
421 break;
422 case ColorOrder::RBG:
423 SetPixelPWM(baseRow + 0, col, r); // R on first row
424 SetPixelPWM(baseRow + 1, col, b); // B on second row
425 SetPixelPWM(baseRow + 2, col, g); // G on third row
426 break;
427 case ColorOrder::GBR:
428 SetPixelPWM(baseRow + 0, col, g); // G on first row
429 SetPixelPWM(baseRow + 1, col, b); // B on second row
430 SetPixelPWM(baseRow + 2, col, r); // R on third row
431 break;
432 case ColorOrder::BGR:
433 SetPixelPWM(baseRow + 0, col, b); // B on first row
434 SetPixelPWM(baseRow + 1, col, g); // G on second row
435 SetPixelPWM(baseRow + 2, col, r); // R on third row
436 break;
437 }
438}
439
440// =========================================================================================
441// Bulk Operations
442// =========================================================================================
443
444void IS31FL3733::Fill(uint8_t pwm) {
445 // Fill all rows with the same PWM value
446 for (uint8_t row = 0; row < kHardwareRows; row++) {
447 memset(_pwm_matrix[row] + 1, pwm, kHardwareCols);
448
449 // Enqueue row
450 uint16_t rowBit = 1 << row;
451 if (!(_pwmEnqueued & rowBit)) {
452 _pwmEnqueued |= rowBit;
453 _pwmPendingRows.store(row);
454 }
455 }
456
457 // Kick transmission if not already in-flight
458 if (!_pwmTxn.txPtr && !_pwmLocked)
459 _sendRowPWM();
460}
461
462// =========================================================================================
463// Mode Control (ABM / LED Mode Selection - Page 2)
464// =========================================================================================
465
466void IS31FL3733::SetPixelMode(uint8_t row, uint8_t col, ABMMode mode) {
467 // guard against out-of-bounds and 0 (1-based indexing)
468 if (row > kHardwareRows || col > kHardwareCols || !row || !col)
469 return;
470
471 // Convert 1-based to 0-based index
472 uint8_t idx = row - 1;
473
474 // [idx][0] is reserved for row address, so column data starts at offset 1
475 _abm_matrix[idx][col] = static_cast<uint8_t>(mode);
476
477 // Enqueue row for transmission (if not already enqueued)
478 uint16_t rowBit = 1 << idx;
479 if (_abmEnqueued & rowBit)
480 return; // Already enqueued
481
482 _abmEnqueued |= rowBit;
483 _abmPendingRows.store(idx);
484
485 // Kick transmission if not already in-flight
486 if (!_abmTxn.txPtr && !_abmLocked)
487 _sendRowMode();
488}
489
490void IS31FL3733::SetRowMode(uint8_t row, ABMMode mode) {
491 // guard against out-of-bounds and 0 (1-based indexing)
492 if (row > kHardwareRows || !row)
493 return;
494
495 // Convert 1-based to 0-based index
496 uint8_t idx = (row - 1) & 0x0F;
497
498 // Update mode buffer for the row (starting at offset 1 since [idx][0] is reserved for row
499 // address)
500 memset(_abm_matrix[idx] + 1, static_cast<uint8_t>(mode), kHardwareCols);
501
502 // Enqueue row for transmission (if not already enqueued)
503 uint16_t rowBit = 1 << idx;
504 if (_abmEnqueued & rowBit)
505 return; // Already enqueued
506
507 _abmEnqueued |= rowBit;
508 _abmPendingRows.store(idx);
509
510 // Kick transmission if not already in-flight
511 if (!_abmTxn.txPtr && !_abmLocked)
512 _sendRowMode();
513}
514
515void IS31FL3733::SetMatrixMode(ABMMode mode) {
516 // Fill all rows with the same mode value (Page 2)
517 for (uint8_t row = 0; row < kHardwareRows; row++) {
518 memset(_abm_matrix[row] + 1, static_cast<uint8_t>(mode), kHardwareCols);
519
520 // Enqueue row
521 uint16_t rowBit = 1 << row;
522 if (!(_abmEnqueued & rowBit)) {
523 _abmEnqueued |= rowBit;
524 _abmPendingRows.store(row);
525 }
526 }
527
528 // Kick transmission if not already in-flight
529 if (!_abmTxn.txPtr && !_abmLocked) {
530 _sendRowMode();
531 }
532}
533
534ABMMode IS31FL3733::GetPixelMode(uint8_t row, uint8_t col) const {
535 // guard against out-of-bounds and 0 (1-based indexing)
536 if (row > kHardwareRows || col > kHardwareCols || !row || !col)
537 return ABMMode::PWM_MODE;
538
539 // Convert 1-based to 0-based index
540 uint8_t idx = row - 1;
541
542 // Read from cached ABM matrix
543 return static_cast<ABMMode>(_abm_matrix[idx][col]);
544}
545
546void IS31FL3733::SetPixelColorMode(uint8_t row, uint8_t col, ABMMode mode) {
547 if (row > kLogicalRows || col > kHardwareCols || !row || !col)
548 return;
549
550 // Convert 1-based to 0-based index
551 uint8_t idx = row - 1;
552
553 // Map logical row to hardware rows based on color order
554 // baseRow will be 0, 3, 6, or 9 for logical rows 0-3
555 // Add 1 to convert to 1-based indexing for SetPixelMode
556 uint8_t baseRow = idx * 3 + 1;
557
558 // Set mode for each channel (all three use the same ABM mode for "color")
559 SetPixelMode(baseRow + 0, col, mode);
560 SetPixelMode(baseRow + 1, col, mode);
561 SetPixelMode(baseRow + 2, col, mode);
562}
563
564// =========================================================================================
565// Core Transaction Methods
566// =========================================================================================
567
568void IS31FL3733::_ensurePage(uint8_t page) {
569 // Skip page select if we're already on the target page
570 if (_currentPage == page)
571 return;
572
573 // Enqueue unlock transaction (pre-staged in constructor)
574 _cmdTxn[0].chainNext = false;
575 _hw->enqueueWIRE(&_cmdTxn[0]);
576
577 // Update and enqueue page select transaction
578 _pgSelTx[0] = PSR;
579 _pgSelTx[1] = page & 0b11; // Mask to 2 bits (0-3)
580 _cmdTxn[1].chainNext = false;
581 _hw->enqueueWIRE(&_cmdTxn[1]);
582
583 // Update tracked page
584 _currentPage = page;
585}
586
588 _pwmLocked = false; // Clear PWM lock flag
589 _abmLocked = false; // Clear ABM lock flag
590 _sendRowPWM(); // Resume pending PWM writes
591 _sendRowMode(); // Resume pending ABM writes
592}
593
594bool IS31FL3733::_syncWrite(uint16_t pagereg, const uint8_t *data, uint8_t len) {
595 _syncComplete = false;
596 _syncStatus = 0;
597 _syncTargetCmd = 2;
598 _cmdError &= ~((1u << 0) | (1u << 1) | (1u << 2));
599 _cmdCtx[2].initialStatus = 0;
600
601 _asyncWrite(pagereg, data, len, nullptr, nullptr);
602
603 constexpr uint32_t timeoutUs = 100000ul; // 100 ms
604 const uint32_t start = micros();
605 while (!_syncComplete && (static_cast<uint32_t>(micros() - start) < timeoutUs)) {
606 yield();
607 }
608
609 if (!_syncComplete) {
610 _syncStatus = -1;
611 _syncTargetCmd = -1;
612 _syncComplete = true;
613 return false;
614 }
615
616 return (_syncStatus == 0) && ((_cmdError & ((1u << 0) | (1u << 1) | (1u << 2))) == 0) &&
617 (_cmdCtx[2].initialStatus == 0);
618}
619
620bool IS31FL3733::_syncRead(uint16_t pagereg, uint8_t *dest, uint8_t len) {
621 _syncComplete = false;
622 _syncStatus = 0;
623 _syncTargetCmd = 3;
624 _cmdError &= ~((1u << 0) | (1u << 1) | (1u << 2) | (1u << 3));
625 _cmdCtx[3].initialStatus = 0;
626
627 _asyncRead(pagereg, dest, len, nullptr, nullptr);
628
629 constexpr uint32_t timeoutUs = 100000ul; // 100 ms
630 const uint32_t start = micros();
631 while (!_syncComplete && (static_cast<uint32_t>(micros() - start) < timeoutUs)) {
632 yield();
633 }
634
635 if (!_syncComplete) {
636 _syncStatus = -1;
637 _syncTargetCmd = -1;
638 _syncComplete = true;
639 return false;
640 }
641
642 return (_syncStatus == 0) &&
643 ((_cmdError & ((1u << 0) | (1u << 1) | (1u << 2) | (1u << 3))) == 0) &&
644 (_cmdCtx[3].initialStatus == 0);
645}
646
647// =========================================================================================
648// ABM Configuration (Page 3 Register Updates)
649// =========================================================================================
650
651void IS31FL3733::SetABMCallback(uint8_t abmNum, std::function<void()> callback) {
652 if (abmNum < 1 || abmNum > 3)
653 return;
654
655 _abmCallbacks[abmNum - 1] = callback;
656}
657
658void IS31FL3733::ConfigureABM(uint8_t abmNumber, const ABMConfig &config) {
659 if (abmNumber < 1 || abmNumber > 3)
660 return;
661 uint16_t pagereg = 0;
662 // Route to specific ABM configurator
663 switch (abmNumber) {
664 case 1:
665 pagereg = ABM1CR;
666 break;
667 case 2:
668 pagereg = ABM2CR;
669 break;
670 case 3:
671 pagereg = ABM3CR;
672 break;
673 }
674
675 const uint8_t cfg[4] = {
676 static_cast<uint8_t>(static_cast<uint8_t>(config.T1) | static_cast<uint8_t>(config.T2)),
677 static_cast<uint8_t>(static_cast<uint8_t>(config.T3) | static_cast<uint8_t>(config.T4)),
678 static_cast<uint8_t>(static_cast<uint8_t>(config.Tend) |
679 static_cast<uint8_t>(config.Tbegin) |
680 static_cast<uint8_t>((config.Times >> 8) & 0x0F)),
681 static_cast<uint8_t>(config.Times & 0xFF),
682 };
683 _asyncWrite(pagereg, cfg, 4, nullptr, nullptr);
684
685 // Write 1 byte of zeros to TUR (0x0E) to complete latch (per datasheet: "Any write to
686 // 0Eh will latch the ABM configuration")
687 uint8_t turPadding[1] = {0};
688 _asyncWrite(TUR, turPadding, 1, nullptr, nullptr);
689}
690
692 ConfigureABM(1, config);
693}
694
696 ConfigureABM(2, config);
697}
698
700 ConfigureABM(3, config);
701}
702
703void IS31FL3733::EnableABM(bool enable) {
704 if (enable)
705 _crValue |= CR_BEN;
706 else
707 _crValue &= static_cast<uint8_t>(~CR_BEN);
708
709 _asyncWrite(CR, &_crValue, 1, nullptr, nullptr);
710}
711
713 // Ensure Page 3
714 _ensurePage(3);
715
716 // Write any value to TUR (0x0E) to latch configuration
717 // Per datasheet: "Any write to 0Eh will latch the ABM configuration"
718 // We write 0x00 as the trigger value
719 uint8_t tur_value = 0x00;
720 _asyncWrite(TUR, &tur_value, 1, nullptr, nullptr);
721}
722
723// =========================================================================================
724// Static Callbacks
725// =========================================================================================
726
727void IS31FL3733::_osbCallback(void *user, int status) {
728 IS31FL3733 *self = (IS31FL3733 *)user;
729
730 // Update LED On/Off mask from open faults only. Short detection is not used for
731 // masking because this hardware reports spurious shorts on active LED rows.
732 for (size_t i = 0; i < 24; i++) {
733 self->_ledOn[i] = static_cast<uint8_t>(self->_ledOn[i] & ~self->_ledOpen[i]);
734 }
735
736 // Write updated LED On/Off register
737 self->_asyncWrite(LEDONOFF, self->_ledOn, 24, nullptr, nullptr);
738
739 // Restore Page 1 and resume PWM/ABM
740 self->_resumeDataTransfers();
741}
742
743void IS31FL3733::_cmdCallback(void *user, int status) {
744 auto *context = static_cast<CmdTxnContext *>(user);
745 if (!context || !context->self)
746 return;
747
748 IS31FL3733 *self = context->self;
749 const uint8_t bit = static_cast<uint8_t>(1u << context->index);
750
751 self->_cmdReturn |= bit;
752 if (status != 0)
753 self->_cmdError |= bit;
754
755 if (self->_syncTargetCmd == static_cast<int8_t>(context->index)) {
756 self->_syncStatus = status;
757 self->_syncComplete = true;
758 self->_syncTargetCmd = -1;
759 }
760
761 // For non-final phases: accumulate status in next phase's context
762 if (!context->isFinal && context->index < 3) {
763 // Chain accumulated status forward: next phase sees all previous failures
764 self->_cmdCtx[context->index + 1].initialStatus |= status;
765 return; // Don't invoke user callback on intermediate phase
766 }
767
768 // Only invoke user callback on final transaction
769 if (context->isFinal && context->userCallback)
770 context->userCallback(context->user, status);
771}
772
773// =========================================================================================
774// ABM Callback Wrappers (Static Entry Points for PendSV Dispatch)
775// =========================================================================================
776
778 if (_instance && _instance->_abmCallbacks[0])
779 _instance->_abmCallbacks[0]();
780}
781
783 if (_instance && _instance->_abmCallbacks[1])
784 _instance->_abmCallbacks[1]();
785}
786
788 if (_instance && _instance->_abmCallbacks[2])
789 _instance->_abmCallbacks[2]();
790}
791
792} // namespace IS31FL3733
Asynchronous DMA-driven IS31FL3733 LED driver for SAMD SERCOM I2C.
void ConfigureABM2(const ABMConfig &config)
Configure ABM-2 control registers (Page 3 ABM2CR..ABM2CR+4).
static void _osbCallback(void *user, int status)
Open/Short detection callback (updates LED On/Off mask).
void SetMatrixMode(ABMMode mode)
Set mode for the entire matrix (Page 2).
void SetPFS(uint8_t pfs)
Set PWM frequency selection bits in CR (PFS[6:5]). Preserves other tracked CR bits (e....
void ConfigureABM3(const ABMConfig &config)
Configure ABM-3 control registers (Page 3 ABM3CR..ABM3CR+4).
void BeginPwmBatch()
Begin a batched PWM update.
void DeviceOff()
Disable the device (hardware and software shutdown).
void end()
RESET device and disable (hardware shutdown). Reads RESET register to trigger software reset,...
void SetPixelMode(uint8_t row, uint8_t col, ABMMode mode)
Set mode for a single LED (hardware coordinates, Page 2).
void EnableABM(bool enable=true)
Enable or disable ABM engine in CR (Page 3).
static IS31FL3733 * _instance
Static instance pointer for ISR access.
void SetRowPWM(uint8_t row, const uint8_t *pwmValues)
Set PWM values for an entire row (hardware coordinates).
bool begin(uint8_t pfs=0, uint8_t pur=0b111, uint8_t pdr=0b111)
Initialize the device (blocking for initial config).
static void _abm3CallbackWrapper()
Dispatch ABM3 completion callback on a safe context.
static void _abm1CallbackWrapper()
Static wrappers for ABM completion callback dispatch (PendSV-safe entry points).
void ConfigureABM(uint8_t abmNumber, const ABMConfig &config)
Configure ABM timing/loop registers for ABM1/2/3 on Page 3.
void SetSWPUR(uint8_t pur)
Set SW pull-up resistor control (Page 3 SWPUR).
~IS31FL3733()
Destroy the driver; calls end() if begin() succeeded.
void Fill(uint8_t pwm=0)
Fill the entire matrix with a PWM value.
void ConfigureABM1(const ABMConfig &config)
Configure ABM-1 control registers (Page 3 ABM1CR..ABM1CR+4).
void SetABMCallback(uint8_t abmNum, std::function< void()> callback)
Register callback for ABM1/2/3 completion.
static void _abm2CallbackWrapper()
Dispatch ABM2 completion callback on a safe context.
void EndPwmBatch(bool flush=true)
End a batched PWM update and optionally flush queued rows.
void _ensurePage(uint8_t page)
Ensure we're on the target page (skips if already there, enqueues unlock + page-select if needed).
bool _syncWrite(uint16_t pagereg, const uint8_t *data, uint8_t len)
Blocking write helper used by begin() setup flow.
void SetGCC(uint8_t gcc)
Set global current control register (Page 3 GCC).
ABMMode GetPixelMode(uint8_t row, uint8_t col) const
Get mode for a single LED (hardware coordinates, Page 2).
void _resumeDataTransfers()
Restore Page 1, clear _pwmLocked, resume PWM writes.
void SetCSPDR(uint8_t pdr)
Set CS pull-down resistor control (Page 3 CSPDR).
void SetIMR(uint8_t imrMask)
Set interrupt mask register (Common IMR).
void DeviceOn()
Enable the device (hardware and software startup).
bool _syncRead(uint16_t pagereg, uint8_t *dest, uint8_t len)
Blocking read helper used by begin() setup flow.
void SetPixelColor(uint8_t row, uint8_t col, uint8_t r, uint8_t g, uint8_t b)
Set RGB color for a logical pixel (with color order mapping).
void SetRowMode(uint8_t row, ABMMode mode)
Set mode for an entire row (hardware coordinates, Page 2).
static void _cmdCallback(void *user, int status)
Command transaction callback (triggered by SERCOM ISR).
void SetPixelColorMode(uint8_t row, uint8_t col, ABMMode mode)
Set mode for a logical RGB pixel (with color order mapping).
void TriggerABM()
Latch ABM timing updates by writing TUR on Page 3.
void SetPixelPWM(uint8_t row, uint8_t col, uint8_t pwm)
Set PWM duty cycle for a single LED (hardware coordinates).
High-level ABM configuration values before register packing.
AbmLoopBegin Tbegin
Loop begin segment selector.
AbmT1 T1
Fade-in segment duration selector.
AbmT4 T4
Off-time segment duration selector.
AbmT2 T2
Hold-high segment duration selector.
AbmLoopEnd Tend
Loop end segment selector.
AbmT3 T3
Fade-out segment duration selector.
uint16_t Times
Loop count (0 = kAbmLoopForever, max = kAbmLoopTimesMax)
Context passed to SERCOM ISR callback for command-chain transactions.