-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Default to RMT driver, make I2S/LCD optional with parallel/single mode support for ESP32 variants #5267
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
Important Review skippedBot user detected. To trigger a single review, invoke the You can disable this status message by setting the Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughReplaces the single Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Comment |
Co-authored-by: DedeHai <6280424+DedeHai@users.noreply.github.com>
|
@copilot you need to distinguish the outputs between "single I2S" and "parallel I2S" as the two NPB methods are different. |
- Renamed useParallelI2S to useI2S globally - Added _useI2S flag to PolyBus class (separate from _useParallelI2S) - Implemented logic in finalizeInit() to distinguish between parallel and single I2S based on bus types - Restored single I2S support for ESP32 S2 - Added BusManager wrapper functions for I2S flags - Added UI display showing I2S/RMT driver per bus in settings_leds.htm Co-authored-by: DedeHai <6280424+DedeHai@users.noreply.github.com>
Implemented in commit afdd0e3:
|
Changed hasI2SOutput() to hasParallelOutput() check since single I2S uses same memory as RMT. Co-authored-by: DedeHai <6280424+DedeHai@users.noreply.github.com>
|
@coderabbitai review, please check that no functionality is lost and that there are no conflicting declarations. and that the outputs always use the correct driver as intended. |
🧩 Analysis chain🏁 Script executed: Length of output: 171 🏁 Script executed: Length of output: 281 🏁 Script executed: Length of output: 697 🏁 Script executed: Length of output: 5023 🏁 Script executed: Length of output: 6551 🏁 Script executed: Length of output: 5872 🏁 Script executed: Length of output: 5005 🏁 Script executed: Length of output: 1482 🏁 Script executed: Length of output: 6135 🏁 Script executed: Length of output: 20239 🏁 Script executed: Length of output: 505 🏁 Script executed: Length of output: 3684 🏁 Script executed: Length of output: 5522 🏁 Script executed: Length of output: 1067 🏁 Script executed: Length of output: 699 🏁 Script executed: Length of output: 990 @DedeHai, I've completed a comprehensive review of PR #5267. Here's my analysis: ✅ No Conflicting DeclarationsAll variable declarations are properly scoped:
✅ Driver Selection Logic - Correct for All VariantsESP32 (standard):
ESP32-S2:
ESP32-S3:
ESP32-C3:
✅ ESP32-S2 I2S Support PreservedThe S2 support for both parallel and single I2S modes is properly implemented and was not removed. ✅ Functionality Preserved with Intended Behavior ChangeKey changes (all intentional per PR objectives):
✅ Memory CalculationThe backend correctly allocates extra DMA memory only for parallel I2S mode: const bool usesI2S = (BusManager::hasParallelOutput() && digitalCount <= 8);This aligns with the retrieved learning that single I2S uses the same memory as RMT. ✅ UI Driver Info DisplayThe JavaScript logic correctly displays " (I2S)" or " (RMT)" for each bus based on the driver selection logic. ✅ Channel Adjustment RemovalThe old code that adjusted channels for single I2S on standard ESP32 was removed: if (!_useParallelI2S && channel > 0) channel--;This is acceptable because single I2S methods in NeoPixelBus are hardcoded to specific hardware buses and don't rely on the channel parameter passed during bus creation. SummaryNo functionality is lost ✅ The implementation is solid and achieves all the PR objectives. The transition from I2S-by-default to RMT-by-default is an intentional breaking change that addresses the flickering issues reported in 0.15.x. 🧠 Learnings used✅ Actions performedReview triggered.
|
|
@coderabbitai please compile a comparison of the bus allocation logic before and after. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 420 🏁 Script executed: Length of output: 5234 🏁 Script executed: Length of output: 2058 🏁 Script executed: Length of output: 54 🏁 Script executed: Length of output: 5550 🏁 Script executed: Length of output: 3352 🏁 Script executed: Length of output: 327 🏁 Script executed: Length of output: 5383 🏁 Script executed: Length of output: 79 🏁 Script executed: Length of output: 5584 @DedeHai, here's a comprehensive comparison of the bus allocation logic before and after PR #5267: Bus Allocation Logic Comparison1. FX_fcn.cpp - finalizeInit() Decision LogicBEFORE (main branch):// determine if it is sensible to use parallel I2S outputs on ESP32
unsigned maxLedsOnBus = 0;
unsigned busType = 0;
for (const auto &bus : busConfigs) {
if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type)) {
digitalCount++;
if (busType == 0) busType = bus.type;
if (busType != bus.type) {
DEBUG_PRINTF_P(PSTR("Mixed digital bus types detected! Forcing single I2S output.\n"));
useParallelI2S = false; // mixed bus types, no parallel I2S
}
if (bus.count > maxLedsOnBus) maxLedsOnBus = bus.count;
}
}
// we may remove 600 LEDs per bus limit when NeoPixelBus is updated beyond 2.8.3
if (maxLedsOnBus <= 600 && useParallelI2S) BusManager::useParallelOutput();
else useParallelI2S = false; // enforce single I2SBehavior: Global AFTER (PR #5267):// Determine if I2S/LCD should be used and whether parallel mode is possible
unsigned maxLedsOnBus = 0;
unsigned busType = 0;
bool mixedBusTypes = false;
for (const auto &bus : busConfigs) {
if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type)) {
digitalCount++;
if (busType == 0) busType = bus.type;
if (busType != bus.type) {
mixedBusTypes = true; // just flag it
}
if (bus.count > maxLedsOnBus) maxLedsOnBus = bus.count;
}
}
// Determine parallel vs single I2S usage
bool useParallelI2S = false;
if (useI2S) {
// Parallel I2S only possible if: no mixed bus types, LEDs per bus <= 600, and enabled by user
if (!mixedBusTypes && maxLedsOnBus <= 600) {
useParallelI2S = true;
DEBUG_PRINTF_P(PSTR("Using parallel I2S/LCD output.\n"));
} else {
DEBUG_PRINTF_P(PSTR("Using single I2S output (mixed types or >600 LEDs/bus).\n"));
}
}
// Set the flags in PolyBus via BusManager
BusManager::useI2SOutput(useI2S);
if (useParallelI2S) {
BusManager::useParallelOutput();
}Behavior: New 2. Memory Calculation ChangeBEFORE:#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S3)
const bool usesI2S = ((useParallelI2S && digitalCount <= 8) || (!useParallelI2S && digitalCount == 1));
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
const bool usesI2S = (useParallelI2S && digitalCount <= 8);Issue: On ESP32/S3, single I2S (first bus) was incorrectly treated as requiring extra DMA memory. AFTER:#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32S2)
const bool usesI2S = (BusManager::hasParallelOutput() && digitalCount <= 8);Fix: Only parallel I2S allocates extra memory. Single I2S uses same memory as RMT. 3. bus_wrapper.h - getI() Channel AllocationESP32 StandardBEFORE:// standard ESP32 has 8 RMT and x1/x8 I2S1 channels
if (_useParallelI2S) {
if (num > 15) return I_NONE;
if (num < 8) offset = 1; // 8 I2S followed by 8 RMT
} else {
if (num > 9) return I_NONE;
if (num == 0) offset = 1; // prefer I2S1 for 1st bus (less flickering but more RAM needed)
}Behavior:
Plus this additional adjustment: // since 0.15.0-b3 I2S1 is favoured for classic ESP32
if (!_useParallelI2S && channel > 0) channel--; // accommodate I2S1 as 1st busAFTER:// standard ESP32 has 8 RMT channels and optionally x8 I2S1 channels
if (_useI2S) {
if (_useParallelI2S) {
// Parallel I2S: use x8 I2S1 channels for first 8 buses, then RMT for remaining
if (num > 15) return I_NONE;
if (num < 8) offset = 1; // 8 I2S followed by 8 RMT
} else {
// Single I2S: use RMT for first buses, single I2S for the last bus
if (num > 8) return I_NONE; // 8 RMT + 1 I2S
if (num == 8) offset = 1; // only last bus uses single I2S1
}
} else {
if (num > 7) return I_NONE; // only 8 RMT channels available
}Behavior:
Channel adjustment removed: // DELETED: if (!_useParallelI2S && channel > 0) channel--;ESP32-S2BEFORE:// ESP32-S2 only has 4 RMT channels
if (_useParallelI2S) {
if (num > 11) return I_NONE;
if (num < 8) offset = 1; // use x8 parallel I2S0 channels followed by RMT
} else {
if (num > 4) return I_NONE;
if (num > 3) offset = 1; // only one I2S0 (use last to allow Audioreactive)
}Behavior:
AFTER:// ESP32-S2 has 4 RMT channels
if (_useI2S) {
if (_useParallelI2S) {
// Parallel I2S: use x8 I2S0 channels for first 8 buses, then RMT for remaining
if (num > 11) return I_NONE;
if (num < 8) offset = 1;
} else {
// Single I2S: use RMT for first buses, single I2S for the last bus
if (num > 4) return I_NONE; // 4 RMT + 1 I2S
if (num == 4) offset = 1; // only last bus uses single I2S0
}
} else {
if (num > 3) return I_NONE; // only 4 RMT channels available
}Behavior:
ESP32-S3BEFORE:// On ESP32-S3 only the first 4 RMT channels are usable for transmitting
if (_useParallelI2S) {
if (num > 11) return I_NONE;
if (num < 8) offset = 1; // use x8 parallel I2S LCD channels, followed by RMT
} else {
if (num > 3) return I_NONE; // do not use single I2S (as it is not supported)
}Behavior:
AFTER:// On ESP32-S3 only the first 4 RMT channels are usable for transmitting
if (_useI2S) {
if (_useParallelI2S) {
// Parallel LCD: use x8 LCD channels for first 8 buses, then RMT for remaining
if (num > 11) return I_NONE;
if (num < 8) offset = 1;
} else {
// Single I2S not supported on S3
if (num > 3) return I_NONE; // only 4 RMT channels available
}
} else {
if (num > 3) return I_NONE; // only 4 RMT channels available
}Behavior: Same as before, but with explicit guard against single I2S mode. More structured conditionals. ESP32-C3No changes - C3 remains 2× RMT only, I2S not supported. Summary of Key Behavioral Changes
The most significant change: RMT is now the default driver instead of I2S, addressing flickering issues while making I2S an explicit opt-in feature. 🧠 Learnings used |
|
@copilot Critical: sameType and maxLC are used before they are defined. The variables sameType and maxLC are referenced at line 335, but they are initialized later in the code: sameType is declared at line 359 |
wled00/data/settings_leds.htm
Outdated
| let n = s.name.substring(2,3); | ||
| let t = parseInt(s.value); | ||
| let drv = gId("drv"+n); | ||
| if (drv && isDig(t) && !isD2P(t) && !is8266() && !isC3()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot the logic of this block seems overly complicated. just use RMT as a default (except for ESP8266) and change to I2S if needed, keep the code as compact as possible.
also in general: if mixed, set buses larger than the number of RMTs to I2S instead of using a single fixed number to make it easier to accomodate future updates.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in commit 5589bfd. Simplified the driver info display logic:
- RMT is now the default
- For parallel I2S: first 8 buses show "(I2S)"
- For single I2S: uses
rmtCount(ESP32: 8, S2/S3: 4) instead of fixed bus numbers - buses >= rmtCount show "(I2S)" - More compact and future-proof for platform changes
Replaced hard-coded values (9 for ESP32, 5 for S2) with maxD-7 to match bus_wrapper.h logic and be more maintainable. Co-authored-by: DedeHai <6280424+DedeHai@users.noreply.github.com>
- Use RMT as default, only show I2S when needed - For single I2S mode, use rmtCount instead of fixed bus numbers - Buses >= rmtCount show I2S (more future-proof for platform changes) - Reduced complexity and improved code maintainability Co-authored-by: DedeHai <6280424+DedeHai@users.noreply.github.com>
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
@copilot the "remove last bus" logic is not working correctly. Even if I2S is enabled, additional buses are possible, just not single pin digital ones. two pin digital or pwm are still possible but the current logic does not allow to add more buses. |
- Changed logic to only limit and remove single-pin digital buses (affected by RMT/I2S channels) - Other bus types (2-pin digital, PWM, virtual) can still be added beyond digital bus limit - Renamed maxBusCount to maxDigitalBusCount for clarity - Remove buses selectively from end, checking if they are single-pin digital Co-authored-by: DedeHai <6280424+DedeHai@users.noreply.github.com>
Fixed in commit 0408865. Changed the bus removal logic to only limit and remove single-pin digital buses that are affected by RMT/I2S channel constraints:
|
|
@copilot automatically removing invalid digital buses is not working and is confusing for users as buses just disappear. Here is a better approach: similar to pin validation, mark invalid digital buses in red, when saving, do a check. use the warning "invalid output combination" |
- Removed automatic bus removal logic (confusing for users) - Mark invalid single-pin digital buses in red (similar to pin validation) - Added buswarning div showing "Invalid output combination" message - Shows count of current vs max allowed digital buses - Users can now see and understand the issue before saving Co-authored-by: DedeHai <6280424+DedeHai@users.noreply.github.com>
Fixed in commit ea9d53c. Replaced automatic bus removal with visual validation:
|
- Merged two separate forEach loops into one for efficiency - Single loop now handles both invalid bus marking and I2S/RMT driver info - Reduced code duplication and improved performance - Fixed digitalBusIndex logic (changed < to <= for parallel I2S check) Co-authored-by: DedeHai <6280424+DedeHai@users.noreply.github.com>
|
@coderabbitai please check the latest changes for potential issues |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 4515 🏁 Script executed: Length of output: 8482 🏁 Script executed: Length of output: 3964 🏁 Script executed: Length of output: 5071 🏁 Script executed: Length of output: 573 @DedeHai, I've reviewed the latest changes. The memory calculation and most of the logic looks solid now, but I found one critical bug in the UI validation: ❌ Critical Bug:
|
| Platform | Mode | I2S Bus | digitalCount | Condition | Status |
|---|---|---|---|---|---|
| ESP32 | Parallel | 0-7 | 1-8 | <= 8 |
✓ |
| ESP32 | Single | 8 | 9 | == 9 |
✓ |
| S2 | Parallel | 0-7 | 1-8 | <= 8 |
✓ |
| S2 | Single | 4 | 5 | == 5 |
✓ |
| S3 | Parallel | 0-7 | 1-8 | <= 8 |
✓ |
| S3 | Single | N/A | N/A | false |
✓ |
UI Bus Count Limits - CORRECT ✓
// RMT only: ESP32=8, S2/S3=4, C3=2
// Parallel I2S: ESP32=16, S2/S3=12
// Single I2S: maxD-7 → ESP32=9, S2=5, S3=4(RMT only)The formula correctly matches bus_wrapper.h logic.
Driver Info Display - CORRECT ✓
let rmtCount = is32() ? 8 : (isS2() || isS3()) ? 4 : 0;
if (digitalBusIndex > rmtCount && !isS3()) info = " (I2S)";Correctly shows I2S for buses beyond RMT count in single I2S mode.
Visual Validation - CORRECT ✓
- Buses exceeding limits are marked red ✓
- Warning message displays count (e.g., "8/4") ✓
- No longer auto-removes buses ✓
📋 Summary
Critical Issues: 1
mixedvariable not reset (line ~340 in settings_leds.htm)
Verified Correct:
- Memory calculation for all platforms ✓
- Bus count limits ✓
- Driver info display ✓
- Visual validation approach ✓
Recommendation: Add mixed = false; at line ~340 before the bus type detection loop, then this PR should be ready!
🧠 Learnings used
Learnt from: DedeHai
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2026-01-03T16:42:26.035Z
Learning: In WLED PR #5267, the bus allocation strategy for single I2S mode was changed: ESP32 now uses single I2S on bus 8 (the last bus, digitalCount=9) instead of bus 0; ESP32-S2 uses single I2S on bus 4 (digitalCount=5). Memory calculation logic must account for which specific bus uses I2S in single mode, not just check digitalCount <= 8.
Learnt from: DedeHai
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2026-01-03T16:42:26.035Z
Learning: In WLED, both parallel I2S and single I2S modes require additional DMA buffers (4× the pixel buffer size). The memory calculation must account for which specific buses use I2S hardware, not just check digitalCount <= 8, because single I2S mode places the I2S bus at the END (ESP32 bus 8 with digitalCount=9, S2 bus 4 with digitalCount=5).
Learnt from: DedeHai
Repo: wled/WLED PR: 4939
File: wled00/FX_fcn.cpp:1176-1187
Timestamp: 2025-09-16T18:08:42.848Z
Learning: In WLED finalizeInit() bus creation (wled00/FX_fcn.cpp), intentionally allowing memory overruns when bus configurations exceed MAX_LED_MEMORY is a deliberate design choice. The trade-off prioritizes creating buses with reduced LED counts over completely failing to create buses, which would cause no LED output and UI failures. This approach forces users to update configurations after migrating to version 0.16 while maintaining basic functionality.
Learnt from: willmmiles
Repo: wled/WLED PR: 4939
File: wled00/FX_fcn.cpp:1183-1194
Timestamp: 2025-09-18T03:17:30.107Z
Learning: In WLED finalizeInit() bus creation (wled00/FX_fcn.cpp), digitalCount is intentionally incremented even for placeholder buses to maintain consistent resource mapping across all bus configurations.
Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-12-28T14:06:48.772Z
Learning: In WLED post-commit ee9ac947, the rendering pipeline uses per-segment buffers and per-pixel bus updates. Unmapped (0xFFFF) mapping entries are now skipped in WS2812FX::show() (no “clear to black”), which can leave physical gap LEDs with stale/random colors unless they are explicitly cleared. This is a behavior change from pre-0.16 where a full physical buffer was effectively refreshed each frame.
Learnt from: blazoncek
Repo: wled/WLED PR: 5140
File: wled00/data/settings_time.htm:66-76
Timestamp: 2025-12-01T07:01:16.949Z
Learning: In WLED PR #5134, the fix for macros being initialized with the enable bit set only handles new configurations, not existing ones. If there is a bug in timer/macro handling code (e.g., in settings_time.htm), it must be fixed to work correctly for existing configurations as well.
Learnt from: mval-sg
Repo: wled/WLED PR: 4876
File: wled00/xml.cpp:0-0
Timestamp: 2025-08-28T08:09:20.630Z
Learning: The WLED codebase has opportunities for refactoring hardcoded array bounds (like the "15" used for DMX channels) to use sizeof(array)/sizeof(array[0]) for more maintainable code, but such changes should be done consistently across the entire codebase in a dedicated refactoring effort.
Learnt from: willmmiles
Repo: wled/WLED PR: 4939
File: wled00/FX_fcn.cpp:1183-1194
Timestamp: 2025-09-18T03:17:30.107Z
Learning: In WLED bus management code, dead code branches in error handling (like unreachable else breaks) are intentionally preserved to accommodate potential future API changes, even when currently unreachable.
Learnt from: DedeHai
Repo: wled/WLED PR: 4997
File: wled00/e131.cpp:33-44
Timestamp: 2025-10-10T18:34:06.550Z
Learning: In WLED's DDP packet handling (ws.cpp and e131.cpp), only prevent out-of-bounds memory access rather than enforcing DDP spec compliance. Don't check the 1440-byte spec limit—accept out-of-spec packets assuming correct encoding. The bounds check `maxDataIndex = c + numLeds * ddpChannelsPerLed; if (maxDataIndex > dataLen) reject` is sufficient and already covers the timecode flag case (when c=4) without needing separate validation.
Learnt from: willmmiles
Repo: wled/WLED PR: 4890
File: lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h:31-36
Timestamp: 2025-09-02T01:45:58.047Z
Learning: For the NeoEsp32RmtHI driver, RISC-V ESP32-C3 support is currently disabled via bus_wrapper.h rather than compile-time guards, as the maintainer willmmiles is working on resolving underlying nested interrupt issues and prefers to centralize the workaround in one location.
Implementation Complete: Parallel/Single I2S Distinction and Driver Info UI
Changes Made:
Renamed global variable
useParallelI2S→useI2SAdded PolyBus second bool
_useI2Sflag (separate from_useParallelI2S)setI2SOutput()andisI2SOutput()Implemented finalizeInit() logic
_useI2Sand_useParallelI2Sflags appropriatelyUpdated bus_wrapper.h getI() logic
Added BusManager wrapper functions
useI2SOutput()andhasI2SOutput()to access PolyBus flags from FX_fcn.cppAdded UI driver info display
Fixed I2S memory calculation
Fixed UI behavior
Testing:
Original prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.
Summary by CodeRabbit
New Features
UI Improvements
Bug Fixes
✏️ Tip: You can customize this high-level summary in your review settings.