diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index d09c8a605a..1340da91c2 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -2,12 +2,7 @@
# [Choice] Python version: 3, 3.9, 3.8, 3.7, 3.6
ARG VARIANT="3"
-FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT}
-
-# [Option] Install Node.js
-ARG INSTALL_NODE="true"
-ARG NODE_VERSION="lts/*"
-RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
+FROM mcr.microsoft.com/devcontainers/python:0-${VARIANT}
# [Optional] If your pip requirements rarely change, uncomment this section to add them to the image.
# COPY requirements.txt /tmp/pip-tmp/
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 2a8e4712d0..5886418245 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -5,10 +5,7 @@
"context": "..",
"args": {
// Update 'VARIANT' to pick a Python version: 3, 3.6, 3.7, 3.8, 3.9
- "VARIANT": "3",
- // Options
- "INSTALL_NODE": "true",
- "NODE_VERSION": "lts/*"
+ "VARIANT": "3"
}
},
@@ -27,34 +24,35 @@
// risk to running the build directly on the host.
// "runArgs": ["--privileged", "-v", "/dev/bus/usb:/dev/bus/usb", "--group-add", "dialout"],
- // Set *default* container specific settings.json values on container create.
- "settings": {
- "terminal.integrated.shell.linux": "/bin/bash",
- "python.pythonPath": "/usr/local/bin/python",
- "python.linting.enabled": true,
- "python.linting.pylintEnabled": true,
- "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
- "python.formatting.blackPath": "/usr/local/py-utils/bin/black",
- "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
- "python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
- "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
- "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
- "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
- "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
- "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint"
+ "customizations": {
+ "vscode": {
+ "settings": {
+ "terminal.integrated.shell.linux": "/bin/bash",
+ "python.pythonPath": "/usr/local/bin/python",
+ "python.linting.enabled": true,
+ "python.linting.pylintEnabled": true,
+ "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
+ "python.formatting.blackPath": "/usr/local/py-utils/bin/black",
+ "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
+ "python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
+ "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
+ "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
+ "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
+ "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
+ "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint"
+ },
+ "extensions": [
+ "ms-python.python",
+ "platformio.platformio-ide"
+ ]
+ }
},
- // Add the IDs of extensions you want installed when the container is created.
- "extensions": [
- "ms-python.python",
- "platformio.platformio-ide"
- ],
-
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
- "postCreateCommand": "npm install",
+ "postCreateCommand": "bash -i -c 'nvm install && npm ci'",
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 6f5a5be862..b9f43cce22 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,2 +1,2 @@
-github: [Aircoookie,blazoncek]
+github: [DedeHai,lost-hope,willmmiles,netmindz,softhack007]
custom: ['https://paypal.me/Aircoookie','https://paypal.me/blazoncek']
diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml
index 285ad419e4..56b63d4386 100644
--- a/.github/ISSUE_TEMPLATE/bug.yml
+++ b/.github/ISSUE_TEMPLATE/bug.yml
@@ -44,7 +44,10 @@ body:
id: version
attributes:
label: What version of WLED?
- description: You can find this in by going to Config -> Security & Updates -> Scroll to Bottom. Copy and paste the entire line after "Server message"
+ description: |-
+ Find this by going to ⚙️ Config → Security & Updates → Scroll to Bottom.
+ Copy and paste the rest of the line that begins “Installed version: ”,
+ or, for older versions, the entire line after “Server message”.
placeholder: "e.g. WLED 0.13.1 (build 2203150)"
validations:
required: true
@@ -60,6 +63,8 @@ body:
- ESP32-S2
- ESP32-C3
- Other
+ - ESP32-C6 (experimental)
+ - ESP32-C5 (experimental)
validations:
required: true
- type: textarea
@@ -80,7 +85,7 @@ body:
id: terms
attributes:
label: Code of Conduct
- description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/Aircoookie/WLED/blob/master/CODE_OF_CONDUCT.md)
+ description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/wled-dev/WLED/blob/main/CODE_OF_CONDUCT.md)
options:
- label: I agree to follow this project's Code of Conduct
required: true
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 0000000000..bc1f9761a9
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,172 @@
+# WLED - ESP32/ESP8266 LED Controller Firmware
+
+WLED is a fast and feature-rich implementation of an ESP32 and ESP8266 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs and SPI-based chipsets. The project consists of C++ firmware for microcontrollers and a modern web interface.
+
+Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
+
+## Working Effectively
+
+### Initial Setup
+- Install Node.js 20+ (specified in `.nvmrc`): Check your version with `node --version`
+- Install dependencies: `npm ci` (takes ~5 seconds)
+- Install PlatformIO for hardware builds: `pip install -r requirements.txt` (takes ~60 seconds)
+
+### Build and Test Workflow
+- **ALWAYS build web UI first**: `npm run build` -- takes 3 seconds. NEVER CANCEL.
+- **Run tests**: `npm test` -- takes 40 seconds. NEVER CANCEL. Set timeout to 2+ minutes.
+- **Development mode**: `npm run dev` -- monitors file changes and auto-rebuilds web UI
+- **Hardware firmware build**: `pio run -e [environment]` -- takes 15+ minutes. NEVER CANCEL. Set timeout to 30+ minutes.
+
+### Build Process Details
+The build has two main phases:
+1. **Web UI Generation** (`npm run build`):
+ - Processes files in `wled00/data/` (HTML, CSS, JS)
+ - Minifies and compresses web content
+ - Generates `wled00/html_*.h` files with embedded web content
+ - **CRITICAL**: Must be done before any hardware build
+
+2. **Hardware Compilation** (`pio run`):
+ - Compiles C++ firmware for various ESP32/ESP8266 targets
+ - Common environments: `nodemcuv2`, `esp32dev`, `esp8266_2m`
+ - List all targets: `pio run --list-targets`
+
+## Before Finishing Work
+
+**CRITICAL: You MUST complete ALL of these steps before marking your work as complete:**
+
+1. **Run the test suite**: `npm test` -- Set timeout to 2+ minutes. NEVER CANCEL.
+ - All tests MUST pass
+ - If tests fail, fix the issue before proceeding
+
+2. **Build at least one hardware environment**: `pio run -e esp32dev` -- Set timeout to 30+ minutes. NEVER CANCEL.
+ - Choose `esp32dev` as it's a common, representative environment
+ - See "Hardware Compilation" section above for the full list of common environments
+ - The build MUST complete successfully without errors
+ - If the build fails, fix the issue before proceeding
+ - **DO NOT skip this step** - it validates that firmware compiles with your changes
+
+3. **For web UI changes only**: Manually test the interface
+ - See "Manual Testing Scenarios" section below
+ - Verify the UI loads and functions correctly
+
+**If any of these validation steps fail, you MUST fix the issues before finishing. Do NOT mark work as complete with failing builds or tests.**
+
+## Validation and Testing
+
+### Web UI Testing
+- **ALWAYS validate web UI changes manually**:
+ - Start local server: `cd wled00/data && python3 -m http.server 8080`
+ - Open `http://localhost:8080/index.htm` in browser
+ - Test basic functionality: color picker, effects, settings pages
+- **Check for JavaScript errors** in browser console
+
+### Code Validation
+- **No automated linting configured** - follow existing code style in files you edit
+- **Code style**: Use tabs for web files (.html/.css/.js), spaces (2 per level) for C++ files
+- **C++ formatting available**: `clang-format` is installed but not in CI
+- **Always run tests before finishing**: `npm test`
+- **MANDATORY: Always run a hardware build before finishing** (see "Before Finishing Work" section below)
+
+### Manual Testing Scenarios
+After making changes to web UI, always test:
+- **Load main interface**: Verify index.htm loads without errors
+- **Navigation**: Test switching between main page and settings pages
+- **Color controls**: Verify color picker and brightness controls work
+- **Effects**: Test effect selection and parameter changes
+- **Settings**: Test form submission and validation
+
+## Common Tasks
+
+### Repository Structure
+```
+wled00/ # Main firmware source (C++)
+ ├── data/ # Web interface files
+ │ ├── index.htm # Main UI
+ │ ├── settings*.htm # Settings pages
+ │ └── *.js/*.css # Frontend resources
+ ├── *.cpp/*.h # Firmware source files
+ └── html_*.h # Generated embedded web files (DO NOT EDIT)
+tools/ # Build tools (Node.js)
+ ├── cdata.js # Web UI build script
+ └── cdata-test.js # Test suite
+platformio.ini # Hardware build configuration
+package.json # Node.js dependencies and scripts
+.github/workflows/ # CI/CD pipelines
+```
+
+### Key Files and Their Purpose
+- `wled00/data/index.htm` - Main web interface
+- `wled00/data/settings*.htm` - Configuration pages
+- `tools/cdata.js` - Converts web files to C++ headers
+- `wled00/wled.h` - Main firmware configuration
+- `platformio.ini` - Hardware build targets and settings
+
+### Development Workflow
+1. **For web UI changes**:
+ - Edit files in `wled00/data/`
+ - Run `npm run build` to regenerate headers
+ - Test with local HTTP server
+ - Run `npm test` to validate build system
+
+2. **For firmware changes**:
+ - Edit files in `wled00/` (but NOT `html_*.h` files)
+ - Ensure web UI is built first (`npm run build`)
+ - Build firmware: `pio run -e [target]`
+ - Flash to device: `pio run -e [target] --target upload`
+
+3. **For both web and firmware**:
+ - Always build web UI first
+ - Test web interface manually
+ - Build and test firmware if making firmware changes
+
+## Build Timing and Timeouts
+
+**IMPORTANT: Use these timeout values when running builds:**
+
+- **Web UI build** (`npm run build`): 3 seconds typical - Set timeout to 30 seconds minimum
+- **Test suite** (`npm test`): 40 seconds typical - Set timeout to 120 seconds (2 minutes) minimum
+- **Hardware builds** (`pio run -e [target]`): 15-20 minutes typical for first build - Set timeout to 1800 seconds (30 minutes) minimum
+ - Subsequent builds are faster due to caching
+ - First builds download toolchains and dependencies which takes significant time
+- **NEVER CANCEL long-running builds** - PlatformIO downloads and compilation require patience
+
+**When validating your changes before finishing, you MUST wait for the hardware build to complete successfully. Set the timeout appropriately and be patient.**
+
+## Troubleshooting
+
+### Common Issues
+- **Build fails with missing html_*.h**: Run `npm run build` first
+- **Web UI looks broken**: Check browser console for JavaScript errors
+- **PlatformIO network errors**: Try again, downloads can be flaky
+- **Node.js version issues**: Ensure Node.js 20+ is installed (check `.nvmrc`)
+
+### When Things Go Wrong
+- **Clear generated files**: `rm -f wled00/html_*.h` then rebuild
+- **Force web UI rebuild**: `npm run build -- --force` or `npm run build -- -f`
+- **Clean PlatformIO cache**: `pio run --target clean`
+- **Reinstall dependencies**: `rm -rf node_modules && npm install`
+
+## Important Notes
+
+- **DO NOT edit `wled00/html_*.h` files** - they are auto-generated
+- **Always commit both source files AND generated html_*.h files**
+- **Web UI must be built before firmware compilation**
+- **Test web interface manually after any web UI changes**
+- **Use VS Code with PlatformIO extension for best development experience**
+- **Hardware builds require appropriate ESP32/ESP8266 development board**
+
+## CI/CD Pipeline
+
+**The GitHub Actions CI workflow will:**
+1. Installs Node.js and Python dependencies
+2. Runs `npm test` to validate build system (MUST pass)
+3. Builds web UI with `npm run build` (automatically run by PlatformIO)
+4. Compiles firmware for ALL hardware targets listed in `default_envs` (MUST succeed for all)
+5. Uploads build artifacts
+
+**To ensure CI success, you MUST locally:**
+- Run `npm test` and ensure it passes
+- Run `pio run -e esp32dev` (or another common environment from "Hardware Compilation" section) and ensure it completes successfully
+- If either fails locally, it WILL fail in CI
+
+**Match this workflow in your local development to ensure CI success. Do not mark work complete until you have validated builds locally.**
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index e5fdfc5a3e..f0d8537035 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -26,7 +26,7 @@ jobs:
build:
- name: Build Enviornments
+ name: Build Environments
runs-on: ubuntu-latest
needs: get_default_envs
strategy:
@@ -38,8 +38,12 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
+ node-version-file: '.nvmrc'
cache: 'npm'
- - run: npm ci
+ - run: |
+ npm ci
+ VERSION=`date +%y%m%d0`
+ sed -i -r -e "s/define VERSION .+/define VERSION $VERSION/" wled00/wled.h
- name: Cache PlatformIO
uses: actions/cache@v4
with:
@@ -56,6 +60,7 @@ jobs:
cache: 'pip'
- name: Install PlatformIO
run: pip install -r requirements.txt
+
- name: Build firmware
run: pio run -e ${{ matrix.environment }}
- uses: actions/upload-artifact@v4
@@ -74,7 +79,7 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v4
with:
- node-version: '20.x'
+ node-version-file: '.nvmrc'
cache: 'npm'
- run: npm ci
- run: npm test
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
new file mode 100644
index 0000000000..2d47aefe42
--- /dev/null
+++ b/.github/workflows/nightly.yml
@@ -0,0 +1,50 @@
+
+name: Deploy Nightly
+on:
+ # This can be used to automatically publish nightlies at UTC nighttime
+ schedule:
+ - cron: '0 2 * * *' # run at 2 AM UTC
+ # This can be used to allow manually triggering nightlies from the web interface
+ workflow_dispatch:
+
+jobs:
+ wled_build:
+ uses: ./.github/workflows/build.yml
+ nightly:
+ name: Deploy nightly
+ runs-on: ubuntu-latest
+ needs: wled_build
+ steps:
+ - name: Download artifacts
+ uses: actions/download-artifact@v4
+ with:
+ merge-multiple: true
+ - name: Show Files
+ run: ls -la
+ - name: "✏️ Generate release changelog"
+ id: changelog
+ uses: janheinrichmerker/action-github-changelog-generator@v2.4
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ sinceTag: v0.15.0
+ output: CHANGELOG_NIGHTLY.md
+ # Exclude issues that were closed without resolution from changelog
+ excludeLabels: 'stale,wontfix,duplicate,invalid,external,question,use-as-is,not_planned'
+ - name: Update Nightly Release
+ uses: andelf/nightly-release@main
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ tag_name: nightly
+ name: 'Nightly Release $$'
+ prerelease: true
+ body_path: CHANGELOG_NIGHTLY.md
+ files: |
+ *.bin
+ *.bin.gz
+ - name: Repository Dispatch
+ uses: peter-evans/repository-dispatch@v3
+ with:
+ repository: wled/WLED-WebInstaller
+ event-type: release-nightly
+ token: ${{ secrets.PAT_PUBLIC }}
diff --git a/.github/workflows/pr-merge.yaml b/.github/workflows/pr-merge.yaml
new file mode 100644
index 0000000000..1efc366cc5
--- /dev/null
+++ b/.github/workflows/pr-merge.yaml
@@ -0,0 +1,38 @@
+ name: Notify Discord on PR Merge
+ on:
+ workflow_dispatch:
+ pull_request_target:
+ types: [closed]
+
+ jobs:
+ notify:
+ runs-on: ubuntu-latest
+ if: github.event.pull_request.merged == true
+ steps:
+ - name: Get User Permission
+ id: checkAccess
+ uses: actions-cool/check-user-permission@v2
+ with:
+ require: write
+ username: ${{ github.triggering_actor }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Check User Permission
+ if: steps.checkAccess.outputs.require-result == 'false'
+ run: |
+ echo "${{ github.triggering_actor }} does not have permissions on this repo."
+ echo "Current permission level is ${{ steps.checkAccess.outputs.user-permission }}"
+ echo "Job originally triggered by ${{ github.actor }}"
+ exit 1
+ - name: Send Discord notification
+ env:
+ PR_NUMBER: ${{ github.event.pull_request.number }}
+ PR_TITLE: ${{ github.event.pull_request.title }}
+ PR_URL: ${{ github.event.pull_request.html_url }}
+ ACTOR: ${{ github.actor }}
+ run: |
+ jq -n \
+ --arg content "Pull Request #${PR_NUMBER} \"${PR_TITLE}\" merged by ${ACTOR}
+ ${PR_URL} . It will be included in the next nightly builds, please test" \
+ '{content: $content}' \
+ | curl -H "Content-Type: application/json" -d @- ${{ secrets.DISCORD_WEBHOOK_BETA_TESTERS }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 27beec99c3..59de4316cb 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -18,9 +18,19 @@ jobs:
- uses: actions/download-artifact@v4
with:
merge-multiple: true
+ - name: "✏️ Generate release changelog"
+ id: changelog
+ uses: janheinrichmerker/action-github-changelog-generator@v2.4
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ sinceTag: v0.15.0
+ maxIssues: 500
+ # Exclude issues that were closed without resolution from changelog
+ excludeLabels: 'stale,wontfix,duplicate,invalid,external,question,use-as-is,not_planned'
- name: Create draft release
uses: softprops/action-gh-release@v1
with:
+ body: ${{ steps.changelog.outputs.changelog }}
draft: True
files: |
*.bin
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
new file mode 100644
index 0000000000..a9b7aa9b68
--- /dev/null
+++ b/.github/workflows/test.yaml
@@ -0,0 +1,13 @@
+on:
+ workflow_dispatch:
+
+jobs:
+ dispatch:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Repository Dispatch
+ uses: peter-evans/repository-dispatch@v3
+ with:
+ repository: wled/WLED-WebInstaller
+ event-type: release-nightly
+ token: ${{ secrets.PAT_PUBLIC }}
diff --git a/.github/workflows/usermods.yml b/.github/workflows/usermods.yml
new file mode 100644
index 0000000000..e8ab65066d
--- /dev/null
+++ b/.github/workflows/usermods.yml
@@ -0,0 +1,74 @@
+name: Usermod CI
+
+on:
+ pull_request:
+ paths:
+ - usermods/**
+
+jobs:
+
+ get_usermod_envs:
+ # Only run for pull requests from forks (not from branches within wled/WLED)
+ if: github.event.pull_request.head.repo.full_name != github.repository
+ name: Gather Usermods
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
+ with:
+ python-version: '3.12'
+ cache: 'pip'
+ - name: Install PlatformIO
+ run: pip install -r requirements.txt
+ - name: Get default environments
+ id: envs
+ run: |
+ echo "usermods=$(find usermods/ -name library.json | xargs dirname | xargs -n 1 basename | jq -R | grep -v PWM_fan | grep -v BME68X_v2| grep -v pixels_dice_tray | jq --slurp -c)" >> $GITHUB_OUTPUT
+ outputs:
+ usermods: ${{ steps.envs.outputs.usermods }}
+
+
+ build:
+ # Only run for pull requests from forks (not from branches within wled/WLED)
+ if: github.event.pull_request.head.repo.full_name != github.repository
+ name: Build Enviornments
+ runs-on: ubuntu-latest
+ needs: get_usermod_envs
+ strategy:
+ fail-fast: false
+ matrix:
+ usermod: ${{ fromJSON(needs.get_usermod_envs.outputs.usermods) }}
+ environment: [usermods_esp32, usermods_esp32c3, usermods_esp32s2, usermods_esp32s3]
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: 'npm'
+ - run: npm ci
+ - name: Cache PlatformIO
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.platformio/.cache
+ ~/.buildcache
+ build_output
+ key: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-${{ hashFiles('wled00/**', 'usermods/**') }}
+ restore-keys: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.12'
+ cache: 'pip'
+ - name: Install PlatformIO
+ run: pip install -r requirements.txt
+ - name: Add usermods environment
+ run: |
+ cp -v usermods/platformio_override.usermods.ini platformio_override.ini
+ echo >> platformio_override.ini
+ echo "custom_usermods = ${{ matrix.usermod }}" >> platformio_override.ini
+ cat platformio_override.ini
+
+ - name: Build firmware
+ run: pio run -e ${{ matrix.environment }}
diff --git a/.gitignore b/.gitignore
index 8f083e3f6a..62e72a9a0a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,12 @@
.pioenvs
.piolibdeps
.vscode
+compile_commands.json
+__pycache__/
+
+/.dummy
+/dependencies.lock
+/managed_components
esp01-update.sh
platformio_override.ini
@@ -15,6 +21,7 @@ wled-update.sh
/build_output/
/node_modules/
+/logs/
/wled00/extLibs
/wled00/LittleFS
@@ -22,3 +29,4 @@ wled-update.sh
/wled00/Release
/wled00/wled00.ino.cpp
/wled00/html_*.h
+/wled00/js_*.h
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000000..10fef252a9
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+20.18
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c570ac1f7e..f591fc2b2c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -173,7 +173,7 @@
- v0.15.0-b2
- WS2805 support (RGB + WW + CW, 600kbps)
- Unified PSRAM use
-- NeoPixelBus v2.7.9
+- NeoPixelBus v2.7.9 (for future WS2805 support)
- Ubiquitous PSRAM mode for all variants of ESP32
- SSD1309_64 I2C Support for FLD Usermod (#3836 by @THATDONFC)
- Palette cycling fix (add support for `{"seg":[{"pal":"X~Y~"}]}` or `{"seg":[{"pal":"X~Yr"}]}`)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 06c221fca6..1cd77ecd14 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,43 +1,177 @@
-## Thank you for making WLED better!
+# Thank you for making WLED better!
-Here are a few suggestions to make it easier for you to contribute!
+WLED is a community-driven project, and every contribution matters! We appreciate your time and effort.
-### Describe your PR
+Our maintainers are here for two things: **helping you** improve your code, and **keeping WLED** lean, efficient, and maintainable.
+We'll work with you to refine your contribution, but we'll also push back if something might create technical debt or add features without clear value. Don't take it personally - we're just protecting WLED's architecture while helping your contribution succeed!
-Please add a description of your proposed code changes. It does not need to be an exhaustive essay, however a PR with no description or just a few words might not get accepted, simply because very basic information is missing.
+## Getting Started
+
+Here are a few suggestions to make it easier for you to contribute:
+
+### PR from a branch in your own fork
+Start your pull request (PR) in a branch of your own fork. Don't make a PR directly from your main branch.
+This lets you update your PR if needed, while you can work on other tasks in 'main' or in other branches.
+
+> [!TIP]
+> **The easiest way to start your first PR**
+> When viewing a file in `wled/WLED`, click on the "pen" icon and start making changes.
+> When you choose to 'Commit changes', GitHub will automatically create a PR from your fork.
+>
+>
-A good description helps us to review and understand your proposed changes. For example, you could say a few words about
-* what you try to achieve (new feature, fixing a bug, refactoring, security enhancements, etc.)
-* how your code works (short technical summary - focus on important aspects that might not be obvious when reading the code)
-* testing you performed, known limitations, open ends you possibly could not solve.
-* any areas where you like to get help from an experienced maintainer (yes WLED has become big 😉)
### Target branch for pull requests
-Please make all PRs against the `0_15` branch.
+Please make all PRs against the `main` branch.
+
+### Describing your PR
+
+Please add a description of your proposed code changes.
+A PR with no description or just a few words might not get accepted, simply because very basic information is missing.
+No need to write an essay!
+
+A good description helps us to review and understand your proposed changes. For example, you could say a few words about
+* What you try to achieve (new feature, fixing a bug, refactoring, security enhancements, etc.)
+* How your code works (short technical summary - focus on important aspects that might not be obvious when reading the code)
+* Testing you performed, known limitations, anything you couldn't quite solve.
+* Let us know if you'd like guidance from a maintainer (WLED is a big project 😉)
+
+### Testing Your Changes
+
+Before submitting:
+
+- ✅ Does it compile?
+- ✅ Does your feature/fix actually work?
+- ✅ Did you break anything else?
+- ✅ Tested on actual hardware if possible?
+
+Mention your testing in the PR description (e.g., "Tested on ESP32 + WS2812B").
+
+## During Review
+
+We're all volunteers, so reviews can take some time (longer during busy times).
+Don't worry - we haven't forgotten you! Feel free to ping after a week if there's no activity.
### Updating your code
-While the PR is open - and under review by maintainers - you may be asked to modify your PR source code.
-You can simply update your own branch, and push changes in response to reviewer recommendations.
-Github will pick up the changes so your PR stays up-to-date.
+While the PR is open, you can keep updating your branch - just push more commits! GitHub will automatically update your PR.
+
+You don't need to squash commits or clean up history - we'll handle that when merging.
-> [!CAUTION]
+> [!CAUTION]
> Do not use "force-push" while your PR is open!
-> It has many subtle and unexpected consequences on our github reposistory.
-> For example, we regularly lost review comments when the PR author force-pushes code changes. So, pretty please, do not force-push.
+> It has many subtle and unexpected consequences on our GitHub repository.
+> For example, we regularly lose review comments when the PR author force-pushes code changes. Our review bot (coderabbit) may become unable to properly track changes, it gets confused or stops responding to questions.
+> So, pretty please, do not force-push.
+
+> [!TIP]
+> Use [cherry-picking](https://docs.github.com/en/desktop/managing-commits/cherry-picking-a-commit-in-github-desktop) to copy commits from one branch to another.
+
+
+### Responding to Reviews
+
+When we ask for changes:
+
+- **Add new commits** - please don't amend or force-push
+- **Reply in the PR** - let us know when you've addressed comments
+- **Ask questions** - if something's unclear, just ask!
+- **Be patient** - we're all volunteers here 😊
+
+You can reference feedback in commit messages:
+> ```text
+> Fix naming per @Aircoookie's suggestion
+> ```
+
+### Dealing with Merge Conflicts
+
+Got conflicts with `main`? No worries - here's how to fix them:
+
+**Using GitHub Desktop** (easier for beginners):
+
+1. Click **Fetch origin**, then **Pull origin**
+2. If conflicts exist, GitHub Desktop will warn you - click **View conflicts**
+3. Open the conflicted files in your editor (VS Code, etc.)
+4. Remove the conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`) and keep the correct code
+5. Save the files
+6. Back in GitHub Desktop, commit the merge (it'll suggest a message)
+7. Click **Push origin**
+**Using command line**:
-You can find a collection of very useful tips and tricks here: https://github.com/Aircoookie/WLED/wiki/How-to-properly-submit-a-PR
+ ```bash
+ git fetch origin
+ git merge origin/main
+ # Fix conflicts in your editor
+ git add .
+ git commit
+ git push
+ ```
+Either way works fine - pick what you're comfortable with! Merging is simpler than rebasing and keeps everything connected.
+
+#### When you MUST rebase (really rare!)
+
+Sometimes you might hit merge conflicts with `main` that are harder to solve. Here's what to try:
+
+1. **Merge instead of rebase** (safest option):
+ ```bash
+ git fetch origin
+ git merge origin/main
+ git push
+ ```
+ Keeps review comments attached and CI results visible!
+
+2. **Use cherry-picking** to copy commits between branches without rewriting history - [here's how](https://docs.github.com/en/desktop/managing-commits/cherry-picking-a-commit-in-github-desktop).
+
+3. **If all else fails, use `--force-with-lease`** (not plain `--force`):
+ ```bash
+ git rebase origin/main
+ git push --force-with-lease
+ ```
+ Then **leave a comment** explaining why you had to force-push, and be ready to re-address some feedback.
+
+### Additional Resources
+Want to know more? Check out:
+- 📚 [GitHub Desktop documentation](https://docs.github.com/en/desktop) - if you prefer GUI tools
+- 🎓 [How to properly submit a PR](https://github.com/wled-dev/WLED/wiki/How-to-properly-submit-a-PR) - detailed tips and tricks
+
+
+## After Approval
+Once approved, a maintainer will merge your PR (possibly squashing commits).
+Your contribution will be in the next WLED release - thank you! 🎉
+
+
+## Coding Guidelines
+
+### Source Code from an AI agent or bot
+> [!IMPORTANT]
+> It's OK if you took help from an AI for writing your source code.
+>
+> AI tools can be very helpful, but as the contributor, **you're responsible for the code**.
+
+* Make sure you really understand the AI-generated code, don't just accept it because it "seems to work".
+* Don't let the AI change existing code without double-checking by you as the contributor. Often, the result will not be complete. For example, previous source code comments may be lost.
+* Remember that AI is still "Often-Wrong" ;-)
+* If you don't feel confident using English, you can use AI for translating code comments and descriptions into English. AI bots are very good at understanding language. However, always check if the results are correct. The translation might still have wrong technical terms, or errors in some details.
+
+#### Best Practice with AI
+
+AI tools are powerful but "often wrong" - your judgment is essential! 😊
+
+- ✅ **Understand the code** - As the person contributing to WLED, make sure you understand exactly what the AI-generated source code does
+- ✅ **Review carefully** - AI can lose comments, introduce bugs, or make unnecessary changes
+- ✅ **Be transparent** - Add a comment like `// This section was AI-generated` for larger chunks
+- ✅ **Use AI for translation** - AI is great for translating comments to English (but verify technical terms!)
### Code style
-When in doubt, it is easiest to replicate the code style you find in the files you want to edit :)
-Below are the guidelines we use in the WLED repository.
+Don't stress too much about style! When in doubt, just match the style in the files you're editing. 😊
+
+Here are our main guidelines:
#### Indentation
-We use tabs for Indentation in Web files (.html/.css/.js) and spaces (2 per indentation level) for all other files.
+We use tabs for indentation in Web files (.html/.css/.js) and spaces (2 per indentation level) for all other files.
You are all set if you have enabled `Editor: Detect Indentation` in VS Code.
#### Blocks
@@ -55,7 +189,7 @@ if (a == b) {
if (a == b) doStuff(a);
```
-Acceptable - however the first variant is usually easier to read:
+Also acceptable (though the first style is usually easier to read):
```cpp
if (a == b)
{
@@ -86,23 +220,25 @@ if( a==b ){
#### Comments
Comments should have a space between the delimiting characters (e.g. `//`) and the comment text.
-Note: This is a recent change, the majority of the codebase still has comments without spaces.
+We're gradually adopting this style - don't worry if you see older code without spaces!
Good:
-```
-// This is a comment.
-
-/* This is a CSS inline comment */
+```cpp
+// This is a short inline comment.
/*
- * This is a comment
+ * This is a longer comment
* wrapping over multiple lines,
* used in WLED for file headers and function explanations
*/
-
+```
+```css
+/* This is a CSS inline comment */
+```
+```html
```
There is no hard character limit for a comment within a line,
though as a rule of thumb consider wrapping after 120 characters.
-Inline comments are OK if they describe that line only and are not exceedingly wide.
\ No newline at end of file
+Inline comments are OK if they describe that line only and are not exceedingly wide.
diff --git a/boards/adafruit_matrixportal_esp32s3_wled.json b/boards/adafruit_matrixportal_esp32s3_wled.json
new file mode 100644
index 0000000000..3b487d0d4b
--- /dev/null
+++ b/boards/adafruit_matrixportal_esp32s3_wled.json
@@ -0,0 +1,58 @@
+{
+ "build": {
+ "arduino":{
+ "ldscript": "esp32s3_out.ld",
+ "partitions": "default_8MB.csv"
+ },
+ "core": "esp32",
+ "extra_flags": [
+ "-DARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3",
+ "-DARDUINO_USB_CDC_ON_BOOT=1",
+ "-DARDUINO_RUNNING_CORE=1",
+ "-DARDUINO_EVENT_RUNNING_CORE=1",
+ "-DBOARD_HAS_PSRAM"
+ ],
+ "f_cpu": "240000000L",
+ "f_flash": "80000000L",
+ "flash_mode": "qio",
+ "hwids": [
+ [
+ "0x239A",
+ "0x8125"
+ ],
+ [
+ "0x239A",
+ "0x0125"
+ ],
+ [
+ "0x239A",
+ "0x8126"
+ ]
+ ],
+ "mcu": "esp32s3",
+ "variant": "adafruit_matrixportal_esp32s3"
+ },
+ "connectivity": [
+ "bluetooth",
+ "wifi"
+ ],
+ "debug": {
+ "openocd_target": "esp32s3.cfg"
+ },
+ "frameworks": [
+ "arduino",
+ "espidf"
+ ],
+ "name": "Adafruit MatrixPortal ESP32-S3 for WLED",
+ "upload": {
+ "flash_size": "8MB",
+ "maximum_ram_size": 327680,
+ "maximum_size": 8388608,
+ "use_1200bps_touch": true,
+ "wait_for_upload_port": true,
+ "require_upload_port": true,
+ "speed": 460800
+ },
+ "url": "https://www.adafruit.com/product/5778",
+ "vendor": "Adafruit"
+}
diff --git a/boards/lilygo-t7-s3.json b/boards/lilygo-t7-s3.json
new file mode 100644
index 0000000000..4bf071fc7e
--- /dev/null
+++ b/boards/lilygo-t7-s3.json
@@ -0,0 +1,47 @@
+{
+ "build": {
+ "arduino":{
+ "ldscript": "esp32s3_out.ld",
+ "memory_type": "qio_opi",
+ "partitions": "default_16MB.csv"
+ },
+ "core": "esp32",
+ "extra_flags": [
+ "-DARDUINO_TTGO_T7_S3",
+ "-DBOARD_HAS_PSRAM",
+ "-DARDUINO_USB_MODE=1"
+ ],
+ "f_cpu": "240000000L",
+ "f_flash": "80000000L",
+ "flash_mode": "qio",
+ "hwids": [
+ [
+ "0X303A",
+ "0x1001"
+ ]
+ ],
+ "mcu": "esp32s3",
+ "variant": "esp32s3"
+ },
+ "connectivity": [
+ "wifi",
+ "bluetooth"
+ ],
+ "debug": {
+ "openocd_target": "esp32s3.cfg"
+ },
+ "frameworks": [
+ "arduino",
+ "espidf"
+ ],
+ "name": "LILYGO T3-S3",
+ "upload": {
+ "flash_size": "16MB",
+ "maximum_ram_size": 327680,
+ "maximum_size": 16777216,
+ "require_upload_port": true,
+ "speed": 921600
+ },
+ "url": "https://www.aliexpress.us/item/3256804591247074.html",
+ "vendor": "LILYGO"
+}
\ No newline at end of file
diff --git a/boards/lolin_s3_mini.json b/boards/lolin_s3_mini.json
new file mode 100644
index 0000000000..7f55f0bde2
--- /dev/null
+++ b/boards/lolin_s3_mini.json
@@ -0,0 +1,47 @@
+{
+ "build": {
+ "arduino": {
+ "ldscript": "esp32s3_out.ld",
+ "memory_type": "qio_qspi"
+ },
+ "core": "esp32",
+ "extra_flags": [
+ "-DBOARD_HAS_PSRAM",
+ "-DARDUINO_LOLIN_S3_MINI",
+ "-DARDUINO_USB_MODE=1"
+ ],
+ "f_cpu": "240000000L",
+ "f_flash": "80000000L",
+ "flash_mode": "qio",
+ "hwids": [
+ [
+ "0x303A",
+ "0x8167"
+ ]
+ ],
+ "mcu": "esp32s3",
+ "variant": "lolin_s3_mini"
+ },
+ "connectivity": [
+ "bluetooth",
+ "wifi"
+ ],
+ "debug": {
+ "openocd_target": "esp32s3.cfg"
+ },
+ "frameworks": [
+ "arduino",
+ "espidf"
+ ],
+ "name": "WEMOS LOLIN S3 Mini",
+ "upload": {
+ "flash_size": "4MB",
+ "maximum_ram_size": 327680,
+ "maximum_size": 4194304,
+ "require_upload_port": true,
+ "speed": 460800
+ },
+ "url": "https://www.wemos.cc/en/latest/s3/index.html",
+ "vendor": "WEMOS"
+}
+
\ No newline at end of file
diff --git a/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp b/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp
new file mode 100644
index 0000000000..68cb9010ec
--- /dev/null
+++ b/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp
@@ -0,0 +1,504 @@
+/* esp8266_waveform imported from platform source code
+ Modified for WLED to work around a fault in the NMI handling,
+ which can result in the system locking up and hard WDT crashes.
+
+ Imported from https://github.com/esp8266/Arduino/blob/7e0d20e2b9034994f573a236364e0aef17fd66de/cores/esp8266/core_esp8266_waveform_phase.cpp
+*/
+
+
+/*
+ esp8266_waveform - General purpose waveform generation and control,
+ supporting outputs on all pins in parallel.
+
+ Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
+ Copyright (c) 2020 Dirk O. Kaar.
+
+ The core idea is to have a programmable waveform generator with a unique
+ high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
+ set to 1-shot mode and is always loaded with the time until the next edge
+ of any live waveforms.
+
+ Up to one waveform generator per pin supported.
+
+ Each waveform generator is synchronized to the ESP clock cycle counter, not the
+ timer. This allows for removing interrupt jitter and delay as the counter
+ always increments once per 80MHz clock. Changes to a waveform are
+ contiguous and only take effect on the next waveform transition,
+ allowing for smooth transitions.
+
+ This replaces older tone(), analogWrite(), and the Servo classes.
+
+ Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount()
+ clock cycle time, or an interval measured in clock cycles, but not TIMER1
+ cycles (which may be 2 CPU clock cycles @ 160MHz).
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#include "core_esp8266_waveform.h"
+#include
+#include "debug.h"
+#include "ets_sys.h"
+#include
+
+
+// ----- @willmmiles begin patch -----
+// Linker magic
+extern "C" void usePWMFixedNMI(void) {};
+
+// NMI crash workaround
+// Sometimes the NMI fails to return, stalling the CPU. When this happens,
+// the next NMI gets a return address /inside the NMI handler function/.
+// We work around this by caching the last NMI return address, and restoring
+// the epc3 and eps3 registers to the previous values if the observed epc3
+// happens to be pointing to the _NMILevelVector function.
+extern "C" void _NMILevelVector();
+extern "C" void _UserExceptionVector_1(); // the next function after _NMILevelVector
+static inline IRAM_ATTR void nmiCrashWorkaround() {
+ static uintptr_t epc3_backup, eps3_backup;
+
+ uintptr_t epc3, eps3;
+ __asm__ __volatile__("rsr %0,epc3; rsr %1,eps3":"=a"(epc3),"=a" (eps3));
+ if ((epc3 < (uintptr_t) &_NMILevelVector) || (epc3 >= (uintptr_t) &_UserExceptionVector_1)) {
+ // Address is good; save backup
+ epc3_backup = epc3;
+ eps3_backup = eps3;
+ } else {
+ // Address is inside the NMI handler -- restore from backup
+ __asm__ __volatile__("wsr %0,epc3; wsr %1,eps3"::"a"(epc3_backup),"a"(eps3_backup));
+ }
+}
+// ----- @willmmiles end patch -----
+
+
+// No-op calls to override the PWM implementation
+extern "C" void _setPWMFreq_weak(uint32_t freq) { (void) freq; }
+extern "C" IRAM_ATTR bool _stopPWM_weak(int pin) { (void) pin; return false; }
+extern "C" bool _setPWM_weak(int pin, uint32_t val, uint32_t range) { (void) pin; (void) val; (void) range; return false; }
+
+
+// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
+constexpr bool ISCPUFREQ160MHZ = clockCyclesPerMicrosecond() == 160;
+// Maximum delay between IRQs, Timer1, <= 2^23 / 80MHz
+constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000);
+// Maximum servicing time for any single IRQ
+constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18);
+// The latency between in-ISR rearming of the timer and the earliest firing
+constexpr int32_t IRQLATENCYCCYS = microsecondsToClockCycles(2);
+// The SDK and hardware take some time to actually get to our NMI code
+constexpr int32_t DELTAIRQCCYS = ISCPUFREQ160MHZ ?
+ microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2);
+
+// for INFINITE, the NMI proceeds on the waveform without expiry deadline.
+// for EXPIRES, the NMI expires the waveform automatically on the expiry ccy.
+// for UPDATEEXPIRY, the NMI recomputes the exact expiry ccy and transitions to EXPIRES.
+// for UPDATEPHASE, the NMI recomputes the target timings
+// for INIT, the NMI initializes nextPeriodCcy, and if expiryCcy != 0 includes UPDATEEXPIRY.
+enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, UPDATEPHASE = 3, INIT = 4};
+
+// Waveform generator can create tones, PWM, and servos
+typedef struct {
+ uint32_t nextPeriodCcy; // ESP clock cycle when a period begins.
+ uint32_t endDutyCcy; // ESP clock cycle when going from duty to off
+ int32_t dutyCcys; // Set next off cycle at low->high to maintain phase
+ int32_t adjDutyCcys; // Temporary correction for next period
+ int32_t periodCcys; // Set next phase cycle at low->high to maintain phase
+ uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count
+ WaveformMode mode;
+ bool autoPwm; // perform PWM duty to idle cycle ratio correction under high load at the expense of precise timings
+} Waveform;
+
+namespace {
+
+ static struct {
+ Waveform pins[17]; // State of all possible pins
+ uint32_t states = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
+ uint32_t enabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
+
+ // Enable lock-free by only allowing updates to waveform.states and waveform.enabled from IRQ service routine
+ int32_t toSetBits = 0; // Message to the NMI handler to start/modify exactly one waveform
+ int32_t toDisableBits = 0; // Message to the NMI handler to disable exactly one pin from waveform generation
+
+ // toSetBits temporaries
+ // cheaper than packing them in every Waveform, since we permit only one use at a time
+ uint32_t phaseCcy; // positive phase offset ccy count
+ int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin
+
+ uint32_t(*timer1CB)() = nullptr;
+
+ bool timer1Running = false;
+
+ uint32_t nextEventCcy;
+ } waveform;
+
+}
+
+// Interrupt on/off control
+static IRAM_ATTR void timer1Interrupt();
+
+// Non-speed critical bits
+#pragma GCC optimize ("Os")
+
+static void initTimer() {
+ timer1_disable();
+ ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
+ ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
+ timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
+ waveform.timer1Running = true;
+ timer1_write(IRQLATENCYCCYS); // Cause an interrupt post-haste
+}
+
+static void IRAM_ATTR deinitTimer() {
+ ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
+ timer1_disable();
+ timer1_isr_init();
+ waveform.timer1Running = false;
+}
+
+extern "C" {
+
+// Set a callback. Pass in NULL to stop it
+void setTimer1Callback_weak(uint32_t (*fn)()) {
+ waveform.timer1CB = fn;
+ std::atomic_thread_fence(std::memory_order_acq_rel);
+ if (!waveform.timer1Running && fn) {
+ initTimer();
+ } else if (waveform.timer1Running && !fn && !waveform.enabled) {
+ deinitTimer();
+ }
+}
+
+// Start up a waveform on a pin, or change the current one. Will change to the new
+// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
+// first, then it will immediately begin.
+int startWaveformClockCycles_weak(uint8_t pin, uint32_t highCcys, uint32_t lowCcys,
+ uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys, bool autoPwm) {
+ uint32_t periodCcys = highCcys + lowCcys;
+ if (periodCcys < MAXIRQTICKSCCYS) {
+ if (!highCcys) {
+ periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys;
+ }
+ else if (!lowCcys) {
+ highCcys = periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys;
+ }
+ }
+ // sanity checks, including mixed signed/unsigned arithmetic safety
+ if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) ||
+ static_cast(periodCcys) <= 0 ||
+ static_cast(highCcys) < 0 || static_cast(lowCcys) < 0) {
+ return false;
+ }
+ Waveform& wave = waveform.pins[pin];
+ wave.dutyCcys = highCcys;
+ wave.adjDutyCcys = 0;
+ wave.periodCcys = periodCcys;
+ wave.autoPwm = autoPwm;
+ waveform.alignPhase = (alignPhase < 0) ? -1 : alignPhase;
+ waveform.phaseCcy = phaseOffsetCcys;
+
+ std::atomic_thread_fence(std::memory_order_acquire);
+ const uint32_t pinBit = 1UL << pin;
+ if (!(waveform.enabled & pinBit)) {
+ // wave.nextPeriodCcy and wave.endDutyCcy are initialized by the ISR
+ wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count
+ wave.mode = WaveformMode::INIT;
+ if (!wave.dutyCcys) {
+ // If initially at zero duty cycle, force GPIO off
+ if (pin == 16) {
+ GP16O = 0;
+ }
+ else {
+ GPOC = pinBit;
+ }
+ }
+ std::atomic_thread_fence(std::memory_order_release);
+ waveform.toSetBits = 1UL << pin;
+ std::atomic_thread_fence(std::memory_order_release);
+ if (!waveform.timer1Running) {
+ initTimer();
+ }
+ else if (T1V > IRQLATENCYCCYS) {
+ // Must not interfere if Timer is due shortly
+ timer1_write(IRQLATENCYCCYS);
+ }
+ }
+ else {
+ wave.mode = WaveformMode::INFINITE; // turn off possible expiry to make update atomic from NMI
+ std::atomic_thread_fence(std::memory_order_release);
+ if (runTimeCcys) {
+ wave.expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count
+ wave.mode = WaveformMode::UPDATEEXPIRY;
+ std::atomic_thread_fence(std::memory_order_release);
+ waveform.toSetBits = 1UL << pin;
+ } else if (alignPhase >= 0) {
+ // @willmmiles new feature
+ wave.mode = WaveformMode::UPDATEPHASE; // recalculate start
+ std::atomic_thread_fence(std::memory_order_release);
+ waveform.toSetBits = 1UL << pin;
+ }
+ }
+ std::atomic_thread_fence(std::memory_order_acq_rel);
+ while (waveform.toSetBits) {
+ esp_yield(); // Wait for waveform to update
+ std::atomic_thread_fence(std::memory_order_acquire);
+ }
+ return true;
+}
+
+// Stops a waveform on a pin
+IRAM_ATTR int stopWaveform_weak(uint8_t pin) {
+ // Can't possibly need to stop anything if there is no timer active
+ if (!waveform.timer1Running) {
+ return false;
+ }
+ // If user sends in a pin >16 but <32, this will always point to a 0 bit
+ // If they send >=32, then the shift will result in 0 and it will also return false
+ std::atomic_thread_fence(std::memory_order_acquire);
+ const uint32_t pinBit = 1UL << pin;
+ if (waveform.enabled & pinBit) {
+ waveform.toDisableBits = 1UL << pin;
+ std::atomic_thread_fence(std::memory_order_release);
+ // Must not interfere if Timer is due shortly
+ if (T1V > IRQLATENCYCCYS) {
+ timer1_write(IRQLATENCYCCYS);
+ }
+ while (waveform.toDisableBits) {
+ /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
+ std::atomic_thread_fence(std::memory_order_acquire);
+ }
+ }
+ if (!waveform.enabled && !waveform.timer1CB) {
+ deinitTimer();
+ }
+ return true;
+}
+
+};
+
+// Speed critical bits
+#pragma GCC optimize ("O2")
+
+// For dynamic CPU clock frequency switch in loop the scaling logic would have to be adapted.
+// Using constexpr makes sure that the CPU clock frequency is compile-time fixed.
+static inline IRAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool isCPU2X) {
+ if (ISCPUFREQ160MHZ) {
+ return isCPU2X ? ccys : (ccys >> 1);
+ }
+ else {
+ return isCPU2X ? (ccys << 1) : ccys;
+ }
+}
+
+static IRAM_ATTR void timer1Interrupt() {
+ const uint32_t isrStartCcy = ESP.getCycleCount();
+ //int32_t clockDrift = isrStartCcy - waveform.nextEventCcy;
+
+ // ----- @willmmiles begin patch -----
+ nmiCrashWorkaround();
+ // ----- @willmmiles end patch -----
+
+ const bool isCPU2X = CPU2X & 1;
+ if ((waveform.toSetBits && !(waveform.enabled & waveform.toSetBits)) || waveform.toDisableBits) {
+ // Handle enable/disable requests from main app.
+ waveform.enabled = (waveform.enabled & ~waveform.toDisableBits) | waveform.toSetBits; // Set the requested waveforms on/off
+ // Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
+ waveform.toDisableBits = 0;
+ }
+
+ if (waveform.toSetBits) {
+ const int toSetPin = __builtin_ffs(waveform.toSetBits) - 1;
+ Waveform& wave = waveform.pins[toSetPin];
+ switch (wave.mode) {
+ case WaveformMode::INIT:
+ waveform.states &= ~waveform.toSetBits; // Clear the state of any just started
+ if (waveform.alignPhase >= 0 && waveform.enabled & (1UL << waveform.alignPhase)) {
+ wave.nextPeriodCcy = waveform.pins[waveform.alignPhase].nextPeriodCcy + scaleCcys(waveform.phaseCcy, isCPU2X);
+ }
+ else {
+ wave.nextPeriodCcy = waveform.nextEventCcy;
+ }
+ if (!wave.expiryCcy) {
+ wave.mode = WaveformMode::INFINITE;
+ break;
+ }
+ // fall through
+ case WaveformMode::UPDATEEXPIRY:
+ // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count
+ wave.expiryCcy = wave.nextPeriodCcy + scaleCcys(wave.expiryCcy, isCPU2X);
+ wave.mode = WaveformMode::EXPIRES;
+ break;
+ // @willmmiles new feature
+ case WaveformMode::UPDATEPHASE:
+ // in WaveformMode::UPDATEPHASE, we recalculate the targets
+ if ((waveform.alignPhase >= 0) && (waveform.enabled & (1UL << waveform.alignPhase))) {
+ // Compute phase shift to realign with target
+ auto const newPeriodCcy = waveform.pins[waveform.alignPhase].nextPeriodCcy + scaleCcys(waveform.phaseCcy, isCPU2X);
+ auto const period = scaleCcys(wave.periodCcys, isCPU2X);
+ auto shift = ((static_cast (newPeriodCcy - wave.nextPeriodCcy) + period/2) % period) - (period/2);
+ wave.nextPeriodCcy += static_cast(shift);
+ if (static_cast(wave.endDutyCcy - wave.nextPeriodCcy) > 0) {
+ wave.endDutyCcy = wave.nextPeriodCcy;
+ }
+ }
+ default:
+ break;
+ }
+ waveform.toSetBits = 0;
+ }
+
+ // Exit the loop if the next event, if any, is sufficiently distant.
+ const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS;
+ uint32_t busyPins = waveform.enabled;
+ waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS;
+
+ uint32_t now = ESP.getCycleCount();
+ uint32_t isrNextEventCcy = now;
+ while (busyPins) {
+ if (static_cast(isrNextEventCcy - now) > IRQLATENCYCCYS) {
+ waveform.nextEventCcy = isrNextEventCcy;
+ break;
+ }
+ isrNextEventCcy = waveform.nextEventCcy;
+ uint32_t loopPins = busyPins;
+ while (loopPins) {
+ const int pin = __builtin_ffsl(loopPins) - 1;
+ const uint32_t pinBit = 1UL << pin;
+ loopPins ^= pinBit;
+
+ Waveform& wave = waveform.pins[pin];
+
+/* @willmmiles - wtf? We don't want to accumulate drift
+ if (clockDrift) {
+ wave.endDutyCcy += clockDrift;
+ wave.nextPeriodCcy += clockDrift;
+ wave.expiryCcy += clockDrift;
+ }
+*/
+
+ uint32_t waveNextEventCcy = (waveform.states & pinBit) ? wave.endDutyCcy : wave.nextPeriodCcy;
+ if (WaveformMode::EXPIRES == wave.mode &&
+ static_cast(waveNextEventCcy - wave.expiryCcy) >= 0 &&
+ static_cast(now - wave.expiryCcy) >= 0) {
+ // Disable any waveforms that are done
+ waveform.enabled ^= pinBit;
+ busyPins ^= pinBit;
+ }
+ else {
+ const int32_t overshootCcys = now - waveNextEventCcy;
+ if (overshootCcys >= 0) {
+ const int32_t periodCcys = scaleCcys(wave.periodCcys, isCPU2X);
+ if (waveform.states & pinBit) {
+ // active configuration and forward are 100% duty
+ if (wave.periodCcys == wave.dutyCcys) {
+ wave.nextPeriodCcy += periodCcys;
+ wave.endDutyCcy = wave.nextPeriodCcy;
+ }
+ else {
+ if (wave.autoPwm) {
+ wave.adjDutyCcys += overshootCcys;
+ }
+ waveform.states ^= pinBit;
+ if (16 == pin) {
+ GP16O = 0;
+ }
+ else {
+ GPOC = pinBit;
+ }
+ }
+ waveNextEventCcy = wave.nextPeriodCcy;
+ }
+ else {
+ wave.nextPeriodCcy += periodCcys;
+ if (!wave.dutyCcys) {
+ wave.endDutyCcy = wave.nextPeriodCcy;
+ }
+ else {
+ int32_t dutyCcys = scaleCcys(wave.dutyCcys, isCPU2X);
+ if (dutyCcys <= wave.adjDutyCcys) {
+ dutyCcys >>= 1;
+ wave.adjDutyCcys -= dutyCcys;
+ }
+ else if (wave.adjDutyCcys) {
+ dutyCcys -= wave.adjDutyCcys;
+ wave.adjDutyCcys = 0;
+ }
+ wave.endDutyCcy = now + dutyCcys;
+ if (static_cast(wave.endDutyCcy - wave.nextPeriodCcy) > 0) {
+ wave.endDutyCcy = wave.nextPeriodCcy;
+ }
+ waveform.states |= pinBit;
+ if (16 == pin) {
+ GP16O = 1;
+ }
+ else {
+ GPOS = pinBit;
+ }
+ }
+ waveNextEventCcy = wave.endDutyCcy;
+ }
+
+ if (WaveformMode::EXPIRES == wave.mode && static_cast(waveNextEventCcy - wave.expiryCcy) > 0) {
+ waveNextEventCcy = wave.expiryCcy;
+ }
+ }
+
+ if (static_cast(waveNextEventCcy - isrTimeoutCcy) >= 0) {
+ busyPins ^= pinBit;
+ if (static_cast(waveform.nextEventCcy - waveNextEventCcy) > 0) {
+ waveform.nextEventCcy = waveNextEventCcy;
+ }
+ }
+ else if (static_cast(isrNextEventCcy - waveNextEventCcy) > 0) {
+ isrNextEventCcy = waveNextEventCcy;
+ }
+ }
+ now = ESP.getCycleCount();
+ }
+ //clockDrift = 0;
+ }
+
+ int32_t callbackCcys = 0;
+ if (waveform.timer1CB) {
+ callbackCcys = scaleCcys(waveform.timer1CB(), isCPU2X);
+ }
+ now = ESP.getCycleCount();
+ int32_t nextEventCcys = waveform.nextEventCcy - now;
+ // Account for unknown duration of timer1CB().
+ if (waveform.timer1CB && nextEventCcys > callbackCcys) {
+ waveform.nextEventCcy = now + callbackCcys;
+ nextEventCcys = callbackCcys;
+ }
+
+ // Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
+ int32_t deltaIrqCcys = DELTAIRQCCYS;
+ int32_t irqLatencyCcys = IRQLATENCYCCYS;
+ if (isCPU2X) {
+ nextEventCcys >>= 1;
+ deltaIrqCcys >>= 1;
+ irqLatencyCcys >>= 1;
+ }
+
+ // Firing timer too soon, the NMI occurs before ISR has returned.
+ if (nextEventCcys < irqLatencyCcys + deltaIrqCcys) {
+ waveform.nextEventCcy = now + IRQLATENCYCCYS + DELTAIRQCCYS;
+ nextEventCcys = irqLatencyCcys;
+ }
+ else {
+ nextEventCcys -= deltaIrqCcys;
+ }
+
+ // Register access is fast and edge IRQ was configured before.
+ T1L = nextEventCcys;
+}
diff --git a/lib/ESP8266PWM/src/core_esp8266_waveform_pwm.cpp b/lib/ESP8266PWM/src/core_esp8266_waveform_pwm.cpp
deleted file mode 100644
index 78c7160d90..0000000000
--- a/lib/ESP8266PWM/src/core_esp8266_waveform_pwm.cpp
+++ /dev/null
@@ -1,717 +0,0 @@
-/* esp8266_waveform imported from platform source code
- Modified for WLED to work around a fault in the NMI handling,
- which can result in the system locking up and hard WDT crashes.
-
- Imported from https://github.com/esp8266/Arduino/blob/7e0d20e2b9034994f573a236364e0aef17fd66de/cores/esp8266/core_esp8266_waveform_pwm.cpp
-*/
-
-/*
- esp8266_waveform - General purpose waveform generation and control,
- supporting outputs on all pins in parallel.
-
- Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
-
- The core idea is to have a programmable waveform generator with a unique
- high and low period (defined in microseconds or CPU clock cycles). TIMER1
- is set to 1-shot mode and is always loaded with the time until the next
- edge of any live waveforms.
-
- Up to one waveform generator per pin supported.
-
- Each waveform generator is synchronized to the ESP clock cycle counter, not
- the timer. This allows for removing interrupt jitter and delay as the
- counter always increments once per 80MHz clock. Changes to a waveform are
- contiguous and only take effect on the next waveform transition,
- allowing for smooth transitions.
-
- This replaces older tone(), analogWrite(), and the Servo classes.
-
- Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
- clock cycle count, or an interval measured in CPU clock cycles, but not
- TIMER1 cycles (which may be 2 CPU clock cycles @ 160MHz).
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-*/
-
-
-#include
-#include
-#include "ets_sys.h"
-#include "core_esp8266_waveform.h"
-#include "user_interface.h"
-
-extern "C" {
-
-// Linker magic
-void usePWMFixedNMI() {};
-
-// Maximum delay between IRQs
-#define MAXIRQUS (10000)
-
-// Waveform generator can create tones, PWM, and servos
-typedef struct {
- uint32_t nextServiceCycle; // ESP cycle timer when a transition required
- uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop
- uint32_t timeHighCycles; // Actual running waveform period (adjusted using desiredCycles)
- uint32_t timeLowCycles; //
- uint32_t desiredHighCycles; // Ideal waveform period to drive the error signal
- uint32_t desiredLowCycles; //
- uint32_t lastEdge; // Cycle when this generator last changed
-} Waveform;
-
-class WVFState {
-public:
- Waveform waveform[17]; // State of all possible pins
- uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
- uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
-
- // Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine
- uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin
- uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation
-
- uint32_t waveformToChange = 0; // Mask of pin to change. One bit set in main app, cleared when effected in the NMI
- uint32_t waveformNewHigh = 0;
- uint32_t waveformNewLow = 0;
-
- uint32_t (*timer1CB)() = NULL;
-
- // Optimize the NMI inner loop by keeping track of the min and max GPIO that we
- // are generating. In the common case (1 PWM) these may be the same pin and
- // we can avoid looking at the other pins.
- uint16_t startPin = 0;
- uint16_t endPin = 0;
-};
-static WVFState wvfState;
-
-
-// Ensure everything is read/written to RAM
-#define MEMBARRIER() { __asm__ volatile("" ::: "memory"); }
-
-// Non-speed critical bits
-#pragma GCC optimize ("Os")
-
-// Interrupt on/off control
-static IRAM_ATTR void timer1Interrupt();
-static bool timerRunning = false;
-
-static __attribute__((noinline)) void initTimer() {
- if (!timerRunning) {
- timer1_disable();
- ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
- ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
- timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
- timerRunning = true;
- timer1_write(microsecondsToClockCycles(10));
- }
-}
-
-static IRAM_ATTR void forceTimerInterrupt() {
- if (T1L > microsecondsToClockCycles(10)) {
- T1L = microsecondsToClockCycles(10);
- }
-}
-
-// PWM implementation using special purpose state machine
-//
-// Keep an ordered list of pins with the delta in cycles between each
-// element, with a terminal entry making up the remainder of the PWM
-// period. With this method sum(all deltas) == PWM period clock cycles.
-//
-// At t=0 set all pins high and set the timeout for the 1st edge.
-// On interrupt, if we're at the last element reset to t=0 state
-// Otherwise, clear that pin down and set delay for next element
-// and so forth.
-
-constexpr int maxPWMs = 8;
-
-// PWM machine state
-typedef struct PWMState {
- uint32_t mask; // Bitmask of active pins
- uint32_t cnt; // How many entries
- uint32_t idx; // Where the state machine is along the list
- uint8_t pin[maxPWMs + 1];
- uint32_t delta[maxPWMs + 1];
- uint32_t nextServiceCycle; // Clock cycle for next step
- struct PWMState *pwmUpdate; // Set by main code, cleared by ISR
-} PWMState;
-
-static PWMState pwmState;
-static uint32_t _pwmFreq = 1000;
-static uint32_t _pwmPeriod = microsecondsToClockCycles(1000000UL) / _pwmFreq;
-
-
-// If there are no more scheduled activities, shut down Timer 1.
-// Otherwise, do nothing.
-static IRAM_ATTR void disableIdleTimer() {
- if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) {
- ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
- timer1_disable();
- timer1_isr_init();
- timerRunning = false;
- }
-}
-
-// Notify the NMI that a new PWM state is available through the mailbox.
-// Wait for mailbox to be emptied (either busy or delay() as needed)
-static IRAM_ATTR void _notifyPWM(PWMState *p, bool idle) {
- p->pwmUpdate = nullptr;
- pwmState.pwmUpdate = p;
- MEMBARRIER();
- forceTimerInterrupt();
- while (pwmState.pwmUpdate) {
- if (idle) {
- esp_yield();
- }
- MEMBARRIER();
- }
-}
-
-static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range);
-
-
-// Called when analogWriteFreq() changed to update the PWM total period
-//extern void _setPWMFreq_weak(uint32_t freq) __attribute__((weak));
-void _setPWMFreq_weak(uint32_t freq) {
- _pwmFreq = freq;
-
- // Convert frequency into clock cycles
- uint32_t cc = microsecondsToClockCycles(1000000UL) / freq;
-
- // Simple static adjustment to bring period closer to requested due to overhead
- // Empirically determined as a constant PWM delay and a function of the number of PWMs
-#if F_CPU == 80000000
- cc -= ((microsecondsToClockCycles(pwmState.cnt) * 13) >> 4) + 110;
-#else
- cc -= ((microsecondsToClockCycles(pwmState.cnt) * 10) >> 4) + 75;
-#endif
-
- if (cc == _pwmPeriod) {
- return; // No change
- }
-
- _pwmPeriod = cc;
-
- if (pwmState.cnt) {
- PWMState p; // The working copy since we can't edit the one in use
- p.mask = 0;
- p.cnt = 0;
- for (uint32_t i = 0; i < pwmState.cnt; i++) {
- auto pin = pwmState.pin[i];
- _addPWMtoList(p, pin, wvfState.waveform[pin].desiredHighCycles, wvfState.waveform[pin].desiredLowCycles);
- }
- // Update and wait for mailbox to be emptied
- initTimer();
- _notifyPWM(&p, true);
- disableIdleTimer();
- }
-}
-/*
-static void _setPWMFreq_bound(uint32_t freq) __attribute__((weakref("_setPWMFreq_weak")));
-void _setPWMFreq(uint32_t freq) {
- _setPWMFreq_bound(freq);
-}
-*/
-
-// Helper routine to remove an entry from the state machine
-// and clean up any marked-off entries
-static void _cleanAndRemovePWM(PWMState *p, int pin) {
- uint32_t leftover = 0;
- uint32_t in, out;
- for (in = 0, out = 0; in < p->cnt; in++) {
- if ((p->pin[in] != pin) && (p->mask & (1<pin[in]))) {
- p->pin[out] = p->pin[in];
- p->delta[out] = p->delta[in] + leftover;
- leftover = 0;
- out++;
- } else {
- leftover += p->delta[in];
- p->mask &= ~(1<pin[in]);
- }
- }
- p->cnt = out;
- // Final pin is never used: p->pin[out] = 0xff;
- p->delta[out] = p->delta[in] + leftover;
-}
-
-
-// Disable PWM on a specific pin (i.e. when a digitalWrite or analogWrite(0%/100%))
-//extern bool _stopPWM_weak(uint8_t pin) __attribute__((weak));
-IRAM_ATTR bool _stopPWM_weak(uint8_t pin) {
- if (!((1<= _pwmPeriod) {
- cc = _pwmPeriod - 1;
- }
-
- if (p.cnt == 0) {
- // Starting up from scratch, special case 1st element and PWM period
- p.pin[0] = pin;
- p.delta[0] = cc;
- // Final pin is never used: p.pin[1] = 0xff;
- p.delta[1] = _pwmPeriod - cc;
- } else {
- uint32_t ttl = 0;
- uint32_t i;
- // Skip along until we're at the spot to insert
- for (i=0; (i <= p.cnt) && (ttl + p.delta[i] < cc); i++) {
- ttl += p.delta[i];
- }
- // Shift everything out by one to make space for new edge
- for (int32_t j = p.cnt; j >= (int)i; j--) {
- p.pin[j + 1] = p.pin[j];
- p.delta[j + 1] = p.delta[j];
- }
- int off = cc - ttl; // The delta from the last edge to the one we're inserting
- p.pin[i] = pin;
- p.delta[i] = off; // Add the delta to this new pin
- p.delta[i + 1] -= off; // And subtract it from the follower to keep sum(deltas) constant
- }
- p.cnt++;
- p.mask |= 1<= maxPWMs) {
- return false; // No space left
- }
-
- // Sanity check for all-on/off
- uint32_t cc = (_pwmPeriod * val) / range;
- if ((cc == 0) || (cc >= _pwmPeriod)) {
- digitalWrite(pin, cc ? HIGH : LOW);
- return true;
- }
-
- _addPWMtoList(p, pin, val, range);
-
- // Set mailbox and wait for ISR to copy it over
- initTimer();
- _notifyPWM(&p, true);
- disableIdleTimer();
-
- // Potentially recalculate the PWM period if we've added another pin
- _setPWMFreq(_pwmFreq);
-
- return true;
-}
-/*
-static bool _setPWM_bound(int pin, uint32_t val, uint32_t range) __attribute__((weakref("_setPWM_weak")));
-bool _setPWM(int pin, uint32_t val, uint32_t range) {
- return _setPWM_bound(pin, val, range);
-}
-*/
-
-// Start up a waveform on a pin, or change the current one. Will change to the new
-// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
-// first, then it will immediately begin.
-//extern int startWaveformClockCycles_weak(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) __attribute__((weak));
-int startWaveformClockCycles_weak(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles,
- int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
- (void) alignPhase;
- (void) phaseOffsetUS;
- (void) autoPwm;
-
- if ((pin > 16) || isFlashInterfacePin(pin) || (timeHighCycles == 0)) {
- return false;
- }
- Waveform *wave = &wvfState.waveform[pin];
- wave->expiryCycle = runTimeCycles ? ESP.getCycleCount() + runTimeCycles : 0;
- if (runTimeCycles && !wave->expiryCycle) {
- wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so avoid setting it
- }
-
- _stopPWM(pin); // Make sure there's no PWM live here
-
- uint32_t mask = 1<timeHighCycles = timeHighCycles;
- wave->desiredHighCycles = timeHighCycles;
- wave->timeLowCycles = timeLowCycles;
- wave->desiredLowCycles = timeLowCycles;
- wave->lastEdge = 0;
- wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(1);
- wvfState.waveformToEnable |= mask;
- MEMBARRIER();
- initTimer();
- forceTimerInterrupt();
- while (wvfState.waveformToEnable) {
- esp_yield(); // Wait for waveform to update
- MEMBARRIER();
- }
- }
-
- return true;
-}
-/*
-static int startWaveformClockCycles_bound(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) __attribute__((weakref("startWaveformClockCycles_weak")));
-int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
- return startWaveformClockCycles_bound(pin, timeHighCycles, timeLowCycles, runTimeCycles, alignPhase, phaseOffsetUS, autoPwm);
-}
-
-
-// This version falls-thru to the proper startWaveformClockCycles call and is invariant across waveform generators
-int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS,
- int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
- return startWaveformClockCycles_bound(pin,
- microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS),
- microsecondsToClockCycles(runTimeUS), alignPhase, microsecondsToClockCycles(phaseOffsetUS), autoPwm);
-}
-*/
-
-// Set a callback. Pass in NULL to stop it
-//extern void setTimer1Callback_weak(uint32_t (*fn)()) __attribute__((weak));
-void setTimer1Callback_weak(uint32_t (*fn)()) {
- wvfState.timer1CB = fn;
- if (fn) {
- initTimer();
- forceTimerInterrupt();
- }
- disableIdleTimer();
-}
-/*
-static void setTimer1Callback_bound(uint32_t (*fn)()) __attribute__((weakref("setTimer1Callback_weak")));
-void setTimer1Callback(uint32_t (*fn)()) {
- setTimer1Callback_bound(fn);
-}
-*/
-
-// Stops a waveform on a pin
-//extern int stopWaveform_weak(uint8_t pin) __attribute__((weak));
-IRAM_ATTR int stopWaveform_weak(uint8_t pin) {
- // Can't possibly need to stop anything if there is no timer active
- if (!timerRunning) {
- return false;
- }
- // If user sends in a pin >16 but <32, this will always point to a 0 bit
- // If they send >=32, then the shift will result in 0 and it will also return false
- uint32_t mask = 1<= (uintptr_t) &_UserExceptionVector_1)) {
- // Address is good; save backup
- epc3_backup = epc3;
- eps3_backup = eps3;
- } else {
- // Address is inside the NMI handler -- restore from backup
- __asm__ __volatile__("wsr %0,epc3; wsr %1,eps3"::"a"(epc3_backup),"a"(eps3_backup));
- }
-}
-// ----- @willmmiles end patch -----
-
-
-// The SDK and hardware take some time to actually get to our NMI code, so
-// decrement the next IRQ's timer value by a bit so we can actually catch the
-// real CPU cycle counter we want for the waveforms.
-
-// The SDK also sometimes is running at a different speed the the Arduino core
-// so the ESP cycle counter is actually running at a variable speed.
-// adjust(x) takes care of adjusting a delta clock cycle amount accordingly.
-#if F_CPU == 80000000
- #define DELTAIRQ (microsecondsToClockCycles(9)/4)
- #define adjust(x) ((x) << (turbo ? 1 : 0))
-#else
- #define DELTAIRQ (microsecondsToClockCycles(9)/8)
- #define adjust(x) ((x) >> 0)
-#endif
-
-// When the time to the next edge is greater than this, RTI and set another IRQ to minimize CPU usage
-#define MINIRQTIME microsecondsToClockCycles(6)
-
-static IRAM_ATTR void timer1Interrupt() {
- // ----- @willmmiles begin patch -----
- nmiCrashWorkaround();
- // ----- @willmmiles end patch -----
-
- // Flag if the core is at 160 MHz, for use by adjust()
- bool turbo = (*(uint32_t*)0x3FF00014) & 1 ? true : false;
-
- uint32_t nextEventCycle = GetCycleCountIRQ() + microsecondsToClockCycles(MAXIRQUS);
- uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14);
-
- if (wvfState.waveformToEnable || wvfState.waveformToDisable) {
- // Handle enable/disable requests from main app
- wvfState.waveformEnabled = (wvfState.waveformEnabled & ~wvfState.waveformToDisable) | wvfState.waveformToEnable; // Set the requested waveforms on/off
- wvfState.waveformState &= ~wvfState.waveformToEnable; // And clear the state of any just started
- wvfState.waveformToEnable = 0;
- wvfState.waveformToDisable = 0;
- // No mem barrier. Globals must be written to RAM on ISR exit.
- // Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
- wvfState.startPin = __builtin_ffs(wvfState.waveformEnabled) - 1;
- // Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one)
- wvfState.endPin = 32 - __builtin_clz(wvfState.waveformEnabled);
- } else if (!pwmState.cnt && pwmState.pwmUpdate) {
- // Start up the PWM generator by copying from the mailbox
- pwmState.cnt = 1;
- pwmState.idx = 1; // Ensure copy this cycle, cause it to start at t=0
- pwmState.nextServiceCycle = GetCycleCountIRQ(); // Do it this loop!
- // No need for mem barrier here. Global must be written by IRQ exit
- }
-
- bool done = false;
- if (wvfState.waveformEnabled || pwmState.cnt) {
- do {
- nextEventCycle = GetCycleCountIRQ() + microsecondsToClockCycles(MAXIRQUS);
-
- // PWM state machine implementation
- if (pwmState.cnt) {
- int32_t cyclesToGo;
- do {
- cyclesToGo = pwmState.nextServiceCycle - GetCycleCountIRQ();
- if (cyclesToGo < 0) {
- if (pwmState.idx == pwmState.cnt) { // Start of pulses, possibly copy new
- if (pwmState.pwmUpdate) {
- // Do the memory copy from temp to global and clear mailbox
- pwmState = *(PWMState*)pwmState.pwmUpdate;
- }
- GPOS = pwmState.mask; // Set all active pins high
- if (pwmState.mask & (1<<16)) {
- GP16O = 1;
- }
- pwmState.idx = 0;
- } else {
- do {
- // Drop the pin at this edge
- if (pwmState.mask & (1<expiryCycle) {
- int32_t expiryToGo = wave->expiryCycle - now;
- if (expiryToGo < 0) {
- // Done, remove!
- if (i == 16) {
- GP16O = 0;
- }
- GPOC = mask;
- wvfState.waveformEnabled &= ~mask;
- continue;
- }
- }
-
- // Check for toggles
- int32_t cyclesToGo = wave->nextServiceCycle - now;
- if (cyclesToGo < 0) {
- uint32_t nextEdgeCycles;
- uint32_t desired = 0;
- uint32_t *timeToUpdate;
- wvfState.waveformState ^= mask;
- if (wvfState.waveformState & mask) {
- if (i == 16) {
- GP16O = 1;
- }
- GPOS = mask;
-
- if (wvfState.waveformToChange & mask) {
- // Copy over next full-cycle timings
- wave->timeHighCycles = wvfState.waveformNewHigh;
- wave->desiredHighCycles = wvfState.waveformNewHigh;
- wave->timeLowCycles = wvfState.waveformNewLow;
- wave->desiredLowCycles = wvfState.waveformNewLow;
- wave->lastEdge = 0;
- wvfState.waveformToChange = 0;
- }
- if (wave->lastEdge) {
- desired = wave->desiredLowCycles;
- timeToUpdate = &wave->timeLowCycles;
- }
- nextEdgeCycles = wave->timeHighCycles;
- } else {
- if (i == 16) {
- GP16O = 0;
- }
- GPOC = mask;
- desired = wave->desiredHighCycles;
- timeToUpdate = &wave->timeHighCycles;
- nextEdgeCycles = wave->timeLowCycles;
- }
- if (desired) {
- desired = adjust(desired);
- int32_t err = desired - (now - wave->lastEdge);
- if (abs(err) < desired) { // If we've lost > the entire phase, ignore this error signal
- err /= 2;
- *timeToUpdate += err;
- }
- }
- nextEdgeCycles = adjust(nextEdgeCycles);
- wave->nextServiceCycle = now + nextEdgeCycles;
- wave->lastEdge = now;
- }
- nextEventCycle = earliest(nextEventCycle, wave->nextServiceCycle);
- }
-
- // Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur
- uint32_t now = GetCycleCountIRQ();
- int32_t cycleDeltaNextEvent = nextEventCycle - now;
- int32_t cyclesLeftTimeout = timeoutCycle - now;
- done = (cycleDeltaNextEvent > MINIRQTIME) || (cyclesLeftTimeout < 0);
- } while (!done);
- } // if (wvfState.waveformEnabled)
-
- if (wvfState.timer1CB) {
- nextEventCycle = earliest(nextEventCycle, GetCycleCountIRQ() + wvfState.timer1CB());
- }
-
- int32_t nextEventCycles = nextEventCycle - GetCycleCountIRQ();
-
- if (nextEventCycles < MINIRQTIME) {
- nextEventCycles = MINIRQTIME;
- }
- nextEventCycles -= DELTAIRQ;
-
- // Do it here instead of global function to save time and because we know it's edge-IRQ
- T1L = nextEventCycles >> (turbo ? 1 : 0);
-}
-
-};
diff --git a/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h b/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h
new file mode 100644
index 0000000000..02e066f741
--- /dev/null
+++ b/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h
@@ -0,0 +1,469 @@
+/*-------------------------------------------------------------------------
+NeoPixel driver for ESP32 RMTs using High-priority Interrupt
+
+(NB. This cannot be mixed with the non-HI driver.)
+
+Written by Will M. Miles.
+
+I invest time and resources providing this open source code,
+please support me by donating (see https://github.com/Makuna/NeoPixelBus)
+
+-------------------------------------------------------------------------
+This file is part of the Makuna/NeoPixelBus library.
+
+NeoPixelBus is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as
+published by the Free Software Foundation, either version 3 of
+the License, or (at your option) any later version.
+
+NeoPixelBus is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with NeoPixel. If not, see
+.
+-------------------------------------------------------------------------*/
+
+#pragma once
+
+#if defined(ARDUINO_ARCH_ESP32)
+
+// Use the NeoEspRmtSpeed types from the driver-based implementation
+#include
+
+
+namespace NeoEsp32RmtHiMethodDriver {
+ // Install the driver for a specific channel, specifying timing properties
+ esp_err_t Install(rmt_channel_t channel, uint32_t rmtBit0, uint32_t rmtBit1, uint32_t resetDuration);
+
+ // Remove the driver on a specific channel
+ esp_err_t Uninstall(rmt_channel_t channel);
+
+ // Write a buffer of data to a specific channel.
+ // Buffer reference is held until write completes.
+ esp_err_t Write(rmt_channel_t channel, const uint8_t *src, size_t src_size);
+
+ // Wait until transaction is complete.
+ esp_err_t WaitForTxDone(rmt_channel_t channel, TickType_t wait_time);
+};
+
+template class NeoEsp32RmtHIMethodBase
+{
+public:
+ typedef NeoNoSettings SettingsObject;
+
+ NeoEsp32RmtHIMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize) :
+ _sizeData(pixelCount * elementSize + settingsSize),
+ _pin(pin)
+ {
+ construct();
+ }
+
+ NeoEsp32RmtHIMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize, NeoBusChannel channel) :
+ _sizeData(pixelCount* elementSize + settingsSize),
+ _pin(pin),
+ _channel(channel)
+ {
+ construct();
+ }
+
+ ~NeoEsp32RmtHIMethodBase()
+ {
+ // wait until the last send finishes before destructing everything
+ // arbitrary time out of 10 seconds
+ ESP_ERROR_CHECK_WITHOUT_ABORT(NeoEsp32RmtHiMethodDriver::WaitForTxDone(_channel.RmtChannelNumber, 10000 / portTICK_PERIOD_MS));
+
+ ESP_ERROR_CHECK(NeoEsp32RmtHiMethodDriver::Uninstall(_channel.RmtChannelNumber));
+
+ gpio_matrix_out(_pin, SIG_GPIO_OUT_IDX, false, false);
+ pinMode(_pin, INPUT);
+
+ free(_dataEditing);
+ free(_dataSending);
+ }
+
+ bool IsReadyToUpdate() const
+ {
+ return (ESP_OK == ESP_ERROR_CHECK_WITHOUT_ABORT_SILENT_TIMEOUT(NeoEsp32RmtHiMethodDriver::WaitForTxDone(_channel.RmtChannelNumber, 0)));
+ }
+
+ void Initialize()
+ {
+ rmt_config_t config = {};
+
+ config.rmt_mode = RMT_MODE_TX;
+ config.channel = _channel.RmtChannelNumber;
+ config.gpio_num = static_cast(_pin);
+ config.mem_block_num = 1;
+ config.tx_config.loop_en = false;
+
+ config.tx_config.idle_output_en = true;
+ config.tx_config.idle_level = T_SPEED::IdleLevel;
+
+ config.tx_config.carrier_en = false;
+ config.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
+
+ config.clk_div = T_SPEED::RmtClockDivider;
+
+ ESP_ERROR_CHECK(rmt_config(&config)); // Uses ESP library
+ ESP_ERROR_CHECK(NeoEsp32RmtHiMethodDriver::Install(_channel.RmtChannelNumber, T_SPEED::RmtBit0, T_SPEED::RmtBit1, T_SPEED::RmtDurationReset));
+ }
+
+ void Update(bool maintainBufferConsistency)
+ {
+ // wait for not actively sending data
+ // this will time out at 10 seconds, an arbitrarily long period of time
+ // and do nothing if this happens
+ if (ESP_OK == ESP_ERROR_CHECK_WITHOUT_ABORT(NeoEsp32RmtHiMethodDriver::WaitForTxDone(_channel.RmtChannelNumber, 10000 / portTICK_PERIOD_MS)))
+ {
+ // now start the RMT transmit with the editing buffer before we swap
+ ESP_ERROR_CHECK_WITHOUT_ABORT(NeoEsp32RmtHiMethodDriver::Write(_channel.RmtChannelNumber, _dataEditing, _sizeData));
+
+ if (maintainBufferConsistency)
+ {
+ // copy editing to sending,
+ // this maintains the contract that "colors present before will
+ // be the same after", otherwise GetPixelColor will be inconsistent
+ memcpy(_dataSending, _dataEditing, _sizeData);
+ }
+
+ // swap so the user can modify without affecting the async operation
+ std::swap(_dataSending, _dataEditing);
+ }
+ }
+
+ bool AlwaysUpdate()
+ {
+ // this method requires update to be called only if changes to buffer
+ return false;
+ }
+
+ bool SwapBuffers()
+ {
+ std::swap(_dataSending, _dataEditing);
+ return true;
+ }
+
+ uint8_t* getData() const
+ {
+ return _dataEditing;
+ };
+
+ size_t getDataSize() const
+ {
+ return _sizeData;
+ }
+
+ void applySettings([[maybe_unused]] const SettingsObject& settings)
+ {
+ }
+
+private:
+ const size_t _sizeData; // Size of '_data*' buffers
+ const uint8_t _pin; // output pin number
+ const T_CHANNEL _channel; // holds instance for multi channel support
+
+ // Holds data stream which include LED color values and other settings as needed
+ uint8_t* _dataEditing; // exposed for get and set
+ uint8_t* _dataSending; // used for async send using RMT
+
+
+ void construct()
+ {
+ _dataEditing = static_cast(malloc(_sizeData));
+ // data cleared later in Begin()
+
+ _dataSending = static_cast(malloc(_sizeData));
+ // no need to initialize it, it gets overwritten on every send
+ }
+};
+
+// normal
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2811Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2812xMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2816Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2805Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINSk6812Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTm1814Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTm1829Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTm1914Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINApa106Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTx1812Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINGs1903Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHIN800KbpsMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHIN400KbpsMethod;
+typedef NeoEsp32RmtHINWs2805Method NeoEsp32RmtHINWs2814Method;
+
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2811Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2812xMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2816Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2805Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Sk6812Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tm1814Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tm1829Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tm1914Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Apa106Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tx1812Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Gs1903Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0800KbpsMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0400KbpsMethod;
+typedef NeoEsp32RmtHI0Ws2805Method NeoEsp32RmtHI0Ws2814Method;
+
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2811Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2812xMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2816Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2805Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Sk6812Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tm1814Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tm1829Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tm1914Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Apa106Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tx1812Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Gs1903Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1800KbpsMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1400KbpsMethod;
+typedef NeoEsp32RmtHI1Ws2805Method NeoEsp32RmtHI1Ws2814Method;
+
+#if !defined(CONFIG_IDF_TARGET_ESP32C3)
+
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2811Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2812xMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2816Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2805Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Sk6812Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tm1814Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tm1829Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tm1914Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Apa106Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tx1812Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Gs1903Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2800KbpsMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2400KbpsMethod;
+typedef NeoEsp32RmtHI2Ws2805Method NeoEsp32RmtHI2Ws2814Method;
+
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2811Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2812xMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2816Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2805Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Sk6812Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tm1814Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tm1829Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tm1914Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Apa106Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tx1812Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Gs1903Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3800KbpsMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3400KbpsMethod;
+typedef NeoEsp32RmtHI3Ws2805Method NeoEsp32RmtHI3Ws2814Method;
+
+#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
+
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2811Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2812xMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2816Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2805Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Sk6812Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tm1814Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tm1829Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tm1914Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Apa106Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tx1812Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Gs1903Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4800KbpsMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4400KbpsMethod;
+typedef NeoEsp32RmtHI4Ws2805Method NeoEsp32RmtHI4Ws2814Method;
+
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2811Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2812xMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2816Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2805Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Sk6812Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tm1814Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tm1829Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tm1914Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Apa106Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tx1812Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Gs1903Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5800KbpsMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5400KbpsMethod;
+typedef NeoEsp32RmtHI5Ws2805Method NeoEsp32RmtHI5Ws2814Method;
+
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2811Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2812xMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2816Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2805Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Sk6812Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tm1814Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tm1829Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tm1914Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Apa106Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tx1812Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Gs1903Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6800KbpsMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6400KbpsMethod;
+typedef NeoEsp32RmtHI6Ws2805Method NeoEsp32RmtHI6Ws2814Method;
+
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2811Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2812xMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2816Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2805Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Sk6812Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Tm1814Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Tm1829Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Tm1914Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Apa106Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Tx1812Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Gs1903Method;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7800KbpsMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7400KbpsMethod;
+typedef NeoEsp32RmtHI7Ws2805Method NeoEsp32RmtHI7Ws2814Method;
+
+#endif // !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
+#endif // !defined(CONFIG_IDF_TARGET_ESP32C3)
+
+// inverted
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2811InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2812xInvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2816InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2805InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINSk6812InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTm1814InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTm1829InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTm1914InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINApa106InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTx1812InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINGs1903InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHIN800KbpsInvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHIN400KbpsInvertedMethod;
+typedef NeoEsp32RmtHINWs2805InvertedMethod NeoEsp32RmtHINWs2814InvertedMethod;
+
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2811InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2812xInvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2816InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2805InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Sk6812InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tm1814InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tm1829InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tm1914InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Apa106InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tx1812InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Gs1903InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0800KbpsInvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0400KbpsInvertedMethod;
+typedef NeoEsp32RmtHI0Ws2805InvertedMethod NeoEsp32RmtHI0Ws2814InvertedMethod;
+
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2811InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2812xInvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2816InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2805InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Sk6812InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tm1814InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tm1829InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tm1914InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Apa106InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tx1812InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Gs1903InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1800KbpsInvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1400KbpsInvertedMethod;
+typedef NeoEsp32RmtHI1Ws2805InvertedMethod NeoEsp32RmtHI1Ws2814InvertedMethod;
+
+#if !defined(CONFIG_IDF_TARGET_ESP32C3)
+
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2811InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2812xInvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2816InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2805InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Sk6812InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tm1814InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tm1829InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tm1914InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Apa106InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tx1812InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Gs1903InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2800KbpsInvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2400KbpsInvertedMethod;
+typedef NeoEsp32RmtHI2Ws2805InvertedMethod NeoEsp32RmtHI2Ws2814InvertedMethod;
+
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2811InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2812xInvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2805InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2816InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Sk6812InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tm1814InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tm1829InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tm1914InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Apa106InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tx1812InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Gs1903InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3800KbpsInvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3400KbpsInvertedMethod;
+typedef NeoEsp32RmtHI3Ws2805InvertedMethod NeoEsp32RmtHI3Ws2814InvertedMethod;
+
+#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
+
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2811InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2812xInvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2816InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2805InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Sk6812InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tm1814InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tm1829InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tm1914InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Apa106InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tx1812InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Gs1903InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4800KbpsInvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4400KbpsInvertedMethod;
+typedef NeoEsp32RmtHI4Ws2805InvertedMethod NeoEsp32RmtHI4Ws2814InvertedMethod;
+
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2811InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2812xInvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2816InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2805InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Sk6812InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tm1814InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tm1829InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tm1914InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Apa106InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tx1812InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Gs1903InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5800KbpsInvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5400KbpsInvertedMethod;
+typedef NeoEsp32RmtHI5Ws2805InvertedMethod NeoEsp32RmtHI5Ws2814InvertedMethod;
+
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2811InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2812xInvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2816InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2805InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Sk6812InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tm1814InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tm1829InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tm1914InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Apa106InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tx1812InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Gs1903InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6800KbpsInvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6400KbpsInvertedMethod;
+typedef NeoEsp32RmtHI6Ws2805InvertedMethod NeoEsp32RmtHI6Ws2814InvertedMethod;
+
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2811InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2812xInvertedMethod;
+typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2816InvertedMethod;
+typedef NeoEsp32RmtHIMethodBase